brane 0.0.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +362 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +91 -0
- package/DESIGN.md +402 -0
- package/Dockerfile +61 -0
- package/LICENSE +656 -17
- package/README.md +174 -2
- package/SECURITY.md +205 -0
- package/dist/absorb/brane-id.js +11 -0
- package/dist/absorb/chatgpt.js +161 -0
- package/dist/absorb/claude-code.js +504 -0
- package/dist/absorb/index.js +543 -0
- package/dist/absorb/organelle.js +345 -0
- package/dist/absorb/run.js +520 -0
- package/dist/absorb/sources.js +168 -0
- package/dist/absorb/types.js +3 -0
- package/dist/agent-env.js +45 -0
- package/dist/agents.js +1168 -0
- package/dist/claude-live-session.js +124 -0
- package/dist/cli.js +50 -0
- package/dist/codex-tool-log.js +144 -0
- package/dist/commands/absorb.js +291 -0
- package/dist/commands/attach.js +31 -0
- package/dist/commands/certify.js +62 -0
- package/dist/commands/config.js +116 -0
- package/dist/commands/cost.js +126 -0
- package/dist/commands/daemon.js +350 -0
- package/dist/commands/doctor.js +262 -0
- package/dist/commands/fold.js +95 -0
- package/dist/commands/list.js +89 -0
- package/dist/commands/mcp.js +84 -0
- package/dist/commands/meld.js +332 -0
- package/dist/commands/memory.js +215 -0
- package/dist/commands/revive.js +74 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/spawn.js +1094 -0
- package/dist/commands/token.js +153 -0
- package/dist/copilot-tool-log.js +145 -0
- package/dist/cost/pricing.js +116 -0
- package/dist/cost/reprice.js +136 -0
- package/dist/crush-tool-log.js +293 -0
- package/dist/daemon/agent-scope.js +116 -0
- package/dist/daemon/awaiting.js +54 -0
- package/dist/daemon/cert-manager.js +349 -0
- package/dist/daemon/client.js +262 -0
- package/dist/daemon/gated-routes.js +112 -0
- package/dist/daemon/keep-awake.js +103 -0
- package/dist/daemon/local-socket.js +145 -0
- package/dist/daemon/presence-gate.js +206 -0
- package/dist/daemon/server.js +6349 -0
- package/dist/daemon/signal-source.js +35 -0
- package/dist/daemon/tailscale-rebind.js +58 -0
- package/dist/daemon/token-ring.js +263 -0
- package/dist/daemon/token-routes.js +190 -0
- package/dist/db/events.js +152 -0
- package/dist/db/mentions.js +179 -0
- package/dist/db/schema.js +633 -0
- package/dist/db/tokens.js +583 -0
- package/dist/db/tool-uses.js +647 -0
- package/dist/diagram/index.js +85 -0
- package/dist/diagram/label.js +455 -0
- package/dist/diagram/layout.js +476 -0
- package/dist/diagram/links.js +50 -0
- package/dist/diagram/metrics.js +152 -0
- package/dist/diagram/parse.js +487 -0
- package/dist/diagram/svg.js +389 -0
- package/dist/diagram/types.js +9 -0
- package/dist/export.js +629 -0
- package/dist/files/archive-types.js +2 -0
- package/dist/files/archive.js +182 -0
- package/dist/files/browse.js +327 -0
- package/dist/files/bytes.js +162 -0
- package/dist/files/codecs.js +33 -0
- package/dist/files/find.js +212 -0
- package/dist/files/tar.js +223 -0
- package/dist/files/zip.js +209 -0
- package/dist/folds.js +72 -0
- package/dist/grammars/ebnf.js +92 -0
- package/dist/highlight.js +250 -0
- package/dist/ids.js +24 -0
- package/dist/markdown-diagnostics.js +233 -0
- package/dist/math.js +168 -0
- package/dist/mcp/server.js +886 -0
- package/dist/memory/embedder.js +145 -0
- package/dist/memory/embedding-worker.js +71 -0
- package/dist/memory/embeddings.js +91 -0
- package/dist/memory/index.js +232 -0
- package/dist/memory/storage.js +587 -0
- package/dist/memory/wiki.js +688 -0
- package/dist/memory/wordpiece.js +247 -0
- package/dist/mentions-scan.js +40 -0
- package/dist/presence.js +259 -0
- package/dist/session-affinity.js +185 -0
- package/dist/shared/api-request.js +124 -0
- package/dist/shared/block-glyphs.js +114 -0
- package/dist/shared/diagram-css.js +17 -0
- package/dist/shared/export-selection.js +101 -0
- package/dist/shared/find-in-text.js +110 -0
- package/dist/shared/glow-hues.js +225 -0
- package/dist/shared/hex-dump.js +62 -0
- package/dist/shared/hex-pages.js +52 -0
- package/dist/shared/hit-drum.js +79 -0
- package/dist/shared/known-nids.js +58 -0
- package/dist/shared/list-scroll-keys.js +56 -0
- package/dist/shared/markdown-lists.js +72 -0
- package/dist/shared/markdown-math.js +485 -0
- package/dist/shared/markdown-mentions.js +79 -0
- package/dist/shared/markdown-nids.js +41 -0
- package/dist/shared/markdown-protocols.js +53 -0
- package/dist/shared/markdown-refs.js +123 -0
- package/dist/shared/membrane-invasion.js +342 -0
- package/dist/shared/membrane-motion.js +83 -0
- package/dist/shared/membrane-palette.js +253 -0
- package/dist/shared/membrane-terrain.js +212 -0
- package/dist/shared/message-selection.js +123 -0
- package/dist/shared/mo-glyph.js +57 -0
- package/dist/shared/narrative-format.js +237 -0
- package/dist/shared/narrative-menu.js +82 -0
- package/dist/shared/narrative-sort.js +47 -0
- package/dist/shared/narrative-status.js +52 -0
- package/dist/shared/no-pan.js +85 -0
- package/dist/shared/pane-layout.js +50 -0
- package/dist/shared/payload-inline.js +83 -0
- package/dist/shared/speaker-color.js +61 -0
- package/dist/shared/token-rotation.js +14 -0
- package/dist/shared/view-url.js +197 -0
- package/dist/shared/wiki-links.js +70 -0
- package/dist/shared/wiki-window.js +4 -0
- package/dist/tailscale.js +55 -0
- package/dist/test/absorb-claude.test.js +892 -0
- package/dist/test/agent-scope.test.js +193 -0
- package/dist/test/agents.test.js +55 -0
- package/dist/test/api-request.test.js +122 -0
- package/dist/test/awaiting-glow.test.js +54 -0
- package/dist/test/awaiting.test.js +122 -0
- package/dist/test/bind-token.test.js +88 -0
- package/dist/test/block-glyphs.test.js +68 -0
- package/dist/test/cert-manager.test.js +262 -0
- package/dist/test/claude-live-session.test.js +110 -0
- package/dist/test/codex-tool-log.test.js +568 -0
- package/dist/test/config.test.js +71 -0
- package/dist/test/copilot-tool-log.test.js +93 -0
- package/dist/test/cors-preflight.test.js +49 -0
- package/dist/test/cost-linger.test.js +31 -0
- package/dist/test/cost-pricing.test.js +72 -0
- package/dist/test/cost-reprice.test.js +139 -0
- package/dist/test/cost-sessions.test.js +301 -0
- package/dist/test/crush-tool-log.test.js +289 -0
- package/dist/test/daemon-client.test.js +109 -0
- package/dist/test/daemon-socket.test.js +293 -0
- package/dist/test/diagram-arrows.test.js +205 -0
- package/dist/test/diagram-export.test.js +210 -0
- package/dist/test/diagram-labels.test.js +313 -0
- package/dist/test/diagram-parse.test.js +191 -0
- package/dist/test/diagram-render.test.js +175 -0
- package/dist/test/diagram-review.test.js +288 -0
- package/dist/test/diagram-self-loop-labels.test.js +303 -0
- package/dist/test/embedder.test.js +84 -0
- package/dist/test/events.test.js +123 -0
- package/dist/test/export-selection.test.js +102 -0
- package/dist/test/export.test.js +505 -0
- package/dist/test/files-browse.test.js +393 -0
- package/dist/test/files-find.test.js +272 -0
- package/dist/test/find-in-text.test.js +86 -0
- package/dist/test/first-paint-viewport.test.js +59 -0
- package/dist/test/fixtures/embedding-golden.js +23 -0
- package/dist/test/fixtures/wordpiece-golden.js +167 -0
- package/dist/test/glow-hues.test.js +202 -0
- package/dist/test/harness-cost.test.js +423 -0
- package/dist/test/helpers.js +21 -0
- package/dist/test/hex-pages.test.js +68 -0
- package/dist/test/highlight.test.js +69 -0
- package/dist/test/hit-drum.test.js +71 -0
- package/dist/test/hook-config.test.js +262 -0
- package/dist/test/json-gzip.test.js +70 -0
- package/dist/test/key-routing.test.js +58 -0
- package/dist/test/known-nids.test.js +106 -0
- package/dist/test/link-preview.test.js +67 -0
- package/dist/test/list-scroll-keys.test.js +66 -0
- package/dist/test/listen-own-words.test.js +147 -0
- package/dist/test/listen-timeout.test.js +33 -0
- package/dist/test/markdown-diagnostics.test.js +121 -0
- package/dist/test/markdown-lists.test.js +182 -0
- package/dist/test/markdown-math.test.js +256 -0
- package/dist/test/markdown-nids.test.js +146 -0
- package/dist/test/markdown-protocols.test.js +139 -0
- package/dist/test/mcp-bridge-cancel.test.js +98 -0
- package/dist/test/mcp-bridge-reconnect.test.js +121 -0
- package/dist/test/mcp-narrative.test.js +193 -0
- package/dist/test/mcp.test.js +548 -0
- package/dist/test/meld.test.js +85 -0
- package/dist/test/membrane-invasion.test.js +482 -0
- package/dist/test/membrane-motion.test.js +101 -0
- package/dist/test/membrane-palette.test.js +166 -0
- package/dist/test/membrane-terrain.test.js +170 -0
- package/dist/test/memory-log.test.js +202 -0
- package/dist/test/mentions-e2e.test.js +261 -0
- package/dist/test/mentions-ledger.test.js +265 -0
- package/dist/test/mentions-listen.test.js +265 -0
- package/dist/test/mentions.test.js +238 -0
- package/dist/test/message-selection.test.js +114 -0
- package/dist/test/mo-glyph.test.js +22 -0
- package/dist/test/model-resolution.test.js +166 -0
- package/dist/test/narrate-validate.test.js +77 -0
- package/dist/test/narrate-wait.test.js +160 -0
- package/dist/test/narrative-format.test.js +219 -0
- package/dist/test/narrative-menu.test.js +137 -0
- package/dist/test/narrative-sort.test.js +75 -0
- package/dist/test/no-pan.test.js +60 -0
- package/dist/test/pane-layout.test.js +153 -0
- package/dist/test/paths.test.js +37 -0
- package/dist/test/payload-inline.test.js +91 -0
- package/dist/test/precache-fonts.test.js +72 -0
- package/dist/test/presence-gate.test.js +440 -0
- package/dist/test/presence.test.js +160 -0
- package/dist/test/read-events.test.js +220 -0
- package/dist/test/remote-meld.test.js +115 -0
- package/dist/test/revive-cli.test.js +93 -0
- package/dist/test/search-floor.test.js +206 -0
- package/dist/test/search-fusion.test.js +168 -0
- package/dist/test/search.test.js +205 -0
- package/dist/test/security.test.js +48 -0
- package/dist/test/selection-wiring.test.js +61 -0
- package/dist/test/session-affinity.test.js +295 -0
- package/dist/test/signal-source.test.js +32 -0
- package/dist/test/socket-account.test.js +268 -0
- package/dist/test/spawn-toml.test.js +72 -0
- package/dist/test/speaker-color.test.js +90 -0
- package/dist/test/tailscale-rebind.test.js +79 -0
- package/dist/test/terminal-cursor.test.js +61 -0
- package/dist/test/terminal-resync.test.js +23 -0
- package/dist/test/thin-cli.test.js +382 -0
- package/dist/test/token-bootstrap.test.js +272 -0
- package/dist/test/token-devices.test.js +587 -0
- package/dist/test/token-lapse.test.js +94 -0
- package/dist/test/token-ring.test.js +188 -0
- package/dist/test/token-rotate-cli.test.js +58 -0
- package/dist/test/token-routes.test.js +95 -0
- package/dist/test/tool-uses.test.js +1006 -0
- package/dist/test/usage-ledger.test.js +126 -0
- package/dist/test/view-url.test.js +581 -0
- package/dist/test/wiki-links.test.js +57 -0
- package/dist/test/wiki-sources.test.js +85 -0
- package/dist/test/wiki-window.test.js +31 -0
- package/dist/test/wiki.test.js +399 -0
- package/dist/test/wordpiece.test.js +68 -0
- package/dist/tls.js +148 -0
- package/dist/tmux.js +202 -0
- package/dist/user-agent.js +46 -0
- package/docker-entrypoint.sh +141 -0
- package/docs/README.md +3 -0
- package/docs/absorb.md +231 -0
- package/docs/cost-ledger.md +125 -0
- package/docs/daemon-api.md +189 -0
- package/docs/diagrams.md +163 -0
- package/docs/doctor.md +36 -0
- package/docs/export-scope.md +69 -0
- package/docs/files-view.md +133 -0
- package/docs/link-previews.md +38 -0
- package/docs/math.md +32 -0
- package/docs/mcp.md +69 -0
- package/docs/memory-search.md +75 -0
- package/docs/mentions.md +101 -0
- package/docs/multi-brane.md +14 -0
- package/docs/passkeys.md +165 -0
- package/docs/presence.md +202 -0
- package/docs/releasing.md +53 -0
- package/docs/rendering-warnings.md +22 -0
- package/docs/request-layer.md +72 -0
- package/docs/selection-store.md +50 -0
- package/docs/session-affinity.md +61 -0
- package/docs/terminal-mirroring.md +211 -0
- package/docs/token-rotation.md +413 -0
- package/docs/view-urls.md +114 -0
- package/docs/web-app.md +23 -0
- package/docs/wiki.md +43 -0
- package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
- package/expo/dist/apple-touch-icon.png +0 -0
- package/expo/dist/favicon.svg +3 -0
- package/expo/dist/index.html +177 -0
- package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/expo/dist/katex/katex.min.css +1 -0
- package/expo/dist/manifest.json +16 -0
- package/expo/dist/metadata.json +1 -0
- package/expo/dist/og/membrane.v1.png +0 -0
- package/expo/dist/pdf.min.mjs +28 -0
- package/expo/dist/pdf.worker.min.mjs +28 -0
- package/expo/dist/sw.js +154 -0
- package/package.json +81 -23
- package/protocols/LIVE_loop.md +407 -0
- package/protocols/bebrief.md +41 -0
- package/protocols/digest.md +231 -0
- package/protocols/fly.md +266 -0
- package/protocols/idle.md +81 -0
- package/protocols/keeper.md +829 -0
- package/protocols/markdown.md +80 -0
- package/protocols/memory.md +419 -0
- package/protocols/mentions.md +93 -0
- package/protocols/payloads.md +53 -0
- package/protocols/prelude.md +91 -0
- package/protocols/ramify.md +105 -0
- package/protocols/review.md +356 -0
- package/protocols/rrr.md +232 -0
- package/protocols/scribe.md +429 -0
- package/protocols/summarize.md +203 -0
- package/protocols/twin.md +174 -0
- package/protocols/wiki.md +438 -0
- package/protocols/worker.md +38 -0
- package/wiki/shared/append-only-event-log.md +148 -0
- package/wiki/shared/index.md +48 -0
- package/wiki/shared/owning-your-narratives.md +161 -0
- package/wiki/shared/premature-ontologies.md +435 -0
- package/wiki/shared/sedimentary-superintelligence.md +128 -0
- package/wiki/shared/sequestering-the-narrative.md +143 -0
- package/wiki/shared/why-wikis-work.md +216 -0
- package/.npmignore +0 -2
- package/.travis.yml +0 -5
- package/index.js +0 -8
- package/index.js~ +0 -12
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/** Parser for the flowchart subset brane renders.
|
|
2
|
+
*
|
|
3
|
+
* Scope is empirical. Across every ```mermaid fence in the archive — 21
|
|
4
|
+
* diagrams, 316 lines, home brane plus the absorbed ChatGPT and organelle
|
|
5
|
+
* branes — the census was:
|
|
6
|
+
*
|
|
7
|
+
* headers flowchart TD 9 · graph TD 8 · flowchart LR 3 · stateDiagram-v2 1
|
|
8
|
+
* arrows --> 131 · <--> 9 · zero -.->, ==>, ---, ~~~
|
|
9
|
+
* shapes [rect] 125 · (round) 9 · [(cyl)] 5 · {diamond} 4 · >asym] 4 · [[sub]] 1
|
|
10
|
+
* also subgraph 10 · & fan-out 13 · <br/> 15 · classDef/class 3+3 · style 2 · %%
|
|
11
|
+
* absent click, linkStyle, and every diagram type but flowchart
|
|
12
|
+
*
|
|
13
|
+
* So: flowcharts, and everything else refuses. A refusal is not a failure —
|
|
14
|
+
* it renders as the highlighted code block a mermaid fence already produces,
|
|
15
|
+
* and `narrate` reports it so the author learns the diagram did not draw.
|
|
16
|
+
*
|
|
17
|
+
* The dotted and thick link forms are parsed although the census counted
|
|
18
|
+
* none: they cost one alternation here and one dash array in the renderer,
|
|
19
|
+
* and refusing them would be a worse first impression than drawing them.
|
|
20
|
+
* That turned out to be the right call for a reason better than taste — run
|
|
21
|
+
* against the archive, this parser found a `-. "…" .->` the census's
|
|
22
|
+
* arrow-shape grep had missed, because it only looked for bare arrows.
|
|
23
|
+
*
|
|
24
|
+
* Current standing against the archive: 20 of 21 draw. The one refusal is
|
|
25
|
+
* the `stateDiagram-v2`, which is a later increment rather than a defect.
|
|
26
|
+
*/
|
|
27
|
+
import { parseLabel, plainLabel, textLines } from "./label.js";
|
|
28
|
+
/** A header, with two tolerances mermaid has and the first draft did not:
|
|
29
|
+
* a trailing `;` (statements strip one, but the header was tested raw, so
|
|
30
|
+
* `graph TD;` — a common habit in the absorbed ChatGPT history — was
|
|
31
|
+
* refused), and an absent direction, which mermaid defaults to TB. */
|
|
32
|
+
const HEADER = /^\s*(flowchart|graph)(?:\s+(TB|TD|LR|RL|BT))?\s*;?\s*$/i;
|
|
33
|
+
/** Diagram types brane recognises by name, so the refusal can say which. */
|
|
34
|
+
const OTHER_TYPES = /^\s*(sequenceDiagram|stateDiagram(?:-v2)?|classDiagram|erDiagram|gantt|pie|gitGraph|journey|timeline|mindmap|quadrantChart|xychart(?:-beta)?|requirementDiagram|C4Context|sankey(?:-beta)?|block(?:-beta)?)\b/i;
|
|
35
|
+
/** Openers longest-first: `[[` must beat `[`, `((` must beat `(`. */
|
|
36
|
+
const SHAPES = [
|
|
37
|
+
["([", "])", "stadium"],
|
|
38
|
+
["[[", "]]", "subroutine"],
|
|
39
|
+
["[(", ")]", "cylinder"],
|
|
40
|
+
["((", "))", "circle"],
|
|
41
|
+
["{{", "}}", "hexagon"],
|
|
42
|
+
["[", "]", "rect"],
|
|
43
|
+
["(", ")", "round"],
|
|
44
|
+
["{", "}", "diamond"],
|
|
45
|
+
[">", "]", "asymmetric"],
|
|
46
|
+
];
|
|
47
|
+
/** The inline-label link forms: `A -- text --> B`, `A -. text .-> B`,
|
|
48
|
+
* `A == text ==> B`. Tried before the bare forms, and safe there because each
|
|
49
|
+
* requires whitespace where the bare form requires more dashes.
|
|
50
|
+
*
|
|
51
|
+
* The census found no `-- text -->`, which is why the spec left these out —
|
|
52
|
+
* but running the parser over the archive turned up a `-. "…" .->` in a
|
|
53
|
+
* lattice diagram, so the census undercounted rather than the form being
|
|
54
|
+
* unused.
|
|
55
|
+
*
|
|
56
|
+
* Two properties of the label group are load-bearing, and the obvious
|
|
57
|
+
* `\s+(.+?)\s+` has neither.
|
|
58
|
+
*
|
|
59
|
+
* **It cannot match whitespace at either end.** `\s+(.+?)\s+` makes the
|
|
60
|
+
* boundary between the padding and the label ambiguous at every position, and
|
|
61
|
+
* with three such quantifiers the cost is cubic in the run length: on
|
|
62
|
+
* `A -- <N spaces> x` this measured 17 ms at N=250, 30 ms at 500, 195 ms at
|
|
63
|
+
* 1000 and 1542 ms at 2000 — a clean 8× per doubling. `MAX_SOURCE_BYTES`
|
|
64
|
+
* is no defence, since it bounds *n* while the cost is n³. It mattered
|
|
65
|
+
* because `diagnoseMarkdown` runs on **every narration containing a mermaid
|
|
66
|
+
* fence**, so this was a daemon-wide stall reachable by narrating.
|
|
67
|
+
* `[^\s](?:[^]*?[^\s])?` anchors both ends on a non-space and removes the
|
|
68
|
+
* ambiguity outright.
|
|
69
|
+
*
|
|
70
|
+
* **It cannot contain the link's own punctuation.** `(.+?)` is lazy but will
|
|
71
|
+
* still cross a whole link to reach a later terminator, so `A -.-> B -.-> C`
|
|
72
|
+
* matched one edge `A → C` labelled `-> B -` and dropped B — a plausible,
|
|
73
|
+
* wrong diagram drawn in silence, which is worse than a refusal.
|
|
74
|
+
*
|
|
75
|
+
* What each form excludes differs, and the archive is why. A solid label
|
|
76
|
+
* cannot contain `>`, since that is what stops `A -- x --> B -- y --> C`
|
|
77
|
+
* spanning two links. A *dotted* label may — the lattice diagram's
|
|
78
|
+
* `-. "All<String, true> ⇒ ⊥" .->` needs it — so it excludes `.` instead,
|
|
79
|
+
* which is the dotted closer's own distinguishing character, and additionally
|
|
80
|
+
* forbids a leading `-` or `>`, which is what declines the `-.->` in
|
|
81
|
+
* `A -.-> B -.-> C` before the label ever begins.
|
|
82
|
+
*
|
|
83
|
+
* The cost is that a dotted label containing a literal `.` is not read. The
|
|
84
|
+
* archive has none, and the failure is a refusal — source plus a warning —
|
|
85
|
+
* rather than a wrong picture, which is the trade this whole module makes.
|
|
86
|
+
*
|
|
87
|
+
* **The opener is exactly two dashes**, as in mermaid, not `-{2,}`. Three is
|
|
88
|
+
* an open link, so a greedy opener read `A --- B --> C` as one edge `A → C`
|
|
89
|
+
* labelled `B`, losing a node in silence the same way. With `--`, the `-` in
|
|
90
|
+
* `--- B` cannot be the required whitespace and the form declines, leaving
|
|
91
|
+
* the bare `---` to match as the open link it is.
|
|
92
|
+
*/
|
|
93
|
+
const LABELLED_ARROWS = [
|
|
94
|
+
[/^<--[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+-{2,}>/, "biarrow", "solid"],
|
|
95
|
+
[/^-\.[ \t]*([^\s.>-][^.]*[^\s.]|[^\s.>-])[ \t]*\.-*->/, "arrow", "dotted"],
|
|
96
|
+
[/^==[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+={2,}>/, "arrow", "thick"],
|
|
97
|
+
[/^--[ \t]+([^\s>][^>]*[^\s>]|[^\s>])[ \t]+-{2,}>/, "arrow", "solid"],
|
|
98
|
+
];
|
|
99
|
+
/** A line longer than this is refused unparsed.
|
|
100
|
+
*
|
|
101
|
+
* Belt to the braces above: the patterns are linear now, but a parser on the
|
|
102
|
+
* `narrate` path should not depend on every future regex staying that way,
|
|
103
|
+
* and no diagram anyone reads has a 2,000-character line. */
|
|
104
|
+
const MAX_LINE_LENGTH = 2000;
|
|
105
|
+
const ARROWS = [
|
|
106
|
+
[/^<-{2,}>/, "biarrow", "solid"],
|
|
107
|
+
[/^<=+>/, "biarrow", "thick"],
|
|
108
|
+
[/^-\.-*->/, "arrow", "dotted"],
|
|
109
|
+
[/^={2,}>/, "arrow", "thick"],
|
|
110
|
+
[/^-{2,}>/, "arrow", "solid"],
|
|
111
|
+
[/^-{3,}/, "open", "solid"],
|
|
112
|
+
];
|
|
113
|
+
/** A node id. Hyphens are legal in one — `claude-0qjv4nny` is the shape brane
|
|
114
|
+
* cares about most — but a naive `[\w-]*` eats the arrow in `A-->B`, which
|
|
115
|
+
* agents write without spaces. Requiring a word character *after* each hyphen
|
|
116
|
+
* stops the id at the first `--` while still admitting the hyphenated form. */
|
|
117
|
+
const ID = /^[A-Za-z_]\w*(?:-[A-Za-z0-9_]+)*/;
|
|
118
|
+
/** Style properties worth honouring, and the only ones let through. An
|
|
119
|
+
* arbitrary CSS property from a fence is markup we did not write. */
|
|
120
|
+
const SAFE_STYLE = new Set([
|
|
121
|
+
"fill", "stroke", "stroke-width", "stroke-dasharray", "color",
|
|
122
|
+
// The weight and slant of a node's whole label. Dropped in silence before,
|
|
123
|
+
// which is how `classDef key font-weight:bold` drew nothing and said nothing
|
|
124
|
+
// (claude-cdouqlgt:24).
|
|
125
|
+
"font-weight", "font-style",
|
|
126
|
+
]);
|
|
127
|
+
/** Values are colours, lengths and keywords — never `url(...)`, never a
|
|
128
|
+
* semicolon that could close the declaration and open another. */
|
|
129
|
+
const SAFE_VALUE = /^[#\w\s.,%()-]{1,64}$/;
|
|
130
|
+
function parseStyleBody(body) {
|
|
131
|
+
const out = {};
|
|
132
|
+
for (const decl of body.split(/[,;]/)) {
|
|
133
|
+
const i = decl.indexOf(":");
|
|
134
|
+
if (i < 0)
|
|
135
|
+
continue;
|
|
136
|
+
const prop = decl.slice(0, i).trim().toLowerCase();
|
|
137
|
+
const value = decl.slice(i + 1).trim();
|
|
138
|
+
if (!SAFE_STYLE.has(prop))
|
|
139
|
+
continue;
|
|
140
|
+
if (!SAFE_VALUE.test(value) || /url\s*\(/i.test(value))
|
|
141
|
+
continue;
|
|
142
|
+
if (FONT_VALUES[prop] && !FONT_VALUES[prop].test(value))
|
|
143
|
+
continue;
|
|
144
|
+
out[prop] = value;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/** Values each font property may take. `SAFE_VALUE` alone would pass any
|
|
149
|
+
* word, and a word the SVG doesn't know is one the browser ignores — better
|
|
150
|
+
* dropped here, where the drop is the documented behaviour. */
|
|
151
|
+
const FONT_VALUES = {
|
|
152
|
+
"font-weight": /^(normal|bold|bolder|lighter|[1-9]00)$/i,
|
|
153
|
+
"font-style": /^(normal|italic|oblique)$/i,
|
|
154
|
+
};
|
|
155
|
+
const peek = (c) => c.s.slice(c.i);
|
|
156
|
+
function skipWs(c) { while (c.i < c.s.length && /\s/.test(c.s[c.i]))
|
|
157
|
+
c.i++; }
|
|
158
|
+
/** The end of a label that starts at `from`, given its closing delimiter.
|
|
159
|
+
*
|
|
160
|
+
* Quoting is why this is not `indexOf`. A quoted label may contain the
|
|
161
|
+
* delimiter that would otherwise end it, and the corpus does it twice: a node
|
|
162
|
+
* `ARR["Array[…]"]` whose text holds a `]`, and an edge label
|
|
163
|
+
* `-->|"P(C|A,B)"|` whose text holds a `|` — a conditional probability, where
|
|
164
|
+
* the bar is the notation. Scanning to the first delimiter truncates both and
|
|
165
|
+
* then fails on the remainder.
|
|
166
|
+
*
|
|
167
|
+
* Returns the index of the closer, or -1. */
|
|
168
|
+
function findClose(s, from, close) {
|
|
169
|
+
const q = s[from];
|
|
170
|
+
if (q === '"' || q === "'" || q === "`") {
|
|
171
|
+
const endQuote = s.indexOf(q, from + 1);
|
|
172
|
+
if (endQuote >= 0) {
|
|
173
|
+
const after = s.indexOf(close, endQuote + 1);
|
|
174
|
+
// Only the run between the quote and the closer may be whitespace; a
|
|
175
|
+
// quote that isn't actually wrapping the whole label falls through to
|
|
176
|
+
// the plain scan below.
|
|
177
|
+
if (after >= 0 && !s.slice(endQuote + 1, after).trim())
|
|
178
|
+
return after;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return s.indexOf(close, from);
|
|
182
|
+
}
|
|
183
|
+
function readNodeRef(c) {
|
|
184
|
+
skipWs(c);
|
|
185
|
+
const m = ID.exec(peek(c));
|
|
186
|
+
if (!m)
|
|
187
|
+
return null;
|
|
188
|
+
const id = m[0];
|
|
189
|
+
c.i += id.length;
|
|
190
|
+
let label;
|
|
191
|
+
let shape;
|
|
192
|
+
for (const [open, close, kind] of SHAPES) {
|
|
193
|
+
if (!peek(c).startsWith(open))
|
|
194
|
+
continue;
|
|
195
|
+
const end = findClose(c.s, c.i + open.length, close);
|
|
196
|
+
if (end < 0)
|
|
197
|
+
continue;
|
|
198
|
+
// Tables are a node's alone; a subgraph title read through here has them
|
|
199
|
+
// flattened below.
|
|
200
|
+
label = parseLabel(c.s.slice(c.i + open.length, end), { tables: true });
|
|
201
|
+
shape = kind;
|
|
202
|
+
c.i = end + close.length;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
// `A:::className` — the inline form of `class A className`.
|
|
206
|
+
const classes = [];
|
|
207
|
+
while (peek(c).startsWith(":::")) {
|
|
208
|
+
c.i += 3;
|
|
209
|
+
const cm = ID.exec(peek(c));
|
|
210
|
+
if (!cm)
|
|
211
|
+
break;
|
|
212
|
+
classes.push(cm[0]);
|
|
213
|
+
c.i += cm[0].length;
|
|
214
|
+
}
|
|
215
|
+
return { id, label, shape, classes: classes.length ? classes : undefined };
|
|
216
|
+
}
|
|
217
|
+
function readArrow(c) {
|
|
218
|
+
skipWs(c);
|
|
219
|
+
for (const [re, arrow, line] of LABELLED_ARROWS) {
|
|
220
|
+
const m = re.exec(peek(c));
|
|
221
|
+
if (!m)
|
|
222
|
+
continue;
|
|
223
|
+
c.i += m[0].length;
|
|
224
|
+
return { arrow, line, label: parseLabel(m[1]) };
|
|
225
|
+
}
|
|
226
|
+
for (const [re, arrow, line] of ARROWS) {
|
|
227
|
+
const m = re.exec(peek(c));
|
|
228
|
+
if (!m)
|
|
229
|
+
continue;
|
|
230
|
+
c.i += m[0].length;
|
|
231
|
+
let label;
|
|
232
|
+
if (peek(c).startsWith("|")) {
|
|
233
|
+
const end = findClose(c.s, c.i + 1, "|");
|
|
234
|
+
if (end < 0)
|
|
235
|
+
return null;
|
|
236
|
+
label = parseLabel(c.s.slice(c.i + 1, end));
|
|
237
|
+
c.i = end + 1;
|
|
238
|
+
}
|
|
239
|
+
return { arrow, line, label };
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
export function parseDiagram(source) {
|
|
244
|
+
const rawLines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
245
|
+
let direction = null;
|
|
246
|
+
let headerAt = -1;
|
|
247
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
248
|
+
const t = rawLines[i].trim();
|
|
249
|
+
if (!t || t.startsWith("%%"))
|
|
250
|
+
continue;
|
|
251
|
+
const h = HEADER.exec(t);
|
|
252
|
+
if (h) {
|
|
253
|
+
const d = (h[2] ?? "TB").toUpperCase();
|
|
254
|
+
direction = (d === "TD" ? "TB" : d);
|
|
255
|
+
headerAt = i;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const other = OTHER_TYPES.exec(t);
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
refusal: other
|
|
262
|
+
? { kind: "unsupported", message: `${other[1]} diagrams are not rendered yet`, line: i + 1 }
|
|
263
|
+
: { kind: "parse_error", message: `expected a \`flowchart\` or \`graph\` header, found \`${t.slice(0, 40)}\``, line: i + 1 },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (direction === null) {
|
|
267
|
+
return { ok: false, refusal: { kind: "parse_error", message: "no diagram header" } };
|
|
268
|
+
}
|
|
269
|
+
const nodes = new Map();
|
|
270
|
+
const edges = [];
|
|
271
|
+
const subgraphs = [];
|
|
272
|
+
const classDefs = {};
|
|
273
|
+
const stack = [];
|
|
274
|
+
const literal = [];
|
|
275
|
+
let lineNo = 0;
|
|
276
|
+
/** A label's literal-markup notes, attributed to the statement it is in. */
|
|
277
|
+
const noted = (label) => {
|
|
278
|
+
for (const message of label?.literal ?? [])
|
|
279
|
+
literal.push({ message, line: lineNo });
|
|
280
|
+
return label;
|
|
281
|
+
};
|
|
282
|
+
// A node belongs to the subgraph it is *mentioned inside*, whether or not
|
|
283
|
+
// that is where it first appeared. Claiming only on creation meant the
|
|
284
|
+
// common `subgraph S` / bare member-list form — where the nodes were already
|
|
285
|
+
// introduced by an edge above — produced an empty cluster, which dagre then
|
|
286
|
+
// sized as nothing and the renderer drew as a floating title.
|
|
287
|
+
const claimed = new Set();
|
|
288
|
+
const joinCurrent = (id) => {
|
|
289
|
+
if (!stack.length || claimed.has(id))
|
|
290
|
+
return;
|
|
291
|
+
stack[stack.length - 1].nodes.push(id);
|
|
292
|
+
claimed.add(id);
|
|
293
|
+
};
|
|
294
|
+
const touch = (id) => {
|
|
295
|
+
let n = nodes.get(id);
|
|
296
|
+
if (!n) {
|
|
297
|
+
const l = plainLabel(id);
|
|
298
|
+
n = { id, lines: l.lines, runs: l.runs, shape: "rect", classes: [] };
|
|
299
|
+
nodes.set(id, n);
|
|
300
|
+
}
|
|
301
|
+
joinCurrent(id);
|
|
302
|
+
return n;
|
|
303
|
+
};
|
|
304
|
+
const apply = (ref) => {
|
|
305
|
+
const n = touch(ref.id);
|
|
306
|
+
if (noted(ref.label)) {
|
|
307
|
+
n.lines = ref.label.lines;
|
|
308
|
+
n.runs = ref.label.runs;
|
|
309
|
+
}
|
|
310
|
+
if (ref.shape)
|
|
311
|
+
n.shape = ref.shape;
|
|
312
|
+
if (ref.classes)
|
|
313
|
+
n.classes.push(...ref.classes);
|
|
314
|
+
return n;
|
|
315
|
+
};
|
|
316
|
+
for (let i = headerAt + 1; i < rawLines.length; i++) {
|
|
317
|
+
lineNo = i + 1;
|
|
318
|
+
// A trailing `;` is optional statement punctuation in mermaid.
|
|
319
|
+
// `%%` opens a comment only at the start of a line — mermaid's own rule.
|
|
320
|
+
// Stripping it anywhere broke any label containing one, e.g. `A[100%% done]`.
|
|
321
|
+
let line = rawLines[i];
|
|
322
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
323
|
+
return { ok: false, refusal: { kind: "unsupported", message: `line is over ${MAX_LINE_LENGTH} characters`, line: lineNo } };
|
|
324
|
+
}
|
|
325
|
+
// A markdown string may span lines — its newlines are its line breaks —
|
|
326
|
+
// so a statement runs on until every ``"` `` it opened is closed. The
|
|
327
|
+
// joined statement is held to the same length bound as a line, which is
|
|
328
|
+
// what keeps the patterns below linear-bounded on it.
|
|
329
|
+
if (!/^\s*%%/.test(line)) {
|
|
330
|
+
while (opensMarkdownString(line)) {
|
|
331
|
+
if (++i >= rawLines.length) {
|
|
332
|
+
return { ok: false, refusal: { kind: "parse_error", message: "a markdown string (`\"`…`\"`) is never closed", line: lineNo } };
|
|
333
|
+
}
|
|
334
|
+
line += "\n" + rawLines[i];
|
|
335
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
336
|
+
return { ok: false, refusal: { kind: "unsupported", message: `statement is over ${MAX_LINE_LENGTH} characters`, line: lineNo } };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const text = (/^\s*%%/.test(line) ? "" : line).replace(/;\s*$/, "").trim();
|
|
341
|
+
if (!text)
|
|
342
|
+
continue;
|
|
343
|
+
if (/^end$/i.test(text)) {
|
|
344
|
+
if (!stack.length) {
|
|
345
|
+
return { ok: false, refusal: { kind: "parse_error", message: "`end` without `subgraph`", line: lineNo } };
|
|
346
|
+
}
|
|
347
|
+
subgraphs.push(stack.pop());
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
let m;
|
|
351
|
+
if ((m = /^subgraph\s+([\s\S]*)$/i.exec(text))) {
|
|
352
|
+
const rest = m[1].trim();
|
|
353
|
+
const ref = readNodeRef({ s: rest, i: 0 });
|
|
354
|
+
// `subgraph id[Title]`, `subgraph id`, or `subgraph "Free text"`.
|
|
355
|
+
const isIdForm = ref && ref.id.length > 0 && /^[A-Za-z_][\w-]*(\[|\(|\{|$)/.test(rest);
|
|
356
|
+
const id = isIdForm ? ref.id : `sg${subgraphs.length + stack.length}`;
|
|
357
|
+
// A title is drawn on one line, so a multi-line label joins with spaces.
|
|
358
|
+
const label = noted(isIdForm ? (ref.label ?? plainLabel(ref.id)) : parseLabel(rest));
|
|
359
|
+
const titleRuns = textLines(label.runs).flatMap((l, k) => (k ? [{ text: " " }, ...l] : l));
|
|
360
|
+
stack.push({ id, title: label.lines.join(" "), titleRuns, nodes: [] });
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (/^direction\s+/i.test(text))
|
|
364
|
+
continue; // per-subgraph direction: ignored, not refused
|
|
365
|
+
if ((m = /^classDef\s+([\w,-]+)\s+(.*)$/i.exec(text))) {
|
|
366
|
+
const decl = parseStyleBody(m[2]);
|
|
367
|
+
for (const name of m[1].split(","))
|
|
368
|
+
classDefs[name.trim()] = decl;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if ((m = /^class\s+([\w,\s-]+)\s+([\w-]+)$/i.exec(text))) {
|
|
372
|
+
for (const id of m[1].split(",")) {
|
|
373
|
+
const t = id.trim();
|
|
374
|
+
if (t)
|
|
375
|
+
touch(t).classes.push(m[2]);
|
|
376
|
+
}
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if ((m = /^style\s+([\w-]+)\s+(.*)$/i.exec(text))) {
|
|
380
|
+
const n = touch(m[1]);
|
|
381
|
+
n.style = { ...(n.style ?? {}), ...parseStyleBody(m[2]) };
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (/^(click|linkStyle|linkstyle)\b/i.test(text)) {
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
refusal: { kind: "unsupported", message: `\`${text.split(/\s/)[0]}\` is not supported`, line: lineNo },
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// Statement: a group of nodes, then zero or more (arrow, group) pairs.
|
|
391
|
+
const c = { s: text, i: 0 };
|
|
392
|
+
let group = [];
|
|
393
|
+
let ref = readNodeRef(c);
|
|
394
|
+
if (!ref) {
|
|
395
|
+
return { ok: false, refusal: { kind: "parse_error", message: `cannot read \`${text.slice(0, 40)}\``, line: lineNo } };
|
|
396
|
+
}
|
|
397
|
+
group.push(apply(ref));
|
|
398
|
+
skipWs(c);
|
|
399
|
+
while (peek(c).startsWith("&")) {
|
|
400
|
+
c.i += 1;
|
|
401
|
+
const r = readNodeRef(c);
|
|
402
|
+
if (!r) {
|
|
403
|
+
return { ok: false, refusal: { kind: "parse_error", message: "`&` with no node after it", line: lineNo } };
|
|
404
|
+
}
|
|
405
|
+
group.push(apply(r));
|
|
406
|
+
skipWs(c);
|
|
407
|
+
}
|
|
408
|
+
while (c.i < text.length) {
|
|
409
|
+
const arr = readArrow(c);
|
|
410
|
+
noted(arr?.label);
|
|
411
|
+
if (!arr) {
|
|
412
|
+
return { ok: false, refusal: { kind: "parse_error", message: `cannot read \`${peek(c).slice(0, 24)}\``, line: lineNo } };
|
|
413
|
+
}
|
|
414
|
+
const next = [];
|
|
415
|
+
const r0 = readNodeRef(c);
|
|
416
|
+
if (!r0) {
|
|
417
|
+
return { ok: false, refusal: { kind: "parse_error", message: "an arrow with no node after it", line: lineNo } };
|
|
418
|
+
}
|
|
419
|
+
next.push(apply(r0));
|
|
420
|
+
skipWs(c);
|
|
421
|
+
while (peek(c).startsWith("&")) {
|
|
422
|
+
c.i += 1;
|
|
423
|
+
const r = readNodeRef(c);
|
|
424
|
+
if (!r) {
|
|
425
|
+
return { ok: false, refusal: { kind: "parse_error", message: "`&` with no node after it", line: lineNo } };
|
|
426
|
+
}
|
|
427
|
+
next.push(apply(r));
|
|
428
|
+
skipWs(c);
|
|
429
|
+
}
|
|
430
|
+
// `A & B --> C & D` is the cross product, which is what mermaid does.
|
|
431
|
+
for (const a of group) {
|
|
432
|
+
for (const b of next) {
|
|
433
|
+
edges.push({
|
|
434
|
+
from: a.id, to: b.id,
|
|
435
|
+
label: arr.label?.lines.join(" "), labelRuns: arr.label && textLines(arr.label.runs),
|
|
436
|
+
arrow: arr.arrow, line: arr.line,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
group = next;
|
|
441
|
+
skipWs(c);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (stack.length) {
|
|
445
|
+
return { ok: false, refusal: { kind: "parse_error", message: "`subgraph` with no `end`" } };
|
|
446
|
+
}
|
|
447
|
+
// An edge to or from a subgraph id is legal mermaid and dagre cannot rank it
|
|
448
|
+
// — it throws, which `renderDiagram` caught and turned into a silent
|
|
449
|
+
// fallback: source shown, no warning, `strict: true` not refusing. Refusing
|
|
450
|
+
// here instead is what keeps "warns iff the operator sees source" true.
|
|
451
|
+
const clusterIds = new Set(subgraphs.map((sg) => sg.id));
|
|
452
|
+
for (const e of edges) {
|
|
453
|
+
for (const end of [e.from, e.to]) {
|
|
454
|
+
if (clusterIds.has(end)) {
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
refusal: {
|
|
458
|
+
kind: "unsupported",
|
|
459
|
+
message: `\`${end}\` is a subgraph, and an edge to a subgraph is not drawn yet`,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (nodes.size === 0) {
|
|
466
|
+
return { ok: false, refusal: { kind: "parse_error", message: "no nodes" } };
|
|
467
|
+
}
|
|
468
|
+
return { ok: true, ir: { direction, nodes: [...nodes.values()], edges, subgraphs, classDefs, literal } };
|
|
469
|
+
}
|
|
470
|
+
/** Whether `s` leaves a markdown string open: a ``"` `` with no ``` `" ```
|
|
471
|
+
* after it. One linear pass; a markdown string cannot contain either quote
|
|
472
|
+
* character, so the first closer is its closer. */
|
|
473
|
+
export function opensMarkdownString(s) {
|
|
474
|
+
let open = false;
|
|
475
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
476
|
+
if (!open && s[i] === '"' && s[i + 1] === "`") {
|
|
477
|
+
open = true;
|
|
478
|
+
i++;
|
|
479
|
+
}
|
|
480
|
+
else if (open && s[i] === "`" && s[i + 1] === '"') {
|
|
481
|
+
open = false;
|
|
482
|
+
i++;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return open;
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=parse.js.map
|