From b5c4530b59b81e9b57b03c01920bbc47c8a43d68 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 12:28:20 -0700 Subject: [PATCH 01/22] site: Forge redesign + CodeMirror-based daslang highlighting Lands the new "Forge" design across the daslang.io site and replaces three mismatched highlighters with one CodeMirror mode driven by the canonical lexer keyword set. Site: - Landing rewrite (forge.css/forge.js/index.html): Forge tokens, hero terminal panel with three live samples, dasProfile bench renderer, install tabs, news feed, ecosystem footer. - /playground/ skinned in Forge (forge-skin.css over web/ui's main.css). - Blog: 25 posts migrated from borisbat/dascf-blog with Hexo-style front matter, build_blog.py renders HTML + RSS + changelist. - /news/ from _news/*.md (27 entries, oldest 2019 -> newest 2026-05-09). - /downloads.html for binary releases. Highlighters unified: - Vendored CodeMirror 5.65.16 + simple-mode addon under site/files/cm/. - Single keyword module (daslang-keywords.js) sourced from ds2_lexer.lpp (105 reserved words); used by both daslang-mode.js (CodeMirror) and highlight.js (blog tokenizer). - cm-forge.css theme maps CM token classes to Forge tokens; replaces the eclipse theme + JS-heuristic mode previously used by /playground/. - Hero editor is now a CodeMirror instance (drops ~150 lines of contenteditable wiring); "playground" button trampolines current source into /playground/ via #code= URL hash. - Pygments daslang lexer in doc/source/daslang.py refreshed: removed move/move_new/nothing (no longer real), added capture/static_elif/template/const/default/typedecl/uninitialized. Sphinx docs reskinned via doc/source/_static/custom.css + forge-logo. CI (.github/workflows/pages.yml): builds WASM via Emscripten, copies site/files (including cm/ bundle), renders blog + news, snapshots dasProfile JSON, drops everything into the Pages artifact. --- .github/workflows/pages.yml | 55 +- .gitignore | 7 +- doc/source/_static/custom.css | 316 ++++ doc/source/_static/forge-logo.svg | 18 + doc/source/conf.py | 16 +- doc/source/daslang.py | 10 +- site/.gitignore | 30 + site/README.md | 253 +++ ...daslang-site-has-been-released-check-it.md | 5 + ...enchmarks-has-been-updated-all-lang-com.md | 5 + ...-lot-of-documentation-has-been-added-ti.md | 6 + site/_news/2020-09-05-version-0-2-released.md | 5 + site/_news/2021-11-18-version-0-3-released.md | 5 + ...2022-12-14-we-now-have-an-official-blog.md | 6 + ...aslang-now-happily-runs-in-the-web-brow.md | 6 + ...ersion-0-4-of-daslang-has-been-released.md | 5 + .../2024-07-01-dascript-renamed-to-daslang.md | 5 + ...version-0-5-is-coming-very-soon-it-will.md | 5 + ...assive-update-on-data-initialization-sy.md | 6 + ...ersion-0-5-of-daslang-has-been-released.md | 6 + ...overview-of-gen2-syntax-as-well-as-gen1.md | 6 + ...e-now-have-linq-like-capabilities-in-da.md | 6 + ...pre-releases-are-available-releases-are.md | 6 + ...ay-hello-to-templates-and-typemacros-in.md | 6 + ...ife-improvements-in-daslang-little-thin.md | 6 + ...we-are-getting-ready-for-the-release-of.md | 6 + ...aslang-0-6-is-just-around-the-corner-st.md | 6 + ...aslang-0-6-0-is-officially-released-che.md | 6 + .../2026-03-25-0-6-1-is-just-about-ready.md | 5 + ...astrudel-gets-sf2-soundfont-support-mid.md | 5 + site/_news/2026-04-04-0-6-1-is-out.md | 6 + ...astrudel-gets-module-reference-and-a-15.md | 6 + .../2026-05-01-0-6-2-is-just-about-ready.md | 6 + ...-08-0-6-2-rc2-is-out-its-getting-closer.md | 6 + ...0-6-2-rc3-is-out-post-rc2-polish-across.md | 6 + site/blog/_posts/a-matter-of-multipliction.md | 78 + site/blog/_posts/ai-friendly.md | 48 + site/blog/_posts/better-safe.md | 103 ++ site/blog/_posts/builtin-lock-check-md.md | 118 ++ site/blog/_posts/daslang-it-is.md | 54 + site/blog/_posts/data-initialization.md | 225 +++ site/blog/_posts/for-loop-noise.md | 270 ++++ site/blog/_posts/gc-in-the-wild.md | 107 ++ site/blog/_posts/gen2-syntax.md | 137 ++ site/blog/_posts/hectic.md | 67 + site/blog/_posts/how-to-edsl.md | 135 ++ site/blog/_posts/instruments.md | 120 ++ site/blog/_posts/its-not-a-script.md | 79 + site/blog/_posts/linq-that.md | 124 ++ site/blog/_posts/of-pipes-and-blocks.md | 96 ++ site/blog/_posts/pattern-matching.md | 73 + site/blog/_posts/performance-and-aliasing.md | 104 ++ site/blog/_posts/quality-of-life.md | 294 ++++ site/blog/_posts/running-it-live.md | 43 + site/blog/_posts/something-about-jit.md | 80 + site/blog/_posts/templates-what.md | 245 +++ site/blog/_posts/version-4.md | 104 ++ .../_posts/wake-up-and-test-the-damn-thing.md | 76 + site/blog/_posts/we-can-has-dasilb.md | 152 ++ site/blog/_posts/zero-point-five.md | 59 + site/blog/build_blog.py | 504 ++++++ site/blog/template.html | 81 + site/downloads.html | 203 +++ site/files/button.js | 1 - site/files/cm/cm-forge.css | 53 + site/files/cm/codemirror.min.css | 1 + site/files/cm/codemirror.min.js | 1 + site/files/cm/daslang-keywords.js | 48 + site/files/cm/daslang-mode.js | 81 + site/files/cm/simple-mode.js | 1 + site/files/forge-favicon.svg | 12 + site/files/forge.css | 845 ++++++++++ site/files/forge.js | 413 +++++ site/files/highlight.js | 111 ++ site/files/pixel.gif | Bin 43 -> 0 bytes site/files/runner.js | 98 ++ site/files/spacer.gif | Bin 49 -> 0 bytes site/files/style.css | 146 -- site/files/widgets.js | 8 - site/index.html | 1365 +++++------------ site/playground/forge-skin.css | 112 ++ site/playground/index.html | 103 ++ site/playground/playground-init.js | 39 + tools/seed_news.py | 180 +++ web/ui/src/main.js | 2 +- 85 files changed, 7082 insertions(+), 1175 deletions(-) create mode 100644 doc/source/_static/custom.css create mode 100644 doc/source/_static/forge-logo.svg create mode 100644 site/.gitignore create mode 100644 site/README.md create mode 100644 site/_news/2019-08-15-daslang-site-has-been-released-check-it.md create mode 100644 site/_news/2019-10-28-benchmarks-has-been-updated-all-lang-com.md create mode 100644 site/_news/2020-08-24-a-lot-of-documentation-has-been-added-ti.md create mode 100644 site/_news/2020-09-05-version-0-2-released.md create mode 100644 site/_news/2021-11-18-version-0-3-released.md create mode 100644 site/_news/2022-12-14-we-now-have-an-official-blog.md create mode 100644 site/_news/2023-01-01-daslang-now-happily-runs-in-the-web-brow.md create mode 100644 site/_news/2023-05-01-version-0-4-of-daslang-has-been-released.md create mode 100644 site/_news/2024-07-01-dascript-renamed-to-daslang.md create mode 100644 site/_news/2024-07-07-version-0-5-is-coming-very-soon-it-will.md create mode 100644 site/_news/2024-07-28-massive-update-on-data-initialization-sy.md create mode 100644 site/_news/2024-10-10-version-0-5-of-daslang-has-been-released.md create mode 100644 site/_news/2025-05-06-overview-of-gen2-syntax-as-well-as-gen1.md create mode 100644 site/_news/2025-06-10-we-now-have-linq-like-capabilities-in-da.md create mode 100644 site/_news/2025-12-30-pre-releases-are-available-releases-are.md create mode 100644 site/_news/2026-01-20-say-hello-to-templates-and-typemacros-in.md create mode 100644 site/_news/2026-01-22-life-improvements-in-daslang-little-thin.md create mode 100644 site/_news/2026-02-20-we-are-getting-ready-for-the-release-of.md create mode 100644 site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md create mode 100644 site/_news/2026-03-01-daslang-0-6-0-is-officially-released-che.md create mode 100644 site/_news/2026-03-25-0-6-1-is-just-about-ready.md create mode 100644 site/_news/2026-03-29-dastrudel-gets-sf2-soundfont-support-mid.md create mode 100644 site/_news/2026-04-04-0-6-1-is-out.md create mode 100644 site/_news/2026-04-17-dastrudel-gets-module-reference-and-a-15.md create mode 100644 site/_news/2026-05-01-0-6-2-is-just-about-ready.md create mode 100644 site/_news/2026-05-08-0-6-2-rc2-is-out-its-getting-closer.md create mode 100644 site/_news/2026-05-09-0-6-2-rc3-is-out-post-rc2-polish-across.md create mode 100644 site/blog/_posts/a-matter-of-multipliction.md create mode 100644 site/blog/_posts/ai-friendly.md create mode 100644 site/blog/_posts/better-safe.md create mode 100644 site/blog/_posts/builtin-lock-check-md.md create mode 100644 site/blog/_posts/daslang-it-is.md create mode 100644 site/blog/_posts/data-initialization.md create mode 100644 site/blog/_posts/for-loop-noise.md create mode 100644 site/blog/_posts/gc-in-the-wild.md create mode 100644 site/blog/_posts/gen2-syntax.md create mode 100644 site/blog/_posts/hectic.md create mode 100644 site/blog/_posts/how-to-edsl.md create mode 100644 site/blog/_posts/instruments.md create mode 100644 site/blog/_posts/its-not-a-script.md create mode 100644 site/blog/_posts/linq-that.md create mode 100644 site/blog/_posts/of-pipes-and-blocks.md create mode 100644 site/blog/_posts/pattern-matching.md create mode 100644 site/blog/_posts/performance-and-aliasing.md create mode 100644 site/blog/_posts/quality-of-life.md create mode 100644 site/blog/_posts/running-it-live.md create mode 100644 site/blog/_posts/something-about-jit.md create mode 100644 site/blog/_posts/templates-what.md create mode 100644 site/blog/_posts/version-4.md create mode 100644 site/blog/_posts/wake-up-and-test-the-damn-thing.md create mode 100644 site/blog/_posts/we-can-has-dasilb.md create mode 100644 site/blog/_posts/zero-point-five.md create mode 100644 site/blog/build_blog.py create mode 100644 site/blog/template.html create mode 100644 site/downloads.html delete mode 100644 site/files/button.js create mode 100644 site/files/cm/cm-forge.css create mode 100644 site/files/cm/codemirror.min.css create mode 100644 site/files/cm/codemirror.min.js create mode 100644 site/files/cm/daslang-keywords.js create mode 100644 site/files/cm/daslang-mode.js create mode 100644 site/files/cm/simple-mode.js create mode 100644 site/files/forge-favicon.svg create mode 100644 site/files/forge.css create mode 100644 site/files/forge.js create mode 100644 site/files/highlight.js delete mode 100644 site/files/pixel.gif create mode 100644 site/files/runner.js delete mode 100644 site/files/spacer.gif delete mode 100644 site/files/style.css delete mode 100644 site/files/widgets.js create mode 100644 site/playground/forge-skin.css create mode 100644 site/playground/index.html create mode 100644 site/playground/playground-init.js create mode 100644 tools/seed_news.py diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e1b9e6150a..57d84a0835 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -26,7 +26,9 @@ jobs: uses: actions/checkout@v4 - name: "Install Python dependencies" - run: pip install -r doc/requirements.txt + run: | + pip install -r doc/requirements.txt + pip install markdown - name: "Install CMake and Ninja" uses: lukka/get-cmake@latest @@ -71,6 +73,26 @@ jobs: working_directory: build/latex continue_on_error: true + - name: "Build WASM (Emscripten) for /playground/ + /files/wasm/" + run: | + set -eux + cd web + bash step0_emsdk_install.sh + # step1 sources emsdk_env.sh — re-export EMSDK for the configure step + bash -c "source emsdk/emsdk_env.sh && \ + mkdir -p build && cd build && \ + cmake -DCMAKE_BUILD_TYPE=Release -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ && \ + ninja" + continue-on-error: true + + - name: "Fetch dasProfile benchmark JSON" + run: | + set -eux + curl -sSL -o site/files/profile_results.json \ + https://raw.githubusercontent.com/borisbat/dasProfile/main/profile_results.json + continue-on-error: true + - name: "Stage site for deployment" run: | set -eux @@ -78,6 +100,7 @@ jobs: # Static landing page and assets cp site/index.html _site/ + cp site/downloads.html _site/ cp site/robots.txt _site/ cp -r site/files _site/files @@ -88,6 +111,36 @@ jobs: cp build/latex/daslang.pdf _site/doc/daslang.pdf || echo "WARNING: daslang.pdf not found" cp build/latex/daslangstdlib.pdf _site/doc/daslangstdlib.pdf || echo "WARNING: daslangstdlib.pdf not found" + # Blog + news + changelist (regenerated each publish) + python3 site/blog/build_blog.py \ + --posts site/blog/_posts \ + --news site/_news \ + --template site/blog/template.html \ + --out _site/ + + # Playground: vendor web/ui IDE + WASM artifacts + if [ -d web/output ]; then + mkdir -p _site/playground _site/files/wasm + # Copy IDE source files (CodeMirror, jQuery, main.js/css, etc.) + cp -r web/ui/src/* _site/playground/ + # Copy WASM artifacts + cp web/output/daslang_static.js _site/playground/ || true + cp web/output/daslang_static.wasm _site/playground/ || true + cp web/output/daslang_static.js _site/files/wasm/ || true + cp web/output/daslang_static.wasm _site/files/wasm/ || true + # Copy sample data if present + cp -r web/ui/samples _site/playground/samples 2>/dev/null || true + # Our Forge-skinned playground/index.html + skin CSS + init shim take precedence. + # CodeMirror bundle (codemirror.min.js/css, simple-mode.js, daslang-*.js, + # cm-forge.css) is served from /files/cm/ — copied via `cp -r site/files` + # earlier, so nothing extra to copy here. + cp site/playground/index.html _site/playground/index.html + cp site/playground/forge-skin.css _site/playground/ + cp site/playground/playground-init.js _site/playground/ + else + echo "WARNING: web/output not found — playground will be unavailable" + fi + # GitHub Pages control files touch _site/.nojekyll diff --git a/.gitignore b/.gitignore index e5ebf3baed..5b88bbc77c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,11 @@ mouse-data/*.db-wal mouse-data/*.db-shm modules/dasSFML/libsfml/ -site/ +# site/doc/ is the Sphinx HTML output deployed by .github/workflows/pages.yml. +# Source for site/* (forge.css, blog/_posts/*.md, _news/*.md, etc.) IS tracked; +# the build-time output (blog/*.html, news/*.html, files/wasm/, etc.) is +# ignored via site/.gitignore. +site/doc/ doc/sphinx-build/ doc/sphinx-build-latex/ doc/source/stdlib/generated/ @@ -58,6 +62,7 @@ web/output/daScript.wasm web/output/.idea/ yzg.code-workspace +daScript.code-workspace examples/wip.das examples/wip_macro.das diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css new file mode 100644 index 0000000000..238d5506a9 --- /dev/null +++ b/doc/source/_static/custom.css @@ -0,0 +1,316 @@ +/* ────────────────────────────────────────────────────────────────── + * Forge — daslang.io / docs (Sphinx + sphinx_rtd_theme retoken) + * + * Colors and fonts match site/files/forge.css. We only restyle — + * no template overrides. + * ────────────────────────────────────────────────────────────────── */ + +:root { + --bg: #0d0c0a; + --bg-2: #15130f; + --bg-3: #1c1a14; + --fg: #e8e2d2; + --fg-dim: #9b9281; + --fg-faint: #5b5547; + --rule: #2a2620; + --amber: #e8a13a; + --amber-dim: #a87420; + --green: #9bc46a; + --red: #d96d4f; + --blue: #6aa9c4; + --font-sans: "Inter Tight", "Inter", -apple-system, system-ui, sans-serif; + --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace; +} + +/* ─── Web font import ───────────────────────────────────────────── */ +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=JetBrains+Mono:wght@400;500;600;700&display=swap'); + +/* ─── Page-level ──────────────────────────────────────────────── */ +html, body, .wy-body-for-nav { + background: var(--bg) !important; +} +body, .wy-body-for-nav { + font-family: var(--font-sans) !important; + color: var(--fg) !important; +} + +/* ─── Sidebar ─────────────────────────────────────────────────── */ +.wy-nav-side { + background: var(--bg-2) !important; + border-right: 1px solid var(--rule); +} +.wy-side-nav-search { + background: var(--bg) !important; + border-bottom: 1px solid var(--rule); +} +.wy-side-nav-search input[type="text"] { + background: var(--bg-2) !important; + border: 1px solid var(--rule) !important; + color: var(--fg) !important; + font-family: var(--font-mono); + font-size: 12.5px; +} +.wy-side-nav-search > a, +.wy-side-nav-search .wy-dropdown > a { + color: var(--fg) !important; + font-family: var(--font-mono); +} +.wy-side-nav-search .version { + color: var(--fg-faint) !important; + font-family: var(--font-mono); +} +.wy-menu-vertical { + font-family: var(--font-sans); +} +.wy-menu-vertical p.caption, +.wy-menu-vertical .caption-text { + color: var(--fg-faint) !important; + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 1.3px; + text-transform: uppercase; +} +.wy-menu-vertical a { + color: var(--fg-dim) !important; + padding: 0.4045em 1.618em; +} +.wy-menu-vertical a:hover { + background: var(--bg-3) !important; + color: var(--fg) !important; +} +/* Expanded current-section subtree — RTD defaults to a light gray block + * that we must explicitly redarken at every nesting level. */ +.wy-menu-vertical li.current, +.wy-menu-vertical li.current > ul, +.wy-menu-vertical li.current ul, +.wy-menu-vertical li.toctree-l1.current, +.wy-menu-vertical li.toctree-l2.current, +.wy-menu-vertical li.toctree-l3.current, +.wy-menu-vertical li.toctree-l4.current, +.wy-menu-vertical li.toctree-l5.current { + background: var(--bg-2) !important; +} + +/* All sidebar links, every depth, regardless of state. */ +.wy-menu-vertical a, +.wy-menu-vertical li.current a, +.wy-menu-vertical li.current > a, +.wy-menu-vertical li.toctree-l1 a, +.wy-menu-vertical li.toctree-l2 a, +.wy-menu-vertical li.toctree-l3 a, +.wy-menu-vertical li.toctree-l4 a, +.wy-menu-vertical li.toctree-l5 a, +.wy-menu-vertical li.toctree-l1.current > a, +.wy-menu-vertical li.toctree-l2.current > a, +.wy-menu-vertical li.toctree-l3.current > a { + background: transparent !important; + color: var(--fg-dim) !important; + border-right: none !important; + border-left: none !important; + border-top: none !important; + border-bottom: none !important; +} + +/* Active (current page) link gets the amber left-marker. */ +.wy-menu-vertical li.current > a, +.wy-menu-vertical li.toctree-l1.current > a:first-child { + background: var(--bg) !important; + color: var(--fg) !important; + border-left: 2px solid var(--amber) !important; +} + +/* Hover state across all levels. */ +.wy-menu-vertical a:hover, +.wy-menu-vertical li.current a:hover { + background: var(--bg-3) !important; + color: var(--fg) !important; +} + +/* ─── Top breadcrumb / nav bar ───────────────────────────────── */ +.wy-nav-top { + background: var(--bg) !important; + border-bottom: 1px solid var(--rule); +} +.wy-nav-top a { + color: var(--fg) !important; + font-family: var(--font-mono); +} +.wy-nav-content-wrap { + background: var(--bg) !important; +} +.wy-nav-content { + background: var(--bg) !important; + max-width: 920px; +} + +/* ─── Body / RST content ─────────────────────────────────────── */ +.rst-content { + color: var(--fg) !important; + font-family: var(--font-sans); +} +.rst-content h1, .rst-content h2, .rst-content h3, +.rst-content h4, .rst-content h5, .rst-content h6 { + color: var(--fg) !important; + font-family: var(--font-sans); + font-weight: 600; + letter-spacing: -0.015em; + border-bottom: none !important; +} +.rst-content h1 { font-size: 2.4em; letter-spacing: -0.02em; } +.rst-content h2 { font-size: 1.7em; margin-top: 1.6em; } +.rst-content h3 { font-size: 1.3em; margin-top: 1.4em; } +.rst-content p, .rst-content li { + color: var(--fg-dim) !important; + line-height: 1.65; +} +.rst-content strong, .rst-content b { color: var(--fg) !important; } + +/* Links */ +a, a:visited { color: var(--amber) !important; } +a:hover { color: var(--fg) !important; border-bottom-color: var(--fg) !important; } +.rst-content a, .rst-content a:visited { + color: var(--amber) !important; + text-decoration: none; + border-bottom: 1px solid var(--amber-dim); +} +.rst-content a:hover { + color: var(--fg) !important; + border-bottom-color: var(--fg); +} + +/* Inline code */ +.rst-content code.literal, +.rst-content tt.literal, +.rst-content code.docutils.literal { + background: var(--bg-2) !important; + border: 1px solid var(--rule) !important; + color: var(--amber) !important; + font-family: var(--font-mono) !important; + font-size: 0.85em; + padding: 1px 5px; + border-radius: 3px; +} + +/* Code blocks (pygments output kept; just retoken the chrome) */ +.rst-content pre, +.rst-content div[class^="highlight"] { + background: var(--bg-2) !important; + border: 1px solid var(--rule) !important; + border-radius: 8px !important; +} +.rst-content pre { + color: var(--fg); + font-family: var(--font-mono) !important; + font-size: 13px; + line-height: 1.65; + padding: 14px 16px; +} +.rst-content div[class^="highlight"] pre { + background: transparent !important; + border: none !important; + padding: 14px 16px; +} + +/* Tables */ +.rst-content table.docutils, +.rst-content table.field-list, +.wy-table-responsive table { + background: transparent !important; + border: 1px solid var(--rule) !important; +} +.rst-content table.docutils thead, +.wy-table-responsive table thead { + background: var(--bg-2) !important; + color: var(--fg); +} +.rst-content table.docutils th, +.rst-content table.docutils td, +.wy-table-responsive table th, +.wy-table-responsive table td { + background: transparent !important; + border: 1px solid var(--rule) !important; + color: var(--fg-dim) !important; +} +.rst-content table.docutils tbody tr:nth-child(odd) td, +.wy-table-responsive table tbody tr:nth-child(odd) td { + background: rgba(255, 255, 255, 0.015) !important; +} + +/* Admonitions (note / warning / etc.) */ +.rst-content .admonition { + background: var(--bg-2) !important; + border: 1px solid var(--rule) !important; + border-left: 2px solid var(--blue) !important; + border-radius: 4px; + color: var(--fg-dim); +} +.rst-content .admonition.warning, +.rst-content .admonition.attention, +.rst-content .admonition.caution { + border-left-color: var(--amber) !important; +} +.rst-content .admonition.error, +.rst-content .admonition.danger { + border-left-color: var(--red) !important; +} +.rst-content .admonition-title { + background: transparent !important; + color: var(--fg) !important; + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 1.2px; + text-transform: uppercase; +} + +/* Domain signatures (function/class/etc. directives) */ +.rst-content dl.py dt, +.rst-content dl.c dt, +.rst-content dl.cpp dt, +.rst-content dl.rst dt { + background: var(--bg-2) !important; + border: 1px solid var(--rule) !important; + border-radius: 4px; + color: var(--fg); + font-family: var(--font-mono); +} + +/* Footer */ +.rst-footer-buttons .btn { + background: transparent !important; + color: var(--fg) !important; + border: 1px solid var(--rule) !important; + font-family: var(--font-mono); + border-radius: 4px; +} +.rst-footer-buttons .btn:hover { + background: var(--bg-2) !important; + border-color: var(--amber-dim) !important; +} +footer { + color: var(--fg-faint) !important; +} +footer a { color: var(--fg-dim) !important; } + +/* Search results */ +.search { color: var(--fg) !important; } +.search li a { color: var(--fg) !important; } + +/* Pygments token colors — mirror the forge.css mapping so Sphinx-rendered + * code blocks share the landing's palette. */ +.highlight { background: transparent !important; color: var(--fg); } +.highlight .k, .highlight .kd, .highlight .kr, +.highlight .kn, .highlight .kp, .highlight .kt { color: var(--amber); } +.highlight .nb, .highlight .nc, .highlight .nt, +.highlight .nf { color: var(--blue); } +.highlight .s, .highlight .s1, .highlight .s2, +.highlight .sb, .highlight .sc, .highlight .sd, +.highlight .se, .highlight .si, .highlight .sx, +.highlight .sr, .highlight .ss { color: var(--green); } +.highlight .m, .highlight .mi, .highlight .mf, +.highlight .mh, .highlight .mo { color: #d8a8d8; } +.highlight .c, .highlight .c1, .highlight .cm, +.highlight .cs, .highlight .cp { color: var(--fg-faint); font-style: italic; } +.highlight .na, .highlight .nd { color: var(--red); } +.highlight .n { color: var(--fg); } +.highlight .o, .highlight .p { color: var(--fg-dim); } +.highlight .err { color: var(--red); background: transparent; border: none; } diff --git a/doc/source/_static/forge-logo.svg b/doc/source/_static/forge-logo.svg new file mode 100644 index 0000000000..b7c8e8dd62 --- /dev/null +++ b/doc/source/_static/forge-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + > + daslang + .io + diff --git a/doc/source/conf.py b/doc/source/conf.py index 71cd93b808..5ccb17bfb8 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -123,7 +123,13 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +html_theme_options = { + 'style_nav_header_background': '#0d0c0a', + 'collapse_navigation': False, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'titles_only': False, +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] @@ -137,7 +143,7 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'daslang.png' +html_logo = '_static/forge-logo.svg' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -147,7 +153,11 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] +html_static_path = ['_static'] + +# Custom CSS — Forge token overlay on top of sphinx_rtd_theme. +# See doc/source/_static/custom.css for the retoken rules. +html_css_files = ['custom.css'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/doc/source/daslang.py b/doc/source/daslang.py index 32c9910876..0f163e812a 100644 --- a/doc/source/daslang.py +++ b/doc/source/daslang.py @@ -480,13 +480,13 @@ class DaslangLexer(RegexLexer): (r'0[xX][0-9a-fA-F_]+[uU]?[lL]?', Number.Hex), (r'[0-9][0-9_]*\.[0-9_]*([eE][+-]?[0-9_]+)?[fFlL]?', Number.Float), (r'[0-9][0-9_]*[uU]?[lL]?', Number.Integer), - # Keywords + # Keywords (sourced from src/parser/ds2_lexer.lpp) (words(( 'struct', 'class', 'let', 'var', 'def', 'while', 'if', 'static_if', - 'else', 'elif', 'for', 'finally', 'in', 'is', 'as', + 'else', 'elif', 'static_elif', 'for', 'finally', 'in', 'is', 'as', 'where', 'return', 'yield', 'break', 'continue', 'pass', 'try', 'recover', 'delete', 'deref', - 'new', 'typeinfo', 'type', 'array', 'table', + 'new', 'typeinfo', 'type', 'typedecl', 'array', 'table', 'block', 'function', 'lambda', 'generator', 'expect', 'override', 'abstract', 'sealed', 'require', 'module', 'public', 'private', @@ -495,10 +495,10 @@ class DaslangLexer(RegexLexer): 'assume', 'unsafe', 'addr', 'label', 'goto', 'implicit', 'explicit', 'shared', 'smart_ptr', 'inscope', 'static', 'fixed_array', 'iterator', 'bitfield', - 'move_new', 'move', + 'capture', 'template', 'const', 'default', 'uninitialized', ), prefix=r'\b', suffix=r'\b'), Keyword), # Boolean / null - (words(('true', 'false', 'null', 'nothing'), prefix=r'\b', suffix=r'\b'), Keyword.Constant), + (words(('true', 'false', 'null'), prefix=r'\b', suffix=r'\b'), Keyword.Constant), # Built-in types (words(( 'void', 'bool', 'string', 'auto', diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000000..7366bba9a1 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,30 @@ +# Generated by site/blog/build_blog.py. Re-runnable from sources: +# blog/_posts/*.md, blog/template.html, blog/build_blog.py, _news/*.md +# CI emits the same output via .github/workflows/pages.yml. +blog/*.html +!blog/template.html +blog/feed.xml +changelist.html +news/ +files/news.json + +# Playground: vendored from web/ui/src/ for local-dev preview only. +# CI rebuilds these from source on every publish. To preview locally: +# cp web/ui/src/* site/playground/ +# cp web/output/daslang_static.{js,wasm} site/playground/ +playground/codemirror.css +playground/codemirror.min.js +playground/eclipse.css +playground/jquery-3.6.0.min.js +playground/main.css +playground/main.js +playground/samples/ +playground/daslang_static.js +playground/daslang_static.wasm + +# WASM artifact built by pages.yml from web/. +files/wasm/ + +# dasProfile snapshot fetched fresh by pages.yml from +# https://raw.githubusercontent.com/borisbat/dasProfile/main/profile_results.json +files/profile_results.json diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000000..6ff95d8d1a --- /dev/null +++ b/site/README.md @@ -0,0 +1,253 @@ +# daslang.io — local development + +The site published at [dascript.org](https://dascript.org) and deployed by +[`.github/workflows/pages.yml`](../.github/workflows/pages.yml). This README is +the canonical guide for running each surface locally — `pages.yml` references +the same commands and treats this file as the source of truth. + +## What's in `site/` + +``` +site/ +├── index.html # landing — hand-authored +├── downloads.html # downloads & links — hand-authored +├── files/ +│ ├── forge.css # tokens + components (source of truth) +│ ├── forge.js # sample switcher · install tabs · bench · news +│ ├── highlight.js # daslang tokenizer (hero + blog code blocks) +│ ├── runner.js # mini-playground WASM shim +│ ├── forge-favicon.svg +│ ├── profile_results.json # fetched by CI from borisbat/dasProfile (gitignored) +│ ├── news.json # generated by build_blog.py (gitignored) +│ └── wasm/ # daslang_static.{js,wasm} from CI (gitignored) +├── blog/ +│ ├── _posts/*.md # 25 posts (source of truth — Markdown w/ Hexo extensions) +│ ├── template.html # Forge chrome (used by build_blog.py for posts + index) +│ ├── build_blog.py # generates blog/, news/, changelist.html, news.json +│ ├── index.html # GENERATED — post listing +│ ├── .html # GENERATED — one per post +│ └── feed.xml # GENERATED — Atom feed +├── _news/*.md # short news entries (one .md per item, body optional) +├── changelist.html # GENERATED by build_blog.py — full news list +├── news/.html # GENERATED — micro-pages for news entries with bodies +├── playground/ +│ ├── index.html # Forge nav + IDE body (overlay for web/ui) +│ ├── forge-skin.css # CSS overrides on web/ui/src/main.css +│ ├── cm-forge.css # CodeMirror dark theme (replaces eclipse.css colors) +│ └── *.{js,css} # vendored from web/ui/src/ for local-dev (gitignored) +└── doc/ # Sphinx HTML output (gitignored, deployed by CI) +``` + +Sources (`forge.css`, `*.md`, `*.py`, `template.html`, `*.html` hand-authored) +are committed. Build artifacts (`blog/.html`, `news/`, `changelist.html`, +`files/wasm/`, `files/news.json`, `files/profile_results.json`, `doc/`) are +generated and gitignored — see [`site/.gitignore`](.gitignore). + +## Quick start (landing only) + +```bash +cd site && python3 -m http.server 8000 +open http://localhost:8000/ +``` + +The landing page works on its own (no build step). Open DevTools — the +benchmarks section, install-tab toggle, and hero sample switcher should all +work without errors. The hero `▶ run` button is degraded until you build the +WASM artifact (see below). + +## Building the blog + news + changelist + +```bash +pip install --user markdown # one time +python3 site/blog/build_blog.py \ + --posts site/blog/_posts \ + --news site/_news \ + --template site/blog/template.html \ + --out site/ +``` + +This regenerates: +- `site/blog/index.html`, `site/blog/.html` for all 25 posts +- `site/blog/feed.xml` (Atom) +- `site/changelist.html` (full news list) +- `site/news/.html` for news entries with bodies +- `site/files/news.json` (top 8 for landing § 05) + +Then re-run the http.server. Refresh. + +## Adding a new blog post + +```bash +# 1. Create a Markdown file under site/blog/_posts/.md with front matter: +cat > site/blog/_posts/.md <<'EOF' +--- +title: Your post title +date: 2026-05-13 14:30:00 +tags: + - daslang + - performance +--- +Body in Markdown. Use ```daslang fenced blocks for code. +Content after the marker only shows on the post page, not on the index. + +Cross-link another post: {% post_link other-slug %} +EOF + +# 2. Rebuild (see above) +python3 site/blog/build_blog.py --posts site/blog/_posts --news site/_news \ + --template site/blog/template.html --out site/ + +# 3. Push — pages.yml rebuilds and deploys. +``` + +## Adding a short news entry (one-liner on the landing) + +```bash +# Create a file under site/_news/.md (no body for a one-liner): +cat > site/_news/.md <<'EOF' +--- +date: 2026-05-13 +tag: 0.6.2 +title: One-line description +link: https://github.com/.../releases/tag/v0.6.2 +--- +EOF + +# Rebuild — same command as the blog. The entry lands in the top-5 § 05 feed +# on the landing AND in the full /changelist.html. If you add body content +# after the closing ---, build_blog.py also emits /news/.html. +``` + +## Sphinx docs (retokened to Forge) + +```bash +# Local sphinx-build inside the project's venv. +sphinx-build -W --keep-going -b html doc/source build/site +open build/site/index.html +``` + +(Memory note: on Boris's machine, an alternative path is +`/Users/borisbatkin/Library/Python/3.11/bin/sphinx-build`, version 7.2.6.) + +Verify: dark `#0d0c0a` background, amber `#e8a13a` accent on links and the +active sidebar item, JetBrains Mono code blocks, the `>` typographic logo in +the top-left. Build must complete with `-W` (zero warnings). + +## Mini playground (hero "▶ run") + +The mini playground needs the WASM artifact at `site/files/wasm/`. Build it +once (then `pip install`-free; subsequent `ninja` rebuilds are fast): + +```bash +cd web/ +bash step0_emsdk_install.sh # ~1 GB into web/emsdk/ +source emsdk/emsdk_env.sh # activate for this shell +mkdir -p build && cd build +cmake -DCMAKE_BUILD_TYPE=Release -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ +ninja # 5–15 min first build + +# Drop the artifact where the mini runner expects it: +mkdir -p ../../site/files/wasm +cp ../output/daslang_static.{js,wasm} ../../site/files/wasm/ +``` + +Re-run the http.server. The hero `▶ run` button should now compile and run +the sample. Edits to the code block re-tokenize on input; click `▶ run` to +re-execute. + +## Full playground (`/playground/`) + +The full IDE is `web/ui/` (CodeMirror, samples picker, multi-file). For local +preview, vendor its source files into `site/playground/`: + +```bash +cp web/ui/src/* site/playground/ +# Forge skin auto-loads from site/playground/forge-skin.css + cm-forge.css. +``` + +Plus the WASM artifact (same as the mini playground): + +```bash +cp web/output/daslang_static.{js,wasm} site/playground/ +``` + +Open `http://localhost:8000/playground/`. Forge nav at the top, CodeMirror +editor with Forge tokens (amber keywords, blue types, green strings, faint +comments), output panel below. + +## Full `_site` preview (everything stitched together) + +Replicate `pages.yml` end-to-end locally: + +```bash +mkdir -p _site +cp site/*.html _site/ +cp site/robots.txt _site/ +cp -r site/files _site/files + +# Blog +python3 site/blog/build_blog.py \ + --posts site/blog/_posts \ + --news site/_news \ + --template site/blog/template.html \ + --out _site/ + +# Playground (assumes web/ has been built — see above) +mkdir -p _site/playground _site/files/wasm +cp -r web/ui/src/* _site/playground/ +cp web/output/daslang_static.{js,wasm} _site/playground/ +cp web/output/daslang_static.{js,wasm} _site/files/wasm/ +cp site/playground/index.html _site/playground/index.html +cp site/playground/{forge-skin,cm-forge}.css _site/playground/ + +# Sphinx (requires daslang binary for das2rst — see doc/ build instructions) +sphinx-build -b html doc/source _site/doc + +cd _site && python3 -m http.server 8000 +``` + +Click through: landing → blog → a post → docs (Sphinx) → playground → run a +sample. No broken links, consistent Forge chrome on every page. + +## Updating dasProfile snapshot + +The benchmark numbers come from +[borisbat/dasProfile/profile_results.json](https://github.com/borisbat/dasProfile/blob/main/profile_results.json). +CI fetches the latest on every publish. Local dev: + +```bash +curl -sSL -o site/files/profile_results.json \ + https://raw.githubusercontent.com/borisbat/dasProfile/main/profile_results.json +``` + +The benchmarks chart on the landing reads it directly — no rebuild needed. + +## Common gotchas + +- **Hero `▶ run` shows "WASM runtime not deployed"** — you haven't built the + WASM artifact yet. Either follow "Mini playground" above, or just preview + the static landing without it (the run button is the only thing that won't + work). +- **`/playground/` shows a blank page** — you forgot to `cp web/ui/src/* + site/playground/`. The CodeMirror + jQuery + `main.js` are needed. +- **Blog code blocks aren't syntax-highlighted** — make sure + `site/files/highlight.js` is loaded. The blog template includes it; if you + built with `--out /tmp/foo/` and serve from there, your asset paths may + diverge from the `../files/` prefix the template uses. +- **`/changelist.html` is empty** — re-run `build_blog.py`; it generates the + changelist from `site/_news/*.md`. + +## How CI differs from local dev + +The CI flow in [`.github/workflows/pages.yml`](../.github/workflows/pages.yml) +mirrors the steps above. Differences worth knowing: + +- CI installs `emsdk` fresh every run (cached across runs by GitHub). +- CI fetches `profile_results.json` on each publish. +- CI builds Sphinx with `-W` (treats warnings as errors). Local builds should + do the same before pushing. +- CI uploads to GitHub Pages via `actions/deploy-pages@v4`. + +If something works locally but fails on CI, walk through the workflow file +side-by-side with this README — the commands should match. diff --git a/site/_news/2019-08-15-daslang-site-has-been-released-check-it.md b/site/_news/2019-08-15-daslang-site-has-been-released-check-it.md new file mode 100644 index 0000000000..c7ad17c8d4 --- /dev/null +++ b/site/_news/2019-08-15-daslang-site-has-been-released-check-it.md @@ -0,0 +1,5 @@ +--- +date: 2019-08-15 +tag: docs +title: Daslang site has been released, check it out. +--- diff --git a/site/_news/2019-10-28-benchmarks-has-been-updated-all-lang-com.md b/site/_news/2019-10-28-benchmarks-has-been-updated-all-lang-com.md new file mode 100644 index 0000000000..b3efee74a5 --- /dev/null +++ b/site/_news/2019-10-28-benchmarks-has-been-updated-all-lang-com.md @@ -0,0 +1,5 @@ +--- +date: 2019-10-28 +tag: news +title: Benchmarks has been updated. All lang compilers were built in LLVM8.0, architecture AVX. +--- diff --git a/site/_news/2020-08-24-a-lot-of-documentation-has-been-added-ti.md b/site/_news/2020-08-24-a-lot-of-documentation-has-been-added-ti.md new file mode 100644 index 0000000000..7e4af20aaf --- /dev/null +++ b/site/_news/2020-08-24-a-lot-of-documentation-has-been-added-ti.md @@ -0,0 +1,6 @@ +--- +date: 2020-08-24 +tag: docs +title: A lot of documentation has been added. tio.run sandbox added. +link: https://tiorun.gaijin.team/##S0ksTi7KLCj5/z86taIgv6gklislNU0hNzEzj0sBCAqKMvNKNJQ8UnNy8nUUwvOLclIUlTS5/v8HAA +--- diff --git a/site/_news/2020-09-05-version-0-2-released.md b/site/_news/2020-09-05-version-0-2-released.md new file mode 100644 index 0000000000..022f93afe1 --- /dev/null +++ b/site/_news/2020-09-05-version-0-2-released.md @@ -0,0 +1,5 @@ +--- +date: 2020-09-05 +tag: 0.2 +title: Version 0.2 released. +--- diff --git a/site/_news/2021-11-18-version-0-3-released.md b/site/_news/2021-11-18-version-0-3-released.md new file mode 100644 index 0000000000..471d814cbf --- /dev/null +++ b/site/_news/2021-11-18-version-0-3-released.md @@ -0,0 +1,5 @@ +--- +date: 2021-11-18 +tag: 0.3 +title: Version 0.3 released. +--- diff --git a/site/_news/2022-12-14-we-now-have-an-official-blog.md b/site/_news/2022-12-14-we-now-have-an-official-blog.md new file mode 100644 index 0000000000..c123d47ffc --- /dev/null +++ b/site/_news/2022-12-14-we-now-have-an-official-blog.md @@ -0,0 +1,6 @@ +--- +date: 2022-12-14 +tag: blog +title: We now have an official blog +link: https://borisbat.github.io/dascf-blog +--- diff --git a/site/_news/2023-01-01-daslang-now-happily-runs-in-the-web-brow.md b/site/_news/2023-01-01-daslang-now-happily-runs-in-the-web-brow.md new file mode 100644 index 0000000000..a3c59ed73b --- /dev/null +++ b/site/_news/2023-01-01-daslang-now-happily-runs-in-the-web-brow.md @@ -0,0 +1,6 @@ +--- +date: 2023-01-01 +tag: news +title: Daslang now happily runs in the web browser. Check it out. +link: https://gaijinentertainment.github.io/try-dascript/ +--- diff --git a/site/_news/2023-05-01-version-0-4-of-daslang-has-been-released.md b/site/_news/2023-05-01-version-0-4-of-daslang-has-been-released.md new file mode 100644 index 0000000000..5acf7ce97f --- /dev/null +++ b/site/_news/2023-05-01-version-0-4-of-daslang-has-been-released.md @@ -0,0 +1,5 @@ +--- +date: 2023-05-01 +tag: 0.4 +title: Version 0.4 of Daslang has been released. The development team is currently working on the next major release, which will include a fully featured JIT compiler. This JIT compiler is currently available as a preview. +--- diff --git a/site/_news/2024-07-01-dascript-renamed-to-daslang.md b/site/_news/2024-07-01-dascript-renamed-to-daslang.md new file mode 100644 index 0000000000..2b2a398356 --- /dev/null +++ b/site/_news/2024-07-01-dascript-renamed-to-daslang.md @@ -0,0 +1,5 @@ +--- +date: 2024-07-01 +tag: news +title: daScript renamed to Daslang! +--- diff --git a/site/_news/2024-07-07-version-0-5-is-coming-very-soon-it-will.md b/site/_news/2024-07-07-version-0-5-is-coming-very-soon-it-will.md new file mode 100644 index 0000000000..47093e70cb --- /dev/null +++ b/site/_news/2024-07-07-version-0-5-is-coming-very-soon-it-will.md @@ -0,0 +1,5 @@ +--- +date: 2024-07-07 +tag: 0.5 +title: Version 0.5 is coming very soon. It will include a lot of new features and improvements. Stay tuned! Expect JIT, serialization, performance improvements, new syntax features, and more. +--- diff --git a/site/_news/2024-07-28-massive-update-on-data-initialization-sy.md b/site/_news/2024-07-28-massive-update-on-data-initialization-sy.md new file mode 100644 index 0000000000..57fd1660d8 --- /dev/null +++ b/site/_news/2024-07-28-massive-update-on-data-initialization-sy.md @@ -0,0 +1,6 @@ +--- +date: 2024-07-28 +tag: blog +title: Massive update on data initialization syntax. Table and set comprehensions are now available. See the Data Initialization in DAS for more details. +link: https://borisbat.github.io/dascf-blog/2024/07/23/data-initialization/ +--- diff --git a/site/_news/2024-10-10-version-0-5-of-daslang-has-been-released.md b/site/_news/2024-10-10-version-0-5-of-daslang-has-been-released.md new file mode 100644 index 0000000000..b360f0f187 --- /dev/null +++ b/site/_news/2024-10-10-version-0-5-of-daslang-has-been-released.md @@ -0,0 +1,6 @@ +--- +date: 2024-10-10 +tag: 0.5 +title: Version 0.5 of Daslang has been released. This version includes a lot of new features and improvements, such as JIT, serialization, performance improvements, new syntax features. +link: https://borisbat.github.io/dascf-blog/2024/10/10/zero-point-five/ +--- diff --git a/site/_news/2025-05-06-overview-of-gen2-syntax-as-well-as-gen1.md b/site/_news/2025-05-06-overview-of-gen2-syntax-as-well-as-gen1.md new file mode 100644 index 0000000000..f196f56b19 --- /dev/null +++ b/site/_news/2025-05-06-overview-of-gen2-syntax-as-well-as-gen1.md @@ -0,0 +1,6 @@ +--- +date: 2025-05-06 +tag: blog +title: Overview of gen2 syntax as well as gen1.5 syntax, as well as conversion process. All about 'options gen2' +link: https://borisbat.github.io/dascf-blog/2025/05/04/gen2-syntax/ +--- diff --git a/site/_news/2025-06-10-we-now-have-linq-like-capabilities-in-da.md b/site/_news/2025-06-10-we-now-have-linq-like-capabilities-in-da.md new file mode 100644 index 0000000000..a8f8fe0364 --- /dev/null +++ b/site/_news/2025-06-10-we-now-have-linq-like-capabilities-in-da.md @@ -0,0 +1,6 @@ +--- +date: 2025-06-10 +tag: blog +title: We now have LINQ-like capabilities in Daslang. See blog for details. +link: https://borisbat.github.io/dascf-blog/2025/10/08/linq-that/#more +--- diff --git a/site/_news/2025-12-30-pre-releases-are-available-releases-are.md b/site/_news/2025-12-30-pre-releases-are-available-releases-are.md new file mode 100644 index 0000000000..1c7d384325 --- /dev/null +++ b/site/_news/2025-12-30-pre-releases-are-available-releases-are.md @@ -0,0 +1,6 @@ +--- +date: 2025-12-30 +tag: jit +title: Pre-releases are available, RELEASES are coming soon. Bundle has JIT and modules. Bare bones version is just to play with the language. +link: https://github.com/GaijinEntertainment/daScript/tags +--- diff --git a/site/_news/2026-01-20-say-hello-to-templates-and-typemacros-in.md b/site/_news/2026-01-20-say-hello-to-templates-and-typemacros-in.md new file mode 100644 index 0000000000..6d480c5cd4 --- /dev/null +++ b/site/_news/2026-01-20-say-hello-to-templates-and-typemacros-in.md @@ -0,0 +1,6 @@ +--- +date: 2026-01-20 +tag: blog +title: Say hello to templates and typemacros in Daslang! It's trurtles all the way down. +link: https://borisbat.github.io/dascf-blog/2026/01/19/templates-what/ +--- diff --git a/site/_news/2026-01-22-life-improvements-in-daslang-little-thin.md b/site/_news/2026-01-22-life-improvements-in-daslang-little-thin.md new file mode 100644 index 0000000000..426fc01909 --- /dev/null +++ b/site/_news/2026-01-22-life-improvements-in-daslang-little-thin.md @@ -0,0 +1,6 @@ +--- +date: 2026-01-22 +tag: blog +title: Life improvements in Daslang! Little things with big impact. +link: https://borisbat.github.io/dascf-blog/2026/01/22/quality-of-life/ +--- diff --git a/site/_news/2026-02-20-we-are-getting-ready-for-the-release-of.md b/site/_news/2026-02-20-we-are-getting-ready-for-the-release-of.md new file mode 100644 index 0000000000..c749e1f6a9 --- /dev/null +++ b/site/_news/2026-02-20-we-are-getting-ready-for-the-release-of.md @@ -0,0 +1,6 @@ +--- +date: 2026-02-20 +tag: 0.6 +title: We are getting ready for the release of version 0.6. You can preview the documentation online. +link: doc/index.html +--- diff --git a/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md b/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md new file mode 100644 index 0000000000..b1325ee02c --- /dev/null +++ b/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md @@ -0,0 +1,6 @@ +--- +date: 2026-02-28 +tag: 0.6 +title: Daslang 0.6 is just around the corner! Stay tuned for the official release. You can preview release candidate 0.6.0-RC1 0.6.0-RC2 here. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.0-RC1 +--- diff --git a/site/_news/2026-03-01-daslang-0-6-0-is-officially-released-che.md b/site/_news/2026-03-01-daslang-0-6-0-is-officially-released-che.md new file mode 100644 index 0000000000..062750ecad --- /dev/null +++ b/site/_news/2026-03-01-daslang-0-6-0-is-officially-released-che.md @@ -0,0 +1,6 @@ +--- +date: 2026-03-01 +tag: 0.6.0 +title: Daslang 0.6.0 is officially released! Check out the 0.6.0 release. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.0 +--- diff --git a/site/_news/2026-03-25-0-6-1-is-just-about-ready.md b/site/_news/2026-03-25-0-6-1-is-just-about-ready.md new file mode 100644 index 0000000000..602f3434d3 --- /dev/null +++ b/site/_news/2026-03-25-0-6-1-is-just-about-ready.md @@ -0,0 +1,5 @@ +--- +date: 2026-03-25 +tag: 0.6.1 +title: 0.6.1 is just about ready. +--- diff --git a/site/_news/2026-03-29-dastrudel-gets-sf2-soundfont-support-mid.md b/site/_news/2026-03-29-dastrudel-gets-sf2-soundfont-support-mid.md new file mode 100644 index 0000000000..87d221d18f --- /dev/null +++ b/site/_news/2026-03-29-dastrudel-gets-sf2-soundfont-support-mid.md @@ -0,0 +1,5 @@ +--- +date: 2026-03-29 +tag: strudel +title: daStrudel gets SF2 SoundFont support, MIDI playback, and a redesigned two-mode player with real-time visualization. +--- diff --git a/site/_news/2026-04-04-0-6-1-is-out.md b/site/_news/2026-04-04-0-6-1-is-out.md new file mode 100644 index 0000000000..e268f3c6b5 --- /dev/null +++ b/site/_news/2026-04-04-0-6-1-is-out.md @@ -0,0 +1,6 @@ +--- +date: 2026-04-04 +tag: 0.6.1 +title: 0.6.1 is out! +link: https://github.com/GaijinEntertainment/daScript/releases/tag/0.6.1-RC2 +--- diff --git a/site/_news/2026-04-17-dastrudel-gets-module-reference-and-a-15.md b/site/_news/2026-04-17-dastrudel-gets-module-reference-and-a-15.md new file mode 100644 index 0000000000..df5c9a33f3 --- /dev/null +++ b/site/_news/2026-04-17-dastrudel-gets-module-reference-and-a-15.md @@ -0,0 +1,6 @@ +--- +date: 2026-04-17 +tag: strudel +title: daStrudel gets module reference and a 15-part tutorial series. +link: doc/stdlib/sec_strudel.html +--- diff --git a/site/_news/2026-05-01-0-6-2-is-just-about-ready.md b/site/_news/2026-05-01-0-6-2-is-just-about-ready.md new file mode 100644 index 0000000000..50181a1854 --- /dev/null +++ b/site/_news/2026-05-01-0-6-2-is-just-about-ready.md @@ -0,0 +1,6 @@ +--- +date: 2026-05-01 +tag: 0.6.2 +title: 0.6.2 is just about ready. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.2-RC1 +--- diff --git a/site/_news/2026-05-08-0-6-2-rc2-is-out-its-getting-closer.md b/site/_news/2026-05-08-0-6-2-rc2-is-out-its-getting-closer.md new file mode 100644 index 0000000000..c34c75cc57 --- /dev/null +++ b/site/_news/2026-05-08-0-6-2-rc2-is-out-its-getting-closer.md @@ -0,0 +1,6 @@ +--- +date: 2026-05-08 +tag: 0.6.2-rc2 +title: 0.6.2-RC2 is out! Its getting closer. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.2-RC2 +--- diff --git a/site/_news/2026-05-09-0-6-2-rc3-is-out-post-rc2-polish-across.md b/site/_news/2026-05-09-0-6-2-rc3-is-out-post-rc2-polish-across.md new file mode 100644 index 0000000000..b74720b3d0 --- /dev/null +++ b/site/_news/2026-05-09-0-6-2-rc3-is-out-post-rc2-polish-across.md @@ -0,0 +1,6 @@ +--- +date: 2026-05-09 +tag: 0.6.2-rc3 +title: 0.6.2-RC3 is out — post-RC2 polish across `@live` on struct fields, MCP `daslib/` fallback for unqualified module names, blind-mouse Q&A cache MCP server, nine new perf/style lint rules (PERF013–017, STYLE016–019), and a `find_call_macro` null-deref fix. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.2-RC3 +--- diff --git a/site/blog/_posts/a-matter-of-multipliction.md b/site/blog/_posts/a-matter-of-multipliction.md new file mode 100644 index 0000000000..0bc37475b7 --- /dev/null +++ b/site/blog/_posts/a-matter-of-multipliction.md @@ -0,0 +1,78 @@ +--- +title: A matter of multiplication +date: 2023-03-20 23:28:56 +tags: + - OpenAI + - daScript +--- + +Its a matter of multiplication, really. Productivity factors do not just add up (thats why they are called factors). + + + +Programming environment is a collection of multipliers. When you add more, the growth is exponential. The growth of productivity that is. + +There is [GitHub Copilot](https://github.com/features/copilot). Its good for C++. It works so well with daScript. It writes a lot of code for me and makes completion awesome. +Seriously, if u did not, yet - give it a try. There is also copilot labs - but it looks too experimental, and does not quite do it justice. +Integration with vscode is watertight. + +Speaking of vscode. I tend to write a lot of code in daScript this days. I am many times more productive with it, +so at some point I'll rewrite entire daScript in daScript. The new year resolution was to start moving parts. But then again, JIT happens. +This being said daScript had [vscode plugin](https://marketplace.visualstudio.com/items?itemName=profelis.dascript-plugin), and I don't want to write anything without it. Because reasons. +Reasons like completion, navigation, debugger, and upcoming release of refactoring; because inlays, and most importantly hint overlays. + +But we need to go deeper. Like modules/OpenAI deeper. There is a sample there, under the newly created OpenAI daScript bindings. +What it does, it writes entire unit test, given function. It does good job. It even uses `fuzzer`. Only comment and function were provided. + + + def strcat(a,b) + return a + b + + [test] // use any_string and any_file_name for strcat + def test_strcat ( t:T? ) + t |> run("testing strcat") <| @@ ( t : T? ) + var fake <- Faker() + fuzz <| + let a = fake |> any_string(fake) + let b = fake |> any_file_name(fake) + t |> equal(a+b, strcat(a,b)) + +I suspect at some point this and other prompts will make it into plugin. At the end of the day, it's that simple + + def generate_test ( func, description : string ) : string + var prompt = build_string <| $ ( writer ) + writer |> write(unit_test_prompt) |> write(func) |> write("\n[test] // ") |> write(description) |> write("\ndef test_") + var ccr <- openai_create_completion([[Completion() + model = unit_test_model, prompt = prompt, max_tokens = 200, + top_p = 1.0, temperature = 0., frequency_penalty = 0.52, + presence_penalty = 0.5, best_of = 1, stop <- [{string "// --"; "\}"}] + ]]) + if ccr |> is_valid + var choice = ccr.choices[0].text |> replace("\\r","") |> safe_unescape + return build_string <| $(writer) + writer |> write("[test] // ") |> write(description) |> write("\ndef test_") |> write(choice) + else + to_log(LOG_ERROR, "error: {openai_get_last_error()}\n") + return "" + +The [OpenAI API](https://platform.openai.com/docs/api-reference) was trivial to integrate. +There was just one catch.I wanted it to work with the plugin, regardless of daScript build. +That means I could not use `libhv/libhv` (did you know we have LibHV integration?), or any integration for that matter. It has to be raw daslib. + +Fear not. We now have `daslib/curl`. It calls curl. The command line 'curl', via 'popen'. Its surprisingly robust, and if u need it to not block - there is always `jobque`. +I was contemplating async/await API and ultimately decided against it. One day we'll have a real high load use case with coroutine framework; until then lets not overengineer. + +We also have `daslib/base64`, because sometimes we get answers in base64. That, and JSON. We had `daslib/json` for a long time. +The implementations for both of them are all very academic, so to speak. I'm writing the simplest daScript code, and surprisingly it runs just fine. +I've yet to encounter any performance issues worth mentioning with all those straightforward implementations. +"Just use good algorithms", like it ever works. So far so good though. Even with interpreter. + +Interestingly, JIT likes it. Somehow it seem to favor simple code. Go figure. I'm going to add base64 to the test suit and actually compare. +It's been JIT > AOT all day. + +It's amazing to watch them multiply. Once the critical mass has been reached, adding new legos is a breeze. +I can build so many things now. And I can do it fast. + + + + diff --git a/site/blog/_posts/ai-friendly.md b/site/blog/_posts/ai-friendly.md new file mode 100644 index 0000000000..992418788f --- /dev/null +++ b/site/blog/_posts/ai-friendly.md @@ -0,0 +1,48 @@ +--- +title: ai-friendly +date: 2026-03-06 20:19:47 +tags: + - daScript + - Claude +--- + +You say You are AI friendly. But how friendly are You? + + + +All this talk, adjusting syntax, working on iteration speed - surely this is going to be beneficial one day. Right? + +Well, its time we found out. Claude Code with Opus 4.6 is actually capable of doing serious tasks on Daslang codebase, both in C++ and DAS. +Good news - it knows DAS out of the box. Bad news - it knows gen1 syntax well. Good news - it was easy to have it adjust. + +And adjusted it is. With the right amount of instructions and skills, it started to write DAS code pretty fast. +With even more instructions it became capable of writing some fairly decent macros. And it started to look like something beautiful. +Iteration speeds were decent, code was being written. Things were looking up. + +Then it got better. At some point one can't help but ask - "so, how do I make your job easier". "make me an MCP server, duh". +Ok. And I'm like - "I know nada about it, why don't u make me the simpliest one?" + +So we got protocol down, JSON parsing, HTTP layer, all this boiler plate. Made a tool. Made it pick it up. Cool. +Asked for which other tools it wanted. Got a wish list. Started going though it - mainly by making it make it's own tools. + +It was fascinating to see more and more tool calls, and less and less 'greep' and 'bash' in the output. +The more tools we made, the faster things got. A hammer took a minute. A saw was there quickly. +A lathe arrived in a jiffy. By the time I came to my senses, we were building industrial grade 5-axis CNC machine. + +Whats even more interesting, a lot of functionality is very similar to what our Vscode plugin is doing for people. +Other stuff was very close to different log modes which we already had. Some were shortcuts for the tools - just to speed things up. + +There was hardly anything new, apart from the MCP bindings. +I am finally approaching stuff, which we don't have exposed via plugin - so I wonder how that would go. + +Another big change is the focus on testing. To iterate fast you can't afford to regress. +So every new feature now gets decent set of tests from the get go. We went from ~1k to about 4k, and it is growing fast. + +I'd like to mention documentation and tutorials, but in this new reality its a given. Someone will read them. Or something... + +At the end of the day MCP server is going to be part of releases. +0.6.1 will go out with CLAUDE.md, skills, MCP server, and many more goodies. + +Stay tuned. + + diff --git a/site/blog/_posts/better-safe.md b/site/blog/_posts/better-safe.md new file mode 100644 index 0000000000..c09d9d30cb --- /dev/null +++ b/site/blog/_posts/better-safe.md @@ -0,0 +1,103 @@ +--- +title: Better safe +date: 2022-11-14 18:29:45 +tags: + - C++ + - daScript +--- + +[Herb Sutter is right](https://youtu.be/ELeZAKCN4tY?t=2391). Its the same thing year after year after year. `Lifetime`, `Initialization`, `Type safety`, and `Bounds safety` are the things which your language should do for you. + + + +### LIFETIME + +daScript does not have any safe way to access objects outside of their lifetime. Seriously, if you can find one - lets us know; we will fix it. + +daScript goes out of its way, sometimes even at cost of performance, to make sure there are no lifetime related bugs. Array and table locking happens in runtime. The bulk of effort happens at the language design stage. Here are some of the ideas on how we are doing it: +* pointer delete is unsafe; `GC` or `GC0` are the safe way +* local references are unsafe; `assume` to the rescue +* lambda capture is explicit, lambda capture by reference is unsafe +* arrays and tables are locked when iterated, or explicitly +* data is not shared between contexts; data can be shared between jobs or threads via safe channels +* inter-context invocations are locking +* temporary types + +#### `unsafe` keyword + +There is an `unsafe` keyword; it helps with performance and practicality - it also helps to narrow down the bugs. +When facing a lifetime bug, there is a 99.9% chance it has occurred under the `unsafe` umbrella. + +#### Temporary types + +Concept of temporary types is unique to daScript. Temporary (or local) types insure that data can't be accessed outside of its scope. + +Consider the following example: + +```python +[export] +def main + var tab <- {{ "1" => 1; "2" => 2 }} + lock(tab) <| $ ( temp_tab ) + let one = temp_tab |> find("1") + if one != null + print("found {*one}\n") +``` + +Pointer `one` is temporary (as well as temp_tab). `lock` insures that table can't be modified. Temporary pointer can not exit the block - it can't be copied, or returned. As a result, having pointer to the table element is safe. + +#### Pointer dereferencing and pointer arithmetic + +daScript has both. Pointer dereference is safe, however null pointer dereference always causes panic. Pointer arithmetics are unsafe. +There are no safe ways to create dangling pointers. Taking the address of the value is unsafe. + +It is possible to make temporary pointers to some values, via `safe_addr` - but those are covered under temporary type protection. + +### INITIALIZATION + +daScript does not have uninitialized data structures of any kind. + +```python +[export] +def main + let a : int + print("{a}\n") +``` + +Always prints 0. If not initialized - everything is a semantic zero of some kind. Strings are empty. +Pointers are null. Arrays and tables are empty. Iterators are closed. There is no safe way to get to uninitialized memory. + +Case closed? Well, almost. daScript is designed to talk to C++. It is always possible to bind the `safe` C++ function, which does unsafe things. + +### TYPE SAFETY + +daScript types are safe by default - there is no need for `type safety guidelines'. +* `reinterpret` casts are unsafe +* casting `const` away is unsafe +* LSP casts are safe, but can be disabled via `explicit` keyword +* there is no automatic type promotion +** with the exception of any pointer to void pointer (but not from void pointer) +* types can be inspected with a combination of `static_if` and `typeinfo` at compilation time +* everything is always initialized +* there are no unions, only variants - and those are safe; or they panic +* there are no va_args - macros are the future; and the future is now + +All good? Well, almost. Macros can do just about anything, and 'alwaysSafe' flag on expression is there for a good reason. +But then you can always disable user code macros from the `CodeOfPolicies`, or `Lint`, or with another macro. + +### BOUNDS SAFETY + +In daScript every indexing operation causes bounds checks - on static or dynamic arrays, as well as tables. Accessing pointer by index is unsafe. + +For practical purposes there is always `[unsafe_deref]`, however optimizer does decent job in JIT or AOT. At some point interpreter will follow, and its rarely an interpreter bottleneck anyway. + +As always - you can always bind custom C++ type which throws it out of the window, but you don't have to - indexing is explicitly bound; its always good to check. + +### Anything else? + +`Lint`. daScript has lint macros and they are awesome. We've found that many things traditionally left to code reviews are easily automateable. Some of them are already in the `CodeOfPolicies`. + +### Where do we go from here? + +daScript is a fine ballance between safe and robust. Try to keep your code safe, but occasional isolated `unsafe` here and there for practical purposes is perfectly fine. + diff --git a/site/blog/_posts/builtin-lock-check-md.md b/site/blog/_posts/builtin-lock-check-md.md new file mode 100644 index 0000000000..495bb6e2bc --- /dev/null +++ b/site/blog/_posts/builtin-lock-check-md.md @@ -0,0 +1,118 @@ +--- +title: Lock and load +date: 2022-11-13 11:58:49 +tags: daScript +--- + +Consider the following example: + +```python +[export] +def main + var a <- [{int 1;2;3;4}] + for x in a + a |> push(13) // kaboom +``` + +It causes daScript runtime panic. + + + +``` +unhandled exception +: can't resize locked array +``` + +The underlying reason for the panic is that the array data is allocated dynamically. +`push` among other operations like `resize`,`reserve`,`emplace`, and `erase` can sometimes cause the array data to be reallocated and moved to a new memory location, which in turn would cause loop variables to expire while still pointing to the no longer used region of the heap - which is obviously unsafe. + +To prevent this from happening locking is implemented. +An array iterator increases the lock count during its initialization, and decreases it during the iterator's finalization. +To access the lock counter during the finalization, the array iterator caches the pointer to the original array. + +```python +[export] +def main + var a <- [{int[] 1}] + print("before the first loop: {lock_count(a)}\n") + for x in a + print("before the second loop: {lock_count(a)}\n") + for y in a + print("inside the second loop: {lock_count(a)}\n") + print("after the second loop: {lock_count(a)}\n") + print("after the first loop: {lock_count(a)}\n") +``` + +The result is exactly as expected + +``` +before the first loop: 0 +before the second loop: 1 +inside the second loop: 2 +after the second loop: 1 +after the first loop: 0 +``` + +Similar counter is also implemented for the tables, which also happen to store their data dynamically. + +On the surface this provides a fool-proof mechanism which prevents dangling references to the array data, however, this is not the case. Consider another example: + +```python +[export] +def main + var a : array< array > + a |> emplace <| [{int 1;2;3}] + a |> emplace <| [{int 4;5;6}] + for x in a[0] + for y in a[1] + print("before resize: {lock_count(a)}\n") // prints '0' + resize(a, 3) // kaboom + print("{y}\n") +``` + +Lock count on the `a` before the resize is 0. However the code above is unsafe. +Resizing `a` may cause relocation of both `a[0]` and `a[1]`, which are being iterated over. +That way cached pointers to the arrays inside the iterators expire. + +However, example above causes daScript runtime panic during the `resize`: + +``` +unhandled exception +: object contains locked elements and can't be resized +``` + +If we check `options log` we could see what actually happens: + +```python +def private builtin`resize ( var Arr:array aka numT> explicit; newSize:int const ) + _builtin_verify_locks(Arr) + __builtin_array_resize(Arr,newSize,24,__context__) +``` + +daScript generates custom resize function for the `array>`, and calls lock verification function `_builtin_verify_locks`. +Internally, it looks over provided data and checks lock counters on all arrays (and tables). Panic occurs if any of them are non-zero. + +```python +[export] +def main + profile(1,"with lock check") <| + var a : array< array > + for t in range(10000) + a |> emplace <| [{int t;t+1;t+2}] // lock checked every time + profile(1,"without lock check") <| + var a : array< array > + unsafe + set_verify_array_locks(a, false) + for t in range(10000) + a |> emplace <| [{int t;t+1;t+2}] // lock check ignored +``` + +It's important to note that lock verification could have a significant performance overhead. + +``` +"with lock check", 1.10712, 1 +"without lock check", 0.002006, 1 +``` + +The best way to avoid such overhead is to not create data structures with internal locking. +set_verify_array_locks' and `set_verify_table_locks` provide more practical albeit unsafe solution. diff --git a/site/blog/_posts/daslang-it-is.md b/site/blog/_posts/daslang-it-is.md new file mode 100644 index 0000000000..520c311f9e --- /dev/null +++ b/site/blog/_posts/daslang-it-is.md @@ -0,0 +1,54 @@ +--- +title: Daslang it is +date: 2023-12-28 20:51:24 +tags: + - daScript + - C++ + - C# +--- + +The plug has been pulled. Daslang it is. Take a look at dasCHash too ( C#->DAS ). + + + +{% post_link its-not-a-script %} so renaming it was a matter of time; and the time is now. Too bad, I liked the old name. Now lets make a new one proud. + +We are well on our way to 0.5. +* the works - oh how I love the works; bugs fixed, documentation updated, small features, large features, this and that. +* serialization is there and is kicking ass. Can save. Can load. Can check if it needs to be regenerated. Blazing fast. +* JIT is well on the way. Lots of new tests. A lot more things can now go all the way JIT. Some crash though and LLVM has not been on its best behaviour but more of that later. + +We took the detour though. Welcome C# -> DAS. Yes, really. [I like `hash` instead of `sharp`](https://github.com/borisbat/dasCHash). Why, you ask? Did someone mention Unity? +There is some sidekick code on the Unity side and we'll publish it at some point too. There will be more announcements. + +On the plus side Daslang now supports a lot better property system. Classes got face lift. Static properties. Abstract properties. C#-style overrides, all that jazz:: + + class Foo + dir : float3 + def Foo ( x,y,z:float ) + dir = float3(x,y,z) + def Foo ( d:float3 ) + dir = d + def const operator . length + return length(dir) + def operator . length := ( value:float ) + dir = normalize(dir) * value + +JIT is whole different story. It feels like it should have been done by now; after all its getting close. There is whole LLVM issue. +LLVM-C interface feels like an afterthought. My main complaint is that it crashes on wrong types of input, instead of reporting an error. +I guess I should have manually wrapped it by now but that seems like an overkill. Pain to debug, demotivating. + +LLVM crashes on some optimizations; my hope is to upgrade the version. I'm currently stuck to 15.7 because, you guessed it, opaque pointers:: + + LLVMContextSetOpaquePointers(LLVMGetGlobalContext(), 0) + +Yes, that thing. Once addressed, for sure some of the optimization related crashes will go away. Right? Right??? +The more I play with it, the more it feels like a C++ compiler backend, and not a 'general purpose multi-platform backend'. +The results are spectacular though 18.7 mrays/sec vs 7.2 mrays/sec for raytracing demo on single core on JIT vs AOT. +So hopefully soon it will get stable enough. + +We did some work on standalone contexts. Hopefully soon. Once there - everything possible goes native. We'll see how it plays out. +Looking at the new year with hope. This one was a tad slow at times. See you in the next one. + +P.S. What wishes do I have for new year? I'm glad you asked. Mercy. Just kidding. + diff --git a/site/blog/_posts/data-initialization.md b/site/blog/_posts/data-initialization.md new file mode 100644 index 0000000000..d69bfe1a8c --- /dev/null +++ b/site/blog/_posts/data-initialization.md @@ -0,0 +1,225 @@ +--- +title: Data initialization in DAS +date: 2024-07-23 21:35:17 +tags: + - daScript + - C++ +--- + +Updated on 2024-07-29 16:31:00 + +Sometimes shorthand notation arrives first. + + + +Some of the DAS syntax has been AI-inspired. Tools like github Copilot like to write DAS code in a certain manner. +Who am I to disagree? After all they represent collective wisdom of the internet. What could possibly go wrong? + +So, first things first, here are some new DAS syntax features (no 'Expressions' has been harmed in the process):: + +This is structure, variant, or tuple initializer:: + + var val = Foo(a=1,b=2.0) // same as [[Foo() a=1,b=2.0]] + +This is array initializer:: + + var arr <- [1, 2, 3, 4] // same as [{auto 1;2;3;4}] + +This is table (set) initializer:: + + var set <- {1, 2, 3, 4} // same as {{1; 2; 3; 4}} + +This is table initializer:: + + var tab <- {1=>"1", 2=>"2", 3=>"3"} // same as {{1=>"1", 2=>"2", 3=>"3"}} + +This is tuple initializer:: + + var tup <- (1,2.,"3") // same as [[auto 1,2.,"3"]] + +This is array comprehension:: + + var acomp <- [for x in 0..10; x*x; where x%2==0] // same as [{for x in 0..10; x*x; where x%2==0}] + +This is table (set) comprehension:: + + var scomp <- {for x in 0..10; x*x; where x%2==0} // this is new + +This is table comprehension:: + + var scomp <- {for x in 0..10; x*x => "{x*x}"; where x%2==0} // this is also new + +Now, with that out of the way. + +I'm not opposed to symbolic syntax also known as concise syntax. Smaller programs are typically easier to read. +The downside is you actually have to learn what unfamiliar symbols mean. +Languages like C++ gave us decent amount of familiar symbols. Imagine:: + + a ? b->c() : *d.e() + +C# adds more symbols:: + + a?.b?[d] ?? e + +DAS adds even more:: + + a <- [[b() c]] <| $ ( var e ) + e.f = @@g + +Some of it is a shorthand notation for data initialization. + +DAS parser is fast. Part of the reason is LR(1) (or LALR(1)). That limits syntax somewhat and also limits some of the symbol reuse. +At this point there is whole bunch of code which limits massive changes to certain extent as well. + +There is certain inherit logic in how data initialization shorthand is organized. Time to voice it explicitly. +Also for there is a shorthand, there is a full notation. Time to introduce this one as well. + +Everything in [[ here ]] is local data initialization. It could be array, structure, class, tuple, or variant. + +Lets start with fixed arrays:: + + var a1 = [[int 1;2;3;4]] + var a2 = [[auto 1;2;3;4]] // here it will infer type, so a2 is exactly the same + +The full notation is:: + + var a1 = fixed_array(1,2,3,4) + var a2 = fixed_array(1,2,3,4) // when type is not specified, auto is assumed + +Dynamic arrays are similar, only [{ }] are used, and not [[ ]]:: + + var a1 <- [{int 1;2;3;4}] + var a2 <- [{auto 1;2;3;4}] + var a2 <- [1,2,3,4] // same thing, shorter syntax + +The full notation is:: + + var a1 <- array(1,2,3,4) + var a2 <- array(1,2,3,4) + +Now, lets look at the structures:: + + struct Foo + a : int = 5 + b : float = 3.0 + + var a1 = [[Foo() b=2.0]] + var a1 = Foo(b=2.0) // same thing, shorter syntax + var a2 = unsafe([[Foo a=1, b=2.0]]) + var a3 = [[Foo() a=1,b=2.0; a=2,b=3.0]] + var a3 = Foo(a=1,b=2.0; a=2,b=3.0) // same thing, shorter syntax + var a4 <- [{Foo() a=1, b=2.0}] + +This brings a few questions. +* Foo() means Foo will be initialized. Imagine calling Foo(), and then applying fields. +* Foo in a2 is unsafe, because by default it's unsafe to have structures uninitialized. (there is [safe_when_uninitialized] annotation to mitigate that) +* a3 is a fixed array of 2 different Foo +* a4 is an array with 1 element Foo ( note [{ }] syntax for dynamic arrays ) + +Full notation is:: + + var a1 = struct(b=2.0) + var a3 = struct(a=1,b=2.0; a=2,b=3.0) + var a4 <- to_array_move(struct(a=1,b=2.0)) + +There is no version of uninitialized structure full notation (a2). + +Classes are very similar to structures:: + + class Foo + a : int + b : float + def Foo + a = 5 + b = 3.0 + + var a = new [[Foo() b=2.0]] + var a = new Foo(b=2.0) // same thing, shorter syntax + +The full notation is:: + + var a = new class(b=2.0) + +Note, that Foo() initializer will be called (as oppose to structures, where default field values are used for initialization of unspecified values). + +Class can technically be constructed on the stack, but its unsafe to do so. In that case its exactly the same shorthand notation as structures (and with keyword class for the full notation). + +Variants follow the same pattern:: + + variant Foo + a : int + b : float + + var a1 = [[Foo a=1]] + var a1 = Foo(a=1) // same thing, shorter syntax + var a2 = [[Foo a=1; b=2.1]] // fixed array of 2 different variants + var a2 = Foo(a=1;b=2.1) // same thing, shorter syntax + var a3 <- [{Foo a=1; b=2.2}] // array of 2 different variants + +The full notation is:: + + var a1 = variant(a=1) + var a2 = variant(a=1; b=2.1) + var a3 <- to_array_move(variant(a=1; b=2.2)) + +Tuples follow suit:: + + tuple Foo + a : int + b : float + + var a1 = [[Foo a=1,b=2.0]] + var a1 = Foo(a=1,b=2.0) // same thing, shorter syntax + var a2 = [[Foo a=1; b=2.1]] + var a2 = Foo(a=1; b=2.1) // same thing, shorter syntax + var a3 <- [{Foo a=1; b=2.2}] + + var b1 = [[auto 1,2.0]] + var b1 = (1,2.0) // same thing, shorter syntax + var b2 = [[auto 1,1.0; 2,2.1]] + var b3 <- [{auto 1,2.2}] + + return 1,2.2 // same as return [[auto 1,2.2]] + +Note, that tuple type declaration is a weak type, i.e. type alias (typedef). a1 is the same type as b1, and so on. +The only difference is tuple fields can be accessed via field name. I.e. a1.a or b1._0, though a1._0 is also available. + +The full notation is:: + + var a1 = tuple(a=1,b=2.0) + var a2 = tuple(a=1;b=2.0) + var a3 <-> to_array_move(tuple(a=1,b=2.2)) + + var b1 = tuple(1,2.0) + var b2 = fixed_array(tuple(1,1.0),tuple(2,2.1)) + var b3 <- array(tuple(1,2.2)) + + return tuple(1,2.2) + +There are two types of tables. A key-value pair and a table which only has keys. Everything between double curly bracers is a table:: + + var t1 <- {{ 1; 2; 3; 4 }} + var t1 <- { 1, 2, 3, 4 } // same thing, shorter syntax + var t2 <- {{ 1=>"a"; 2=>"b"; 3=>"c"; 4=>"d" }} + var t2 <- { 1=>"a", 2=>"b", 3=>"c", 4=>"d" } // same thing - shorter syntax + +In reality 1=>"a" is yet another shorthand for [[auto 1,"a"]] which is in turn a tuple initialization. + +The full notation is:: + + var t1 <- table(1,2,3,4) + var t2 <- table(1=>"a", 2=>"b", 3=>"c", 4=>"d") + +Types can be specified (and verified) explicitly:: + + var t1 <- table(1,2,3,4) + var t2 <- table(1=>"a", 2=>"b", 3=>"c", 4=>"d") + +If we go deeper under the hood, we will find:: + + var t2 : table <- to_table_move ( fixed_array> ( tuple(1,"a"), tuple(2,"b"), ... ) ) + +But thats a topic for another blog post. + + + diff --git a/site/blog/_posts/for-loop-noise.md b/site/blog/_posts/for-loop-noise.md new file mode 100644 index 0000000000..dffc3efb65 --- /dev/null +++ b/site/blog/_posts/for-loop-noise.md @@ -0,0 +1,270 @@ +--- +title: For loop noise +date: 2023-01-27 16:24:05 +tags: + - C++ + - daScript +--- + +It's stupid. It's ugly. Its also measurable performance gain. + + + +Let's write a node-based interpreter, just like the one we have, only smaller: + + #include + + struct Node { + virtual void eval() = 0; + }; + + void eval ( std::vector & nodes ) { + for ( auto n : nodes ) { + n->eval(); + } + } + +Disassembler (CLANG 15 -O3) says its good: + + mov rbx, qword ptr [rdi] // this is nodes.begin() + mov r14, qword ptr [rdi + 8] // this is nodes.end() + cmp rbx, r14 // if they are the same, we skip the loop + je .LBB0_3 + .LBB0_1: + mov rdi, qword ptr [rbx] // vtable_ptr = nodes_ptr + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + call qword ptr [rax] // this is n->eval() i.e. (*call_ptr)() + add rbx, 8 // nodes_ptr ++ + cmp rbx, r14 // if nodes_ptr!=nodes.end() we continue looping + jne .LBB0_1 + +Sometimes we need an index of a node. Easy-peasy: + + void eval ( std::vector & nodes ) { + for ( int i=0; i!=nodes.size(); ++i ) { + nodes[i]->eval(); // and we can use index i + } + } + +But now disassembler hates us: + + mov rax, qword ptr [rdi] // this is nodes.begin() + cmp qword ptr [rdi + 8], rax // if its the same as nodes.end() we skip the loop + je .LBB0_3 + mov r14, rdi + xor ebx, ebx + .LBB0_2: + mov rdi, qword ptr [rax + 8*rbx] // vtable_ptr = nodes.data() [i] + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + call qword ptr [rax] // this is n->eval() is (*call_ptr)() + inc rbx // this is our index = i++ + mov rax, qword ptr [r14] // this is b = nodes.begin() + mov rcx, qword ptr [r14 + 8] // this is e = nodes.end() + sub rcx, rax // this is .size() = (e - b) / 8 + sar rcx, 3 + cmp rbx, rcx // if i!=.size() we keep looping + jne .LBB0_2 + +What happens? In one word - aliasing. n->eval() can technically resize nodes. +So it reads an computes .size() every single iteration. +I know, I know, its nothing new. There are tones of articles about it. +And yet like a mad man I keep writing that code like an incantation every single time I need an index. +But no more: + + void eval3 ( std::vector & nodes ) { + for ( int i=0, is=nodes.size(); i!=is; ++i ) { + nodes[i]->eval(); // and we can use index i + } + } + +This is better: + + mov rax, qword ptr [rdi + 8] // this is nodes.begin() + sub rax, qword ptr [rdi] // this is nodes.end() + shr rax, 3 // this is .size() + test eax, eax // if its zero, we skip the loop + je .LBB0_3 + mov r14, rdi + mov r15d, eax + xor ebx, ebx + .LBB0_2: + mov rax, qword ptr [r14] // we load nodes.data() here + mov rdi, qword ptr [rax + 8*rbx] // vtable_ptr = nodes.data() [i] + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + call qword ptr [rax] // this is n->eval() is (*call_ptr)() + inc rbx // this is our index=i++ + cmp r15, rbx // if i!=size() we keep looping + jne .LBB0_2 + +Now this is getting to the impractical territory. We now need to cache two variables, write more incantations: + + void eval4 ( std::vector & nodes ) { + auto data = nodes.data(); for ( int i=0, is=nodes.size(); i!=is; ++i ) { + data[i]->eval(); // and we can use index i + } + } + +However all is well in the land of disassembly: + + mov r14, qword ptr [rdi] // this is b = nodes.begin() + mov rax, qword ptr [rdi + 8] // this is e = nodes.end() + sub rax, r14 // this is nodes.size() = (e-b) / 8 + shr rax, 3 + test eax, eax // if its 0 we skip the loop + je .LBB0_3 + mov r15d, eax + xor ebx, ebx + .LBB0_2: + mov rdi, qword ptr [r14 + 8*rbx] // vtable_ptr = nodes_data [index] + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + call qword ptr [rax] // this is n->eval() is (*call_ptr)() + inc rbx // this is index = i++ + cmp r15, rbx // if index!=.size() we keep looping + jne .LBB0_2 + +The prologue of the loop is longer, but at least the loop itself is good. + +Let's add an index, and actually pass it to the node like this: + + struct Node { + virtual void eval(int i) = 0; + }; + + void eval ( std::vector & nodes ) { + int i=0; + for ( auto n : nodes ) { + n->eval(i); + i ++; + } + } + +And look, all is well in the land of disassembly: + + mov rbx, qword ptr [rdi] // nodes_ptr = nodes.begin + mov r14, qword ptr [rdi + 8] // nodes.end + cmp rbx, r14 // if they are the same, skip the loop + je .LBB0_3 + xor ebp, ebp // index = 0 + .LBB0_2: + mov rdi, qword ptr [rbx] // vtable = nodes_ptr + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + mov esi, ebp // pass i as first argument + call qword ptr [rax] // (*call_ptr) ( i ) + inc ebp // i ++ + add rbx, 8 // nodes_ptr ++ + cmp rbx, r14 // if nodes_ptr != nodes_end continue looping + jne .LBB0_2 + +Also what if there are multiple sources? Do we cache every data() if size() is the same? + +So, if u need index, there are indeed two and a half "good" options +* cache .data() and .size() + not exactly readable, not easy to maintain +* use range based for, make index variable, increase manually + error prone, there could be continue in the middle +* its ok to cache just .size() + data is still aliased + +In daScript there is multiple source syntax, which can be used for multiple sources and indices: + + for n1,n2 in arr1,arr2 + ... + for n,i in nodes,count(start,step) // or just count() + ... + +The upside is that iterators cache values. The downside is that each source is tested for the end of loop individually. +Jit manages to keep everything in registers. + +AOT is an interesting story. Lets write the minimal framework necessary to simulate daScript `Array` and `das_iterator`: + + #include + + #define __forceinline inline + + struct Node { + virtual void eval() = 0; + }; + + struct Context { }; + + struct Array { + char * data; + int size; + int lock; + }; + + template + struct TArray : Array { }; + + void array_lock ( Context & ctx, Array & arr ) { arr.lock ++; } + void array_unlock ( Context & ctx, Array & arr ) { assert(arr.lock); arr.lock --; } + + template + struct das_iterator; + + template + struct das_iterator> { + __forceinline das_iterator(TArray & r) { + that = &r; + array_end = (TT *)(that->data + that->size * sizeof(TT)); + } + template + __forceinline bool first(Context * __context__, QQ * & i) { + context = __context__; + array_lock(*__context__, *that); + i = (QQ *)that->data; + return i != (QQ *)array_end; + } + template + __forceinline bool next(Context *, QQ * & i) { + i++; return i != (QQ *)array_end; + } + template + __forceinline void close(Context * __context__, QQ * & i) { + array_unlock(*__context__, *that); + context = nullptr; + i = nullptr; + } + ~das_iterator() { + TT * dummy = nullptr; + if (context) close(context, dummy); + } + Array * that; + TT * array_end; + Context * context = nullptr; + }; + + void eval ( TArray & nodes, Context * __context__ ) { + das_iterator> node(nodes); + Node ** n = nullptr; + bool needLoop = true; + needLoop = node.first(__context__, n) && needLoop; + for ( ; needLoop; needLoop = node.next(__context__,n) ) { + (*n)->eval(); + } + node.close(__context__,n); + } + +Things are good in the land of disassembler: + + .LBB2_2: + mov rdi, qword ptr [r15 + rbx] // vtable_ptr = node_ptr + mov rax, qword ptr [rdi] // call_ptr = vtable_ptr[0] + call qword ptr [rax] // (*call_ptr)() + add rbx, 8 // node_ptr ++ + cmp r12, rbx // if node_ptr != data.end() keep looping + jne .LBB2_2 + +Prologue and epilogue are slightly longer, but everything is in the registers. + +`count` happens to be my daScript incantation of the day. Just add `count' when you need index. +AOT recognizes it, JIT recognizes it. Its fast. It keeps disassembler happy. + +To summarize when possible use daScript; it will do all those incantations for you. +For C++ +* use range based loop when you can +* at least cache size when you can't +* when writing performance critical C++ - cache everything +* reconsider rewriting in daScript + + diff --git a/site/blog/_posts/gc-in-the-wild.md b/site/blog/_posts/gc-in-the-wild.md new file mode 100644 index 0000000000..55b3c2425a --- /dev/null +++ b/site/blog/_posts/gc-in-the-wild.md @@ -0,0 +1,107 @@ +--- +title: Garbage collection in the wild +date: 2024-04-16 21:07:26 +tags: + - daScript + - GC +--- + + +What do a mafia boss and a garbage-collector have in common? +Both make sure you'll never see unwanted things again! + + + +Until you do that is. + +------------------ +First things first +------------------ + +How to turn it on:: + + options persistent_heap + +Or persistent_heap=true in the CodeOfPolicies. + +What happens when its on? + +* LinearHeapAllocator is replaced with PersistentHeapAllocator +* LinearStringAllocator is replaced with PersistentStringAllocator +* allocations become slightly slower +* `delete` now works well, as oppose to sometimes maybe +* `heap_collect` can be called from anywhere interpreted (yes, the whole stack) + +What happens when `heap_collect` is called? + +* mark phase is called on heaps +* data walker walks every global variable, and marks ranges of used memmory +* data walker walks stack and marks ranges of used memory on local variables, as well as arguments +* sweep phase is called on heaps, where unmarked areas are marked as free + +`finalizer` machinery DOES NOT GET CALLED on any objects of any kind. + +What happens when `delete` is called? + +* `finalizer` machinery is called for the specified argument. +* Eventually context level delete is called, which in turn calls heap->free. + +Do not forget to mark [[do_not_delete]] on the non-owning fields of the structure or class. + +There is a way to call delete for the local variable automatically:: + + let inscope foo = .... // will add delete foo in the finally section of the scope + +What happens with strings? + +* there is a way to delete them explicitly. I highly recommend to not do it. +* there is a way to turn on string interning via `options intern_strings` or through the CodeOfPolicies. +* functions which do not have `captureString` side effect delete string builder after their execution + +-------------------- +Second things second +-------------------- + +So, whats the problem? + +1. Mark phase is 'slow' i.e. it grows somewhat linearly with the amount of data it needs to walk. +2. Mark phase is blocking, i.e. nothing can be happening with the context during the mark phase. + +This is pretty fundamental. But fear not, we have plans, backup plans, and even more backup plans. + +For the embedded scenario, when possible, do not use DAS side as storage, and reset context after every use or after every frame. +Do not use GC, use linear heap allocator. In fact, by default, daScript is using linear heap allocator. It hardly deletes. +Reset is very very fast. There is a locking mechanism which prevents reset for a few frames, if need be. +Basically if it's a game script on top of ECS - linear allocator, reset, store data in ECS. + +When you have to use persistent_heap, the strategy is to call 'heap_collect' every so often, and see if it 'just does it', which is often the case. +There is always a way to call it less often - sprinkle some 'inscope' and 'delete' around the code with obvious lifetime. It might get you ways. +Keeping data in the ECS helps a lot as well, be that external solution or DECS. + +The idea is to call `heap_collect`, but not often at all. Have some automatic thresholds (mainly on strings). +daScript plugin does that with a good success. + +Move things to jobs or threads. `daslib/jobque_boost` is good for more things, than just parallelizing evaluation. Jobs and threads are separate contexts, they get collected separately and independently. + +------------------------ +Now for the nitty-gritty +------------------------ + +In no particular order. + +DAS is more like GO. Most of the local variables are on the stack. So the whole 'generation 0 first' thing is not an optimization. +Also, just like GO, a lot of future work will be about compiler determining lifetime of the objects and automatically disposing of them. + +let inscope on raw pod is pretty much always a go. In fact I'm planning for the compiler to add let 'secret_inscope' which clears the array data, without calling finalize. It`s safe like 99% of the time, and I can detect the rest. + +'captureString' sideeffect is important. Compiler is half way there - I'm currently working on the ways to infer it for the DAS functions, but it's already correct for the builtin ones. +When not present, string builder result is destroyed after the call. ie print("foo={i}") which creates temporary string will delete it soon after the print is called. + +------------------- +When all else fails +------------------- + +I've yet to see this - but when it does - the plan is to add custom project-specific macros to address project-specific lifetime. +At the end of the day C++ programs run without GC, and DAS can behave like on - but with a lot less work, and the benefit of calling GC when other methods fail. +Basically at this point You'll have to flip the script - it becomes a manual memory management with GC backup, not an automatic one with some manual hints. + diff --git a/site/blog/_posts/gen2-syntax.md b/site/blog/_posts/gen2-syntax.md new file mode 100644 index 0000000000..16a87d0d9d --- /dev/null +++ b/site/blog/_posts/gen2-syntax.md @@ -0,0 +1,137 @@ +--- +title: options gen2 +date: 2025-05-04 20:57:24 +tags: daScript +--- + +Its very easy to ignore reality. Ignoring effects of ignoring reality - now that's tricky. + + + +First things first. How do we go from gen1 (or gen1.5) to gen2? Fear not:: + + daslang -run-fmt -i -v2 hello_world.das + +It will convert in place (hence -i). + +Now, Boris, what on earth are you talking about? Why it's generation 2 syntax for daslang. +Its now enabled by default in the CodeOfPolicies. Its also -v2syntax (or -v1syntax) as a command line argument. +And of cause its an option:: + + option gen2 // that is + +Now, to the very first obvious question. Why new syntax? + +It's simple, really. We use AI more and more. And AI likes to write code a certain way. It tries to write daslang code, +but there were missing or misleading bits. New syntax fixes a lot of that. + +People tend to write code they are used to, as well. Daslang integrates into C++. Some cross-pollination is unavoidable; +turns out a tad more is needed. + +There are also issues with ambiguity, as well as misc parser problems. New syntax addresses a lot of those too. + +To the next obvious question. Why keep two? + +As we discovered daslang really has two target audiences. One likes to write script-like code in python-like language. +Another writes foundational systems from the C++ inspired background. Both gravitate towards the DSL's, but get there from the different perspective. +Code looks very different. Syntax now reflects it. + +--------------------------------------------- +So, lets give it some overview. What changed? +--------------------------------------------- + +Most obvious one is blocks, and constructions. Blocks now require explicit { }, and constructions now require ( ) around conditions:: + + for ( x in 0..5 ) { // notice () and {} + print("x = {x}\n" ) // notice no semicolon + } + if ( true ) { + bar() + } elif ( false ) { + foo() + } else { + par() + } + while ( something ) { + break + } + with ( someexpr ) { + pass + } + unsafe { + that_thing() + } + +Semicolon still separate expressions, but it is not required for the last one in the line. + +In type, function, and other declarations, we now use column almost exclusively:: + + def add ( a:int, b:float ) { // notice , though ; still works + return float(a) + b + } + +Field annotations are now using @ syntax:: + + struct Foo { + @do_not_delete a : Foo? + } + +All sorts of calls and member access can now be written via dot:: + + let a = SomeEnum.value // was SomeEnum value + foo.bar(args) // was foo->bar(args), can still use -> + someblock(args) // was invoke(block,args), can still use invoke + +All sorts of shortcuts for data initialization, including das figuring out move vs copy:: + + let a = [1,2,3,4,5] // was let a <- [[auto 1;2;3;4;5]] + var b = (1,2.,"3") // was var b = [[auto 1,2.,"3"]] + var c = {1=>"1",2=>"2"} // was var c <- {{1=>"1"; 2=>"2"}} + var d = Foo(a=1,b=2.) // was [[Foo() a=1,b=2.]] + let e = default // was [[FooBar]] + +We now have tuple variable expansion:: + + var (a,b) = some_tuple_function() + print("a={a} b={b}, ab={a`b}\n") // can access fields, as well as combined tuples + for ( (a,b) in some_tuple_container() ) { // works the same in the for loop + print("a={a} b={b}, ab={a`b}\n") + } + +Missing comprehensions:: + + var a = {for (a in 0..20); a=>"{a}"; where is_odd(a) } // table comprehension + +Capture is now spelled explicitly (old way still works with &, -> and := symbols):: + + var a <- @ capture(move(a),clone(b)) ( x:int ) { + b.set(x) + return a[x] + } + +Blocks (local functions, lambdas) no longer need piping on initialization. Some blocks require nothing at all:: + + var a <- $ ( a,b ) { a + b; } // local block + foo() { // same as foo(${print("bar\n);}) + print("bar\n"); + } + foo() $ ( a, b ) { // same as foo($(a,b){print("a={a} b={b}\n")}) + print("a={a} b={b}\n") + } + +String builder now supports format:: + + var f = 13.; f ++ + print("f = {f:3.2f}\n") // same as std::fmt in C++ + +Typeinfo command is now outside of ( ), and < > now requires type:: + + typeinfo sizeof(type) // typeinfo(sizeof type) + typeinfo has_field(bar) // typeinfo(has_field type) + +There are bunch of other small things, and I did my best documenting them. + +The best part is most of advanced syntax works with gen1 as well. Only [[ ]] and similar constructions are gone, +and even those are just off via CodeOfPolicies. You can convert to version 1.5 - which will fix obsolete syntax, +and keep the 'script' portion there. Or you can go straight to version 2. Its really down to culture, +and task at hand. diff --git a/site/blog/_posts/hectic.md b/site/blog/_posts/hectic.md new file mode 100644 index 0000000000..af622bd575 --- /dev/null +++ b/site/blog/_posts/hectic.md @@ -0,0 +1,67 @@ +--- +title: Hectic +date: 2023-04-17 10:44:39 +tags: + - OpenAI + - Telegram + - daScript +--- + +Hectic is exactly the word which comes to mind, when I look at the commit history for the month. + + + +There are bug-fixes. Always. Little things. Corner cases. Its especially good, when you are trying to change something big. + +Things like properties. Properties are now proper functions, and proper operators. We had generic . operator for a while now. +Looked like this + + def operator . ( t:Foo; name:string ) + +Now, with some generic magic, like [constant_expression] you can even make it return different types. +Feels like a hack, though. Feels like good old C++ programming, which is exactly what I've been trying to avoid. +So now we also have something like this + + def operator . yourFieldNameHere ( t:Foo ) + +Proper function. Takes what it does, returns what it wants. More so, ManagedStructureAnnotation now implements its properties in exactly that manner. +It so happens, that old implementation was const-insensitive. Now it's proper const correct. So the big codebase had to adjust some sections of code, to also be const correct. +Took couple weeks, give or take. Made code cleaner. Found couple real bugs, like you always do. + +Then alone came telegram API. Its huge, so I had to automate bindings yet again. Got HTML page with documentation, converted to XHTML. +Got PugiXML (hi Zeux) and wrote some basic bindings. Manually. Because its there to stay, and it needs to be extra robust. +With PugiXML got the XHTML parsed, generated huge 10k lines file with everything in it, including JSON serialization. + +Found out it takes 4-5 seconds to compile. That is awful. It's like being back to C++ land. Again. Tried to optimize the compiler even more. +Thats hard. At this point outside of major expression\type rewrite, there are no more simple gains. Either way got it down to 3.5-4 seconds. +Than put on my thinking hat, and turned those functions into generics. And its down to 0.3 sec. You never use entire API, its too big. +So we are back in the land of instant restarts and reloads, like we should. + +Then there was JSON. Some API prefer not having 'default' or 'empty' values in there, to the point where they consider it 'broken'. +Also You can prompt OpenAI to output JSON. It mainly works. Sometimes it does not. Sometimes it outputs mildly broken JSON. +Well hello def try_fixing_broken_json ( var bad:string ). It fixes some of it, otherwise retry the request. + +Then there were UTF8 utils and JSON. The parser used to ignore \u. UTF32 decoder was non existent. Hacks propagated. But no more. +Hello def decode_unicode_escape ( str:string ), hello more bugfixes. Its now reasonably robust. +One day I'll have a problem which requires performant base64 encoder, utf8 utils, and other goo. Then I'll optimize it. Until then it works. + +Afterwards I needed a database. So I added Sqlite3 and wrote some SQL. It needs more macros. It probably needs something like LINQ. +That will take a few days to setup, once I have a real-world problem which needs it. Mine was too small - so a few wrappers did it. + +Then came few high-performance demos, which needed high-performance low-level intrinsics. Yes, in daScript. +memset16, memset32, memset64, memset128 - because filling things with 8 byte values is nice, but i need std::fill only better. +gather_scatter, gather_store, gather_store_mask_not_equ, and bunch of more - because software rasterization in the interpreted language is where its at (you'd be surprised). + +Bunch of fixes in JIT to support the above, along with more testing on real world scenarios. JIT rocks. <2ms rasterization on the 1920x1080 image was surprising. +I guess it's not 1992 any more. + +Than came the installer. CMake install, all that jazz. Because soon we will be releasing pre-built binaries. At least for windows and mac. +Probably with everything. Possibly with everything but LLVM modules first. So GLFW\SFML\BGFX or command line apps. Images and fonts. Imgui. +Should come with batteries included. + +Installer brought up some bugs in the CMake compilation scripts. Turns out default build was not using SSE2, let alone AVX512 under MSVC. +Also CLANG build for windows needs to be revisited. Integrated Microsoft-clang (sic!) builds fine. LLVM ones, not so much. I'll fix that one day soon. +I'll probably switch to LLVM14 or even LLVM15 for the LLVM\JIT bindings, now that I'm at it. I so want proper JIT on Mac M1\M2. Because reasons. + +daScript needs ML framework, though. OpenAI is nice, but it needs real ML framework. There is a branch which has libTorch compiled in. Takers? + diff --git a/site/blog/_posts/how-to-edsl.md b/site/blog/_posts/how-to-edsl.md new file mode 100644 index 0000000000..31823cc52f --- /dev/null +++ b/site/blog/_posts/how-to-edsl.md @@ -0,0 +1,135 @@ +--- +title: How to EDSL +date: 2025-06-05 19:56:40 +tags: + - daScript + - edsl +--- + +There are ways to ~~die~~ EDSL, and there are ways to ~~die~~ EDSL. + + + +This is a bit off tangent, but using the right language to define and solve problem is paramount. +Lets take two extremes: a javascript program to compute a fibonacci sequence, and an equivalent system of polynomials, which when solved produces the same result. +Like seriously see Matiyasevich's Theorem (or MRDP Theorem) for more details. Gotta love those Diophantine equations, but they are not exactly very readable. +Writing them down for the fibonacci would be way harder than the classic example from [the main page of DAS website](https://daslang.io). + +If you came from C++, template meta-programming is probably your EDSL tool of choice (sprinkle with preprocessor tricks on as-needed basis). +You'll end up with boost::spirit if you are lucky. That, and clear understanding, that this is not the way. +Perhaps you'll discover other forms of code generation in the process. Something-something->C++ or even C. + +If you remember LISP, or played with HAXE, or just know better - then maybe DAS is for you; and in more ways then one. + +Lets start with examples. First there were reader macros: + + require daslib/regex_boost + + var r1 <- %regex~[\w\.+-]+@[\w\.-]+\.[\w\.-]+%% + +Just like that. It reads random input, it generates bunch of code\data. It can even tell when to stop, although default implementation looks for %%. +Its all in the AstReaderMacro. After all it does not get any more free-form than that. + +But wait, there is even more. It can transform text and just output text. Which is _then_ parsed: + + require daslib/spoof + + let template_basic_value = %spoof_template~(tname,tvalue) + struct %tname { + value = %tvalue + } + %% + + %spoof_instance~template_basic_value(Foo_1,1)%% + +Its very strong and flexible. But... we are not here for that. Surely manual sunrise is fun, but automatic one is a lot more practical. +Luckily we have several good examples. Lets start with match: + + require daslib/match + + [sideeffects] + def static_array_match(A : int[3]) { + match(A) { + if (fixed_array($v(a), $v(b), $v(c)) && (a + b + c) == 6) { // total of 3 elements, sum is 6 + return 1 + } + if (fixed_array(0, ...)) { // starts with 0 + return 0 + } + if (fixed_array(..., 13)) { // ends with 13 + return 2 + } + if (fixed_array(12, ..., 12)) { // starts and ends with 12 + return 3 + } + if (_) { + return -1 + } + } + } + +It looks like DAS, but its not exactly it. There is a lot of code-transforming machinery behind 'match' AstCallMacro. +I specifically don't go into implementation details - nothing reads better than code anyway. It's pretty small, too. + +And the reason that it's pretty small, is the reification. Turns out 'match' implementation is written in yet another EDSL. +All this qmacro_... struff in 'daslib/templates_boost' is, in fact, another set of macros. + +Which in turn are written on top of 'daslib/templates' via replace EDSL. +I'm telling u, its turtles all the way down. + +That, and sideways. If you take a look at spoof again - there is argument parser: + + require peg/peg + + [macro_function] + def spoof_args(invocation : string; blk : block<(var res : array>; errors : array) : void>) { + parse(invocation) { + var element_list : array> + rule(WS, "(", WS, *comma_separated_elements as els, WS, element as last, WS, ")") { + els |> push <| last + return <- els + } + var comma_separated_elements : tuple + rule(WS, element as e, WS, ",") { + return e + } + var element : tuple + rule(ident_ as e, WS, "=", WS, element_value as v) { + return (e, v) + } + rule(ident_ as e) { + return (e, "") + } + var sub_element : string + rule("\\", "{set(0..255)}" as Ch) { // escape + return string(Ch) + } + rule("#", string_ as e, commit) { // contents of the string (do we need unescape?) + return e + } + rule(string_ as e, commit) { // "e" + return "\"{e}\"" + } + rule("(", element as e, ")") { // (e) + return "({e})" + } + rule("{not_set(')',',')}" as e) { // any character except ')' and ',' + return e + } + var element_value : string + rule(sub_element as head, *sub_element as tail) { + return head + tail |> join("") + } + var ident_ : string + rule("{set('a'..'z','A'..'Z','_')}" as fc, "{*set('a'..'z','A'..'Z','_','0'..'9')}" as tail) { + return fc + tail + } + } + } + +And just like that we are back in the land of boost::spirit. The DAS way. With call macros, reader macros, ast rewrites, and many other tricks. +That, and absolutely no preprocessor. + +There are many more tricks up my sleeve. There are generics, operator and property overloads, nameless blocks, and even pass macros. +Sky really is the limit. There is no excuse to keep writing boilerplate code. Imagine how do you want your code to look, and then try to get very close to it. + diff --git a/site/blog/_posts/instruments.md b/site/blog/_posts/instruments.md new file mode 100644 index 0000000000..adea920617 --- /dev/null +++ b/site/blog/_posts/instruments.md @@ -0,0 +1,120 @@ +--- +title: Implementing instruments +date: 2022-12-11 10:36:22 +tags: + - daScript +--- + +During the last few days I took a small detour from the land of the JIT, to the land of the profiler. + + + +There is of course the `DAS_ENABLE_PROFILER` define which can give some decent idea of hot spots, +but it was brought to my attention that we need to go deeper. + +My first thought was to extend the built-in profiler capabilities with the timing information, +add exporting capabilities, and be done. +Built-in profiler has relatively low overhead and is already half the way there. + +I might still do it, but there are downsides. +First and foremost its a build configuration option which requires separate build configuration or separate executable. +Second disadvantage is that its implemented in C++. Long term direction is to rewrite as much of daScript in daScript as possible. +Binding additional optional interfaces is not exactly conducive for the goal. +Lastly we have perfectly good instrumentation which is already bound and used by the debugger. Expanding on it would only be natural. + +At the end of the day results can be seen in the `profiler.das` and `profilter_boost.das` parts of `daslib`. + +Instrumentation starts with creating debug agent in form of `DapiDebugAgent`. +Its an event listener, with user defined behaviors for the specific events. For the profiler we will only be interested in the following ones + + def abstract onInstall ( agent:DebugAgent? ) : void + def abstract onUninstall ( agent:DebugAgent? ) : void + def abstract onCreateContext ( var ctx:Context ) : void + def abstract onDestroyContext ( var ctx:Context ) : void + def abstract onInstrumentFunction ( var ctx:Context; fn:SimFunction?; entering:bool; userData:uint64 ) : void + +`onInstall` is called when the debug agent is installed. When `fork_debug_agent_context` is called + +1. a clone of the current context is created +2. an argument function address is used to call an agent creation function +3. in its turn it creates an instance of a 'DapiDebugAgent' and registers it with `install_new_debug_agent` +4. `onInstall` is called on newly created debug agent, once is registered + +There is a lock on the global debug agent API so that no additional locking action is required on the implementation side. + +The profiler needs to be installed during the compilation time, so macro initialization is exactly the spot to put it. +`[_macro]` annotation makes it so that the function is invoked during the macro module initialization (as well as marking module to be a macro module). +We call `is_compiling_macros_in_module("profiler")` to make sure it only happens once and at the right time. + +Here is what happens during the initialization + +1. profiler settings are collected from the command line arguments +2. initial reference time tick is collected to mark the application starting point +3. if profiler log is specified, the appropriate log file is created, and headers are written + +`onUninstall` is called when the debug agent is uninstalled. That happens during the daScript environment shutdown. +Its only used to close the log file. + +`onCreateContext` is called when the new daScript context is created or cloned. This is where the instrumentation comes in handy. +Calling `instrument_all_functions(ctx)` ensures that all context functions will invoke `onInstrumentFunction`. +It is safe to call instrumentation multiple times, but checking if specific context code has already been instrumented saves performance. +Cloned contexts share code and jobs or thread are always cloned. +`ctx.getCodeAllocatorId` returns unique ID of the context code, and thus can identify multiple instrumentations. + +Obviously we don't want instrumentation to happen for just any context. We are not interested in the macro contexts, +contexts which are used for the folding during the compilation, or contexts which are being debugged. +Luckily those have specific context flags in the `ctx.category` which we can verify. + +`onDestroyContext` is called when the daScript context is destroyed, after the `shutdown` infrastructure. +What we do there is dump the log and the profiler output for the context, as well as manually delete the memory associated with storing the profile data. + +This is where performance spikes will occur. Making that long log file takes time. +Once the logging is separated into its own API, I'm going to move the logger into a separate thread. +In the meantime logging is off by default, and requires `--das-profiler-log-file` command line argument. + +`onInstrumentFunction` gets called on the instrumented contexts, every time a function is entered or leaved. +`SimFunction` is provided to identify specific function. +Its possible to assign additional user data to the function, but we don't need it for the profiler. + +In this implementation I choose to collect entries, so that they can be later analyzed and written in the log. + + struct PerfEvent + fun : SimFunction? // function call + ts : int64 // delta time in nanoseconds from the application start + entering : bool // entering or leaving the function + +This structure happens to correspond well with the [Google Chrome Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview) +Open Google Chrome, Press F12, go to performance section, load the log, enjoy the view ![Call tree](/images/call_tree.PNG) +There are other tools which accept this format as well. + +By default profiler outputs full call graph. + + [I] wait_1_msec 1 1000300ns + [I] main 1 0ns + [I] builtin`intptr C1? 1 300ns + [I] fibR Ci 7 1714900ns + [I] fibR Ci 27 1183900ns + [I] fibR Ci 56 993100ns + [I] fibR Ci 105 566400ns + [I] fibR Ci 126 445400ns + [I] fibR Ci 140 187400ns + [I] fibR Ci 120 140500ns + [I] fibR Ci 81 39500ns + [I] fibR Ci 55 30300ns + [I] fibR Ci 21 5000ns + [I] fibR Ci 12 5200ns + [I] fibR Ci 2 300ns + [I] fibR Ci 1 200ns + +It includes call count as well as total time in nanoseconds for each call. + +`--das-profiler-manual` is there so that recording does not happen once the application starts. +Instead application is expected to call `enable_profiler(this_context())` and `disable_profiler(this_context())` for the sections which we are interested in. + +Overall this little project prooved a success. Implementation is extremely manageable and expandable. +Its a reasonable pattern to follow for the other instrumentation tools, which surely are things to come (did anyone say memory profiler?) + +The only significant downside of this profiler implementation is its performance overhead. +Instrumentation on the macro level (adding prologue and epilogue to each function) would be more robust, as well as compatible with AOT and JIT. +At that point sampling based profiler would be in order, ideally integrated with some existing profiling infrastructure. +That however, is a topic for a whole separate exercise. diff --git a/site/blog/_posts/its-not-a-script.md b/site/blog/_posts/its-not-a-script.md new file mode 100644 index 0000000000..fd3627f23d --- /dev/null +++ b/site/blog/_posts/its-not-a-script.md @@ -0,0 +1,79 @@ +--- +title: It's not a script +date: 2022-12-31 12:03:22 +tags: daScript +--- + +Whats in a name? The sheer fact that I'm writing this post implies a problem. +"It's not a script" is something we had to say on numerous occasions. + +- `Boris ELI5 script?` +- Script is a simple programming langue you write your game logic in. When it gets slow you rewrite it to C++. + +daScript is none of the above. + + + +Its not a simple programming language. Its statically typed multi-paradigm programming language with macros. + +This looks like any other script: + + def fib(n) + if n < 2 + return n + return fib(n - 1) + fib(n - 2) + +It's a lie. If you peek behind the smoke and mirrors via `options log` you'll see the next level of reality: + + [fastcall] + def private `fib ( n:int const explicit ) : int const + return (n < 2) ? n : (__::`fib(n - 1) + __::`fib(n - 2)) + +Which comes as a result of instancing of a generic function in the current module. +Types are inferred automatically, upon instancing. `fastcall` annotation is assigned by an optimizer. + +At its lowest level daScript is C. It has pointers and pointer arithmetics. Static arrays. Structures. +daScript strings are very much C strings. MemCopy. In fact `=` operator is a memcpy. +There is no hidden overhead, or abstraction penalties. + +Sure its missing some very specific syntax sugar, like `union` and `bitfields`, but it nothing you can't rectify with macros. +There is some light ABI overhead when interacting with daScript Context, but none inside of the Context. +At the end of the day low level daScript code can be converted to C, and vice versa. + +The next level of abstraction comes with blocks and lambdas, dynamic arrays and tables, classes, generators and iterators. +It still looks like any other script (python anyone? ruby?): + + def each_even_in_range(start, stop) + return <- generator() <| + for t in start..stop + if t % 2 == 0 + yield t + return false + + [export] + def main + for t in each_even_in_range(0, 10) + print("t = {t}\n") + +The reality, however, gets [more and more peculiar](https://tinyurl.com/mpjh4fuz). + +After that come macros. Think LISP, or HAXE. daScript is the ultimate EDSL, expanding it is so easy. +There are even reader macros and comment parsing macros. +[Something like adding interfaces to an OOP model](https://tinyurl.com/27c93mkf) takes only [couple pages of code to implement](https://tinyurl.com/49ym77ey). + +A friend recently compared daScript to an iceberg, with the `script like` portion sticking on top. +There is of cause a question of `how do you learn all this machinery?`. But the alternative questions are a lot less appealing. + +In daScript conception the idea was 'never have to rewrite to C or C++`. So far never had to. +In fact naively written daScript typically outperforms naively written C++, and similar optimizations are available in both. + +The upcoming JIT often outperforms C++ on similar tests. On the 3D math heavy code the results can be dramatic. +`pathtracer` computes 16.3 mil rays a second with JIT, and 5.1 mil rays with AOT (and one day i'll write a detail post as of why). + +And thats exactly why daScript is not a script. + +* its not simple +* you don't write your game logic in it, you write your everything in it. yes your physics, your renderer, your shaders, and, one day, perhaps your kernel drivers. +* no, it does not get slow, and you never have to rewrite it to C++. or C. ever + +Its obscure enough to be renamed. But those *.das files aren't. daSlang? dasLang? diff --git a/site/blog/_posts/linq-that.md b/site/blog/_posts/linq-that.md new file mode 100644 index 0000000000..7bcc01ca4c --- /dev/null +++ b/site/blog/_posts/linq-that.md @@ -0,0 +1,124 @@ +--- +title: LINQ? +date: 2025-10-08 17:27:00 +tags: daScript +--- + +LINQ? But Boris, DASLANG already has high-order functions. + + + +Like seriously, just require daslib/functional and you are done. Its only 340 lines. +It reads like a Haskell tutorial. Well. Almost. Its concise. Its lazy. Its also slow. + +Lets start with an example + + require daslib/functional + + [export] + def main { + let seq = (range(10) + |> each() + |> map(@(x : int) => x + 1) + |> filter(@(x : int) => x % 2 == 0) + |> reduce(@(a, b) => a * b)) + print("2*4*6*8*10 = {seq}\n") + } + +What we have here, is a chain of 3 iterators, and a reduction loop. Yea, dog slow. +I think I'm going to deprecate it eventually. I never liked it. I could never remember function names. + +------------------- +I always liked LINQ +------------------- + +There is a better way + + require daslib/linq_boost + + [export] + def main { + let seq = (range(10) + .each() + ._select(_+1) + ._where(_ % 2 == 0) + .aggregate(1, $(a, b) => a * b) + ._fold() + ) + print("2*4*6*8*10 = {seq}\n") + } + +On the first glance its very similar. Minus the fold thing of cause. And the anonymous notation for the 'select' and 'where'. +Also it uses blocks instead of lambda. But the overall feel is the same. + +Performance however is not. If the 'fold' was removed it would produce code, very similar to that of functional. +Inside the fold lies the magic + + [export] + def main { + var seq = invoke($ ( var source : iterator ) { + var pass0 <- [for (x in source); x + 1; where ((x+1) % 2 == 0)] + var pass1 = pass0 |> aggregate(1, $(a, b) => a * b) + delete pass0 + return pass1 + }, unsafe(each(range(10))) ) + print("2*4*6*8*10 = {seq}\n") + } + +Its obviously missing the select-where-aggregate onto one function fold. That and many more are coming soon. +But its already two orders of magnitude faster, to the point where its already useable. + +It's also compatible with DECS, although more support is needed + + for (i in range(3)) { + create_entity <| @(eid, cmp) { + cmp.eid := eid + cmp.index := i + cmp.text := "number_{i}" + } + } + commit() + var one_then_two = (from_decs($(index : int; text : string){}) + ._where(_.index < 2) + .reverse() + .to_array() + ) + +---------------------------------------- +Now, why is it possible to make it fast? +---------------------------------------- + +* only blocks are supported; no functions, no lambdas +* both array and iterator sources are supported + - only iterators are consumed +* all intermediate calculations are done on top of the arrays + - temporary data is immediately deleted +* everything folds + - if it does not already fold, it will + +Its all very early. Its all very promising. + +--------------------- +What is already there +--------------------- + +* all basic operations from C# LINQ and a few extra + - the list is on the very top of [daslib/linq](https://github.com/GaijinEntertainment/daScript/blob/master/daslib/linq.das) +* some obvious folds (select/where, order/distinct and such) +* basic DECS integration + +---------- +Whats next +---------- + +* more folds +* wide support for DECS (including edit) +* support for SQL and dasSQLITE +* possibly custom reader macro for even more familiar syntax? + +Suggestions are welcome, as always. +Also, folds are easy to write. See [daslib/linq_boost](https://github.com/GaijinEntertainment/daScript/blob/master/daslib/linq_boost.das) for details. + + + + diff --git a/site/blog/_posts/of-pipes-and-blocks.md b/site/blog/_posts/of-pipes-and-blocks.md new file mode 100644 index 0000000000..fec06f16b5 --- /dev/null +++ b/site/blog/_posts/of-pipes-and-blocks.md @@ -0,0 +1,96 @@ +--- +title: Of pipes and blocks +date: 2024-09-17 22:16:07 +tags: + - daScript +--- + +Das is not a write-only code. The idea is we read more than we write. +There is "write as you speak", but there is also "read as you listen". + + + +Picture this + + verify_if_even(map(each(range(50)), @(x:int) => x+x )) + +And this + + ( range(50) + |> each() + |> map( @(x : int) => x + x ) + |> verify_if_even() + ) + +I can read both. First one is hard. I need to flip it in my head first. Second one happens in the order its written. +Welcome to pipes. + +Lets lay some more pipes + + add_element(some_collection, 12) + some_collection |> add_element(12) + +I can read both. First one is ever so familiar. Second one makes sense a lot more. + +Even more pipes + + field.init |> move_new <| clone(expr.init) + +It's a verb. Its a bit obscure, but at least I don't have to guess what we are moving to where. + +Left pipe stacks. Plus we can always skip empty bracers + + take_3_elements <| 1 <| 2 <| 3 + +We can get silly too + + 1 |> take_3_elements <| 2 <| 3 + +Speaking of left pipe. Its mainly there for blocks. +Ruby blocks were an initial inspiration, but this days its more common than not. +So lets pipe some blocks + + get_component(renderSettings) <| $(var shadow : ShadowSettings) + shadow.powWeight = 0.6 + +Or better yet + + renderSettings |> get_component <| $(var shadow : ShadowSettings) + shadow.powWeight = 0.6 + +Lets read it. We take renderSettings. We get it's component, which is shadow of type ShadowSettings, and then we change powWeight. + +Almost good. Almost. Every time there is a right pipe and then block - we know what comes next the moment we see that pipe. +Its a block. Its always a block. Actually, sometimes it's lambda, or local function. But you got the gist. +So lets make it even better + + renderSettings |> get_component $(var shadow : ShadowSettings) + shadow.powWeight = 0.6 + +Notice, no left pipe. This is new. Perhaps old syntax will go away entirely at some point. Old initialization sure will. + +We can pass types + + some_type_function(type) $ ... + type |> some_type_function() $ ... + +But we can make it a type-function + + [type_function] + def some_type_function ( t : auto(TT); ... + + some_type_function $ ... + +Its the same thing. Only not really. Because 'some_type_function' becomes keyword, and can no longer be specialized. But it sure looks cool. +It can take multiple types + + some_other_type_function_which_takes(and,the,rest,of,the,arguments) <| of \ + <| which <| some <| are <| piped + +Just like you say it. + + + + + + diff --git a/site/blog/_posts/pattern-matching.md b/site/blog/_posts/pattern-matching.md new file mode 100644 index 0000000000..f63ca62bf6 --- /dev/null +++ b/site/blog/_posts/pattern-matching.md @@ -0,0 +1,73 @@ +--- +title: Pattern matching +date: 2023-02-07 11:45:17 +tags: + - daScript + - ChatGPT +--- + +In the world of computer programming, there is a concept known as pattern matching. +This technique allows us to take a complex value, such as an array or a variant, and compare it to a set of patterns. +If the value fits a certain pattern, the matching process continues and we can extract specific values from that value. +This is a powerful tool for making our code more readable and efficient, +and in this section we'll be exploring the different ways that pattern matching can be used in daScript. + + + +Previous paragraph was generated by ChatGPT. I've also asked it to be written in [Richard Feynman](https://en.wikipedia.org/wiki/Richard_Feynman) style and now I can't stop hearing it in my head. + +But why stop there. Behold the new and exciting [pattern matching documentation](https://github.com/GaijinEntertainment/daScript/blob/master/doc/source/reference/language/pattern_matching.rst). + +Writing documentation is hard. We have all those auto-documenting tools. Back in the 1812 I discovered [doxygen](https://www.doxygen.nl). daScript has something similar implemented via comment macros. +We generate rst. From the rst we generate PDF version, as well as WEB version. But at the end of the day someone has to write all this text around samples, explain what the samples do, make it look professional. + +There is also the pesky question of being (and not being) native speaker of English language. After all all your base are belong to us. Some? Any? +Whats good for the blog is not good for `THE OFFICIAL DOCUMENTATION OF THE WORLDS MOST SERIOUS PROGRAMMING LANGUAGE` (all caps intended). + +In the ideal scenario I would like to just copy-paste my unit tests for each type of the pattern matching available, and the documentation magically appears. +Turns out that in 2023 the reality is not that far. I occasionally had to ask ChatGPT to stick to correct daScript syntax and well formed rst format. +I had to modify couple samples for the concepts to be more transparent. At times it feels like talking to a student with reasonable attention span and good sense of language. + +Now, this post would not be complete without some useful information about implementation details. After all the documentation is there and praising ChatGPT is just a trend. +So lets get right to it. + +daslib/match is how pattern matching is implemented. Its 100% implemented in daScript using macros. The rabbit hole is getting deeper and deeper. + +I've added support for `keywords`. Its now possible to specify a keyword and wether it needs an `oxford comma` for the significant white space. +That way instead of + + match(expr) <| + if ... + ... + if match_type(type,blah) + ... + +We can write + + match expr + if ... + ... + if match_type blah + ... + +Keyword is a one-way street. Once something is keyword, it can no longer appear anywhere other than keyword context or require statement. +It may eventually leak into few other constructs (like right side of the `is` and `as`), but for now its very limited. + +I've extended support for the `canVisitArguments` in the `CallMacro`. Its now down to individual arguments. +After all we don't want to infer anything inside `quote`, nor do we want to see any changes for similar macros (`qmacro_function` comes to mind). + +`match` does not start matching, until match expression is resolved. There are several patterns implemented - and I've tried to stick to original daScript syntax with each of them. +It was easy, because its actually original syntax with the standard AST. + +[[Expression]] matches required as and is operations. They were already in place. For the rest of the structures I had to indicate it with [match_as_is] annotation. +There is no good way to check if function exists at compilation time. Think macros. Think potentially infinite inference. So for expressions it just happens. +The rest of them need an annotation. One day there will be a way to indicate that handled type has similar machinery as well. + +Lastly `match` does not actually create variables (via &), even though I had it in in earlier implementations. Because `lifetime` of the expression can change, and local references are unsafe. + +Now for the question of the year: "what took you so long?". It seems like an obvious concept. It could easily be in the core language. +It is also something I wanted to be implemented with macros only with the very specific use cases in mind. +I don't have a habit of using pattern matching in my day to day programming, because C++ does not have one (and daScript did not have one). +I am very much looking forward to developing such habit. + +The entire implementation is ~500 loc, and the code is written to serve as ~~en example~~ a sample of more advanced macros. diff --git a/site/blog/_posts/performance-and-aliasing.md b/site/blog/_posts/performance-and-aliasing.md new file mode 100644 index 0000000000..754524a4c1 --- /dev/null +++ b/site/blog/_posts/performance-and-aliasing.md @@ -0,0 +1,104 @@ +--- +title: Performance and CMRES aliasing +date: 2023-01-21 14:31:03 +tags: + - daScript +--- + +From dascript.org "... is high-level, statically strong typed scripting language, designed to be fast ..." + +With an emphasis on fast. Performance first, last, and foremost. + + + +There are many ways, how we make it fast. But today we are going to focus on CMRES (Copy-or-Move Result) aliasing. + +Here is basic idea. Say we have a function 'set', which returns result of type `Foo` like this + + struct Foo + a : int + + def set ( a : Foo ) + var b = [[Foo a = 13]] + b.a += a.a + return b + +From the language perspective is semantic equivalent of + + def set ( a : Foo; var cmres : Foo ) : Foo& + var b = [[Foo a = 13]] + b.a += a.a + cmres = b + return cmres // address only + +After optimizations we get the following + + def set ( a : Foo; var cmres : Foo ) : Foo& + cmres = [[Foo a = 13]] + cmres.a += a.a + return cmres // address only + +Now, consider this code + + var a = [[Foo a = 1]] + var b = set(a) + +Interpreter ABI requires caller to allocate function result. That means that function knows nothing about where CMRES came from. +More interestingly daScript compiler assumes that CMRES does not alias anything, including input parameters or global variables. + +As the result the example above will be equivalent of + + var a = [[Foo a = 1]] + var b : Foo + set(a, b) // b passes as CMRES directly + +This is neat optimization which saves copying from result of `set` to the destination (which is b). +Now consider the following code + + var a = [[Foo a = 1]] + a = set(a) + +The same optimization can't be safely performed, because `cmres = [[Foo a=13]]` will clobber input argument a, +which will result in a.a = 26. + +So, how do we make it fast? There is `no_aliasing` setting in the CodeOfPolicies, as well as `options no_aliasing`. +When enabled, it produces the following error message + + hello_world.das:14:8: + a = set(a) + ^^^ + 14:8 - 14:10 + 40211: function set result aliases argument a + some form of ... a ... = set( ... a ... ) + +This gives programmer opportunity to find out, if otherwise common optimization has been disabled. + +There is also an option of telling the compiler to ignore CMRES aliasing for the specific function altogether. + + [never_alias_cmres] + def set ( a : Foo ) + var b = [[Foo a = 13]] + b.a += a.a + return b + +this will force the code above to produce a.a = 26. Surprisingly there are good uses for it. +As well as for the `[alias_cmres]` annotation, which will always disable the optimization. + +The more interesting aspect of it is how daScript programming can be a dialog with the compiler, +as oppose to more traditional monolog-style code writing. Don't just write it, and hope for the best. +Check out what it actually produces. + +I often program with `options log` to see, how my code ends up working. +Some type related questions are best answered with `options log_infer_passes` - you can see your macros expanding pass-by-pass. +There are `options log_stack`, `options log_nodes`, and many more. One day I'll even write an entire post about them. + +Now there is even `options log_aliasing`. For the example above it will tell you this + + ALIASING: + function set CS<::Foo> returns by reference + argument 0 aliasing result with type Foo const + hello_world.das:12:8: set aliases with CMRES, stack optimization disabled + +So don't just talk to the compiler. Listen to what it has to say and you'll never have to walk alone. + +P.S. And now of something completely different. Did you know we have integrated (into vscode) debugger? Did you know it can now debug macros? diff --git a/site/blog/_posts/quality-of-life.md b/site/blog/_posts/quality-of-life.md new file mode 100644 index 0000000000..1bdf3e6832 --- /dev/null +++ b/site/blog/_posts/quality-of-life.md @@ -0,0 +1,294 @@ +--- +title: quality-of-life +date: 2026-01-22 20:21:45 +tags: + - daScript + - daslang +--- + +Sometimes little things make big difference. + + + +Type less. Like somewhat less. When its obvious. + +In no particular order. It's been a minute - so I probably forgot something good from 2025. + +------------- +String format +------------- + +It's just like fmt. Because it is fmt. + + [export] + def main { + var a = 123.456789 + print("{a:.8g}\n") // that syntax. its basically print("{fmt(":.8g",a)}\n") + // and it does not need 'require strings' + } + +------------------- +We can expand tuple +------------------- + +And not just in the let. + + [export] + def main { + var a = [(foo=1,bar=2.), (foo=3,bar=4.)] + var (F,B) = a[0] // yes, in the let + print("F={F}, B={B}\n") + for ( (f,b) in a ) { // but also in the loop iterators + print("f={f}, b={b}\n") + } + } + + +---------- +It's a for +---------- + +'for' is almost 'zip'. Almost. := into clone makes a lot of sense. Like define clone, and then := it. +'each' is the other white meat. Like define 'each' and then iterate like its nothing. + + [expect_any_bitfield(b)] + def each(b) : iterator { + let bits = uint64(b) + return <- generator() <| $() { + var mask = 1ul + for (i in range(typeinfo sizeof(b) << 3)) { + yield ((bits & mask) != 0ul) + mask <<= 1ul + } + return false + } + } + + ... + for ( x in bitfield(0x1317) ) // no need to type each(bitfield(0x1317)) + +---------------- +It's an operator +---------------- + +It's fun with properties. Because EDSL matters. Sometimes fancy assignments carry meaning. +Sometimes it's performance based, but with properties it's one off - it sort of matters. Kinda. + + struct Foo { + a : int + def operator . A { return "{a}"; } + def operator . A := ( v : string ) { a = to_int(v); } + def operator . A &= ( v : string ) { a &= to_int(v); } + } + + var f : Foo + f.A = "123" + f.A &= "1" + +It's even more fun, when it's indices. Because performance matters. Sometimes its EDSL. +But when its a bit array - overwriting [] &= speeds this up 3x. +Trust me. Or better yet, don't. Tests are in the code-base, under examples/profile/bool_array_tests/test04.das +Yes, all 3, interpreter, AOT, JIT. No, LLVM does not fold it. + + struct Foo { + a : uint + def operator [] ( index : int ) : bool { + return (a & (1u << uint(index))) != 0u + } + def operator [] =( index : int, v : bool) { + let idx = uint(index) + if (v) { + a = a | (1u << idx) + } else { + a = a & ~(1u << idx) + } + } + def operator [] &=( index : int, v : bool) { + let idx = uint(index) + if (!v) { + a = a & ~(1u << idx) + } + } + } + +---------------- +Consume argument +---------------- + +Sometimes it eats inputs. But noone reads documentation. + + [consume(a)] + def foo ( var a ) { + do(a) + delete a + } + + foo(<-b) // just like that, or else its an error + for(consume_argument(b)) // or like that + +And what if it is not? + + foo(a) + ^^^ + argument a is not passed as moved value, a + use '<-' operator to move the argument, i.e. <-a + +------------------------ +Boris would eat anything +------------------------ + +Maybe not. But parser will. + + bitfield // sure, who cares if its , or ; + +Half the time I can't remember myself + + def foo ( a,b : int, c : float; d ) // it's all the same + +Sometimes it can't figure it out. Most times it can. Type something. Like it matters.... not. + +I can trail commas. + + [1,2,3,4,] or like Foo(a=1,b=2,) // because why not + +Some things are like somewhat related, right. Just type something. + + t.foo(a,b,c) // what could this possibly mean? + +Thats a hard one. We try class method (or structure method). We try static class method. +We also try foo(t,a,b,c). Something would compile. Probably. + + a.b.foo(c) // that one even harder + +It's a method of a.b. Or a property. It's all good. Few years ago you could tell one from the other. +Now there is 'options log', vscode plugin, or just go with the flow. Feel the vibe. +Copilot likes it. It does not have to thing. You might like it. + +Or don't. Good old a->foo(b) still works. CodeOfPolicies is still there - turn it off if you will. + + +----------------------- +Last block pipes itself +----------------------- + +And if it does not have argument, it does not need block sign + + def repeat(a : int; b : block) { + for (i in 0 .. a) { + b() + } + } + + def repeati(a : int; b : block<(v : int)>) { + for (i in 0 .. a) { + b(i) + } + } + + [export] + def main { + repeat(5) { // note, no $, no $(), and no <| $() + print("Hello, World!\n") + } + repeati(5) $(i) { // note, no <| + print("Hello, World! {i}\n") + } + } + +------------- +Typedef what? +------------- + +Local typedef is very local + + def foo ( var a ) { + typedef TT = typedecl(a) // like why not. it goes out of scope - its gone + print("a is {typeinfo typename(type)}") + } + +Class typedefs are very classy + + struct Foo { + typedef TT = ... + +Same thing. Intuitive visibility applies. Good for templates. I have entire blog post about templates. +It's also good for less typing. + +-------------------------- +Bitfield magic is so magic +-------------------------- + +Sure + + bitfield : uint8 < one, two > // cause why not. uint16, uint64 works as well + + bitfield Foo { + a + b + ab = a | b // oh, wow, bitfields can now have constants too + } + +Also + + daslib/bitfield_boost // its an array. behold 'each', '[]' and all that jazz + + +------------ +Annotations? +------------ + +Sure. Write one for enum via 'enumeration_macro'. I'm sure someone will ask for bitfield annotation one day. + +---------- +Typemacro? +---------- + + typedef Counter = $counter(type) // it's obvious + typedef Counter = counter // it's just as obvious. sort of + +-------- +Modules? +-------- + + module Foo public !inscope // its awesome. + +It's so awesome, its visible everywhere, even if you don't require it implicitly or explicitly. +EDLS? Perhaps daslib/match should be that. + +--------------------------- +It doubles as documentation +--------------------------- + +Like who would want a complex class as a hash key (hi JavaScript)? As if cuckoo hash is ever good. + + daslib/flat_hash_table + daslib/cuckoo_hash_table + +Bit arrays? Really? But then again, memory is getting more expensive. + + daslib/bool_array + +--------- +Releases? +--------- + +Sort of. Pre-releases. They are on github alright. + +JIT works well. It may just replace AOT, unless you are on a platform where it can't. Like 'cross-compile' can't. + +Next big thing - compile your module as 'external' module, without making it a part of the submodules. +Next big thing - integration examples. +Next big thing - headers and libraries as part of the release. (Libraries sort of are already. Well, almost.) + +Once there, we call it a release. Until then - download and play with big one. Or small one. Or wait a tad more. +Or sign up for edenspark.io + +------------------- +The need for speed? +------------------- + +Still there. Compilation time went down 2x to 3x last year. Seriously. Build times went down to. +We are getting more robust. It will get faster - there are more tricks up that sleeve. + + + diff --git a/site/blog/_posts/running-it-live.md b/site/blog/_posts/running-it-live.md new file mode 100644 index 0000000000..d4012fa772 --- /dev/null +++ b/site/blog/_posts/running-it-live.md @@ -0,0 +1,43 @@ +--- +title: running-it-live +date: 2026-03-20 09:55:49 +tags: + - daScript + - daslang +--- + +I like live coding. Here, I said it. + + + +Daslang is built for it, its in the DNA. Games had it from pretty much the get go. Edenspark has it. +Even raw daslang sort of had it with the live.das. Kind of. But we can do better. + +As part of 0.6.1 (which is gonna be first week of April) daslang-live executable will be there, next to the regular dalang executable. +Or you can check it right now - clone master, build, run. +There are accompanying modules, and tones of examples. Even couple of reasonably implemented games like 'arcanoid' and 'sequence'. + +More importantly, this is what your app should do - from the get go. Sadly, it's app specific - so I can't just provide you with "make it awesome" function, +which does it for you. But I can provide you with an example of such integration - and thats the 2nd purpose for daslang-live. It's small enough, +and will arrive fully commented and documented. + +Here are some things your game needs to handle, to support live coding - apart from the obvious ability to reload and recompile, without restart. +* be able to do full restart automatically. because things will get broken to the point, where it has to restart. +* handle broken state from script not compiling, and be able to reload after it's fixed +* handle broken state from script failing with exception - and clear all the data it could have potentially corrupted +* persist data between reloads - either in the global storage, or by keeping scripts stateless +* handle multiple instances of the app running - or block it + +You would also want an MCP server, if AI is doing some of your live codding. To me it kept forgetting how to talk to the app. + +There are more considerations - like the rest of the app needs to handle reloads gracefully. Once you start live-coding, you'll know all about it. +Because it will fail, crash, get into states which require full reloads, or worse. + +The way I approach it, I treat live-ability as very high priority. If I'm live codding, and something in the process failed - I fix it, +till it survives those errors - and allows them to be addressed live. + +I'd like to get to the point, where I start with black screen in the morning, and end up with the game at the evening - without having to restart or crash once. +It's not there yet, but it's getting pretty close. + +Obviously, its not just for the games. I've tried it with the REST API development, MCP plugin itself, few bigger projects. +The robustness it adds to the system is an icing on the cake. Its like testing your app 100% of the time, in the real world scenarios. diff --git a/site/blog/_posts/something-about-jit.md b/site/blog/_posts/something-about-jit.md new file mode 100644 index 0000000000..d0c84b2ce8 --- /dev/null +++ b/site/blog/_posts/something-about-jit.md @@ -0,0 +1,80 @@ +--- +title: There is something about JIT +date: 2022-11-20 09:46:37 +tags: + - C++ + - daScript + - gamedev +--- + +In the early days of daScript development having robust JIT was never a priority. +In fact during the initial planning meetings JIT was postponed until the cows came home. + + + +It may seem controversial until you realize the role JIT plays in the development pipeline. +Final code is converted to C++ via AOT and that includes development builds. +As the result, only a small percentage of the code is interpreted at any given time. +Guaranteed it may include some hot spots but between the ECS and the blasting fast daScript interpreter its very rarely an issue. +On top of that JIT is prohibited on most gaming platforms, so it does not solve all that many game development problems. + +When [Dasbox](https://github.com/imp5imp5/dasbox) came online things changed. Some of the games developed for it were quickly CPU bound. + +My [first attempt](https://github.com/borisbat/dasXbyak/tree/main/jit) at JIT was soon to follow. +I initially considered LLVM, but decided against it due to size of dependencies and the amount of C++ bindings I have to write. +I settled on XBYAK and limiting the exercise to x64 platform only. After all, this is where the bulk of JIT happens. + +This project quickly showed some promising results in both performance and compilation time but ultimately failed. +The endeavour turned out to be bit more than I could chew in a reasonable amount of time. +x64 assembly is cumbersome - there are too many options, and they affect performance. Register coloring is not uniform. +It was looking like a full time job for a medium size team, not a 2-3 month project. + +On the plus side daScript now has well integrated x86/x64 inline assembly generator. +The WEB version demonstrated another issue. Running the interpreter on top of already managed code is silly. +It was back to the freezer for the JIT. + +In the meantime more fun things were happening. I've integrated LLVM CLANG compiler infrastructure for the purposes of automating C++ bindings. +Then I've made it self hosting. [LLVM CLANG binding](https://github.com/GaijinEntertainment/daScript/tree/master/modules/dasClangBind) was generated via LLVM CLANG binding. +The minimalists version which bound only functions needed for the binding was no more. +All the sudden it became fairly easy to bind large modules like SFML, BGFX, Imgui, and ultimately LLVM-C. + +With LLVM-C in place it was time to take that JIT out of the closet - and it went reasonably well. +There is of course the initial learning curve; this is my first LLVM based project after all. +On top of that the majority of the examples are written using C++ interfaces to LLVM and at times LLVM-C seems like a bastard love child. +But once the ball started rolling it became clear that this is the way. + +During the initial phase of daScript development [a set of performance tests](https://github.com/GaijinEntertainment/daScript/tree/master/examples/profile/tests) emerged. +I was constantly comparing performance with the other scripting languages, as well as C++ baseline. Its just as important for the JIT. +Plus I was hoping that given the nature of LLVM I would be able to give it more information than I give to the C++ compiler in AOT. +In theory it should result in better performance. +If thats indeed the case, we can generate AOT that way as well - LLVM is perfectly capable of outputting a dynamic or static library. + +There is this warm nostalgic feeling of looking at the same code few years down the road. I started with the fibbonacci and went down the list. +Here is performance snapshot from this morning. All tests compiled with MSVC 2019, Release. JIT is using an LLVM version 13. + +| Test | C++ | daScript | +|-------------------------|------------|------------| +| dictionary | 0.045285 |__0.019225__| +| exp loop | 0.003801 | 0.003735 | +| fibbonacci loop |__0.001612__| 0.001936 | +| fibbonacci recursive | 0.00624 | 0.004375 | +| n-bodies |__0.699158__| 0.787601 | +| particles kinematics | 0.004502 |__0.002981__| +| primes loop | 0.038882 | 0.038846 | +| tree | 0.187152 | 0.173577 | + + +We are not really comparing apples to apples here. +* In the dictionary test C++ uses std::unordered_map. daScript tables are more robust. +* C++ n-bodies implementation is manually vectorized and performs better than naive daScript version + +There are two tests which actually stand out. +* In `fibbonacci loop` MSVC produces better block structure. +* particles kinematics is faster with daScript due to better vectorization, as well as some fancy float3 optimizations in the jit itself + +Even this early on overall JIT performance looks on par or slightly faster than C++. +It would only get better from here - aliasing optimizations are not implemented yet, as well as many other LLVM features. + +My next big goal is to make JIT fully functional. Its already robust enough to try on individual hot spots here and there, but it will take some time to implement the remainder of the daScript language features and runtime. +Next big performance test is going to be the path-tracer. I expect great things due to the vectorized nature of the code. Stay tuned. + diff --git a/site/blog/_posts/templates-what.md b/site/blog/_posts/templates-what.md new file mode 100644 index 0000000000..a962baea4a --- /dev/null +++ b/site/blog/_posts/templates-what.md @@ -0,0 +1,245 @@ +--- +title: Templates? Templates?? Type macros! +date: 2026-01-19 09:58:46 +tags: + - daScript + - daslang +--- + +This is going to be a good one. It's an iceberg. + + + +And since it's an iceberg, you can stop going down the template rabbit hole at just about any level. + +Examples are under examples/typeMacro. I suspect there will be many more, given the magnitude of the disaster. +Think C++ templates on steroids. + +Here it goes. First macro1.das + + module macro1 public + + require daslib/typemacro_boost + + [template_structure(CounterType, initialValue=1)] + struct template TemplateCounter { + private counter : CounterType + def TemplateCounter { + counter = CounterType($v(initialValue)) + } + def next { + counter ++ + } + def const value { + return counter + } + } + +And this is how we use it in example1.das + + require macro1 + + def print_counter(c : $TemplateCounter < auto(CType) >) { + print("Counter value: {c.value()}\n") + } + + [export] + def main { + var ct = default < $TemplateCounter < int > > + ct.next() + print_counter(ct) + print("Counter value: {ct.value()}\n") + } + +If no default value specified its a type (like CounterType), otherwise its a constant (like initialValue). +All good, case closed. Unless it brings more questions than answers, that is. + +Just don't forget it's a macro, so it needs to be in the separate file. + +---------- +It's macro +---------- + +Why the separate file? Well, because its a macro - not a template. So it needs to compile for it to run for it to work in the next modules. + +In fact it's a collection of macros, which write another macros, which in turn write more macros. But I'm getting ahead of myself. + +Whats with the 'template' keyword? Class, structure, or function can be marked as 'template. +Then is just hangs there. Infer does not find it. It does not affect compilation. But the ast is kept. +And can be accessed by other macros. + +This explains $v(initialValue) but does that mean we can now use reification on the templates? Why, yes. + + qmacro_template_class("InstanceOfTheCounter",type) // is how example above works + qmacro_template_function(@@some_function) // is how to do the same with the function + +If that's a macro with reification, how come its CounterType and not a $(t(CounterType))? +Thats because template_structure macro adds typedef CounterType = $t(CounterType) equivalent +into the instance of the TemplateCounter. + + // did you know this is valid? + struct Foo { + typedef Bar = int // its a local (to structure) typedef + def far ( a : Bar ) ... // we can use it in all fields and methods + } + +All good, case closed. Unless it brings more questions than answers. Didn't I say it already? + +--------------------------- +It's a macro within a macro +--------------------------- + +If it's a macro, how do I write one? I want some custom stuff. I'd like to generate types and add a few related functions. + +Here it goes. First macro2.das + + module macro2 public + + require daslib/typemacro_boost + + struct template TemplateCounter { + counter : CounterType + def TemplateCounter { + counter = CounterType($v(initialValue)) + } + def next { + counter ++ + } + def value { + return counter + } + } + + def template print_counter(c : $t(resType)) { + print("Counter value: {c.value()}\n") + } + + [typemacro_function] + def TemplateCounter(macroArgument, passArgument : TypeDeclPtr; counter_type : TypeDeclPtr; initialValue : int = 123) : TypeDeclPtr { + var inscope template_type <- typeinfo ast_typedecl(type) + var inscope template_arguments <- [ + TypeMacroTemplateArgument(name="CounterType", argument_type <- clone_type(counter_type)) + ] + var extra_template_arguments <- [ + ("initialValue", "{initialValue}") + ] + if (passArgument != null) { + return <- TypeDeclPtr() if (!is_typemacro_template_instance(passArgument, template_type, extra_template_arguments)) + return <- TypeDeclPtr() if (!infer_struct_aliases(passArgument.structType, template_arguments)) + return <- infer_template_types(passArgument, template_arguments) + } else { + return <- TypeDeclPtr() if (!verify_arguments(template_arguments)) + var struct_name = template_structure_name(template_type.structType, template_arguments, extra_template_arguments) + var existing_struct = compiling_program().find_unique_structure(struct_name) + return <- new TypeDecl(baseType = Type.tStructure, structType = existing_struct, at = template_type.at) if (existing_struct != null) + var inscope resType <- qmacro_template_class(struct_name, type) + make_typemacro_template_instance(resType.structType, template_type.structType, extra_template_arguments) + add_structure_aliases(resType.structType, template_arguments) + + // HERE! LOOK! CUSTOM STUFF. WE ADD print_counter FUNCTION!!!! + var inscope print_fn <- qmacro_template_function(@@print_counter) + add_function(compiling_module(), print_fn) + + return <- resType + } + } + +And this is how we use it in example2.das + + require macro2 + + [export] + def main { + var ct = default < $TemplateCounter > + ct.next() + print_counter(ct) + print("Counter value: {ct.value()}\n") + } + +typemacro_function here handles all the necessary interactions. Lets look at some details. + +When passArgument is not null, we are dealing with a generic function. + + def foo ( a : $TemplateCounter ) // when passing arguments to foo, infer happens + +We check if its a template instance via 'is_typemacro_template_instance' - it basically uses 'typemacro_template' annotation for it. +We collect alias template types with an 'infer_struct_aliases' (like CounterType in the example). +We infer generic function`s arguments with 'infer_template_types'. +If any of it fails - its not a matching generic function, and will be ignored at inferring function call. + +When passArgument is null, we are dealing with a regular type declaration. + + def foo ( a : $TempalteCoutner) // no infer, passArguments are null + +So we first check, if we already instanced it. +If its not instanced - we use reification (qmacro_template_class) to instance it. +We add typemacro_template annotation, and structure aliases (like CounterType in the example). + +We do all the other 'custom' stuff afterwards - like add additional functions, types, enums, etc etc. +Otherwise we can just call a type, which we would otherwise generate. + +It's obviously way too much boilerplate. But fear not. We can piggy-back on all that existing code-generation. + +First, lets look at macro3.das - all the relevant changes + + [typemacro_template_function(TemplateCounter)] + def makeTemplateCounter(macroArgument, passArgument : TypeDeclPtr; CounterType : TypeDeclPtr; initialValue : int = 123) : TypeDeclPtr { + return <- default + } + + [typemacro_function] + def TemplateCounter(macroArgument, passArgument : TypeDeclPtr; CounterType : TypeDeclPtr; initialValue : int = 123) : TypeDeclPtr { + var inscope resType <- makeTemplateCounter(macroArgument, passArgument, CounterType, initialValue) + if (resType != null && passArgument == null) { + if (!is_custom_work_done(resType.structType)) { + mark_custom_work_done(resType.structType) + + // HERE! LOOK! CUSTOM STUFF. WE ADD print_counter FUNCTION!!!! + var inscope print_fn <- qmacro_template_function(@@print_counter) + add_function(compiling_module(), print_fn) + } + } + return <- resType + } + +typmacro_template_function does all that generation, from the example above - minus the custom part. +We also add a check to see, if we need to add functions - so that we don't do it twice. + +All good, case closed.... oh wait. It's like deja vu all over again. +What about typemacro_function macro? + +------------------------------------------- +It's a macro within a macro, within a macro +------------------------------------------- + +I'm glad you asked. We are almost at the bottom of the iceberg (or are we?) + +Behold, typemacro interface from ast.das + + [macro_interface] + class AstTypeMacro { + def abstract visit(prog : ProgramPtr; mod : Module?; td, passT : TypeDeclPtr) : TypeDeclPtr + } + +This is where we get macroArgument (via td) and passArgument (via passT). + +macroArgument are what we pass to typemacro. For example $TemplateCounter(13) is in fact TypeDecl(baseType=Type.typeMacro, dimExpr=[type,13]). + +passArgument is literally what was passed to a generic function. From the example1 print_counter receives TypeDecl(baseTypeType.tStructure, structType=TemplateCounter). +Yes, really. "TemplateCounter". Thats how template_structure_name generates name. No, you can't type it as a valid DAS structure name - but you can alias. + +At the end typemacro_function takes function declaration, and converts it into a class inherited from AstTypeMacro. +It generates visit function, which in turn converts macroArguments info appropriate typemacro_function arguments. +Default values are passed, when arguments are not available (too few??). Types are checked and errors are generated. +All that wonderful boiler-plate, which you can also bypass - if you want one of those typemacro with variable number of arguments and such. + +daslib/typemacro_boost is an entertaining reading, as well as one of the best examples of macro magic - +together with daslib/templates_boost, daslib/match, peg/peg, and many more to come. + +All good, case closed... zomg. + +qmacro_template_class and qmacro_template_function are MACROS in daslib/templates_boost. +They sit on top QRulesVisitor and TemplateVisitor. + +And that is is .... NOT. They are registered via call_macro MACRO, from daslib/ast_boost. +Its turtles all the way down. diff --git a/site/blog/_posts/version-4.md b/site/blog/_posts/version-4.md new file mode 100644 index 0000000000..89106746a3 --- /dev/null +++ b/site/blog/_posts/version-4.md @@ -0,0 +1,104 @@ +--- +title: 0.4 and THE PLAN +date: 2023-05-01 17:11:37 +tags: daScript +--- + +0.4 is out. Its a big deal. Its not a big deal. + + + +It took a year and a half. But what exactly 'it' entailed? I was trying to get JIT in the 0.4, but its not to be. It needs more love. +Otherwise its a good stopping point; nothing more, nothing less. + +The language is a lot more mature. Its shaping nicely, its a joy to work with, it makes me happy more often than not. +Despite my best efforts OOP support got a lot more mature. Constant and sealed methods, sealed classes, interfaces, member operators, and all that jazz. +I guess being multi paradigm language entails supporting programming models you do not necessarily adore. Its getting good though, and makes sense. +Macros got a lot stronger, especially with reification. They are now ready for the next step to become safe in the user code. +Lots of small things add up to make big difference. Accessors and methods. Extensive operator overloading. Variant types. More flexible generics with contracts. +Error reporting is more on point. Parser even allows dangling commas and semicolons. Surprisingly we compile faster, and when we don't - we know how to speed things up. +Macros now live in their own contexts, and for the most part don't pollute result contexts. This got safer with lock checks and validations - I wish it was free though. +We've made more things unsafe - some which we thought were safe; they weren't. C++ minefield leaves a stamp on a character, hard to get around - but I'm trying; I promise I'll be more paranoid in future. + +Having clear goals for the releases seem like a good idea. Master is safe. Grab the latest master, build it, work with it. +Once releases come online - grab the latest master, work with. There is no point in lots of small releases, since there is no plan to support older versions just yet. +However we are the the point where clear direction will facilitate the process. Plus it provides framework for the feedback. +"Boris, you are missing XXX" or "Boris, we need YYY faster" is a types of suggestions which I always consider. + +To call it 0.5, it will take + +* JIT. "all tests compile and run with jit; all demos compile and run with jit; showcase examples are there - including partial and full jit; jit happy with threading, jobs, etc" +* serialization. save compilation results in binary form, add macro support for 'after the serialization' so that the state can be restored. main idea is to get the startup time down by a lot. +* validation. argument aliasing, 'shader like' code annotation. project callbacks for validation. user `scripts` should be safe enough. +* 'safe' macros. signing unsafe flags. every builtin macro to sign unsafe flags. goes under that same `safe enough` umbrella for the user `scripts`. +* binary releases. "go to webpage, follow the download link, download the latest master binaries for Win64 or Mac OSX". +* 64-bit happy. len to replace deprecated length. 64 bit pointer arithmetics. 64 bit everything in the memory model. array size\capacity and table size\capacity to be 64 bit. 64 bit array indices. counter to replace deprecated count. non of this is necessary just yet, not even close. but it will be. +* the works - getting documentation up to date, updating website, updating readme, updating build, replacing 0.4 with 0.5 everywhere, perhaps documenting the release process, and, of cause, making the branch. + +It will have more things than that. I like tangents. I like things which increase productivity, make quality of life better, or otherwise cool. I often just add them. But the list above will make it to 0.5, or the 0.5 'not to be, left for 0.6' list. + +I like 'by the end of the year'. Its a bit optimistic. JIT and serialization are the big ones. JIT is almost there. Serialization by itself is easy - its the implications to the environment which would take time and effort. + +1.0 is ways away. I don't have any specific date. I don't even consider specific date. Tall call it 1.0 it will take the following + +* versioning. daslib/version/... and the works. that way we can run old and new versions of daslib alongside, and maintain old releases if need be. +* standalone das contexts. AOT -> C++ custom class, which has all the functions explicitly as member functions. Does not require compilation or source code. 'Just works'. Can be linked with the daScript runtime, minus the compiler. Can function as part of runtime. +* many portions of the compiler rewritten to daScript - AOT, print, and debug print - to become standalone contexts first; optimizations and later infer to follow. +* smart_ptr validation. potentially changing memory model, possibly changing to ref-types - it `works` when its impossible to write macro which leaks smart_ptr. possibly moving entire ast to daScript native types, and getting read of smart_ptr altogether. +* compiler portion of the runtime explicitly separated. should be able to link without a compiler. +* safe default C++ bindings. some postprocessing on the module, to force string null to empty, among other binding issues, to be addressed automatically. potentially mark everything C++ unsafe, and always require daScript boost package - in the paranoid mode. + +There are lesser topics which will find there way in alone the way. I'm fine for some of them to be released past 1.0 if they have to. + +Major language features: + +* inlining. +* unsafe. this needs major cleanup. + +Parser and lexer: + +* bind PRCE or RE2. current regex engine is cute. a better multi-regex rewrite is overdue for the purposes of LEXER, but a robust PCRE\RE2 implementation with possibly identical front-end would make things a bit more robust. +* parsing framework. I'd like me some LR1 and PEG. Ideally with the same front end. Needs good macro support to make rules look organic. once there, daScript parser needs to be rewritten in daScript. +* ast_print should produce compilable results. + +Homogenous environment: + +* bind VULKAN. expand GLSL. possibly support SPIR-V explicitly. make homogeneous compute model - current options are based on OpenGL compute, and BGFX compute; they are not spelled out explicitly. potentially they can be. +* expand GLSL to support HLSL. support permutations in form of pins. +* bind some sort of ML framework. There is LIBTORCH branch, but someone with experience should actually do it. +* consider CUDA? + +Optimizations and linter: + +* data flow analysis. its a bit cumbersome to do on a tree, but just as doable and just as robust. if need be can be converted in different form. +* better boolean optimizations. we do very little at the moment. +* even better constant folding. we do a lot, but there is much more - we hardly do any partial folding, i.e. *0, *1, +0, etc. yes, that simple. +* CSE - which ties into data flow analysis. something very basic will keep interpreter happy. anything above is a waste - JIT and AOT are more than capable. +* AOT optimizations. We can be less naive with ABI. + +Modules: + +* upgrade ImGUI to the latest version. should be as simple as get-latest, re-run binder, re-compile. Its possible there are still issues with the binder - clean up is more important than 'just do what needs to be done with bound code'. It needs to be 100% autogenerated. +* upgrade BGFX to the latest version. ImGUI stuff applies. recompiling tools should be part of the build, not manually built and copied. +* upgrade SFML (or let it go). it was used in the DASBOX, but never exposed. I'm not sure where this goes any more. +* expand PugiXML bindings; once there is use case. +* expand LibHV binging; servers and web development are already very possible - streamlining the process is a good idea. +* expand Sqlite3 bindings; set of macros is necessary, to make it similar to LINQ. +* expand raster. should be able to draw a tad more in a safe fashion in both 8 and 32 bit. +* UI framework. I have ideas. I don't like any existing solution. This is bad, right? :) + +Tools: + +* refactoring in the plugin. 'extract method', 'extract expression' to name a few. + +Tests: + +* AOT should be part of testing. +* Old tests need to be ported to new tests. +* Multithreaded version of test compiler with bunch of sanitizers as part of the release. +* Fuzzy tests need more work. Some sort of result driven randomization. +* Can we generate an ast and try to crash the infer? Can we do the same for the parser? + +I call the list above THE PLAN. Its nice to have it spelled out. I'll continue revisiting it to keep things more transparent. + + diff --git a/site/blog/_posts/wake-up-and-test-the-damn-thing.md b/site/blog/_posts/wake-up-and-test-the-damn-thing.md new file mode 100644 index 0000000000..23e21bf198 --- /dev/null +++ b/site/blog/_posts/wake-up-and-test-the-damn-thing.md @@ -0,0 +1,76 @@ +--- +title: Wake up and test the damn thing +date: 2023-02-25 18:44:19 +tags: daScript +--- + +It has to be stable. The time has come to test the damn thing. + + + +Project can live on its own use cases for a long time, especially if it piggybacks on a bunch of other projects with the big boy processes in place. +Its particularly true for the compiler, or the interpreter, or whatever daScript is this days. + +Large codebase happens to also be a large collection of fairly specific user scenarios and coding styles. +Supplemented with measurable amount of basic tests it accounts for reasonable workflow. +Spice it up with some limited self hosting for good measure. +Add an evangelist or better yet an Apostle to merge the damn thing and you got yourself a winner. +Until you don't that is. + +Now to get to the next level one needs a solid foundation. It all started with proper testing framework. +Ironically that immediately created bunch of technical debt, namely "lets take old tests and move them to the new framework". +Which of cause we have not. Yet. + +Here is a taste. It showcases `testing_boost` itself, with some limited fuzzy testing (`fuzzer`) and basic data faking (`faker`). + + require dastest/testing_boost public + require daslib/fuzzer + require daslib/faker + + [test] + def test_bit_counting_everything ( t : T? ) + ... + t |> run("popcnt") <| @ ( t : T? ) + var fake <- Faker() + fuzz <| + var x = fake |> random_uint() + var bits = x + var count = 0u + while bits != 0u + bits &= bits - 1u + count ++ + t |> equal ( count, popcnt(x) ) + +The idea is to cover entire language with a basic functionality test, as well as fuzzy tests for just about everything testable in `daslib`. +[We are making good progress.](https://github.com/GaijinEntertainment/daScript/tree/master/tests) + +Then came the JIT. As I was writing it, I was writing a test for every single JIT feature there was, be that constructs, intrinsic functions, or annotations. +Turns out JIT is a very good _cover entire language_ estimator. It just about covers it. + +[For now JIT tests live next to JIT,](https://github.com/borisbat/dasLLVM/tree/main/tests) but onces fully featured they'll live as part of the main big set of tests. +We'll run them with and without JIT, with and without AOT. + +The test is not a test when it does not run every single time something changes. So it's been integrated with the pull-request process. +The process is not the process if it does not run every single time. So now I do pull requests for everything I check in. +And then it gets built. For every reasonable platform, including WASM. They run tests, and if nothing fails - I merge. + +If the change is significant I run code analyzer. In fact we run several. There are couple on the piggyback projects. +So far there are two which we have integrated as part of Github infrastructure (CodeQL and Microsoft C++ Code Analysis). +Every now and then I run them just because. They've made a lot of noise, they've also pointed at some very real issues. + +On top of all that there is daScript runtime itself. Linter gets bigger every day (did u know we have lint macros? of cause we do) +We now verify CMRES aliasing, and we are about to verify array and table argument aliasing. +Runtime checks locks. With the right options runtime checks lifetime of smart_ptr. If we don't catch it early, we will. + +It is time to finish JIT. But it is also time to test the damn thing. +It has to be fast. It has to be stable. Its inevitable. + + + + + + + + + + diff --git a/site/blog/_posts/we-can-has-dasilb.md b/site/blog/_posts/we-can-has-dasilb.md new file mode 100644 index 0000000000..c5d6ca1381 --- /dev/null +++ b/site/blog/_posts/we-can-has-dasilb.md @@ -0,0 +1,152 @@ +--- +title: we-can-has-dasilb +date: 2026-02-20 21:20:35 +tags: + - daScript + - daslang +--- + +Sometimes technical debt gets paid off. + + + +[Documentation. So much documentation.](https://daslang.io/doc/index.html) +And tutorials. [C-integration tutorials](https://daslang.io/doc/reference/tutorials.html#c-integration-tutorials), +[C++ integration tutorials](https://daslang.io/doc/reference/tutorials.html#tutorials-integration-cpp), +[Language tutorials](https://daslang.io/doc/reference/tutorials.html#language-tutorials), +and even [Macro tutorials](https://daslang.io/doc/reference/tutorials.html#macro-tutorials). + +Its in the GIT already. It's on the [website](https://daslang.io). +Its so awesome, it almost makes me cry. Almost. + +First 90% were easy. Second 90% we kind of hard. The remaining 90% took even longer. +I can't wait to finish another 90%. It takes years to realize, how amazing MSDN documentation is. + +But wait, it gets better. Daslib is maturing. Little missing bits are adding up. +A lot a bugfixes - but most are features. +They are all documented. Many are in the tutorials too. + +Let me go through some of them. + +---------- +interfaces +---------- + +This one got a lot more useful. + +``` +Interface inheritance: [interface] class IChild : IParent +— ancestor getters auto-generated for is/as/?as +Default method implementations — non-abstract methods inherited by proxy +Completeness checking — compile error on missing abstract methods +``` + +------------------- +regex & regex_boost +------------------- + +It's still no PRCE. It's still very naive. But it just got a lot closer. +Still no lookbehind though. + +``` +Counted repetitions: {n}, {n,}, {n,m} +Anchors/boundaries: ^ (BOS), \b, \B +Non-capturing groups: (?:...), named groups: (?P...) +Lazy quantifiers: *?, +?, ??, {n,m}? +Lookahead: (?=...) / (?!...) +Case-insensitive + dot-all mode flags + +regex_search, regex_split, regex_match_all, regex_group_by_name +regex_replace with template strings ($0, $1, ${name}) +operator [] for group access by index or name + +Reader macro flag suffix: %regex~pattern~flags%% (i = case-insensitive, s = dotAll) +``` + +---- +json +---- + +``` +Proper \uXXXX Unicode escape → UTF-8 encoding +``` + +------------- +strings_boost +------------- + +``` +contains(str, sub), count(str, sub), last_index_of(str, sub) +pad_right / pad_left (with optional fill char) +trim_prefix / trim_suffix +capitalize, is_null_or_whitespace +``` + +---- +decs +---- + +This one gonna get a lot more love soon. Like batch object creation. + +``` +is_alive(eid) — check entity is alive (generation matches) +entity_count() — total alive entity count across archetypes +get_component(eid, name, defval) — named component by value with fallback +``` + +--------- +algorithm +--------- + +``` +upper_bound, equal_range — binary search family (with/without comparator) +fill, is_sorted, rotate, min_element, max_element +``` + +---------- +functional +---------- + +``` +reduce_or_default, fold, scan — reduction/accumulation family +enumerate, for_each, find, find_index, partition +tap, iterate, chain, pairwise, flat_map +repeat now infinite by default; echo is non-destructive +``` + +---- +linq +---- + +``` +order_descending / order_descending_inplace / order_descending_to_array +count(iter, predicate) — count matching elements +skip_last / take_last (4 variants each) +zip / zip_to_array with 3 sources +``` + +------------ +jobque_boost +------------ + +``` +try_pop, try_pop_clone — non-blocking channel pop +pop_with_timeout, pop_with_timeout_clone — timed channel pop +with_wait_group — auto-join wait group pattern +done(status) — alias for notify_and_release +parallel_for, parallel_for_each, parallel_map — automatic job partitioning macros +``` + +------------------------------- +bitfield_trait / bitfield_boost +------------------------------- + +``` +each / each_bit_name now support bitfield8, bitfield16, bitfield64 (not just bitfield) +each renamed to each_bit in bitfield_boost, returns iterator +``` + +--- + +Soon it will all be unleashed on the unsuspected world in form of 0.6 release. +Like with downloads, binaries built, modules built against the release, the whole 9 yards. diff --git a/site/blog/_posts/zero-point-five.md b/site/blog/_posts/zero-point-five.md new file mode 100644 index 0000000000..fd44665c0b --- /dev/null +++ b/site/blog/_posts/zero-point-five.md @@ -0,0 +1,59 @@ +--- +title: Zero point five +date: 2024-10-10 17:36:36 +tags: daslang +--- + +Construction can't be finished, only stopped. 0.5 is out. + + + +Whats in there? JIT is good. Less bugs are less buggy. Simpler syntax is what it is. Who would not like [1,2,3] vs [[auto 1,2,3]]. +Things are slightly faster. There are type macros, and type functions - and there will be a post about that at some point, once the helper functions and macros are stable. +Serialization is a big deal - helps with the load times. + +With that out of the way, lets talk about future. 0.6 biggest feature is going to be syntax revamp. + +Sure, a few things will tag alone. I'll likely add proper regex bindings, and few other libraries. +Maybe standalone contexts will finally make it. This and that, but syntax change is where its at. + +We are going to say good bye to the significant whitespace - after long transition period with double parsers, and with the script to convert existing files. + +To understand why first thing is to understand why its there in the first place beside being modern and in vouge, my love for less typing notwithstanding. +The main reason is C++ integration of the language which is very much not like C++. + +The idea was that people will switch the mindset together with syntax, and not write C++ code is Daslang. Naive, I know. +Lets look at couple of examples from our short but intense history. + +Originally there was no plan to support classes, or structure methods for that matter; the idea was not to have significant OOP support at all. +First thing people did is an OOP implementation on top of structures. +If you can't beat them, join them—and then lead them - now we have classes, visibility, and other OOP bells and whistles. + +Then there were properties. First via :=, and now we support += operators and such. +More stuff followed - think match, initialization shortcuts, dot after enum, and many a thing. + +Thats because people like to write code a certain way. +AI completion (like copilot) reflects it even more. Who am I to tell how collective unconscious (?) wants to write their binary search. + +At this point I suspect I'd have to add more syntax sugar around some basic generic data types, +though tuples do the job nicely minus the visibility. There will be another post about that as well. + +That being said the single biggest complaint was significant white space. Turns out there are enough people who don't like python syntax, +and some won't touch something like that with a 6 ft pole. Worse a flex\bison pair which we use for parsing does not like it either, +it takes constant effort and limits features significantly. + +There is also an issue of 'cryptography'. Which is people really don't like to read something like + + $ <| [ann1,ann2(name=3)] [[&foo,:=bar]] ( ... ) + +For it has to many cryptic characters. Who knew. + +There is also a learning curve. C# was easy to pick up, because I already knew C++. Python was harder, and took getting used to. +JS was ok, because many things were familiar, and I only had to learn new concepts (even more so with C#). + +This is where we are going with 0.6. I'll probably look a lot more like C#. It will get more verbose and less cryptic. +It will also get the dreaded 'transitioning from C-like language' tutorial, with all the things which are the same. + +I'm not looking forward to that stupid das -> das_c_like script, but I am looking forward to simpler parser. +I'd like to use my tricks to make syntax better, as oppose to make semicolon work 'every time there is new line but only when necessary'. + diff --git a/site/blog/build_blog.py b/site/blog/build_blog.py new file mode 100644 index 0000000000..1539dea85d --- /dev/null +++ b/site/blog/build_blog.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python3 +""" +Build the daslang.io blog + changelist + landing news feed from Markdown. + +Input: + posts_dir/ — long-form blog posts (Hexo-format Markdown) + news_dir/ — short news entries (front-matter-only, body optional) + template — Forge HTML wrapper with {{title}} / {{body}} / {{root}} / {{description}} slots + +Output (under out_dir): + blog/index.html — post listing + blog/.html — one per post + blog/feed.xml — Atom feed of posts + news/.html — one per news entry that has a body + changelist.html — full long-form news list (all entries by date desc) + files/news.json — top-N news for forge.js to render landing § 05 + +Hexo extensions translated: + → excerpt boundary (excerpt above used on index) + {% post_link %} → post title + +Usage: + python3 build_blog.py --posts site/blog/_posts --news site/_news \\ + --template site/blog/template.html --out _site/ + +Local dev: pass --out site/ to write next to the sources so you can preview +from `cd site && python3 -m http.server`. +""" + +from __future__ import annotations + +import argparse +import html +import re +import sys +from dataclasses import dataclass, field +from datetime import datetime +from pathlib import Path + +try: + import markdown +except ImportError as e: + sys.exit(f"build_blog.py requires `markdown`: pip install markdown ({e})") + + +# ─── Front matter ──────────────────────────────────────────────────── + +# Minimal YAML-ish parser for the small set of keys we use. Avoids the +# pyyaml dep so the CI step is just `pip install markdown pygments`. + +KEY_RE = re.compile(r'^([a-zA-Z_]+):\s*(.*)$') + + +@dataclass +class Entry: + slug: str + title: str + date: str # YYYY-MM-DD + tag: str = '' + tags: list = field(default_factory=list) + link: str = '' + body_md: str = '' + excerpt_md: str = '' + has_body: bool = False + kind: str = 'post' # 'post' or 'news' + source: Path = None + + +def parse_front_matter(text: str) -> tuple[dict, str]: + if not text.startswith('---'): + return {}, text + parts = text.split('---', 2) + if len(parts) < 3: + return {}, text + fm: dict = {} + current_key = None + for line in parts[1].splitlines(): + if not line.strip(): + continue + # Continuation of a list (Hexo "tags:\n - foo\n - bar") + m_list = re.match(r'^\s*-\s*(.*)$', line) + if m_list and current_key: + fm.setdefault(current_key + '__list', []).append(m_list.group(1).strip()) + continue + m = KEY_RE.match(line) + if m: + key, val = m.group(1), m.group(2).strip() + current_key = key + if val: + fm[key] = val + # Collapse __list into + for key in list(fm): + if key.endswith('__list'): + fm[key[:-6]] = fm.pop(key) + return fm, parts[2].lstrip('\n') + + +# ─── Slug + date helpers ───────────────────────────────────────────── + +def slug_of(path: Path) -> str: + return path.stem + + +DATE_DT_RE = re.compile(r'^(\d{4})-(\d{2})-(\d{2})') + +def normalize_date(s: str) -> str: + """Accept YYYY-MM-DD or YYYY-MM-DD HH:MM:SS, return YYYY-MM-DD.""" + if not s: + return '1970-01-01' + m = DATE_DT_RE.match(s) + if m: + return f"{m.group(1)}-{m.group(2)}-{m.group(3)}" + return s + + +# ─── Hexo extension preprocessing ──────────────────────────────────── + +POST_LINK_RE = re.compile(r'\{\%\s*post_link\s+([a-zA-Z0-9_\-]+)(?:\s+([^%]+?))?\s*\%\}') + +def expand_post_links(md_body: str, posts_by_slug: dict) -> str: + """{% post_link slug %} → markdown link to the post.""" + def repl(m): + slug = m.group(1) + custom = (m.group(2) or '').strip() + target = posts_by_slug.get(slug) + title = custom or (target.title if target else slug) + return f"[{title}]({slug}.html)" + return POST_LINK_RE.sub(repl, md_body) + + +# Hexo posts use 4-space indents for code blocks; some have trailing :: lines +# (RST habit). Convert RST-style "::\n\n code" into ```daslang fences. +RST_CODE_BLOCK_RE = re.compile( + r'(::\s*\n)(\n(?: [^\n]*\n?)+)', # "::" line then indented block + re.MULTILINE, +) + +def normalize_rst_code_blocks(md_body: str) -> str: + def repl(m): + header, block = m.group(1), m.group(2) + # Strip 4-space indent from the block + lines = [ln[4:] if ln.startswith(' ') else ln for ln in block.lstrip('\n').splitlines()] + return '\n```daslang\n' + '\n'.join(lines).rstrip() + '\n```\n' + return RST_CODE_BLOCK_RE.sub(repl, md_body) + + +# Convert pure 4-space-indented blocks (no preceding ::) into daslang fences. +INDENT_BLOCK_RE = re.compile( + r'(?:^\n)((?:^ [^\n]*\n?)+)', + re.MULTILINE, +) + +def normalize_indent_code_blocks(md_body: str) -> str: + """Replace 4-space-indent code blocks not already inside a fence with + fenced daslang blocks. Skips blocks following a list marker.""" + out_lines = [] + in_fence = False + in_list_block = False + indent_block: list[str] = [] + for line in md_body.splitlines(keepends=True): + if line.startswith('```'): + in_fence = not in_fence + if indent_block: + out_lines.extend(indent_block); indent_block = [] + out_lines.append(line) + continue + if in_fence: + out_lines.append(line) + continue + # Detect list continuation: lines starting with "- ", "* ", "1. " then + # subsequent 4-space-indented lines belong to that list item. + bare = line.lstrip(' \t') + if re.match(r'^(?:[-*+]\s|\d+\.\s)', bare): + in_list_block = True + if indent_block: + out_lines.extend(indent_block); indent_block = [] + out_lines.append(line) + continue + if line.strip() == '': + if indent_block: + # Flush as a fenced block + code = ''.join(ln[4:] if ln.startswith(' ') else ln for ln in indent_block) + out_lines.append('\n```daslang\n' + code.rstrip() + '\n```\n') + indent_block = [] + in_list_block = False + out_lines.append(line) + continue + if line.startswith(' ') and not in_list_block: + indent_block.append(line) + continue + if indent_block: + out_lines.extend(indent_block); indent_block = [] + out_lines.append(line) + if indent_block: + code = ''.join(ln[4:] if ln.startswith(' ') else ln for ln in indent_block) + out_lines.append('\n```daslang\n' + code.rstrip() + '\n```\n') + return ''.join(out_lines) + + +def split_excerpt(md_body: str) -> tuple[str, str]: + """Split on . Returns (excerpt, full).""" + if '' in md_body: + excerpt, _, _ = md_body.partition('') + return excerpt.strip(), md_body.replace('', '').strip() + return md_body.strip()[:240], md_body.strip() + + +# ─── Markdown renderer ─────────────────────────────────────────────── + +def make_md(): + # No codehilite — daslang isn't in pygments. We emit standard + # CommonMark `
` and let
+    # site/files/highlight.js tokenize daslang blocks client-side.
+    return markdown.Markdown(
+        extensions=['fenced_code', 'tables', 'def_list', 'attr_list'],
+    )
+
+
+# ─── Page rendering ──────────────────────────────────────────────────
+
+def render_template(tpl: str, root: str, title: str, body: str, description: str = '') -> str:
+    return (tpl
+        .replace('{{root}}', root)
+        .replace('{{title}}', html.escape(title))
+        .replace('{{description}}', html.escape(description or title))
+        .replace('{{body}}', body))
+
+
+def render_post(entry: Entry, prev: Entry | None, next: Entry | None, md, posts_by_slug) -> str:
+    body_md = expand_post_links(entry.body_md, posts_by_slug)
+    body_md = normalize_rst_code_blocks(body_md)
+    body_md = normalize_indent_code_blocks(body_md)
+    md.reset()
+    body_html = md.convert(body_md)
+    tags_html = ''
+    if entry.tags:
+        tags_html = ' · '.join(html.escape(t) for t in entry.tags)
+    elif entry.tag:
+        tags_html = html.escape(entry.tag)
+    nav_html = ''
+    parts = []
+    if prev:
+        parts.append(f'← {html.escape(prev.title)}')
+    else:
+        parts.append('')
+    if next:
+        parts.append(f'{html.escape(next.title)} →')
+    else:
+        parts.append('')
+    nav_html = f'
{parts[0]}{parts[1]}
' + return f"""
+
+ +

{html.escape(entry.title)}

+
+ {body_html} +
+ {nav_html} +
+
+""" + + +def render_index(posts: list[Entry], md) -> str: + items = [] + for p in posts: + md.reset() + excerpt_html = md.convert(p.excerpt_md) if p.excerpt_md else '' + tag = p.tags[0] if p.tags else p.tag + items.append(f"""
+
{html.escape(p.date)}
+
{html.escape(tag)}
+
+ +
{excerpt_html}
+
+
""") + return f"""
+
+ +

Notes from the language.

+

+ Design rationale, refactor stories, and the occasional shipping + announcement. Newest first. +

+
+{chr(10).join(items)} +
+
+
+""" + + +def render_changelist(news: list[Entry]) -> str: + rows = [] + for n in news: + href = n.link or (f"news/{n.slug}.html" if n.has_body else '') + title = html.escape(n.title) + title_html = f'{title}' if href else title + rows.append(f"""
+
{html.escape(n.date)}
+
{html.escape(n.tag)}
+
{title_html}
+
""") + return f"""
+
+ +

Everything, in order.

+

+ Releases, tooling, ecosystem notes, and site updates. Newest + first. The top 5 also appear in § 05 on the landing. +

+
+{chr(10).join(rows)} +
+
+
+""" + + +def render_news_page(entry: Entry, md) -> str: + md.reset() + body_html = md.convert(entry.body_md) + return f"""
+
+ +

{html.escape(entry.title)}

+
+ {body_html} +
+
+
+""" + + +def render_atom_feed(posts: list[Entry], site_url: str) -> str: + updated = posts[0].date if posts else '1970-01-01' + entries = [] + for p in posts: + url = f"{site_url}/blog/{p.slug}.html" + entries.append(f""" + {html.escape(p.title)} + + {url} + {p.date}T00:00:00Z + {html.escape(p.excerpt_md)} +""") + return f""" + +Daslang blog + + +{updated}T00:00:00Z +{site_url}/blog/ +{chr(10).join(entries)} + +""" + + +# ─── Main ───────────────────────────────────────────────────────────── + +def load_posts(d: Path) -> list[Entry]: + out = [] + for f in sorted(d.glob('*.md')): + text = f.read_text(encoding='utf-8') + fm, body = parse_front_matter(text) + if not fm.get('title') or not fm.get('date'): + print(f"warn: {f.name} missing title/date — skipping", file=sys.stderr) + continue + excerpt, full = split_excerpt(body) + out.append(Entry( + slug=slug_of(f), + title=fm['title'], + date=normalize_date(fm['date']), + tag=fm.get('tag', ''), + tags=fm.get('tags') if isinstance(fm.get('tags'), list) else ( + [fm['tags']] if fm.get('tags') else []), + body_md=full, + excerpt_md=excerpt, + has_body=bool(full.strip()), + kind='post', + source=f, + )) + out.sort(key=lambda e: e.date, reverse=True) + return out + + +def load_news(d: Path) -> list[Entry]: + out = [] + if not d.exists(): + return out + for f in sorted(d.glob('*.md')): + text = f.read_text(encoding='utf-8') + fm, body = parse_front_matter(text) + if not fm.get('title') or not fm.get('date'): + print(f"warn: {f.name} missing title/date — skipping", file=sys.stderr) + continue + body = body.strip() + out.append(Entry( + slug=slug_of(f), + title=fm['title'], + date=normalize_date(fm['date']), + tag=fm.get('tag', 'news'), + link=fm.get('link', ''), + body_md=body, + has_body=bool(body), + kind='news', + source=f, + )) + out.sort(key=lambda e: e.date, reverse=True) + return out + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('--posts', required=True, type=Path) + ap.add_argument('--news', type=Path) + ap.add_argument('--template', required=True, type=Path) + ap.add_argument('--out', required=True, type=Path) + ap.add_argument('--site-url', default='https://dascript.org') + args = ap.parse_args() + + tpl = args.template.read_text(encoding='utf-8') + out = args.out + out.mkdir(parents=True, exist_ok=True) + (out / 'blog').mkdir(parents=True, exist_ok=True) + + md = make_md() + + posts = load_posts(args.posts) + posts_by_slug = {p.slug: p for p in posts} + + # 1. Per-post pages + for i, p in enumerate(posts): + prev_ = posts[i - 1] if i > 0 else None + next_ = posts[i + 1] if i + 1 < len(posts) else None + body = render_post(p, prev_, next_, md, posts_by_slug) + html_out = render_template(tpl, root='../', title=p.title, body=body, + description=(p.excerpt_md[:160] if p.excerpt_md else p.title)) + (out / 'blog' / f'{p.slug}.html').write_text(html_out, encoding='utf-8') + + # 2. Blog index + index_body = render_index(posts, md) + (out / 'blog' / 'index.html').write_text( + render_template(tpl, root='../', title='Blog', body=index_body, + description='Daslang blog — design notes, refactor stories, releases.'), + encoding='utf-8') + + # 3. Atom feed + (out / 'blog' / 'feed.xml').write_text( + render_atom_feed(posts, args.site_url), encoding='utf-8') + + # 4. News + changelist + news: list[Entry] = [] + if args.news: + news = load_news(args.news) + (out / 'news').mkdir(parents=True, exist_ok=True) + for n in news: + if n.has_body: + body = render_news_page(n, md) + html_out = render_template(tpl, root='../', title=n.title, body=body, + description=n.title) + (out / 'news' / f'{n.slug}.html').write_text(html_out, encoding='utf-8') + + # 5. changelist.html — full long-form news list + changelist_body = render_changelist(news) + (out / 'changelist.html').write_text( + render_template(tpl, root='', title='Change list', + body=changelist_body, + description='Releases, tooling, and ecosystem updates.'), + encoding='utf-8') + + # 6. news.json for landing § 05 (top 5) + (out / 'files').mkdir(parents=True, exist_ok=True) + import json + top_news = [ + { + 'date': n.date, 'tag': n.tag, 'title': n.title, + 'link': n.link or (f"news/{n.slug}.html" if n.has_body else ''), + } + for n in news[:8] + ] + (out / 'files' / 'news.json').write_text( + json.dumps(top_news, indent=2), encoding='utf-8') + + print(f"built {len(posts)} posts, {len(news)} news entries → {out}/") + + +if __name__ == '__main__': + main() diff --git a/site/blog/template.html b/site/blog/template.html new file mode 100644 index 0000000000..29f16cfe0a --- /dev/null +++ b/site/blog/template.html @@ -0,0 +1,81 @@ + + + + + + {{title}} — Daslang + + + + + + + + + + +
+ + +{{body}} + + +
+ + diff --git a/site/downloads.html b/site/downloads.html new file mode 100644 index 0000000000..1304928e3c --- /dev/null +++ b/site/downloads.html @@ -0,0 +1,203 @@ + + + + + + Downloads — Daslang + + + + + + + + + +
+ + + +
+
+ +

Releases, repos, and ecosystem.

+

+ The compiler, the language server, frameworks, and a handful of + third-party projects built on top of daslang. +

+
+
+ +
+
+ +
+
+

Pre-built binaries

+

+ Every tagged release ships a multi-platform bundle on + GitHub Releases. Pick the archive that matches your + platform, unzip, add to PATH. +

+ github.com/GaijinEntertainment/daScript/releases → +
+ +
+
+
+ +
+
+ +
+
+
vscode
+
daslang Language Server
+
VSCode extension with auto-completion, navigation, and inline help — written in daslang itself. +
profelis/daScript-plugin → +
+
+
+
playground
+
daslang.io/playground
+
Run daslang in your browser via WebAssembly. Multi-file workspace, sample picker. +
open playground → +
+
+
+
framework
+
dasBox
+
Simple framework for building games in daslang. +
imp5imp5/dasbox → +
+
+
+
+
+ +
+
+ +
+
+

Where to learn more.

+

Blogs, tutorials, articles, and projects built on top of daslang.

+
+ +
+
+
+ + + +
+ + diff --git a/site/files/button.js b/site/files/button.js deleted file mode 100644 index ad30e7d35d..0000000000 --- a/site/files/button.js +++ /dev/null @@ -1 +0,0 @@ -(window.__twttrll=window.__twttrll||[]).push([[3],{166:function(t,e,a){var r=a(37),n=a(170),s=a(7);(r=Object.create(r)).build=s(r.build,null,n),t.exports=r},167:function(t,e,a){var r=a(36),n=a(33),s=a(35),i=a(0),o=a(7),u=a(32),c=a(4),l=a(174);t.exports=function(t){t.params({partner:{fallback:o(u.val,u,"partner")}}),t.define("scribeItems",function(){return{}}),t.define("scribeNamespace",function(){return{client:"tfw"}}),t.define("scribeData",function(){return{widget_origin:s.rootDocumentLocation(),widget_frame:s.isFramed()&&s.currentDocumentLocation(),widget_partner:this.params.partner,widget_site_screen_name:l(u.val("site")),widget_site_user_id:c.asNumber(u.val("site:id")),widget_creator_screen_name:l(u.val("creator")),widget_creator_user_id:c.asNumber(u.val("creator:id"))}}),t.define("scribe",function(t,e,a){t=i.aug(this.scribeNamespace(),t||{}),e=i.aug(this.scribeData(),e||{}),r.scribe(t,e,!1,a)}),t.define("scribeInteraction",function(t,e,a){var r=n.extractTermsFromDOM(t.target);r.action=t.type,"url"===r.element&&(r.element=n.clickEventElement(t.target)),this.scribe(r,e,a)})}},169:function(t,e,a){var r=a(4),n=a(0);t.exports=function(t){t.define("widgetDataAttributes",function(){return{}}),t.define("setDataAttributes",function(){var t=this.sandbox.sandboxEl;n.forIn(this.widgetDataAttributes(),function(e,a){r.hasValue(a)&&t.setAttribute("data-"+e,a)})}),t.after("render",function(){this.setDataAttributes()})}},170:function(t,e,a){var r=a(38),n=a(0),s=a(171);function i(){r.apply(this,arguments),this.Widget=this.Component}i.prototype=Object.create(r.prototype),n.aug(i.prototype,{factory:s,build:function(){return r.prototype.build.apply(this,arguments)},selectors:function(t){var e=this.Widget.prototype.selectors;t=t||{},this.Widget.prototype.selectors=n.aug({},t,e)}}),t.exports=i},171:function(t,e,a){var r=a(6),n=a(34),s=a(39),i=a(0),o=a(7),u=a(172),c="twitter-widget-";t.exports=function(){var t=s();function e(e,a){t.apply(this,arguments),this.id=c+u(),this.sandbox=a}return e.prototype=Object.create(t.prototype),i.aug(e.prototype,{selectors:{},hydrate:function(){return r.resolve()},prepForInsertion:function(){},render:function(){return r.resolve()},show:function(){return r.resolve()},resize:function(){return r.resolve()},select:function(t,e){return 1===arguments.length&&(e=t,t=this.el),t?(e=this.selectors[e]||e,i.toRealArray(t.querySelectorAll(e))):[]},selectOne:function(){return this.select.apply(this,arguments)[0]},selectLast:function(){return this.select.apply(this,arguments).pop()},on:function(t,e,a){var r,s=this.el;this.el&&(t=(t||"").split(/\s+/),2===arguments.length?a=e:r=e,r=this.selectors[r]||r,a=o(a,this),t.forEach(r?function(t){n.delegate(s,t,r,a)}:function(t){s.addEventListener(t,a,!1)}))}}),e}},172:function(t,e){var a=0;t.exports=function(){return String(a++)}},174:function(t,e){t.exports=function(t){return t&&"@"===t[0]?t.substr(1):t}},212:function(t,e){var a=/\{\{([\w_]+)\}\}/g;t.exports=function(t,e){return t.replace(a,function(t,a){return void 0!==e[a]?e[a]:t})}},216:function(t,e,a){var r=a(6),n=a(166),s=a(32),i=a(19),o=a(212),u=a(0),c=a(11),l=a(7),p=a(70),h=a(68),m=p.followButtonHtmlPath,f="Twitter Follow Button",d="twitter-follow-button";function g(t){return"large"===t?"l":"m"}t.exports=n.couple(a(167),a(169),function(t){t.params({screenName:{required:!0},lang:{required:!0,transform:h.matchLanguage,fallback:"en"},size:{fallback:"medium",transform:g},showScreenName:{fallback:!0},showCount:{fallback:!0},partner:{fallback:l(s.val,s,"partner")},count:{},preview:{}}),t.define("getUrlParams",function(){return u.compact({id:this.id,lang:this.params.lang,size:this.params.size,screen_name:this.params.screenName,show_count:"none"!==this.params.count&&this.params.showCount,show_screen_name:this.params.showScreenName,preview:this.params.preview,partner:this.params.partner,dnt:i.enabled(),time:+new Date})}),t.around("widgetDataAttributes",function(t){return u.aug({"screen-name":this.params.screenName},t())}),t.around("scribeNamespace",function(t){return u.aug(t(),{page:"button",section:"follow"})}),t.define("scribeImpression",function(){this.scribe({action:"impression"},{language:this.params.lang,message:[this.params.size,"none"===this.params.count?"nocount":"withcount"].join(":")+":"})}),t.override("render",function(){var t=o(m,{lang:this.params.lang}),e=c.encode(this.getUrlParams()),a=p.resourceBaseUrl+t+"#"+e;return this.scribeImpression(),r.all([this.sandbox.setTitle(f),this.sandbox.addClass(d),this.sandbox.loadDocument(a)])})})},250:function(t,e,a){var r=a(6),n=a(5),s=a(8),i=a(32),o=a(19),u=a(212),c=a(76),l=a(0),p=a(11),h=a(3),m=a(166),f=a(7),d=a(70),g=a(68),b=d.tweetButtonHtmlPath,w="Twitter Tweet Button",v="twitter-tweet-button",y="twitter-share-button",_="twitter-hashtag-button",x="twitter-mention-button",N=["share","hashtag","mention"];function D(t){return"large"===t?"l":"m"}function k(t){return l.contains(N,t)}function z(t){return h.hashTag(t,!1)}function A(t){return/\+/.test(t)&&!/ /.test(t)?t.replace(/\+/g," "):t}t.exports=m.couple(a(167),a(169),function(t){t.params({lang:{required:!0,transform:g.matchLanguage,fallback:"en"},size:{fallback:"medium",transform:D},type:{fallback:"share",validate:k},text:{transform:A},screenName:{transform:h.screenName},buttonHashtag:{transform:z},partner:{fallback:f(i.val,i,"partner")},via:{},related:{},hashtags:{},url:{}}),t.define("getUrlParams",function(){var t=this.params.text,e=this.params.url,a=this.params.via,r=this.params.related,i=c.getScreenNameFromPage();return"share"===this.params.type?(t=t||n.title,e=e||c.getCanonicalURL()||s.href,a=a||i):i&&(r=r?i+","+r:i),l.compact({id:this.id,lang:this.params.lang,size:this.params.size,type:this.params.type,text:t,url:e,via:a,related:r,button_hashtag:this.params.buttonHashtag,screen_name:this.params.screenName,hashtags:this.params.hashtags,partner:this.params.partner,original_referer:s.href,dnt:o.enabled(),time:+new Date})}),t.around("widgetDataAttributes",function(t){return"mention"==this.params.type?l.aug({"screen-name":this.params.screenName},t()):"hashtag"==this.params.type?l.aug({hashtag:this.params.buttonHashtag},t()):l.aug({url:this.params.url},t())}),t.around("scribeNamespace",function(t){return l.aug(t(),{page:"button",section:this.params.type})}),t.define("scribeImpression",function(){this.scribe({action:"impression"},{language:this.params.lang,message:[this.params.size,"nocount"].join(":")+":"})}),t.override("render",function(){var t,e=u(b,{lang:this.params.lang}),a=p.encode(this.getUrlParams()),n=d.resourceBaseUrl+e+"#"+a;switch(this.params.type){case"hashtag":t=_;break;case"mention":t=x;break;default:t=y}return this.scribeImpression(),r.all([this.sandbox.setTitle(w),this.sandbox.addClass(v),this.sandbox.addClass(t),this.sandbox.loadDocument(n)])})})},85:function(t,e,a){var r=a(166);t.exports=r.build([a(216)])},90:function(t,e,a){var r=a(166);t.exports=r.build([a(250)])}}]); \ No newline at end of file diff --git a/site/files/cm/cm-forge.css b/site/files/cm/cm-forge.css new file mode 100644 index 0000000000..cfa76a024d --- /dev/null +++ b/site/files/cm/cm-forge.css @@ -0,0 +1,53 @@ +/* CodeMirror "forge" theme — applied via theme:'forge' on the CM instance. + * Token classes inherit colors from forge.css :root. Shared by the hero + * editor (site/index.html) and the full playground (site/playground/). */ + +.CodeMirror, +.cm-s-forge { + background: var(--bg-2) !important; + color: var(--fg) !important; + font-family: var(--font-mono) !important; + font-size: 13px !important; + line-height: 1.65 !important; + height: 100% !important; +} + +.cm-s-forge .CodeMirror-gutters { + background: var(--bg-2) !important; + border-right: 1px solid var(--rule) !important; +} +.cm-s-forge .CodeMirror-linenumber { color: var(--fg-faint) !important; } +/* Cursor — the base CM rule (border:0!important + background:#7e7) is the fat + * IME cursor; the regular cursor is the inner span. Style both. */ +.cm-s-forge .CodeMirror-cursor, +.cm-s-forge div.CodeMirror-cursor { + border-left: 1px solid var(--amber) !important; + border-right: none !important; + background: transparent !important; +} +.cm-s-forge.CodeMirror-focused .CodeMirror-cursors, +.cm-s-forge .CodeMirror-focused .CodeMirror-cursors { visibility: visible !important; } +.cm-s-forge .CodeMirror-activeline-background { background: rgba(232, 161, 58, 0.04) !important; } +.cm-s-forge .CodeMirror-selected { background: rgba(232, 161, 58, 0.15) !important; } +.cm-s-forge .CodeMirror-line::selection, +.cm-s-forge .CodeMirror-line ::selection { background: rgba(232, 161, 58, 0.20) !important; } +.cm-s-forge .CodeMirror-matchingbracket { color: var(--amber) !important; background: rgba(232, 161, 58, 0.10); } + +/* Token coloring */ +.cm-s-forge .cm-keyword, +.cm-s-forge .cm-builtin { color: var(--amber) !important; } +.cm-s-forge .cm-atom, +.cm-s-forge .cm-def { color: var(--amber) !important; } +.cm-s-forge .cm-type, +.cm-s-forge .cm-variable-2, +.cm-s-forge .cm-tag { color: var(--blue) !important; } +.cm-s-forge .cm-string, +.cm-s-forge .cm-string-2 { color: var(--green) !important; } +.cm-s-forge .cm-number { color: #d8a8d8 !important; } +.cm-s-forge .cm-comment { color: var(--fg-faint) !important; font-style: italic; } +.cm-s-forge .cm-attribute, +.cm-s-forge .cm-meta { color: var(--red) !important; } +.cm-s-forge .cm-variable { color: var(--fg) !important; } +.cm-s-forge .cm-operator, +.cm-s-forge .cm-punctuation { color: var(--fg-dim) !important; } +.cm-s-forge .cm-error { color: var(--red) !important; background: transparent !important; } diff --git a/site/files/cm/codemirror.min.css b/site/files/cm/codemirror.min.css new file mode 100644 index 0000000000..2ef25eaa5e --- /dev/null +++ b/site/files/cm/codemirror.min.css @@ -0,0 +1 @@ +.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.cm-fat-cursor{caret-color:transparent}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:0;position:relative;z-index:0}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none;outline:0}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0} \ No newline at end of file diff --git a/site/files/cm/codemirror.min.js b/site/files/cm/codemirror.min.js new file mode 100644 index 0000000000..9c78bd4648 --- /dev/null +++ b/site/files/cm/codemirror.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,l=navigator.platform,d=/gecko\/\d/i.test(e),s=/MSIE \d/.test(e),a=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),u=/Edge\/(\d+)/.exec(e),w=s||a||u,v=w&&(s?document.documentMode||6:+(u||a)[1]),x=!u&&/WebKit\//.test(e),s=x&&/Qt\/\d+\.\d+/.test(e),m=!u&&/Chrome\/(\d+)/.exec(e),V=m&&+m[1],K=/Opera\//.test(e),j=/Apple Computer/.test(navigator.vendor),c=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),X=/PhantomJS/.test(e),Y=j&&(/Mobile\/\w+/.test(e)||2t)return i;o.to==t&&(o.from!=o.to&&"before"==n?r=i:Fe=i),o.from==t&&(o.from!=o.to&&"before"!=n?r=i:Fe=i)}return null!=r?r:Fe}Ee=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,Re=/[stwN]/,ze=/[LRr]/,Ie=/[Lb1n]/,Be=/[1n]/;var Ee,Re,ze,Ie,Be,Ge=function(e,t){var n="ltr"==t?"L":"R";if(0==e.length||"ltr"==t&&!Ee.test(e))return!1;for(var r,i=e.length,o=[],l=0;l=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(t=e.first&&tn?F(n,W(e,n).text.length):(e=W(e,(n=t).line).text.length,null==(t=n.ch)||e=this.string.length},g.prototype.sol=function(){return this.pos==this.lineStart},g.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},g.prototype.next=function(){if(this.post},g.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},g.prototype.skipToEnd=function(){this.pos=this.string.length},g.prototype.skipTo=function(e){e=this.string.indexOf(e,this.pos);if(-1e.options.maxHighlightLength&&ft(e.doc.mode,r.state),o=At(e,t,r),i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))),t.styles}function Wt(n,r,e){var t=n.doc,i=n.display;if(!t.mode.startState)return new Ot(t,!0,r);var o=function(e,t,n){for(var r,i,o=e.doc,l=n?-1:t-(e.doc.mode.innerMode?1e3:100),s=t;lt.first&&W(t,o-1).stateAfter,s=l?Ot.fromSaved(t,l,o):new Ot(t,gt(t.mode),o);return t.iter(o,r,function(e){Ht(n,e.text,s);var t=s.line;e.stateAfter=t==r-1||t%5==0||t>=i.viewFrom&&tt.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}Ot.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},Ot.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},Ot.prototype.nextLine=function(){this.line++,0e.options.maxHighlightLength?(s=!1,l&&Ht(e,t,r,c.pos),c.pos=t.length,null):zt(Pt(n,c,r.state,h),o);if(!h||(d=h[0].name)&&(f="m-"+(f?d+" "+f:d)),!s||u!=f){for(;a=t:l.to>t),(r=r||[]).push(new Ut(s,l.from,o?null:l.to)))}return r}(n,r,o),s=function(e,t,n){var r;if(e)for(var i=0;i=t:l.to>t))&&(l.from!=t||"bookmark"!=s.type||n&&!l.marker.insertLeft)||(o=null==l.from||(s.inclusiveLeft?l.from<=t:l.frome.lastLine())return t;var n,r=W(e,t);if(!on(e,r))return t;for(;n=Jt(r);)r=n.find(1,!0).line;return H(r)+1}function on(e,t){var n=Gt&&t.markedSpans;if(n)for(var r,i=0;in.maxLineLength&&(n.maxLineLength=t,n.maxLine=e)})}var un=function(e,t,n){this.text=e,Yt(this,t),this.height=n?n(this):1};un.prototype.lineNo=function(){return H(this)},$e(un);var cn={},hn={};function dn(e,t){if(!e||/^\s*$/.test(e))return null;t=t.addModeClass?hn:cn;return t[e]||(t[e]=e.replace(/\S+/g,"cm-$&"))}function fn(e,t){var n=ne("span",null,null,x?"padding-right: .1px":null),r={pre:ne("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o=i?t.rest[i-1]:t.line,l=void 0,l=(r.pos=0,r.addToken=gn,function(e){if(null!=tt)return tt;var t=y(e,document.createTextNode("AخA")),n=le(t,0,1).getBoundingClientRect(),t=le(t,1,2).getBoundingClientRect();return te(e),n&&n.left!=n.right&&(tt=t.right-n.right<3)}(e.display.measure)&&(l=Ve(o,e.doc.direction))&&(r.addToken=function(h,d){return function(e,t,n,r,i,o,l){n=n?n+" cm-force-border":"cm-force-border";for(var s=e.pos,a=s+t.length;;){for(var u=void 0,c=0;cs&&u.from<=s);c++);if(u.to>=a)return h(e,t,n,r,i,o,l);h(e,t.slice(0,u.to-s),n,r,null,o,l),r=null,t=t.slice(u.to-s),s=u.to}}}(r.addToken,l)),r.map=[],t!=e.display.externalMeasured&&H(o));!function(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(r)for(var l,s,a,u,c,h,d,f=i.length,p=0,g=1,m="",v=0;;){if(v==p){a=u=c=s="",h=d=null,v=1/0;for(var y=[],b=void 0,w=0;wp||C.collapsed&&x.to==p&&x.from==p)){if(null!=x.to&&x.to!=p&&v>x.to&&(v=x.to,u=""),C.className&&(a+=" "+C.className),C.css&&(s=(s?s+";":"")+C.css),C.startStyle&&x.from==p&&(c+=" "+C.startStyle),C.endStyle&&x.to==v&&(b=b||[]).push(C.endStyle,x.to),C.title&&((d=d||{}).title=C.title),C.attributes)for(var S in C.attributes)(d=d||{})[S]=C.attributes[S];C.collapsed&&(!h||qt(h.marker,C)<0)&&(h=x)}else x.from>p&&v>x.from&&(v=x.from)}if(b)for(var L=0;Ln)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}}function zn(e,t,n,r){return Gn(e,Bn(e,t),n,r)}function In(e,t){if(t>=e.display.viewFrom&&t=e.lineN&&tt)&&(i=(o=a-s)-1,a<=t&&(l="right")),null!=i){if(r=e[u+2],s==a&&n==(r.insertLeft?"left":"right")&&(l=n),"left"==n&&0==i)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)r=e[2+(u-=3)],l="left";if("right"==n&&i==a-s)for(;u=i.text.length?(t=i.text.length,e="before"):t<=0&&(t=0,e="after"),!a)return s("before"==e?t-1:t,"before"==e);function u(e,t,n){return s(n?e-1:e,1==a[t].level!=n)}var c=Pe(a,t,e),h=Fe,c=u(t,c,"before"==e);return null!=h&&(c.other=u(t,h,"before"!=e)),c}function tr(e,t){var n=0,t=(t=E(e.doc,t),e.options.lineWrapping||(n=cr(e.display)*t.ch),W(e.doc,t.line)),e=ln(t)+Dn(e.display);return{left:n,right:n,top:e,bottom:e+t.height}}function nr(e,t,n,r,i){e=F(e,t,n);return e.xRel=i,r&&(e.outside=r),e}function rr(e,t,n){var r=e.doc;if((n+=e.display.viewOffset)<0)return nr(r.first,0,null,-1,-1);var i=bt(r,n),o=r.first+r.size-1;if(o=a.bottom?1:0)}return c=We(e.text,c,1),nr(t,c,g,f,r-p)}(e,l,i,t,n),a=function(e,t){var n,r=Gt&&e.markedSpans;if(r)for(var i=0;it)&&(!n||qt(n,o.marker)<0)&&(n=o.marker)}return n}(l,s.ch+(0r},i,e)}}function or(e,t,n,r){return ir(e,t,n=n||Bn(e,t),Zn(e,t,Gn(e,n,r),"line").top)}function lr(e,t,n,r){return!(e.bottom<=n)&&(e.top>n||(r?e.left:e.right)>t)}function sr(n,r,i,o,l,s,a){var e,t=He(function(e){var e=l[e],t=1!=e.level;return lr(er(n,F(i,t?e.to:e.from,t?"before":"after"),"line",r,o),s,a,!0)},0,l.length-1),u=l[t];return 0a&&(u=l[t-1])),u}function ar(e,t,n,r,i,o,l){for(var l=ir(e,t,r,l),s=l.begin,a=l.end,u=(/\s/.test(t.text.charAt(a-1))&&a--,null),c=null,h=0;h=a||f.to<=s||(d=(d=Gn(e,r,1!=f.level?Math.min(a,f.to)-1:Math.max(s,f.from)).right)a?{from:u.from,to:a,level:u.level}:u}function ur(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==Un){Un=M("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)Un.appendChild(document.createTextNode("x")),Un.appendChild(M("br"));Un.appendChild(document.createTextNode("x"))}y(e.measure,Un);var n=Un.offsetHeight/50;return 3=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var n=e.display.view,r=0;rt)&&(o.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=o.viewTo?Gt&&nn(e.doc,t)o.viewFrom?yr(e):(o.viewFrom+=r,o.viewTo+=r):t<=o.viewFrom&&n>=o.viewTo?yr(e):t<=o.viewFrom?(l=br(e,n,n+r,1))?(o.view=o.view.slice(l.index),o.viewFrom=l.lineN,o.viewTo+=r):yr(e):n>=o.viewTo?(l=br(e,t,t,-1))?(o.view=o.view.slice(0,l.index),o.viewTo=l.lineN):yr(e):(l=br(e,t,t,-1),i=br(e,n,n+r,1),l&&i?(o.view=o.view.slice(0,l.index).concat(yn(e,l.lineN,i.lineN)).concat(o.view.slice(i.index)),o.viewTo+=r):yr(e)),o.externalMeasured);l&&(n=i.lineN&&t=r.viewTo||null!=(i=r.view[mr(e,t)]).node&&-1==L(r=i.changes||(i.changes=[]),n)&&r.push(n)}function yr(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function br(e,t,n,r){var i,o=mr(e,t),l=e.display.view;if(!Gt||n==e.doc.first+e.doc.size)return{index:o,lineN:n};for(var s=e.display.viewFrom,a=0;a=e.display.viewTo||s.to().linet||t==n&&l.to==t)&&(r(Math.max(l.from,t),Math.min(l.to,n),1==l.level?"rtl":"ltr",o),i=!0)}i||r(t,n,"ltr")}(C,g||0,null==m?b:m,function(e,t,n,r){var i,o,l,s,a,u="ltr"==n,c=w(e,u?"left":"right"),h=w(t-1,u?"right":"left"),d=null==g&&0==e,f=null==m&&t==b,p=0==r,r=!C||r==C.length-1;h.top-c.top<=3?(i=(k?d:f)&&p?S:(u?c:h).left,a=(k?f:d)&&r?L:(u?h:c).right,T(i,c.top,a-i,c.bottom)):(a=u?(o=k&&d&&p?S:c.left,l=k?L:x(e,n,"before"),s=k?S:x(t,n,"after"),k&&f&&r?L:h.right):(o=k?x(e,n,"before"):S,l=!k&&d&&p?L:c.right,s=!k&&f&&r?S:h.left,k?x(t,n,"after"):L),T(o,c.top,l-o,c.bottom),c.bottome.display.sizerWidth&&((a=Math.ceil(c/cr(e.display)))>e.display.maxLineLength&&(e.display.maxLineLength=a,e.display.maxLine=s.line,e.display.maxLineChanged=!0))}}2=o&&(i=bt(t,ln(W(t,n))-e.wrapper.clientHeight),o=n)),{from:i,to:Math.max(o,i+1)}}function Hr(e,t){var n=e.display,r=ur(e.display),i=(t.top<0&&(t.top=0),(e.curOp&&null!=e.curOp.scrollTop?e.curOp:n.scroller).scrollTop),o=En(e),l={},s=(t.bottom-t.top>o&&(t.bottom=t.top+o),e.doc.height+Wn(n)),a=t.tops-r,r=(t.topi+o&&((a=Math.min(t.top,(r?s:t.bottom)-o))!=i&&(l.scrollTop=a)),e.options.fixedGutter?0:n.gutters.offsetWidth),s=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft-r,o=Pn(e)-n.gutters.offsetWidth,i=t.right-t.left>o;return i&&(t.right=t.left+o),t.left<10?l.scrollLeft=0:t.lefto+s-3&&(l.scrollLeft=t.right+(i?0:10)-o),l}function Fr(e,t){null!=t&&(Rr(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc:e.curOp).scrollTop+t)}function Pr(e){Rr(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function Er(e,t,n){null==t&&null==n||Rr(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function Rr(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,zr(e,tr(e,t.from),tr(e,t.to),t.margin))}function zr(e,t,n,r){t=Hr(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});Er(e,t.scrollLeft,t.scrollTop)}function Ir(e,t){Math.abs(e.doc.scrollTop-t)<2||(d||ri(e,{top:t}),Br(e,t,!0),d&&ri(e),Qr(e,100))}function Br(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),e.display.scroller.scrollTop==t&&!n||(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Gr(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r||(e.doc.scrollLeft=t,li(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function Ur(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+Wn(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+Fn(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}function Vr(e,t,n){this.cm=n;var r=this.vert=M("div",[M("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=M("div",[M("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),k(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),k(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,w&&v<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")}function Kr(){}Vr.prototype.update=function(e){var t,n=e.scrollWidth>e.clientWidth+1,r=e.scrollHeight>e.clientHeight+1,i=e.nativeBarWidth;return r?(this.vert.style.display="block",this.vert.style.bottom=n?i+"px":"0",t=e.viewHeight-(n?i:0),this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+t)+"px"):(this.vert.scrollTop=0,this.vert.style.display="",this.vert.firstChild.style.height="0"),n?(this.horiz.style.display="block",this.horiz.style.right=r?i+"px":"0",this.horiz.style.left=e.barLeft+"px",t=e.viewWidth-e.barLeft-(r?i:0),this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+t)+"px"):(this.horiz.style.display="",this.horiz.firstChild.style.width="0"),!this.checkedZeroWidth&&0=l.viewTo)||l.maxLineChanged&&o.options.lineWrapping,i.update=i.mustUpdate&&new ei(o,i.mustUpdate&&{top:i.scrollTop,ensure:i.scrollToPos},i.forceUpdate)}for(var s=0;s(i.defaultView.innerHeight||i.documentElement.clientHeight)&&(r=!1),null==r||X||(o=M("div","​",null,"position: absolute;\n top: "+(t.top-n.viewOffset-Dn(e.display))+"px;\n height: "+(t.bottom-t.top+Fn(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;"),e.display.lineSpace.appendChild(o),o.scrollIntoView(r),e.display.lineSpace.removeChild(o)))}(w,v));var S=b.maybeHiddenMarkers,L=b.maybeUnhiddenMarkers;if(S)for(var k=0;k=l.display.viewTo||(s=+new Date+l.options.workTime,a=Wt(l,c.highlightFrontier),u=[],c.iter(a.line,Math.min(c.first+c.size,l.display.viewTo+500),function(e){if(a.line>=l.display.viewFrom){for(var t=e.styles,n=e.text.length>l.options.maxHighlightLength?ft(c.mode,a.state):null,r=At(l,e,a,!0),n=(n&&(a.state=n),e.styles=r.styles,e.styleClasses),r=r.classes,i=(r?e.styleClasses=r:n&&(e.styleClasses=null),!t||t.length!=e.styles.length||n!=r&&(!n||!r||n.bgClass!=r.bgClass||n.textClass!=r.textClass)),o=0;!i&&os)return Qr(l,l.options.workDelay),!0}),c.highlightFrontier=a.line,c.modeFrontier=Math.max(c.modeFrontier,a.line),u.length&&h(l,function(){for(var e=0;e=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==wr(e))return!1;si(e)&&(yr(e),t.dims=hr(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),l=Math.min(i,t.visible.to+e.options.viewportMargin),r=(n.viewFroml&&n.viewTo-l<20&&(l=Math.min(i,n.viewTo)),Gt&&(o=nn(e.doc,o),l=rn(e.doc,l)),o!=n.viewFrom||l!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth),i=(i=o,o=l,0==(c=(l=e).display).view.length||i>=c.viewTo||o<=c.viewFrom?(c.view=yn(l,i,o),c.viewFrom=i):(c.viewFrom>i?c.view=yn(l,i,c.viewFrom).concat(c.view):c.viewFromo&&(c.view=c.view.slice(0,mr(l,o)))),c.viewTo=o,n.viewOffset=ln(W(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px",wr(e));if(!r&&0==i&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;var l=function(e){if(e.hasFocus())return null;if(!(n=N(ue(e)))||!re(e.display.lineDiv,n))return null;var t,n={activeElt:n};return window.getSelection&&(t=he(e).getSelection()).anchorNode&&t.extend&&re(e.display.lineDiv,t.anchorNode)&&(n.anchorNode=t.anchorNode,n.anchorOffset=t.anchorOffset,n.focusNode=t.focusNode,n.focusOffset=t.focusOffset),n}(e),s=(4=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break;if(!ti(e,t))break;Ar(e);var i=Ur(e);xr(e),jr(e,i),oi(e,i),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function ri(e,t){var n,t=new ei(e,t);ti(e,t)&&(Ar(e),ni(e,t),n=Ur(e),xr(e),jr(e,n),oi(e,n),t.finish())}function ii(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px",b(e,"gutterChanged",e)}function oi(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Fn(e)+"px"}function li(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=dr(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",l=0;ll.clientWidth,a=l.scrollHeight>l.clientHeight;if(r&&s||n&&a){if(n&&C&&x)e:for(var u=t.target,c=o.view;u!=l;u=u.parentNode)for(var h=0;hs-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(o=(o=l).lastOp==r?(Wi(o.done),z(o.done)):o.done.length&&!z(o.done).ranges?z(o.done):1l.undoDepth;)l.done.shift(),l.done[0].ranges||l.done.shift()}l.done.push(n),l.generation=++l.maxGeneration,l.lastModTime=l.lastSelTime=s,l.lastOp=l.lastSelOp=r,l.lastOrigin=l.lastSelOrigin=t.origin,i||O(e,"historyAdded")}function Fi(e,t,n,r){var i,o,l,s=e.history,a=r&&r.origin;n==s.lastSelOp||a&&s.lastSelOrigin==a&&(s.lastModTime==s.lastSelTime&&s.lastOrigin==a||(e=e,i=a,o=z(s.done),l=t,"*"==(i=i.charAt(0))||"+"==i&&o.ranges.length==l.ranges.length&&o.somethingSelected()==l.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)))?s.done[s.done.length-1]=t:Pi(t,s.done),s.lastSelTime=+new Date,s.lastSelOrigin=a,s.lastSelOp=n,r&&!1!==r.clearRedo&&Wi(s.undone)}function Pi(e,t){var n=z(t);n&&n.ranges&&n.equals(e)||t.push(e)}function Ei(t,n,e,r){var i=n["spans_"+t.id],o=0;t.iter(Math.max(t.first,e),Math.min(t.first+t.size,r),function(e){e.markedSpans&&((i=i||(n["spans_"+t.id]={}))[o]=e.markedSpans),++o})}function Ri(e,t){var n=t["spans_"+e.id];if(!n)return null;for(var r=[],i=0;i=t.ch:s.to>t.ch))){if(i&&(O(a,"beforeCursorEnter"),a.explicitlyCleared)){if(o.markedSpans){--l;continue}break}if(a.atomic){if(n){var s=a.find(r<0?1:-1),h=void 0;if((s=(r<0?c:u)?Qi(e,s,-r,s&&s.line==t.line?o:null):s)&&s.line==t.line&&(h=P(s,n))&&(r<0?h<0:0e.first?E(e,F(t.line-1)):null:0e.lastLine())){t.from.linei?{from:t.from,to:F(i,W(e,i).text.length),text:[t.text[0]],origin:t.origin}:t).removed=mt(e,t.from,t.to),n=n||xi(e,t),e.cm){var i=e.cm,o=t,l=r,s=i.doc,a=i.display,u=o.from,c=o.to,h=!1,d=u.line,f=(i.options.lineWrapping||(d=H(tn(W(s,u.line))),s.iter(d,c.line+1,function(e){if(e==a.maxLine)return h=!0})),-1a.maxLineLength&&(a.maxLine=e,a.maxLineLength=t,a.maxLineChanged=!0,h=!1)}),h&&(i.curOp.updateMaxLine=!0)),s),p=u.line;if(f.modeFrontier=Math.min(f.modeFrontier,p),!(f.highlightFrontiert.display.maxLineLength&&(t.display.maxLine=u,t.display.maxLineLength=c,t.display.maxLineChanged=!0)}null!=r&&t&&this.collapsed&&R(t,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,t&&$i(t.doc)),t&&b(t,"markerCleared",t,this,r,i),n&&Zr(t),this.parent&&this.parent.clear()}},mo.prototype.find=function(e,t){var n,r;null==e&&"bookmark"==this.type&&(e=1);for(var i=0;i=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(i,o,l){i=E(this,i),o=E(this,o);var s=[],a=i.line;return this.iter(i.line,o.line+1,function(e){var t=e.markedSpans;if(t)for(var n=0;n=r.to||null==r.from&&a!=i.line||null!=r.from&&a==o.line&&r.from>=o.ch||l&&!l(r.marker)||s.push(r.marker.parent||r.marker)}++a}),s},getAllMarks:function(){var r=[];return this.iter(function(e){var t=e.markedSpans;if(t)for(var n=0;nt&&(t=e.from),null!=e.to&&e.toe.text.length?null:t}function Ko(e,t,n){e=Vo(e,t.ch,n);return null==e?null:new F(t.line,e,n<0?"after":"before")}function jo(e,t,n,r,i){if(e){"rtl"==t.doc.direction&&(i=-i);var o,l,s,a,e=Ve(n,t.doc.direction);if(e)return o=i<0==(1==(e=i<0?z(e):e[0]).level)?"after":"before",0=n.text.length?(s.ch=n.text.length,s.sticky="before"):s.ch<=0&&(s.ch=0,s.sticky="after");var r=Pe(a,s.ch,s.sticky),i=a[r];if("ltr"==t.doc.direction&&i.level%2==0&&(0s.ch:i.from=i.from&&d>=c.begin))return new F(s.line,d,h?"before":"after")}function f(e,t,n){for(var r=function(e,t){return t?new F(s.line,u(e,1),"before"):new F(s.line,e,"after")};0<=e&&el.doc.first&&((n=W(l.doc,e.line-1).text)&&(e=new F(e.line,1),l.replaceRange(t.charAt(0)+l.doc.lineSeparator()+n.charAt(n.length-1),F(e.line-1,n.length-1),e,"+transpose")))),i.push(new G(e,e)));l.setSelections(i)})},newlineAndIndent:function(r){return h(r,function(){for(var e=(t=r.listSelections()).length-1;0<=e;e--)r.replaceRange(r.doc.lineSeparator(),t[e].anchor,t[e].head,"+input");for(var t=r.listSelections(),n=0;nc&&t.push(new G(F(s,c),F(s,we(u,l,n))))}t.length||t.push(new G(f,f)),U(g,vi(d,y.ranges.slice(0,v).concat(t),v),{origin:"*mouse",scroll:!1}),d.scrollIntoView(e)}else{var h,r=m,i=ul(d,e,p.unit),e=r.anchor,e=0=n.to||o.linea.bottom?20:0)&&setTimeout(I(d,function(){u==i&&(l.scroller.scrollTop+=r,e(t))}),50))}:n)(e)}),i=I(d,n);d.state.selectingText=i,k(l.wrapper.ownerDocument,"mousemove",r),k(l.wrapper.ownerDocument,"mouseup",i)})(i,s,o,a)):Qe(e)==h.scroller&&D(e):2==n?(t&&Gi(c.doc,t),setTimeout(function(){return h.input.focus()},20)):3==n&&(Q?c.display.input.onContextMenu(e):Mr(c)))))}function ul(e,t,n){if("char"==n)return new G(t,t);if("word"==n)return e.findWordAt(t);if("line"==n)return new G(F(t.line,0),E(e.doc,F(t.line+1,0)));n=n(e,t);return new G(n.from,n.to)}function cl(e,t,n,r){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch(e){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&D(t);var l=e.display,r=l.lineDiv.getBoundingClientRect();if(o>r.bottom||!Ye(e,n))return qe(t);o-=r.top-l.viewOffset;for(var s=0;s=i)return O(e,n,e,bt(e.doc,o),e.display.gutterSpecs[s].className,t),qe(t)}}function hl(e,t){return cl(e,t,"gutterClick",!0)}function dl(e,t){var n,r;An(e.display,t)||(r=t,Ye(n=e,"gutterContextMenu")&&cl(n,r,"gutterContextMenu",!1))||A(e,t,"contextmenu")||Q||e.display.input.onContextMenu(t)}function fl(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),Yn(e)}ol.prototype.compare=function(e,t,n){return this.time+400>e&&0==P(t,this.pos)&&n==this.button};var pl={toString:function(){return"CodeMirror.Init"}},gl={},ml={};function vl(e,t,n){!t!=!(n&&n!=pl)&&(n=e.display.dragFunctions,(t=t?k:T)(e.display.scroller,"dragstart",n.start),t(e.display.scroller,"dragenter",n.enter),t(e.display.scroller,"dragover",n.over),t(e.display.scroller,"dragleave",n.leave),t(e.display.scroller,"drop",n.drop))}function yl(e){e.options.lineWrapping?(ie(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(ee(e.display.wrapper,"CodeMirror-wrap"),an(e)),pr(e),R(e),Yn(e),setTimeout(function(){return jr(e)},100)}function p(e,t){var n=this;if(!(this instanceof p))return new p(e,t);this.options=t=t?fe(t):{},fe(gl,t,!1);var r,i=t.value,o=("string"==typeof i?i=new f(i,t.mode,null,t.lineSeparator,t.direction):t.mode&&(i.modeOption=t.mode),this.doc=i,new p.inputStyles[t.inputStyle](this)),e=this.display=new hi(e,i,o,t),l=(fl(e.wrapper.CodeMirror=this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),$r(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new pe,keySeq:null,specialChars:null},t.autofocus&&!_&&e.input.focus(),w&&v<11&&setTimeout(function(){return n.display.input.reset(!0)},20),this),s=l.display;k(s.scroller,"mousedown",I(l,al)),k(s.scroller,"dblclick",w&&v<11?I(l,function(e){var t;A(l,e)||(!(t=gr(l,e))||hl(l,e)||An(l.display,e)||(D(e),e=l.findWordAt(t),Gi(l.doc,e.anchor,e.head)))}):function(e){return A(l,e)||D(e)}),k(s.scroller,"contextmenu",function(e){return dl(l,e)}),k(s.input.getField(),"contextmenu",function(e){s.scroller.contains(e.target)||dl(l,e)});var a,u={end:0};function c(){s.activeTouch&&(a=setTimeout(function(){return s.activeTouch=null},1e3),(u=s.activeTouch).end=+new Date)}function h(e,t){if(null==t.left)return 1;var n=t.left-e.left,t=t.top-e.top;return 400o.first?S(W(o,t-1).text,null,l):0:"add"==n?c=a+e.options.indentUnit:"subtract"==n?c=a-e.options.indentUnit:"number"==typeof n&&(c=a+n);var c=Math.max(0,c),h="",d=0;if(e.options.indentWithTabs)for(var f=Math.floor(c/l);f;--f)d+=l,h+="\t";if(dl,a=rt(t),u=null;if(s&&1l?"cut":"+input")});to(e.doc,f),b(e,"inputRead",e,f)}t&&!s&&kl(e,t),Pr(e),e.curOp.updateInput<2&&(e.curOp.updateInput=h),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function Ll(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");return n&&(e.preventDefault(),t.isReadOnly()||t.options.disableInput||!t.hasFocus()||h(t,function(){return Sl(t,n,0,null,"paste")}),1)}function kl(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;0<=r;r--){var i=n.ranges[r];if(!(100=n.first+n.size||(r=new F(e,r.ch,r.sticky),!(s=W(n,e))))return;r=jo(l,n.cm,s,r.line,a)}else r=t;return 1}if("char"==o||"codepoint"==o)u();else if("column"==o)u(!0);else if("word"==o||"group"==o)for(var c=null,h="group"==o,d=n.cm&&n.cm.getHelper(r,"wordChars"),f=!0;!(i<0)||u(!f);f=!1){var p=s.text.charAt(r.ch)||"\n",p=Ne(p,d)?"w":h&&"\n"==p?"n":!h||/\s/.test(p)?null:"p";if(!h||f||p||(p="s"),c&&c!=p){i<0&&(i=1,u(),r.sticky="after");break}if(p&&(c=p),0=s.height){l.hitSide=!0;break}o+=5*n}return l}function r(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new pe,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null}function Dl(e,t){var n=In(e,t.line);if(!n||n.hidden)return null;var r=W(e.doc,t.line),n=Rn(n,r,t.line),r=Ve(r,e.doc.direction),e="left",r=(r&&(e=Pe(r,t.ch)%2?"right":"left"),Kn(n.map,t.ch,e));return r.offset="right"==r.collapse?r.end:r.start,r}function Wl(e,t){return t&&(e.bad=!0),e}function Hl(e,t,n){var r;if(t==e.display.lineDiv){if(!(r=e.display.lineDiv.childNodes[n]))return Wl(e.clipPos(F(e.display.viewTo-1)),!0);t=null,n=0}else for(r=t;;r=r.parentNode){if(!r||r==e.display.lineDiv)return null;if(r.parentNode&&r.parentNode==e.display.lineDiv)break}for(var i=0;i=t.display.viewTo||n.line=t.display.viewFrom&&Dl(t,r)||{node:i[0].measure.map[2],offset:0},r=n.linet.firstLine()&&(i=F(i.line-1,W(t.doc,i.line-1).length)),r.ch==W(t.doc,r.line).text.length&&r.linen.viewTo-1)return!1;var o,l=i.line==n.viewFrom||0==(l=mr(t,i.line))?(e=H(n.view[0].line),n.view[0].node):(e=H(n.view[l].line),n.view[l-1].node.nextSibling),r=mr(t,r.line),n=r==n.view.length-1?(o=n.viewTo-1,n.lineDiv.lastChild):(o=H(n.view[r+1].line)-1,n.view[r+1].node.previousSibling);if(!l)return!1;for(var s=t.doc.splitLines(function(o,e,t,l,s){var n="",a=!1,u=o.doc.lineSeparator(),c=!1;function h(){a&&(n+=u,c&&(n+=u),a=c=!1)}function d(e){e&&(h(),n+=e)}for(;!function e(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(n)d(n);else if(n=t.getAttribute("cm-marker"))(n=o.findMarks(F(l,0),F(s+1,0),(i=+n,function(e){return e.id==i}))).length&&(n=n[0].find(0))&&d(mt(o.doc,n.from,n.to).join(u));else if("false"!=t.getAttribute("contenteditable")&&(n=/^(pre|div|p|li|table|br)$/i.test(t.nodeName),/^br$/i.test(t.nodeName)||0!=t.textContent.length)){n&&h();for(var r=0;ri.ch&&p.charCodeAt(p.length-c-1)==g.charCodeAt(g.length-c-1);)u--,c++;s[s.length-1]=p.slice(0,p.length-c).replace(/^\u200b+/,""),s[0]=s[0].slice(u).replace(/\u200b+$/,"");r=F(e,u),l=F(o,a.length?z(a).length-c:0);return 1n&&(wl(this,i.head.line,e,!0),n=i.head.line,r==this.doc.sel.primIndex&&Pr(this));else{for(var o=i.from(),i=i.to(),l=Math.max(n,o.line),n=Math.min(this.lastLine(),i.line-(i.ch?0:1))+1,s=l;s>1;if((l?n[2*l-1]:0)>=o)i=l;else{if(!(n[2*l+1]l)&&e.top>t.offsetHeight?a=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=l&&(a=e.bottom),u+t.offsetWidth>o&&(u=o-t.offsetWidth)),t.style.top=a+"px",t.style.left=t.style.right="","right"==i?(u=s.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?u=0:"middle"==i&&(u=(s.sizer.clientWidth-t.offsetWidth)/2),t.style.left=u+"px"),n&&(r=this,l={left:u,top:a,right:u+t.offsetWidth,bottom:a+t.offsetHeight},null!=(l=Hr(r,l)).scrollTop&&Ir(r,l.scrollTop),null!=l.scrollLeft&&Gr(r,l.scrollLeft))},triggerOnKeyDown:t(nl),triggerOnKeyPress:t(il),triggerOnKeyUp:rl,triggerOnMouseDown:t(al),execCommand:function(e){if(Yo.hasOwnProperty(e))return Yo[e].call(null,this)},triggerElectric:t(function(e){kl(this,e)}),findPosH:function(e,t,n,r){for(var i=1,o=(t<0&&(i=-1,t=-t),E(this.doc,e)),l=0;l>=|<<|>>|<-|->|<\||\|>|=>|::|\+\+|--|==|!=|<=|>=|\+=|-=|\*=|\/=|%=|\^\^=|\|\|=|&&=|\^=|&=|\|=|@@|\?\?|\?\.|\?\[|:=/, token: 'operator' }, + + { regex: /[a-zA-Z_][a-zA-Z0-9_]*/, token: identToken }, + + { regex: /[+\-*\/%=<>!&|^~@$:;,.()\[\]{}?]/, token: 'operator' }, + ], + + comment: [ + { regex: /\/\*/, token: 'comment', push: 'comment' }, + { regex: /\*\//, token: 'comment', pop: true }, + { regex: /[^*\/]+/, token: 'comment' }, + { regex: /./, token: 'comment' }, + ], + + string: [ + { regex: /\\./, token: 'string' }, + { regex: /\{/, token: 'string-2', push: 'interp' }, + { regex: /"/, token: 'string', pop: true }, + { regex: /[^\\"{]+/, token: 'string' }, + { regex: /./, token: 'string' }, + ], + + // String interpolation `{...}` — reuses start-state tokenizing for the body. + interp: [ + { regex: /\}/, token: 'string-2', pop: true }, + { regex: /\/\/.*/, token: 'comment' }, + { regex: /"/, token: 'string', push: 'string' }, + { regex: /\|\||&&|<-|->|<\||\|>|=>|::|==|!=|<=|>=|@@|\?\?|\?\.|\?\[|:=/, token: 'operator' }, + { regex: /0[xX][0-9a-fA-F][0-9a-fA-F_]*[uUlL]?/, token: 'number' }, + { regex: /\d[\d_]*(?:\.\d[\d_]*)?(?:[eE][+\-]?\d+)?(?:lf|d|f|F|u8|U8|ul|UL|u|U|l|L)?/, token: 'number' }, + { regex: /[a-zA-Z_][a-zA-Z0-9_]*/, token: identToken }, + { regex: /[+\-*\/%=<>!&|^~@$:;,.()\[\]?]/, token: 'operator' }, + { regex: /\s+/, token: null }, + ], + + meta: { + dontIndentStates: ['comment', 'string', 'interp'], + lineComment: '//', + }, + }); +})(typeof window !== 'undefined' ? window : globalThis); diff --git a/site/files/cm/simple-mode.js b/site/files/cm/simple-mode.js new file mode 100644 index 0000000000..af2945a42f --- /dev/null +++ b/site/files/cm/simple-mode.js @@ -0,0 +1 @@ +!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(v){"use strict";function h(e,t){if(!e.hasOwnProperty(t))throw new Error("Undefined state "+t+" in simple mode")}function k(e,t){if(!e)return/(?:)/;var n="";return e=e instanceof RegExp?(e.ignoreCase&&(n="i"),e.unicode&&(n+="u"),e.source):String(e),new RegExp((!1===t?"":"^")+"(?:"+e+")",n)}function g(e,t){(e.next||e.push)&&h(t,e.next||e.push),this.regex=k(e.regex),this.token=function(e){if(!e)return null;if(e.apply)return e;if("string"==typeof e)return e.replace(/\./g," ");for(var t=[],n=0;n + + + + + + + + > + diff --git a/site/files/forge.css b/site/files/forge.css new file mode 100644 index 0000000000..20ffa539a9 --- /dev/null +++ b/site/files/forge.css @@ -0,0 +1,845 @@ +/* ─────────────────────────────────────────────────────────────── + Forge — daslang.io design tokens + components + See ~/Downloads/daslang_website/handoff/HANDOFF.md for the spec. + ─────────────────────────────────────────────────────────────── */ + +:root { + /* Backgrounds */ + --bg: #0d0c0a; + --bg-2: #15130f; + --bg-3: #1c1a14; + + /* Foreground */ + --fg: #e8e2d2; + --fg-dim: #9b9281; + --fg-faint: #5b5547; + + /* Structure */ + --rule: #2a2620; + + /* Accent */ + --amber: #e8a13a; + --amber-dim: #a87420; + + /* Syntax / status */ + --green: #9bc46a; + --red: #d96d4f; + --blue: #6aa9c4; + + /* Type */ + --font-sans: "Inter Tight", "Inter", -apple-system, system-ui, sans-serif; + --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace; +} + +/* ───── Reset / base ───── */ + +*, *::before, *::after { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + background: var(--bg); + color: var(--fg); + font-family: var(--font-sans); + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { color: inherit; text-decoration: none; cursor: pointer; } +a:hover { color: var(--amber); } + +img { max-width: 100%; display: block; } + +h1, h2, h3, h4 { margin: 0; font-weight: 600; } + +p { margin: 0; } + +button { font: inherit; border: 0; background: none; color: inherit; cursor: pointer; padding: 0; } + +/* ───── Layout ───── */ + +.forge-container { + max-width: 1180px; + margin: 0 auto; + padding: 0 32px; + position: relative; +} + +/* Dot grid + subtle warm radial glow behind the hero. */ +.forge-page { + position: relative; + min-height: 100%; + overflow-x: hidden; +} +.forge-page::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background-image: + radial-gradient(800px 400px at 80% -100px, rgba(232, 161, 58, 0.10), transparent 60%), + radial-gradient(circle at center, rgba(255, 255, 255, 0.04) 1px, transparent 1px); + background-size: auto, 22px 22px; + -webkit-mask-image: linear-gradient(to bottom, black, black 60%, transparent); + mask-image: linear-gradient(to bottom, black, black 60%, transparent); +} + +/* ───── Nav ───── */ + +.forge-nav { + position: relative; + border-bottom: 1px solid var(--rule); +} +.forge-nav__inner { + display: flex; + align-items: center; + justify-content: space-between; + height: 56px; +} +.forge-nav__left { + display: flex; + align-items: center; + gap: 32px; +} +.forge-nav__links { + display: flex; + gap: 22px; + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--fg-dim); +} +.forge-nav__right { + display: flex; + align-items: center; + gap: 14px; + font-family: var(--font-mono); + font-size: 12.5px; +} +.forge-nav__version { color: var(--fg-faint); } +.forge-nav__github { + color: var(--fg); + padding: 6px 12px; + border: 1px solid var(--rule); + border-radius: 4px; +} +.forge-nav__install { + color: var(--bg); + background: var(--amber); + padding: 6px 12px; + border-radius: 4px; + font-weight: 600; +} +.forge-nav__install:hover { color: var(--bg); background: #f4b04a; } + +/* Logo mark */ +.forge-mark { + display: flex; + align-items: center; + gap: 10px; + font-family: var(--font-mono); + font-size: 15px; + font-weight: 600; + color: var(--fg); +} +.forge-mark__glyph { + width: 22px; height: 22px; + border-radius: 4px; + background: linear-gradient(135deg, var(--amber), var(--amber-dim)); + display: grid; + place-items: center; + color: var(--bg); + font-size: 13px; + font-weight: 800; +} +.forge-mark__tld { color: var(--fg-faint); font-weight: 400; } + +/* ───── Hero ───── */ + +.forge-hero { position: relative; padding: 72px 0 88px; } +.forge-hero__grid { + display: grid; + grid-template-columns: 1.05fr 1fr; + gap: 56px; + align-items: center; +} + +.forge-kicker { + font-family: var(--font-mono); + font-size: 12px; + color: var(--amber); + letter-spacing: 1.5px; + text-transform: uppercase; + margin-bottom: 20px; +} + +.forge-hero h1 { + font-family: var(--font-sans); + font-size: 64px; + line-height: 1.02; + font-weight: 600; + letter-spacing: -0.03em; + color: var(--fg); +} +.forge-hero h1 em { + font-style: italic; + font-weight: 400; + color: var(--amber); +} + +.forge-hero__lede { + margin-top: 22px; + font-size: 17px; + line-height: 1.55; + color: var(--fg-dim); + max-width: 480px; +} + +.forge-hero__ctas { + display: flex; + gap: 12px; + margin-top: 32px; + align-items: center; +} + +.forge-btn-primary, +.forge-btn-secondary { + font-family: var(--font-mono); + font-size: 13.5px; + padding: 12px 18px; + border-radius: 5px; + display: inline-flex; + align-items: center; + gap: 8px; + text-decoration: none; + line-height: 1; +} +.forge-btn-primary { + background: var(--amber); + color: var(--bg); + font-weight: 600; +} +.forge-btn-primary:hover { color: var(--bg); background: #f4b04a; } +.forge-btn-secondary { + color: var(--fg); + border: 1px solid var(--rule); +} +.forge-btn-secondary:hover { color: var(--fg); border-color: var(--amber-dim); } + +/* Stat row under the hero CTAs */ +.forge-stats { + display: flex; + gap: 28px; + margin-top: 44px; + padding-top: 24px; + border-top: 1px solid var(--rule); + font-family: var(--font-mono); + font-size: 12px; +} +.forge-stat__n { color: var(--fg); font-size: 22px; } +.forge-stat__l { color: var(--fg-faint); margin-top: 2px; } + +/* ───── Terminal / code panel (hero right column) ───── */ + +.forge-terminal { + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 8px; + box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(232, 161, 58, 0.05); + overflow: hidden; +} +.forge-terminal__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + border-bottom: 1px solid var(--rule); + font-family: var(--font-mono); + font-size: 12px; + color: var(--fg-dim); +} +.forge-terminal__dots { display: flex; gap: 6px; } +.forge-terminal__dot { + width: 11px; height: 11px; + border-radius: 50%; +} +.forge-terminal__dot--red { background: #e84a3a; } +.forge-terminal__dot--amber { background: var(--amber); } +.forge-terminal__dot--green { background: var(--green); } +.forge-terminal__label { flex: 1; text-align: center; } +.forge-terminal__status { color: var(--amber); } + +/* Tabs above the code panel (sample switcher) */ +.forge-sample-tabs { + display: flex; + border-bottom: 1px solid var(--rule); + font-family: var(--font-mono); + font-size: 12px; +} +.forge-sample-tab { + padding: 10px 16px; + color: var(--fg-faint); + border-right: 1px solid var(--rule); + cursor: pointer; + background: transparent; + border-bottom: 2px solid transparent; +} +.forge-sample-tab.is-active { + color: var(--fg); + background: var(--bg); + border-bottom-color: var(--amber); +} +.forge-sample-tab:hover:not(.is-active) { color: var(--fg-dim); } + +/* Hero editor — CodeMirror instance. The CM build supplies its own gutter and + * line-number rendering via lineNumbers:true; cm-forge.css colors the tokens. */ +.forge-editor { + height: 320px; + overflow: hidden; +} +.forge-editor .CodeMirror { height: 100%; } + +/* Token colors (kept for blog code blocks rendered by highlight.js + pygments) */ +.tok-kw, .pygments .k, .pygments .kd, .pygments .kr { color: var(--amber); } +.tok-ty, .pygments .nb, .pygments .nc, .pygments .nt { color: var(--blue); } +.tok-s, .pygments .s, .pygments .s1, .pygments .s2 { color: var(--green); } +.tok-n, .pygments .m, .pygments .mi, .pygments .mf { color: #d8a8d8; } +.tok-c, .pygments .c, .pygments .c1, .pygments .cm { color: var(--fg-faint); font-style: italic; } +.tok-a, .pygments .na { color: var(--red); } +.tok-id, .pygments .n { color: var(--fg); } +.tok-p, .pygments .o, .pygments .p { color: var(--fg-dim); } + +/* Output panel beneath the code */ +.forge-output { + border-top: 1px solid var(--rule); + background: var(--bg); + padding: 12px 16px; + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--fg-dim); +} +.forge-output__line { white-space: pre; } +.forge-output__prompt { color: var(--green); } +.forge-output__stdout { color: var(--fg); } +.forge-output__stderr { color: var(--red); } +.forge-output__ok { color: var(--green); margin-top: 4px; } +.forge-output__hint { color: var(--fg-faint); } + +/* Run button on the terminal header */ +.forge-run { + font-family: var(--font-mono); + font-size: 12px; + color: var(--amber); + border: 1px solid var(--amber-dim); + border-radius: 3px; + padding: 3px 9px; + cursor: pointer; + background: transparent; +} +.forge-play { + font-family: var(--font-mono); + font-size: 12px; + color: var(--fg-dim); + border: 1px solid var(--rule); + border-radius: 3px; + padding: 3px 9px; + margin-right: 6px; + cursor: pointer; + background: transparent; + text-decoration: none; +} +.forge-play:hover { color: var(--fg); border-color: var(--fg-dim); } +.forge-run:hover { background: rgba(232, 161, 58, 0.10); } +.forge-run:disabled { opacity: 0.5; cursor: default; } +.forge-play:disabled { opacity: 0.5; cursor: default; } + +/* ───── Section labels (§ 0N — name) ───── */ + +.forge-section { + border-top: 1px solid var(--rule); + padding: 88px 0; +} +.forge-section--alt { background: var(--bg-2); } + +.forge-section-label { + display: flex; + align-items: center; + gap: 12px; + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--amber); + text-transform: uppercase; + letter-spacing: 1.4px; + margin-bottom: 16px; +} +.forge-section-label__num { color: var(--fg-faint); } +.forge-section-label__rule { flex: none; width: 36px; height: 1px; background: var(--amber-dim); } + +.forge-h2 { + font-family: var(--font-sans); + font-size: 38px; + font-weight: 600; + letter-spacing: -0.022em; + line-height: 1.1; + margin: 0; + color: var(--fg); +} +.forge-p { + color: var(--fg-dim); + font-size: 16px; + line-height: 1.6; + margin-top: 18px; + max-width: 460px; +} +.forge-link-amber { + display: inline-block; + margin-top: 14px; + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--amber); + text-decoration: none; + border-bottom: 1px solid var(--amber-dim); +} + +/* ───── § 01 Benchmarks ───── */ + +.forge-bench__grid { + display: grid; + grid-template-columns: 0.9fr 1.4fr; + gap: 56px; + margin-top: 28px; + align-items: start; +} +.forge-bench__meta { + margin-top: 22px; + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--fg-faint); +} +.forge-bench__meta > div { line-height: 1.6; } +.forge-bench__panel { + background: var(--bg); + border: 1px solid var(--rule); + border-radius: 8px; + padding: 20px 22px; +} +.forge-bench__controls { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 14px; + padding-bottom: 14px; + border-bottom: 1px dashed var(--rule); +} +.forge-bench__cats { display: flex; gap: 2px; font-family: var(--font-mono); font-size: 11.5px; } +.forge-bench__cat { + padding: 6px 12px; + color: var(--fg-faint); + border-radius: 4px; + cursor: pointer; + background: transparent; +} +.forge-bench__cat.is-active { color: var(--fg); background: var(--bg-2); border: 1px solid var(--rule); padding: 5px 11px; } +.forge-bench__bm { + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--fg); + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 4px; + padding: 5px 10px; +} +.forge-bench__row { + display: grid; + grid-template-columns: 170px 1fr 60px; + gap: 14px; + align-items: center; + padding: 7px 0; + border-bottom: 1px dashed var(--rule); +} +.forge-bench__row:last-child { border-bottom: none; } +.forge-bench__label { + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--fg-dim); +} +.forge-bench__row--das .forge-bench__label { color: var(--fg); font-weight: 600; } +.forge-bench__bar { + height: 14px; + background: var(--bg-2); + border-radius: 2px; + overflow: hidden; + position: relative; +} +.forge-bench__bar-fill { height: 100%; } +.forge-bench__row--das .forge-bench__bar-fill { background: var(--amber); } +.forge-bench__row--das-ref .forge-bench__bar-fill { background: var(--blue); opacity: 0.75; } +.forge-bench__row--das-ref .forge-bench__label { color: var(--blue); font-style: italic; } +.forge-bench__row--native .forge-bench__bar-fill { background: var(--green); } +.forge-bench__row--rival .forge-bench__bar-fill { background: var(--fg-dim); opacity: 0.55; } +.forge-bench__num { font-family: var(--font-mono); font-size: 12px; color: var(--fg); text-align: right; } +.forge-bench__caption { + margin-top: 16px; + padding-top: 14px; + border-top: 1px dashed var(--rule); + font-family: var(--font-mono); + font-size: 11px; + color: var(--fg-faint); +} + +/* ───── § 02 Features ───── */ + +.forge-features__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: var(--rule); + margin-top: 36px; + border: 1px solid var(--rule); +} +.forge-feature { + background: var(--bg); + padding: 24px 24px 28px; + min-height: 180px; +} +.forge-feature__kicker { + font-family: var(--font-mono); + color: var(--amber); + font-size: 11.5px; + letter-spacing: 1.4px; + text-transform: uppercase; +} +.forge-feature__title { + margin-top: 12px; + font-size: 19px; + font-weight: 600; + letter-spacing: -0.01em; +} +.forge-feature__body { + margin-top: 10px; + color: var(--fg-dim); + font-size: 14px; + line-height: 1.55; +} + +/* ───── § 03 Platforms ───── */ + +.forge-platforms__grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + gap: 56px; + align-items: start; +} +.forge-platforms__cells { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: var(--rule); + border: 1px solid var(--rule); +} +.forge-platform { + background: var(--bg); + padding: 20px 18px; + min-height: 84px; +} +.forge-platform__name { font-family: var(--font-mono); font-size: 13.5px; color: var(--fg); } +.forge-platform__sub { font-family: var(--font-mono); font-size: 11.5px; color: var(--fg-faint); margin-top: 4px; } + +/* ───── § 04 Install ───── */ + +.forge-install__grid { + display: grid; + grid-template-columns: 1fr 1.3fr; + gap: 56px; + align-items: start; + margin-top: 24px; +} +.forge-install__panel { + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 8px; + overflow: hidden; +} +.forge-install__tabs { + display: flex; + border-bottom: 1px solid var(--rule); + font-family: var(--font-mono); + font-size: 12px; +} +.forge-install__tab { + cursor: pointer; + padding: 10px 16px; + color: var(--fg-faint); + border-right: 1px solid var(--rule); + background: transparent; + border-bottom: 2px solid transparent; +} +.forge-install__tab.is-active { + color: var(--fg); + background: var(--bg); + border-bottom-color: var(--amber); +} +.forge-install__body { + padding: 20px 22px; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.7; + color: var(--fg); + min-height: 230px; +} +.forge-install__pane { display: none; } +.forge-install__pane.is-active { display: block; } +.forge-install__hint { color: var(--fg-faint); } +.forge-install__cmd { } +.forge-install__cmd .prompt { color: var(--green); } +.forge-install__sp { height: 14px; } + +/* ───── § 05 News ───── */ + +.forge-news__grid { + display: grid; + grid-template-columns: 1fr 1.5fr; + gap: 56px; + align-items: start; +} +.forge-news__row { + display: grid; + grid-template-columns: 120px 90px 1fr; + gap: 18px; + padding: 18px 0; + border-top: 1px solid var(--rule); +} +.forge-news__date { font-family: var(--font-mono); font-size: 12px; color: var(--fg-faint); } +.forge-news__tag { + font-family: var(--font-mono); + font-size: 11px; + color: var(--amber); + border: 1px solid var(--amber-dim); + border-radius: 3px; + padding: 2px 7px; + align-self: start; + justify-self: start; + text-transform: lowercase; +} +.forge-news__title { color: var(--fg); font-size: 14.5px; line-height: 1.4; } + +/* ───── Blog: listing + post ───── */ + +.forge-blog-list { margin-top: 24px; } +.forge-blog-item { + display: grid; + grid-template-columns: 140px 90px 1fr; + gap: 18px; + padding: 22px 0; + border-top: 1px solid var(--rule); + align-items: start; +} +.forge-blog-item:hover { background: rgba(232, 161, 58, 0.02); } +.forge-blog-item__date { font-family: var(--font-mono); font-size: 12px; color: var(--fg-faint); } +.forge-blog-item__tag { + font-family: var(--font-mono); + font-size: 11px; + color: var(--amber); + border: 1px solid var(--amber-dim); + border-radius: 3px; + padding: 2px 7px; + align-self: start; + justify-self: start; + text-transform: lowercase; +} +.forge-blog-item__title { + font-family: var(--font-sans); + font-size: 19px; + font-weight: 600; + color: var(--fg); + letter-spacing: -0.01em; + line-height: 1.3; +} +.forge-blog-item__title a { color: inherit; } +.forge-blog-item__title a:hover { color: var(--amber); } +.forge-blog-item__excerpt { + margin-top: 8px; + color: var(--fg-dim); + font-size: 14.5px; + line-height: 1.55; +} + +.forge-post { padding: 56px 0 88px; } +.forge-post__meta { + display: flex; + align-items: center; + gap: 14px; + font-family: var(--font-mono); + font-size: 12px; + color: var(--fg-faint); + margin-bottom: 18px; +} +.forge-post__meta .forge-news__tag { font-size: 11px; } +.forge-post h1.forge-post__title { + font-family: var(--font-sans); + font-size: 44px; + line-height: 1.1; + font-weight: 600; + letter-spacing: -0.02em; + margin: 0 0 24px; + color: var(--fg); +} +.forge-post__body { + max-width: 760px; + font-size: 16px; + line-height: 1.65; + color: var(--fg); +} +.forge-post__body h2 { + margin: 40px 0 14px; + font-family: var(--font-sans); + font-size: 26px; + font-weight: 600; + letter-spacing: -0.015em; + color: var(--fg); +} +.forge-post__body h3 { + margin: 32px 0 12px; + font-family: var(--font-sans); + font-size: 20px; + font-weight: 600; + color: var(--fg); +} +.forge-post__body p { margin: 14px 0; color: var(--fg-dim); } +.forge-post__body strong, .forge-post__body b { color: var(--fg); font-weight: 600; } +.forge-post__body ul, .forge-post__body ol { margin: 14px 0 14px 24px; color: var(--fg-dim); } +.forge-post__body li { margin: 6px 0; } +.forge-post__body a { color: var(--amber); border-bottom: 1px solid var(--amber-dim); } +.forge-post__body a:hover { color: var(--fg); border-bottom-color: var(--fg); } +.forge-post__body blockquote { + margin: 18px 0; + padding: 12px 18px; + border-left: 2px solid var(--amber-dim); + background: var(--bg-2); + color: var(--fg-dim); +} +.forge-post__body code { + font-family: var(--font-mono); + font-size: 13px; + background: var(--bg-2); + border: 1px solid var(--rule); + color: var(--amber); + padding: 1px 5px; + border-radius: 3px; +} +.forge-post__body pre { + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 8px; + padding: 16px 18px; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.65; + margin: 18px 0; +} +.forge-post__body pre code { + background: none; + border: none; + padding: 0; + color: var(--fg); +} +.forge-post__body hr { + border: 0; + border-top: 1px solid var(--rule); + margin: 32px 0; +} +.forge-post__body img { margin: 18px 0; border-radius: 4px; } + +.forge-post-nav { + margin-top: 56px; + padding-top: 24px; + border-top: 1px solid var(--rule); + display: flex; + justify-content: space-between; + font-family: var(--font-mono); + font-size: 12.5px; +} +.forge-post-nav a { color: var(--fg-dim); } +.forge-post-nav a:hover { color: var(--amber); } + +/* Changelist: same layout as blog listing, wider. */ +.forge-changelist { margin-top: 24px; } + +/* ───── Footer ───── */ + +.forge-footer { + padding: 56px 0 64px; + border-top: 1px solid var(--rule); + font-family: var(--font-mono); + font-size: 12.5px; +} +.forge-footer__grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1fr 1fr; + gap: 32px; +} +.forge-footer__about { + margin-top: 14px; + color: var(--fg-faint); + line-height: 1.6; +} +.forge-footer__col-head { + color: var(--fg-faint); + margin-bottom: 12px; + text-transform: uppercase; + letter-spacing: 1.2px; + font-size: 11px; +} +.forge-footer__link { color: var(--fg); display: block; margin-bottom: 8px; } +.forge-footer__link:hover { color: var(--amber); } + +/* ───── Responsive ───── */ + +/* Tablet 768–1023: stack hero */ +@media (max-width: 1023px) { + .forge-hero__grid { grid-template-columns: 1fr; gap: 40px; } + .forge-nav__links { gap: 18px; } +} + +/* Mobile <768 */ +@media (max-width: 767px) { + .forge-container { padding: 0 20px; } + .forge-hero { padding: 48px 0 64px; } + .forge-hero h1 { font-size: 38px; } + .forge-hero__lede { font-size: 15px; } + .forge-hero__ctas { flex-direction: column; align-items: stretch; } + .forge-btn-primary, .forge-btn-secondary { justify-content: center; } + .forge-stats { gap: 18px; flex-wrap: wrap; } + .forge-stat { min-width: 45%; } + .forge-editor { height: 240px; } + .forge-editor .CodeMirror { font-size: 11px; } + .forge-nav__links { display: none; } + .forge-section { padding: 56px 0; } + .forge-h2 { font-size: 26px; } + .forge-bench__grid, + .forge-platforms__grid, + .forge-install__grid, + .forge-news__grid { grid-template-columns: 1fr; gap: 36px; } + .forge-bench__row { grid-template-columns: 110px 1fr 50px; gap: 8px; } + .forge-bench__bar { height: 5px; } + .forge-features__grid { grid-template-columns: 1fr; } + .forge-platforms__cells { grid-template-columns: repeat(2, 1fr); } + .forge-footer__grid { grid-template-columns: 1fr 1fr; } + .forge-news__row { grid-template-columns: 90px 1fr; grid-template-areas: "date title" "tag tag"; } + .forge-news__date { grid-area: date; } + .forge-news__title { grid-area: title; } + .forge-news__tag { grid-area: tag; } +} + +/* Tablet bench grid adjustment */ +@media (max-width: 1023px) { + .forge-bench__grid, + .forge-platforms__grid, + .forge-install__grid, + .forge-news__grid { grid-template-columns: 1fr; gap: 40px; } + .forge-features__grid { grid-template-columns: repeat(2, 1fr); } + .forge-footer__grid { grid-template-columns: 1fr 1fr 1fr; } +} diff --git a/site/files/forge.js b/site/files/forge.js new file mode 100644 index 0000000000..695a5964e1 --- /dev/null +++ b/site/files/forge.js @@ -0,0 +1,413 @@ +// Forge interactivity: hero sample switcher + (later) install-tab toggle + +// bench renderer. Phase 1 ships only the hero pieces. + +(function () { + const SAMPLES = { + hello: { + label: 'hello.das', + code: +`[export] +def main() { + let name = "world" + print("hello, {name}\\n") + for (i in range(3)) { + print(" tick {i}\\n") + } +}`, + output: [ + { kind: 'cmd', text: 'daslang run hello.das' }, + { kind: 'stdout', text: 'hello, world' }, + { kind: 'hint', text: ' tick 0 · tick 1 · tick 2' }, + { kind: 'ok', text: '✓ 0.42 ms · interpreted' }, + ], + }, + fib: { + label: 'fib.das', + code: +`def fib_rec(n : int) : int { + return n < 2 ? n : fib_rec(n-1) + fib_rec(n-2) +} + +def fib_iter(n : int) : int { + var a = 0 + var b = 1 + for (_ in range(n)) { + let t = a + b; a = b; b = t + } + return a +} + +[export] +def main() { + for (i in range(10)) { + print("fib({i}) rec={fib_rec(i)} iter={fib_iter(i)}\\n") + } +}`, + output: [ + { kind: 'cmd', text: 'daslang run fib.das' }, + { kind: 'stdout', text: 'fib(0) rec=0 iter=0' }, + { kind: 'stdout', text: 'fib(1) rec=1 iter=1' }, + { kind: 'hint', text: '…through fib(9) rec=34 iter=34' }, + { kind: 'ok', text: '✓ 0.21 ms · interpreted' }, + ], + }, + generic: { + label: 'generic.das', + code: +`struct Pet { + name : string + age : int +} + +// Generic: compile-time dispatched per type via static_if + typeinfo. +def show(v : auto(T)) : string { + static_if (typeinfo is_numeric(type)) { + return "{typeinfo typename(type)}: {v}" + } elif (typeinfo is_string(type)) { + return "string: {v}" + } elif (typeinfo is_ref_type(type)) { + static_if (typeinfo has_field(v)) { + return "{typeinfo typename(type)}: {v.name}" + } + return "{typeinfo typename(type)}: " + } else { + return "?: {v}" + } +} + +// Partial specialization: wins for any array, regardless of T. +def show(v : array) : string { + return "array<{typeinfo typename(type)}>[{length(v)}]" +} + +[export] +def main() { + let nums <- [1, 2, 3, 4] + print(show(42) + "\\n") + print(show(3.14) + "\\n") + print(show("hello") + "\\n") + print(show(Pet(name="ada", age=3)) + "\\n") + print(show(nums) + "\\n") +}`, + output: [ + { kind: 'cmd', text: 'daslang run generic.das' }, + { kind: 'stdout', text: 'int const: 42' }, + { kind: 'stdout', text: 'float const: 3.14' }, + { kind: 'stdout', text: 'string: hello' }, + { kind: 'stdout', text: 'Pet const: ada' }, + { kind: 'stdout', text: 'array[4]' }, + { kind: 'ok', text: '✓ 0.13 ms · interpreted' }, + ], + }, + }; + + function renderOutput(outputEl, lines) { + const html = lines.map(l => { + if (l.kind === 'cmd') { + return `
$ ${escapeHtml(l.text)}
`; + } + const cls = 'forge-output__' + (l.kind === 'cmd' ? 'stdout' : l.kind); + return `
${escapeHtml(l.text)}
`; + }).join(''); + outputEl.innerHTML = html; + } + + function escapeHtml(s) { + return s.replace(/&/g, '&').replace(//g, '>'); + } + + let currentSample = 'hello'; + let heroEditor = null; + // User edits per sample key — preserved across tab switches. + const editedCode = {}; + + function initHeroEditor() { + const el = document.getElementById('hero-editor'); + if (!el || !window.CodeMirror) return; + heroEditor = window.CodeMirror(el, { + mode: 'daslang', + theme: 'forge', + lineNumbers: true, + matchBrackets: true, + indentWithTabs: false, + tabSize: 4, + indentUnit: 4, + styleActiveLine: true, + }); + } + + function showSample(key) { + const sample = SAMPLES[key]; + if (!sample) return; + // Save any in-flight edit for the outgoing tab before switching. + if (heroEditor && currentSample && currentSample !== key) { + const live = heroEditor.getValue(); + if (live && live !== SAMPLES[currentSample]?.code) { + editedCode[currentSample] = live; + } + } + currentSample = key; + const text = editedCode[key] ?? sample.code; + if (heroEditor) heroEditor.setValue(text); + const fileEl = document.getElementById('hero-file'); + const outEl = document.getElementById('hero-output'); + if (fileEl) fileEl.textContent = sample.label; + if (outEl) renderOutput(outEl, sample.output); + document.querySelectorAll('.forge-sample-tab').forEach(t => { + t.classList.toggle('is-active', t.dataset.sample === key); + }); + } + + function wireSampleTabs() { + document.querySelectorAll('.forge-sample-tab').forEach(tab => { + tab.addEventListener('click', () => showSample(tab.dataset.sample)); + }); + } + + // ↗ playground — open the current hero buffer in the full IDE. + // Code travels via URL hash; playground-init.js applies it after the CM + // instance comes up. + function wirePlaygroundButton() { + const btn = document.getElementById('hero-playground'); + if (!btn) return; + btn.addEventListener('click', () => { + const code = heroEditor ? heroEditor.getValue() : ''; + const url = 'playground/index.html#code=' + encodeURIComponent(code); + window.open(url, '_blank'); + }); + } + + async function wireRunButton() { + const btn = document.getElementById('hero-run'); + const outEl = document.getElementById('hero-output'); + if (!btn || !outEl) return; + // Enable as soon as the runner.js is loaded — even if WASM isn't yet + // available, we let the click try to load it and degrade. + btn.disabled = false; + btn.addEventListener('click', async () => { + if (!window.daslangRun) { + renderOutput(outEl, [{ kind: 'stderr', text: 'runner unavailable — try the full /playground/' }]); + return; + } + btn.disabled = true; + const prev = btn.textContent; + btn.textContent = '… running'; + renderOutput(outEl, [ + { kind: 'cmd', text: 'daslang run ' + (SAMPLES[currentSample]?.label || 'hero.das') }, + { kind: 'hint', text: '…' }, + ]); + try { + const r = await window.daslangRun(heroEditor ? heroEditor.getValue() : ''); + if (r.unavailable) { + renderOutput(outEl, [ + { kind: 'stderr', text: 'WASM runtime not deployed at ./files/wasm/ — see site/README.md' }, + { kind: 'hint', text: 'open the full IDE at /playground/ for now' }, + ]); + } else { + const lines = [{ kind: 'cmd', text: 'daslang run ' + (SAMPLES[currentSample]?.label || 'hero.das') }]; + if (r.stdout) lines.push({ kind: 'stdout', text: r.stdout }); + if (r.stderr) lines.push({ kind: 'stderr', text: r.stderr }); + const tag = r.exitCode === 0 ? 'ok' : 'stderr'; + lines.push({ kind: tag, text: (r.exitCode === 0 ? '✓ ' : '✗ ') + r.durationMs.toFixed(2) + ' ms · interpreted' }); + renderOutput(outEl, lines); + } + } finally { + btn.disabled = false; + btn.textContent = prev; + } + }); + } + + // ─── Install-section tab toggle ──────────────────────────────── + function wireInstallTabs() { + const tabs = document.querySelectorAll('.forge-install__tab'); + const panes = document.querySelectorAll('.forge-install__pane'); + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const key = tab.dataset.pane; + tabs.forEach(t => t.classList.toggle('is-active', t === tab)); + panes.forEach(p => p.classList.toggle('is-active', p.dataset.pane === key)); + }); + }); + } + + // ─── Benchmarks (§ 01) ───────────────────────────────────────── + // + // Source: site/files/profile_results.json, vendored from + // github.com/borisbat/dasProfile. Refreshed by .github/workflows/pages.yml. + + // Column order + display headers per category. Ported from + // dasProfile/update_readme_benchmarks.py:SECTION_CONFIGS so the chart + // mirrors the README exactly. + const BENCH_COLS = { + 'Interpreted': [ + { id: 'DAS INTERPRETER', label: 'Daslang · interp', group: 'das' }, + { id: 'LUAU', label: 'Luau', group: 'rival' }, + { id: 'LUA', label: 'Lua', group: 'rival' }, + { id: 'LUAJIT -joff', label: 'LuaJIT -joff', group: 'rival' }, + { id: 'QUIRREL', label: 'Quirrel', group: 'rival' }, + { id: 'QUICKJS', label: 'QuickJS', group: 'rival' }, + { id: 'MONO --interpreter', label: 'Mono --interpreter',group: 'rival' }, + ], + 'AOT or JIT': [ + { id: 'DAS AOT', label: 'Daslang · AOT', group: 'das' }, + { id: 'DAS JIT', label: 'Daslang · JIT', group: 'das' }, + { id: 'C++', label: 'C++ · -O2', group: 'native' }, + { id: 'LUAU --codegen', label: 'Luau --codegen', group: 'rival' }, + { id: 'LUAJIT', label: 'LuaJIT', group: 'rival' }, + { id: 'MONO', label: 'Mono', group: 'rival' }, + { id: '.NET', label: '.NET', group: 'rival' }, + ], + }; + + let benchData = null; + let benchCat = 'Interpreted'; + let benchBm = 'sha256'; + + async function loadBench() { + try { + const r = await fetch('./files/profile_results.json'); + benchData = await r.json(); + } catch (e) { + const cap = document.getElementById('bench-caption'); + if (cap) cap.textContent = 'benchmark data unavailable'; + return; + } + populateBmDropdown(); + renderBench(); + wireBenchControls(); + } + + function populateBmDropdown() { + const sel = document.getElementById('bench-bm'); + if (!sel || !benchData) return; + const cat = benchData[benchCat]; + if (!cat) return; + sel.innerHTML = Object.keys(cat).map(name => + `${escapeHtml(name)}` + ).join(''); + } + + function wireBenchControls() { + document.querySelectorAll('.forge-bench__cat').forEach(btn => { + btn.addEventListener('click', () => { + benchCat = btn.dataset.cat; + document.querySelectorAll('.forge-bench__cat').forEach(b => + b.classList.toggle('is-active', b.dataset.cat === benchCat)); + if (!(benchBm in (benchData[benchCat] || {}))) { + benchBm = Object.keys(benchData[benchCat] || { sha256: [] })[0]; + } + populateBmDropdown(); + renderBench(); + }); + }); + const sel = document.getElementById('bench-bm'); + if (sel) sel.addEventListener('change', () => { + benchBm = sel.value; + renderBench(); + }); + } + + function renderBench() { + const rowsEl = document.getElementById('bench-rows'); + const capEl = document.getElementById('bench-caption'); + if (!rowsEl || !benchData) return; + const cat = benchData[benchCat] || {}; + const series = cat[benchBm] || []; + const byLang = Object.fromEntries(series.map(e => [e.language, e.time])); + let cols = BENCH_COLS[benchCat] || []; + + // Experiment: on the AOT·JIT chart, splice in Daslang's interpreter + // time (from the Interpreted dataset) as a reference row so readers + // can see how the interp tier stacks against the compiled tiers. It's + // an outlier — clearly slower — but informative. + if (benchCat === 'AOT or JIT') { + const interpSeries = (benchData['Interpreted'] || {})[benchBm] || []; + const interpEntry = interpSeries.find(e => e.language === 'DAS INTERPRETER'); + if (interpEntry) { + byLang['DAS INTERP (ref)'] = interpEntry.time; + cols = [ + ...cols.slice(0, 2), // DAS AOT, DAS JIT + { id: 'DAS INTERP (ref)', label: 'Daslang · interp (ref)', group: 'das-ref' }, + ...cols.slice(2), + ]; + } + } + + const times = cols.map(c => byLang[c.id]).filter(t => typeof t === 'number' && t > 0); + if (times.length === 0) { + rowsEl.innerHTML = '
no data for this combination
'; + return; + } + const fastest = Math.min(...times); + const slowest = Math.max(...times); + + rowsEl.innerHTML = cols.map(c => { + const t = byLang[c.id]; + if (typeof t !== 'number' || t <= 0) { + return `
+
${escapeHtml(c.label)}
+
+
+
`; + } + const rel = t / fastest; + const pct = (t / slowest) * 100; + return `
+
${escapeHtml(c.label)}
+
+
${rel.toFixed(2)}×
+
`; + }).join(''); + + if (capEl) { + const cpu = benchData.cpu || 'unknown CPU'; + const v = benchData.versions || {}; + const ts = (benchData.timestamp || '').split(' ').slice(0, 4).join(' '); + capEl.textContent = `${cpu} · daslang ${v.daslang || '?'} · LLVM ${v.llvm || '?'} · captured ${ts} · source: github.com/borisbat/dasProfile`; + } + } + + // ─── § 05 News feed (top 5 from news.json) ───────────────────── + async function loadNews() { + const rowsEl = document.getElementById('news-rows'); + if (!rowsEl) return; + let news; + try { + const r = await fetch('./files/news.json'); + news = await r.json(); + } catch (e) { + return; // leave the hardcoded placeholders + } + const top = (news || []).slice(0, 5); + if (!top.length) return; + rowsEl.innerHTML = top.map(n => { + const title = escapeHtml(n.title); + const titleHtml = n.link + ? `${title}` + : title; + return `
+
${escapeHtml(n.date)}
+
${escapeHtml(n.tag)}
+
${titleHtml}
+
`; + }).join(''); + } + + function init() { + wireSampleTabs(); + initHeroEditor(); + showSample('hello'); + wireRunButton(); + wirePlaygroundButton(); + wireInstallTabs(); + loadBench(); + loadNews(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/site/files/highlight.js b/site/files/highlight.js new file mode 100644 index 0000000000..ca1ed01a77 --- /dev/null +++ b/site/files/highlight.js @@ -0,0 +1,111 @@ +// Token-based highlighter for blog code blocks. +// Used by markdown-fenced `
` produced by
+// site/blog/build_blog.py. The hero + playground now use CodeMirror — this
+// file no longer ships an editable variant.
+//
+// Keyword/type sets come from site/files/cm/daslang-keywords.js when loaded;
+// otherwise the local fallback (smaller, but always present) takes over so
+// pre-generated blog pages keep working if the keywords script is unavailable.
+
+(function (global) {
+    const FALLBACK_KEYWORDS = new Set([
+        'def', 'let', 'var', 'for', 'in', 'if', 'else', 'elif', 'while', 'return',
+        'struct', 'class', 'require', 'module', 'options', 'export', 'typeinfo',
+        'static_if', 'true', 'false', 'null', 'as', 'is', 'cast',
+        'yield',
+    ]);
+    const FALLBACK_TYPES = new Set([
+        'int', 'int2', 'int3', 'int4', 'uint', 'uint2', 'uint3', 'uint4',
+        'float', 'float2', 'float3', 'float4', 'double', 'bool', 'string',
+        'void', 'auto', 'array', 'table', 'range', 'tuple', 'variant',
+        'lambda', 'generator', 'block', 'iterator',
+    ]);
+
+    function resolveSets() {
+        const ext = global.daslangKeywords;
+        if (ext) {
+            // Atoms collapse into 'kw' so they get amber via .tok-kw.
+            const KW = new Set([...ext.KEYWORDS, ...ext.ATOMS]);
+            return { KEYWORDS: KW, TYPES: ext.TYPES };
+        }
+        return { KEYWORDS: FALLBACK_KEYWORDS, TYPES: FALLBACK_TYPES };
+    }
+
+    function tokenize(src) {
+        const { KEYWORDS, TYPES } = resolveSets();
+        const out = [];
+        let i = 0;
+        const push = (t, k) => out.push({ t, k });
+        while (i < src.length) {
+            const c = src[i];
+            if (c === '/' && src[i + 1] === '/') {
+                let j = i;
+                while (j < src.length && src[j] !== '\n') j++;
+                push(src.slice(i, j), 'c'); i = j; continue;
+            }
+            if (c === '"') {
+                let j = i + 1;
+                while (j < src.length && src[j] !== '"') {
+                    if (src[j] === '\\') j++;
+                    j++;
+                }
+                j = Math.min(j + 1, src.length);
+                push(src.slice(i, j), 's'); i = j; continue;
+            }
+            if (c === '[') {
+                let j = i;
+                while (j < src.length && src[j] !== ']') j++;
+                j = Math.min(j + 1, src.length);
+                push(src.slice(i, j), 'a'); i = j; continue;
+            }
+            if (/[A-Za-z_]/.test(c)) {
+                let j = i;
+                while (j < src.length && /[A-Za-z0-9_]/.test(src[j])) j++;
+                const w = src.slice(i, j);
+                const k = KEYWORDS.has(w) ? 'kw' : TYPES.has(w) ? 'ty' : 'id';
+                push(w, k); i = j; continue;
+            }
+            if (/[0-9]/.test(c)) {
+                let j = i;
+                while (j < src.length && /[0-9.]/.test(src[j])) j++;
+                push(src.slice(i, j), 'n'); i = j; continue;
+            }
+            if (c === '<') {
+                let j = i + 1;
+                while (j < src.length && src[j] !== '>' && src[j] !== '\n') j++;
+                if (src[j] === '>') { push(src.slice(i, j + 1), 'ty'); i = j + 1; continue; }
+            }
+            push(c, /\s/.test(c) ? 'w' : 'p');
+            i++;
+        }
+        return out;
+    }
+
+    function esc(s) {
+        return s.replace(/&/g, '&').replace(//g, '>');
+    }
+
+    // Walk all `
` (or "language-das")
+    // and replace their contents with tokenized HTML. Idempotent.
+    function highlightAll() {
+        const blocks = document.querySelectorAll(
+            'code.language-daslang, code.language-das, pre code:not(.tok-id):not(.tok-kw)');
+        blocks.forEach(code => {
+            const cls = code.className || '';
+            if (cls && !/language-(das|daslang)/.test(cls)) return;
+            const src = code.textContent;
+            const tokens = tokenize(src);
+            code.innerHTML = tokens.map(t =>
+                `${esc(t.t)}`
+            ).join('');
+        });
+    }
+
+    if (document.readyState === 'loading') {
+        document.addEventListener('DOMContentLoaded', highlightAll);
+    } else {
+        setTimeout(highlightAll, 0);
+    }
+
+    global.dasHighlight = { tokenize, highlightAll };
+})(window);
diff --git a/site/files/pixel.gif b/site/files/pixel.gif
deleted file mode 100644
index 1d11fa9ada9e93505b3d736acb204083f45d5fbf..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 43
scmZ?wbhEHbWMp7uX!y@?;J^U}1_s5SEQ~;kK?g*DWEhy3To@Uw0n;G|I{*Lx

diff --git a/site/files/runner.js b/site/files/runner.js
new file mode 100644
index 0000000000..fd1572a595
--- /dev/null
+++ b/site/files/runner.js
@@ -0,0 +1,98 @@
+// Mini-playground WASM runner for the hero panel.
+//
+// Loads daslang_static.{js,wasm} from ./files/wasm/ (vendored by pages.yml
+// from the wasm_build.yml artifact) and exposes window.daslangRun(source)
+// returning {stdout, stderr, durationMs}.
+//
+// Gracefully degrades: if the WASM artifact is missing (local-dev without a
+// build), daslangRun returns an explanatory message and the ▶ Run button
+// stays in a degraded "open full playground" state.
+
+(function () {
+    const WASM_DIR = './files/wasm/';
+
+    let stdoutBuf = [];
+    let stderrBuf = [];
+    let runtimeReady = false;
+    let runtimeBroken = false;
+    let runtimePromise = null;
+
+    // Pre-define Module so daslang_static.js sees our overrides at startup.
+    window.Module = {
+        preRun: [],
+        postRun: [],
+        print: (text) => { stdoutBuf.push(text); },
+        printErr: (text) => { stderrBuf.push(text); },
+        onRuntimeInitialized: () => { runtimeReady = true; },
+        onAbort: (err) => { runtimeBroken = true; console.error('[daslang] runtime aborted:', err); },
+        locateFile: (path) => WASM_DIR + path,
+        noExitRuntime: true,
+    };
+
+    function loadRuntime() {
+        if (runtimePromise) return runtimePromise;
+        runtimePromise = new Promise((resolve, reject) => {
+            const s = document.createElement('script');
+            s.src = WASM_DIR + 'daslang_static.js';
+            s.async = true;
+            s.onerror = () => {
+                runtimeBroken = true;
+                reject(new Error('daslang_static.js not found at ' + s.src));
+            };
+            // Resolve once the runtime is ready — onRuntimeInitialized fires
+            // after the wasm has loaded and exports are wired.
+            const tick = () => {
+                if (runtimeReady) return resolve();
+                if (runtimeBroken) return reject(new Error('runtime aborted'));
+                setTimeout(tick, 50);
+            };
+            document.head.appendChild(s);
+            tick();
+        });
+        return runtimePromise;
+    }
+
+    async function daslangRun(source) {
+        const t0 = performance.now();
+        try {
+            await loadRuntime();
+        } catch (e) {
+            return {
+                stdout: '',
+                stderr: '',
+                durationMs: 0,
+                exitCode: -1,
+                unavailable: true,
+                error: e.message,
+            };
+        }
+        stdoutBuf = [];
+        stderrBuf = [];
+        let exitCode = 0;
+        // FS lives on Module in modern Emscripten modularized builds, not as
+        // a global. The web/ui IDE happens to work because its build exposes
+        // FS through the script's IIFE; ours doesn't.
+        const M = window.Module;
+        try {
+            M.FS.writeFile('main.das', source);
+        } catch (e) {
+            stderrBuf.push('FS error: ' + (e.message || e));
+        }
+        try {
+            M.callMain(['main.das']);
+        } catch (e) {
+            stderrBuf.push(String(e.message || e));
+            exitCode = -1;
+        }
+        const durationMs = performance.now() - t0;
+        return {
+            stdout: stdoutBuf.join('\n'),
+            stderr: stderrBuf.join('\n'),
+            durationMs,
+            exitCode,
+        };
+    }
+
+    window.daslangRun = daslangRun;
+    window.daslangRuntimeReady = () => runtimeReady;
+})();
diff --git a/site/files/spacer.gif b/site/files/spacer.gif
deleted file mode 100644
index 099c95f3c4975c910c5e02d85ff2cc414952561f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 49
zcmZ?wbhEHb a {
-  display: inline-block;
-  padding: 0.3rem 0.8rem;
-}
-#menu a:visited {
-  text-decoration: none;
-  color: white;
-}
-#menu a:link {
-  text-decoration: none;
-  color: white;
-}
-#menu a:hover {
-  text-decoration: none;
-  color: #cd8c58;
-}
-
-main {
-  box-sizing: content-box;
-  margin: 0 auto;
-  padding: 0 1.5rem;
-  max-width: 100ch;
-}
-
-.intro {
-  margin-top: 3rem;
-  display: flex;
-  flex-wrap: wrap;
-}
-.intro img {
-  display: inline-block;
-  max-width: 100%;
-  margin: 0 auto;
-  position: relative;
-  left: -10px; /* The current logo has weird extra space on the left */
-}
-.intro > h1 {
-  flex-basis: 192px;
-  flex-basis: calc(192px + 2rem);
-  flex-grow: 1;
-  margin: 0 auto;
-  display: flex;
-  overflow: hidden;
-  align-items: center;
-}
-.news {
-  flex-basis: 60ch;
-  flex-grow: 99;
-}
-.news p {
-  font-family: 'Tahoma', Helvetica, sans-serif;
-  font-size: 1.3rem;
-  color: black;
-  margin-top: 0.5rem;
-}
-
-h2 {
-  font: 400 1.625rem 'Lato', helvetica, arial, sans-serif;
-  color: #39c;
-}
-a {
-  color: #3399cc;
-  text-decoration: none;
-  transition: color 0.2s, text-shadow 0.2s;
-  outline: none;
-}
-a:hover {
-  color: #143352;
-}
-
-.snippets pre {
-  color: #3d4d5c;
-  font: 13px 'Source Code Pro', Menlo, Monaco, Consolas, monospace;
-  background: #fcfcfc;
-  border-radius: 2px;
-  border: dashed 1px #dae2e7;
-  padding: 0.5rem;
-  overflow: auto;
-}
-
-footer {
-  margin-top: 2rem;
-  border-top: 1px solid #000;
-  text-align: center;
-  color: gray;
-}
-
-iframe {
-  max-width: 100%;
-}
-
-/* Bar charts on the performance page.*/
-/* Licensed with MIT license from https://github.com/wren-lang/wren */
-
-table.chart {
-  margin: 4px 0 0 0;
-  padding: 5px 0 5px 25px;
-}
-
-table.chart td, th {
-  line-height: 18px;
-  margin: 0;
-  padding: 1px 0;
-}
-
-table.chart th {
-  font-size: 14px;
-  width: 200px;
-}
-
-table.chart .chart-bar {
-  display: inline-block;
-  font: 12px;
-  color: #222222;
-  background: hsl(200, 60%, 50%);
-  border-bottom: solid 1px hsl(210, 60%, 20%);
-  text-align: right;
-  border-radius: 2px;
-}
-
-table.chart .chart-bar.featured {
-  background: hsl(200, 60%, 70%);
-  border-bottom: solid 1px hsl(210, 60%, 20%);
-}
-
-/* End of wren style */
\ No newline at end of file
diff --git a/site/files/widgets.js b/site/files/widgets.js
deleted file mode 100644
index 8f8962306a..0000000000
--- a/site/files/widgets.js
+++ /dev/null
@@ -1,8 +0,0 @@
-Function&&Function.prototype&&Function.prototype.bind&&(/MSIE ([6789]|10)/.test(navigator.userAgent)||(window.__twttr&&window.__twttr.widgets&&window.__twttr.widgets.loaded&&window.twttr.widgets.load&&window.twttr.widgets.load(),window.__twttr&&window.__twttr.widgets&&window.__twttr.widgets.init||function(t){function e(e){for(var n,i,o=e[0],s=e[1],a=0,c=[];a-1},forIn:i,isObject:s,isEmptyObject:a,toType:o,isType:function(t,e){return t==o(e)},toRealArray:u}},function(t,e){t.exports=window},function(t,e,n){var r=n(6);t.exports=function(){var t=this;this.promise=new r(function(e,n){t.resolve=e,t.reject=n})}},function(t,e,n){var r=n(11),i=/(?:^|(?:https?:)?\/\/(?:www\.)?twitter\.com(?::\d+)?(?:\/intent\/(?:follow|user)\/?\?screen_name=|(?:\/#!)?\/))@?([\w]+)(?:\?|&|$)/i,o=/(?:^|(?:https?:)?\/\/(?:www\.)?twitter\.com(?::\d+)?\/(?:#!\/)?[\w_]+\/status(?:es)?\/)(\d+)/i,s=/^http(s?):\/\/(\w+\.)*twitter\.com([:/]|$)/i,a=/^http(s?):\/\/pbs\.twimg\.com\//,u=/^#?([^.,<>!\s/#\-()'"]+)$/,c=/twitter\.com(?::\d{2,4})?\/intent\/(\w+)/,d=/^https?:\/\/(?:www\.)?twitter\.com\/\w+\/timelines\/(\d+)/i,l=/^https?:\/\/(?:www\.)?twitter\.com\/i\/moments\/(\d+)/i,f=/^https?:\/\/(?:www\.)?twitter\.com\/(\w+)\/(?:likes|favorites)/i,h=/^https?:\/\/(?:www\.)?twitter\.com\/(\w+)\/lists\/([\w-%]+)/i,p=/^https?:\/\/(?:www\.)?twitter\.com\/i\/live\/(\d+)/i,m=/^https?:\/\/syndication\.twitter\.com\/settings/i,v=/^https?:\/\/(localhost|platform)\.twitter\.com(?::\d+)?\/widgets\/widget_iframe\.(.+)/i,g=/^https?:\/\/(?:www\.)?twitter\.com\/search\?q=(\w+)/i;function w(t){return"string"==typeof t&&i.test(t)&&RegExp.$1.length<=20}function y(t){if(w(t))return RegExp.$1}function b(t,e){var n=r.decodeURL(t);if(e=e||!1,n.screen_name=y(t),n.screen_name)return r.url("https://twitter.com/intent/"+(e?"follow":"user"),n)}function _(t){return"string"==typeof t&&u.test(t)}function E(t){return"string"==typeof t&&o.test(t)}t.exports={isHashTag:_,hashTag:function(t,e){if(e=void 0===e||e,_(t))return(e?"#":"")+RegExp.$1},isScreenName:w,screenName:y,isStatus:E,status:function(t){return E(t)&&RegExp.$1},intentForProfileURL:b,intentForFollowURL:function(t){return b(t,!0)},isTwitterURL:function(t){return s.test(t)},isTwimgURL:function(t){return a.test(t)},isIntentURL:function(t){return c.test(t)},isSettingsURL:function(t){return m.test(t)},isWidgetIframeURL:function(t){return v.test(t)},isSearchUrl:function(t){return g.test(t)},regexen:{profile:i},momentId:function(t){return l.test(t)&&RegExp.$1},collectionId:function(t){return d.test(t)&&RegExp.$1},intentType:function(t){return c.test(t)&&RegExp.$1},likesScreenName:function(t){return f.test(t)&&RegExp.$1},listScreenNameAndSlug:function(t){var e,n,r;if(h.test(t)){e=RegExp.$1,n=RegExp.$2;try{r=decodeURIComponent(n)}catch(t){}return{ownerScreenName:e,slug:r||n}}return!1},eventId:function(t){return p.test(t)&&RegExp.$1}}},function(t,e,n){var r=n(0),i=[!0,1,"1","on","ON","true","TRUE","yes","YES"],o=[!1,0,"0","off","OFF","false","FALSE","no","NO"];function s(t){return void 0!==t&&null!==t&&""!==t}function a(t){return c(t)&&t%1==0}function u(t){return c(t)&&!a(t)}function c(t){return s(t)&&!isNaN(t)}function d(t){return r.contains(o,t)}function l(t){return r.contains(i,t)}t.exports={hasValue:s,isInt:a,isFloat:u,isNumber:c,isString:function(t){return"string"===r.toType(t)},isArray:function(t){return s(t)&&"array"==r.toType(t)},isTruthValue:l,isFalseValue:d,asInt:function(t){if(a(t))return parseInt(t,10)},asFloat:function(t){if(u(t))return t},asNumber:function(t){if(c(t))return t},asBoolean:function(t){return!(!s(t)||!l(t)&&(d(t)||!t))}}},function(t,e){t.exports=document},function(t,e,n){var r=n(1),i=n(20),o=n(45);i.hasPromiseSupport()||(r.Promise=o),t.exports=r.Promise},function(t,e,n){var r=n(0);t.exports=function(t,e){var n=Array.prototype.slice.call(arguments,2);return function(){var i=r.toRealArray(arguments);return t.apply(e,n.concat(i))}}},function(t,e){t.exports=location},function(t,e,n){var r=n(47);t.exports=new r("__twttr")},function(t,e,n){var r=n(0),i=/\b([\w-_]+)\b/g;function o(t){return new RegExp("\\b"+t+"\\b","g")}function s(t,e){t.classList?t.classList.add(e):o(e).test(t.className)||(t.className+=" "+e)}function a(t,e){t.classList?t.classList.remove(e):t.className=t.className.replace(o(e)," ")}function u(t,e){return t.classList?t.classList.contains(e):r.contains(c(t),e)}function c(t){return r.toRealArray(t.classList?t.classList:t.className.match(i))}t.exports={add:s,remove:a,replace:function(t,e,n){if(t.classList&&u(t,e))return a(t,e),void s(t,n);t.className=t.className.replace(o(e),n)},toggle:function(t,e,n){return void 0===n&&t.classList&&t.classList.toggle?t.classList.toggle(e,n):(n?s(t,e):a(t,e),n)},present:u,list:c}},function(t,e,n){var r=n(4),i=n(0);function o(t){return encodeURIComponent(t).replace(/\+/g,"%2B").replace(/'/g,"%27")}function s(t){return decodeURIComponent(t)}function a(t){var e=[];return i.forIn(t,function(t,n){var s=o(t);i.isType("array",n)||(n=[n]),n.forEach(function(t){r.hasValue(t)&&e.push(s+"="+o(t))})}),e.sort().join("&")}function u(t){var e={};return t?(t.split("&").forEach(function(t){var n=t.split("="),r=s(n[0]),o=s(n[1]);if(2==n.length){if(!i.isType("array",e[r]))return r in e?(e[r]=[e[r]],void e[r].push(o)):void(e[r]=o);e[r].push(o)}}),e):{}}t.exports={url:function(t,e){return a(e).length>0?i.contains(t,"?")?t+"&"+a(e):t+"?"+a(e):t},decodeURL:function(t){var e=t&&t.split("?");return 2==e.length?u(e[1]):{}},decode:u,encode:a,encodePart:o,decodePart:s}},function(t,e,n){var r=n(8),i=n(1),o=n(0),s={},a=o.contains(r.href,"tw_debug=true");function u(){}function c(){}function d(){return i.performance&&+i.performance.now()||+new Date}function l(t,e){if(i.console&&i.console[t])switch(e.length){case 1:i.console[t](e[0]);break;case 2:i.console[t](e[0],e[1]);break;case 3:i.console[t](e[0],e[1],e[2]);break;case 4:i.console[t](e[0],e[1],e[2],e[3]);break;case 5:i.console[t](e[0],e[1],e[2],e[3],e[4]);break;default:0!==e.length&&i.console.warn&&i.console.warn("too many params passed to logger."+t)}}t.exports={devError:u,devInfo:c,devObject:function(t,e){},publicError:function(){l("error",o.toRealArray(arguments))},publicLog:function(){l("info",o.toRealArray(arguments))},time:function(t){a&&(s[t]=d())},timeEnd:function(t){a&&s[t]&&(d(),s[t])}}},function(t,e,n){var r=n(19),i=n(4),o=n(11),s=n(0),a=n(116);t.exports=function(t){var e=t.href&&t.href.split("?")[1],n=e?o.decode(e):{},u={lang:a(t),width:t.getAttribute("data-width")||t.getAttribute("width"),height:t.getAttribute("data-height")||t.getAttribute("height"),related:t.getAttribute("data-related"),partner:t.getAttribute("data-partner")};return i.asBoolean(t.getAttribute("data-dnt"))&&r.setOn(),s.forIn(u,function(t,e){var r=n[t];n[t]=i.hasValue(r)?r:e}),s.compact(n)}},function(t,e,n){var r=n(79),i=n(22);t.exports=function(){var t="data-twitter-extracted-"+i.generate();return function(e,n){return r(e,n).filter(function(e){return!e.hasAttribute(t)}).map(function(e){return e.setAttribute(t,"true"),e})}}},function(t,e){function n(t,e,n,r,i,o){this.factory=t,this.Sandbox=e,this.srcEl=o,this.targetEl=i,this.parameters=r,this.className=n}n.prototype.destroy=function(){this.srcEl=this.targetEl=null},t.exports=n},function(t,e,n){var r=n(6),i=n(49),o=n(19),s=n(4),a=n(0);t.exports=function(t,e,n){var u;return t=t||[],e=e||{},u="ƒ("+t.join(", ")+", target, [options]);",function(){var c,d,l,f,h=Array.prototype.slice.apply(arguments,[0,t.length]),p=Array.prototype.slice.apply(arguments,[t.length]);return p.forEach(function(t){t&&(t.nodeType!==Node.ELEMENT_NODE?a.isType("function",t)?c=t:a.isType("object",t)&&(d=t):l=t)}),h.length!==t.length||0===p.length?(c&&a.async(function(){c(!1)}),r.reject(new Error("Not enough parameters. Expected: "+u))):l?(d=a.aug({},d||{},e),t.forEach(function(t){d[t]=h.shift()}),s.asBoolean(d.dnt)&&o.setOn(),f=i.addWidget(n(d,l)),c&&f.then(c,function(){c(!1)}),f):(c&&a.async(function(){c(!1)}),r.reject(new Error("No target element specified. Expected: "+u)))}}},function(t,e,n){var r=n(99),i=n(2),o=n(0);function s(t,e){return function(){try{e.resolve(t.call(this))}catch(t){e.reject(t)}}}t.exports={sync:function(t,e){t.call(e)},read:function(t,e){var n=new i;return r.read(s(t,n),e),n.promise},write:function(t,e){var n=new i;return r.write(s(t,n),e),n.promise},defer:function(t,e,n){var a=new i;return o.isType("function",t)&&(n=e,e=t,t=1),r.defer(t,s(e,a),n),a.promise}}},function(t,e,n){var r=n(9),i=["https://syndication.twitter.com","https://cdn.syndication.twimg.com","https://localhost.twitter.com:8444"],o=["https://syndication.twitter.com","https://localhost.twitter.com:8445"],s=function(t,e){return t.indexOf(e)>-1},a=function(){var t=r.get("backendHost");return t&&s(i,t)?t:"https://cdn.syndication.twimg.com"},u=function(){var t=r.get("settingsSvcHost");return t&&s(o,t)?t:"https://syndication.twitter.com"};function c(t,e){var n=[t];return e.forEach(function(t){n.push(function(t){var e=(t||"").toString(),n="/"===e.slice(0,1)?1:0,r=function(t){return"/"===t.slice(-1)}(e)?-1:void 0;return e.slice(n,r)}(t))}),n.join("/")}t.exports={cookieConsent:function(t){var e=t||[];return e.unshift("cookie/consent"),c(u(),e)},eventVideo:function(t){var e=t||[];return e.unshift("video/event"),c(a(),e)},grid:function(t){var e=t||[];return e.unshift("grid/collection"),c(a(),e)},moment:function(t){var e=t||[];return e.unshift("moments"),c(a(),e)},settings:function(t){var e=t||[];return e.unshift("settings"),c(u(),e)},timeline:function(t){var e=t||[];return e.unshift("timeline"),c(a(),e)},tweetBatch:function(t){var e=t||[];return e.unshift("tweets.json"),c(a(),e)},video:function(t){var e=t||[];return e.unshift("widgets/video"),c(a(),e)}}},function(t,e,n){var r=n(5),i=n(8),o=n(35),s=n(77),a=n(4),u=n(32),c=!1,d=/https?:\/\/([^/]+).*/i;t.exports={setOn:function(){c=!0},enabled:function(t,e){return!!(c||a.asBoolean(u.val("dnt"))||s.isUrlSensitive(e||i.host)||o.isFramed()&&s.isUrlSensitive(o.rootDocumentLocation())||(t=d.test(t||r.referrer)&&RegExp.$1)&&s.isUrlSensitive(t))}}},function(t,e,n){var r=n(5),i=n(12),o=n(92),s=n(1),a=n(0),u=o.userAgent;function c(t){return/(Trident|MSIE|Edge[/ ]?\d)/.test(t=t||u)}t.exports={retina:function(t){return(t=t||s).devicePixelRatio?t.devicePixelRatio>=1.5:!!t.matchMedia&&t.matchMedia("only screen and (min-resolution: 144dpi)").matches},anyIE:c,ie9:function(t){return/MSIE 9/.test(t=t||u)},ie10:function(t){return/MSIE 10/.test(t=t||u)},ios:function(t){return/(iPad|iPhone|iPod)/.test(t=t||u)},android:function(t){return/^Mozilla\/5\.0 \(Linux; (U; )?Android/.test(t=t||u)},canPostMessage:function(t,e){return t=t||s,e=e||u,t.postMessage&&!(c(e)&&t.opener)},touch:function(t,e,n){return t=t||s,e=e||o,n=n||u,"ontouchstart"in t||/Opera Mini/.test(n)||e.msMaxTouchPoints>0},cssTransitions:function(){var t=r.body.style;return void 0!==t.transition||void 0!==t.webkitTransition||void 0!==t.mozTransition||void 0!==t.oTransition||void 0!==t.msTransition},hasPromiseSupport:function(){return!!(s.Promise&&s.Promise.resolve&&s.Promise.reject&&s.Promise.all&&s.Promise.race&&(new s.Promise(function(e){t=e}),a.isType("function",t)));var t},hasIntersectionObserverSupport:function(){return!!s.IntersectionObserver},hasPerformanceInformation:function(){return s.performance&&s.performance.getEntriesByType},hasLocalStorageSupport:function(){try{return s.localStorage.setItem("local_storage_support_test","true"),void 0!==s.localStorage}catch(t){return i.devError("window.localStorage is not supported:",t),!1}}}},function(t,e,n){var r=n(6),i=n(2);function o(t,e){return t.then(e,e)}function s(t){return t instanceof r}t.exports={always:o,allResolved:function(t){var e;return void 0===t?r.reject(new Error("undefined is not an object")):Array.isArray(t)?(e=t.length)?new r(function(n,r){var i=0,o=[];function a(){(i+=1)===e&&(0===o.length?r():n(o))}function u(t){o.push(t),a()}t.forEach(function(t){s(t)?t.then(u,a):u(t)})}):r.resolve([]):r.reject(new Error("Type error"))},some:function(t){var e;return e=(t=t||[]).length,t=t.filter(s),e?e!==t.length?r.reject("non-Promise passed to .some"):new r(function(e,n){var r=0;function i(){(r+=1)===t.length&&n()}t.forEach(function(t){t.then(e,i)})}):r.reject("no promises passed to .some")},isPromise:s,allSettled:function(t){function e(){}return r.all((t||[]).map(function(t){return o(t,e)}))},timeout:function(t,e){var n=new i;return setTimeout(function(){n.reject(new Error("Promise timed out"))},e),t.then(function(t){n.resolve(t)},function(t){n.reject(t)}),n.promise}}},function(t,e){var n="i",r=0,i=0;t.exports={generate:function(){return n+String(+new Date)+Math.floor(1e5*Math.random())+r++},deterministic:function(){return n+String(i++)}}},function(t,e,n){var r=n(46),i=n(48),o=n(0);t.exports=o.aug(r.get("events")||{},i.Emitter)},function(t,e,n){var r=n(25),i=n(107);t.exports=r.build([i])},function(t,e,n){var r=n(37),i=n(104),o=n(7);(r=Object.create(r)).build=o(r.build,null,i),t.exports=r},function(t,e,n){var r=n(37),i=n(38),o=n(7);(r=Object.create(r)).build=o(r.build,null,i),t.exports=r},function(t,e,n){var r=n(81),i=n(73),o=n(82),s=n(8),a=n(68),u=n(72),c=n(19),d=n(4),l=n(22),f=n(0);function h(t){if(!t||!t.headers)throw new Error("unexpected response schema");return{html:t.body,config:t.config,pollInterval:1e3*parseInt(t.headers.xPolling,10)||null,maxCursorPosition:t.headers.maxPosition,minCursorPosition:t.headers.minPosition}}function p(t){if(t&&t.headers)throw new Error(t.headers.status);throw t instanceof Error?t:new Error(t)}t.exports=function(t){t.params({instanceId:{required:!0,fallback:l.deterministic},lang:{required:!0,transform:a.matchLanguage,fallback:"en"},tweetLimit:{transform:d.asInt}}),t.defineProperty("endpoint",{get:function(){throw new Error("endpoint not specified")}}),t.defineProperty("pollEndpoint",{get:function(){return this.endpoint}}),t.define("cbId",function(t){var e=t?"_new":"_old";return"tl_"+this.params.instanceId+"_"+this.id+e}),t.define("queryParams",function(){return{lang:this.params.lang,tz:u.getTimezoneOffset(),t:r(),domain:s.host,tweet_limit:this.params.tweetLimit,dnt:c.enabled()}}),t.define("fetch",function(){return i.fetch(this.endpoint,this.queryParams(),o,this.cbId()).then(h,p)}),t.define("poll",function(t,e){var n,r;return n={since_id:(t=t||{}).sinceId,max_id:t.maxId,min_position:t.minPosition,max_position:t.maxPosition},r=f.aug(this.queryParams(),n),i.fetch(this.pollEndpoint,r,o,this.cbId(e)).then(h,p)})}},function(t,e,n){var r=n(48).makeEmitter();t.exports={emitter:r,START:"start",ALL_WIDGETS_RENDER_START:"all_widgets_render_start",ALL_WIDGETS_RENDER_END:"all_widgets_render_end",ALL_WIDGETS_AND_IMAGES_LOADED:"all_widgets_and_images_loaded"}},function(t,e,n){var r=n(5),i=n(0);t.exports=function(t,e,n){var o;if(n=n||r,t=t||{},e=e||{},t.name){try{o=n.createElement('')}catch(e){(o=n.createElement("iframe")).name=t.name}delete t.name}else o=n.createElement("iframe");return t.id&&(o.id=t.id,delete t.id),o.allowtransparency="true",o.scrolling="no",o.setAttribute("frameBorder",0),o.setAttribute("allowTransparency",!0),i.forIn(t,function(t,e){o.setAttribute(t,e)}),i.forIn(e,function(t,e){o.style[t]=e}),o}},function(t,e,n){var r=n(1).JSON;t.exports={stringify:r.stringify||r.encode,parse:r.parse||r.decode}},function(t,e,n){var r=n(0),i=n(40);t.exports={closest:function t(e,n,o){var s;if(n)return o=o||n&&n.ownerDocument,s=r.isType("function",e)?e:function(t){return function(e){return!!e.tagName&&i(e,t)}}(e),n===o?s(n)?n:void 0:s(n)?n:t(s,n.parentNode,o)}}},function(t,e,n){var r,i=n(5);function o(t){var e,n,o,s=0;for(r={},e=(t=t||i).getElementsByTagName("meta");e[s];s++){if(n=e[s],/^twitter:/.test(n.getAttribute("name")))o=n.getAttribute("name").replace(/^twitter:/,"");else{if(!/^twitter:/.test(n.getAttribute("property")))continue;o=n.getAttribute("property").replace(/^twitter:/,"")}r[o]=n.getAttribute("content")||n.getAttribute("value")}}o(),t.exports={init:o,val:function(t){return r[t]}}},function(t,e,n){var r=n(5),i=n(30),o=n(19),s=n(0),a=n(41),u=n(9),c=n(3),d=n(31),l=a.version,f=u.get("clientEventEndpoint")||"https://syndication.twitter.com/i/jot",h=1;function p(t){return s.aug({client:"tfw"},t||{})}function m(t,e,n){return e=e||{},s.aug({},e,{_category_:t,triggered_on:e.triggered_on||+new Date,dnt:o.enabled(n)})}t.exports={extractTermsFromDOM:function t(e,n){var r;return n=n||{},e&&e.nodeType===Node.ELEMENT_NODE?((r=e.getAttribute("data-scribe"))&&r.split(" ").forEach(function(t){var e=t.trim().split(":"),r=e[0],i=e[1];r&&i&&!n[r]&&(n[r]=i)}),t(e.parentNode,n)):n},clickEventElement:function(t){var e=d.closest("[data-expanded-url]",t),n=e&&e.getAttribute("data-expanded-url");return n&&c.isTwitterURL(n)?"twitter_url":"url"},flattenClientEventPayload:function(t,e){return s.aug({},e,{event_namespace:t})},formatGenericEventData:m,formatClientEventData:function(t,e,n){var i=t&&t.widget_origin||r.referrer;return(t=m("tfw_client_event",t,i)).client_version=l,t.format_version=void 0!==n?n:1,e||(t.widget_origin=i),t},formatClientEventNamespace:p,formatTweetAssociation:function(t,e){var n={};return(e=e||{}).association_namespace=p(t),n[h]=e,n},noticeSeen:function(t){return"notice"===t.element&&"seen"===t.action},splitLogEntry:function(t){var e,n,r,i,o;return t.item_ids&&t.item_ids.length>1?(e=Math.floor(t.item_ids.length/2),n=t.item_ids.slice(0,e),r={},i=t.item_ids.slice(e),o={},n.forEach(function(e){r[e]=t.item_details[e]}),i.forEach(function(e){o[e]=t.item_details[e]}),[s.aug({},t,{item_ids:n,item_details:r}),s.aug({},t,{item_ids:i,item_details:o})]):[t]},stringify:function(t){var e,n=Array.prototype.toJSON;return delete Array.prototype.toJSON,e=i.stringify(t),n&&(Array.prototype.toJSON=n),e},AUDIENCE_ENDPOINT:"https://syndication.twitter.com/i/jot/syndication",CLIENT_EVENT_ENDPOINT:f,RUFOUS_REDIRECT:"https://platform.twitter.com/jot.html"}},function(t,e,n){var r=n(10),i={},o=-1,s={};function a(t){var e=t.getAttribute("data-twitter-event-id");return e||(t.setAttribute("data-twitter-event-id",++o),o)}function u(t,e,n){var r=0,i=t&&t.length||0;for(r=0;r0&&(n=n.slice(0,1),o.canFlushOneItem(n[0])||(n[0].input.data.message=""),c(n)),a&&(u(a)?c:function(t){i.init(),t.forEach(function(t){var e=t.input.namespace,n=t.input.data,r=t.input.offsite,o=t.input.version;i.clientEvent(e,n,r,o)}),i.flush().then(function(){t.forEach(function(t){t.taskDoneDeferred.resolve()})},function(){t.forEach(function(t){t.taskDoneDeferred.reject()})})})(a)}});function u(t){return 1===t.length&&o.canFlushOneItem(t[0])}function c(t){t.forEach(function(t){var e=t.input.namespace,n=t.input.data,r=t.input.offsite,i=t.input.version;o.clientEvent(e,n,r,i),t.taskDoneDeferred.resolve()})}t.exports={scribe:function(t,e,n,r){return a.add({namespace:t,data:e,offsite:n,version:r})},pause:function(){a.pause()},resume:function(){a.resume()}}},function(t,e,n){var r=n(102),i=n(103),o=n(0);t.exports={couple:function(){return o.toRealArray(arguments)},build:function(t,e,n){var o=new t;return(e=i(r(e||[]))).forEach(function(t){t.call(null,o)}),o.build(n)}}},function(t,e,n){var r=n(105),i=n(0),o=n(39);function s(){this.Component=this.factory(),this._adviceArgs=[],this._lastArgs=[]}i.aug(s.prototype,{factory:o,build:function(t){var e=this;return this.Component,i.aug(this.Component.prototype.boundParams,t),this._adviceArgs.concat(this._lastArgs).forEach(function(t){(function(t,e,n){var r=this[e];if(!r)throw new Error(e+" does not exist");this[e]=t(r,n)}).apply(e.Component.prototype,t)}),delete this._lastArgs,delete this._adviceArgs,this.Component},params:function(t){var e=this.Component.prototype.paramConfigs;t=t||{},this.Component.prototype.paramConfigs=i.aug({},t,e)},define:function(t,e){if(t in this.Component.prototype)throw new Error(t+" has previously been defined");this.override(t,e)},defineStatic:function(t,e){this.Component[t]=e},override:function(t,e){this.Component.prototype[t]=e},defineProperty:function(t,e){if(t in this.Component.prototype)throw new Error(t+" has previously been defined");this.overrideProperty(t,e)},overrideProperty:function(t,e){var n=i.aug({configurable:!0},e);Object.defineProperty(this.Component.prototype,t,n)},before:function(t,e){this._adviceArgs.push([r.before,t,e])},after:function(t,e){this._adviceArgs.push([r.after,t,e])},around:function(t,e){this._adviceArgs.push([r.around,t,e])},last:function(t,e){this._lastArgs.push([r.after,t,e])}}),t.exports=s},function(t,e,n){var r=n(0);function i(){return!0}function o(t){return t}t.exports=function(){function t(t){var e=this;t=t||{},this.params=Object.keys(this.paramConfigs).reduce(function(n,s){var a=[],u=e.boundParams,c=e.paramConfigs[s],d=c.validate||i,l=c.transform||o;if(s in u&&a.push(u[s]),s in t&&a.push(t[s]),a="fallback"in c?a.concat(c.fallback):a,n[s]=function(t,e,n){var i=null;return t.some(function(t){if(t=r.isType("function",t)?t():t,e(t))return i=n(t),!0}),i}(a,d,l),c.required&&null==n[s])throw new Error(s+" is a required parameter");return n},{}),this.initialize()}return r.aug(t.prototype,{paramConfigs:{},boundParams:{},initialize:function(){}}),t}},function(t,e,n){var r=n(1).HTMLElement,i=r.prototype.matches||r.prototype.matchesSelector||r.prototype.webkitMatchesSelector||r.prototype.mozMatchesSelector||r.prototype.msMatchesSelector||r.prototype.oMatchesSelector;t.exports=function(t,e){if(i)return i.call(t,e)}},function(t){t.exports={version:"7e980dd:1559715853415"}},function(t,e,n){var r,i=n(10),o=n(5),s=n(1),a=n(32),u=n(50),c=n(4),d=n(22),l="csptest";t.exports={inlineStyle:function(){var t=l+d.generate(),e=o.createElement("div"),n=o.createElement("style"),f="."+t+" { visibility: hidden; }";return!!o.body&&(c.asBoolean(a.val("widgets:csp"))&&(r=!1),void 0!==r?r:(e.style.display="none",i.add(e,t),n.type="text/css",n.appendChild(o.createTextNode(f)),o.body.appendChild(n),o.body.appendChild(e),r="hidden"===s.getComputedStyle(e).visibility,u(e),u(n),r))}}},function(t,e,n){var r=n(1);t.exports=function(t,e,n){var i,o=0;return n=n||null,function s(){var a=n||this,u=arguments,c=+new Date;if(r.clearTimeout(i),c-o>e)return o=c,void t.apply(a,u);i=r.setTimeout(function(){s.apply(a,u)},e)}}},function(t,e){t.exports=function(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height}}},function(t,e,n){
-/*!
- * @overview es6-promise - a tiny implementation of Promises/A+.
- * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
- * @license   Licensed under MIT license
- *            See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
- * @version   v4.2.5+7f2b526d
- */var r;r=function(){"use strict";function t(t){return"function"==typeof t}var e=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},n=0,r=void 0,i=void 0,o=function(t,e){f[n]=t,f[n+1]=e,2===(n+=2)&&(i?i(h):w())},s="undefined"!=typeof window?window:void 0,a=s||{},u=a.MutationObserver||a.WebKitMutationObserver,c="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),d="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel;function l(){var t=setTimeout;return function(){return t(h,1)}}var f=new Array(1e3);function h(){for(var t=0;t=0&&this._handlers[t].splice(n,1):this._handlers[t]=[])},trigger:function(t,e){var n=this._handlers&&this._handlers[t];(e=e||{}).type=t,n&&n.forEach(function(t){r.async(i(t,this,e))})}};t.exports={Emitter:o,makeEmitter:function(){return r.aug(function(){},o)}}},function(t,e,n){var r=n(98),i=n(74),o=n(6),s=n(21),a=n(7),u=n(0),c=new i(function(t){var e=function(t){return t.reduce(function(t,e){return t[e.className]=t[e.className]||[],t[e.className].push(e),t},{})}(t.map(r.fromRawTask));u.forIn(e,function(t,e){s.allSettled(e.map(function(t){return t.initialize()})).then(function(){e.forEach(function(t){o.all([t.hydrate(),t.insertIntoDom()]).then(a(t.render,t)).then(a(t.success,t),a(t.fail,t))})})})});t.exports={addWidget:function(t){return c.add(t)}}},function(t,e,n){var r=n(17);t.exports=function(t){return r.write(function(){t&&t.parentNode&&t.parentNode.removeChild(t)})}},function(t,e,n){n(12),t.exports={log:function(t,e){}}},function(t,e,n){var r=n(1);function i(t){return(t=t||r).getSelection&&t.getSelection()}t.exports={getSelection:i,getSelectedText:function(t){var e=i(t);return e?e.toString():""}}},function(t,e,n){var r=n(5),i=n(1),o=n(2),s=2e4;t.exports=function(t){var e=new o,n=r.createElement("img");return n.onload=n.onerror=function(){i.setTimeout(e.resolve,50)},n.src=t,i.setTimeout(e.reject,s),e.promise}},function(t,e,n){var r=n(108);t.exports=function(t){t.define("createElement",r),t.define("createFragment",r),t.define("htmlToElement",r),t.define("hasSelectedText",r),t.define("addRootClass",r),t.define("removeRootClass",r),t.define("hasRootClass",r),t.define("prependStyleSheet",r),t.define("appendStyleSheet",r),t.define("prependCss",r),t.define("appendCss",r),t.define("makeVisible",r),t.define("injectWidgetEl",r),t.define("matchHeightToContent",r),t.define("matchWidthToContent",r)}},function(t,e){t.exports=function(t){var e,n=!1;return function(){return n?e:(n=!0,e=t.apply(this,arguments))}}},function(t,e,n){var r=n(15),i=n(117),o=n(57);t.exports=function(t,e,n){return new r(i,o,"twitter-dm-button",t,e,n)}},function(t,e,n){var r=n(58),i=n(24);t.exports=r.isSupported()?r:i},function(t,e,n){var r=n(25),i=n(118);t.exports=r.build([i])},function(t,e,n){var r=n(15),i=n(121),o=n(60);t.exports=function(t,e,n){return new r(i,o,"twitter-follow-button",t,e,n)}},function(t,e,n){var r=n(25),i=n(122);t.exports=r.build([i])},function(t,e,n){var r=n(15),i=n(129),o=n(24);t.exports=function(t,e,n){return new r(i,o,"twitter-moment",t,e,n)}},function(t,e,n){var r=n(15),i=n(131),o=n(24);t.exports=function(t,e,n){return new r(i,o,"periscope-on-air",t,e,n)}},function(t,e,n){var r=n(80),i=n(133),o=n(137),s=n(139),a=n(141),u=n(143),c={collection:i,event:o,likes:s,list:a,profile:u,url:l},d=[u,s,i,a,o];function l(t){return r(d,function(e){try{return new e(t)}catch(t){}})}t.exports=function(t){return t?function(t){var e,n;return e=(t.sourceType+"").toLowerCase(),(n=c[e])?new n(t):null}(t)||l(t):null}},function(t,e,n){var r=n(15),i=n(145),o=n(24);t.exports=function(t,e,n){return new r(i,o,"twitter-timeline",t,e,n)}},function(t,e,n){var r=n(15),i=n(147),o=n(57);t.exports=function(t,e,n){return new r(i,o,"twitter-tweet",t,e,n)}},function(t,e,n){var r=n(15),i=n(149),o=n(60);t.exports=function(t,e,n){var s=t&&t.type||"share";return new r(i,o,"hashtag"==s?"twitter-hashtag-button":"mention"==s?"twitter-mention-button":"twitter-share-button",t,e,n)}},function(t,e,n){var r=n(36),i=n(35),o=n(0);t.exports=function(t){var e={widget_origin:i.rootDocumentLocation(),widget_frame:i.isFramed()?i.currentDocumentLocation():null,duration_ms:t.duration,item_ids:t.widgetIds||[]},n=o.aug(t.namespace,{page:"page",component:"performance"});r.scribe(n,e)}},function(t,e,n){var r=n(0),i=n(134),o=["ar","fa","he","ur"];t.exports={isRtlLang:function(t){return t=String(t).toLowerCase(),r.contains(o,t)},matchLanguage:function(t){return t=(t=(t||"").toLowerCase()).replace("_","-"),i(t)?t:(t=t.replace(/-.*/,""),i(t)?t:"en")}}},function(t,e,n){var r=n(110),i=n(113);function o(t){return r.settingsLoaded().then(function(e){return e[t]})}function s(){return o("experiments")}t.exports={shouldObtainCookieConsent:function(){return o("shouldObtainCookieConsent")},getExperiments:s,getExperiment:function(t){return s().then(function(e){if(!e[t])throw new Error("Experiment not found");return e[t]})},getActiveExperimentDataString:function(){return s().then(function(t){var e=Object.keys(t).reduce(function(e,n){var r;return t[n].version&&(r=n.split("_").slice(-1)[0],e.push(r+";"+t[n].bucket)),e},[]);return i(e.join(","))})},getExperimentKeys:function(){return s().then(function(t){return Object.keys(t)})},load:function(){r.load()}}},function(t){t.exports={tweetButtonHtmlPath:"/widgets/tweet_button.d753e00c3e838c1b2558149bd3f6ecb8.{{lang}}.html",followButtonHtmlPath:"/widgets/follow_button.d753e00c3e838c1b2558149bd3f6ecb8.{{lang}}.html",hubHtmlPath:"/widgets/hub.html",widgetIframeHtmlPath:"/widgets/widget_iframe.d753e00c3e838c1b2558149bd3f6ecb8.html",resourceBaseUrl:"https://platform.twitter.com"}},function(t,e,n){var r=n(3),i=n(95),o=n(23),s=n(11),a={favorite:["favorite","like"],follow:["follow"],like:["favorite","like"],retweet:["retweet"],tweet:["tweet"]};function u(t){this.srcEl=[],this.element=t}u.open=function(t,e,n){var u=(r.intentType(t)||"").toLowerCase();r.isTwitterURL(t)&&(function(t,e){i.open(t,{},e)}(t,n),e&&o.trigger("click",{target:e,region:"intent",type:"click",data:{}}),e&&a[u]&&a[u].forEach(function(n){o.trigger(n,{target:e,region:"intent",type:n,data:function(t,e){var n=s.decodeURL(e);switch(t){case"favorite":case"like":return{tweet_id:n.tweet_id};case"follow":return{screen_name:n.screen_name,user_id:n.user_id};case"retweet":return{source_tweet_id:n.tweet_id};default:return{}}}(u,t)})}))},t.exports=u},function(t,e){t.exports={getTimezoneOffset:function(){var t=(new Date).toString().match(/(GMT[+-]?\d+)/);return t&&t[0]||"GMT"}}},function(t,e,n){var r=n(5),i=n(9),o=n(2),s=n(0),a=n(11),u="cb",c=0;t.exports={fetch:function(t,e,n,d){var l,f,h;return d=function(t){if(t)return t.replace(/[^\w$]/g,"_")}(d||u+c++),l=i.fullPath(["callbacks",d]),f=r.createElement("script"),h=new o,e=s.aug({},e,{callback:l,suppress_response_codes:!0}),i.set(["callbacks",d],function(t){var e;t=(e=n(t||!1)).resp,e.success?h.resolve(t):h.reject(t),f.onload=f.onreadystatechange=null,f.parentNode&&f.parentNode.removeChild(f),i.unset(["callbacks",d])}),f.onerror=function(){h.reject(new Error("failed to fetch "+f.src))},f.src=a.url(t,e),f.async="async",r.body.appendChild(f),h.promise}}},function(t,e,n){var r=n(2),i=n(100),o=n(7);function s(t){this._inputsQueue=[],this._task=t,this._hasFlushBeenScheduled=!1}s.prototype.add=function(t){var e=new r;return this._inputsQueue.push({input:t,taskDoneDeferred:e}),this._hasFlushBeenScheduled||(this._hasFlushBeenScheduled=!0,i(o(this._flush,this))),e.promise},s.prototype._flush=function(){try{this._task.call(null,this._inputsQueue)}catch(t){this._inputsQueue.forEach(function(e){e.taskDoneDeferred.reject(t)})}this._inputsQueue=[],this._hasFlushBeenScheduled=!1},t.exports=s},function(t,e){t.exports=function(t,e){return t.reduce(function(t,n){var r=e(n);return t[r]=t[r]||[],t[r].push(n),t},{})}},function(t,e,n){var r=n(5),i=n(8),o=n(3);function s(t,e){var n,r;return e=e||i,/^https?:\/\//.test(t)?t:/^\/\//.test(t)?e.protocol+t:(n=e.host+(e.port.length?":"+e.port:""),0!==t.indexOf("/")&&((r=e.pathname.split("/")).pop(),r.push(t),t="/"+r.join("/")),[e.protocol,"//",n,t].join(""))}t.exports={absolutize:s,getCanonicalURL:function(){for(var t,e=r.getElementsByTagName("link"),n=0;e[n];n++)if("canonical"==(t=e[n]).rel)return s(t.href)},getScreenNameFromPage:function(){for(var t,e,n,i=[r.getElementsByTagName("a"),r.getElementsByTagName("link")],s=0,a=0,u=/\bme\b/;t=i[s];s++)for(a=0;e=t[a];a++)if(u.test(e.rel)&&(n=o.screenName(e.href)))return n}}},function(t,e,n){var r=n(8),i=/^[^#?]*\.(gov|mil)(:\d+)?([#?].*)?$/i,o={};function s(t){return t in o?o[t]:o[t]=i.test(t)}t.exports={isUrlSensitive:s,isHostPageSensitive:function(){return s(r.host)}}},function(t,e,n){var r=n(19),i=n(51),o=n(11),s=n(33),a=n(0),u=n(9).get("scribeCallback"),c=2083,d=[],l=o.url(s.CLIENT_EVENT_ENDPOINT,{dnt:0,l:""}),f=encodeURIComponent(l).length;function h(t,e,n,r){var i=!a.isObject(t),o=!!e&&!a.isObject(e);i||o||(u&&u(arguments),p(s.formatClientEventNamespace(t),s.formatClientEventData(e,n,r),s.CLIENT_EVENT_ENDPOINT))}function p(t,e,n){var r,u;n&&a.isObject(t)&&a.isObject(e)&&(i.log(t,e),r=s.flattenClientEventPayload(t,e),u={l:s.stringify(r)},s.noticeSeen(t)&&(u.notice_seen=!0),r.dnt&&(u.dnt=1),w(o.url(n,u)))}function m(t,e,n,r){var i=!a.isObject(t),o=!!e&&!a.isObject(e);if(!i&&!o)return v(s.flattenClientEventPayload(s.formatClientEventNamespace(t),s.formatClientEventData(e,n,r)))}function v(t){return d.push(t),d}function g(t){return encodeURIComponent(t).length+3}function w(t){return(new Image).src=t}t.exports={canFlushOneItem:function(t){var e=g(s.stringify(t));return f+e1&&m({page:"widgets_js",component:"scribe_pixel",action:"batch_log"},{}),t=d,d=[],t.reduce(function(e,n,r){var i=e.length,o=i&&e[i-1];return r+1==t.length&&n.event_namespace&&"batch_log"==n.event_namespace.action&&(n.message=["entries:"+r,"requests:"+i].join("/")),function t(e){return Array.isArray(e)||(e=[e]),e.reduce(function(e,n){var r,i=s.stringify(n),o=g(i);return f+o1&&(e=e.concat(t(r))),e},[])}(n).forEach(function(t){var n=g(t);(!o||o.urlLength+n>c)&&(o={urlLength:f,items:[]},e.push(o)),o.urlLength+=n,o.items.push(t)}),e},[]).map(function(t){var e={l:t.items};return r.enabled()&&(e.dnt=1),w(o.url(s.CLIENT_EVENT_ENDPOINT,e))})},interaction:function(t,e,n,r){var i=s.extractTermsFromDOM(t.target||t.srcElement);i.action=r||"click",h(i,e,n)}}},function(t,e,n){var r=n(0),i=n(40);t.exports=function(t,e){return i(t,e)?[t]:r.toRealArray(t.querySelectorAll(e))}},function(t,e){t.exports=function(t,e,n){for(var r,i=0;ie?{coordinate:0,size:e}:{coordinate:n(e)-n(t),size:t}}function v(t,e,n){var i,o;e=r.parse(e),n=n||{},i=m(e.width,n.width||h),e.left=i.coordinate,e.width=i.size,o=m(e.height,n.height||p),e.top=o.coordinate,e.height=o.size,this.win=t,this.features=function(t){var e=[];return l.forIn(t,function(t,n){e.push(t+"="+n)}),e.join(",")}(e)}r=(new o).defaults({width:550,height:520,personalbar:"0",toolbar:"0",location:"1",scrollbars:"1",resizable:"1"}),v.prototype.open=function(t,e){var n=e&&"click"==e.type&&a.closest("a",e.target),r=e&&(e.altKey||e.metaKey||e.shiftKey),i=n&&(u.ios()||u.android());if(c.isTwitterURL(t))return r||i?this:(this.name=f+d.generate(),this.popup=this.win.open(t,this.name,this.features),e&&s.preventDefault(e),this)},v.open=function(t,e,n){return new v(i,e).open(t,n)},t.exports=v},function(t,e,n){var r=n(4),i=n(0);function o(){this.assertions=[],this._defaults={}}o.prototype.assert=function(t,e){return this.assertions.push({fn:t,msg:e||"assertion failed"}),this},o.prototype.defaults=function(t){return this._defaults=t||this._defaults,this},o.prototype.require=function(t){var e=this;return(t=Array.isArray(t)?t:i.toRealArray(arguments)).forEach(function(t){e.assert(function(t){return function(e){return r.hasValue(e[t])}}(t),"required: "+t)}),this},o.prototype.parse=function(t){var e,n;if(e=i.aug({},this._defaults,t||{}),(n=this.assertions.reduce(function(t,n){return n.fn(e)||t.push(n.msg),t},[])).length>0)throw new Error(n.join("\n"));return e},t.exports=o},function(t,e,n){var r=n(5),i=n(6),o=n(21),s=n(49),a=n(32),u=n(9),c=n(36),d=n(23),l=n(4),f=n(0),h=n(69),p=n(114),m=n(28);function v(){var t=a.val("widgets:autoload")||!0;return!l.isFalseValue(t)&&(l.isTruthValue(t)?r.body:r.querySelectorAll(t))}function g(t){var e,n;return t=(t=t||r.body).length?f.toRealArray(t):[t],c.pause(),n=t.reduce(function(t,e){return t.concat(p.reduce(function(t,n){return t.concat(n(e))},[]))},[]),m.emitter.trigger(m.ALL_WIDGETS_RENDER_START,{widgets:n}),e=o.allResolved(n.map(function(t){return s.addWidget(t)})).then(function(t){d.trigger("loaded",{widgets:t}),t&&t.length&&m.emitter.trigger(m.ALL_WIDGETS_RENDER_END,{widgets:t})}),o.always(e,function(){c.resume()}),e}t.exports={load:g,loadPage:function(){var t=v();return h.load(),!1===t?i.resolve():(u.set("widgets.loaded",!0),g(t))},_getPageLoadTarget:v}},function(t,e,n){var r=n(10),i=n(17),o=n(23),s=n(50),a=n(6),u=n(21);function c(t,e){this._widget=null,this._sandbox=null,this._hydrated=!1,this._insertedIntoDom=!1,this._Sandbox=t.Sandbox,this._factory=t.factory,this._widgetParams=t.parameters,this._resolve=e,this._className=t.className,this._renderedClassName=t.className+"-rendered",this._errorClassName=t.className+"-error",this._srcEl=t.srcEl,this._targetGlobal=function(t){return(t.srcEl||t.targetEl).ownerDocument.defaultView}(t),this._insertionStrategy=function(e){var n=t.srcEl,r=t.targetEl;n?r.insertBefore(e,n):r.appendChild(e)}}c.fromRawTask=function(t){return new c(t.input,t.taskDoneDeferred.resolve)},c.prototype.initialize=function(){var t=this,e=new this._Sandbox(this._targetGlobal);return this._factory(this._widgetParams,e).then(function(n){return t._widget=n,t._sandbox=e,n})},c.prototype.insertIntoDom=function(){var t=this;return this._widget?this._sandbox.insert(this._widget.id,{class:[this._className,this._renderedClassName].join(" ")},null,this._insertionStrategy).then(function(){t._insertedIntoDom=!0}):a.reject(new Error("cannot insert widget into DOM before it is initialized"))},c.prototype.hydrate=function(){var t=this;return this._widget?this._widget.hydrate().then(function(){t._hydrated=!0}):a.reject(new Error("cannot hydrate widget before it is initialized"))},c.prototype.render=function(){var t=this;function e(e){return s(t._sandbox.sandboxEl).then(function(){return a.reject(e)})}return this._hydrated?this._insertedIntoDom?t._widget.render(t._sandbox).then(function(){return t._sandbox.onResize(function(){return t._widget.resize().then(function(){o.trigger("resize",{target:t._sandbox.sandboxEl})})}),t._widget.show()}).then(function(){return s(t._srcEl).then(function(){return t._sandbox.sandboxEl})},e):e(new Error("cannot render widget before DOM insertion")):e(new Error("cannot render widget before hydration"))},c.prototype.fail=function(){var t=this;return this._srcEl?u.always(i.write(function(){r.add(t._srcEl,t._errorClassName)}),function(){o.trigger("rendered",{target:t._srcEl}),t._resolve(t._srcEl)}):(t._resolve(),a.resolve())},c.prototype.success=function(){o.trigger("rendered",{target:this._sandbox.sandboxEl}),this._resolve(this._sandbox.sandboxEl)},t.exports=c},function(t,e,n){var r;!function(){"use strict";var i=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)};function o(){this.frames=[],this.lastId=0,this.raf=i,this.batch={hash:{},read:[],write:[],mode:null}}o.prototype.read=function(t,e){var n=this.add("read",t,e),r=n.id;return this.batch.read.push(n.id),"reading"===this.batch.mode||this.batch.scheduled?r:(this.scheduleBatch(),r)},o.prototype.write=function(t,e){var n=this.add("write",t,e),r=this.batch.mode,i=n.id;return this.batch.write.push(n.id),"writing"===r||"reading"===r||this.batch.scheduled?i:(this.scheduleBatch(),i)},o.prototype.defer=function(t,e,n){"function"==typeof t&&(n=e,e=t,t=1);var r=this,i=t-1;return this.schedule(i,function(){r.run({fn:e,ctx:n})})},o.prototype.clear=function(t){if("function"==typeof t)return this.clearFrame(t);t=Number(t);var e=this.batch.hash[t];if(e){var n=this.batch[e.type],r=n.indexOf(t);delete this.batch.hash[t],~r&&n.splice(r,1)}},o.prototype.clearFrame=function(t){var e=this.frames.indexOf(t);~e&&this.frames.splice(e,1)},o.prototype.scheduleBatch=function(){var t=this;this.schedule(0,function(){t.batch.scheduled=!1,t.runBatch()}),this.batch.scheduled=!0},o.prototype.uniqueId=function(){return++this.lastId},o.prototype.flush=function(t){for(var e;e=t.shift();)this.run(this.batch.hash[e])},o.prototype.runBatch=function(){try{this.batch.mode="reading",this.flush(this.batch.read),this.batch.mode="writing",this.flush(this.batch.write),this.batch.mode=null}catch(t){throw this.runBatch(),t}},o.prototype.add=function(t,e,n){var r=this.uniqueId();return this.batch.hash[r]={id:r,fn:e,ctx:n,type:t}},o.prototype.run=function(t){var e=t.ctx||this,n=t.fn;if(delete this.batch.hash[t.id],!this.onError)return n.call(e);try{n.call(e)}catch(t){this.onError(t)}},o.prototype.loop=function(){var t,e=this,n=this.raf,r=!1;function i(){var t=e.frames.shift();e.frames.length?n(i):e.looping=!1,t&&t()}this.looping||(t=setTimeout(function(){r=!0,i()},500),n(function(){r||(clearTimeout(t),i())}),this.looping=!0)},o.prototype.schedule=function(t,e){return this.frames[t]?this.schedule(t+1,e):(this.loop(),this.frames[t]=e)};var s=new o;void 0!==t&&t.exports?t.exports=s:void 0===(r=function(){return s}.call(e,n,e,t))||(t.exports=r)}()},function(t,e,n){var r=n(45).Promise;t.exports=r._asap},function(t,e,n){var r,i,o,s=n(5),a=n(1),u=n(29),c=n(19),d=n(2),l=n(6),f=n(51),h=n(33),p=n(0),m=n(24),v=n(9).get("scribeCallback"),g=Math.floor(1e3*Math.random())+"_",w="rufous-frame-"+g+"-",y="rufous-form-"+g+"-",b=0,_=!1,E=new d;function x(){var t=o.createElement("form"),e=o.createElement("input"),n=o.createElement("input");return b++,t.action=h.CLIENT_EVENT_ENDPOINT,t.method="POST",t.target=w+b,t.id=y+b,e.type="hidden",e.name="dnt",e.value=c.enabled(),n.type="hidden",n.name="tfw_redirect",n.value=h.RUFOUS_REDIRECT,t.appendChild(e),t.appendChild(n),t}function A(){var t=w+b;return u({id:t,name:t,width:0,height:0,border:0},{display:"none"},o.doc)}t.exports={clientEvent:function(t,e,n,i){(function(t,e){var n=!p.isObject(t),r=!!e&&!p.isObject(e),i=n||r;return i})(t,e)||(v&&v(arguments),E.promise.then(function(){!function(t,e){var n,i,s;p.isObject(t)&&p.isObject(e)&&(f.log(t,e),s=h.flattenClientEventPayload(t,e),(n=r.firstChild).value=+(+n.value||s.dnt||0),(i=o.createElement("input")).type="hidden",i.name="l",i.value=h.stringify(s),r.appendChild(i))}(h.formatClientEventNamespace(t),h.formatClientEventData(e,n,i))}))},flush:function(){return E.promise.then(function(){var t;return r.children.length<=2?l.reject():(t=l.all([o.doc.body.appendChild(r),o.doc.body.appendChild(i)]).then(function(t){var e=t[0],n=t[1];return n.addEventListener("load",function(){!function(t,e){return function(){var n=t.parentNode;n&&(n.removeChild(t),n.removeChild(e))}}(e,n)()}),e.submit(),t}),r=x(),i=A(),t)})},init:function(){return _?E.promise:((o=new m(a)).insert("rufous-sandbox",null,{display:"none"},function(t){s.body.appendChild(t)}).then(function(){o.setTitle("Twitter analytics iframe"),r=x(),i=A(),E.resolve([r,i])}),_=!0,E.promise)}}},function(t,e,n){var r=n(0);t.exports=function t(e){var n=[];return e.forEach(function(e){var i=r.isType("array",e)?t(e):[e];n=n.concat(i)}),n}},function(t,e){t.exports=function(t){return t.filter(function(e,n){return t.indexOf(e)===n})}},function(t,e,n){var r=n(38),i=n(0),o=n(106);function s(){r.apply(this,arguments)}s.prototype=Object.create(r.prototype),i.aug(s.prototype,{factory:o}),t.exports=s},function(t,e,n){var r=n(21),i=n(0),o=n(7);t.exports={before:function(t,e){return function(){var n,i=this,o=arguments;return n=e.apply(this,arguments),r.isPromise(n)?n.then(function(){return t.apply(i,o)}):t.apply(this,arguments)}},after:function(t,e){return function(){var n,i=this,o=arguments;function s(t,e){return r.isPromise(e)?e.then(function(){return t}):t}return n=t.apply(this,arguments),r.isPromise(n)?n.then(function(t){return s(t,e.apply(i,o))}):s(n,e.apply(this,arguments))}},around:function(t,e){return function(){var n=i.toRealArray(arguments);return n.unshift(o(t,this)),e.apply(this,n)}}}},function(t,e,n){var r=n(10),i=n(17),o=n(39),s=n(6),a=n(0);t.exports=function(){var t=o();function e(e){t.apply(this,arguments),Object.defineProperty(this,"targetGlobal",{value:e})}return e.prototype=Object.create(t.prototype),a.aug(e.prototype,{id:null,initialized:!1,width:0,height:0,sandboxEl:null,insert:function(){return s.reject()},onResize:function(){},addClass:function(t){var e=this.sandboxEl;return t=Array.isArray(t)?t:[t],i.write(function(){t.forEach(function(t){r.add(e,t)})})},removeClass:function(t){var e=this.sandboxEl;return t=Array.isArray(t)?t:[t],i.write(function(){t.forEach(function(t){r.remove(e,t)})})},styleSelf:function(t){var e=this;return i.write(function(){a.forIn(t,function(t,n){e.sandboxEl.style[t]=n})})}}),e}},function(t,e,n){var r=n(5),i=n(10),o=n(17),s=n(52),a=n(25),u=n(53),c=n(42),d=n(43),l=n(29),f=n(12),h=n(44),p=n(2),m=n(6),v=n(0),g=n(9),w=n(22),y=n(7),b={allowfullscreen:"true"},_={position:"absolute",visibility:"hidden",display:"block",width:"0px",height:"0px",padding:"0",border:"none"},E={position:"static",visibility:"visible"},x="SandboxRoot",A=".SandboxRoot { display: none; }",T=50;function S(t,e,n,r){return e=v.aug({id:t},b,e),n=v.aug({},_,n),l(e,n,r)}function R(t,e,n,i,s){var a=new p,u=w.generate(),c=S(t,e,n,s);return g.set(["sandbox",u],function(){var t=c.contentWindow.document;o.write(function(){t.write("")}).then(function(){t.close(),a.resolve(c)})}),c.src=["javascript:",'document.write("");',"try { window.parent.document; }",'catch (e) { document.domain="'+r.domain+'"; }',"window.parent."+g.fullPath(["sandbox",u])+"();"].join(""),c.addEventListener("error",a.reject,!1),o.write(function(){i.parentNode.replaceChild(c,i)}),a.promise}t.exports=a.couple(n(54),function(t){t.overrideProperty("id",{get:function(){return this.sandboxEl&&this.sandboxEl.id}}),t.overrideProperty("initialized",{get:function(){return!!this.win}}),t.overrideProperty("width",{get:function(){return this._width}}),t.overrideProperty("height",{get:function(){return this._height}}),t.overrideProperty("sandboxEl",{get:function(){return this.iframeEl}}),t.defineProperty("iframeEl",{get:function(){return this._iframe}}),t.defineProperty("rootEl",{get:function(){return this.doc&&this.doc.documentElement}}),t.defineProperty("widgetEl",{get:function(){return this.doc&&this.doc.body.firstElementChild}}),t.defineProperty("win",{get:function(){return this.iframeEl&&this.iframeEl.contentWindow}}),t.defineProperty("doc",{get:function(){return this.win&&this.win.document}}),t.define("_updateCachedDimensions",function(){var t=this;return o.read(function(){var e,n=h(t.sandboxEl);"visible"==t.sandboxEl.style.visibility?t._width=n.width:(e=h(t.sandboxEl.parentElement).width,t._width=Math.min(n.width,e)),t._height=n.height})}),t.define("_setTargetToBlank",function(){var t=this.createElement("base");t.target="_blank",this.doc.head.appendChild(t)}),t.define("_didResize",function(){var t=this,e=this._resizeHandlers.slice(0);return this._updateCachedDimensions().then(function(){e.forEach(function(e){e(t)})})}),t.define("setTitle",function(t){this.iframeEl.title=t}),t.override("createElement",function(t){return this.doc.createElement(t)}),t.override("createFragment",function(){return this.doc.createDocumentFragment()}),t.override("htmlToElement",function(t){var e;return(e=this.createElement("div")).innerHTML=t,e.firstElementChild}),t.override("hasSelectedText",function(){return!!s.getSelectedText(this.win)}),t.override("addRootClass",function(t){var e=this.rootEl;return t=Array.isArray(t)?t:[t],this.initialized?o.write(function(){t.forEach(function(t){i.add(e,t)})}):m.reject(new Error("sandbox not initialized"))}),t.override("removeRootClass",function(t){var e=this.rootEl;return t=Array.isArray(t)?t:[t],this.initialized?o.write(function(){t.forEach(function(t){i.remove(e,t)})}):m.reject(new Error("sandbox not initialized"))}),t.override("hasRootClass",function(t){return i.present(this.rootEl,t)}),t.define("addStyleSheet",function(t,e){var n,r=new p;return this.initialized?((n=this.createElement("link")).type="text/css",n.rel="stylesheet",n.href=t,n.addEventListener("load",r.resolve,!1),n.addEventListener("error",r.reject,!1),o.write(y(e,null,n)).then(function(){return u(t).then(r.resolve,r.reject),r.promise})):m.reject(new Error("sandbox not initialized"))}),t.override("prependStyleSheet",function(t){var e=this.doc;return this.addStyleSheet(t,function(t){var n=e.head.firstElementChild;return n?e.head.insertBefore(t,n):e.head.appendChild(t)})}),t.override("appendStyleSheet",function(t){var e=this.doc;return this.addStyleSheet(t,function(t){return e.head.appendChild(t)})}),t.define("addCss",function(t,e){var n;return c.inlineStyle()?((n=this.createElement("style")).type="text/css",n.appendChild(this.doc.createTextNode(t)),o.write(y(e,null,n))):(f.devError("CSP enabled; cannot embed inline styles"),m.resolve())}),t.override("prependCss",function(t){var e=this.doc;return this.addCss(t,function(t){var n=e.head.firstElementChild;return n?e.head.insertBefore(t,n):e.head.appendChild(t)})}),t.override("appendCss",function(t){var e=this.doc;return this.addCss(t,function(t){return e.head.appendChild(t)})}),t.override("makeVisible",function(){var t=this;return this.styleSelf(E).then(function(){t._updateCachedDimensions()})}),t.override("injectWidgetEl",function(t){var e=this;return this.initialized?this.widgetEl?m.reject(new Error("widget already injected")):o.write(function(){e.doc.body.appendChild(t)}):m.reject(new Error("sandbox not initialized"))}),t.override("matchHeightToContent",function(){var t,e=this;return o.read(function(){t=e.widgetEl?h(e.widgetEl).height:0}),o.write(function(){e.sandboxEl.style.height=t+"px"}).then(function(){return e._updateCachedDimensions()})}),t.override("matchWidthToContent",function(){var t,e=this;return o.read(function(){t=e.widgetEl?h(e.widgetEl).width:0}),o.write(function(){e.sandboxEl.style.width=t+"px"}).then(function(){return e._updateCachedDimensions()})}),t.after("initialize",function(){this._iframe=null,this._width=this._height=0,this._resizeHandlers=[]}),t.override("insert",function(t,e,n,r){var i=this,s=new p,a=this.targetGlobal.document,u=S(t,e,n,a);return o.write(y(r,null,u)),u.addEventListener("load",function(){(function(t){try{t.contentWindow.document}catch(t){return m.reject(t)}return m.resolve(t)})(u).then(null,y(R,null,t,e,n,u,a)).then(s.resolve,s.reject)},!1),u.addEventListener("error",s.reject,!1),s.promise.then(function(t){var e=d(i._didResize,T,i);return i._iframe=t,i.win.addEventListener("resize",e,!1),m.all([i._setTargetToBlank(),i.addRootClass(x),i.prependCss(A)])})}),t.override("onResize",function(t){this._resizeHandlers.push(t)}),t.after("styleSelf",function(){return this._updateCachedDimensions()})})},function(t,e){t.exports=function(){throw new Error("unimplemented method")}},function(t,e,n){var r=n(2),i=n(7),o=100,s=3e3;function a(t,e){this._inputsQueue=[],this._task=t,this._isPaused=!1,this._flushDelay=e&&e.flushDelay||o,this._pauseLength=e&&e.pauseLength||s,this._flushTimeout=void 0}a.prototype.add=function(t){var e=new r;return this._inputsQueue.push({input:t,taskDoneDeferred:e}),this._scheduleFlush(),e.promise},a.prototype._scheduleFlush=function(){this._isPaused||(clearTimeout(this._flushTimeout),this._flushTimeout=setTimeout(i(this._flush,this),this._flushDelay))},a.prototype._flush=function(){try{this._task.call(null,this._inputsQueue)}catch(t){this._inputsQueue.forEach(function(e){e.taskDoneDeferred.reject(t)})}this._inputsQueue=[],this._flushTimeout=void 0},a.prototype.pause=function(t){clearTimeout(this._flushTimeout),this._isPaused=!0,!t&&this._pauseLength&&setTimeout(i(this.resume,this),this._pauseLength)},a.prototype.resume=function(){this._isPaused=!1,this._scheduleFlush()},t.exports=a},function(t,e,n){var r,i=n(70),o=n(29),s=n(2),a=n(5),u=n(18),c=n(20),d=n(30),l=n(8),f=n(12),h=n(111),p=n(55),m=n(9),v=n(11),g=n(112),w=n(0),y=n(1),b=p(function(){return new s});function _(t){var e=t||{should_obtain_cookie_consent:!0,experiments:{}};return new g(e.should_obtain_cookie_consent,e.experiments)}t.exports={load:function(){var t,e,n,s;if(c.ie9()||c.ie10()||"http:"!==l.protocol&&"https:"!==l.protocol)return f.devError("Using default settings due to unsupported browser or protocol."),r=_(),void b().resolve();t={origin:l.origin},u.settings().indexOf("localhost")>-1&&(t.localSettings=!0),e=v.url(i.resourceBaseUrl+i.widgetIframeHtmlPath,t),n=function(t){var n;if(e.substr(0,t.origin.length)===t.origin)try{(n="string"==typeof t.data?d.parse(t.data):t.data).namespace===h.settings&&(r=_(n.settings),b().resolve())}catch(t){f.devError(t)}},y.addEventListener("message",n),s=o({src:e,title:"Twitter settings iframe"},{display:"none"}),a.body.appendChild(s)},settingsLoaded:function(){var t,e,n;return t=new s,e=m.get("experimentOverride"),b().promise.then(function(){e&&e.name&&e.assignment&&((n={})[e.name]={bucket:e.assignment},r.experiments=w.aug(r.experiments,n)),t.resolve(r)}).catch(function(e){t.reject(e)}),t.promise}}},function(t,e){t.exports={settings:"twttr.settings"}},function(t,e){t.exports=function(t,e){this.shouldObtainCookieConsent=t,this.experiments=e||{}}},function(t,e){t.exports=function(t){return t.split("").map(function(t){return t.charCodeAt(0).toString(16)}).join("")}},function(t,e,n){t.exports=[n(115),n(120),n(128),n(130),n(132),n(146),n(148)]},function(t,e,n){var r=n(11),i=n(4),o=n(0),s=n(13),a=n(14)(),u=n(56),c="a.twitter-dm-button";t.exports=function(t){return a(t,c).map(function(t){return u(function(t){var e=t.getAttribute("data-show-screen-name"),n=s(t),a=t.getAttribute("href"),u=t.getAttribute("data-screen-name"),c=e?i.asBoolean(e):null,d=t.getAttribute("data-size"),l=r.decodeURL(a),f=l.recipient_id,h=t.getAttribute("data-text")||l.text,p=t.getAttribute("data-welcome-message-id")||l.welcomeMessageId;return o.aug(n,{screenName:u,showScreenName:c,size:d,text:h,userId:f,welcomeMessageId:p})}(t),t.parentNode,t)})}},function(t,e,n){var r=n(0);t.exports=function t(e){var n;if(e)return n=e.lang||e.getAttribute("data-lang"),r.isType("string",n)?n:t(e.parentElement)}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return n.e(2).then(function(r){var o;try{o=n(84),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(119),i=n(1),o=n(10),s=n(34),a=n(17),u=n(52),c=n(25),d=n(53),l=n(42),f=n(44),h=n(7),p=n(43),m=n(6),v=n(0),g=50,w={position:"absolute",visibility:"hidden",display:"block",transform:"rotate(0deg)"},y={position:"static",visibility:"visible"},b="twitter-widget",_="open",E="SandboxRoot",x=".SandboxRoot { display: none; max-height: 10000px; }";t.exports=c.couple(n(54),function(t){t.defineStatic("isSupported",function(){return!!i.HTMLElement.prototype.attachShadow&&l.inlineStyle()}),t.overrideProperty("id",{get:function(){return this.sandboxEl&&this.sandboxEl.id}}),t.overrideProperty("initialized",{get:function(){return!!this._shadowHost}}),t.overrideProperty("width",{get:function(){return this._width}}),t.overrideProperty("height",{get:function(){return this._height}}),t.overrideProperty("sandboxEl",{get:function(){return this._shadowHost}}),t.define("_updateCachedDimensions",function(){var t=this;return a.read(function(){var e,n=f(t.sandboxEl);"visible"==t.sandboxEl.style.visibility?t._width=n.width:(e=f(t.sandboxEl.parentElement).width,t._width=Math.min(n.width,e)),t._height=n.height})}),t.define("_didResize",function(){var t=this,e=this._resizeHandlers.slice(0);return this._updateCachedDimensions().then(function(){e.forEach(function(e){e(t)})})}),t.override("createElement",function(t){return this.targetGlobal.document.createElement(t)}),t.override("createFragment",function(){return this.targetGlobal.document.createDocumentFragment()}),t.override("htmlToElement",function(t){var e;return(e=this.createElement("div")).innerHTML=t,e.firstElementChild}),t.override("hasSelectedText",function(){return!!u.getSelectedText(this.targetGlobal)}),t.override("addRootClass",function(t){var e=this._shadowRootBody;return t=Array.isArray(t)?t:[t],this.initialized?a.write(function(){t.forEach(function(t){o.add(e,t)})}):m.reject(new Error("sandbox not initialized"))}),t.override("removeRootClass",function(t){var e=this._shadowRootBody;return t=Array.isArray(t)?t:[t],this.initialized?a.write(function(){t.forEach(function(t){o.remove(e,t)})}):m.reject(new Error("sandbox not initialized"))}),t.override("hasRootClass",function(t){return o.present(this._shadowRootBody,t)}),t.override("addStyleSheet",function(t,e){return this.addCss('@import url("'+t+'");',e).then(function(){return d(t)})}),t.override("prependStyleSheet",function(t){var e=this._shadowRoot;return this.addStyleSheet(t,function(t){var n=e.firstElementChild;return n?e.insertBefore(t,n):e.appendChild(t)})}),t.override("appendStyleSheet",function(t){var e=this._shadowRoot;return this.addStyleSheet(t,function(t){return e.appendChild(t)})}),t.override("addCss",function(t,e){var n;return this.initialized?l.inlineStyle()?((n=this.createElement("style")).type="text/css",n.appendChild(this.targetGlobal.document.createTextNode(t)),a.write(h(e,null,n))):m.resolve():m.reject(new Error("sandbox not initialized"))}),t.override("prependCss",function(t){var e=this._shadowRoot;return this.addCss(t,function(t){var n=e.firstElementChild;return n?e.insertBefore(t,n):e.appendChild(t)})}),t.override("appendCss",function(t){var e=this._shadowRoot;return this.addCss(t,function(t){return e.appendChild(t)})}),t.override("makeVisible",function(){return this.styleSelf(y)}),t.override("injectWidgetEl",function(t){var e=this;return this.initialized?this._shadowRootBody.firstElementChild?m.reject(new Error("widget already injected")):a.write(function(){e._shadowRootBody.appendChild(t)}).then(function(){return e._updateCachedDimensions()}).then(function(){var t=p(e._didResize,g,e);new r(e._shadowRootBody,t)}):m.reject(new Error("sandbox not initialized"))}),t.override("matchHeightToContent",function(){return m.resolve()}),t.override("matchWidthToContent",function(){return m.resolve()}),t.override("insert",function(t,e,n,r){var i=this.targetGlobal.document,o=this._shadowHost=i.createElement(b),u=this._shadowRoot=o.attachShadow({mode:_}),c=this._shadowRootBody=i.createElement("div");return v.forIn(e||{},function(t,e){o.setAttribute(t,e)}),o.id=t,u.appendChild(c),s.delegate(c,"click","A",function(t,e){e.hasAttribute("target")||e.setAttribute("target","_blank")}),m.all([this.styleSelf(w),this.addRootClass(E),this.prependCss(x),a.write(r.bind(null,o))])}),t.override("onResize",function(t){this._resizeHandlers.push(t)}),t.after("initialize",function(){this._shadowHost=this._shadowRoot=this._shadowRootBody=null,this._width=this._height=0,this._resizeHandlers=[]}),t.after("styleSelf",function(){return this._updateCachedDimensions()})})},function(t,e){var n;(n=function(t,e){function r(t,e){if(t.resizedAttached){if(t.resizedAttached)return void t.resizedAttached.add(e)}else t.resizedAttached=new function(){var t,e;this.q=[],this.add=function(t){this.q.push(t)},this.call=function(){for(t=0,e=this.q.length;t
',t.appendChild(t.resizeSensor),{fixed:1,absolute:1}[function(t,e){return t.currentStyle?t.currentStyle[e]:window.getComputedStyle?window.getComputedStyle(t,null).getPropertyValue(e):t.style[e]}(t,"position")]||(t.style.position="relative");var i,o,s=t.resizeSensor.childNodes[0],a=s.childNodes[0],u=t.resizeSensor.childNodes[1],c=(u.childNodes[0],function(){a.style.width=s.offsetWidth+10+"px",a.style.height=s.offsetHeight+10+"px",s.scrollLeft=s.scrollWidth,s.scrollTop=s.scrollHeight,u.scrollLeft=u.scrollWidth,u.scrollTop=u.scrollHeight,i=t.offsetWidth,o=t.offsetHeight});c();var d=function(t,e,n){t.attachEvent?t.attachEvent("on"+e,n):t.addEventListener(e,n)},l=function(){t.offsetWidth==i&&t.offsetHeight==o||t.resizedAttached&&t.resizedAttached.call(),c()};d(s,"scroll",l),d(u,"scroll",l)}var i=Object.prototype.toString.call(t),o="[object Array]"===i||"[object NodeList]"===i||"[object HTMLCollection]"===i||"undefined"!=typeof jQuery&&t instanceof jQuery||"undefined"!=typeof Elements&&t instanceof Elements;if(o)for(var s=0,a=t.length;s0;return this.updateCachedDimensions().then(function(){e&&t._resizeHandlers.forEach(function(e){e(t)})})}),t.define("loadDocument",function(t){var e=new a;return this.initialized?this.iframeEl.src?u.reject(new Error("widget already loaded")):(this.iframeEl.addEventListener("load",e.resolve,!1),this.iframeEl.addEventListener("error",e.reject,!1),this.iframeEl.src=t,e.promise):u.reject(new Error("sandbox not initialized"))}),t.after("initialize",function(){this._iframe=null,this._width=this._height=0,this._resizeHandlers=[]}),t.override("insert",function(t,e,n,i){var a=this;return e=d.aug({id:t},e),n=d.aug({},l,n),this._iframe=s(e,n),h[t]=this,this.onResize(o(function(){a.makeVisible()})),r.write(c(i,null,this._iframe))}),t.override("onResize",function(t){this._resizeHandlers.push(t)}),t.after("styleSelf",function(){return this.updateCachedDimensions()})}},function(t,e,n){var r=n(1),i=n(124),o=n(126),s=n(23),a=n(4),u=n(127);t.exports=function(t){(new i).attachReceiver(new o.Receiver(r,"twttr.button")).bind("twttr.private.trigger",function(t,e){var n=u(this);s.trigger(t,{target:n,region:e,type:t,data:{}})}).bind("twttr.private.resizeButton",function(e){var n=u(this),r=n&&n.id,i=a.asInt(e.width),o=a.asInt(e.height);r&&void 0!==i&&void 0!==o&&t(r,i,o)})}},function(t,e,n){var r=n(30),i=n(125),o=n(0),s=n(6),a=n(21),u="2.0";function c(t){this.registry=t||{}}function d(t){var e,n;return e=o.isType("string",t),n=o.isType("number",t),e||n||null===t}function l(t,e){return{jsonrpc:u,id:d(t)?t:null,error:e}}c.prototype._invoke=function(t,e){var n,r,i;n=this.registry[t.method],r=t.params||[],r=o.isType("array",r)?r:[r];try{i=n.apply(e.source||null,r)}catch(t){i=s.reject(t.message)}return a.isPromise(i)?i:s.resolve(i)},c.prototype._processRequest=function(t,e){var n,r;return function(t){var e,n,r;return!!o.isObject(t)&&(e=t.jsonrpc===u,n=o.isType("string",t.method),r=!("id"in t)||d(t.id),e&&n&&r)}(t)?(n="params"in t&&(r=t.params,!o.isObject(r)||o.isType("function",r))?s.resolve(l(t.id,i.INVALID_PARAMS)):this.registry[t.method]?this._invoke(t,{source:e}).then(function(e){return n=t.id,{jsonrpc:u,id:n,result:e};var n},function(){return l(t.id,i.INTERNAL_ERROR)}):s.resolve(l(t.id,i.METHOD_NOT_FOUND)),null!=t.id?n:s.resolve()):s.resolve(l(t.id,i.INVALID_REQUEST))},c.prototype.attachReceiver=function(t){return t.attachTo(this),this},c.prototype.bind=function(t,e){return this.registry[t]=e,this},c.prototype.receive=function(t,e){var n,a,u,c=this;try{u=t,t=o.isType("string",u)?r.parse(u):u}catch(t){return s.resolve(l(null,i.PARSE_ERROR))}return e=e||null,a=((n=o.isType("array",t))?t:[t]).map(function(t){return c._processRequest(t,e)}),n?function(t){return s.all(t).then(function(t){return(t=t.filter(function(t){return void 0!==t})).length?t:void 0})}(a):a[0]},t.exports=c},function(t){t.exports={PARSE_ERROR:{code:-32700,message:"Parse error"},INVALID_REQUEST:{code:-32600,message:"Invalid Request"},INVALID_PARAMS:{code:-32602,message:"Invalid params"},METHOD_NOT_FOUND:{code:-32601,message:"Method not found"},INTERNAL_ERROR:{code:-32603,message:"Internal error"}}},function(t,e,n){var r=n(8),i=n(1),o=n(30),s=n(2),a=n(20),u=n(0),c=n(3),d=n(7),l=a.ie9();function f(t,e,n){var r;t&&t.postMessage&&(l?r=(n||"")+o.stringify(e):n?(r={})[n]=e:r=e,t.postMessage(r,"*"))}function h(t){return u.isType("string",t)?t:"JSONRPC"}function p(t,e){return e?u.isType("string",t)&&0===t.indexOf(e)?t.substring(e.length):t&&t[e]?t[e]:void 0:t}function m(t,e){var n=t.document;this.filter=h(e),this.server=null,this.isTwitterFrame=c.isTwitterURL(n.location.href),t.addEventListener("message",d(this._onMessage,this),!1)}function v(t,e){this.pending={},this.target=t,this.isTwitterHost=c.isTwitterURL(r.href),this.filter=h(e),i.addEventListener("message",d(this._onMessage,this),!1)}u.aug(m.prototype,{_onMessage:function(t){var e,n=this;this.server&&(this.isTwitterFrame&&!c.isTwitterURL(t.origin)||(e=p(t.data,this.filter))&&this.server.receive(e,t.source).then(function(e){e&&f(t.source,e,n.filter)}))},attachTo:function(t){this.server=t},detach:function(){this.server=null}}),u.aug(v.prototype,{_processResponse:function(t){var e=this.pending[t.id];e&&(e.resolve(t),delete this.pending[t.id])},_onMessage:function(t){var e;if((!this.isTwitterHost||c.isTwitterURL(t.origin))&&(e=p(t.data,this.filter))){if(u.isType("string",e))try{e=o.parse(e)}catch(t){return}(e=u.isType("array",e)?e:[e]).forEach(d(this._processResponse,this))}},send:function(t){var e=new s;return t.id?this.pending[t.id]=e:e.resolve(),f(this.target,t,this.filter),e.promise}}),t.exports={Receiver:m,Dispatcher:v,_stringifyPayload:function(t){return arguments.length>0&&(l=!!t),l}}},function(t,e,n){var r=n(5);t.exports=function(t){for(var e,n=r.getElementsByTagName("iframe"),i=0;n[i];i++)if((e=n[i]).contentWindow===t)return e}},function(t,e,n){var r=n(4),i=n(0),o=n(3),s=n(13),a=n(14)(),u=n(61),c="a.twitter-moment";t.exports=function(t){return a(t,c).map(function(t){return u(function(t){var e=s(t),n={momentId:o.momentId(t.href),chrome:t.getAttribute("data-chrome"),limit:t.getAttribute("data-limit")};return i.forIn(n,function(t,n){var i=e[t];e[t]=r.hasValue(i)?i:n}),e}(t),t.parentNode,t)})}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return Promise.all([n.e(0),n.e(4)]).then(function(r){var o;try{o=n(86),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(0),i=n(13),o=n(14)(),s=n(62),a="a.periscope-on-air",u=/^https?:\/\/(?:www\.)?(?:periscope|pscp)\.tv\/@?([a-zA-Z0-9_]+)\/?$/i;t.exports=function(t){return o(t,a).map(function(t){return s(function(t){var e=i(t),n=t.getAttribute("href"),o=t.getAttribute("data-size"),s=u.exec(n)[1];return r.aug(e,{username:s,size:o})}(t),t.parentNode,t)})}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return n.e(5).then(function(r){var o;try{o=n(87),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(4),i=n(0),o=n(63),s=n(13),a=n(14)(),u=n(64),c=n(3),d=n(12),l="a.twitter-timeline,div.twitter-timeline,a.twitter-grid",f="Embedded Search timelines have been deprecated. See https://twittercommunity.com/t/deprecating-widget-settings/102295.",h="You may have been affected by an update to settings in embedded timelines. See https://twittercommunity.com/t/deprecating-widget-settings/102295.",p="Embedded grids have been deprecated and will now render as timelines. Please update your embed code to use the twitter-timeline class. More info: https://twittercommunity.com/t/update-on-the-embedded-grid-display-type/119564.";t.exports=function(t){return a(t,l).map(function(t){return u(function(t){var e=s(t),n=t.getAttribute("data-show-replies"),a={isPreconfigured:!!t.getAttribute("data-widget-id"),chrome:t.getAttribute("data-chrome"),tweetLimit:t.getAttribute("data-tweet-limit")||t.getAttribute("data-limit"),ariaLive:t.getAttribute("data-aria-polite"),theme:t.getAttribute("data-theme"),linkColor:t.getAttribute("data-link-color"),borderColor:t.getAttribute("data-border-color"),showReplies:n?r.asBoolean(n):null,profileScreenName:t.getAttribute("data-screen-name"),profileUserId:t.getAttribute("data-user-id"),favoritesScreenName:t.getAttribute("data-favorites-screen-name"),favoritesUserId:t.getAttribute("data-favorites-user-id"),likesScreenName:t.getAttribute("data-likes-screen-name"),likesUserId:t.getAttribute("data-likes-user-id"),listOwnerScreenName:t.getAttribute("data-list-owner-screen-name"),listOwnerUserId:t.getAttribute("data-list-owner-id"),listId:t.getAttribute("data-list-id"),listSlug:t.getAttribute("data-list-slug"),customTimelineId:t.getAttribute("data-custom-timeline-id"),staticContent:t.getAttribute("data-static-content"),url:t.href};return a.isPreconfigured&&(c.isSearchUrl(a.url)?d.publicError(f,t):d.publicLog(h,t)),"twitter-grid"===t.className&&d.publicLog(p,t),(a=i.aug(a,e)).dataSource=o(a),a.id=a.dataSource&&a.dataSource.id,a}(t),t.parentNode,t)})}},function(t,e,n){var r=n(26);t.exports=r.build([n(27),n(136)])},function(t,e,n){var r=n(0),i=n(135);t.exports=function(t){return"en"===t||r.contains(i,t)}},function(t,e){t.exports=["hi","zh-cn","fr","zh-tw","msa","fil","fi","sv","pl","ja","ko","de","it","pt","es","ru","id","tr","da","no","nl","hu","fa","ar","ur","he","th","cs","uk","vi","ro","bn","el","en-gb","gu","kn","mr","ta","bg","ca","hr","sr","sk"]},function(t,e,n){var r=n(3),i=n(0),o=n(18),s="collection:";function a(t,e){return r.collectionId(t)||e}t.exports=function(t){t.params({id:{},url:{}}),t.overrideProperty("id",{get:function(){var t=a(this.params.url,this.params.id);return s+t}}),t.overrideProperty("endpoint",{get:function(){return o.timeline(["collection"])}}),t.around("queryParams",function(t){return i.aug(t(),{collection_id:a(this.params.url,this.params.id)})}),t.before("initialize",function(){if(!a(this.params.url,this.params.id))throw new Error("one of url or id is required")})}},function(t,e,n){var r=n(26);t.exports=r.build([n(27),n(138)])},function(t,e,n){var r=n(3),i=n(0),o=n(18),s="event:";function a(t,e){return r.eventId(t)||e}t.exports=function(t){t.params({id:{},url:{}}),t.overrideProperty("id",{get:function(){var t=a(this.params.url,this.params.id);return s+t}}),t.overrideProperty("endpoint",{get:function(){return o.timeline(["event"])}}),t.around("queryParams",function(t){return i.aug(t(),{event_id:a(this.params.url,this.params.id)})}),t.before("initialize",function(){if(!a(this.params.url,this.params.id))throw new Error("one of url or id is required")})}},function(t,e,n){var r=n(26);t.exports=r.build([n(27),n(140)])},function(t,e,n){var r=n(3),i=n(0),o=n(18),s="likes:";function a(t){return r.likesScreenName(t.url)||t.screenName}t.exports=function(t){t.params({screenName:{},userId:{},url:{}}),t.overrideProperty("id",{get:function(){var t=a(this.params)||this.params.userId;return s+t}}),t.overrideProperty("endpoint",{get:function(){return o.timeline(["likes"])}}),t.define("_getLikesQueryParam",function(){var t=a(this.params);return t?{screen_name:t}:{user_id:this.params.userId}}),t.around("queryParams",function(t){return i.aug(t(),this._getLikesQueryParam())}),t.before("initialize",function(){if(!a(this.params)&&!this.params.userId)throw new Error("screen name or user id is required")})}},function(t,e,n){var r=n(26);t.exports=r.build([n(27),n(142)])},function(t,e,n){var r=n(3),i=n(0),o=n(18),s="list:";function a(t){var e=r.listScreenNameAndSlug(t.url)||t;return i.compact({screen_name:e.ownerScreenName,user_id:e.ownerUserId,list_slug:e.slug})}t.exports=function(t){t.params({id:{},ownerScreenName:{},ownerUserId:{},slug:{},url:{}}),t.overrideProperty("id",{get:function(){var t,e,n;return this.params.id?s+this.params.id:(e=(t=a(this.params))&&t.list_slug.replace(/-/g,"_"),n=t&&(t.screen_name||t.user_id),s+(n+":")+e)}}),t.overrideProperty("endpoint",{get:function(){return o.timeline(["list"])}}),t.define("_getListQueryParam",function(){return this.params.id?{list_id:this.params.id}:a(this.params)}),t.around("queryParams",function(t){return i.aug(t(),this._getListQueryParam())}),t.before("initialize",function(){var t=a(this.params);if(i.isEmptyObject(t)&&!this.params.id)throw new Error("qualified slug or list id required")})}},function(t,e,n){var r=n(26);t.exports=r.build([n(27),n(144)])},function(t,e,n){var r=n(3),i=n(4),o=n(0),s=n(18),a="profile:";function u(t,e){return r.screenName(t)||e}t.exports=function(t){t.params({showReplies:{fallback:!1,transform:i.asBoolean},screenName:{},userId:{},url:{}}),t.overrideProperty("id",{get:function(){var t=u(this.params.url,this.params.screenName);return a+(t||this.params.userId)}}),t.overrideProperty("endpoint",{get:function(){return s.timeline(["profile"])}}),t.define("_getProfileQueryParam",function(){var t=u(this.params.url,this.params.screenName),e=t?{screen_name:t}:{user_id:this.params.userId};return o.aug(e,{with_replies:this.params.showReplies?"true":"false"})}),t.around("queryParams",function(t){return o.aug(t(),this._getProfileQueryParam())}),t.before("initialize",function(){if(!u(this.params.url,this.params.screenName)&&!this.params.userId)throw new Error("screen name or user id is required")})}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return Promise.all([n.e(0),n.e(6)]).then(function(r){var o;try{o=n(88),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(10),i=n(3),o=n(0),s=n(13),a=n(14)(),u=n(65),c="blockquote.twitter-tweet, blockquote.twitter-video",d=/\btw-align-(left|right|center)\b/;t.exports=function(t){return a(t,c).map(function(t){return u(function(t){var e=s(t),n=t.getElementsByTagName("A"),a=n&&n[n.length-1],u=a&&i.status(a.href),c=t.getAttribute("data-conversation"),l="none"==c||"hidden"==c||r.present(t,"tw-hide-thread"),f=t.getAttribute("data-cards"),h="none"==f||"hidden"==f||r.present(t,"tw-hide-media"),p=t.getAttribute("data-align")||t.getAttribute("align"),m=t.getAttribute("data-link-color"),v=t.getAttribute("data-theme");return!p&&d.test(t.className)&&(p=RegExp.$1),o.aug(e,{tweetId:u,hideThread:l,hideCard:h,align:p,linkColor:m,theme:v,id:u})}(t),t.parentNode,t)})}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return Promise.all([n.e(0),n.e(7)]).then(function(r){var o;try{o=n(89),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(10),i=n(0),o=n(13),s=n(14)(),a=n(66),u=n(4),c="a.twitter-share-button, a.twitter-mention-button, a.twitter-hashtag-button",d="twitter-hashtag-button",l="twitter-mention-button";t.exports=function(t){return s(t,c).map(function(t){return a(function(t){var e=o(t),n={screenName:t.getAttribute("data-button-screen-name"),text:t.getAttribute("data-text"),type:t.getAttribute("data-type"),size:t.getAttribute("data-size"),url:t.getAttribute("data-url"),hashtags:t.getAttribute("data-hashtags"),via:t.getAttribute("data-via"),buttonHashtag:t.getAttribute("data-button-hashtag")};return i.forIn(n,function(t,n){var r=e[t];e[t]=u.hasValue(r)?r:n}),e.screenName=e.screenName||e.screen_name,e.buttonHashtag=e.buttonHashtag||e.button_hashtag||e.hashtag,r.present(t,d)&&(e.type="hashtag"),r.present(t,l)&&(e.type="mention"),e}(t),t.parentNode,t)})}},function(t,e,n){var r=n(2);t.exports=function(t,e){var i=new r;return n.e(3).then(function(r){var o;try{o=n(90),i.resolve(new o(t,e))}catch(t){i.reject(t)}}.bind(null,n)).catch(function(t){i.reject(t)}),i.promise}},function(t,e,n){var r=n(0);t.exports=r.aug({},n(151),n(152),n(153),n(154),n(155),n(156),n(157))},function(t,e,n){var r=n(56),i=n(16)(["userId"],{},r);t.exports={createDMButton:i}},function(t,e,n){var r=n(59),i=n(16)(["screenName"],{},r);t.exports={createFollowButton:i}},function(t,e,n){var r=n(61),i=n(16)(["momentId"],{},r);t.exports={createMoment:i}},function(t,e,n){var r=n(62),i=n(16)(["username"],{},r);t.exports={createPeriscopeOnAirButton:i}},function(t,e,n){var r=n(8),i=n(12),o=n(3),s=n(0),a=n(4),u=n(63),c=n(64),d=n(16)([],{},c),l=n(6),f="Embedded grids have been deprecated. Please use twttr.widgets.createTimeline instead. More info: https://twittercommunity.com/t/update-on-the-embedded-grid-display-type/119564.",h={createTimeline:p,createGridFromCollection:function(t){var e=s.toRealArray(arguments).slice(1),n={sourceType:"collection",id:t};return e.unshift(n),i.publicLog(f),p.apply(this,e)}};function p(t){var e,n=s.toRealArray(arguments).slice(1);return a.isString(t)||a.isNumber(t)?l.reject("Embedded timelines with widget settings have been deprecated. See https://twittercommunity.com/t/deprecating-widget-settings/102295."):s.isObject(t)?(t=t||{},n.forEach(function(t){s.isType("object",t)&&function(t){t.ariaLive=t.ariaPolite}(e=t)}),e||(e={},n.push(e)),t.lang=e.lang,t.tweetLimit=e.tweetLimit,t.showReplies=e.showReplies,e.dataSource=u(t),d.apply(this,n)):l.reject("data source must be an object.")}o.isTwitterURL(r.href)&&(h.createTimelinePreview=function(t,e,n){var r={previewParams:t,useLegacyDefaults:!0,isPreviewTimeline:!0};return r.dataSource=u(r),d(e,r,n)}),t.exports=h},function(t,e,n){var r,i=n(0),o=n(65),s=n(16),a=(r=s(["tweetId"],{},o),function(){return i.toRealArray(arguments).slice(1).forEach(function(t){i.isType("object",t)&&(t.hideCard="none"==t.cards||"hidden"==t.cards,t.hideThread="none"==t.conversation||"hidden"==t.conversation)}),r.apply(this,arguments)});t.exports={createTweet:a,createTweetEmbed:a,createVideo:a}},function(t,e,n){var r=n(0),i=n(66),o=n(16),s=o(["url"],{type:"share"},i),a=o(["buttonHashtag"],{type:"hashtag"},i),u=o(["screenName"],{type:"mention"},i);function c(t){return function(){return r.toRealArray(arguments).slice(1).forEach(function(t){r.isType("object",t)&&(t.screenName=t.screenName||t.screen_name,t.buttonHashtag=t.buttonHashtag||t.button_hashtag||t.hashtag)}),t.apply(this,arguments)}}t.exports={createShareButton:c(s),createHashtagButton:c(a),createMentionButton:c(u)}},function(t,e,n){var r,i,o,s=n(5),a=n(1),u=0,c=[],d=s.createElement("a");function l(){var t,e;for(u=1,t=0,e=c.length;t - - - - - - - Daslang - The High Performance Programming Language - - - - - - - - - -
- -
- -
-
-
-

- Daslang -

-
-

- Daslang (former daScript) is a high-level programming language that features strong static typing. - It is designed to provide high performance and serves as an embeddable 'scripting' language - for C++ applications that require fast and reliable performance, such as games or back end/servers. - Additionally, it functions effectively as a standalone programming language. -

-
-

News

-
    -
  • - May 9th, 2026: 0.6.2-RC3 is out — post-RC2 polish across `@live` on struct fields, MCP `daslib/` fallback for unqualified module names, blind-mouse Q&A cache MCP server, nine new perf/style lint rules (PERF013–017, STYLE016–019), and a `find_call_macro` null-deref fix. - See the Change List for details, or grab the 0.6.2-RC3 pre-release. -
  • -
  • - May 8th, 2026: 0.6.2-RC2 is out! Its getting closer. - See the Change List for details, or grab the 0.6.2-RC2 pre-release. -
  • -
  • - May 1st, 2026: 0.6.2 is just about ready. - See the Change List, or grab the - 0.6.2-RC1 pre-release. -
  • -
  • - April 17th, 2026: daStrudel gets module reference and a 15-part tutorial series. -
  • -
  • - April 4th, 2026: 0.6.1 is out! -
  • -
  • - March 29th, 2026: daStrudel gets SF2 SoundFont support, MIDI playback, and a redesigned two-mode player with real-time visualization. See the Change List. -
  • -
  • - March 25th, 2026: 0.6.1 is just about ready. See the Change List for what's new. -
  • -
  • - March 1st, 2026: Daslang 0.6.0 is officially released! Check out the - 0.6.0 release. -
  • -
  • - Feb 28th, 2026: Daslang 0.6 is just around the corner! Stay tuned for the official release. - You can preview release candidate 0.6.0-RC1 - 0.6.0-RC2 here. -
  • -
  • - Feb 20th, 2026: We are getting ready for the release of version 0.6. - You can preview the documentation online. -
  • -
  • - Jan 22nd, 2026: Life improvements in Daslang! - Little things with big impact. -
  • -
  • - Jan 20th, 2026: Say hello to templates and typemacros in Daslang! - It's trurtles all the way down. -
  • -
  • - December 30th, 2025: Pre-releases are available, RELEASES are coming soon. - Bundle has JIT and modules. Bare bones version is just to play with the language. -
  • -
  • - June 10th, 2025: We now have LINQ-like capabilities in Daslang. - See blog for details. -
  • -
  • - May 6th, 2025: Overview of gen2 syntax as well as gen1.5 syntax, as well as conversion process. - All about 'options gen2' -
  • -
  • - Oct 10th, 2024: Version 0.5 of Daslang has been released. This version includes a lot of new features and improvements, - such as JIT, serialization, performance improvements, new syntax features. - See plans for 0.6 for more details. -
  • -
  • - July 28th, 2024: Massive update on data initialization syntax. Table and set comprehensions are now available. - See the Data Initialization in DAS for more details. -
  • -
  • - July 7th, 2024: Version 0.5 is coming very soon. It will include a lot of new features and improvements. Stay tuned! - Expect JIT, serialization, performance improvements, new syntax features, and more. -
  • -
  • - July 1st, 2024: daScript renamed to Daslang! -
  • -
  • - May 1st, 2023: Version 0.4 of Daslang has been released. The development team is currently working on the next major release, - which will include a fully featured JIT compiler. This JIT compiler is currently available as a preview. -
  • -
  • - 1st Jan 2023: Daslang now happily runs in the web browser. Check it out. -
  • -
  • - 14 Dec 2022: We now have an official blog -
  • -
  • - November 18th, 2021: Version 0.3 released. -
  • -
  • - September 5th, 2020: Version 0.2 released. -
  • -
  • - 24 August 2020: A lot of documentation has been added.
    - tio.run sandbox - added. -
  • -
  • - 28 October 2019: Benchmarks has been updated. All lang compilers - were built in LLVM8.0, architecture AVX. -
  • -
  • - 15 August 2019: Daslang site has been released, check it - out. -
  • -
-
-
- -
-

Overview

- -

Daslang is:

-
    -
  • - Extremely fast programming language that can rival compiled or JIT languages even in interpreter mode. - It consistently outperforms dynamically interpreted scripting languages such as Lua. When used in AOT mode, - Daslang is often faster than naively written C++ due to its SSE-friendly POD-types, and it even surpasses some - of the best JIT VMs like V8 or LuaJIT. As a result, there's no need to rewrite your Daslang code in C++ to optimize your application. -
  • -
  • - Safe and versatile programming language that provides all the benefits of static typing. Many errors that would break an - application in runtime in languages like Lua or JavaScript won't even compile in Daslang. Additionally, due to its support for - generics, type inference, and macros, Daslang is easy and fluid to use. Safety is a fundamental pillar of Daslang's design, - making it safer in many cases than languages like C++ or Java. -
  • -
  • - Real embedded programming language that requires no external dependencies other than a C++17 compiler. Its interop capabilities - are both super easy to use and safe, as is expected from any embedded scripting language that's meant to call native code. -
  • -
-

- Daslang offers a wide range of features like: -

-
    -
  • amazing performance
  • -
  • strong static typing
  • -
  • instant hot reload
  • -
  • Ruby-like blocks
  • -
  • semantic indenting
  • -
  • lexical scoping
  • -
  • high performance interpretation
  • -
  • Ahead-of-Time compilation (optimization for release build)
  • -
  • generators
  • -
  • generics and type inference
  • -
  • macros
  • -
  • optional types (i.e. pointers)
  • -
  • native HW friendly POD types
  • -
  • C++ friendly and fast interop
  • -
  • semi-manual memory management
  • -
  • - fast and easy-to-reset execution context, allowing automatic burst - free memory management for stored procedures -
    - no memory leaks with no GC/reference counting cost. -
  • -
  • extendable type and function system
  • -
  • - cheap multi-threading support - explicit separation of execution - context and code context. -
  • -
  • - Open Source - BSD licence -
  • -
  • - powerful embedding API -
      -
    • e.g. functions can be defined by scripts or in C++
    • -
    • - e.g. objects can fully exist in the Context or be bound to native - code -
    • -
    • and more
    • -
    -
  • -
+ + + -

- Daslang takes inspiration from languages like Python, Ruby, Kotlin, and Haxe when it comes to its explicitness and readability. - However, it is also designed to be extremely fast, making it an ideal choice for performance-critical applications. -

+ Daslang — Not a script. A language. + + -

- A Visual Studio Code extension is available for Daslang, providing comprehensive support for the language. This extension offers features - such as code diagnostics, code completion, code navigation, hints, and more. Additionally, it provides a rich debugging environment, allowing - developers to easily debug their Daslang code. It also includes a profiler for measuring and optimizing code performance. You can find this - extension on the Visual Studio Code Marketplace at the following link: - https://marketplace.visualstudio.com/items?itemName=profelis.dascript-plugin. -

-
+ + -
-

What Does it look like?

-

- Daslang's syntax is reminiscent of Kotlin and (Typed) Python, while its static strong typed nature is similar to ML or Zig. - Its POD types are closely aligned with hardware/machine types. Additionally, the language makes use of type inference, - as can be seen in the following Fibonacci code snippet. -

+ + + + -
-

-def fibR(n) {
-    if (n < 2) {
-        return n
-    } else {
-        return fibR(n - 1) + fibR(n - 2)
-    }
-}
-def fibI(n) {
-    var last = 0
-    var cur = 1
-    for ( i in 0..n-1 ) {
-        let tmp = cur
-        cur += last
-        last = tmp
-    }
-    return cur
-}
- Also, for those who prefer Python-style indenting over curly brackets, it is also possible to write: -
options gen2=false
-
-def fibR(n)
-    if n < 2
-        return n
-    else
-        return fibR(n - 1) + fibR(n - 2)
-
-def fibI(n)
-    var last = 0
-    var cur = 1
-    for i in 0..n-1
-        let tmp = cur
-        cur += last
-        last = tmp
-    return cur
+ + + + + +
+ + +
- -
-

Development state

-

- Daslang's current version is 0.6 (or just about), and it's already being used in production projects at Gaijin Entertainment. - Until the release of version 1.0, there will be no patches or fixes for older versions. - Instead, all updates and fixes will be made available exclusively in the master branch. -

-

- Daslang has been successfully compiled and run on a wide range of platforms, including Windows (x86 & x64), Linux (x64), - macOS, Xbox One, Xbox One Series X, PlayStation 4, PlayStation 5, Nintendo Switch, iOS, Android, and WebAssembly. -

-

- Has been tested with the following compilers with C++17 support: -

-
-

- MS Visual C++ 17, 19, and 22 (x86 & x64)
- Linux GCC
- LLVM 7 and above
-

-
-

- To compile, Daslang requires support for C++17. -

-
- -
-

Performance

-

- Interpreted  |  AOT or JIT -

-

- As an interpreted language, Daslang typically outperforms everything, including the blazing fast LuaJIT. -

-

- Daslang particularly excels in interop with C++ functions, providing an easy and seamless experience when it comes to integrating new functions and types. -

-

- The overhead of data-oriented processing in interpretation mode in Daslang is comparable to C++ in Debug mode, as demonstrated by the Particles sample. -

- - Tested on AMD Ryzen Threadripper 3990X 64-Core Processor at Mon Sep 30 15:32:40 2024
- CLANG 17.0.6
- -

Interpreted

-

dictionary

- - - - - - - - -
DAS INTERPRETER
0.0189  (1.13)
LUA
0.0660  (3.96)
LUAJIT -joff
LUAU
0.0307  (1.84)
QUICKJS
0.1020  (6.13)
QUIRREL
0.0880  (5.28)
SQUIRREL3
0.1110  (6.67)
-

exp loop

- - - - - - - - -
DAS INTERPRETER
LUA
0.0520  (4.18)
LUAJIT -joff
0.0382  (3.08)
LUAU
0.0204  (1.64)
QUICKJS
0.0870  (7.00)
QUIRREL
0.0860  (6.92)
SQUIRREL3
0.0850  (6.84)
-

fibonacci loop

- - - - - - - - -
DAS INTERPRETER
LUA
0.0900  (2.53)
LUAJIT -joff
0.0454  (1.28)
LUAU
0.0775  (2.18)
QUICKJS
0.1260  (3.54)
QUIRREL
0.1390  (3.91)
SQUIRREL3
0.1110  (3.12)
-

fibonacci recursive

- - - - - - - - -
DAS INTERPRETER
LUA
0.1020  (1.91)
LUAJIT -joff
0.0588  (1.10)
LUAU
0.0892  (1.67)
QUICKJS
0.1670  (3.12)
QUIRREL
0.2580  (4.82)
SQUIRREL3
0.2750  (5.14)
-

float2string

- - - - - - - - -
DAS INTERPRETER
LUA
0.6210  (8.19)
LUAJIT -joff
0.1723  (2.27)
LUAU
QUICKJS
3.6120  (47.63)
QUIRREL
0.1080  (1.42)
SQUIRREL3
0.1070  (1.41)
-

mandelbrot

- - - - - -
DAS INTERPRETER
LUA
0.1640  (53.93)
LUAJIT -joff
0.0597  (19.64)
LUAU
0.0555  (18.24)
-

n-bodies

- - - - - - - - -
DAS INTERPRETER
LUA
1.2500  (5.96)
LUAJIT -joff
0.5590  (2.66)
LUAU
0.6912  (3.29)
QUICKJS
2.4450  (11.65)
QUIRREL
0.2400  (1.14)
SQUIRREL3
0.2870  (1.37)
-

native loop

- - - -
DAS INTERPRETER
LUAJIT -joff
0.8819  (23.68)
-

particles kinematics

- - - - - - - - -
DAS INTERPRETER
LUA
1.2270  (103.66)
LUAJIT -joff
0.3007  (25.41)
LUAU
0.3925  (33.16)
QUICKJS
1.4440  (121.99)
QUIRREL
0.6090  (51.45)
SQUIRREL3
1.4890  (125.79)
-

primes loop

- - - - - - - - -
DAS INTERPRETER
LUA
0.0960  (2.34)
LUAJIT -joff
0.0690  (1.68)
LUAU
0.0942  (2.29)
QUICKJS
0.1310  (3.19)
QUIRREL
0.3340  (8.13)
SQUIRREL3
0.3360  (8.17)
-

queen

- - - - - - - -
DAS INTERPRETER
0.0013  (1.01)
LUA
0.0020  (1.60)
LUAJIT -joff
LUAU
0.0018  (1.40)
QUIRREL
0.0040  (3.19)
SQUIRREL3
0.0040  (3.19)
-

sha256

- - - - -
DAS INTERPRETER
LUAJIT -joff
0.2541  (2.10)
LUAU
0.7353  (6.06)
-

sort

- - - - - - - -
DAS INTERPRETER
0.0320  (1.33)
LUA
0.0920  (3.83)
LUAJIT -joff
0.0701  (2.92)
LUAU
0.0531  (2.21)
QUIRREL
SQUIRREL3
0.0280  (1.17)
-

spectral norm

- - - - - - - - -
DAS INTERPRETER
LUA
0.5930  (2.90)
LUAJIT -joff
0.2894  (1.42)
LUAU
0.2391  (1.17)
QUICKJS
0.8880  (4.35)
QUIRREL
1.4390  (7.05)
SQUIRREL3
1.4800  (7.25)
-

string2float

- - - - - - - - -
DAS INTERPRETER
LUA
0.2040  (7.00)
LUAJIT -joff
0.1362  (4.67)
LUAU
0.1271  (4.36)
QUICKJS
0.4590  (15.75)
QUIRREL
0.3060  (10.50)
SQUIRREL3
0.4290  (14.72)
-

tree

- - - - - -
DAS INTERPRETER
1.7543  (1.17)
LUA
2.9080  (1.94)
LUAJIT -joff
LUAU
1.7688  (1.18)
- - -

- As a compiled Ahead-of-Time language, Daslang is significantly faster than both LuaJIT and Chrome JS, both of which have extremely fast Just-in-Time compilers. -

-

- In terms of performance, Daslang is nearly on par with naive C++ (compiled with clang-cl llvm 8.0.1 with all optimizations), typically differing by no more than 10%. In some cases, Daslang can even outperform C++. -

-

- Daslang Just-in-Time compilation is based on LLVM backend, and often outperforms C++, due to Daslang providing more information to LLVM than AOT. -

-

- It is worth noting that unlike Just-in-Time compilation, Ahead-of-Time compilation can be used on any platform, including those with signed binary executables like iOS or consoles. -

- - Tested on AMD Ryzen Threadripper 3990X 64-Core Processor at Mon Sep 30 15:32:40 2024
- CLANG 17.0.6
- -

AOT or JIT

-

dictionary

- - - - - - - - -
.NET
0.0795  (7.87)
C++
0.0439  (4.34)
DAS AOT
DAS JIT
0.0105  (1.03)
LUAJIT
0.0121  (1.20)
LUAU --codegen
0.0233  (2.31)
MONO
0.0766  (7.58)
-

exp loop

- - - - - - - - -
.NET
0.0060  (1.63)
C++
DAS AOT
0.0062  (1.68)
DAS JIT
LUAJIT
0.0179  (4.85)
LUAU --codegen
0.0085  (2.30)
MONO
0.0100  (2.72)
-

fibonacci loop

- - - - - - - - -
.NET
0.0030  (1.89)
C++
DAS AOT
DAS JIT
0.0019  (1.21)
LUAJIT
0.0048  (3.02)
LUAU --codegen
0.0328  (20.72)
MONO
0.0030  (1.89)
-

fibonacci recursive

- - - - - - - - -
.NET
0.0063  (1.56)
C++
0.0043  (1.07)
DAS AOT
DAS JIT
0.0045  (1.11)
LUAJIT
0.0129  (3.17)
LUAU --codegen
0.0602  (14.83)
MONO
0.0085  (2.10)
-

float2string

- - - - - - - - -
.NET
0.2835  (4.22)
C++
0.3021  (4.49)
DAS AOT
DAS JIT
LUAJIT
0.1564  (2.33)
LUAU --codegen
0.0695  (1.03)
MONO
0.4566  (6.79)
-

mandelbrot

- - - - - - - - -
.NET
0.0020  (4.45)
C++
DAS AOT
0.0006  (1.40)
DAS JIT
0.0005  (1.02)
LUAJIT
0.0076  (16.92)
LUAU --codegen
0.0379  (84.48)
MONO
0.0040  (8.90)
-

n-bodies

- - - - - - - - -
.NET
0.0390  (1.89)
C++
0.0270  (1.31)
DAS AOT
0.0274  (1.33)
DAS JIT
LUAJIT
0.0820  (3.97)
LUAU --codegen
0.2302  (11.13)
MONO
0.0805  (3.89)
-

native loop

- - - - - -
.NET
0.1615  (13.20)
DAS AOT
DAS JIT
LUAJIT
-

particles kinematics

- - - - - - - - -
.NET
0.0069  (2.32)
C++
0.0035  (1.17)
DAS AOT
DAS JIT
LUAJIT
0.1997  (67.00)
LUAU --codegen
0.2144  (71.96)
MONO
0.0915  (30.71)
-

primes loop

- - - - - - - - -
.NET
0.0380  (2.82)
C++
0.0386  (2.87)
DAS AOT
DAS JIT
LUAJIT
0.0137  (1.01)
LUAU --codegen
0.0350  (2.60)
MONO
0.0380  (2.82)
-

queen

- - - - - - - -
.NET
0.0050  (43.86)
C++
0.0001  (1.03)
DAS AOT
0.0001  (1.25)
DAS JIT
LUAJIT
0.0004  (3.87)
LUAU --codegen
0.0009  (7.69)
-

sha256

- - - - - - -
.NET
0.0080  (1.64)
DAS AOT
0.0052  (1.06)
DAS JIT
LUAJIT
0.0339  (6.94)
LUAU --codegen
0.5664  (115.84)
-

sort

- - - - - - - - -
.NET
0.0130  (2.37)
C++
0.0057  (1.04)
DAS AOT
DAS JIT
0.0096  (1.74)
LUAJIT
0.0730  (13.28)
LUAU --codegen
0.0478  (8.69)
MONO
0.0100  (1.82)
-

spectral norm

- - - - - - - - -
.NET
C++
0.0123  (1.03)
DAS AOT
0.0123  (1.03)
DAS JIT
0.0124  (1.03)
LUAJIT
0.0147  (1.22)
LUAU --codegen
0.0574  (4.78)
MONO
0.0260  (2.17)
-

string2float

- - - - - - - - -
.NET
0.1065  (4.41)
C++
0.1015  (4.20)
DAS AOT
DAS JIT
LUAJIT
0.1173  (4.86)
LUAU --codegen
0.1158  (4.80)
MONO
0.1853  (7.68)
-

tree

- - - - - - - - -
.NET
0.2465  (1.41)
C++
DAS AOT
0.1777  (1.02)
DAS JIT
0.1776  (1.02)
LUAJIT
0.9647  (5.52)
LUAU --codegen
1.3965  (7.99)
MONO
0.2590  (1.48)
- -

All samples for all languages in comparison are available in - GitHub -

-
- -
-

Change List

- -

0.6.2 (May 2026)

- -

dasSQLITE: Typed SQLite Query Layer

-

modules/dasSQLITE extends daslib/linq with SQL-backed queries — familiar linq-style transforms compile down to typed SQLite operations. After the initial landing, the layer was broadened tutorial-by-tutorial across the rest of the SQLite surface.

-
    -
  • Insert + query macros — typed insert flows, raw query / try_query helpers, deeper _sql(...) analysis with multi-quantifier (multi-Q) lowering, and a parity audit comparing in-memory linq vs. SQL emission
  • -
  • Read-side operators — distinct, take, skip, ordering, aggregates, grouping, the full join family (_left_join, _inner_join, _right_join, _full_outer_join, _cross_join), set operations, multi-source queries, and _in / _not_in against captured collections via SQLite json_each
  • -
  • Write-side — typed update/delete flows, transaction helpers, UPSERT with schema annotations for foreign keys, indexes, defaults, and computed columns, plus user-defined SQL functions via [sql_function] (auto-registered and visible inside _sql chains)
  • -
  • Custom storage types — user-defined adapters plus @sql_json and @sql_blob field support, JSON-path descent inside _sql, column metadata introspection
  • -
  • Schema introspection and migrations — [sql_table(schema_from=...)] + check_schema validate live database schema against the daslang struct at compile time; daslib/sqlite_migrate ships versioned [sql_migration] blocks with typed ALTER macros (add_column / drop_column / rename_column) and full-table rebuilds via [struct_convert] + [sql_table(legacy=true)] for non-trivial changes
  • -
  • Operational — SQL fragment building, ATTACH DATABASE, FTS5 ([sql_fts5] + text_match), pre-migration utilities
  • -
- -

Style Lint and Unified Linting

-

Compile-time linting now covers daslang style alongside the existing correctness and performance passes.

-
    -
  • daslib/style_lint — flags non-idiomatic gen2 patterns: unnecessary block pipes, declaration-then-assignment, array literal construction via repeated push, long comment blocks
  • -
  • Unified lint flow — shared warning collection, nolint suppression, comment-hygiene rules, and a codebase-wide cleanup that drove warnings back to zero
  • -
  • Noise reduction — one-line style enforcement was relaxed where it produced more friction than signal
  • -
  • Nine new perf/style rules — PERF013–017 and STYLE016–019 extend the rule set with additional anti-patterns surfaced during the codebase sweep
  • -
- -

Duplicate Detection: detect-dupe and find-dupe

-
    -
  • utils/detect-dupe — scans files, canonicalizes functions, and clusters exact and fuzzy duplicates with corpus export/import
  • -
  • Pattern filtering — suppresses boilerplate-heavy false positives and handles generics more cleanly
  • -
  • AI-assisted triage — utils/find-dupe consumes detect-dupe reports and asks Claude to partition clusters into real duplicates vs false positives, with JSON and Markdown output
  • -
  • MCP integration — export, duplicate detection, and judgment workflows exposed to AI tooling
  • -
- -

daslib/clargs: Declarative CLI Parsing

-
    -
  • Generated --flag and -f aliases, help text, required-flag handling, and array/bool parsing
  • -
  • get_user_args() handles both interpreter -- invocation and standalone executables correctly
  • -
  • In-tree tools migrated off hand-rolled get_command_line_arguments() onto a shared parser
  • -
- -

Memory Leak Tracking and Diagnostics

-
    -
  • Leak-tracker mode — --das-profiler --das-profiler-leaks records live allocations with captured daslang call stacks
  • -
  • Cheat-sheet documentation — unified guidance for GC leaks, heap reports, smart-pointer tracking, jobque leaks, and handle-registry dumps
  • -
  • Profiler and heap tooling polish supporting leak-audit work across the runtime
  • -
- -

daspkg: Project Bundling for Distribution

-

daspkg release produces a redistributable bundle of a daslang project — the standalone exe, every transitively-required .shared_module dylib, every asset matching the project's release globs, and every transitive dep package's release assets. The bundle runs on a machine with no daslang installed.

-
    -
  • release() hook in .das_package — declares release_main, release_name, release_include / release_exclude globs, and force-included shared modules
  • -
  • Exe-relative shared-module resolution — daslang -exe resolves shared modules in three tiers (exe directory → das_root → absolute path), making relocated bundles portable
  • -
  • -list-shared-modules — auxiliary flag walks program_for_each_module and writes a JSON manifest of every dylib the program touches; daspkg release consumes it for auto-detection
  • -
  • Project-local + relocated-bundle support — get_this_module_dir, the JIT runtime, and the daspkg resolver all honor project-local layouts
  • -
  • Native shared deps — packages can ship platform-specific dylibs alongside their .das sources via release_include_dll; dasPUGIXML enabled by default for Info.plist generation on macOS
  • -
- -

daStrudel: Second Wave

-
    -
  • Better synth and drum voices, more SF2 support, offline/WAV rendering workflows, further live and demo polish
  • -
  • Unique per-event HRTF binaural 3D positioning
  • -
  • Reduced memory usage and multiple leak fixes in the threaded player, visualizer, and shutdown path
  • -
  • Expanded documentation and examples for the newer audio stack
  • -
- -

GC / AST / Ownership Cleanup

-

Core compiler internals continued the transition away from older smart_ptr-style AST ownership. The biggest user-facing result: macros became much easier to write.

-
    -
  • TypeDecl, Expression, annotations, and related AST paths moved deeper into gc_node-style ownership
  • -
  • Macro, visitor, binding, and validator code no longer fights smart_ptr-era ownership patterns and pointer conversions
  • -
  • Tracker stability improved when GC is active, reducing false crashes during debugging
  • -
- -

Compiler, AOT, and JIT

-
    -
  • Continued push toward more daslang-authored AOT logic and cleaner AOT/JIT boundaries
  • -
  • Better standalone-exe behavior, transitive JIT type resolution, prologue sizing, function registration
  • -
  • More build configurations (RelWithDebInfo, static PIC), more daslib AOT coverage, platform-specific AOT fixes
  • -
  • Tuple-strict typer — tuple field names are now part of the type, so tuple<a:int;b:int> and tuple<x:int;y:int> no longer collide and unnamed tuples remain distinct from named ones
  • -
  • Option<T> / Result<T,E> via [template_tuple] — both types are now structural named-tuples generated through a generic mechanism, replacing bespoke handling
  • -
  • Call-site block arrow bodydef f(...) : T => expr shorthand parses alongside the existing block form
  • -
  • Error reporting audit — diagnostics retagged for consistency, with Program::deduplicateErrors suppressing repeated identical errors from the same site
  • -
  • super walks past empty intermediatessuper now skips empty intermediate base classes when looking for a method
  • -
  • Block-form global variable annotations@field annotations now propagate to globals declared inside variable { ... } blocks
  • -
  • cbind prefixes — generated bindings can be namespaced with a per-include prefix
  • -
  • Compiler refactorProgram and Context extracted from simulate / ast into their own translation units; serialization unified with eden / daNetGame
  • -
- -

Runtime Libraries and Infrastructure

-
    -
  • dasPEG — more tests, docs, CI coverage, and standalone/LLVM fixes
  • -
  • Core libraries — validated numeric conversions in daslib/strings_convert, continued Option/Result work including non-copyable support, C API completeness; daslib/clargs now returns Result from parsing for cleaner error handling
  • -
  • das_hash_map — new in-tree hash map backend that avoids empty-construction allocations and behaves better in thread-local usage
  • -
  • Path-aware glob in daslib/fio — match_glob, glob, glob_filtered, plus expand_glob / parse_file_list promoted from the MCP utility into the stdlib
  • -
  • Math: common numeric functions — sinh, cosh, tanh, asinh, acosh, atanh, log10, log1p, expm1, cbrt, trunc, hypot, fmod, remainder added to the math module
  • -
  • das-fmt vendored in-tree — utils/das-fmt/ now holds a local copy of the formatter; CI runs against it instead of an external clone
  • -
  • Filesystem guidance, handle-registry tutorial work, class-boost coverage, integer-returning main() for tools, compilation progress reporting
  • -
- -

Build, CI, and Web

-
    -
  • Tutorials build in CI — every tutorial now compiles in CI as part of the regular check run
  • -
  • Web target reuses the main CMakeLists — daslang-web picks up tests and tracks the same build configuration as the native targets, removing the duplicated CMake graph
  • -
  • MCP: parse-aware C++ source-search tools — cpp_find_symbol, cpp_grep_usage, cpp_outline, cpp_goto_definition join the existing daslang-side tools, with a configurable search root and git-signature staleness detection
  • -
  • MCP: auto-retry unqualified module names under daslib/ — when a tool call references e.g. fio and the symbol can't be found, the server retries against daslib/fio before reporting a miss
  • -
  • daslib/blind_mouse: personal Q&A cache MCP server — mouse__ask / mouse__add keep a curated .md answer cache for "how do I X?" / "what's the pattern for Y?" questions across sessions
  • -
  • @live extends to struct fields — live_host annotates individual struct fields with @live, with per-field init_hash combining f.init and g.init so partial-struct reloads stay coherent
  • -
- -

Bug Fixes

-
    -
  • Build, tooling, CI — package .gitignore handling, PEG standalone/LLVM, require fixes, dasbind fixes, glob dependency tracking, doc/error-position corrections, daslang plugin completion no longer silently bails when an external binding (e.g. OpenGL, GLSL preprocessor) is in scope, and find_call_macro null-deref fix with macro_call regression coverage
  • -
  • Runtime/compiler correctness — JIT global-function arguments, handled-type property write propagation, function-lookup cache pointer reuse, runWithCatch state cleanup, ASAN follow-up, fix for the standalone-exe shutdown crash, and fix for top-level let-init now correctly registering builtins
  • -
  • Fusion engine — TSan-safe v_ldu via DAS_LDU_WORKHORSE, call1 / call2 loadSize stamped from fnPtr->debugInfo, and a TSan suppression for the over-read in fusion call/return shells
  • -
  • Language edge cases — finally loop rework, clearer inference failure on bad calls, GCC reference shadow fixes, strict-weak-ordering cleanup
  • -
  • Graphics and platform — OpenGL/package integration cleanup after the daspkg migration
  • -
  • Architecture-specific — skipped #pragma float_control on __e2k__ to silence warnings on the Elbrus toolchain
  • -
- -

Examples

-
    -
  • Asteroids — full asteroid-shooter game with waves, audio, and powerups; later migrated to DECS for the entity model with a polish pass on visuals and powerup VFX
  • -
  • Pacman — classic arcade game added to the examples/ lineup
  • -
  • River Run — top-down river shooter built across three rounds: initial gameplay (river splits, combat, VFX, audio, HUD, collisions), then scenery / bonus / engine sound / tuning, and finally shadows, pickup burst, rich music, 3D HUD, and a juice pass
  • -
- -

0.6.1 (April 2026)

- -

VSCode Extension: dastest Integration

-

Run tests and benchmarks directly from the editor — inline buttons, Test Explorer panel, per-test pass/fail feedback without leaving the IDE.

-
    -
  • Inline run buttons for individual tests and benchmarks
  • -
  • Test Explorer integration with per-test results
  • -
  • JIT and isolated mode setting toggles (read fresh each run, no reload needed)
  • -
  • Live stdout/stderr streaming during tests (previously silent until completion)
  • -
  • Cross-platform compiler auto-detection
  • -
  • Better error messages (compiler not found vs dastest not found)
  • -
  • dastest isolated mode fixes (forward --bench, full per-test JSON results)
  • -
- -

daspkg: Package Manager

-
    -
  • Full-featured package manager built entirely in daslang
  • -
  • Commands: install (local/git/index), update, upgrade, remove, build, check, list, doctor, introduce, withdraw
  • -
  • .das_package manifests — executable daslang scripts (not static JSON) with SDK-aware build logic, conditional steps, programmatic version checks
  • -
  • Dependency resolution — transitive deps, semver constraints (>=1.0,<2.0), version/branch pinning, cycle detection, update rollback
  • -
  • Global install — --global installs to das_root/modules/, shared across projects; auto-use compatible global versions; shadow detection
  • -
  • Native module builds — CMake-based automatic compilation with doctor for toolchain validation
  • -
  • Ecosystem migration — dasImgui, dasImguiNodeEditor, dasMinfft, dasTelegram extracted from submodules to standalone daspkg packages
  • -
  • 4 example packages: ImGui, node-editor, minfft, Claude API Telegram bot
  • -
  • 151 unit tests + 70 integration tests
  • -
- -

daslang-live: Live-Reloading Application Host

-
    -
  • New daslang-live.exe — compile/init/update/shutdown lifecycle, automatic GC, hot reload on file changes
  • -
  • 6 live modules (live_commands, live_api, live_watch, decs_live, glfw_live, opengl_live)
  • -
  • @live macro replaces ~60 lines of manual reload boilerplate
  • -
  • REST API with help endpoint, error guard (503 on compile/exception errors)
  • -
  • Exception recovery — clears corrupted persistent store, recovers on reload
  • -
  • Single-instance enforcement, dependency file watching, -cwd flag
  • -
  • 9 examples including Arcanoid (full 3D Breakout) and Sequence (board game with 3 AI bots)
  • -
- -

AST Pattern Matching: daslib/ast_match

-
    -
  • Pattern matching on compiled AST using qmacro tags in reverse ($e, $v, $i, $t, $c, $f, $b, $a)
  • -
  • Three macros: qmatch, qmatch_block, qmatch_function
  • -
  • Wildcards, comprehension matching, block/lambda/local-function discrimination
  • -
  • 305 tests, full language reference documentation
  • -
- -

Compiler-Free Standalone Executables

-
    -
  • Builtin .das moved to daslib/ — all built-in .das wrappers eliminated from compileBuiltinModule
  • -
  • Standalone exes no longer need the compiler — libDaScript can be split into runtime-only and compiler libraries
  • -
  • Minimal runtime — possible to run daScript contexts with only C++-bound modules, no .das parsing at all
  • -
  • ~10ms faster compilation on static builds (35ms → 25ms for hello world), ~7ms on dynamic
  • -
- -

Performance Lint: daslib/perf_lint

-
    -
  • 9 rules: PERF001–PERF009 (string concat in loop, character_at usage, push without reserve, unnecessary conversions, redundant move-init)
  • -
  • Expression chain walking, closure-aware, inferStack reporting
  • -
  • Auto-lint via require daslib/perf_lint or standalone batch tool
  • -
- -

More New Features

-
    -
  • Pointer safe-at — a?[index] for pointer types, returns null if pointer is null (interpreter, AOT, JIT)
  • -
  • C API type introspection — ~40 new C functions to query struct layouts, enum values, function signatures
  • -
  • New builtins — get_module_file_name, get_key (O(1) table value-to-key), first_character, with_das_string, system(), daslib/command_line
  • -
- -

daStrudel: Pattern Music Engine

-
    -
  • SF2 SoundFont support — load General MIDI soundfonts, 128 GM presets, per-voice reverb/chorus sends, exclusive class/cut groups, note-off scheduling
  • -
  • Two-mode player — main-thread mode (strudel_tick from render loop, direct pianoroll/PCM access) and threaded mode (headless audio, channel-based commands)
  • -
  • Clean API — no StrudelState pointer passing, functions operate on per-context globals, strudel_add_track returns integer index
  • -
  • Visualizer combinators — pianoroll(), spectrum(), scope(), vectorscope(), drums() as pass-through pattern pipes with rolling index
  • -
  • MIDI player — load and play .mid files through SF2, per-channel gain/pan, looping, audio visualization channels
  • -
  • Accurate timing — integer sample accumulation (no float drift), consumed_position hardware clock for visualization sync
  • -
  • 6 demos: player, synth (100% generated), SF2 chill beat, piano (Ode to Joy), live-reload, OpenGL visualizer with 5-track pianoroll/spectrum/drums
  • -
- -

Performance

-
    -
  • Faster CondFolding — optimizer handles multiple transforms per pass
  • -
  • Bulk DECS creation — create_entities ~10x faster (174ns vs 1772ns for 1000 entities)
  • -
  • Optimized character_at — scans to index instead of full strlen
  • -
- -

MCP Server (AI Tools)

-
    -
  • 7 new live tools for controlling daslang-live from AI assistants
  • -
  • lint tool runs both paranoid lint and perf_lint in one pass
  • -
  • shutdown tool, failures_only param for run_test
  • -
  • Fix ast-grep venv detection, standalone exe subprocess hang, temp file collisions
  • -
- -

Bug Fixes

-
    -
  • AOT das_null_coalescing wrong type source
  • -
  • ASAN support on MSVC (missing __declspec, libhv flag forwarding)
  • -
  • ModuleGroup use-after-free in daslang-live
  • -
  • Fuzzer-found crashes (clone/ascend, type-macro size queries)
  • -
  • Fusion ASAN false positives
  • -
  • Cross-platform path hardcoding in tests
  • -
  • JIT standalone exe fixes, JIT node coverage
  • -
  • Array/table lock access flags
  • -
  • Handled const string type emission
  • -
- -

File I/O

-
    -
  • Explicit error reporting — Go-style string error out-params on 16 filesystem functions
  • -
  • Fixed stat/fstat side effects annotation
  • -
- -

stb Libraries

-
    -
  • stbtt glyph shape API exposed
  • -
  • Two-layer safe stb_truetype API (70 tests, zero unsafe in high-level Font methods)
  • -
- -

Documentation & CI

-
    -
  • Full daslang-live RST documentation
  • -
  • CMake install overhaul (complete examples, modules, tutorials, utilities)
  • -
  • Updated CLI help for all command-line switches
  • -
  • Custom CI runners (Linux/Windows fat, macOS xlarge)
  • -
  • Extended CI checks, correct timeouts
  • -
  • Codebase-wide lint sweep (daslib, modules, examples, tutorials)
  • -
- -
- -
-

Documentation

- -

Daslang 0.6

-

- Online - Daslang 0.6 reference manual and Standard Libraries manual -

-

- Offline Reference Manual (PDF)
- Offline Standard Libraries Manual (PDF) -

-
- -
-

Try it online!

- -

- Try it in your browser - implemented with Emscripten, runs in your browser. -
- Try it online - implemented with tio.run fork. -

-
- -
-

Downloads and links

+ + + +
+
+ +
+
▮ a high-performance programming language
+

+ Not a script.
+ A language. +

+

+ Daslang is a strong, statically-typed programming language with + generic programming, iterative type inference, and three + execution tiers — interpreter, AOT to C++, JIT via LLVM. Embed + it in your C++ engine, or build a standalone application + end-to-end. +

+ +
+
+
interp · AOT · JIT
+
three execution tiers, one source
+
+
+
9
+
platforms incl. PS5 & Switch
+
+
+
embed · standalone
+
two ways to ship
+
+
+
+ + +
+
+
+ + + +
+
hello.das
+ + +
+
+ + + +
+
+
+
$ daslang run hello.das
+
hello, world
+
tick 0 · tick 1 · tick 2
+
✓ 0.42 ms · interpreted
+
+
+
+
+ + +
+
+ +
+
+

The fastest interpreter, and a JIT that often beats C++.

+

+ Daslang's interpreter wins 12 of 16 cross-language + benchmarks — against LuaJIT (no-JIT), Luau, Lua, + Quirrel, QuickJS, and Mono interpreter. The AOT compiler + to C++ typically matches native C++; the LLVM JIT + regularly beats it. Same source, same semantics across + all three tiers — pick the one that fits your deployment. + Lower numbers are faster. +

+
+
· captured on Apple M1 Max · daslang 0.6.2
+
· normalised to fastest entry in the row
+
· source: github.com/borisbat/dasProfile
+
+
+
+
+
+ + +
+ +
+
+ +
+
+ loading benchmark data… +
+
+
+
+
+ + +
+
+ +

+ A real language. Embeddable when you need it. Standalone when you don't. +

+
+
+
01 · static
+
Strong static typing
+
Iterative type inference, generics with compile-time reflection. Errors before they ship.
+
+
+
02 · aot
+
AOT, JIT & interpreter
+
Same source, three execution tiers. AOT to C++ for consoles where JIT is forbidden.
+
+
+
03 · interop
+
Zero-friction C++ interop
+
Bind a function in one line. Calls cross the boundary an order of magnitude faster than peers.
+
+
+
04 · modes
+
Embedded or standalone
+
Drop it into a C++ host as a scripting layer, or compile a standalone Daslang binary end-to-end. Same language, same toolchain.
+
+
+
05 · macros
+
Compile-time macros
+
Reshape syntax to match the domain. The language reflects the problem, not the other way around.
+
+
+
06 · safety
+
Safe by default
+
Performance counts. But not at the cost of safety — unless you explicitly opt out for the hot path.
+
+
+
+
+ + +
+
+ +
+

Ships everywhere C++ ships — as a binary, or built into your engine.

+
+
Windows
x86 · x64
+
Linux
x64 · GCC · LLVM
+
macOS
arm64 · x64
+
iOS
AOT · signed
+
Android
arm64 · x64
+
WebAssembly
browser embed
+
PlayStation 4 / 5
AOT
+
Xbox One / Series X
AOT
+
Nintendo Switch
AOT
+
+
+
+
+ + +
+
+ +
+
+

Grab a release. Or build from source.

+

+ Daslang is early — packaged installers (brew, apt, scoop) are on + the roadmap. For now: pre-built binaries on GitHub, or a CMake + build straight from the repo. AOT compiles to a single C++ stub + you link with your host. +

+ github.com/GaijinEntertainment/daScript → +
+
+
+ + +
+
+
+
# 1 — grab a pre-built binary
+
$ open https://github.com/GaijinEntertainment/daScript/releases
+
# pick the archive for your platform, unzip, add to PATH
+
+
# 2 — verify it runs
+
$ daslang --version
+
$ daslang run hello.das
+
+
# 3 — (optional) AOT to C++ for native speed
+
$ daslang -aot main.das main_aot.cpp
+
+
+
# requires CMake ≥ 3.15 and a C++17 compiler
+
$ git clone https://github.com/GaijinEntertainment/daScript.git daslang
+
$ cd daslang && git submodule update --init --recursive
+
+
$ cmake -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo
+
$ cmake --build build --target daslang --config RelWithDebInfo
+
+
# the binary lands in ./bin/daslang
+
$ ./bin/daslang run hello.das
+
+
+
+
+
+
+ + +
+
+ +
+
+

Recent activity.

+

The latest from the daslang team — releases, tooling, and ecosystem.

+ full change list → +
+
+ +
+
2026-05-13
+
site
+
Redesigned daslang.io live — integrated playground, dasProfile bench, blog migration.
+
+
+
2026-05-12
+
0.6.2
+
JIT allocator memory effects landed — 6b9f27aa4.
+
+
+
+
+
+ + + -

Latest release

-

- Daslang latest release 0.6.1 can be found on GitHub. -

-

GitHub Repository

-

- Daslang's GitHub repository is - here -

-

Authors blog

-

- Boris Batkin blog on Daslang (in English) -

-

Other useful links

-

- Language server plugin for VSCode with auto-completion, help, etc - itself written in Daslang.
- dasBox - simple framework allowing create games in Daslang.
- Edenspark - Open Source AI-assisted platform for making games on PC and consoles.
- Article on history of Daslang.
- Spiiin's blog on Daslang (in Russian)
-

-
-
+ - + + + + + + + - diff --git a/site/playground/forge-skin.css b/site/playground/forge-skin.css new file mode 100644 index 0000000000..f2c4809615 --- /dev/null +++ b/site/playground/forge-skin.css @@ -0,0 +1,112 @@ +/* ──────────────────────────────────────────────────────────────── + * Forge skin for /playground/ — overrides web/ui/src/main.css to + * land the playground inside the Forge visual language without + * forking web/ui source. Loaded AFTER main.css in playground/index.html. + * + * Tokens come from ../files/forge.css (shared :root). + * ──────────────────────────────────────────────────────────────── */ + +body { + background: var(--bg) !important; + color: var(--fg) !important; + font-family: var(--font-sans) !important; + font-size: 14px !important; + margin: 0 !important; +} + +/* The IDE keeps its own top bar (`.header`) below our Forge nav. */ +.header { + background: var(--bg-2) !important; + border-bottom: 1px solid var(--rule) !important; + color: var(--fg-dim); + font-family: var(--font-mono); + font-size: 13px !important; + height: 40px !important; + padding: 4px 32px !important; +} +.header img { display: none; } +.header_part { margin: 0 14px !important; } +.header_part a { color: var(--fg-dim) !important; text-decoration: none; } +.header_part a:hover { color: var(--amber) !important; } +.header_part b { color: var(--fg); font-family: var(--font-mono); } + +/* Main 2-column workspace */ +.main { background: var(--bg) !important; color: var(--fg) !important; } +.main_header { + padding: 16px 32px !important; + border-bottom: 1px solid var(--rule); + background: var(--bg); +} +.main_header_left, .main_header_right { + gap: 14px !important; + font-family: var(--font-mono); + font-size: 12.5px; +} +.main_name { + font-family: var(--font-mono) !important; + font-size: 14px !important; + color: var(--fg) !important; +} + +.button_header { + background: var(--amber) !important; + color: var(--bg) !important; + border: none !important; + border-radius: 4px !important; + font-family: var(--font-mono) !important; + font-size: 13px !important; + font-weight: 600; + padding: 8px 16px !important; + height: 32px !important; + cursor: pointer; +} +.button_header:hover { background: #f4b04a !important; } +.button_header:disabled { opacity: 0.5; cursor: default; } + +.select_header { + background: var(--bg-2) !important; + color: var(--fg) !important; + border: 1px solid var(--rule) !important; + border-radius: 4px !important; + font-family: var(--font-mono) !important; + font-size: 12.5px !important; + padding: 6px 10px !important; + height: 32px !important; +} + +.main_col { padding: 16px 0 !important; } +.main_col_part { + padding: 0 24px !important; + max-width: 48vw; +} + +#code, #output { + height: 78vh !important; + margin-top: 14px !important; + border: 1px solid var(--rule) !important; + border-radius: 8px; + background: var(--bg-2) !important; + overflow: hidden; +} +#code { padding: 0; } +#output { + background: var(--bg-2) !important; + overflow-y: scroll !important; + padding: 12px 16px; + font-family: var(--font-mono); + font-size: 13px; +} + +.output_line { + font-family: var(--font-mono) !important; + line-height: 1.55; + padding: 1px 0; + /* web/ui's printOutput sets inline background-color per line — neutralize it + * so the dark panel shows through. Color is restored on .output_line_text. */ + background: transparent !important; +} +.output_line_time { color: var(--fg-faint) !important; width: 100px !important; font-size: 11.5px; } +.output_line_text { color: var(--fg) !important; background: transparent !important; } + +footer { color: var(--fg-faint) !important; padding: 24px 32px; background: var(--bg); border-top: 1px solid var(--rule); } +.footer_p { font-size: 12px !important; font-family: var(--font-mono); } diff --git a/site/playground/index.html b/site/playground/index.html new file mode 100644 index 0000000000..7449037bba --- /dev/null +++ b/site/playground/index.html @@ -0,0 +1,103 @@ + + + + + + Playground — Daslang + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
main.das
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
output
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+ + + diff --git a/site/playground/playground-init.js b/site/playground/playground-init.js new file mode 100644 index 0000000000..8335aeee6b --- /dev/null +++ b/site/playground/playground-init.js @@ -0,0 +1,39 @@ +// Overlay for web/ui's CodeMirror init: switch to the daslang mode + forge theme, +// and accept code shared from the landing-page hero via `#code=`. +// Loaded BEFORE main.js, which calls CodeMirror() and exposes `window.code`. + +window.FORGE_PLAYGROUND_OPTS = { + lineNumbers: true, + matchBrackets: true, + indentWithTabs: false, + styleActiveLine: true, + theme: 'forge', + mode: 'daslang', + tabSize: 4, + indentUnit: 4, + highlightSelectionMatches: { showToken: /\w/ }, +}; + +// `#code=` — the hero's "↗ playground" button sets this +// when opening the playground. We poll for the CM instance (created later by +// main.js) and inject the source once it shows up. +function applySharedCodeFromHash() { + if (!window.location.hash || !window.location.hash.startsWith('#code=')) return; + let src; + try { src = decodeURIComponent(window.location.hash.slice(6)); } + catch (e) { return; } + const deadline = Date.now() + 5000; + (function tryApply() { + if (window.code && typeof window.code.setValue === 'function') { + window.code.setValue(src); + return; + } + if (Date.now() < deadline) setTimeout(tryApply, 50); + })(); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', applySharedCodeFromHash); +} else { + applySharedCodeFromHash(); +} diff --git a/tools/seed_news.py b/tools/seed_news.py new file mode 100644 index 0000000000..961fae1084 --- /dev/null +++ b/tools/seed_news.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +Seed `site/_news/.md` files from the legacy `page-news` div in the +pre-redesign `site/index.html`. One-time tool — re-runnable but should only +be needed once during the website-forge migration. + +Usage: + python3 tools/seed_news.py + +Defaults: + old_index.html = /tmp/old_site_index.html + out_dir = site/_news +""" + +import re +import sys +from html import unescape +from pathlib import Path + +MONTHS = { + 'jan': 1, 'january': 1, + 'feb': 2, 'february': 2, + 'mar': 3, 'march': 3, + 'apr': 4, 'april': 4, + 'may': 5, + 'jun': 6, 'june': 6, + 'jul': 7, 'july': 7, + 'aug': 8, 'august': 8, + 'sep': 9, 'sept': 9, 'september': 9, + 'oct': 10, 'october': 10, + 'nov': 11, 'november': 11, + 'dec': 12, 'december': 12, +} + +# Examples this needs to parse: +# May 9th, 2026 / May 9, 2026 +# 1st Jan 2023 / 1 Jan 2023 +# 24 August 2020 +# November 18th, 2021 +DATE_PATTERNS = [ + # "May 9th, 2026" / "May 9, 2026" + re.compile(r'\b([A-Za-z]+)\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(\d{4})\b'), + # "9 May 2026" / "1st Jan 2023" + re.compile(r'\b(\d{1,2})(?:st|nd|rd|th)?\s+([A-Za-z]+)\s+(\d{4})\b'), +] + +def parse_date(text): + for pat in DATE_PATTERNS: + m = pat.search(text) + if not m: + continue + groups = m.groups() + # Identify which group is the month name + if groups[0].lower() in MONTHS: + month, day, year = groups[0], int(groups[1]), int(groups[2]) + elif groups[1].lower() in MONTHS: + day, month, year = int(groups[0]), groups[1], int(groups[2]) + else: + continue + month_num = MONTHS.get(month.lower()) + if not month_num: + continue + return f"{year:04d}-{month_num:02d}-{day:02d}" + return None + + +VERSION_RE = re.compile(r'\b(\d+\.\d+(?:\.\d+)?(?:-RC\d+)?)\b') + +def infer_tag(body, title): + """Pick a short tag — prefer a version mention, fall back to a generic.""" + for src in (body, title): + m = VERSION_RE.search(src) + if m: + return m.group(1).lower() + text = (body + ' ' + title).lower() + if 'strudel' in text: + return 'strudel' + if 'jit' in text: + return 'jit' + if 'site' in text or 'website' in text or 'documentation' in text: + return 'docs' + if 'blog' in text: + return 'blog' + return 'news' + + +SLUG_BAD = re.compile(r'[^a-z0-9]+') + +def slugify(date, title): + body = SLUG_BAD.sub('-', title.lower()).strip('-')[:40].strip('-') + return f"{date}-{body or 'entry'}" + + +HREF_RE = re.compile(r']*href="([^"]*)"[^>]*>', re.IGNORECASE) +TAG_RE = re.compile(r'<[^>]+>') +WS_RE = re.compile(r'\s+') + +def extract_link(html): + """First with an http(s) href, or any non-anchor (e.g. /doc/) link.""" + external = None + internal = None + for m in HREF_RE.finditer(html): + href = m.group(1) + if not href or href.startswith('#'): + continue + if href.startswith('http'): + if not external: + external = href + elif not internal: + internal = href + return external or internal + + +def strip_html(s): + s = unescape(s) + s = TAG_RE.sub('', s) + return WS_RE.sub(' ', s).strip() + + +# Entry start:
  • ...DATE: BODY
  • +ENTRY_RE = re.compile( + r'
  • \s*([^<]+)\s*:?\s*(.*?)
  • ', + re.DOTALL, +) + + +def seed(input_path, out_dir): + html = Path(input_path).read_text(encoding='utf-8') + # Limit to the page-news div + start = html.find('id="page-news"') + end = html.find('end page-news', start) + if start == -1: + sys.exit("page-news div not found") + section = html[start:end] if end != -1 else html[start:] + + out = Path(out_dir) + out.mkdir(parents=True, exist_ok=True) + + seeded = 0 + skipped = 0 + for m in ENTRY_RE.finditer(section): + bold_text, body_html = m.group(1), m.group(2) + date = parse_date(bold_text) + if not date: + skipped += 1 + print(f"skip (no date): {bold_text!r}", file=sys.stderr) + continue + link = extract_link(body_html) + title = strip_html(body_html) + # Trim trailing "See the Change List for details" cruft + title = re.sub(r'\s+See the Change List.*', '', title) + title = re.sub(r'\s+See plans .*', '', title) + # Strip leading punctuation + title = title.lstrip(' .;,') + if not title: + skipped += 1 + continue + tag = infer_tag(body_html + ' ' + title, '') + slug = slugify(date, title) + path = out / f"{slug}.md" + + fm_lines = [ + "---", + f"date: {date}", + f"tag: {tag}", + f"title: {title.replace(chr(10), ' ')}", + ] + if link: + fm_lines.append(f"link: {link}") + fm_lines.append("---") + path.write_text("\n".join(fm_lines) + "\n", encoding='utf-8') + seeded += 1 + + print(f"seeded {seeded} entries to {out}/ (skipped {skipped})") + + +if __name__ == "__main__": + src = sys.argv[1] if len(sys.argv) > 1 else "/tmp/old_site_index.html" + dst = sys.argv[2] if len(sys.argv) > 2 else "site/_news" + seed(src, dst) diff --git a/web/ui/src/main.js b/web/ui/src/main.js index 4ff702d419..cfa28daab3 100644 --- a/web/ui/src/main.js +++ b/web/ui/src/main.js @@ -22,7 +22,7 @@ pageInit = function () { - code = CodeMirror( editorCode, { + code = CodeMirror( editorCode, window.FORGE_PLAYGROUND_OPTS || { lineNumbers: true, matchBrackets: true, indentWithTabs: false, styleActiveLine: true, theme:'eclipse', mode:"application/javascript", tabSize: 4, indentUnit: 4, highlightSelectionMatches: {showToken: /\w/} From 76ed0120cbb9400ee9c886bfb2ca1f832ee56bcd Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 12:56:48 -0700 Subject: [PATCH 02/22] site: Playwright e2e scaffolding for /playground/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of the playground multi-file plan: lock the test harness shape before the real features land. No production code touched. - site/tests/playground/ — Playwright config + fixtures + one smoke spec asserting the page loads with the Forge-themed CodeMirror instance and the daslang mode is tokenizing source. - .github/workflows/playground-e2e.yml — PR + branch-push CI gate. Builds the site without WASM (skipping the 5-10 min Emscripten step), boots python -m http.server against _site/, runs the suite minus any spec tagged @wasm. Uploads playwright-report/ on failure. - site/.gitignore — tests/playground/{node_modules,playwright-report, test-results,blob-report}/ - .gitignore — .playwright-mcp/ scratch dir from the local MCP Subsequent phases will land their specs alongside their features (dropdowns, tabs, share button, hero handoff). --- .github/workflows/playground-e2e.yml | 96 +++++++++++++++++++ .gitignore | 1 + site/.gitignore | 6 ++ .../playground/existing-page-loads.spec.js | 20 ++++ site/tests/playground/fixtures.js | 22 +++++ site/tests/playground/package-lock.json | 72 ++++++++++++++ site/tests/playground/package.json | 14 +++ site/tests/playground/playwright.config.js | 25 +++++ 8 files changed, 256 insertions(+) create mode 100644 .github/workflows/playground-e2e.yml create mode 100644 site/tests/playground/existing-page-loads.spec.js create mode 100644 site/tests/playground/fixtures.js create mode 100644 site/tests/playground/package-lock.json create mode 100644 site/tests/playground/package.json create mode 100644 site/tests/playground/playwright.config.js diff --git a/.github/workflows/playground-e2e.yml b/.github/workflows/playground-e2e.yml new file mode 100644 index 0000000000..6850a776b5 --- /dev/null +++ b/.github/workflows/playground-e2e.yml @@ -0,0 +1,96 @@ +name: Playground e2e (no WASM) + +# Fast PR gate: builds the site without WASM (skips the 5-10 min Emscripten +# step) and runs the Playwright suite minus any specs tagged `@wasm`. The full +# WASM suite is reserved for the master-only workflow (forthcoming). + +on: + pull_request: + paths: + - 'site/**' + - 'web/ui/**' + - '.github/workflows/playground-e2e.yml' + push: + branches-ignore: [master] + paths: + - 'site/**' + - 'web/ui/**' + - '.github/workflows/playground-e2e.yml' + workflow_dispatch: + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: site/tests/playground/package-lock.json + + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: "Install Python build deps" + run: pip install markdown + + - name: "Stage _site (no WASM)" + run: | + set -eux + mkdir -p _site/playground + + # Static landing page + assets + cp site/index.html _site/ + cp site/downloads.html _site/ || true + cp -r site/files _site/files + + # Playground: vendor web/ui IDE (no WASM artifacts) + cp -r web/ui/src/* _site/playground/ + cp -r web/ui/samples _site/playground/samples 2>/dev/null || true + cp site/playground/index.html _site/playground/index.html + cp site/playground/forge-skin.css _site/playground/ + cp site/playground/playground-init.js _site/playground/ + + # Blog + news (template change validation) + python3 site/blog/build_blog.py \ + --posts site/blog/_posts \ + --news site/_news \ + --template site/blog/template.html \ + --out _site/ + + touch _site/.nojekyll + ls _site _site/playground + + - name: "Install Playwright" + working-directory: site/tests/playground + run: | + npm ci + npx playwright install --with-deps chromium + + - name: "Serve _site and run tests" + working-directory: site/tests/playground + run: | + set -eux + python3 -m http.server 8765 --directory "${GITHUB_WORKSPACE}/_site" & + SERVER_PID=$! + # Wait for server to accept connections. + for _ in $(seq 1 30); do + if curl -fs http://localhost:8765/playground/ > /dev/null; then break; fi + sleep 0.2 + done + BASE_URL=http://localhost:8765 npx playwright test --grep-invert '@wasm' + STATUS=$? + kill $SERVER_PID || true + exit $STATUS + + - name: "Upload Playwright report on failure" + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: site/tests/playground/playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index 5b88bbc77c..2fc659faa1 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ src/parser/ds_parser.output .mcp.json sgconfig.yml utils/mcp/mcp_server.log +.playwright-mcp/ # Emscripten SDK (local install) emsdk/ diff --git a/site/.gitignore b/site/.gitignore index 7366bba9a1..29ec0d0c35 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -28,3 +28,9 @@ files/wasm/ # dasProfile snapshot fetched fresh by pages.yml from # https://raw.githubusercontent.com/borisbat/dasProfile/main/profile_results.json files/profile_results.json + +# Playwright e2e suite local artifacts. +tests/playground/node_modules/ +tests/playground/playwright-report/ +tests/playground/test-results/ +tests/playground/blob-report/ diff --git a/site/tests/playground/existing-page-loads.spec.js b/site/tests/playground/existing-page-loads.spec.js new file mode 100644 index 0000000000..302592c228 --- /dev/null +++ b/site/tests/playground/existing-page-loads.spec.js @@ -0,0 +1,20 @@ +// Phase-1 smoke spec: prove the test harness wires up against /playground/. +// Subsequent specs replace this with the real feature coverage; this one +// stays as a regression guard for "the page literally renders." + +const { test, expect } = require('./fixtures.js'); + +test('playground renders with Forge-themed CodeMirror', async ({ playground }) => { + await expect(playground).toHaveTitle(/Playground.*Daslang/i); + + const cm = playground.locator('.CodeMirror').first(); + await expect(cm).toBeVisible(); + await expect(cm).toHaveClass(/cm-s-forge/); +}); + +test('daslang mode tokenizes the default sample', async ({ playground }) => { + // The default first example puts daslang source in the editor; once CM + // is up the line-mode emits .cm-keyword spans for `def`, `let`, etc. + const tokens = playground.locator('.CodeMirror-code .cm-keyword'); + await expect(tokens.first()).toBeVisible({ timeout: 10_000 }); +}); diff --git a/site/tests/playground/fixtures.js b/site/tests/playground/fixtures.js new file mode 100644 index 0000000000..233039ab68 --- /dev/null +++ b/site/tests/playground/fixtures.js @@ -0,0 +1,22 @@ +// Shared Playwright fixtures + helpers for /playground/ specs. +// `playground` fixture: navigates to /playground/ and waits for CodeMirror to +// fully initialize so subsequent assertions don't race the WASM script load. + +const base = require('@playwright/test'); + +const test = base.test.extend({ + playground: async ({ page }, use) => { + await page.goto('/playground/'); + await page.locator('.CodeMirror').waitFor({ state: 'visible' }); + await page.waitForFunction( + () => typeof window.code === 'object' && window.code !== null && typeof window.code.swapDoc === 'function', + null, + { timeout: 10_000 } + ); + await use(page); + }, +}); + +const { expect } = base; + +module.exports = { test, expect }; diff --git a/site/tests/playground/package-lock.json b/site/tests/playground/package-lock.json new file mode 100644 index 0000000000..0ab5ca759d --- /dev/null +++ b/site/tests/playground/package-lock.json @@ -0,0 +1,72 @@ +{ + "name": "daslang-playground-e2e", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "daslang-playground-e2e", + "devDependencies": { + "@playwright/test": "^1.50.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/site/tests/playground/package.json b/site/tests/playground/package.json new file mode 100644 index 0000000000..e2dfeb5de3 --- /dev/null +++ b/site/tests/playground/package.json @@ -0,0 +1,14 @@ +{ + "name": "daslang-playground-e2e", + "private": true, + "description": "End-to-end tests for /playground/ — driven by Playwright.", + "scripts": { + "test": "playwright test", + "test:headed": "playwright test --headed", + "test:wasm": "playwright test --grep @wasm", + "test:no-wasm": "playwright test --grep-invert @wasm" + }, + "devDependencies": { + "@playwright/test": "^1.50.0" + } +} diff --git a/site/tests/playground/playwright.config.js b/site/tests/playground/playwright.config.js new file mode 100644 index 0000000000..0b7b240001 --- /dev/null +++ b/site/tests/playground/playwright.config.js @@ -0,0 +1,25 @@ +// Playwright config for the /playground/ e2e suite. +// Local runs assume `python3 -m http.server 8765` is serving the built site +// (either `site/` directly during dev, or `_site/` after a CI build). + +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: '.', + testMatch: '*.spec.js', + timeout: 30_000, + expect: { timeout: 5_000 }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 2 : undefined, + reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list', + use: { + baseURL: process.env.BASE_URL || 'http://localhost:8765', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, + ], +}); From 40d3a6cfb3586d89e9e2229a423d28f61edcd139 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 12:59:06 -0700 Subject: [PATCH 03/22] playground: wire dropdowns + multi-file-ready selectSample MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2: the Examples + Tests s. - Fix the NaN check (`!Number.isNaN`) and validate the index range up front; bail cleanly on the "Select..." sentinel. - Refactor selectSample to load every entry of `files[]` in parallel (samples have always shipped a multi-file schema even though loaders only ever read files[0]). The bundle is handed to a new `loadSample` helper that surfaces main.das (or the first file) in the single CM instance and stashes the full bundle on window for phase 3's tab strip to pick up. - web/ui/src/main.js is the canonical source — site/playground/main.js is a gitignored CI re-sync, so the patch goes upstream. - site/tests/playground/dropdowns.spec.js asserts both dropdowns swap the editor buffer and the sentinel-reset behavior holds. --- site/playground/index.html | 4 +- site/tests/playground/dropdowns.spec.js | 59 +++++++++++++++++++++++++ web/ui/src/main.js | 39 +++++++++------- 3 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 site/tests/playground/dropdowns.spec.js diff --git a/site/playground/index.html b/site/playground/index.html index 7449037bba..6ca85f8bb3 100644 --- a/site/playground/index.html +++ b/site/playground/index.html @@ -62,10 +62,10 @@
    main.das
    - +
    - +
    diff --git a/site/tests/playground/dropdowns.spec.js b/site/tests/playground/dropdowns.spec.js new file mode 100644 index 0000000000..8b1c1a651b --- /dev/null +++ b/site/tests/playground/dropdowns.spec.js @@ -0,0 +1,59 @@ +// Examples + Tests
    diff --git a/site/playground/playground-init.js b/site/playground/playground-init.js index 8335aeee6b..3a8b829bba 100644 --- a/site/playground/playground-init.js +++ b/site/playground/playground-init.js @@ -14,18 +14,35 @@ window.FORGE_PLAYGROUND_OPTS = { highlightSelectionMatches: { showToken: /\w/ }, }; -// `#code=` — the hero's "↗ playground" button sets this -// when opening the playground. We poll for the CM instance (created later by -// main.js) and inject the source once it shows up. +// `#code=` — single-file share from the landing hero. +// `#z=` — multi-file share from the playground's ↗ share button. +// Both formats hand off to pgLoadFiles once the tab strip is mounted. function applySharedCodeFromHash() { - if (!window.location.hash || !window.location.hash.startsWith('#code=')) return; - let src; - try { src = decodeURIComponent(window.location.hash.slice(6)); } - catch (e) { return; } + const hash = window.location.hash || ''; + let payload = null; + if (hash.startsWith('#code=')) { + try { + const src = decodeURIComponent(hash.slice(6)); + payload = { files: { 'main.das': src }, active: 'main.das' }; + } catch (e) { return; } + } else if (hash.startsWith('#z=') && window.LZString) { + try { + const json = window.LZString.decompressFromEncodedURIComponent(hash.slice(3)); + const obj = JSON.parse(json); + if (obj && obj.files) payload = obj; + } catch (e) { return; } + } else { + return; + } + + // Stash bundle immediately so pgInit picks it up even if it polls in + // before we do. + window.__pendingSampleBundle = payload.files; const deadline = Date.now() + 5000; (function tryApply() { - if (window.code && typeof window.code.setValue === 'function') { - window.code.setValue(src); + if (typeof window.pgLoadFiles === 'function') { + window.__pendingSampleBundle = null; + window.pgLoadFiles(payload.files, payload.active); return; } if (Date.now() < deadline) setTimeout(tryApply, 50); diff --git a/site/playground/playground-tabs.js b/site/playground/playground-tabs.js new file mode 100644 index 0000000000..4fe5a4832e --- /dev/null +++ b/site/playground/playground-tabs.js @@ -0,0 +1,312 @@ +// Multi-file state for the playground. +// +// pgState holds one CodeMirror.Doc per file, plus the name of the active one. +// editor.swapDoc swaps the visible buffer without re-creating the editor, so +// each file keeps its own undo history and modification state. +// +// Wiring: +// - pageInit (main.js) creates `window.code`; this file polls until that's +// true, then constructs pgState seeded with the editor's current buffer +// as main.das. +// - loadSample() (main.js) calls pgLoadFiles when a multi-file sample lands. +// - runCode() (main.js) iterates pgState before calling Module.callMain. +// - Autosave debounced into localStorage on every CM change. +// +// main.das is fixed name: cannot be deleted, cannot be renamed. Its tab still +// shows a × button for visual symmetry but it's disabled. + +(function () { + const STORAGE_KEY = 'daslang.playground.state.v1'; + const AUTOSAVE_DEBOUNCE_MS = 250; + const ENTRY = 'main.das'; + + let pgState = null; + let tabsEl = null; + let editorMode = 'daslang'; + let autosaveTimer = null; + + function $(sel) { return document.querySelector(sel); } + + // ─── State helpers ──────────────────────────────────────────────── + + function makeDoc(text) { + return new CodeMirror.Doc(text || '', editorMode); + } + + function getStateJson() { + return { + files: Object.fromEntries( + Object.entries(pgState.files).map(([k, doc]) => [k, doc.getValue()]) + ), + active: pgState.active, + }; + } + + function autosave() { + if (!pgState) return; + if (autosaveTimer) clearTimeout(autosaveTimer); + autosaveTimer = setTimeout(() => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(getStateJson())); + } catch (e) { /* quota / disabled — ignore */ } + }, AUTOSAVE_DEBOUNCE_MS); + } + + function attachAutosave(doc) { + doc.on('change', autosave); + } + + function loadAutosave() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return null; + const obj = JSON.parse(raw); + if (!obj || typeof obj !== 'object' || !obj.files) return null; + return obj; + } catch (e) { return null; } + } + + // ─── Tab strip rendering ────────────────────────────────────────── + + function renderTabs() { + if (!tabsEl) return; + const names = Object.keys(pgState.files); + const html = names.map(name => { + const isActive = name === pgState.active; + const isEntry = name === ENTRY; + return ( + '
    ' + + '' + esc(name) + '' + + '' + + '
    ' + ); + }).join(''); + tabsEl.innerHTML = html + ''; + } + + function esc(s) { return s.replace(/&/g, '&').replace(//g, '>'); } + function escAttr(s) { return esc(s).replace(/"/g, '"'); } + + // ─── Operations ─────────────────────────────────────────────────── + + function pgSwitchFile(name) { + if (!(name in pgState.files)) return; + pgState.active = name; + window.code.swapDoc(pgState.files[name]); + // Toggle classes in place — rebuilding the tab DOM here breaks + // dblclick rename (second click lands on a freshly-mounted node). + if (tabsEl) { + tabsEl.querySelectorAll('.pg-tab').forEach(t => { + t.classList.toggle('is-active', t.dataset.file === name); + }); + } + } + + function pgAddFile(name) { + if (!name) { + let i = 1; + while (('untitled' + i + '.das') in pgState.files) i++; + name = 'untitled' + i + '.das'; + } + if (name in pgState.files) return false; + const doc = makeDoc(''); + attachAutosave(doc); + pgState.files[name] = doc; + renderTabs(); + pgSwitchFile(name); + autosave(); + return true; + } + + function pgDeleteFile(name) { + if (name === ENTRY) return false; + if (!(name in pgState.files)) return false; + const doc = pgState.files[name]; + if (doc.getValue().trim().length > 0) { + if (!window.confirm('Discard "' + name + '"?')) return false; + } + delete pgState.files[name]; + const switchTo = pgState.active === name ? ENTRY : pgState.active; + renderTabs(); + if (switchTo !== pgState.active) pgSwitchFile(switchTo); + autosave(); + return true; + } + + function pgRenameFile(oldName, newName) { + if (oldName === ENTRY) return false; + newName = (newName || '').trim(); + if (!newName) return false; + if (!/^[A-Za-z0-9_./-]+\.das$/.test(newName)) { + window.alert('Filename must end in .das and contain only [A-Za-z0-9_./-].'); + return false; + } + if (newName === oldName) return true; + if (newName in pgState.files) { + window.alert('"' + newName + '" already exists.'); + return false; + } + // Preserve insertion order so the tab order stays stable. + const next = {}; + for (const k of Object.keys(pgState.files)) { + next[k === oldName ? newName : k] = pgState.files[k]; + } + pgState.files = next; + if (pgState.active === oldName) pgState.active = newName; + renderTabs(); + autosave(); + return true; + } + + function pgLoadFiles(filesByName, activeOverride) { + if (!filesByName || typeof filesByName !== 'object') return; + // Build fresh Doc per file. Preserve insertion order, but if main.das + // is present, ensure it comes first. + const names = Object.keys(filesByName); + const ordered = names.includes(ENTRY) + ? [ENTRY, ...names.filter(n => n !== ENTRY)] + : names; + const newFiles = {}; + for (const name of ordered) { + const doc = makeDoc(filesByName[name]); + attachAutosave(doc); + newFiles[name] = doc; + } + pgState.files = newFiles; + pgState.active = activeOverride && activeOverride in newFiles + ? activeOverride + : (ordered.includes(ENTRY) ? ENTRY : ordered[0]); + window.code.swapDoc(pgState.files[pgState.active]); + renderTabs(); + autosave(); + } + + // ─── Tab strip event delegation ─────────────────────────────────── + + function bindTabsHandlers() { + if (!tabsEl) return; + + tabsEl.addEventListener('click', (e) => { + const closeBtn = e.target.closest('.pg-tab__close'); + const addBtn = e.target.closest('.pg-tab__add'); + const tab = e.target.closest('.pg-tab'); + if (closeBtn && !closeBtn.disabled) { + pgDeleteFile(closeBtn.dataset.file); + return; + } + if (addBtn) { + pgAddFile(); + return; + } + if (tab) { + pgSwitchFile(tab.dataset.file); + } + }); + + tabsEl.addEventListener('dblclick', (e) => { + const nameEl = e.target.closest('.pg-tab__name'); + if (!nameEl) return; + const name = nameEl.dataset.file; + if (name === ENTRY) return; + startRename(nameEl, name); + }); + } + + function startRename(nameEl, oldName) { + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'pg-tab__rename'; + input.value = oldName; + input.spellcheck = false; + + let done = false; + function commit(apply) { + if (done) return; + done = true; + input.remove(); + if (apply) pgRenameFile(oldName, input.value); + // Re-render either way to drop the input from the DOM cleanly. + renderTabs(); + } + input.addEventListener('blur', () => commit(true)); + input.addEventListener('keydown', (ev) => { + if (ev.key === 'Enter') { ev.preventDefault(); input.blur(); } + else if (ev.key === 'Escape') { ev.preventDefault(); commit(false); } + }); + // Replace the name span with the input in-place. + nameEl.replaceWith(input); + input.focus(); + input.select(); + } + + // ─── Init ───────────────────────────────────────────────────────── + + function tryInit() { + if (typeof CodeMirror === 'undefined' || + typeof window.code === 'undefined' || window.code === null || + typeof window.code.swapDoc !== 'function') { + setTimeout(tryInit, 30); + return; + } + // Use the mode the editor was constructed with. + editorMode = window.code.getOption('mode') || 'daslang'; + tabsEl = document.getElementById('pg-tabs'); + if (!tabsEl) return; + + // Seed pgState. Priority: hash > pending sample bundle > autosave > current editor. + const initialDoc = window.code.getDoc(); + pgState = { + files: { [ENTRY]: initialDoc }, + active: ENTRY, + }; + attachAutosave(initialDoc); + + // Expose globals before applying pending sources so handlers can use them. + window.pgState = pgState; + window.pgLoadFiles = pgLoadFiles; + window.pgSwitchFile = pgSwitchFile; + window.pgAddFile = pgAddFile; + window.pgDeleteFile = pgDeleteFile; + window.pgRenameFile = pgRenameFile; + window.pgStateJson = getStateJson; + + bindTabsHandlers(); + renderTabs(); + + // Priority: URL hash > autosave > default sample > empty editor. + // When restored from hash or autosave we set pgRestoredFromState so + // main.js skips its default selectSample("examples", 0) call (whose + // async fetch would otherwise clobber the restored state ~200ms in). + const hash = window.location.hash || ''; + const hasHash = hash.startsWith('#code=') || hash.startsWith('#z='); + + if (hasHash && window.__pendingSampleBundle) { + const bundle = window.__pendingSampleBundle; + window.__pendingSampleBundle = null; + pgLoadFiles(bundle); + window.pgRestoredFromState = true; + return; + } + + const saved = !hasHash ? loadAutosave() : null; + if (saved && saved.files && Object.keys(saved.files).length > 0) { + pgLoadFiles(saved.files, saved.active); + window.pgRestoredFromState = true; + return; + } + + // No autosave: let the pending default sample (if any) drop in. + if (window.__pendingSampleBundle) { + const bundle = window.__pendingSampleBundle; + window.__pendingSampleBundle = null; + pgLoadFiles(bundle); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', tryInit); + } else { + tryInit(); + } +})(); diff --git a/site/tests/playground/persistence.spec.js b/site/tests/playground/persistence.spec.js new file mode 100644 index 0000000000..92f40face4 --- /dev/null +++ b/site/tests/playground/persistence.spec.js @@ -0,0 +1,65 @@ +// Multi-file state survives a page reload via localStorage autosave. + +const { test, expect } = require('./fixtures.js'); + +async function waitTabsReady(page) { + await page.waitForFunction(() => !!window.pgState, null, { timeout: 10_000 }); + await page.locator('#pg-tabs .pg-tab[data-file="main.das"]').waitFor(); +} + +test('three-file state survives a reload', async ({ playground }) => { + await waitTabsReady(playground); + + // Build state: main.das + utils.das + types.das with distinctive content. + await playground.evaluate(() => { + window.pgSwitchFile('main.das'); + window.code.getDoc().setValue('// main marker\nrequire utils\n'); + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('// utils marker\n'); + window.pgAddFile('types.das'); + window.code.getDoc().setValue('// types marker\n'); + window.pgSwitchFile('main.das'); + }); + + // Wait for the debounced autosave (250ms) to actually fire. + await playground.waitForTimeout(400); + + await playground.reload(); + await waitTabsReady(playground); + + expect(await playground.evaluate(() => Object.keys(window.pgState.files))).toEqual([ + 'main.das', 'utils.das', 'types.das', + ]); + expect(await playground.evaluate(() => window.pgState.active)).toBe('main.das'); + + const mainText = await playground.evaluate(() => window.pgState.files['main.das'].getValue()); + expect(mainText).toContain('main marker'); + const utilsText = await playground.evaluate(() => window.pgState.files['utils.das'].getValue()); + expect(utilsText).toContain('utils marker'); + const typesText = await playground.evaluate(() => window.pgState.files['types.das'].getValue()); + expect(typesText).toContain('types marker'); +}); + +test('autosave does not override URL #code hash on reload', async ({ playground }) => { + await waitTabsReady(playground); + + // Plant autosave state. + await playground.evaluate(() => { + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('autosaved'); + }); + await playground.waitForTimeout(400); + + // Force a fresh page load: same path + just-a-new-hash fires hashchange + // without a reload (so pgInit wouldn't re-run). A cache-buster query + // forces the load. + await playground.goto( + '/playground/?fresh=1#code=' + encodeURIComponent('// shared via hash\n') + ); + await waitTabsReady(playground); + + const files = await playground.evaluate(() => Object.keys(window.pgState.files)); + expect(files).toEqual(['main.das']); + const mainText = await playground.evaluate(() => window.pgState.files['main.das'].getValue()); + expect(mainText).toContain('shared via hash'); +}); diff --git a/site/tests/playground/tabs.spec.js b/site/tests/playground/tabs.spec.js new file mode 100644 index 0000000000..df06017e5c --- /dev/null +++ b/site/tests/playground/tabs.spec.js @@ -0,0 +1,116 @@ +// Multi-file tab strip: add / switch / rename / delete. +// Drives both the UI path (clicks/keys) and the pg* JS API. + +const { test, expect } = require('./fixtures.js'); + +async function waitTabsReady(page) { + // Playwright contexts are clean per test, so localStorage starts empty. + // pgInit polls until window.code is up, so we wait for pgState to surface. + await page.waitForFunction(() => !!window.pgState, null, { timeout: 10_000 }); + await page.locator('#pg-tabs .pg-tab[data-file="main.das"]').waitFor(); +} + +test('initial state: only main.das, × is disabled', async ({ playground }) => { + await waitTabsReady(playground); + const tabs = playground.locator('#pg-tabs .pg-tab'); + await expect(tabs).toHaveCount(1); + await expect(tabs.first()).toHaveClass(/is-active/); + await expect(tabs.first()).toHaveClass(/is-entry/); + await expect(tabs.first().locator('.pg-tab__close')).toBeDisabled(); +}); + +test('+ button adds a new file with an untitled name', async ({ playground }) => { + await waitTabsReady(playground); + await playground.locator('#pg-tabs .pg-tab__add').click(); + await expect(playground.locator('#pg-tabs .pg-tab')).toHaveCount(2); + await expect(playground.locator('#pg-tabs .pg-tab.is-active')).toHaveAttribute( + 'data-file', + 'untitled1.das', + ); +}); + +test('clicking a tab switches the editor buffer', async ({ playground }) => { + await waitTabsReady(playground); + // Add a second file with distinct content. + await playground.evaluate(() => { + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('// utils contents'); + }); + // Switch to main via tab click. + await playground.locator('.pg-tab[data-file="main.das"]').click(); + await expect(playground.locator('#pg-tabs .pg-tab.is-active')).toHaveAttribute( + 'data-file', + 'main.das', + ); + const mainText = await playground.evaluate(() => window.code.getValue()); + expect(mainText).not.toContain('utils contents'); + + // Back to utils. + await playground.locator('.pg-tab[data-file="utils.das"]').click(); + const utilsText = await playground.evaluate(() => window.code.getValue()); + expect(utilsText).toBe('// utils contents'); +}); + +test('double-click renames a file', async ({ playground }) => { + await waitTabsReady(playground); + await playground.evaluate(() => window.pgAddFile('utils.das')); + await playground.locator('.pg-tab[data-file="utils.das"] .pg-tab__name').dblclick(); + const input = playground.locator('input.pg-tab__rename'); + await expect(input).toBeFocused(); + await input.fill('helpers.das'); + await input.press('Enter'); + + await expect(playground.locator('.pg-tab[data-file="helpers.das"]')).toBeVisible(); + await expect(playground.locator('.pg-tab[data-file="utils.das"]')).toHaveCount(0); + expect(await playground.evaluate(() => Object.keys(window.pgState.files))).toEqual([ + 'main.das', 'helpers.das', + ]); +}); + +test('rename rejects invalid filenames', async ({ playground }) => { + await waitTabsReady(playground); + await playground.evaluate(() => window.pgAddFile('utils.das')); + // Suppress the alert dialog. + playground.on('dialog', dialog => dialog.dismiss()); + + const ok = await playground.evaluate(() => window.pgRenameFile('utils.das', 'no_extension')); + expect(ok).toBe(false); + expect(await playground.evaluate(() => Object.keys(window.pgState.files))).toContain('utils.das'); +}); + +test('main.das cannot be renamed or deleted', async ({ playground }) => { + await waitTabsReady(playground); + expect(await playground.evaluate(() => window.pgDeleteFile('main.das'))).toBe(false); + expect(await playground.evaluate(() => window.pgRenameFile('main.das', 'app.das'))).toBe(false); + await expect(playground.locator('.pg-tab[data-file="main.das"]')).toHaveCount(1); +}); + +test('× on a non-entry file removes it and falls back to main.das', async ({ playground }) => { + await waitTabsReady(playground); + await playground.evaluate(() => window.pgAddFile('utils.das')); + // utils.das is empty so confirm() should not be invoked. + await playground.locator('.pg-tab[data-file="utils.das"] .pg-tab__close').click(); + await expect(playground.locator('.pg-tab[data-file="utils.das"]')).toHaveCount(0); + await expect(playground.locator('#pg-tabs .pg-tab.is-active')).toHaveAttribute( + 'data-file', + 'main.das', + ); +}); + +test('× on a non-empty file asks to confirm', async ({ playground }) => { + await waitTabsReady(playground); + await playground.evaluate(() => { + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('not empty'); + }); + + // Reject the confirm — file stays. + playground.once('dialog', d => d.dismiss()); + await playground.locator('.pg-tab[data-file="utils.das"] .pg-tab__close').click(); + await expect(playground.locator('.pg-tab[data-file="utils.das"]')).toHaveCount(1); + + // Accept the confirm — file goes. + playground.once('dialog', d => d.accept()); + await playground.locator('.pg-tab[data-file="utils.das"] .pg-tab__close').click(); + await expect(playground.locator('.pg-tab[data-file="utils.das"]')).toHaveCount(0); +}); diff --git a/web/ui/src/main.js b/web/ui/src/main.js index 3c8688fb6c..7f622c225c 100644 --- a/web/ui/src/main.js +++ b/web/ui/src/main.js @@ -60,7 +60,12 @@ pageInit = function () { } }); - selectSample("examples",0); + // Skip the default sample if pgInit already restored state from URL + // hash or localStorage autosave (otherwise the async fetch overwrites + // the user's work ~200ms after page load). + if (!window.pgRestoredFromState) { + selectSample("examples", 0); + } }); @@ -84,24 +89,40 @@ selectSample = function(type, id) { sampleList[type].value = "init"; } -// Apply a {filename: content} bundle to the editor. Today: surfaces only the -// main.das (or the first file) in the single CM instance. Phase 3 routes this -// through pgState + the tab strip so all files are addressable from the UI. +// Apply a {filename: content} bundle. Once the tab strip is mounted, route +// through pgLoadFiles so every file gets its own Doc. Before then (during +// pageInit's initial selectSample call), stash the bundle for pgInit to pick +// up when it polls in. +// +// Single-file samples are normalized to main.das (the entry callMain runs). +// Multi-file samples are expected to ship a main.das themselves. loadSample = function(filesByName) { const names = Object.keys(filesByName); if (!names.length) return; - const active = filesByName['main.das'] !== undefined ? 'main.das' : names[0]; - code.setValue(filesByName[active]); - // Stash the full bundle so phase 3 can pick it up post-tab-strip. - window.__pendingSampleBundle = filesByName; + let bundle = filesByName; + if (names.length === 1 && names[0] !== 'main.das') { + bundle = { 'main.das': filesByName[names[0]] }; + } + if (typeof window.pgLoadFiles === 'function') { + window.pgLoadFiles(bundle); + return; + } + const active = bundle['main.das'] !== undefined ? 'main.das' : Object.keys(bundle)[0]; + code.setValue(bundle[active]); + window.__pendingSampleBundle = bundle; } runCode = function() { - + // Multi-file: write every file in pgState (if mounted) to MEMFS, then run + // main.das. Falls back to the single-buffer path when pgState isn't up yet. + if (window.pgState && typeof FS !== 'undefined') { + for (const [name, doc] of Object.entries(window.pgState.files)) { + FS.writeFile(name, doc.getValue()); + } + Module.callMain(['main.das']); + return; + } runScript(code.getValue()); - - - } From 9edf21c1d670107b1996fef7c00fcfc6ee75d9f8 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 13:17:17 -0700 Subject: [PATCH 05/22] playground: bundled multi-file Macros sample MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4: vendors tutorials/macros/09_for_loop_macro.das + its companion for_loop_macro_mod.das into web/ui/samples/examples/macros/ and registers the pair as "Macros (multi-file)" in samples/data.json. Picking it from the Examples dropdown populates both tabs. This is the wedge case for multi-file: macros must live in a separate module from the one that uses them, so a single-file playground simply cannot demonstrate them. Running the sample emits the `for ((k,v) in tab)` expansion — a real macro module compiled live in the browser. macro-sample.spec.js covers three cases: - two tabs appear after picking the sample (no-WASM) - main.das contains `require for_loop_macro_mod` + macro syntax (no-WASM) - ▶ run produces section-3 output `apple => 10` (@wasm-tagged; skipped by the no-WASM CI gate) --- site/tests/playground/macro-sample.spec.js | 67 +++++++++++++++ web/ui/samples/data.json | 4 + .../examples/macros/for_loop_macro_mod.das | 81 +++++++++++++++++++ web/ui/samples/examples/macros/main.das | 48 +++++++++++ 4 files changed, 200 insertions(+) create mode 100644 site/tests/playground/macro-sample.spec.js create mode 100644 web/ui/samples/examples/macros/for_loop_macro_mod.das create mode 100644 web/ui/samples/examples/macros/main.das diff --git a/site/tests/playground/macro-sample.spec.js b/site/tests/playground/macro-sample.spec.js new file mode 100644 index 0000000000..cffda2907c --- /dev/null +++ b/site/tests/playground/macro-sample.spec.js @@ -0,0 +1,67 @@ +// Multi-file macro sample: the "Macros (multi-file)" example ships two +// files. The loading half (two tabs appear, main.das is active) runs in +// any tier — even without WASM. The "click ▶ run and check the output" +// half is tagged @wasm so the no-WASM CI gate skips it. + +const { test, expect } = require('./fixtures.js'); + +async function waitTabsReady(page) { + await page.waitForFunction(() => !!window.pgState, null, { timeout: 10_000 }); + await page.locator('#pg-tabs .pg-tab[data-file="main.das"]').waitFor(); +} + +async function pickMacroSample(page) { + // Wait for the dropdown to populate from data.json. + await page.waitForFunction( + () => Array.from(document.getElementById('examples').options) + .some(o => o.text === 'Macros (multi-file)'), + null, + { timeout: 10_000 } + ); + await page.locator('#examples').selectOption({ label: 'Macros (multi-file)' }); + // Wait until both files are present in pgState. + await page.waitForFunction( + () => window.pgState && 'main.das' in window.pgState.files + && 'for_loop_macro_mod.das' in window.pgState.files, + null, + { timeout: 5_000 } + ); +} + +test('Macros sample loads as two tabs', async ({ playground }) => { + await waitTabsReady(playground); + await pickMacroSample(playground); + + await expect(playground.locator('.pg-tab[data-file="main.das"]')).toBeVisible(); + await expect(playground.locator('.pg-tab[data-file="for_loop_macro_mod.das"]')).toBeVisible(); + // main.das should be the active buffer; the entry file flag. + await expect(playground.locator('#pg-tabs .pg-tab.is-active')).toHaveAttribute( + 'data-file', + 'main.das', + ); +}); + +test('Macros sample main.das requires the macro module', async ({ playground }) => { + await waitTabsReady(playground); + await pickMacroSample(playground); + + const mainText = await playground.evaluate(() => window.pgState.files['main.das'].getValue()); + expect(mainText).toContain('require for_loop_macro_mod'); + // The macro syntax `for ((k, v) in tab)` is the headline feature. + expect(mainText).toMatch(/for\s*\(\s*\(\s*k\s*,\s*v\s*\)\s+in\s+tab\s*\)/); +}); + +test('Macros sample runs successfully under WASM @wasm', async ({ playground }) => { + await waitTabsReady(playground); + await pickMacroSample(playground); + // Wait for the WASM runtime to be ready. + await playground.waitForFunction( + () => typeof window.FS !== 'undefined' && typeof window.Module?.callMain === 'function', + null, + { timeout: 30_000 } + ); + await playground.locator('#run').click(); + // Output panel should print the section-3 table-with-counter line. + await expect(playground.locator('.output_line_text', { hasText: 'apple => 10' })) + .toBeVisible({ timeout: 15_000 }); +}); diff --git a/web/ui/samples/data.json b/web/ui/samples/data.json index d5c580a780..98c856a8a2 100644 --- a/web/ui/samples/data.json +++ b/web/ui/samples/data.json @@ -12,6 +12,10 @@ { "name" : "Functions", "files" : ["examples/func.das"] + }, + { + "name" : "Macros (multi-file)", + "files" : ["examples/macros/main.das", "examples/macros/for_loop_macro_mod.das"] } ], diff --git a/web/ui/samples/examples/macros/for_loop_macro_mod.das b/web/ui/samples/examples/macros/for_loop_macro_mod.das new file mode 100644 index 0000000000..30284cddd4 --- /dev/null +++ b/web/ui/samples/examples/macros/for_loop_macro_mod.das @@ -0,0 +1,81 @@ +options gen2 +options no_aot + +// Tutorial macro module: for-loop macro that enables +// for ((k,v) in tab) +// syntax on tables. Rewrites to +// for (k, v in keys(tab), values(tab)) + +module for_loop_macro_mod + +require daslib/ast +require daslib/ast_boost +require strings + +[for_loop_macro(name=table_kv)] +class TableKVForLoop : AstForLoopMacro { + //! Transforms for ((k,v) in tab) into for (k, v in keys(tab), values(tab)). + def override visitExprFor(prog : ProgramPtr; mod : Module?; expr : ExprFor?) : ExpressionPtr { + if (is_in_completion()) { + return default + } + // Find a tuple-expanded iterator whose source is a table + var tab_index = -1 + for (index, src in count(), expr.sources) { + if (index < int(expr.iteratorsTupleExpansion |> length)) { + if (int(expr.iteratorsTupleExpansion[index]) != 0 && src._type != null && src._type.isGoodTableType) { + tab_index = index + break + } + } + } + if (tab_index == -1) { + return default + } + // Split the backtick-joined name "k`v" into key_name and val_name + let joined_name = string(expr.iterators[tab_index]) + let bt = find(joined_name, "`") + if (bt < 0 || find(joined_name, "`", bt + 1) >= 0) { + return default // need exactly 2 parts + } + let key_name = slice(joined_name, 0, bt) + let val_name = slice(joined_name, bt + 1) + // Clone the for expression + var new_for_e = clone_expression(expr) + var new_for = new_for_e as ExprFor + // Erase the table entry from all parallel vectors + let source_at = expr.sources[tab_index].at + new_for.sources |> erase(tab_index) + new_for.iterators |> erase(tab_index) + new_for.iteratorsAt |> erase(tab_index) + new_for.iteratorsAka |> erase(tab_index) + new_for.iteratorsTags |> erase(tab_index) + new_for.iteratorsTupleExpansion |> erase(tab_index) + // Add keys(tab) and values(tab) as new sources + new_for.sources |> emplace_new <| make_kv_call("keys", expr.sources[tab_index], source_at) + new_for.sources |> emplace_new <| make_kv_call("values", expr.sources[tab_index], source_at) + // Add key and value iterator names + let si = new_for.iterators |> length + new_for.iterators |> resize(si + 2) + new_for.iterators[si] := key_name + new_for.iterators[si + 1] := val_name + new_for.iteratorsAka |> resize(si + 2) + new_for.iteratorsAka[si] := "" + new_for.iteratorsAka[si + 1] := "" + new_for.iteratorsAt |> push(expr.iteratorsAt[tab_index]) + new_for.iteratorsAt |> push(expr.iteratorsAt[tab_index]) + new_for.iteratorsTags |> emplace_new <| clone_expression(expr.iteratorsTags[tab_index]) + new_for.iteratorsTags |> emplace_new <| clone_expression(expr.iteratorsTags[tab_index]) + new_for.iteratorsTupleExpansion |> push(0u8) + new_for.iteratorsTupleExpansion |> push(0u8) + // Clear iterator variables for re-inference + new_for.iteratorVariables |> clear() + return new_for_e + } +} + +def make_kv_call(fn_name : string; src_expr : ExpressionPtr; at : LineInfo) : ExpressionPtr { + var call = new ExprCall(at = at, name := fn_name) + call.arguments |> emplace_new <| clone_expression(src_expr) + return call +} diff --git a/web/ui/samples/examples/macros/main.das b/web/ui/samples/examples/macros/main.das new file mode 100644 index 0000000000..8939f3145b --- /dev/null +++ b/web/ui/samples/examples/macros/main.das @@ -0,0 +1,48 @@ +options gen2 + +// Tutorial 09 — For-loop macro +// +// Demonstrates AstForLoopMacro (registered via [for_loop_macro]). +// The macro module defines a for_loop_macro that enables +// for ((k,v) in tab) +// syntax on tables, rewriting it to +// for (k, v in keys(tab), values(tab)) + +require for_loop_macro_mod + +// ---- Section 1: without the macro (the verbose way) ------------------------- + +def section1() { + print("--- Section 1: table iteration without the macro ---\n") + let tab <- { "one" => 1, "two" => 2, "three" => 3 } + for (k, v in keys(tab), values(tab)) { + print(" {k} => {v}\n") + } +} + +// ---- Section 2: with the for_loop_macro ------------------------------------- + +def section2() { + print("\n--- Section 2: for ((k,v) in tab) with the macro ---\n") + var tab <- { "one" => 1, "two" => 2, "three" => 3 } // nolint:LINT003 + for ((k, v) in tab) { + print(" {k} => {v}\n") + } +} + +// ---- Section 3: mixed sources ----------------------------------------------- + +def section3() { + print("\n--- Section 3: mixed sources ---\n") + var tab <- { "apple" => 10, "banana" => 20, "cherry" => 30 } // nolint:LINT003 + for ((k, v), idx in tab, range(100)) { + print(" [{idx}] {k} => {v}\n") + } +} + +[export] +def main() { + section1() + section2() + section3() +} From 1a979e121a70bcc2142f887d1ad0ed6032f7e3a7 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 13:18:21 -0700 Subject: [PATCH 06/22] playground: spec for the multi-file run flow Phase 5: the runCode loop landed back in phase 3 (write every pgState file to MEMFS before callMain). This adds the spec that exercises it: - runs a user-authored two-file program: writes main.das requiring utils with a `hello()` print, asserts the output panel shows "hi, playground". - renaming utils.das breaks the build: after pgRenameFile, `require utils` should fail at compile time, surfaced through stderr. Both tagged @wasm so the no-WASM CI gate skips them. --- site/tests/playground/multi-file-run.spec.js | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 site/tests/playground/multi-file-run.spec.js diff --git a/site/tests/playground/multi-file-run.spec.js b/site/tests/playground/multi-file-run.spec.js new file mode 100644 index 0000000000..a33a359ad2 --- /dev/null +++ b/site/tests/playground/multi-file-run.spec.js @@ -0,0 +1,60 @@ +// Multi-file run: write main.das + utils.das, hit ▶ run, check the +// daslang output. Requires WASM (tagged @wasm so the no-WASM CI gate +// skips this file entirely). + +const { test, expect } = require('./fixtures.js'); + +async function waitTabsReady(page) { + await page.waitForFunction(() => !!window.pgState, null, { timeout: 10_000 }); + await page.locator('#pg-tabs .pg-tab[data-file="main.das"]').waitFor(); +} + +async function waitWasmReady(page) { + await page.waitForFunction( + () => typeof window.FS !== 'undefined' && typeof window.Module?.callMain === 'function', + null, + { timeout: 30_000 } + ); +} + +test('runs a user-authored two-file program @wasm', async ({ playground }) => { + await waitTabsReady(playground); + await waitWasmReady(playground); + + await playground.evaluate(() => { + window.pgSwitchFile('main.das'); + window.code.getDoc().setValue( + 'options gen2\nrequire utils\n[export]\ndef main() {\n hello("playground")\n}\n' + ); + window.pgAddFile('utils.das'); + window.code.getDoc().setValue( + 'options gen2\ndef hello(name : string) {\n print("hi, {name}\\n")\n}\n' + ); + window.pgSwitchFile('main.das'); + }); + + await playground.locator('#run').click(); + await expect(playground.locator('.output_line_text', { hasText: 'hi, playground' })) + .toBeVisible({ timeout: 15_000 }); +}); + +test('renaming utils.das to non-required name breaks the build @wasm', async ({ playground }) => { + await waitTabsReady(playground); + await waitWasmReady(playground); + + await playground.evaluate(() => { + window.pgSwitchFile('main.das'); + window.code.getDoc().setValue( + 'options gen2\nrequire utils\n[export]\ndef main() { hello() }\n' + ); + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('options gen2\ndef hello() { print("ok\\n") }\n'); + }); + // Rename utils.das → helpers.das; require utils now fails to resolve. + expect(await playground.evaluate(() => window.pgRenameFile('utils.das', 'helpers.das'))).toBe(true); + await playground.evaluate(() => window.pgSwitchFile('main.das')); + await playground.locator('#run').click(); + // daslang emits an error message containing "can't open" or "require". + await expect(playground.locator('.output_line_text', { hasText: /can.t (?:open|locate)|require/i })) + .toBeVisible({ timeout: 15_000 }); +}); From fdc37361239956ef308e24caa7fde6f9d1dcb401 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 13:24:33 -0700 Subject: [PATCH 07/22] =?UTF-8?q?playground:=20=E2=86=97=20share=20?= =?UTF-8?q?=E2=80=94=20compressed=20multi-file=20URL=20+=20is.gd=20shorten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 6: a "share this playground" button next to ▶ run. Clicking opens a small popover with a shareable URL of the current state, [Copy], and optional [Shorten to is.gd]. URL encoding: every file in pgState + the active filename serialize as JSON, then LZString.compressToEncodedURIComponent → `#z=`. URL-safe out of the box; a 3-file ~40-line state lands well under 1 KB. is.gd shortener: GET https://is.gd/create.php?format=simple&url=… returns the short URL as plain text with permissive CORS. On success the input swaps to the short form; on failure the button briefly says "failed" and the long URL stays put. Vendored: site/files/lz-string.min.js (~5 KB, MIT, lz-string@1.5.0). forge-skin.css: .button_header--ghost variant for the share button; .pg-share popover (header, URL row, copy/shorten/footer). playground-init.js already dispatches `#z=`/`#code=` to pgLoadFiles from phase 3 — this commit just produces those URLs. share.spec.js: four cases. - share popover surfaces the URL with all files (decode round-trip) - opening the URL in a fresh context restores state (one retry, since new-context bootstrap can race the polling pgInit under high parallelism) - is.gd shorten replaces the URL on success (route() stubs the request) - shorten failure surfaces the error and preserves the long URL --- .gitignore | 1 + site/files/lz-string.min.js | 1 + site/playground/forge-skin.css | 81 ++++++++++++++++ site/playground/index.html | 3 + site/playground/playground-share.js | 140 ++++++++++++++++++++++++++++ site/tests/playground/share.spec.js | 110 ++++++++++++++++++++++ 6 files changed, 336 insertions(+) create mode 100644 site/files/lz-string.min.js create mode 100644 site/playground/playground-share.js create mode 100644 site/tests/playground/share.spec.js diff --git a/.gitignore b/.gitignore index 2fc659faa1..6a3e6f2718 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,4 @@ modules/dasUnitTest/dasModuleUnitTest_debug.pdb build-ubsan/.ninja_log doc/sphinx-build-latex/environment.pickle opencode.json +test-results/ diff --git a/site/files/lz-string.min.js b/site/files/lz-string.min.js new file mode 100644 index 0000000000..f7c26ae2b4 --- /dev/null +++ b/site/files/lz-string.min.js @@ -0,0 +1 @@ +var LZString=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); \ No newline at end of file diff --git a/site/playground/forge-skin.css b/site/playground/forge-skin.css index 0364d58199..49ea6d1dc1 100644 --- a/site/playground/forge-skin.css +++ b/site/playground/forge-skin.css @@ -62,6 +62,87 @@ body { } .button_header:hover { background: #f4b04a !important; } .button_header:disabled { opacity: 0.5; cursor: default; } +.button_header--ghost { + background: transparent !important; + color: var(--fg-dim) !important; + border: 1px solid var(--rule) !important; +} +.button_header--ghost:hover { + background: rgba(232, 161, 58, 0.06) !important; + color: var(--amber) !important; + border-color: var(--amber-dim) !important; +} + +/* ───── Share popover ───── */ +.pg-share { + z-index: 100; + width: 460px; + max-width: calc(100vw - 16px); + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); + padding: 14px 16px; + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--fg); +} +.pg-share__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + color: var(--fg-dim); +} +.pg-share__close { + background: transparent; + border: 0; + color: var(--fg-faint); + font-size: 16px; + cursor: pointer; + line-height: 1; +} +.pg-share__close:hover { color: var(--red); } +.pg-share__row { + display: flex; + gap: 8px; + margin-bottom: 10px; +} +.pg-share__url { + flex: 1; + background: var(--bg); + border: 1px solid var(--rule); + color: var(--fg); + font-family: var(--font-mono); + font-size: 11.5px; + padding: 6px 8px; + border-radius: 4px; + outline: none; +} +.pg-share__url:focus { border-color: var(--amber-dim); } +.pg-share__copy, +.pg-share__shorten { + background: transparent; + border: 1px solid var(--rule); + color: var(--fg-dim); + font-family: var(--font-mono); + font-size: 11.5px; + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; +} +.pg-share__copy:hover, +.pg-share__shorten:hover { color: var(--amber); border-color: var(--amber-dim); } +.pg-share__shorten:disabled { opacity: 0.5; cursor: default; } +.pg-share__footer { + display: flex; + align-items: center; + justify-content: space-between; +} +.pg-share__meta { + color: var(--fg-faint); + font-size: 11px; +} .select_header { background: var(--bg-2) !important; diff --git a/site/playground/index.html b/site/playground/index.html index 85c32777db..f834001ed3 100644 --- a/site/playground/index.html +++ b/site/playground/index.html @@ -53,9 +53,11 @@ + +
    @@ -72,6 +74,7 @@
    +
    diff --git a/site/playground/playground-share.js b/site/playground/playground-share.js new file mode 100644 index 0000000000..a15de919ed --- /dev/null +++ b/site/playground/playground-share.js @@ -0,0 +1,140 @@ +// ↗ share — generate a URL containing every file in pgState, lz-compressed +// into the hash. Click opens a small popover with the URL, a Copy button, +// and an optional "Shorten to is.gd" action. + +(function () { + const SHORTENER = 'https://is.gd/create.php?format=simple&url='; + + function buildShareUrl() { + if (!window.pgState || !window.LZString) return null; + const payload = JSON.stringify({ + files: Object.fromEntries( + Object.entries(window.pgState.files).map(([k, doc]) => [k, doc.getValue()]) + ), + active: window.pgState.active, + }); + const z = window.LZString.compressToEncodedURIComponent(payload); + return location.origin + location.pathname + '#z=' + z; + } + + function fileCount() { + return window.pgState ? Object.keys(window.pgState.files).length : 0; + } + + function makePopover(url) { + const wrap = document.createElement('div'); + wrap.className = 'pg-share'; + wrap.innerHTML = ( + '
    ' + + 'Share this playground' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '' + ); + wrap.querySelector('.pg-share__url').value = url; + return wrap; + } + + function esc(s) { return String(s).replace(/&/g, '&').replace(//g, '>'); } + + function openPopover(anchor) { + closePopover(); + const url = buildShareUrl(); + if (!url) return; + const pop = makePopover(url); + document.body.appendChild(pop); + + // Position under the button. + const r = anchor.getBoundingClientRect(); + pop.style.position = 'absolute'; + pop.style.top = (window.scrollY + r.bottom + 8) + 'px'; + pop.style.right = Math.max(8, window.innerWidth - r.right) + 'px'; + + const input = pop.querySelector('.pg-share__url'); + const copyBtn = pop.querySelector('.pg-share__copy'); + const shortenBtn = pop.querySelector('.pg-share__shorten'); + const closeBtn = pop.querySelector('.pg-share__close'); + + input.addEventListener('focus', () => input.select()); + copyBtn.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(input.value); + copyBtn.textContent = '✓ copied'; + setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500); + } catch (e) { + // Fallback: select text so user can ⌘C manually. + input.select(); + } + }); + shortenBtn.addEventListener('click', async () => { + shortenBtn.disabled = true; + shortenBtn.textContent = '…'; + try { + const resp = await fetch(SHORTENER + encodeURIComponent(input.value)); + if (!resp.ok) throw new Error('is.gd error ' + resp.status); + const short = (await resp.text()).trim(); + if (!/^https?:\/\//.test(short)) throw new Error('non-URL response: ' + short.slice(0, 60)); + input.value = short; + shortenBtn.textContent = '✓ shortened'; + } catch (e) { + shortenBtn.textContent = 'shorten failed'; + console.warn('share-shorten:', e); + } finally { + setTimeout(() => { + shortenBtn.textContent = '↘ Shorten to is.gd'; + shortenBtn.disabled = false; + }, 2000); + } + }); + closeBtn.addEventListener('click', closePopover); + + // Click-outside to close. Capture phase so we see the click before + // the popover's own handlers (which stopPropagation). + setTimeout(() => { + document.addEventListener('mousedown', outsideHandler, true); + }, 0); + window.__pgSharePopover = pop; + } + + function outsideHandler(e) { + const pop = window.__pgSharePopover; + if (!pop) return; + const btn = document.getElementById('share'); + if (pop.contains(e.target) || (btn && btn.contains(e.target))) return; + closePopover(); + } + + function closePopover() { + document.removeEventListener('mousedown', outsideHandler, true); + if (window.__pgSharePopover) { + window.__pgSharePopover.remove(); + window.__pgSharePopover = null; + } + } + + function wireShareButton() { + const btn = document.getElementById('share'); + if (!btn) return; + btn.addEventListener('click', (e) => { + e.stopPropagation(); + if (window.__pgSharePopover) closePopover(); + else openPopover(btn); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', wireShareButton); + } else { + wireShareButton(); + } + + // Expose for tests. + window.pgBuildShareUrl = buildShareUrl; +})(); diff --git a/site/tests/playground/share.spec.js b/site/tests/playground/share.spec.js new file mode 100644 index 0000000000..31a3c29d67 --- /dev/null +++ b/site/tests/playground/share.spec.js @@ -0,0 +1,110 @@ +// ↗ share — compressed multi-file state in a URL hash, with optional is.gd +// shortener. is.gd is stubbed via Playwright's route() so the test does not +// depend on the external service. + +const { test, expect } = require('./fixtures.js'); + +// The "fresh context" test spawns a second browser context. Under high +// parallelism the new-context bootstrap competes with the main suite for +// the shared http.server and can race the polling pgInit. One retry on +// flake is plenty. +test.describe.configure({ retries: 1 }); + +async function waitTabsReady(page) { + await page.waitForFunction(() => !!window.pgState, null, { timeout: 10_000 }); + await page.locator('#pg-tabs .pg-tab[data-file="main.das"]').waitFor(); +} + +test('share popover shows a #z= URL with all files', async ({ playground }) => { + await waitTabsReady(playground); + await playground.evaluate(() => { + window.pgSwitchFile('main.das'); + window.code.getDoc().setValue('// MAIN-PAYLOAD\n'); + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('// UTILS-PAYLOAD\n'); + }); + + await playground.locator('#share').click(); + const popover = playground.locator('.pg-share'); + await expect(popover).toBeVisible(); + const urlInput = popover.locator('.pg-share__url'); + const url = await urlInput.inputValue(); + expect(url).toContain('#z='); + await expect(popover.locator('.pg-share__meta')).toHaveText(/2 files/); + + // Decoding the hash via the same LZString library must round-trip. + const decoded = await playground.evaluate((u) => { + const z = u.split('#z=')[1]; + return JSON.parse(window.LZString.decompressFromEncodedURIComponent(z)); + }, url); + expect(Object.keys(decoded.files).sort()).toEqual(['main.das', 'utils.das']); + expect(decoded.files['main.das']).toContain('MAIN-PAYLOAD'); + expect(decoded.files['utils.das']).toContain('UTILS-PAYLOAD'); +}); + +test('shared URL restores state in a fresh context', async ({ playground, browser }) => { + await waitTabsReady(playground); + await playground.evaluate(() => { + window.pgSwitchFile('main.das'); + window.code.getDoc().setValue('// MAIN-SHARED\n'); + window.pgAddFile('utils.das'); + window.code.getDoc().setValue('// UTILS-SHARED\n'); + }); + const shareUrl = await playground.evaluate(() => window.pgBuildShareUrl()); + + // Open the URL in a brand-new browser context so localStorage is empty + // and only the hash can repopulate the state. + const ctx = await browser.newContext(); + const page2 = await ctx.newPage(); + await page2.goto(shareUrl, { waitUntil: 'networkidle' }); + await page2.locator('.CodeMirror').waitFor(); + // pgInit can surface an empty main.das briefly before the hash payload + // gets applied (both happen via polling). Wait until restoration finishes. + await page2.waitForFunction( + () => window.pgState && 'utils.das' in window.pgState.files, + null, + { timeout: 30_000 } + ); + + const restored = await page2.evaluate(() => ({ + files: Object.keys(window.pgState.files), + main: window.pgState.files['main.das'].getValue(), + utils: window.pgState.files['utils.das']?.getValue(), + })); + expect(restored.files.sort()).toEqual(['main.das', 'utils.das']); + expect(restored.main).toContain('MAIN-SHARED'); + expect(restored.utils).toContain('UTILS-SHARED'); + await ctx.close(); +}); + +test('Shorten button replaces the URL with the is.gd response', async ({ playground }) => { + // Stub is.gd so the test is self-contained. + await playground.route('https://is.gd/create.php**', route => { + return route.fulfill({ + status: 200, + contentType: 'text/plain', + body: 'https://is.gd/ABCxyz', + }); + }); + + await waitTabsReady(playground); + await playground.evaluate(() => window.code.getDoc().setValue('// for shortening\n')); + await playground.locator('#share').click(); + await playground.locator('.pg-share__shorten').click(); + + await expect(playground.locator('.pg-share__url')).toHaveValue('https://is.gd/ABCxyz'); + await expect(playground.locator('.pg-share__shorten')).toHaveText(/shortened/); +}); + +test('Shorten button surfaces a failure', async ({ playground }) => { + await playground.route('https://is.gd/create.php**', route => route.fulfill({ status: 502, body: '' })); + + await waitTabsReady(playground); + await playground.locator('#share').click(); + const longUrl = await playground.locator('.pg-share__url').inputValue(); + await playground.locator('.pg-share__shorten').click(); + + await expect(playground.locator('.pg-share__shorten')).toHaveText(/failed/); + // Long URL untouched so the user still has a working share. + await expect(playground.locator('.pg-share__url')).toHaveValue(longUrl); +}); From fc23aad6c7c95183866543341b912ee6be2ea1d9 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 13:25:36 -0700 Subject: [PATCH 08/22] =?UTF-8?q?playground:=20hero=20=E2=86=97=20playgrou?= =?UTF-8?q?nd=20handoff=20backstop=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 7: the landing hero's "↗ playground" button has been there since the prior PR and emits `playground/index.html#code=` — the legacy single-file format. After the multi-file rework that hash shape still has to land cleanly in main.das. hero-handoff.spec.js opens the landing page, plants a marker into the hero's CodeMirror via the CM JS API, clicks the handoff button, captures the popup page, and asserts /playground/ comes up with a single main.das tab carrying the marker. Full suite: 25 specs locally (3 @wasm-tagged), 22 in the CI no-WASM tier. --- site/tests/playground/hero-handoff.spec.js | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 site/tests/playground/hero-handoff.spec.js diff --git a/site/tests/playground/hero-handoff.spec.js b/site/tests/playground/hero-handoff.spec.js new file mode 100644 index 0000000000..877bf93b43 --- /dev/null +++ b/site/tests/playground/hero-handoff.spec.js @@ -0,0 +1,46 @@ +// Hero ↗ playground handoff: the landing page's hero CodeMirror lets the +// user click ↗ playground to open /playground/ pre-populated with the +// current buffer via the legacy `#code=` hash format. This spec +// is the backstop that the legacy format still routes into main.das after +// the multi-file rework. + +const { test, expect } = require('./fixtures.js'); + +test('hero ↗ playground hands off the current buffer to /playground/', async ({ page, context }) => { + await page.goto('/'); + // The hero editor is a Forge-themed CodeMirror instance. + await page.locator('.forge-editor .CodeMirror').waitFor(); + await page.waitForFunction(() => typeof window.CodeMirror === 'function'); + + // Type a distinctive marker into the hero editor. + const editor = page.locator('.forge-editor .CodeMirror').first(); + await editor.click(); + // Use the CM API directly to avoid contenteditable quirks. + await page.evaluate(() => { + const cmEl = document.querySelector('.forge-editor .CodeMirror'); + const cm = cmEl.CodeMirror; + cm.setValue('// HERO-HANDOFF-MARKER\nprint("via hero")\n'); + }); + + // Clicking ↗ playground opens a new tab. + const popupPromise = context.waitForEvent('page'); + await page.locator('#hero-playground').click(); + const newPage = await popupPromise; + await newPage.waitForLoadState('domcontentloaded'); + + // Hash must be `#code=...` (legacy single-file format). + expect(newPage.url()).toContain('/playground/'); + expect(newPage.url()).toContain('#code='); + + // Playground populates pgState with main.das holding the marker. + await newPage.waitForFunction( + () => !!window.pgState && 'main.das' in window.pgState.files, + null, + { timeout: 30_000 } + ); + const files = await newPage.evaluate(() => Object.keys(window.pgState.files)); + expect(files).toEqual(['main.das']); + const mainText = await newPage.evaluate(() => window.pgState.files['main.das'].getValue()); + expect(mainText).toContain('HERO-HANDOFF-MARKER'); + await newPage.close(); +}); From 3ecda764cd123580a5e297283bb9068160594d32 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 14:01:52 -0700 Subject: [PATCH 09/22] playground: unified toolbar + draggable code/output splitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Single top toolbar: [tabs] [+] [examples] [share] [run] on the left, [clear] on the right. Drop the per-panel "output" label and the Tests dropdown. Random Sequence moves into Examples; other test samples are removed. - New .main_workspace flex-row contains [code | output] split by a draggable handle (11px hit area, 1px line, amber on drag). Position persists to localStorage; double-click resets to 50/50. - The first splitter pass looked correct but did nothing in real browsers: .main_col carried `flex: 1 1 0 !important`, which !important beats the splitter's inline `style.flex = '0 0 N%'`. Switch to a higher-specificity .main_workspace > .main_col selector with no !important so the inline override actually applies. - Splitter spec drives page.mouse.down/move/up and asserts the *measured* column width — the synthetic-event flavor passed even while the layout never moved. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/playground/forge-skin.css | 86 ++++++++++++++--- site/playground/index.html | 50 +++++----- site/playground/playground-splitter.js | 96 +++++++++++++++++++ site/tests/playground/dropdowns.spec.js | 27 +++--- site/tests/playground/splitter.spec.js | 72 ++++++++++++++ web/ui/samples/data.json | 23 +---- .../{tests => examples}/random_sequence.das | 0 web/ui/samples/tests/math.das | 76 --------------- web/ui/samples/tests/math_simple.das | 18 ---- web/ui/samples/tests/print.das | 8 -- web/ui/src/main.js | 14 ++- 11 files changed, 289 insertions(+), 181 deletions(-) create mode 100644 site/playground/playground-splitter.js create mode 100644 site/tests/playground/splitter.spec.js rename web/ui/samples/{tests => examples}/random_sequence.das (100%) delete mode 100644 web/ui/samples/tests/math.das delete mode 100644 web/ui/samples/tests/math_simple.das delete mode 100644 web/ui/samples/tests/print.das diff --git a/site/playground/forge-skin.css b/site/playground/forge-skin.css index 49ea6d1dc1..6f0b628088 100644 --- a/site/playground/forge-skin.css +++ b/site/playground/forge-skin.css @@ -30,12 +30,68 @@ body { .header_part a:hover { color: var(--amber) !important; } .header_part b { color: var(--fg); font-family: var(--font-mono); } -/* Main 2-column workspace */ -.main { background: var(--bg) !important; color: var(--fg) !important; } +/* Main workspace — one bordered container with a unified top toolbar and + * a [code | output] row below split by a draggable handle. */ +.main { + color: var(--fg) !important; + margin: 16px 32px !important; + border: 1px solid var(--rule); + border-radius: 8px; + background: var(--bg-2); + overflow: hidden; + display: flex !important; + flex-direction: column !important; + flex-wrap: nowrap !important; +} +.main_workspace { + display: flex; + flex-direction: row; + flex: 1 1 auto; + min-height: 0; +} +.main_col { + padding: 0 !important; + min-width: 0; + display: flex; + flex-direction: column; +} +/* Higher specificity than upstream's `.main_col { flex: 1 1 50vh; }` so the + * splitter's inline `style.flex = ...` can still override us. */ +.main_workspace > .main_col { + flex: 1 1 0; +} +.main_workspace > .main_col + .main_col { + border-left: 1px solid var(--rule); +} +.pg-splitter { + flex: 0 0 11px; + cursor: col-resize; + background: transparent; + position: relative; + touch-action: none; + user-select: none; +} +.pg-splitter::before { + content: ''; + position: absolute; + top: 0; bottom: 0; + left: 5px; + width: 1px; + background: var(--rule); + transition: background 80ms ease, width 80ms ease, left 80ms ease; +} +.pg-splitter:hover::before, +.pg-splitter:active::before, +.pg-splitter.is-dragging::before { + background: var(--amber); + width: 2px; + left: 4.5px; +} .main_header { - padding: 16px 32px !important; + padding: 10px 16px !important; border-bottom: 1px solid var(--rule); - background: var(--bg); + background: var(--bg-2); + flex: 0 0 auto; } .main_header_left, .main_header_right { gap: 14px !important; @@ -58,6 +114,9 @@ body { font-weight: 600; padding: 8px 16px !important; height: 32px !important; + min-width: 0 !important; + line-height: 1 !important; + white-space: nowrap !important; cursor: pointer; } .button_header:hover { background: #f4b04a !important; } @@ -219,17 +278,22 @@ body { } .pg-tab__add:hover { color: var(--amber); border-color: var(--amber-dim); } -.main_col { padding: 16px 0 !important; } .main_col_part { - padding: 0 24px !important; - max-width: 48vw; + padding: 0 !important; + max-width: none !important; + flex: 1 1 auto; + display: flex; + min-height: 0; } #code, #output { - height: 78vh !important; - margin-top: 14px !important; - border: 1px solid var(--rule) !important; - border-radius: 8px; + flex: 1 1 auto; + height: calc(100vh - 220px) !important; + min-height: 360px; + width: 100%; + margin-top: 0 !important; + border: 0 !important; + border-radius: 0 !important; background: var(--bg-2) !important; overflow: hidden; } diff --git a/site/playground/index.html b/site/playground/index.html index f834001ed3..8d67179fde 100644 --- a/site/playground/index.html +++ b/site/playground/index.html @@ -56,44 +56,40 @@ - - + + +
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    +
    +
    +
    +
    -
    +
    + +
    +
    +
    +
    -
    -
    +
    +
    - -
    -
    -
    -
    output
    -
    -
    - +
    +
    +
    +
    -
    -
    +
    +
    +
    +
    diff --git a/site/playground/playground-splitter.js b/site/playground/playground-splitter.js new file mode 100644 index 0000000000..f1f392efc9 --- /dev/null +++ b/site/playground/playground-splitter.js @@ -0,0 +1,96 @@ +/* Vertical splitter between code and output inside `.main_workspace`. Drag + * sets `flex: 0 0 %` on the left column; the right column keeps + * `flex: 1 1 0` so it absorbs the remainder. Position is persisted to + * localStorage and CodeMirror is refresh()'d so gutter + scroll caches + * reflect the new width. */ + +(function () { + const STORAGE_KEY = 'daslang.playground.splitLeftPct'; + const MIN_PCT = 15; + const MAX_PCT = 85; + + function applyPct(left, right, pct) { + left.style.flex = '0 0 ' + pct + '%'; + right.style.flex = '1 1 0'; + } + + function init() { + const ws = document.querySelector('.main_workspace'); + const cols = ws ? ws.querySelectorAll(':scope > .main_col') : null; + if (!ws || !cols || cols.length !== 2) return; + const [left, right] = cols; + + const handle = document.createElement('div'); + handle.className = 'pg-splitter'; + handle.setAttribute('role', 'separator'); + handle.setAttribute('aria-orientation', 'vertical'); + right.before(handle); + + const saved = parseFloat(localStorage.getItem(STORAGE_KEY)); + if (saved >= MIN_PCT && saved <= MAX_PCT) applyPct(left, right, saved); + + let dragging = false; + let refreshQueued = false; + let activePct = null; + + function refreshCM() { + if (refreshQueued || !window.code) return; + refreshQueued = true; + requestAnimationFrame(() => { + refreshQueued = false; + try { window.code.refresh(); } catch (_) {} + }); + } + + function onMove(e) { + if (!dragging) return; + const rect = ws.getBoundingClientRect(); + let pct = ((e.clientX - rect.left) / rect.width) * 100; + pct = Math.max(MIN_PCT, Math.min(MAX_PCT, pct)); + activePct = pct; + applyPct(left, right, pct); + refreshCM(); + e.preventDefault(); + } + + function endDrag() { + if (!dragging) return; + dragging = false; + handle.classList.remove('is-dragging'); + document.body.style.userSelect = ''; + document.body.style.cursor = ''; + document.removeEventListener('pointermove', onMove); + document.removeEventListener('pointerup', endDrag); + document.removeEventListener('pointercancel', endDrag); + if (activePct != null) { + localStorage.setItem(STORAGE_KEY, String(activePct.toFixed(2))); + } + refreshCM(); + } + + handle.addEventListener('pointerdown', (e) => { + if (e.button !== undefined && e.button !== 0) return; + dragging = true; + handle.classList.add('is-dragging'); + document.body.style.userSelect = 'none'; + document.body.style.cursor = 'col-resize'; + document.addEventListener('pointermove', onMove); + document.addEventListener('pointerup', endDrag); + document.addEventListener('pointercancel', endDrag); + e.preventDefault(); + }); + + // Double-click resets to 50/50. + handle.addEventListener('dblclick', () => { + applyPct(left, right, 50); + localStorage.removeItem(STORAGE_KEY); + refreshCM(); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/site/tests/playground/dropdowns.spec.js b/site/tests/playground/dropdowns.spec.js index 8b1c1a651b..54d9fce9ab 100644 --- a/site/tests/playground/dropdowns.spec.js +++ b/site/tests/playground/dropdowns.spec.js @@ -1,6 +1,7 @@ -// Examples + Tests swaps the editor buffer. Until phase 2 the change +// handler was missing from index.html, so picking an item did nothing. The +// spec guards against that regression. The Tests dropdown was removed in +// the unified-toolbar pass; Random Sequence now lives under Examples. const { test, expect } = require('./fixtures.js'); @@ -8,8 +9,7 @@ async function waitDropdownsPopulated(page) { // selectSample("examples", 0) runs at startup; once samplesData is set the // dropdown has > 1 option (the "Select..." sentinel plus the entries). await page.waitForFunction( - () => document.getElementById('examples').options.length > 1 - && document.getElementById('tests').options.length > 1, + () => document.getElementById('examples').options.length > 1, null, { timeout: 10_000 } ); @@ -33,22 +33,19 @@ test('Examples dropdown loads chosen sample into the editor', async ({ playgroun .toMatch(/def\s+\w+/); }); -test('Tests dropdown swaps the editor too', async ({ playground }) => { +test('Random Sequence sample is selectable from Examples', async ({ playground }) => { await waitDropdownsPopulated(playground); - - // Pick the first available test entry (skipping the "Select test" sentinel). - const firstTestLabel = await playground.evaluate(() => { - const sel = document.getElementById('tests'); - return sel.options[1]?.text; - }); - expect(firstTestLabel).toBeTruthy(); - const before = await editorText(playground); - await playground.locator('#tests').selectOption({ label: firstTestLabel }); + await playground.locator('#examples').selectOption({ label: 'Random Sequence' }); await expect.poll(() => editorText(playground), { timeout: 5_000 }) .not.toBe(before); }); +test('Tests dropdown is no longer in the DOM', async ({ playground }) => { + await waitDropdownsPopulated(playground); + expect(await playground.locator('#tests').count()).toBe(0); +}); + test('Dropdown resets to the "Select..." sentinel after a pick', async ({ playground }) => { await waitDropdownsPopulated(playground); diff --git a/site/tests/playground/splitter.spec.js b/site/tests/playground/splitter.spec.js new file mode 100644 index 0000000000..92a3d4f0a1 --- /dev/null +++ b/site/tests/playground/splitter.spec.js @@ -0,0 +1,72 @@ +// Vertical splitter between code and output. Drag sets `flex: 0 0 %` +// on the left column; the value persists via localStorage. + +const { test, expect } = require('./fixtures.js'); + +async function waitWorkspaceReady(page) { + await page.locator('.main_workspace > .main_col').first().waitFor(); + await page.locator('.pg-splitter').waitFor(); +} + +test('splitter sits between the two main_col panels', async ({ playground }) => { + await waitWorkspaceReady(playground); + const order = await playground.evaluate(() => + Array.from(document.querySelector('.main_workspace').children).map(e => e.className.trim().split(' ')[0]) + ); + expect(order).toEqual(['main_col', 'pg-splitter', 'main_col']); +}); + +test('real mouse drag resizes the left column and persists', async ({ playground }) => { + await waitWorkspaceReady(playground); + const handle = playground.locator('.pg-splitter'); + const ws = playground.locator('.main_workspace'); + const hbox = await handle.boundingBox(); + const wbox = await ws.boundingBox(); + const startX = hbox.x + hbox.width / 2; + const y = hbox.y + hbox.height / 2; + const targetX = wbox.x + wbox.width * 0.30; + // Drive a real pointer drag through Playwright — this catches CSS + // !important defeats that synthetic-event tests don't notice. + await playground.mouse.move(startX, y); + await playground.mouse.down(); + await playground.mouse.move(targetX, y, { steps: 10 }); + await playground.mouse.up(); + + const result = await playground.evaluate(() => { + const ws = document.querySelector('.main_workspace'); + const left = ws.querySelectorAll(':scope > .main_col')[0]; + const right = ws.querySelectorAll(':scope > .main_col')[1]; + return { + inlineFlex: left.style.flex, + leftPx: left.getBoundingClientRect().width, + rightPx: right.getBoundingClientRect().width, + wsPx: ws.getBoundingClientRect().width, + ls: localStorage.getItem('daslang.playground.splitLeftPct'), + }; + }); + expect(result.inlineFlex).toMatch(/^0 0 30(\.\d+)?%$/); + // The visual layout actually changed (this is what the prior synthetic + // test missed when CSS `!important` was beating the inline style). + const leftFrac = result.leftPx / result.wsPx; + expect(leftFrac).toBeGreaterThan(0.27); + expect(leftFrac).toBeLessThan(0.33); + expect(parseFloat(result.ls)).toBeCloseTo(30, 0); +}); + +test('double-click on splitter resets to 50/50', async ({ playground }) => { + await waitWorkspaceReady(playground); + await playground.evaluate(() => { + const ws = document.querySelector('.main_workspace'); + const left = ws.querySelectorAll(':scope > .main_col')[0]; + left.style.flex = '0 0 25%'; + localStorage.setItem('daslang.playground.splitLeftPct', '25'); + }); + await playground.locator('.pg-splitter').dblclick(); + const after = await playground.evaluate(() => { + const ws = document.querySelector('.main_workspace'); + const left = ws.querySelectorAll(':scope > .main_col')[0]; + return { flex: left.style.flex, ls: localStorage.getItem('daslang.playground.splitLeftPct') }; + }); + expect(after.flex).toMatch(/0 0 50%/); + expect(after.ls).toBeNull(); +}); diff --git a/web/ui/samples/data.json b/web/ui/samples/data.json index 98c856a8a2..5959ca423f 100644 --- a/web/ui/samples/data.json +++ b/web/ui/samples/data.json @@ -16,29 +16,10 @@ { "name" : "Macros (multi-file)", "files" : ["examples/macros/main.das", "examples/macros/for_loop_macro_mod.das"] - } - ], - - "tests" : [ - { - "name" : "Print", - "files" : ["tests/print.das"], - "correct_output" : ["4","","Hello world"] - }, - { - "name" : "Simple Math", - "files" : ["tests/math_simple.das"], - "correct_output" : ["-5","27","30603303"] - }, - { - "name" : "Math", - "files" : ["tests/math.das"], - "correct_output" : [] }, { "name" : "Random Sequence", - "files" : ["tests/random_sequence.das"], - "correct_output" : [["\"dictionary\",", null, "20"]] + "files" : ["examples/random_sequence.das"] } ] -} \ No newline at end of file +} diff --git a/web/ui/samples/tests/random_sequence.das b/web/ui/samples/examples/random_sequence.das similarity index 100% rename from web/ui/samples/tests/random_sequence.das rename to web/ui/samples/examples/random_sequence.das diff --git a/web/ui/samples/tests/math.das b/web/ui/samples/tests/math.das deleted file mode 100644 index b17be10606..0000000000 --- a/web/ui/samples/tests/math.das +++ /dev/null @@ -1,76 +0,0 @@ -options gen2 -require math - -options optimize = false - -[sideeffects] -def rightPolicyCeili(a : float) : int { - return ceili(a) -} - -[export] -def main { - assert(uint32_hash(1U) != 1U) - var seed : uint = 1U - let position : int = 1 - assert(min(float2(0.5, -1.), float2(-1., 0.5)) == float2(-1., -1.)) - assert(min(float4(0.5, -1., 0.5, -1.), float4(-1., 0.5, -1., 0.5)) == float4(-1, -1, -1, -1)) - assert(reflect(float3(1, 1, 0), float3(-1, 0, 0)) == float3(-1, 1, 0)) - assert(clamp(100., 0., 200.) == 100.) - assert(clamp(-100., 100., 200.) == 100.) - assert(clamp(1000., 100., 200.) == 200.) - assert(uint_noise_1D(position, seed) != seed) - assert(uint_noise_2D(int2(100, 100), seed) != seed) - assert(uint_noise_3D(int3(100, 100, 100), seed) != seed) - assert(dot(float2(0, 1), float2(1, 0)) == 0.) - assert(dot(float3(0, 1, 0), float3(1, 0, 1)) == 0.) - assert(dot(float4(0, 1, 0, 0), float4(1, 0, 1, 1)) == 0.) - // - assert(length(float2(0, 1)) == 1.) - assert(length(float3(0, 1, 0)) == 1.) - assert(length(float4(0, 0, 0, 1)) == 1.) - assert(length_sq(float2(0, 1)) == 1.) - assert(length_sq(float3(1, 0, 0)) == 1.) - assert(length_sq(float4(0, 1, 0, 0)) == 1.) - assert(cross(float3(0, 1, 0), float3(0, 0, 1)) == float3(1, 0, 0)) - // - assert(sin(0.) == 0.) - assert(cos(0.) == 1.) - assert(tan(0.) == 0.) - assert(asin(0.) == 0.) - assert(acos(1.) == 0.) - assert(abs(asin(sin(0.5)) - 0.5) < 0.0001) - assert(abs(acos(cos(0.5)) - 0.5) < 0.0001) - assert(sin(0.1) * sin(0.1) + cos(0.1) * cos(0.1) == 1.)//cos^2 + sin^2 == 1 - // - assert(atan2(0., 0.) == 0.) - assert(sqrt(4.) == 2.) - assert(pow(2., 2.) == 4.) - assert(ceil(1.1) == 2.) - assert(floor(1.1) == 1.) - assert(ceil(-1.1) == -1.) - assert(floor(-1.1) == -2.) - assert(exp(0.) == 1.) - assert(abs(log(exp(1.)) - 1.) < 1e-6) - assert(pow(0., 0.) == 1.) - assert(pow(2., 2.) == 4.) - assert(exp2(2.) == 4.) - assert(log2(4.) == 2.) - assert(abs(exp(1.) - 2.718281828459045f) < 1e-6) - assert(abs(log(2.718281828459045f) - 1.) < 1e-6) - assert(ceili(float2(0.5, -0.1)) == int2(1, 0)) - assert(floori(float2(0.5, -0.1)) == int2(0, -1)) - assert(roundi(float2(0.501, -0.1)) == int2(1, 0)) - assert(trunci(float2(0.501, -0.1)) == int2(0, 0)) - // and doubles - assert(ceili(0.5lf) == 1) - assert(floori(0.5lf) == 0) - assert(roundi(0.501lf) == 1) - assert(trunci(0.501lf) == 0) - // policy AOT - verify(rightPolicyCeili(1.2) == 2) - // constants - let mme = "{FLT_MIN} {FLT_MAX} {FLT_EPSILON}" - assert(mme == "1.1754944e-38 3.4028235e+38 1.1920929e-07") - return true -} \ No newline at end of file diff --git a/web/ui/samples/tests/math_simple.das b/web/ui/samples/tests/math_simple.das deleted file mode 100644 index c8aa716cb9..0000000000 --- a/web/ui/samples/tests/math_simple.das +++ /dev/null @@ -1,18 +0,0 @@ -options gen2 -[export] -def main { - var a = 5 - var b = -10 - var c = a + b - print("{c}\n") - - a = 3 - b = 9 - c = a * b - print("{c}\n") - - a = 13231 - b = 2313 - c = a * b - print("{c}\n") -} \ No newline at end of file diff --git a/web/ui/samples/tests/print.das b/web/ui/samples/tests/print.das deleted file mode 100644 index f7d8e2e07e..0000000000 --- a/web/ui/samples/tests/print.das +++ /dev/null @@ -1,8 +0,0 @@ -options gen2 -[export] -def main { - var c = 4 - print("{c}\n") - print("\n") - print("Hello world\n") -} \ No newline at end of file diff --git a/web/ui/src/main.js b/web/ui/src/main.js index 7f622c225c..baf12cec00 100644 --- a/web/ui/src/main.js +++ b/web/ui/src/main.js @@ -17,7 +17,7 @@ pageInit = function () { editorOutput = document.getElementById("output"); sampleList["examples"] = document.getElementById("examples"); - sampleList["tests"] = document.getElementById("tests"); + sampleList["tests"] = document.getElementById("tests"); // may be null when the Tests dropdown is removed @@ -37,11 +37,13 @@ pageInit = function () { ["example","test"].forEach(function (n) { let ll = document.getElementById(n+"s"); + if (!ll) return; // the Tests dropdown was removed; skip silently while (ll.firstChild) { ll.removeChild(ll.lastChild); } - for (let i=0;i Date: Wed, 13 May 2026 14:06:19 -0700 Subject: [PATCH 10/22] site: link dasProfile source in benchmarks meta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "source: github.com/borisbat/dasProfile" line under the benchmarks panel was plain text. Wrap it in an with a new `.forge-bench__source` style — amber-dim by default (subtle enough to sit next to fg-faint meta text), full amber + underline on hover. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/files/forge.css | 9 +++++++++ site/index.html | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/site/files/forge.css b/site/files/forge.css index 20ffa539a9..5d2f80298b 100644 --- a/site/files/forge.css +++ b/site/files/forge.css @@ -418,6 +418,15 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi color: var(--fg-faint); } .forge-bench__meta > div { line-height: 1.6; } +.forge-bench__source { + color: var(--amber-dim); + border-bottom: 1px solid transparent; + transition: color 80ms ease, border-color 80ms ease; +} +.forge-bench__source:hover { + color: var(--amber); + border-bottom-color: var(--amber-dim); +} .forge-bench__panel { background: var(--bg); border: 1px solid var(--rule); diff --git a/site/index.html b/site/index.html index 6795e7ad37..97c924195a 100644 --- a/site/index.html +++ b/site/index.html @@ -138,7 +138,7 @@

    The fastest interpreter, and a JIT that often beats C++.
    · captured on Apple M1 Max · daslang 0.6.2
    · normalised to fastest entry in the row
    -
    · source: github.com/borisbat/dasProfile
    +

    From 5c8a7378851ff9bec5b4cc46caad87d23840d44f Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 14:10:52 -0700 Subject: [PATCH 11/22] blog: Disqus comments under each post MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hook up the legacy dascf-blog Disqus account (shortname "https-borisbat-github-io-dascf-blog") under every blog post. Identifier pins to the post slug so threads stay stable across dev/prod URLs and future renames; old comments stay tied to the original blog URLs in the Disqus admin (can be migrated later via Disqus' URL Mapper). - build_blog.py: render_comments() emits a .forge-post__comments section after the article on post pages only (not index/news/ changelist). - forge.css: section wrapper styled to match Forge (top divider, amber "§ comments" label, dark bg-2 thread container with rule border). `color-scheme: dark` on :root hints Disqus to render its Auto theme as dark. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/blog/build_blog.py | 39 +++++++++++++++++++++++++++++++++++++++ site/files/forge.css | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/site/blog/build_blog.py b/site/blog/build_blog.py index 1539dea85d..266330ec30 100644 --- a/site/blog/build_blog.py +++ b/site/blog/build_blog.py @@ -50,6 +50,13 @@ KEY_RE = re.compile(r'^([a-zA-Z_]+):\s*(.*)$') +# Disqus shortname carried over from the legacy borisbat.github.io/dascf-blog +# install — the same account, but new threads under daslang.io URLs (Disqus +# resolves identifier first, then page.url). Old comments stay under the +# original blog URLs in the Disqus admin and can be migrated via the +# Disqus URL Mapper if desired. +DISQUS_SHORTNAME = 'https-borisbat-github-io-dascf-blog' + @dataclass class Entry: @@ -248,6 +255,7 @@ def render_post(entry: Entry, prev: Entry | None, next: Entry | None, md, posts_ else: parts.append('') nav_html = f'
    {parts[0]}{parts[1]}
    ' + comments_html = render_comments(entry) return f"""
    +{comments_html}""" + + +def render_comments(entry: Entry) -> str: + """Disqus thread, pinned by post slug as identifier so threads survive + URL changes (dev/prod, future renames). The embed script reads + `disqus_config` for the URL + identifier.""" + slug = html.escape(entry.slug, quote=True) + return f"""
    + +
    """ diff --git a/site/files/forge.css b/site/files/forge.css index 5d2f80298b..d63a816480 100644 --- a/site/files/forge.css +++ b/site/files/forge.css @@ -29,6 +29,9 @@ /* Type */ --font-sans: "Inter Tight", "Inter", -apple-system, system-ui, sans-serif; --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace; + + /* Hint embedded widgets (Disqus, GitHub, etc.) to render in dark mode. */ + color-scheme: dark; } /* ───── Reset / base ───── */ @@ -774,6 +777,38 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi .forge-post-nav a { color: var(--fg-dim); } .forge-post-nav a:hover { color: var(--amber); } +/* ───── Comments (Disqus) ───── */ + +/* Disqus loads in a sandboxed iframe so we can only style the wrapper — + * the comment styling itself comes from the Disqus admin panel + * (Appearance → Color scheme: Auto). The `color-scheme: dark` hint on + * :root makes Auto resolve to dark. */ +.forge-post__comments { + padding: 56px 0 88px; + border-top: 1px solid var(--rule); + background: var(--bg); +} +.forge-post__comments .forge-section-label { margin-bottom: 18px; } +.forge-post__comments #disqus_thread { + margin-top: 20px; + background: var(--bg-2); + border: 1px solid var(--rule); + border-radius: 8px; + padding: 16px 20px; + min-height: 80px; +} +.forge-post__comments noscript { + display: block; + margin-top: 16px; + color: var(--fg-faint); + font-family: var(--font-mono); + font-size: 12.5px; +} +.forge-post__comments noscript a { + color: var(--amber); + border-bottom: 1px solid var(--amber-dim); +} + /* Changelist: same layout as blog listing, wider. */ .forge-changelist { margin-top: 24px; } From 6f67001b7d6d4b2ab4e152a65065c62e8556bb99 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 14:22:17 -0700 Subject: [PATCH 12/22] site: mobile fallbacks for the hero + playground Two surfaces depend on a real keyboard and (for playground) a 5 MB WASM bundle that's mediocre on a phone. On <768px viewports we now: - Landing hero: hide the live CodeMirror panel + sample tabs + run / playground buttons. A static
    
      block, tokenized by the existing highlight.js, takes its place. The
      terminal frame (dots + filename) is kept so the visual identity stays
      intact. forge.js short-circuits the CM init on mobile so the editor
      doesn't materialize behind the static block.
    - /playground/: a synchronous mobile detector tags  with
      `.is-pg-mobile` so CSS hides the IDE chrome, and on DOMContentLoaded
      overrides `pageInit` so the body.onload bootstrap renders a Forge-
      styled "open on a laptop" notice instead of fetching daslang_static.js
      / .wasm. Spec asserts zero requests for either resource on mobile.
    
    The (already-hidden-on-mobile) playground nav link in .forge-nav__links
    stays as-is; the redundant "playground" version label on the playground
    page is also hidden when the notice is showing.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) 
    ---
     site/files/forge.css                      | 25 +++++++++-
     site/files/forge.js                       | 20 ++++++--
     site/index.html                           | 10 ++++
     site/playground/forge-skin.css            | 56 +++++++++++++++++++++++
     site/playground/index.html                | 40 ++++++++++++++++
     site/tests/playground/mobile-gate.spec.js | 36 +++++++++++++++
     6 files changed, 180 insertions(+), 7 deletions(-)
     create mode 100644 site/tests/playground/mobile-gate.spec.js
    
    diff --git a/site/files/forge.css b/site/files/forge.css
    index d63a816480..aa189e8afc 100644
    --- a/site/files/forge.css
    +++ b/site/files/forge.css
    @@ -848,6 +848,22 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi
         .forge-nav__links { gap: 18px; }
     }
     
    +/* Static hero fallback for mobile (replaces the CodeMirror live demo).
    + * Hidden on desktop; the @media below flips visibility and also hides
    + * the interactive editor + sample tabs + output + run/playground buttons. */
    +.forge-hero__code {
    +    display: none;
    +    margin: 0;
    +    padding: 16px 18px;
    +    background: transparent;
    +    color: var(--fg);
    +    font-family: var(--font-mono);
    +    font-size: 13px;
    +    line-height: 1.55;
    +    overflow-x: auto;
    +    white-space: pre;
    +}
    +
     /* Mobile <768 */
     @media (max-width: 767px) {
         .forge-container { padding: 0 20px; }
    @@ -858,8 +874,13 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi
         .forge-btn-primary, .forge-btn-secondary { justify-content: center; }
         .forge-stats { gap: 18px; flex-wrap: wrap; }
         .forge-stat { min-width: 45%; }
    -    .forge-editor { height: 240px; }
    -    .forge-editor .CodeMirror { font-size: 11px; }
    +    /* Hero swaps from interactive CodeMirror to a static highlighted block. */
    +    .forge-editor,
    +    .forge-output,
    +    .forge-sample-tabs,
    +    #hero-run,
    +    #hero-playground { display: none !important; }
    +    .forge-hero__code { display: block; }
         .forge-nav__links { display: none; }
         .forge-section { padding: 56px 0; }
         .forge-h2 { font-size: 26px; }
    diff --git a/site/files/forge.js b/site/files/forge.js
    index 695a5964e1..abadef08e9 100644
    --- a/site/files/forge.js
    +++ b/site/files/forge.js
    @@ -394,12 +394,22 @@ def main() {
             }).join('');
         }
     
    +    // Mobile shows a static highlighted hero (in index.html, .forge-hero__code).
    +    // CSS hides the .forge-editor / sample tabs / run + playground buttons, so
    +    // we skip the heavy CM init + sample wiring entirely — keeps the static
    +    // block from getting a hidden CodeMirror layered on top of it.
    +    function isMobile() {
    +        return window.matchMedia('(max-width: 767px)').matches;
    +    }
    +
         function init() {
    -        wireSampleTabs();
    -        initHeroEditor();
    -        showSample('hello');
    -        wireRunButton();
    -        wirePlaygroundButton();
    +        if (!isMobile()) {
    +            wireSampleTabs();
    +            initHeroEditor();
    +            showSample('hello');
    +            wireRunButton();
    +            wirePlaygroundButton();
    +        }
             wireInstallTabs();
             loadBench();
             loadNews();
    diff --git a/site/index.html b/site/index.html
    index 97c924195a..d33920880d 100644
    --- a/site/index.html
    +++ b/site/index.html
    @@ -105,6 +105,16 @@ 

    + +
    [export]
    +def main() {
    +    let name = "world"
    +    print("hello, {name}\n")
    +    for (i in range(3)) {
    +        print("  tick {i}\n")
    +    }
    +}
    $ daslang run hello.das
    hello, world
    diff --git a/site/playground/forge-skin.css b/site/playground/forge-skin.css index 6f0b628088..d08b067d9d 100644 --- a/site/playground/forge-skin.css +++ b/site/playground/forge-skin.css @@ -319,3 +319,59 @@ body { footer { color: var(--fg-faint) !important; padding: 24px 32px; background: var(--bg); border-top: 1px solid var(--rule); } .footer_p { font-size: 12px !important; font-family: var(--font-mono); } + +/* ───── Mobile notice — replaces the WASM IDE on narrow viewports ───── */ +html.is-pg-mobile .main, +html.is-pg-mobile .header, +html.is-pg-mobile footer, +html.is-pg-mobile .forge-nav__version { display: none !important; } +html.is-pg-mobile body { background: var(--bg); } + +#pg-mobile-notice { + padding: 64px 0 96px; + background: var(--bg); + color: var(--fg); +} +.pg-mobile-notice__title { + margin: 18px 0 14px; + font-family: var(--font-sans); + font-size: 36px; + line-height: 1.15; + letter-spacing: -0.01em; + color: var(--fg); +} +.pg-mobile-notice__lede { + color: var(--fg-dim); + font-size: 15px; + line-height: 1.6; + max-width: 540px; +} +.pg-mobile-notice__ctas { + margin-top: 24px; + display: flex; + flex-direction: column; + gap: 10px; +} +.pg-mobile-notice__btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--amber); + color: var(--bg); + font-family: var(--font-mono); + font-size: 13px; + font-weight: 600; + padding: 10px 18px; + border-radius: 4px; + text-decoration: none; +} +.pg-mobile-notice__btn:hover { background: #f4b04a; } +.pg-mobile-notice__btn--ghost { + background: transparent; + color: var(--fg-dim); + border: 1px solid var(--rule); +} +.pg-mobile-notice__btn--ghost:hover { + color: var(--amber); + border-color: var(--amber-dim); +} diff --git a/site/playground/index.html b/site/playground/index.html index 8d67179fde..9fef36da42 100644 --- a/site/playground/index.html +++ b/site/playground/index.html @@ -22,6 +22,25 @@ + + + + + diff --git a/site/tests/playground/mobile-gate.spec.js b/site/tests/playground/mobile-gate.spec.js new file mode 100644 index 0000000000..0f3483cc48 --- /dev/null +++ b/site/tests/playground/mobile-gate.spec.js @@ -0,0 +1,36 @@ +// Mobile gate: at narrow viewports, /playground/ must NOT load the WASM +// bundle or initialize the CodeMirror IDE. Instead, render the "open on a +// laptop" notice. Guards against regressing the WASM short-circuit. + +const { test, expect } = require('@playwright/test'); + +test.use({ viewport: { width: 390, height: 800 } }); + +test('mobile playground shows the notice and does not load WASM', async ({ page }) => { + const requested = []; + page.on('request', (r) => requested.push(r.url())); + + await page.goto('/playground/?nocache=mobile', { waitUntil: 'load' }); + + // The is-pg-mobile root class is the gate signal. + await expect.poll(() => page.evaluate(() => document.documentElement.classList.contains('is-pg-mobile'))) + .toBe(true); + + // Notice should be visible, .main hidden by CSS. + await expect(page.locator('#pg-mobile-notice')).toBeVisible(); + await expect(page.locator('.main')).toBeHidden(); + + // Neither the WASM nor the loader JS should ever have been requested. + const heavy = requested.filter(u => + u.includes('daslang_static') || u.endsWith('.wasm')); + expect(heavy).toEqual([]); +}); + +test('desktop playground does not show the mobile notice', async ({ page }) => { + await page.setViewportSize({ width: 1400, height: 900 }); + await page.goto('/playground/?nocache=desktop', { waitUntil: 'domcontentloaded' }); + await page.locator('.CodeMirror').waitFor({ timeout: 10_000 }); + expect(await page.evaluate(() => document.documentElement.classList.contains('is-pg-mobile'))) + .toBe(false); + await expect(page.locator('#pg-mobile-notice')).toBeHidden(); +}); From 6fe8b01c4c9b2e0a8a6865be1bf4b38e2211c05d Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 14:53:49 -0700 Subject: [PATCH 13/22] =?UTF-8?q?site:=20audit=20follow-up=20=E2=80=94=20m?= =?UTF-8?q?obile=20nav,=20blog=20grid,=20dead=20code=20+=20bits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Punch list from a full-site audit: Block fixes - Mobile nav: ≡ hamburger button in the right-side chip strip on every Forge nav (landing, downloads, blog template, playground). Toggles `.forge-nav.is-open`, which reveals `.forge-nav__links` as an absolutely-positioned dropdown panel under the nav. Phone users can now reach docs / benchmarks / downloads / blog / community. - Mobile blog/changelist grid: `.forge-blog-item` carried only its desktop `140px 90px 1fr` shape, which squeezed titles into ~94px at 390px viewport (one word per line). Added a mobile override stacking date+tag on row 1, body on row 2. - Missing image in blog/instruments: `/images/call_tree.PNG` is a Hexo-era absolute path with no asset on this site. Rewrote the sentence to drop the inline image. Cleanup - Removed dead `runTests`/`runTest`/`outputPool` machinery from web/ui/src/main.js (and the mirrored site/playground/main.js) — the Tests dropdown that drove them is gone. Simplified the dropdown populate loop now that only Examples remains. - Deleted orphan CodeMirror files from web/ui/src/: codemirror.css, codemirror.min.js, eclipse.css. The playground now loads the Forge-themed CodeMirror bundle from /files/cm/ — these were ~232 KB of dead bytes copied per CI deploy. site/.gitignore entries for the same files dropped. - Hide `.forge-nav__version` at <480 px so the version label stops crowding [github ↗] on narrow phones. - Fixed "0.6.0-RC1 0.6.0-RC2" double label in the 2026-02-28 news entry (left over from a partial RC1→RC2 edit). - Sphinx `version` was '0.6' while the site shows v0.6.2; bumped to '0.6.2' to match. - hero-handoff.spec.js: added `retries: 1` for the two-window race that flakes under high parallelism (passes alone, fails ~1/3 in full suite). Suite is green-on-retry. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/source/conf.py | 2 +- site/.gitignore | 3 - ...aslang-0-6-is-just-around-the-corner-st.md | 2 +- site/blog/_posts/instruments.md | 2 +- site/blog/template.html | 2 + site/downloads.html | 2 + site/files/forge.css | 59 ++- site/index.html | 2 + site/playground/index.html | 2 + site/tests/playground/hero-handoff.spec.js | 5 + web/ui/src/codemirror.css | 351 ------------------ web/ui/src/codemirror.min.js | 86 ----- web/ui/src/eclipse.css | 52 --- web/ui/src/main.js | 106 +----- 14 files changed, 90 insertions(+), 586 deletions(-) delete mode 100644 web/ui/src/codemirror.css delete mode 100644 web/ui/src/codemirror.min.js delete mode 100644 web/ui/src/eclipse.css diff --git a/doc/source/conf.py b/doc/source/conf.py index 5ccb17bfb8..b815af2c99 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,7 +60,7 @@ # built documents. # # The short X.Y version. -version = u'0.6' +version = u'0.6.2' # The full version, including alpha/beta/rc tags. release = u'0.6.2' diff --git a/site/.gitignore b/site/.gitignore index 29ec0d0c35..1da12d8ae1 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -12,9 +12,6 @@ files/news.json # CI rebuilds these from source on every publish. To preview locally: # cp web/ui/src/* site/playground/ # cp web/output/daslang_static.{js,wasm} site/playground/ -playground/codemirror.css -playground/codemirror.min.js -playground/eclipse.css playground/jquery-3.6.0.min.js playground/main.css playground/main.js diff --git a/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md b/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md index b1325ee02c..1e3247ece1 100644 --- a/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md +++ b/site/_news/2026-02-28-daslang-0-6-is-just-around-the-corner-st.md @@ -1,6 +1,6 @@ --- date: 2026-02-28 tag: 0.6 -title: Daslang 0.6 is just around the corner! Stay tuned for the official release. You can preview release candidate 0.6.0-RC1 0.6.0-RC2 here. +title: Daslang 0.6 is just around the corner! Stay tuned for the official release. You can preview release candidate 0.6.0-RC1 here. link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.0-RC1 --- diff --git a/site/blog/_posts/instruments.md b/site/blog/_posts/instruments.md index adea920617..fa2d0d8296 100644 --- a/site/blog/_posts/instruments.md +++ b/site/blog/_posts/instruments.md @@ -84,7 +84,7 @@ In this implementation I choose to collect entries, so that they can be later an entering : bool // entering or leaving the function This structure happens to correspond well with the [Google Chrome Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview) -Open Google Chrome, Press F12, go to performance section, load the log, enjoy the view ![Call tree](/images/call_tree.PNG) +Open Google Chrome, press F12, go to the performance tab, and load the log — you get the standard Chrome call-tree view. There are other tools which accept this format as well. By default profiler outputs full call graph. diff --git a/site/blog/template.html b/site/blog/template.html index 29f16cfe0a..6a7c6b7b09 100644 --- a/site/blog/template.html +++ b/site/blog/template.html @@ -32,6 +32,8 @@
    + v0.6.2 github ↗ install diff --git a/site/downloads.html b/site/downloads.html index 1304928e3c..353ae938ba 100644 --- a/site/downloads.html +++ b/site/downloads.html @@ -32,6 +32,8 @@
    + v0.6.2 github ↗ install diff --git a/site/files/forge.css b/site/files/forge.css index aa189e8afc..b4be173291 100644 --- a/site/files/forge.css +++ b/site/files/forge.css @@ -864,9 +864,49 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi white-space: pre; } +/* Mobile nav: hamburger button toggles a dropdown panel containing the + * normally-inline `.forge-nav__links`. Burger is hidden on desktop. The + * toggle is a plain inline onclick on the button that flips `.is-open` + * on the nav element. */ +.forge-nav { position: relative; } +.forge-nav__burger { + display: none; + cursor: pointer; + background: transparent; + border: 1px solid var(--rule); + color: var(--fg-dim); + font-family: var(--font-mono); + font-size: 18px; + line-height: 1; + padding: 4px 12px; + border-radius: 4px; + transition: color 80ms ease, border-color 80ms ease; +} +.forge-nav__burger:hover { + color: var(--amber); + border-color: var(--amber-dim); +} + /* Mobile <768 */ @media (max-width: 767px) { .forge-container { padding: 0 20px; } + .forge-nav__burger { display: inline-flex; align-items: center; order: -1; } + .forge-nav__links { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--bg-2); + border-bottom: 1px solid var(--rule); + padding: 16px 24px 20px; + flex-direction: column; + align-items: stretch; + gap: 14px; + font-size: 14px; + z-index: 20; + } + .forge-nav.is-open .forge-nav__links { display: flex; } .forge-hero { padding: 48px 0 64px; } .forge-hero h1 { font-size: 38px; } .forge-hero__lede { font-size: 15px; } @@ -881,7 +921,6 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi #hero-run, #hero-playground { display: none !important; } .forge-hero__code { display: block; } - .forge-nav__links { display: none; } .forge-section { padding: 56px 0; } .forge-h2 { font-size: 26px; } .forge-bench__grid, @@ -897,6 +936,24 @@ button { font: inherit; border: 0; background: none; color: inherit; cursor: poi .forge-news__date { grid-area: date; } .forge-news__title { grid-area: title; } .forge-news__tag { grid-area: tag; } + /* Blog index + changelist rows: stack date+tag on row 1, body on row 2. + * Otherwise the 140px date column squeezes the title into ~94px and + * each word wraps to a new line. */ + .forge-blog-item { + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + gap: 6px 12px; + } + .forge-blog-item__date { grid-row: 1; grid-column: 1; } + .forge-blog-item__tag { grid-row: 1; grid-column: 2; justify-self: end; } + .forge-blog-item__title { grid-row: 2; grid-column: 1 / -1; } + .forge-blog-item > div:nth-child(3):not([class]) { grid-row: 2; grid-column: 1 / -1; } +} + +/* Very narrow phones: drop the redundant version chip so [github ↗] + + * [install] don't collide with it. */ +@media (max-width: 479px) { + .forge-nav__version { display: none; } } /* Tablet bench grid adjustment */ diff --git a/site/index.html b/site/index.html index d33920880d..a8532a79d3 100644 --- a/site/index.html +++ b/site/index.html @@ -43,6 +43,8 @@
    + v0.6.2 github ↗ install diff --git a/site/playground/index.html b/site/playground/index.html index 9fef36da42..088e12dba0 100644 --- a/site/playground/index.html +++ b/site/playground/index.html @@ -59,6 +59,8 @@
    + playground github ↗ install diff --git a/site/tests/playground/hero-handoff.spec.js b/site/tests/playground/hero-handoff.spec.js index 877bf93b43..a22d2911cc 100644 --- a/site/tests/playground/hero-handoff.spec.js +++ b/site/tests/playground/hero-handoff.spec.js @@ -6,6 +6,11 @@ const { test, expect } = require('./fixtures.js'); +// Two-window handoff (new page via `context.waitForEvent('page')`) is flaky +// under high parallelism while another test in a sibling worker is also +// holding focus. One retry on the rare race is enough. +test.describe.configure({ retries: 1 }); + test('hero ↗ playground hands off the current buffer to /playground/', async ({ page, context }) => { await page.goto('/'); // The hero editor is a Forge-themed CodeMirror instance. diff --git a/web/ui/src/codemirror.css b/web/ui/src/codemirror.css deleted file mode 100644 index a70774fa64..0000000000 --- a/web/ui/src/codemirror.css +++ /dev/null @@ -1,351 +0,0 @@ -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - - font-family: Consolas, Menlo, Monaco, monospace; - height: 100%; - width: 100%; - color: black; - direction: ltr; - overflow: scroll; - - border: 1px solid #8d8d8d; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre.CodeMirror-line, -.CodeMirror pre.CodeMirror-line-like { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; -} - -.CodeMirror-guttermarker { color: black; } -.CodeMirror-guttermarker-subtle { color: #999; } - -/* CURSOR */ - -.CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0 !important; - background: #7e7; -} -.cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; -} -.cm-fat-cursor .CodeMirror-line::selection, -.cm-fat-cursor .CodeMirror-line > span::selection, -.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; } -.cm-fat-cursor .CodeMirror-line::-moz-selection, -.cm-fat-cursor .CodeMirror-line > span::-moz-selection, -.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; } -.cm-fat-cursor { caret-color: transparent; } -@-moz-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@-webkit-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} - -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror-overwrite .CodeMirror-cursor {} - -.cm-tab { display: inline-block; text-decoration: inherit; } - -.CodeMirror-rulers { - position: absolute; - left: 0; right: 0; top: -50px; bottom: 0; - overflow: hidden; -} -.CodeMirror-ruler { - border-left: 1px solid #ccc; - top: 0; bottom: 0; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} -.cm-strikethrough {text-decoration: line-through;} - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable, -.cm-s-default .cm-punctuation, -.cm-s-default .cm-property, -.cm-s-default .cm-operator {} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -.CodeMirror-composing { border-bottom: 2px solid; } - -/* Default styles for common addons */ - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} -.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - position: relative; - overflow: hidden; - background: white; -} - -.CodeMirror-scroll { - overflow: scroll !important; /* Things will break if this is overridden */ - /* 50px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -50px; margin-right: -50px; - padding-bottom: 50px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; - z-index: 0; -} -.CodeMirror-sizer { - position: relative; - border-right: 50px solid transparent; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actual scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; - outline: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - min-height: 100%; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - display: inline-block; - vertical-align: top; - margin-bottom: -50px; -} -.CodeMirror-gutter-wrapper { - position: absolute; - z-index: 4; - background: none !important; - border: none !important; -} -.CodeMirror-gutter-background { - position: absolute; - top: 0; bottom: 0; - z-index: 4; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} -.CodeMirror-gutter-wrapper ::selection { background-color: transparent } -.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } - -.CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ -} -.CodeMirror pre.CodeMirror-line, -.CodeMirror pre.CodeMirror-line-like { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; - -webkit-font-variant-ligatures: contextual; - font-variant-ligatures: contextual; -} -.CodeMirror-wrap pre.CodeMirror-line, -.CodeMirror-wrap pre.CodeMirror-line-like { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - padding: 0.1px; /* Force widget margins to stay inside of the container */ -} - -.CodeMirror-widget {} - -.CodeMirror-rtl pre { direction: rtl; } - -.CodeMirror-code { - outline: none; -} - -/* Force content-box sizing for the elements where we expect it */ -.CodeMirror-scroll, -.CodeMirror-sizer, -.CodeMirror-gutter, -.CodeMirror-gutters, -.CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} - -.CodeMirror-cursor { - position: absolute; - pointer-events: none; -} -.CodeMirror-measure pre { position: static; } - -div.CodeMirror-cursors { - visibility: hidden; - position: relative; - z-index: 3; -} -div.CodeMirror-dragcursors { - visibility: visible; -} - -.CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } -.CodeMirror-crosshair { cursor: crosshair; } -.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } -.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } - -.cm-searching { - background-color: #ffa; - background-color: rgba(255, 255, 0, .4); -} - -/* Used to force a border model for a node */ -.cm-force-border { padding-right: .1px; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } -} - -/* See issue #2901 */ -.cm-tab-wrap-hack:after { content: ''; } - -/* Help users use markselection to safely style text background */ -span.CodeMirror-selectedtext { background: none; } - - diff --git a/web/ui/src/codemirror.min.js b/web/ui/src/codemirror.min.js deleted file mode 100644 index e05e94fca6..0000000000 --- a/web/ui/src/codemirror.min.js +++ /dev/null @@ -1,86 +0,0 @@ - -!function(a){if("object"==typeof exports&&"object"==typeof module)module.exports=a();else{if("function"==typeof define&&define.amd)return define([],a);this.CodeMirror=a()}}(function(){"use strict";function v(a,b){if(!(this instanceof v))return new v(a,b);this.options=b=b?hg(b):{},hg(Ad,b,!1),I(b);var c=b.value;"string"==typeof c&&(c=new af(c,b.mode,null,b.lineSeparator)),this.doc=c;var g=new v.inputStyles[b.inputStyle](this),h=this.display=new w(a,c,g);h.wrapper.CodeMirror=this,E(this),C(this),b.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),b.autofocus&&!n&&h.input.focus(),M(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:!1,cutIncoming:!1,selectingText:!1,draggingText:!1,highlight:new Yf,keySeq:null,specialChars:null};var i=this;d&&11>e&&setTimeout(function(){i.display.input.reset(!0)},20),pc(this),Bg(),Vb(this),this.curOp.forceUpdate=!0,ef(this,c),b.autofocus&&!n||i.hasFocus()?setTimeout(ig(Zc,this),20):$c(this);for(var j in Bd)Bd.hasOwnProperty(j)&&Bd[j](this,b[j],Dd);R(this),b.finishInit&&b.finishInit(this);for(var k=0;ke&&(h.gutters.style.zIndex=-1,h.scroller.style.paddingRight=0),f||a&&n||(h.scroller.draggable=!0),b&&(b.appendChild?b.appendChild(h.wrapper):b(h.wrapper)),h.viewFrom=h.viewTo=c.first,h.reportedViewFrom=h.reportedViewTo=c.first,h.view=[],h.renderedView=null,h.externalMeasured=null,h.viewOffset=0,h.lastWrapHeight=h.lastWrapWidth=0,h.updateLineNumbers=null,h.nativeBarWidth=h.barHeight=h.barWidth=0,h.scrollbarsClipped=!1,h.lineNumWidth=h.lineNumInnerWidth=h.lineNumChars=null,h.alignWidgets=!1,h.cachedCharWidth=h.cachedTextHeight=h.cachedPaddingH=null,h.maxLine=null,h.maxLineLength=0,h.maxLineChanged=!1,h.wheelDX=h.wheelDY=h.wheelStartX=h.wheelStartY=null,h.shift=!1,h.selForContextMenu=null,h.activeTouch=null,g.init(h)}function x(a){a.doc.mode=v.getMode(a.options,a.doc.modeOption),y(a)}function y(a){a.doc.iter(function(a){a.stateAfter&&(a.stateAfter=null),a.styles&&(a.styles=null)}),a.doc.frontier=a.doc.first,ib(a,100),a.state.modeGen++,a.curOp&&ic(a)}function z(a){a.options.lineWrapping?(xg(a.display.wrapper,"CodeMirror-wrap"),a.display.sizer.style.minWidth="",a.display.sizerWidth=null):(wg(a.display.wrapper,"CodeMirror-wrap"),H(a)),B(a),ic(a),Fb(a),setTimeout(function(){N(a)},100)}function A(a){var b=Rb(a.display),c=a.options.lineWrapping,d=c&&Math.max(5,a.display.scroller.clientWidth/Sb(a.display)-3);return function(e){if(we(a.doc,e))return 0;var f=0;if(e.widgets)for(var g=0;gb.maxLineLength&&(b.maxLineLength=c,b.maxLine=a)})}function I(a){var b=dg(a.gutters,"CodeMirror-linenumbers");-1==b&&a.lineNumbers?a.gutters=a.gutters.concat(["CodeMirror-linenumbers"]):b>-1&&!a.lineNumbers&&(a.gutters=a.gutters.slice(0),a.gutters.splice(b,1))}function J(a){var b=a.display,c=b.gutters.offsetWidth,d=Math.round(a.doc.height+nb(a.display));return{clientHeight:b.scroller.clientHeight,viewHeight:b.wrapper.clientHeight,scrollWidth:b.scroller.scrollWidth,clientWidth:b.scroller.clientWidth,viewWidth:b.wrapper.clientWidth,barLeft:a.options.fixedGutter?c:0,docHeight:d,scrollHeight:d+pb(a)+b.barHeight,nativeBarWidth:b.nativeBarWidth,gutterWidth:c}}function K(a,b,c){this.cm=c;var f=this.vert=pg("div",[pg("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),g=this.horiz=pg("div",[pg("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");a(f),a(g),Jf(f,"scroll",function(){f.clientHeight&&b(f.scrollTop,"vertical")}),Jf(g,"scroll",function(){g.clientWidth&&b(g.scrollLeft,"horizontal")}),this.checkedOverlay=!1,d&&8>e&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")}function L(){}function M(a){a.display.scrollbars&&(a.display.scrollbars.clear(),a.display.scrollbars.addClass&&wg(a.display.wrapper,a.display.scrollbars.addClass)),a.display.scrollbars=new v.scrollbarModel[a.options.scrollbarStyle](function(b){a.display.wrapper.insertBefore(b,a.display.scrollbarFiller),Jf(b,"mousedown",function(){a.state.focused&&setTimeout(function(){a.display.input.focus()},0)}),b.setAttribute("cm-not-content","true")},function(b,c){"horizontal"==c?Ic(a,b):Hc(a,b)},a),a.display.scrollbars.addClass&&xg(a.display.wrapper,a.display.scrollbars.addClass)}function N(a,b){b||(b=J(a));var c=a.display.barWidth,d=a.display.barHeight;O(a,b);for(var e=0;4>e&&c!=a.display.barWidth||d!=a.display.barHeight;e++)c!=a.display.barWidth&&a.options.lineWrapping&&$(a),O(a,J(a)),c=a.display.barWidth,d=a.display.barHeight}function O(a,b){var c=a.display,d=c.scrollbars.update(b);c.sizer.style.paddingRight=(c.barWidth=d.right)+"px",c.sizer.style.paddingBottom=(c.barHeight=d.bottom)+"px",d.right&&d.bottom?(c.scrollbarFiller.style.display="block",c.scrollbarFiller.style.height=d.bottom+"px",c.scrollbarFiller.style.width=d.right+"px"):c.scrollbarFiller.style.display="",d.bottom&&a.options.coverGutterNextToScrollbar&&a.options.fixedGutter?(c.gutterFiller.style.display="block",c.gutterFiller.style.height=d.bottom+"px",c.gutterFiller.style.width=b.gutterWidth+"px"):c.gutterFiller.style.display=""}function P(a,b,c){var d=c&&null!=c.top?Math.max(0,c.top):a.scroller.scrollTop;d=Math.floor(d-mb(a));var e=c&&null!=c.bottom?c.bottom:d+a.wrapper.clientHeight,f=lf(b,d),g=lf(b,e);if(c&&c.ensure){var h=c.ensure.from.line,i=c.ensure.to.line;f>h?(f=h,g=lf(b,mf(ff(b,h))+a.wrapper.clientHeight)):Math.min(i,b.lastLine())>=g&&(f=lf(b,mf(ff(b,i))-a.wrapper.clientHeight),g=i)}return{from:f,to:Math.max(g,f+1)}}function Q(a){var b=a.display,c=b.view;if(b.alignWidgets||b.gutters.firstChild&&a.options.fixedGutter){for(var d=T(b)-b.scroller.scrollLeft+a.doc.scrollLeft,e=b.gutters.offsetWidth,f=d+"px",g=0;g=c.viewFrom&&b.visible.to<=c.viewTo&&(null==c.updateLineNumbers||c.updateLineNumbers>=c.viewTo)&&c.renderedView==c.view&&0==oc(a))return!1;R(a)&&(kc(a),b.dims=aa(a));var e=d.first+d.size,f=Math.max(b.visible.from-a.options.viewportMargin,d.first),g=Math.min(e,b.visible.to+a.options.viewportMargin);c.viewFromg&&c.viewTo-g<20&&(g=Math.min(e,c.viewTo)),u&&(f=ue(a.doc,f),g=ve(a.doc,g));var h=f!=c.viewFrom||g!=c.viewTo||c.lastWrapHeight!=b.wrapperHeight||c.lastWrapWidth!=b.wrapperWidth;nc(a,f,g),c.viewOffset=mf(ff(a.doc,c.viewFrom)),a.display.mover.style.top=c.viewOffset+"px";var i=oc(a);if(!h&&0==i&&!b.force&&c.renderedView==c.view&&(null==c.updateLineNumbers||c.updateLineNumbers>=c.viewTo))return!1;var j=ug();return i>4&&(c.lineDiv.style.display="none"),ba(a,c.updateLineNumbers,b.dims),i>4&&(c.lineDiv.style.display=""),c.renderedView=c.view,j&&ug()!=j&&j.offsetHeight&&j.focus(),rg(c.cursorDiv),rg(c.selectionDiv),c.gutters.style.height=c.sizer.style.minHeight=0,h&&(c.lastWrapHeight=b.wrapperHeight,c.lastWrapWidth=b.wrapperWidth,ib(a,400)),c.updateLineNumbers=null,!0}function X(a,b){for(var c=b.viewport,d=!0;(d&&a.options.lineWrapping&&b.oldDisplayWidth!=qb(a)||(c&&null!=c.top&&(c={top:Math.min(a.doc.height+nb(a.display)-rb(a),c.top)}),b.visible=P(a.display,a.doc,c),!(b.visible.from>=a.display.viewFrom&&b.visible.to<=a.display.viewTo)))&&W(a,b);d=!1){$(a);var e=J(a);db(a),Z(a,e),N(a,e)}b.signal(a,"update",a),(a.display.viewFrom!=a.display.reportedViewFrom||a.display.viewTo!=a.display.reportedViewTo)&&(b.signal(a,"viewportChange",a,a.display.viewFrom,a.display.viewTo),a.display.reportedViewFrom=a.display.viewFrom,a.display.reportedViewTo=a.display.viewTo)}function Y(a,b){var c=new U(a,b);if(W(a,c)){$(a),X(a,c);var d=J(a);db(a),Z(a,d),N(a,d),c.finish()}}function Z(a,b){a.display.sizer.style.minHeight=b.docHeight+"px";var c=b.docHeight+a.display.barHeight;a.display.heightForcer.style.top=c+"px",a.display.gutters.style.height=Math.max(c+pb(a),b.clientHeight)+"px"}function $(a){for(var b=a.display,c=b.lineDiv.offsetTop,f=0;fe){var i=g.node.offsetTop+g.node.offsetHeight;h=i-c,c=i}else{var j=g.node.getBoundingClientRect();h=j.bottom-j.top}var k=g.line.height-h;if(2>h&&(h=Rb(b)),(k>.001||-.001>k)&&(jf(g.line,h),_(g.line),g.rest))for(var l=0;l=b&&m.lineNumber;m.changes&&(dg(m.changes,"gutter")>-1&&(p=!1),ca(a,m,k,c)),p&&(rg(m.lineNumber),m.lineNumber.appendChild(document.createTextNode(S(a.options,k)))),h=m.node.nextSibling}else{var n=ka(a,m,k,c);g.insertBefore(n,h)}k+=m.size}for(;h;)h=i(h)}function ca(a,b,c,d){for(var e=0;ee&&(a.node.style.zIndex=2)),a.node}function ea(a){var b=a.bgClass?a.bgClass+" "+(a.line.bgClass||""):a.line.bgClass;if(b&&(b+=" CodeMirror-linebackground"),a.background)b?a.background.className=b:(a.background.parentNode.removeChild(a.background),a.background=null);else if(b){var c=da(a);a.background=c.insertBefore(pg("div",null,b),c.firstChild)}}function fa(a,b){var c=a.display.externalMeasured;return c&&c.line==b.line?(a.display.externalMeasured=null,b.measure=c.measure,c.built):Qe(a,b)}function ga(a,b){var c=b.text.className,d=fa(a,b);b.text==b.node&&(b.node=d.pre),b.text.parentNode.replaceChild(d.pre,b.text),b.text=d.pre,d.bgClass!=b.bgClass||d.textClass!=b.textClass?(b.bgClass=d.bgClass,b.textClass=d.textClass,ha(b)):c&&(b.text.className=c)}function ha(a){ea(a),a.line.wrapClass?da(a).className=a.line.wrapClass:a.node!=a.text&&(a.node.className="");var b=a.textClass?a.textClass+" "+(a.line.textClass||""):a.line.textClass;a.text.className=b||""}function ia(a,b,c,d){if(b.gutter&&(b.node.removeChild(b.gutter),b.gutter=null),b.gutterBackground&&(b.node.removeChild(b.gutterBackground),b.gutterBackground=null),b.line.gutterClass){var e=da(b);b.gutterBackground=pg("div",null,"CodeMirror-gutter-background "+b.line.gutterClass,"left: "+(a.options.fixedGutter?d.fixedPos:-d.gutterTotalWidth)+"px; width: "+d.gutterTotalWidth+"px"),e.insertBefore(b.gutterBackground,b.text)}var f=b.line.gutterMarkers;if(a.options.lineNumbers||f){var e=da(b),g=b.gutter=pg("div",null,"CodeMirror-gutter-wrapper","left: "+(a.options.fixedGutter?d.fixedPos:-d.gutterTotalWidth)+"px");if(a.display.input.setUneditable(g),e.insertBefore(g,b.text),b.line.gutterClass&&(g.className+=" "+b.line.gutterClass),!a.options.lineNumbers||f&&f["CodeMirror-linenumbers"]||(b.lineNumber=g.appendChild(pg("div",S(a.options,c),"CodeMirror-linenumber CodeMirror-gutter-elt","left: "+d.gutterLeft["CodeMirror-linenumbers"]+"px; width: "+a.display.lineNumInnerWidth+"px"))),f)for(var h=0;h1)if(va&&va.join("\n")==b){if(d.ranges.length%va.length==0){i=[];for(var j=0;j=0;j--){var k=d.ranges[j],l=k.from(),m=k.to();k.empty()&&(c&&c>0?l=oa(l.line,l.ch-c):a.state.overwrite&&!g&&(m=oa(m.line,Math.min(ff(f,m.line).text.length,m.ch+bg(h).length))));var n=a.curOp.updateInput,o={from:l,to:m,text:i?i[j%i.length]:h,origin:e||(g?"paste":a.state.cutIncoming?"cut":"+input")};hd(a.doc,o),Nf(a,"inputRead",a,o)}b&&!g&&ya(a,b),td(a),a.curOp.updateInput=n,a.curOp.typing=!0,a.state.pasteIncoming=a.state.cutIncoming=!1}function xa(a,b){var c=a.clipboardData&&a.clipboardData.getData("text/plain");return c?(a.preventDefault(),ua(b)||b.options.disableInput||cc(b,function(){wa(b,c,0,null,"paste")}),!0):void 0}function ya(a,b){if(a.options.electricChars&&a.options.smartIndent)for(var c=a.doc.sel,d=c.ranges.length-1;d>=0;d--){var e=c.ranges[d];if(!(e.head.ch>100||d&&c.ranges[d-1].head.line==e.head.line)){var f=a.getModeAt(e.head),g=!1;if(f.electricChars){for(var h=0;h-1){g=vd(a,e.head.line,"smart");break}}else f.electricInput&&f.electricInput.test(ff(a.doc,e.head.line).text.slice(0,e.head.ch))&&(g=vd(a,e.head.line,"smart"));g&&Nf(a,"electricInput",a,e.head.line)}}}function za(a){for(var b=[],c=[],d=0;de?i.map:j[e],g=0;ge?a.line:a.rest[e]),l=f[g]+d;return(0>d||h!=b)&&(l=f[g+(d?1:0)]),oa(k,l)}}}var d=a.text.firstChild,e=!1;if(!b||!tg(d,b))return Fa(oa(kf(a.line),0),!0);if(b==d&&(e=!0,b=d.childNodes[c],c=0,!b)){var f=a.rest?bg(a.rest):a.line;return Fa(oa(kf(f),f.text.length),e)}var g=3==b.nodeType?b:null,h=b;for(g||1!=b.childNodes.length||3!=b.firstChild.nodeType||(g=b.firstChild,c&&(c=g.nodeValue.length));h.parentNode!=d;)h=h.parentNode;var i=a.measure,j=i.maps,l=k(g,h,c);if(l)return Fa(l,e);for(var m=h.nextSibling,n=g?g.nodeValue.length-c:0;m;m=m.nextSibling){if(l=k(m,m.firstChild,0))return Fa(oa(l.line,l.ch-n),e);n+=m.textContent.length}for(var o=h.previousSibling,n=c;o;o=o.previousSibling){if(l=k(o,o.firstChild,-1))return Fa(oa(l.line,l.ch+n),e);n+=m.textContent.length}}function Ia(a,b,c,d,e){function i(a){return function(b){return b.id==a}}function j(b){if(1==b.nodeType){var c=b.getAttribute("cm-text");if(null!=c)return""==c&&(c=b.textContent.replace(/\u200b/g,"")),void(f+=c);var l,k=b.getAttribute("cm-marker");if(k){var m=a.findMarks(oa(d,0),oa(e+1,0),i(+k));return void(m.length&&(l=m[0].find())&&(f+=gf(a.doc,l.from,l.to).join(h)))}if("false"==b.getAttribute("contenteditable"))return;for(var n=0;n=0){var g=sa(f.from(),e.from()),h=ra(f.to(),e.to()),i=f.empty()?e.from()==e.head:f.from()==f.head;b>=d&&--b,a.splice(--d,2,new Ka(i?h:g,i?g:h))}}return new Ja(a,b)}function Ma(a,b){return new Ja([new Ka(a,b||a)],0)}function Na(a,b){return Math.max(a.first,Math.min(b,a.first+a.size-1))}function Oa(a,b){if(b.linec?oa(c,ff(a,c).text.length):Pa(b,ff(a,b.line).text.length)}function Pa(a,b){var c=a.ch;return null==c||c>b?oa(a.line,b):0>c?oa(a.line,0):a}function Qa(a,b){return b>=a.first&&b=f.ch:j.to>f.ch))){if(d&&(Lf(k,"beforeCursorEnter"),k.explicitlyCleared)){if(h.markedSpans){--i;continue}break}if(!k.atomic)continue;var l=k.find(0>g?-1:1);if(0==pa(l,f)&&(l.ch+=g,l.ch<0?l=l.line>a.first?Oa(a,oa(l.line-1)):null:l.ch>h.text.length&&(l=l.lineb&&(b=0),b=Math.round(b),d=Math.round(d),f.appendChild(pg("div",null,"CodeMirror-selected","position: absolute; left: "+a+"px; top: "+b+"px; width: "+(null==c?i-a:c)+"px; height: "+(d-b)+"px"))}function k(b,c,d){function m(c,d){return Kb(a,oa(b,c),"div",f,d)}var k,l,f=ff(e,b),g=f.text.length;return Og(nf(f),c||0,null==d?g:d,function(a,b,e){var n,o,p,f=m(a,"left");if(a==b)n=f,o=p=f.left;else{if(n=m(b-1,"right"),"rtl"==e){var q=f;f=n,n=q}o=f.left,p=n.right}null==c&&0==a&&(o=h),n.top-f.top>3&&(j(o,f.top,null,f.bottom),o=h,f.bottoml.bottom||n.bottom==l.bottom&&n.right>l.right)&&(l=n),h+1>o&&(o=h),j(o,n.top,p-o,n.bottom)}),{start:k,end:l}}var d=a.display,e=a.doc,f=document.createDocumentFragment(),g=ob(a.display),h=g.left,i=Math.max(d.sizerWidth,qb(a)-d.sizer.offsetLeft)-g.right,l=b.from(),m=b.to();if(l.line==m.line)k(l.line,l.ch,m.ch);else{var n=ff(e,l.line),o=ff(e,m.line),p=se(n)==se(o),q=k(l.line,l.ch,p?n.text.length+1:null).end,r=k(m.line,p?0:null,m.ch).start;p&&(q.top0?b.blinker=setInterval(function(){b.cursorDiv.style.visibility=(c=!c)?"":"hidden"},a.options.cursorBlinkRate):a.options.cursorBlinkRate<0&&(b.cursorDiv.style.visibility="hidden")}}function ib(a,b){a.doc.mode.startState&&a.doc.frontier=a.display.viewTo)){var c=+new Date+a.options.workTime,d=Jd(b.mode,lb(a,b.frontier)),e=[];b.iter(b.frontier,Math.min(b.first+b.size,a.display.viewTo+500),function(f){if(b.frontier>=a.display.viewFrom){var g=f.styles,h=f.text.length>a.options.maxHighlightLength,i=Ke(a,f,h?Jd(b.mode,d):d,!0);f.styles=i.styles;var j=f.styleClasses,k=i.classes;k?f.styleClasses=k:j&&(f.styleClasses=null);for(var l=!g||g.length!=f.styles.length||j!=k&&(!j||!k||j.bgClass!=k.bgClass||j.textClass!=k.textClass),m=0;!l&&mc?(ib(a,a.options.workDelay),!0):void 0}),e.length&&cc(a,function(){for(var b=0;bg;--h){if(h<=f.first)return f.first;var i=ff(f,h-1);if(i.stateAfter&&(!c||h<=f.frontier))return h;var j=Zf(i.text,null,a.options.tabSize);(null==e||d>j)&&(e=h-1,d=j)}return e}function lb(a,b,c){var d=a.doc,e=a.display;if(!d.mode.startState)return!0;var f=kb(a,b,c),g=f>d.first&&ff(d,f-1).stateAfter;return g=g?Jd(d.mode,g):Kd(d.mode),d.iter(f,b,function(c){Me(a,c.text,g);var h=f==b-1||f%5==0||f>=e.viewFrom&&f2&&f.push((i.bottom+j.top)/2-c.top)}}f.push(c.bottom-c.top)}}function tb(a,b,c){if(a.line==b)return{map:a.measure.map,cache:a.measure.cache};for(var d=0;dc)return{map:a.measure.maps[d],cache:a.measure.caches[d],before:!0}}function ub(a,b){b=se(b);var c=kf(b),d=a.display.externalMeasured=new gc(a.doc,b,c);d.lineN=c;var e=d.built=Qe(a,d);return d.text=e.pre,sg(a.display.lineMeasure,e.pre),d}function vb(a,b,c,d){return yb(a,xb(a,b),c,d)}function wb(a,b){if(b>=a.display.viewFrom&&b=c.lineN&&bb?(e=0,f=1,g="left"):j>b?(e=b-i,f=e+1):(h==a.length-3||b==j&&a[h+3]>b)&&(f=j-i,e=f-1,b>=j&&(g="right")),null!=e){if(d=a[h+2],i==j&&c==(d.insertLeft?"left":"right")&&(g=c),"left"==c&&0==e)for(;h&&a[h-2]==a[h-3]&&a[h-1].insertLeft;)d=a[(h-=3)+2],g="left";if("right"==c&&e==j-i)for(;hm;m++){for(;i&&og(b.line.text.charAt(g.coverStart+i));)--i;for(;g.coverStart+je&&0==i&&j==g.coverEnd-g.coverStart)l=h.parentNode.getBoundingClientRect();else if(d&&a.options.lineWrapping){var n=qg(h,i,j).getClientRects();l=n.length?n["right"==f?n.length-1:0]:zb}else l=qg(h,i,j).getBoundingClientRect()||zb;if(l.left||l.right||0==i)break;j=i,i-=1,k="right"}d&&11>e&&(l=Cb(a.display.measure,l))}else{i>0&&(k=f="right");var n;l=a.options.lineWrapping&&(n=h.getClientRects()).length>1?n["right"==f?n.length-1:0]:h.getBoundingClientRect()}if(d&&9>e&&!i&&(!l||!l.left&&!l.right)){var o=h.parentNode.getClientRects()[0];l=o?{left:o.left,right:o.left+Sb(a.display),top:o.top,bottom:o.bottom}:zb}for(var p=l.top-b.rect.top,q=l.bottom-b.rect.top,r=(p+q)/2,s=b.view.measure.heights,m=0;mc.from?g(a-1):g(a,d)}d=d||ff(a.doc,b.line),e||(e=xb(a,d));var i=nf(d),j=b.ch;if(!i)return g(j);var k=Yg(i,j),l=h(j,k);return null!=Xg&&(l.other=h(j,Xg)),l}function Mb(a,b){var c=0,b=Oa(a.doc,b);a.options.lineWrapping||(c=Sb(a.display)*b.ch);var d=ff(a.doc,b.line),e=mf(d)+mb(a.display);return{left:c,right:c,top:e,bottom:e+d.height}}function Nb(a,b,c,d){var e=oa(a,b);return e.xRel=d,c&&(e.outside=!0),e}function Ob(a,b,c){var d=a.doc;if(c+=a.display.viewOffset,0>c)return Nb(d.first,0,!0,-1);var e=lf(d,c),f=d.first+d.size-1;if(e>f)return Nb(d.first+d.size-1,ff(d,f).text.length,!0,1);0>b&&(b=0);for(var g=ff(d,e);;){var h=Pb(a,g,e,b,c),i=qe(g),j=i&&i.find(0,!0);if(!i||!(h.ch>j.from.ch||h.ch==j.from.ch&&h.xRel>0))return h;e=kf(g=j.to.line)}}function Pb(a,b,c,d,e){function j(d){var e=Lb(a,oa(c,d),"line",b,i);return g=!0,f>e.bottom?e.left-h:fq)return Nb(c,n,r,1);for(;;){if(k?n==m||n==$g(b,m,1):1>=n-m){for(var s=o>d||q-d>=d-o?m:n,t=d-(s==m?o:q);og(b.text.charAt(s));)++s;var u=Nb(c,s,s==m?p:r,-1>t?-1:t>1?1:0);return u}var v=Math.ceil(l/2),w=m+v;if(k){w=m;for(var x=0;v>x;++x)w=$g(b,w,1)}var y=j(w);y>d?(n=w,q=y,(r=g)&&(q+=1e3),l=v):(m=w,o=y,p=g,l-=v)}}function Rb(a){if(null!=a.cachedTextHeight)return a.cachedTextHeight;if(null==Qb){Qb=pg("pre");for(var b=0;49>b;++b)Qb.appendChild(document.createTextNode("x")),Qb.appendChild(pg("br"));Qb.appendChild(document.createTextNode("x"))}sg(a.measure,Qb);var c=Qb.offsetHeight/50;return c>3&&(a.cachedTextHeight=c),rg(a.measure),c||1}function Sb(a){if(null!=a.cachedCharWidth)return a.cachedCharWidth;var b=pg("span","xxxxxxxxxx"),c=pg("pre",[b]);sg(a.measure,c);var d=b.getBoundingClientRect(),e=(d.right-d.left)/10;return e>2&&(a.cachedCharWidth=e),e||10}function Vb(a){a.curOp={cm:a,viewChanged:!1,startHeight:a.doc.height,forceUpdate:!1,updateInput:null,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++Ub},Tb?Tb.ops.push(a.curOp):a.curOp.ownsGroup=Tb={ops:[a.curOp],delayedCallbacks:[]}}function Wb(a){var b=a.delayedCallbacks,c=0;do{for(;c=c.viewTo)||c.maxLineChanged&&b.options.lineWrapping,a.update=a.mustUpdate&&new U(b,a.mustUpdate&&{top:a.scrollTop,ensure:a.scrollToPos},a.forceUpdate)}function $b(a){a.updatedDisplay=a.mustUpdate&&W(a.cm,a.update)}function _b(a){var b=a.cm,c=b.display;a.updatedDisplay&&$(b),a.barMeasure=J(b),c.maxLineChanged&&!b.options.lineWrapping&&(a.adjustWidthTo=vb(b,c.maxLine,c.maxLine.text.length).left+3,b.display.sizerWidth=a.adjustWidthTo,a.barMeasure.scrollWidth=Math.max(c.scroller.clientWidth,c.sizer.offsetLeft+a.adjustWidthTo+pb(b)+b.display.barWidth),a.maxScrollLeft=Math.max(0,c.sizer.offsetLeft+a.adjustWidthTo-qb(b))),(a.updatedDisplay||a.selectionChanged)&&(a.preparedSelection=c.input.prepareSelection())}function ac(a){var b=a.cm;null!=a.adjustWidthTo&&(b.display.sizer.style.minWidth=a.adjustWidthTo+"px",a.maxScrollLeftf;f=e){var g=new gc(a.doc,ff(a.doc,f),f);e=f+g.size,d.push(g)}return d}function ic(a,b,c,d){null==b&&(b=a.doc.first),null==c&&(c=a.doc.first+a.doc.size),d||(d=0);var e=a.display;if(d&&cb)&&(e.updateLineNumbers=b),a.curOp.viewChanged=!0,b>=e.viewTo)u&&ue(a.doc,b)e.viewFrom?kc(a):(e.viewFrom+=d,e.viewTo+=d);else if(b<=e.viewFrom&&c>=e.viewTo)kc(a);else if(b<=e.viewFrom){var f=mc(a,c,c+d,1);f?(e.view=e.view.slice(f.index),e.viewFrom=f.lineN,e.viewTo+=d):kc(a)}else if(c>=e.viewTo){var f=mc(a,b,b,-1);f?(e.view=e.view.slice(0,f.index),e.viewTo=f.lineN):kc(a)}else{var g=mc(a,b,b,-1),h=mc(a,c,c+d,1);g&&h?(e.view=e.view.slice(0,g.index).concat(hc(a,g.lineN,h.lineN)).concat(e.view.slice(h.index)),e.viewTo+=d):kc(a)}var i=e.externalMeasured;i&&(c=e.lineN&&b=d.viewTo)){var f=d.view[lc(a,b)];if(null!=f.node){var g=f.changes||(f.changes=[]);-1==dg(g,c)&&g.push(c)}}}function kc(a){a.display.viewFrom=a.display.viewTo=a.doc.first,a.display.view=[],a.display.viewOffset=0}function lc(a,b){if(b>=a.display.viewTo)return null;if(b-=a.display.viewFrom,0>b)return null;for(var c=a.display.view,d=0;db)return d}function mc(a,b,c,d){var f,e=lc(a,b),g=a.display.view;if(!u||c==a.doc.first+a.doc.size)return{index:e,lineN:c};for(var h=0,i=a.display.viewFrom;e>h;h++)i+=g[h].size;if(i!=b){if(d>0){if(e==g.length-1)return null;f=i+g[e].size-b,e++}else f=i-b;b+=f,c+=f}for(;ue(a.doc,c)!=c;){if(e==(0>d?0:g.length-1))return null;c+=d*g[e-(0>d?1:0)].size,e+=d}return{index:e,lineN:c}}function nc(a,b,c){var d=a.display,e=d.view;0==e.length||b>=d.viewTo||c<=d.viewFrom?(d.view=hc(a,b,c),d.viewFrom=b):(d.viewFrom>b?d.view=hc(a,b,d.viewFrom).concat(d.view):d.viewFromc&&(d.view=d.view.slice(0,lc(a,c)))),d.viewTo=c}function oc(a){for(var b=a.display.view,c=0,d=0;d400}var b=a.display;Jf(b.scroller,"mousedown",dc(a,uc)),d&&11>e?Jf(b.scroller,"dblclick",dc(a,function(b){if(!Pf(a,b)){var c=tc(a,b);if(c&&!Bc(a,b)&&!sc(a.display,b)){Df(b);var d=a.findWordAt(c);Ta(a.doc,d.anchor,d.head)}}})):Jf(b.scroller,"dblclick",function(b){Pf(a,b)||Df(b)}),s||Jf(b.scroller,"contextmenu",function(b){_c(a,b)});var c,f={end:0};Jf(b.scroller,"touchstart",function(a){if(!h(a)){clearTimeout(c);var d=+new Date;b.activeTouch={start:d,moved:!1,prev:d-f.end<=300?f:null},1==a.touches.length&&(b.activeTouch.left=a.touches[0].pageX,b.activeTouch.top=a.touches[0].pageY)}}),Jf(b.scroller,"touchmove",function(){b.activeTouch&&(b.activeTouch.moved=!0)}),Jf(b.scroller,"touchend",function(c){var d=b.activeTouch;if(d&&!sc(b,c)&&null!=d.left&&!d.moved&&new Date-d.start<300){var f,e=a.coordsChar(b.activeTouch,"page");f=!d.prev||i(d,d.prev)?new Ka(e,e):!d.prev.prev||i(d,d.prev.prev)?a.findWordAt(e):new Ka(oa(e.line,0),Oa(a.doc,oa(e.line+1,0))),a.setSelection(f.anchor,f.head),a.focus(),Df(c)}g()}),Jf(b.scroller,"touchcancel",g),Jf(b.scroller,"scroll",function(){b.scroller.clientHeight&&(Hc(a,b.scroller.scrollTop),Ic(a,b.scroller.scrollLeft,!0),Lf(a,"scroll",a))}),Jf(b.scroller,"mousewheel",function(b){Mc(a,b)}),Jf(b.scroller,"DOMMouseScroll",function(b){Mc(a,b)}),Jf(b.wrapper,"scroll",function(){b.wrapper.scrollTop=b.wrapper.scrollLeft=0}),b.dragFunctions={enter:function(b){Pf(a,b)||Gf(b)},over:function(b){Pf(a,b)||(Fc(a,b),Gf(b))},start:function(b){Ec(a,b)},drop:dc(a,Dc),leave:function(){Gc(a)}};var j=b.input.getField();Jf(j,"keyup",function(b){Wc.call(a,b)}),Jf(j,"keydown",dc(a,Uc)),Jf(j,"keypress",dc(a,Xc)),Jf(j,"focus",ig(Zc,a)),Jf(j,"blur",ig($c,a))}function qc(a,b,c){var d=c&&c!=v.Init;if(!b!=!d){var e=a.display.dragFunctions,f=b?Jf:Kf;f(a.display.scroller,"dragstart",e.start),f(a.display.scroller,"dragenter",e.enter),f(a.display.scroller,"dragover",e.over),f(a.display.scroller,"dragleave",e.leave),f(a.display.scroller,"drop",e.drop)}}function rc(a){var b=a.display;(b.lastWrapHeight!=b.wrapper.clientHeight||b.lastWrapWidth!=b.wrapper.clientWidth)&&(b.cachedCharWidth=b.cachedTextHeight=b.cachedPaddingH=null,b.scrollbarsClipped=!1,a.setSize())}function sc(a,b){for(var c=Hf(b);c!=a.wrapper;c=c.parentNode)if(!c||1==c.nodeType&&"true"==c.getAttribute("cm-ignore-events")||c.parentNode==a.sizer&&c!=a.mover)return!0}function tc(a,b,c,d){var e=a.display;if(!c&&"true"==Hf(b).getAttribute("cm-not-content"))return null;var f,g,h=e.lineSpace.getBoundingClientRect();try{f=b.clientX-h.left,g=b.clientY-h.top}catch(b){return null}var j,i=Ob(a,f,g);if(d&&1==i.xRel&&(j=ff(a.doc,i.line).text).length==i.ch){var k=Zf(j,j.length,a.options.tabSize)-j.length;i=oa(i.line,Math.max(0,Math.round((f-ob(a.display).left)/Sb(a.display))-k))}return i}function uc(a){var b=this,c=b.display;if(!(c.activeTouch&&c.input.supportsTouch()||Pf(b,a))){if(c.shift=a.shiftKey,sc(c,a))return void(f||(c.scroller.draggable=!1,setTimeout(function(){c.scroller.draggable=!0},100)));if(!Bc(b,a)){var d=tc(b,a);switch(window.focus(),If(a)){case 1:b.state.selectingText?b.state.selectingText(a):d?xc(b,a,d):Hf(a)==c.scroller&&Df(a);break;case 2:f&&(b.state.lastMiddleDown=+new Date),d&&Ta(b.doc,d),setTimeout(function(){c.input.focus()},20),Df(a);break;case 3:s?_c(b,a):Yc(b)}}}}function xc(a,b,c){d?setTimeout(ig(ta,a),0):a.curOp.focus=ug();var f,e=+new Date;wc&&wc.time>e-400&&0==pa(wc.pos,c)?f="triple":vc&&vc.time>e-400&&0==pa(vc.pos,c)?(f="double",wc={time:e,pos:c}):(f="single",vc={time:e,pos:c});var i,g=a.doc.sel,h=o?b.metaKey:b.ctrlKey;a.options.dragDrop&&Dg&&!ua(a)&&"single"==f&&(i=g.contains(c))>-1&&(pa((i=g.ranges[i]).from(),c)<0||c.xRel>0)&&(pa(i.to(),c)>0||c.xRel<0)?yc(a,b,c,h):zc(a,b,c,f,h)}function yc(a,b,c,g){var h=a.display,i=+new Date,j=dc(a,function(k){f&&(h.scroller.draggable=!1),a.state.draggingText=!1,Kf(document,"mouseup",j),Kf(h.scroller,"drop",j),Math.abs(b.clientX-k.clientX)+Math.abs(b.clientY-k.clientY)<10&&(Df(k),!g&&+new Date-200=p;p++){var r=ff(g,p).text,s=$f(r,m,f);m==o?e.push(new Ka(oa(p,s),oa(p,s))):r.length>s&&e.push(new Ka(oa(p,s),oa(p,$f(r,o,f))))}e.length||e.push(new Ka(c,c)),Za(g,La(j.ranges.slice(0,i).concat(e),i),{origin:"*mouse",scroll:!1}),a.scrollIntoView(b)}else{var t=h,u=t.anchor,v=b;if("single"!=d){if("double"==d)var w=a.findWordAt(b);else var w=new Ka(oa(b.line,0),Oa(g,oa(b.line+1,0)));pa(w.anchor,u)>0?(v=w.head,u=sa(t.from(),w.anchor)):(v=w.anchor,u=ra(t.to(),w.head))}var e=j.ranges.slice(0);e[i]=new Ka(Oa(g,u),v),Za(g,La(e,i),Wf)}}function r(b){var c=++q,e=tc(a,b,!0,"rect"==d);if(e)if(0!=pa(e,n)){a.curOp.focus=ug(),o(e);var h=P(f,g);(e.line>=h.to||e.linep.bottom?20:0;i&&setTimeout(dc(a,function(){q==c&&(f.scroller.scrollTop+=i,r(b))}),50)}}function s(b){a.state.selectingText=!1,q=1/0,Df(b),f.input.focus(),Kf(document,"mousemove",t),Kf(document,"mouseup",u),g.history.lastSelOrigin=null}var f=a.display,g=a.doc;Df(b);var h,i,j=g.sel,k=j.ranges;if(e&&!b.shiftKey?(i=g.sel.contains(c),h=i>-1?k[i]:new Ka(c,c)):(h=g.sel.primary(),i=g.sel.primIndex),b.altKey)d="rect",e||(h=new Ka(c,c)),c=tc(a,b,!0,!0),i=-1;else if("double"==d){var l=a.findWordAt(c);h=a.display.shift||g.extend?Sa(g,h,l.anchor,l.head):l}else if("triple"==d){var m=new Ka(oa(c.line,0),Oa(g,oa(c.line+1,0)));h=a.display.shift||g.extend?Sa(g,h,m.anchor,m.head):m}else h=Sa(g,h,c);e?-1==i?(i=k.length,Za(g,La(k.concat([h]),i),{scroll:!1,origin:"*mouse"})):k.length>1&&k[i].empty()&&"single"==d&&!b.shiftKey?(Za(g,La(k.slice(0,i).concat(k.slice(i+1)),0),{scroll:!1,origin:"*mouse"}),j=g.sel):Va(g,i,h,Wf):(i=0,Za(g,new Ja([h],0),Wf),j=g.sel);var n=c,p=f.wrapper.getBoundingClientRect(),q=0,t=dc(a,function(a){If(a)?r(a):s(a)}),u=dc(a,s);a.state.selectingText=u,Jf(document,"mousemove",t),Jf(document,"mouseup",u)}function Ac(a,b,c,d,e){try{var f=b.clientX,g=b.clientY}catch(b){return!1}if(f>=Math.floor(a.display.gutters.getBoundingClientRect().right))return!1;d&&Df(b);var h=a.display,i=h.lineDiv.getBoundingClientRect();if(g>i.bottom||!Rf(a,c))return Ff(b);g-=i.top-h.viewOffset;for(var j=0;j=f){var l=lf(a.doc,g),m=a.options.gutters[j];return e(a,c,a,l,m,b),Ff(b)}}}function Bc(a,b){return Ac(a,b,"gutterClick",!0,Nf)}function Dc(a){var b=this;if(Gc(b),!Pf(b,a)&&!sc(b.display,a)){Df(a),d&&(Cc=+new Date);var c=tc(b,a,!0),e=a.dataTransfer.files;if(c&&!ua(b))if(e&&e.length&&window.FileReader&&window.File)for(var f=e.length,g=Array(f),h=0,i=function(a,d){var e=new FileReader;e.onload=dc(b,function(){if(g[d]=e.result,++h==f){c=Oa(b.doc,c);var a={from:c,to:c,text:b.doc.splitLines(g.join(b.doc.lineSeparator())),origin:"paste"};hd(b.doc,a),Ya(b.doc,Ma(c,bd(a)))}}),e.readAsText(a)},j=0;f>j;++j)i(e[j],j);else{if(b.state.draggingText&&b.doc.sel.contains(c)>-1)return b.state.draggingText(a),void setTimeout(function(){b.display.input.focus()},20);try{var g=a.dataTransfer.getData("Text");if(g){if(b.state.draggingText&&!(o?a.altKey:a.ctrlKey))var k=b.listSelections();if($a(b.doc,Ma(c,c)),k)for(var j=0;jj.clientWidth||g&&j.scrollHeight>j.clientHeight){if(g&&o&&f)a:for(var k=c.target,l=h.view;k!=j;k=k.parentNode)for(var m=0;mn?p=Math.max(0,p+n-50):q=Math.min(b.doc.height,q+n+50),Y(b,{top:p,bottom:q})}20>Jc&&(null==h.wheelStartX?(h.wheelStartX=j.scrollLeft,h.wheelStartY=j.scrollTop,h.wheelDX=e,h.wheelDY=g,setTimeout(function(){if(null!=h.wheelStartX){var a=j.scrollLeft-h.wheelStartX,b=j.scrollTop-h.wheelStartY,c=b&&h.wheelDY&&b/h.wheelDY||a&&h.wheelDX&&a/h.wheelDX;h.wheelStartX=h.wheelStartY=null,c&&(Kc=(Kc*Jc+c)/(Jc+1),++Jc)}},200)):(h.wheelDX+=e,h.wheelDY+=g))}}function Nc(a,b,c){if("string"==typeof b&&(b=Ld[b],!b))return!1;a.display.input.ensurePolled();var d=a.display.shift,e=!1;try{ua(a)&&(a.state.suppressEdits=!0),c&&(a.display.shift=!1),e=b(a)!=Uf}finally{a.display.shift=d,a.state.suppressEdits=!1}return e}function Oc(a,b,c){for(var d=0;de&&27==a.keyCode&&(a.returnValue=!1);var c=a.keyCode;b.display.shift=16==c||a.shiftKey;var f=Rc(b,a);i&&(Tc=f?c:null,!f&&88==c&&!Kg&&(o?a.metaKey:a.ctrlKey)&&b.replaceSelection("",null,"cut")),18!=c||/\bCodeMirror-crosshair\b/.test(b.display.lineDiv.className)||Vc(b)}}function Vc(a){function c(a){18!=a.keyCode&&a.altKey||(wg(b,"CodeMirror-crosshair"),Kf(document,"keyup",c),Kf(document,"mouseover",c))}var b=a.display.lineDiv;xg(b,"CodeMirror-crosshair"),Jf(document,"keyup",c),Jf(document,"mouseover",c)}function Wc(a){16==a.keyCode&&(this.doc.sel.shift=!1),Pf(this,a)}function Xc(a){var b=this;if(!(sc(b.display,a)||Pf(b,a)||a.ctrlKey&&!a.altKey||o&&a.metaKey)){var c=a.keyCode,d=a.charCode;if(i&&c==Tc)return Tc=null,void Df(a);if(!i||a.which&&!(a.which<10)||!Rc(b,a)){var e=String.fromCharCode(null==d?c:d);Sc(b,a,e)||b.display.input.onKeyPress(a)}}}function Yc(a){a.state.delayingBlurEvent=!0,setTimeout(function(){a.state.delayingBlurEvent&&(a.state.delayingBlurEvent=!1,$c(a))},100)}function Zc(a){a.state.delayingBlurEvent&&(a.state.delayingBlurEvent=!1),"nocursor"!=a.options.readOnly&&(a.state.focused||(Lf(a,"focus",a),a.state.focused=!0,xg(a.display.wrapper,"CodeMirror-focused"),a.curOp||a.display.selForContextMenu==a.doc.sel||(a.display.input.reset(),f&&setTimeout(function(){a.display.input.reset(!0)},20)),a.display.input.receivedFocus()),hb(a))}function $c(a){a.state.delayingBlurEvent||(a.state.focused&&(Lf(a,"blur",a),a.state.focused=!1,wg(a.display.wrapper,"CodeMirror-focused")),clearInterval(a.display.blinker),setTimeout(function(){a.state.focused||(a.display.shift=!1)},150))}function _c(a,b){sc(a.display,b)||ad(a,b)||a.display.input.onContextMenu(b)}function ad(a,b){return Rf(a,"gutterContextMenu")?Ac(a,b,"gutterContextMenu",!1,Lf):!1}function cd(a,b){if(pa(a,b.from)<0)return a;if(pa(a,b.to)<=0)return bd(b);var c=a.line+b.text.length-(b.to.line-b.from.line)-1,d=a.ch;return a.line==b.to.line&&(d+=bd(b).ch-b.to.ch),oa(c,d)}function dd(a,b){for(var c=[],d=0;d=0;--e)id(a,{from:d[e].from,to:d[e].to,text:e?[""]:b.text});else id(a,b)}}function id(a,b){if(1!=b.text.length||""!=b.text[0]||0!=pa(b.from,b.to)){var c=dd(a,b);sf(a,b,c,a.cm?a.cm.curOp.id:NaN),ld(a,b,c,fe(a,b));var d=[];df(a,function(a,c){c||-1!=dg(d,a.history)||(Cf(a.history,b),d.push(a.history)),ld(a,b,null,fe(a,b))})}}function jd(a,b,c){if(!a.cm||!a.cm.state.suppressEdits){for(var e,d=a.history,f=a.sel,g="undo"==b?d.done:d.undone,h="undo"==b?d.undone:d.done,i=0;i=0;--i){var l=e.changes[i];if(l.origin=b,k&&!gd(a,l,!1))return void(g.length=0);j.push(pf(a,l));var m=i?dd(a,l):bg(g);ld(a,l,m,he(a,l)),!i&&a.cm&&a.cm.scrollIntoView({from:l.from,to:bd(l)});var n=[];df(a,function(a,b){b||-1!=dg(n,a.history)||(Cf(a.history,l),n.push(a.history)),ld(a,l,null,he(a,l))})}}}}function kd(a,b){if(0!=b&&(a.first+=b,a.sel=new Ja(eg(a.sel.ranges,function(a){return new Ka(oa(a.anchor.line+b,a.anchor.ch),oa(a.head.line+b,a.head.ch))}),a.sel.primIndex),a.cm)){ic(a.cm,a.first,a.first-b,b);for(var c=a.cm.display,d=c.viewFrom;da.lastLine())){if(b.from.linef&&(b={from:b.from,to:oa(f,ff(a,f).text.length),text:[b.text[0]],origin:b.origin}),b.removed=gf(a,b.from,b.to),c||(c=dd(a,b)),a.cm?md(a.cm,b,d):Ye(a,b,d),$a(a,c,Vf)}}function md(a,b,c){var d=a.doc,e=a.display,f=b.from,g=b.to,h=!1,i=f.line;a.options.lineWrapping||(i=kf(se(ff(d,f.line))),d.iter(i,g.line+1,function(a){return a==e.maxLine?(h=!0,!0):void 0})),d.sel.contains(b.from,b.to)>-1&&Qf(a),Ye(d,b,c,A(a)),a.options.lineWrapping||(d.iter(i,f.line+b.text.length,function(a){var b=G(a);b>e.maxLineLength&&(e.maxLine=a,e.maxLineLength=b,e.maxLineChanged=!0,h=!1)}),h&&(a.curOp.updateMaxLine=!0)),d.frontier=Math.min(d.frontier,f.line),ib(a,400);var j=b.text.length-(g.line-f.line)-1;b.full?ic(a):f.line!=g.line||1!=b.text.length||Xe(a.doc,b)?ic(a,f.line,g.line+1,j):jc(a,f.line,"text");var k=Rf(a,"changes"),l=Rf(a,"change");if(l||k){var m={from:f,to:g,text:b.text,removed:b.removed,origin:b.origin};l&&Nf(a,"change",a,m),k&&(a.curOp.changeObjs||(a.curOp.changeObjs=[])).push(m)}a.display.selForContextMenu=null}function nd(a,b,c,d,e){if(d||(d=c),pa(d,c)<0){var f=d;d=c,c=f}"string"==typeof b&&(b=a.splitLines(b)),hd(a,{from:c,to:d,text:b,origin:e})}function od(a,b){if(!Pf(a,"scrollCursorIntoView")){var c=a.display,d=c.sizer.getBoundingClientRect(),e=null;if(b.top+d.top<0?e=!0:b.bottom+d.top>(window.innerHeight||document.documentElement.clientHeight)&&(e=!1),null!=e&&!l){var f=pg("div","\u200b",null,"position: absolute; top: "+(b.top-c.viewOffset-mb(a.display))+"px; height: "+(b.bottom-b.top+pb(a)+c.barHeight)+"px; left: "+b.left+"px; width: 2px;");a.display.lineSpace.appendChild(f),f.scrollIntoView(e),a.display.lineSpace.removeChild(f)}}}function pd(a,b,c,d){null==d&&(d=0);for(var e=0;5>e;e++){var f=!1,g=Lb(a,b),h=c&&c!=b?Lb(a,c):g,i=rd(a,Math.min(g.left,h.left),Math.min(g.top,h.top)-d,Math.max(g.left,h.left),Math.max(g.bottom,h.bottom)+d),j=a.doc.scrollTop,k=a.doc.scrollLeft;if(null!=i.scrollTop&&(Hc(a,i.scrollTop),Math.abs(a.doc.scrollTop-j)>1&&(f=!0)),null!=i.scrollLeft&&(Ic(a,i.scrollLeft),Math.abs(a.doc.scrollLeft-k)>1&&(f=!0)),!f)break}return g}function qd(a,b,c,d,e){var f=rd(a,b,c,d,e);null!=f.scrollTop&&Hc(a,f.scrollTop),null!=f.scrollLeft&&Ic(a,f.scrollLeft)}function rd(a,b,c,d,e){var f=a.display,g=Rb(a.display);0>c&&(c=0);var h=a.curOp&&null!=a.curOp.scrollTop?a.curOp.scrollTop:f.scroller.scrollTop,i=rb(a),j={};e-c>i&&(e=c+i);var k=a.doc.height+nb(f),l=g>c,m=e>k-g;if(h>c)j.scrollTop=l?0:c;else if(e>h+i){var n=Math.min(c,(m?k:e)-i);n!=h&&(j.scrollTop=n)}var o=a.curOp&&null!=a.curOp.scrollLeft?a.curOp.scrollLeft:f.scroller.scrollLeft,p=qb(a)-(a.options.fixedGutter?f.gutters.offsetWidth:0),q=d-b>p;return q&&(d=b+p),10>b?j.scrollLeft=0:o>b?j.scrollLeft=Math.max(0,b-(q?0:10)):d>p+o-3&&(j.scrollLeft=d+(q?0:10)-p),j}function sd(a,b,c){(null!=b||null!=c)&&ud(a),null!=b&&(a.curOp.scrollLeft=(null==a.curOp.scrollLeft?a.doc.scrollLeft:a.curOp.scrollLeft)+b),null!=c&&(a.curOp.scrollTop=(null==a.curOp.scrollTop?a.doc.scrollTop:a.curOp.scrollTop)+c)}function td(a){ud(a);var b=a.getCursor(),c=b,d=b;a.options.lineWrapping||(c=b.ch?oa(b.line,b.ch-1):b,d=oa(b.line,b.ch+1)),a.curOp.scrollToPos={from:c,to:d,margin:a.options.cursorScrollMargin,isCursor:!0}}function ud(a){var b=a.curOp.scrollToPos;if(b){a.curOp.scrollToPos=null;var c=Mb(a,b.from),d=Mb(a,b.to),e=rd(a,Math.min(c.left,d.left),Math.min(c.top,d.top)-b.margin,Math.max(c.right,d.right),Math.max(c.bottom,d.bottom)+b.margin);a.scrollTo(e.scrollLeft,e.scrollTop)}}function vd(a,b,c,d){var f,e=a.doc;null==c&&(c="add"),"smart"==c&&(e.mode.indent?f=lb(a,b):c="prev");var g=a.options.tabSize,h=ff(e,b),i=Zf(h.text,null,g);h.stateAfter&&(h.stateAfter=null);var k,j=h.text.match(/^\s*/)[0];if(d||/\S/.test(h.text)){if("smart"==c&&(k=e.mode.indent(f,h.text.slice(j.length),h.text),k==Uf||k>150)){if(!d)return;c="prev"}}else k=0,c="not";"prev"==c?k=b>e.first?Zf(ff(e,b-1).text,null,g):0:"add"==c?k=i+a.options.indentUnit:"subtract"==c?k=i-a.options.indentUnit:"number"==typeof c&&(k=i+c),k=Math.max(0,k); -var l="",m=0;if(a.options.indentWithTabs)for(var n=Math.floor(k/g);n;--n)m+=g,l+=" ";if(k>m&&(l+=ag(k-m)),l!=j)return nd(e,l,oa(b,0),oa(b,j.length),"+input"),h.stateAfter=null,!0;for(var n=0;n=0;b--)nd(a.doc,"",d[b].from,d[b].to,"+delete");td(a)})}function yd(a,b,c,d,e){function k(){var b=f+c;return b=a.first+a.size?j=!1:(f=b,i=ff(a,b))}function l(a){var b=(e?$g:_g)(i,g,c,!0);if(null==b){if(a||!k())return j=!1;g=e?(0>c?Sg:Rg)(i):0>c?i.text.length:0}else g=b;return!0}var f=b.line,g=b.ch,h=c,i=ff(a,f),j=!0;if("char"==d)l();else if("column"==d)l(!0);else if("word"==d||"group"==d)for(var m=null,n="group"==d,o=a.cm&&a.cm.getHelper(b,"wordChars"),p=!0;!(0>c)||l(!p);p=!1){var q=i.text.charAt(g)||"\n",r=lg(q,o)?"w":n&&"\n"==q?"n":!n||/\s/.test(q)?null:"p";if(!n||p||r||(r="s"),m&&m!=r){0>c&&(c=1,l());break}if(r&&(m=r),c>0&&!l(!p))break}var s=cb(a,oa(f,g),h,!0);return j||(s.hitSide=!0),s}function zd(a,b,c,d){var g,e=a.doc,f=b.left;if("page"==d){var h=Math.min(a.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);g=b.top+c*(h-(0>c?1.5:.5)*Rb(a.display))}else"line"==d&&(g=c>0?b.bottom+3:b.top-3);for(;;){var i=Ob(a,f,g);if(!i.outside)break;if(0>c?0>=g:g>=e.height){i.hitSide=!0;break}g+=5*c}return i}function Cd(a,b,c,d){v.defaults[a]=b,c&&(Bd[a]=d?function(a,b,d){d!=Dd&&c(a,b,d)}:c)}function Nd(a){for(var c,d,e,f,b=a.split(/-(?!$)/),a=b[b.length-1],g=0;g0||0==g&&f.clearWhenEmpty!==!1)return f;if(f.replacedWith&&(f.collapsed=!0,f.widgetNode=pg("span",[f.replacedWith],"CodeMirror-widget"),d.handleMouseEvents||f.widgetNode.setAttribute("cm-ignore-events","true"),d.insertLeft&&(f.widgetNode.insertLeft=!0)),f.collapsed){if(re(a,b.line,b,c,f)||b.line!=c.line&&re(a,c.line,b,c,f))throw new Error("Inserting collapsed marker partially overlapping an existing one");u=!0}f.addToHistory&&sf(a,{from:b,to:c,origin:"markText"},a.sel,NaN);var j,h=b.line,i=a.cm;if(a.iter(h,c.line+1,function(a){i&&f.collapsed&&!i.options.lineWrapping&&se(a)==i.display.maxLine&&(j=!0),f.collapsed&&h!=b.line&&jf(a,0),ce(a,new _d(f,h==b.line?b.ch:null,h==c.line?c.ch:null)),++h}),f.collapsed&&a.iter(b.line,c.line+1,function(b){we(a,b)&&jf(b,0)}),f.clearOnEnter&&Jf(f,"beforeCursorEnter",function(){f.clear()}),f.readOnly&&(t=!0,(a.history.done.length||a.history.undone.length)&&a.clearHistory()),f.collapsed&&(f.id=++Td,f.atomic=!0),i){if(j&&(i.curOp.updateMaxLine=!0),f.collapsed)ic(i,b.line,c.line+1);else if(f.className||f.title||f.startStyle||f.endStyle||f.css)for(var k=b.line;k<=c.line;k++)jc(i,k,"text");f.atomic&&ab(i.doc),Nf(i,"markerAdded",i,f)}return f}function Xd(a,b,c,d,e){d=hg(d),d.shared=!1;var f=[Vd(a,b,c,d,e)],g=f[0],h=d.widgetNode;return df(a,function(a){h&&(d.widgetNode=h.cloneNode(!0)),f.push(Vd(a,Oa(a,b),Oa(a,c),d,e));for(var i=0;i=b:f.to>b);(e||(e=[])).push(new _d(g,f.from,i?null:f.to))}}return e}function ee(a,b,c){if(a)for(var e,d=0;d=b:f.to>b);if(h||f.from==b&&"bookmark"==g.type&&(!c||f.marker.insertLeft)){var i=null==f.from||(g.inclusiveLeft?f.from<=b:f.from0&&h)for(var l=0;ll;++l)o.push(q);o.push(i)}return o}function ge(a){for(var b=0;b0)){var k=[i,1],l=pa(j.from,h.from),m=pa(j.to,h.to);(0>l||!g.inclusiveLeft&&!l)&&k.push({from:j.from,to:h.from}),(m>0||!g.inclusiveRight&&!m)&&k.push({from:h.to,to:j.to}),e.splice.apply(e,k),i+=k.length-1}}return e}function je(a){var b=a.markedSpans;if(b){for(var c=0;c=0&&0>=l||0>=k&&l>=0)&&(0>=k&&(pa(j.to,c)>0||i.marker.inclusiveRight&&e.inclusiveLeft)||k>=0&&(pa(j.from,d)<0||i.marker.inclusiveLeft&&e.inclusiveRight)))return!0}}}function se(a){for(var b;b=pe(a);)a=b.find(-1,!0).line;return a}function te(a){for(var b,c;b=qe(a);)a=b.find(1,!0).line,(c||(c=[])).push(a);return c}function ue(a,b){var c=ff(a,b),d=se(c);return c==d?b:kf(d)}function ve(a,b){if(b>a.lastLine())return b;var d,c=ff(a,b);if(!we(a,c))return b;for(;d=qe(c);)c=d.find(1,!0).line;return kf(c)+1}function we(a,b){var c=u&&b.markedSpans;if(c)for(var d,e=0;ee;e++){d&&(d[0]=v.innerMode(a,c).mode);var f=a.token(b,c);if(b.pos>b.start)return f}throw new Error("Mode "+a.name+" failed to advance stream.")}function Ie(a,b,c,d){function e(a){return{start:k.start,end:k.pos,string:k.current(),type:h||null,state:a?Jd(f.mode,j):j}}var h,f=a.doc,g=f.mode;b=Oa(f,b);var l,i=ff(f,b.line),j=lb(a,b.line,c),k=new Sd(i.text,a.options.tabSize);for(d&&(l=[]);(d||k.posa.options.maxHighlightLength?(h=!1,g&&Me(a,b,d,k.pos),k.pos=b.length,l=null):l=Fe(He(c,k,d,m),f),m){var n=m[0].name;n&&(l="m-"+(l?n+" "+l:n))}if(!h||j!=l){for(;ij;){var d=e[i];d>a&&e.splice(i,1,a,e[i+1],d),i+=2,j=Math.min(a,d)}if(b)if(h.opaque)e.splice(c,i-c,a,"cm-overlay "+b),i=c+2;else for(;i>c;c+=2){var f=e[c+1];e[c+1]=(f?f+" ":"")+"cm-overlay "+b}},f)}return{styles:e,classes:f.bgClass||f.textClass?f:null}}function Le(a,b,c){if(!b.styles||b.styles[0]!=a.state.modeGen){var d=lb(a,kf(b)),e=Ke(a,b,b.text.length>a.options.maxHighlightLength?Jd(a.doc.mode,d):d);b.stateAfter=d,b.styles=e.styles,e.classes?b.styleClasses=e.classes:b.styleClasses&&(b.styleClasses=null),c===a.doc.frontier&&a.doc.frontier++}return b.styles}function Me(a,b,c,d){var e=a.doc.mode,f=new Sd(b,a.options.tabSize);for(f.start=f.pos=d||0,""==b&&Ge(e,c);!f.eol();)He(e,f,c),f.start=f.pos}function Pe(a,b){if(!a||/^\s*$/.test(a))return null;var c=b.addModeClass?Oe:Ne;return c[a]||(c[a]=a.replace(/\S+/g,"cm-$&"))}function Qe(a,b){var c=pg("span",null,null,f?"padding-right: .1px":null),e={pre:pg("pre",[c],"CodeMirror-line"),content:c,col:0,pos:0,cm:a,splitSpaces:(d||f)&&a.getOption("lineWrapping")};b.measure={};for(var g=0;g<=(b.rest?b.rest.length:0);g++){var i,h=g?b.rest[g-1]:b.line;e.pos=0,e.addToken=Se,Hg(a.display.measure)&&(i=nf(h))&&(e.addToken=Ue(e.addToken,i)),e.map=[];var j=b!=a.display.externalMeasured&&kf(h);We(h,e,Le(a,h,j)),h.styleClasses&&(h.styleClasses.bgClass&&(e.bgClass=yg(h.styleClasses.bgClass,e.bgClass||"")),h.styleClasses.textClass&&(e.textClass=yg(h.styleClasses.textClass,e.textClass||""))),0==e.map.length&&e.map.push(0,0,e.content.appendChild(Fg(a.display.measure))),0==g?(b.measure.map=e.map,b.measure.cache={}):((b.measure.maps||(b.measure.maps=[])).push(e.map),(b.measure.caches||(b.measure.caches=[])).push({}))}return f&&/\bcm-tab\b/.test(e.content.lastChild.className)&&(e.content.className="cm-tab-wrap-hack"),Lf(a,"renderLine",a,b.line,e.pre),e.pre.className&&(e.textClass=yg(e.pre.className,e.textClass||"")),e}function Re(a){var b=pg("span","\u2022","cm-invalidchar");return b.title="\\u"+a.charCodeAt(0).toString(16),b.setAttribute("aria-label",b.title),b}function Se(a,b,c,f,g,h,i){if(b){var j=a.splitSpaces?b.replace(/ {3,}/g,Te):b,k=a.cm.state.specialChars,l=!1;if(k.test(b))for(var m=document.createDocumentFragment(),n=0;;){k.lastIndex=n;var o=k.exec(b),p=o?o.index-n:b.length-n;if(p){var q=document.createTextNode(j.slice(n,n+p));d&&9>e?m.appendChild(pg("span",[q])):m.appendChild(q),a.map.push(a.pos,a.pos+p,q),a.col+=p,a.pos+=p}if(!o)break;if(n+=p+1," "==o[0]){var r=a.cm.options.tabSize,s=r-a.col%r,q=m.appendChild(pg("span",ag(s),"cm-tab"));q.setAttribute("role","presentation"),q.setAttribute("cm-text"," "),a.col+=s}else if("\r"==o[0]||"\n"==o[0]){var q=m.appendChild(pg("span","\r"==o[0]?"\u240d":"\u2424","cm-invalidchar"));q.setAttribute("cm-text",o[0]),a.col+=1}else{var q=a.cm.options.specialCharPlaceholder(o[0]);q.setAttribute("cm-text",o[0]),d&&9>e?m.appendChild(pg("span",[q])):m.appendChild(q),a.col+=1}a.map.push(a.pos,a.pos+1,q),a.pos++}else{a.col+=b.length;var m=document.createTextNode(j);a.map.push(a.pos,a.pos+b.length,m),d&&9>e&&(l=!0),a.pos+=b.length}if(c||f||g||l||i){var t=c||"";f&&(t+=f),g&&(t+=g);var u=pg("span",[m],t,i);return h&&(u.title=h),a.content.appendChild(u)}a.content.appendChild(m)}}function Te(a){for(var b=" ",c=0;cj&&m.from<=j)break}if(m.to>=k)return a(c,d,e,f,g,h,i);a(c,d.slice(0,m.to-j),e,f,null,h,i),f=null,d=d.slice(m.to-j),j=m.to}}}function Ve(a,b,c,d){var e=!d&&c.widgetNode;e&&a.map.push(a.pos,a.pos+b,e),!d&&a.cm.display.input.needsContentAttribute&&(e||(e=a.content.appendChild(document.createElement("span"))),e.setAttribute("cm-marker",c.id)),e&&(a.cm.display.input.setUneditable(e),a.content.appendChild(e)),a.pos+=b}function We(a,b,c){var d=a.markedSpans,e=a.text,f=0;if(d)for(var k,l,n,o,p,q,r,h=e.length,i=0,g=1,j="",m=0;;){if(m==i){n=o=p=q=l="",r=null,m=1/0;for(var s=[],t=0;ti||v.collapsed&&u.to==i&&u.from==i)?(null!=u.to&&u.to!=i&&m>u.to&&(m=u.to,o=""),v.className&&(n+=" "+v.className),v.css&&(l=v.css),v.startStyle&&u.from==i&&(p+=" "+v.startStyle),v.endStyle&&u.to==m&&(o+=" "+v.endStyle),v.title&&!q&&(q=v.title),v.collapsed&&(!r||ne(r.marker,v)<0)&&(r=u)):u.from>i&&m>u.from&&(m=u.from)}if(r&&(r.from||0)==i){if(Ve(b,(null==r.to?h+1:r.to)-i,r.marker,null==r.from),null==r.to)return;r.to==i&&(r=!1)}if(!r&&s.length)for(var t=0;t=h)break;for(var w=Math.min(h,m);;){if(j){var x=i+j.length;if(!r){var y=x>w?j.slice(0,w-i):j;b.addToken(b,y,k?k+n:n,p,i+y.length==m?o:"",q,l)}if(x>=w){j=j.slice(w-i),i=w;break}i=x,p=""}j=e.slice(f,f=c[g++]),k=Pe(c[g++],b.cm.options)}}else for(var g=1;gc;++c)f.push(new Ce(j[c],e(c),d));return f}var h=b.from,i=b.to,j=b.text,k=ff(a,h.line),l=ff(a,i.line),m=bg(j),n=e(j.length-1),o=i.line-h.line;if(b.full)a.insert(0,g(0,j.length)),a.remove(j.length,a.size-j.length);else if(Xe(a,b)){var p=g(0,j.length-1);f(l,l.text,n),o&&a.remove(h.line,o),p.length&&a.insert(h.line,p)}else if(k==l)if(1==j.length)f(k,k.text.slice(0,h.ch)+m+k.text.slice(i.ch),n);else{var p=g(1,j.length-1);p.push(new Ce(m+k.text.slice(i.ch),n,d)),f(k,k.text.slice(0,h.ch)+j[0],e(0)),a.insert(h.line+1,p)}else if(1==j.length)f(k,k.text.slice(0,h.ch)+j[0]+l.text.slice(i.ch),e(0)),a.remove(h.line+1,o);else{f(k,k.text.slice(0,h.ch)+j[0],e(0)),f(l,m+l.text.slice(i.ch),n);var p=g(1,j.length-1);o>1&&a.remove(h.line+1,o-1),a.insert(h.line+1,p)}Nf(a,"change",a,b)}function Ze(a){this.lines=a,this.parent=null;for(var b=0,c=0;bb||b>=a.size)throw new Error("There is no line "+(b+a.first)+" in the document.");for(var c=a;!c.lines;)for(var d=0;;++d){var e=c.children[d],f=e.chunkSize();if(f>b){c=e;break}b-=f}return c.lines[b]}function gf(a,b,c){var d=[],e=b.line;return a.iter(b.line,c.line+1,function(a){var f=a.text;e==c.line&&(f=f.slice(0,c.ch)),e==b.line&&(f=f.slice(b.ch)),d.push(f),++e}),d}function hf(a,b,c){var d=[];return a.iter(b,c,function(a){d.push(a.text)}),d}function jf(a,b){var c=b-a.height;if(c)for(var d=a;d;d=d.parent)d.height+=c}function kf(a){if(null==a.parent)return null;for(var b=a.parent,c=dg(b.lines,a),d=b.parent;d;b=d,d=d.parent)for(var e=0;d.children[e]!=b;++e)c+=d.children[e].chunkSize();return c+b.first}function lf(a,b){var c=a.first;a:do{for(var d=0;db){a=e;continue a}b-=f,c+=e.chunkSize()}return c}while(!a.lines);for(var d=0;db)break;b-=h}return c+d}function mf(a){a=se(a);for(var b=0,c=a.parent,d=0;d1&&!a.done[a.done.length-2].ranges?(a.done.pop(),bg(a.done)):void 0}function sf(a,b,c,d){var e=a.history;e.undone.length=0;var g,f=+new Date;if((e.lastOp==d||e.lastOrigin==b.origin&&b.origin&&("+"==b.origin.charAt(0)&&a.cm&&e.lastModTime>f-a.cm.options.historyEventDelay||"*"==b.origin.charAt(0)))&&(g=rf(e,e.lastOp==d))){var h=bg(g.changes);0==pa(b.from,b.to)&&0==pa(b.from,h.to)?h.to=bd(b):g.changes.push(pf(a,b))}else{var i=bg(e.done);for(i&&i.ranges||vf(a.sel,e.done),g={changes:[pf(a,b)],generation:e.generation},e.done.push(g);e.done.length>e.undoDepth;)e.done.shift(),e.done[0].ranges||e.done.shift()}e.done.push(c),e.generation=++e.maxGeneration,e.lastModTime=e.lastSelTime=f,e.lastOp=e.lastSelOp=d,e.lastOrigin=e.lastSelOrigin=b.origin,h||Lf(a,"historyAdded")}function tf(a,b,c,d){var e=b.charAt(0);return"*"==e||"+"==e&&c.ranges.length==d.ranges.length&&c.somethingSelected()==d.somethingSelected()&&new Date-a.history.lastSelTime<=(a.cm?a.cm.options.historyEventDelay:500)}function uf(a,b,c,d){var e=a.history,f=d&&d.origin;c==e.lastSelOp||f&&e.lastSelOrigin==f&&(e.lastModTime==e.lastSelTime&&e.lastOrigin==f||tf(a,f,bg(e.done),b))?e.done[e.done.length-1]=b:vf(b,e.done),e.lastSelTime=+new Date,e.lastSelOrigin=f,e.lastSelOp=c,d&&d.clearRedo!==!1&&qf(e.undone)}function vf(a,b){var c=bg(b);c&&c.ranges&&c.equals(a)||b.push(a)}function wf(a,b,c,d){var e=b["spans_"+a.id],f=0;a.iter(Math.max(a.first,c),Math.min(a.first+a.size,d),function(c){c.markedSpans&&((e||(e=b["spans_"+a.id]={}))[f]=c.markedSpans),++f})}function xf(a){if(!a)return null;for(var c,b=0;b-1&&(bg(h)[l]=j[l],delete j[l])}}}return e}function Af(a,b,c,d){c0}function Sf(a){a.prototype.on=function(a,b){Jf(this,a,b)},a.prototype.off=function(a,b){Kf(this,a,b)}}function Yf(){this.id=null}function ag(a){for(;_f.length<=a;)_f.push(bg(_f)+" ");return _f[a]}function bg(a){return a[a.length-1]}function dg(a,b){for(var c=0;c-1&&kg(a)?!0:b.test(a):kg(a)}function mg(a){for(var b in a)if(a.hasOwnProperty(b)&&a[b])return!1;return!0}function og(a){return a.charCodeAt(0)>=768&&ng.test(a)}function pg(a,b,c,d){var e=document.createElement(a);if(c&&(e.className=c),d&&(e.style.cssText=d),"string"==typeof b)e.appendChild(document.createTextNode(b));else if(b)for(var f=0;f0;--b)a.removeChild(a.firstChild);return a}function sg(a,b){return rg(a).appendChild(b)}function ug(){for(var a=document.activeElement;a&&a.root&&a.root.activeElement;)a=a.root.activeElement;return a}function vg(a){return new RegExp("(^|\\s)"+a+"(?:$|\\s)\\s*")}function yg(a,b){for(var c=a.split(" "),d=0;d2&&!(d&&8>e))}var c=Eg?pg("span","\u200b"):pg("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px");return c.setAttribute("cm-text",""),c}function Hg(a){if(null!=Gg)return Gg;var b=sg(a,document.createTextNode("A\u062eA")),c=qg(b,0,1).getBoundingClientRect();if(!c||c.left==c.right)return!1;var d=qg(b,1,2).getBoundingClientRect();return Gg=d.right-c.right<3}function Mg(a){if(null!=Lg)return Lg;var b=sg(a,pg("span","x")),c=b.getBoundingClientRect(),d=qg(b,0,1).getBoundingClientRect();return Lg=Math.abs(c.left-d.left)>1}function Og(a,b,c,d){if(!a)return d(b,c,"ltr");for(var e=!1,f=0;fb||b==c&&g.to==b)&&(d(Math.max(g.from,b),Math.min(g.to,c),1==g.level?"rtl":"ltr"),e=!0)}e||d(b,c,"ltr")}function Pg(a){return a.level%2?a.to:a.from}function Qg(a){return a.level%2?a.from:a.to}function Rg(a){var b=nf(a);return b?Pg(b[0]):0}function Sg(a){var b=nf(a);return b?Qg(bg(b)):a.text.length}function Tg(a,b){var c=ff(a.doc,b),d=se(c);d!=c&&(b=kf(d));var e=nf(d),f=e?e[0].level%2?Sg(d):Rg(d):0;return oa(b,f)}function Ug(a,b){for(var c,d=ff(a.doc,b);c=qe(d);)d=c.find(1,!0).line,b=null;var e=nf(d),f=e?e[0].level%2?Rg(d):Sg(d):d.text.length;return oa(null==b?kf(d):b,f)}function Vg(a,b){var c=Tg(a,b.line),d=ff(a.doc,c.line),e=nf(d);if(!e||0==e[0].level){var f=Math.max(0,d.text.search(/\S/)),g=b.line==c.line&&b.ch<=f&&b.ch;return oa(c.line,g?0:f)}return c}function Wg(a,b,c){var d=a[0].level;return b==d?!0:c==d?!1:c>b}function Yg(a,b){Xg=null;for(var d,c=0;cb)return c;if(e.from==b||e.to==b){if(null!=d)return Wg(a,e.level,a[d].level)?(e.from!=e.to&&(Xg=d),c):(e.from!=e.to&&(Xg=c),d);d=c}}return d}function Zg(a,b,c,d){if(!d)return b+c;do b+=c;while(b>0&&og(a.text.charAt(b)));return b}function $g(a,b,c,d){var e=nf(a);if(!e)return _g(a,b,c,d);for(var f=Yg(e,b),g=e[f],h=Zg(a,b,g.level%2?-c:c,d);;){if(h>g.from&&h0==g.level%2?g.to:g.from);if(g=e[f+=c],!g)return null;h=c>0==g.level%2?Zg(a,g.to,-1,d):Zg(a,g.from,1,d)}}function _g(a,b,c,d){var e=b+c;if(d)for(;e>0&&og(a.text.charAt(e));)e+=c;return 0>e||e>a.text.length?null:e}var a=/gecko\/\d/i.test(navigator.userAgent),b=/MSIE \d/.test(navigator.userAgent),c=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),d=b||c,e=d&&(b?document.documentMode||6:c[1]),f=/WebKit\//.test(navigator.userAgent),g=f&&/Qt\/\d+\.\d+/.test(navigator.userAgent),h=/Chrome\//.test(navigator.userAgent),i=/Opera\//.test(navigator.userAgent),j=/Apple Computer/.test(navigator.vendor),k=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent),l=/PhantomJS/.test(navigator.userAgent),m=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),n=m||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent),o=m||/Mac/.test(navigator.platform),p=/win/i.test(navigator.platform),q=i&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);q&&(q=Number(q[1])),q&&q>=15&&(i=!1,f=!0);var r=o&&(g||i&&(null==q||12.11>q)),s=a||d&&e>=9,t=!1,u=!1;K.prototype=hg({update:function(a){var b=a.scrollWidth>a.clientWidth+1,c=a.scrollHeight>a.clientHeight+1,d=a.nativeBarWidth;if(c){this.vert.style.display="block",this.vert.style.bottom=b?d+"px":"0";var e=a.viewHeight-(b?d:0);this.vert.firstChild.style.height=Math.max(0,a.scrollHeight-a.clientHeight+e)+"px"}else this.vert.style.display="",this.vert.firstChild.style.height="0";if(b){this.horiz.style.display="block",this.horiz.style.right=c?d+"px":"0",this.horiz.style.left=a.barLeft+"px";var f=a.viewWidth-a.barLeft-(c?d:0);this.horiz.firstChild.style.width=a.scrollWidth-a.clientWidth+f+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedOverlay&&a.clientHeight>0&&(0==d&&this.overlayHack(),this.checkedOverlay=!0),{right:c?d:0,bottom:b?d:0}},setScrollLeft:function(a){this.horiz.scrollLeft!=a&&(this.horiz.scrollLeft=a)},setScrollTop:function(a){this.vert.scrollTop!=a&&(this.vert.scrollTop=a)},overlayHack:function(){var a=o&&!k?"12px":"18px";this.horiz.style.minHeight=this.vert.style.minWidth=a;var b=this,c=function(a){Hf(a)!=b.vert&&Hf(a)!=b.horiz&&dc(b.cm,uc)(a)};Jf(this.vert,"mousedown",c),Jf(this.horiz,"mousedown",c)},clear:function(){var a=this.horiz.parentNode;a.removeChild(this.horiz),a.removeChild(this.vert)}},K.prototype),L.prototype=hg({update:function(){return{bottom:0,right:0}},setScrollLeft:function(){},setScrollTop:function(){},clear:function(){}},L.prototype),v.scrollbarModel={"native":K,"null":L},U.prototype.signal=function(a,b){Rf(a,b)&&this.events.push(arguments)},U.prototype.finish=function(){for(var a=0;a=9&&b.hasSelection&&(b.hasSelection=null),b.poll()}),Jf(g,"paste",function(a){return xa(a,c)?!0:(c.state.pasteIncoming=!0,void b.fastPoll())}),Jf(g,"cut",h),Jf(g,"copy",h),Jf(a.scroller,"paste",function(d){sc(a,d)||(c.state.pasteIncoming=!0,b.focus())}),Jf(a.lineSpace,"selectstart",function(b){sc(a,b)||Df(b)}),Jf(g,"compositionstart",function(){var a=c.getCursor("from");b.composing&&b.composing.range.clear(), -b.composing={start:a,range:c.markText(a,c.getCursor("to"),{className:"CodeMirror-composing"})}}),Jf(g,"compositionend",function(){b.composing&&(b.poll(),b.composing.range.clear(),b.composing=null)})},prepareSelection:function(){var a=this.cm,b=a.display,c=a.doc,d=eb(a);if(a.options.moveInputWithCursor){var e=Lb(a,c.sel.primary().head,"div"),f=b.wrapper.getBoundingClientRect(),g=b.lineDiv.getBoundingClientRect();d.teTop=Math.max(0,Math.min(b.wrapper.clientHeight-10,e.top+g.top-f.top)),d.teLeft=Math.max(0,Math.min(b.wrapper.clientWidth-10,e.left+g.left-f.left))}return d},showSelection:function(a){var b=this.cm,c=b.display;sg(c.cursorDiv,a.cursors),sg(c.selectionDiv,a.selection),null!=a.teTop&&(this.wrapper.style.top=a.teTop+"px",this.wrapper.style.left=a.teLeft+"px")},reset:function(a){if(!this.contextMenuPending){var b,c,f=this.cm,g=f.doc;if(f.somethingSelected()){this.prevInput="";var h=g.sel.primary();b=Kg&&(h.to().line-h.from().line>100||(c=f.getSelection()).length>1e3);var i=b?"-":c||f.getSelection();this.textarea.value=i,f.state.focused&&cg(this.textarea),d&&e>=9&&(this.hasSelection=i)}else a||(this.prevInput=this.textarea.value="",d&&e>=9&&(this.hasSelection=null));this.inaccurateSelection=b}},getField:function(){return this.textarea},supportsTouch:function(){return!1},focus:function(){if("nocursor"!=this.cm.options.readOnly&&(!n||ug()!=this.textarea))try{this.textarea.focus()}catch(a){}},blur:function(){this.textarea.blur()},resetPosition:function(){this.wrapper.style.top=this.wrapper.style.left=0},receivedFocus:function(){this.slowPoll()},slowPoll:function(){var a=this;a.pollingFast||a.polling.set(this.cm.options.pollInterval,function(){a.poll(),a.cm.state.focused&&a.slowPoll()})},fastPoll:function(){function c(){var d=b.poll();d||a?(b.pollingFast=!1,b.slowPoll()):(a=!0,b.polling.set(60,c))}var a=!1,b=this;b.pollingFast=!0,b.polling.set(20,c)},poll:function(){var a=this.cm,b=this.textarea,c=this.prevInput;if(this.contextMenuPending||!a.state.focused||Jg(b)&&!c&&!this.composing||ua(a)||a.options.disableInput||a.state.keySeq)return!1;var f=b.value;if(f==c&&!a.somethingSelected())return!1;if(d&&e>=9&&this.hasSelection===f||o&&/[\uf700-\uf7ff]/.test(f))return a.display.input.reset(),!1;if(a.doc.sel==a.display.selForContextMenu){var g=f.charCodeAt(0);if(8203!=g||c||(c="\u200b"),8666==g)return this.reset(),this.cm.execCommand("undo")}for(var h=0,i=Math.min(c.length,f.length);i>h&&c.charCodeAt(h)==f.charCodeAt(h);)++h;var j=this;return cc(a,function(){wa(a,f.slice(h),c.length-h,null,j.composing?"*compose":null),f.length>1e3||f.indexOf("\n")>-1?b.value=j.prevInput="":j.prevInput=f,j.composing&&(j.composing.range.clear(),j.composing.range=a.markText(j.composing.start,a.getCursor("to"),{className:"CodeMirror-composing"}))}),!0},ensurePolled:function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},onKeyPress:function(){d&&e>=9&&(this.hasSelection=null),this.fastPoll()},onContextMenu:function(a){function o(){if(null!=h.selectionStart){var a=c.somethingSelected(),d="\u200b"+(a?h.value:"");h.value="\u21da",h.value=d,b.prevInput=a?"":"\u200b",h.selectionStart=1,h.selectionEnd=d.length,g.selForContextMenu=c.doc.sel}}function p(){if(b.contextMenuPending=!1,b.wrapper.style.position="relative",h.style.cssText=m,d&&9>e&&g.scrollbars.setScrollTop(g.scroller.scrollTop=k),null!=h.selectionStart){(!d||d&&9>e)&&o();var a=0,f=function(){g.selForContextMenu==c.doc.sel&&0==h.selectionStart&&h.selectionEnd>0&&"\u200b"==b.prevInput?dc(c,Ld.selectAll)(c):a++<10?g.detectingSelectAll=setTimeout(f,500):g.input.reset()};g.detectingSelectAll=setTimeout(f,200)}}var b=this,c=b.cm,g=c.display,h=b.textarea,j=tc(c,a),k=g.scroller.scrollTop;if(j&&!i){var l=c.options.resetSelectionOnContextMenu;l&&-1==c.doc.sel.contains(j)&&dc(c,Za)(c.doc,Ma(j),Vf);var m=h.style.cssText;if(b.wrapper.style.position="absolute",h.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(a.clientY-5)+"px; left: "+(a.clientX-5)+"px; z-index: 1000; background: "+(d?"rgba(255, 255, 255, .05)":"transparent")+"; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",f)var n=window.scrollY;if(g.input.focus(),f&&window.scrollTo(null,n),g.input.reset(),c.somethingSelected()||(h.value=b.prevInput=" "),b.contextMenuPending=!0,g.selForContextMenu=c.doc.sel,clearTimeout(g.detectingSelectAll),d&&e>=9&&o(),s){Gf(a);var q=function(){Kf(window,"mouseup",q),setTimeout(p,20)};Jf(window,"mouseup",q)}else setTimeout(p,50)}},setUneditable:fg,needsContentAttribute:!1},Ba.prototype),Da.prototype=hg({init:function(a){function e(a){if(c.somethingSelected())va=c.getSelections(),"cut"==a.type&&c.replaceSelection("",null,"cut");else{if(!c.options.lineWiseCopyCut)return;var b=za(c);va=b.text,"cut"==a.type&&c.operation(function(){c.setSelections(b.ranges,0,Vf),c.replaceSelection("",null,"cut")})}if(a.clipboardData&&!m)a.preventDefault(),a.clipboardData.clearData(),a.clipboardData.setData("text/plain",va.join("\n"));else{var d=Ca(),e=d.firstChild;c.display.lineSpace.insertBefore(d,c.display.lineSpace.firstChild),e.value=va.join("\n");var f=document.activeElement;cg(e),setTimeout(function(){c.display.lineSpace.removeChild(d),f.focus()},50)}}var b=this,c=b.cm,d=b.div=a.lineDiv;d.contentEditable="true",Aa(d),Jf(d,"paste",function(a){xa(a,c)}),Jf(d,"compositionstart",function(a){var d=a.data;if(b.composing={sel:c.doc.sel,data:d,startData:d},d){var e=c.doc.sel.primary(),f=c.getLine(e.head.line),g=f.indexOf(d,Math.max(0,e.head.ch-d.length));g>-1&&g<=e.head.ch&&(b.composing.sel=Ma(oa(e.head.line,g),oa(e.head.line,g+d.length)))}}),Jf(d,"compositionupdate",function(a){b.composing.data=a.data}),Jf(d,"compositionend",function(a){var c=b.composing;c&&(a.data==c.startData||/\u200b/.test(a.data)||(c.data=a.data),setTimeout(function(){c.handled||b.applyComposition(c),b.composing==c&&(b.composing=null)},50))}),Jf(d,"touchstart",function(){b.forceCompositionEnd()}),Jf(d,"input",function(){b.composing||b.pollContent()||cc(b.cm,function(){ic(c)})}),Jf(d,"copy",e),Jf(d,"cut",e)},prepareSelection:function(){var a=eb(this.cm,!1);return a.focus=this.cm.state.focused,a},showSelection:function(a){a&&this.cm.display.view.length&&(a.focus&&this.showPrimarySelection(),this.showMultipleSelections(a))},showPrimarySelection:function(){var b=window.getSelection(),c=this.cm.doc.sel.primary(),d=Ga(this.cm,b.anchorNode,b.anchorOffset),e=Ga(this.cm,b.focusNode,b.focusOffset);if(!d||d.bad||!e||e.bad||0!=pa(sa(d,e),c.from())||0!=pa(ra(d,e),c.to())){var f=Ea(this.cm,c.from()),g=Ea(this.cm,c.to());if(f||g){var h=this.cm.display.view,i=b.rangeCount&&b.getRangeAt(0);if(f){if(!g){var j=h[h.length-1].measure,k=j.maps?j.maps[j.maps.length-1]:j.map;g={node:k[k.length-1],offset:k[k.length-2]-k[k.length-3]}}}else f={node:h[0].measure.map[2],offset:0};try{var l=qg(f.node,f.offset,g.offset,g.node)}catch(m){}l&&(b.removeAllRanges(),b.addRange(l),i&&null==b.anchorNode?b.addRange(i):a&&this.startGracePeriod()),this.rememberSelection()}}},startGracePeriod:function(){var a=this;clearTimeout(this.gracePeriod),this.gracePeriod=setTimeout(function(){a.gracePeriod=!1,a.selectionChanged()&&a.cm.operation(function(){a.cm.curOp.selectionChanged=!0})},20)},showMultipleSelections:function(a){sg(this.cm.display.cursorDiv,a.cursors),sg(this.cm.display.selectionDiv,a.selection)},rememberSelection:function(){var a=window.getSelection();this.lastAnchorNode=a.anchorNode,this.lastAnchorOffset=a.anchorOffset,this.lastFocusNode=a.focusNode,this.lastFocusOffset=a.focusOffset},selectionInEditor:function(){var a=window.getSelection();if(!a.rangeCount)return!1;var b=a.getRangeAt(0).commonAncestorContainer;return tg(this.div,b)},focus:function(){"nocursor"!=this.cm.options.readOnly&&this.div.focus()},blur:function(){this.div.blur()},getField:function(){return this.div},supportsTouch:function(){return!0},receivedFocus:function(){function b(){a.cm.state.focused&&(a.pollSelection(),a.polling.set(a.cm.options.pollInterval,b))}var a=this;this.selectionInEditor()?this.pollSelection():cc(this.cm,function(){a.cm.curOp.selectionChanged=!0}),this.polling.set(this.cm.options.pollInterval,b)},selectionChanged:function(){var a=window.getSelection();return a.anchorNode!=this.lastAnchorNode||a.anchorOffset!=this.lastAnchorOffset||a.focusNode!=this.lastFocusNode||a.focusOffset!=this.lastFocusOffset},pollSelection:function(){if(!this.composing&&!this.gracePeriod&&this.selectionChanged()){var a=window.getSelection(),b=this.cm;this.rememberSelection();var c=Ga(b,a.anchorNode,a.anchorOffset),d=Ga(b,a.focusNode,a.focusOffset);c&&d&&cc(b,function(){Za(b.doc,Ma(c,d),Vf),(c.bad||d.bad)&&(b.curOp.selectionChanged=!0)})}},pollContent:function(){var a=this.cm,b=a.display,c=a.doc.sel.primary(),d=c.from(),e=c.to();if(d.lineb.viewTo-1)return!1;var f;if(d.line==b.viewFrom||0==(f=lc(a,d.line)))var g=kf(b.view[0].line),h=b.view[0].node;else var g=kf(b.view[f].line),h=b.view[f-1].node.nextSibling;var i=lc(a,e.line);if(i==b.view.length-1)var j=b.viewTo-1,k=b.lineDiv.lastChild;else var j=kf(b.view[i+1].line)-1,k=b.view[i+1].node.previousSibling;for(var l=a.doc.splitLines(Ia(a,h,k,g,j)),m=gf(a.doc,oa(g,0),oa(j,ff(a.doc,j).text.length));l.length>1&&m.length>1;)if(bg(l)==bg(m))l.pop(),m.pop(),j--;else{if(l[0]!=m[0])break;l.shift(),m.shift(),g++}for(var n=0,o=0,p=l[0],q=m[0],r=Math.min(p.length,q.length);r>n&&p.charCodeAt(n)==q.charCodeAt(n);)++n;for(var s=bg(l),t=bg(m),u=Math.min(s.length-(1==l.length?n:0),t.length-(1==m.length?n:0));u>o&&s.charCodeAt(s.length-o-1)==t.charCodeAt(t.length-o-1);)++o;l[l.length-1]=s.slice(0,s.length-o),l[0]=l[0].slice(n);var v=oa(g,n),w=oa(j,m.length?bg(m).length-o:0);return l.length>1||l[0]||pa(v,w)?(nd(a.doc,l,v,w,"+input"),!0):void 0},ensurePolled:function(){this.forceCompositionEnd()},reset:function(){this.forceCompositionEnd()},forceCompositionEnd:function(){this.composing&&!this.composing.handled&&(this.applyComposition(this.composing),this.composing.handled=!0,this.div.blur(),this.div.focus())},applyComposition:function(a){a.data&&a.data!=a.startData&&dc(this.cm,wa)(this.cm,a.data,0,a.sel)},setUneditable:function(a){a.setAttribute("contenteditable","false")},onKeyPress:function(a){a.preventDefault(),dc(this.cm,wa)(this.cm,String.fromCharCode(null==a.charCode?a.keyCode:a.charCode),0)},onContextMenu:fg,resetPosition:fg,needsContentAttribute:!0},Da.prototype),v.inputStyles={textarea:Ba,contenteditable:Da},Ja.prototype={primary:function(){return this.ranges[this.primIndex]},equals:function(a){if(a==this)return!0;if(a.primIndex!=this.primIndex||a.ranges.length!=this.ranges.length)return!1;for(var b=0;b=0&&pa(a,d.to())<=0)return c}return-1}},Ka.prototype={from:function(){return sa(this.anchor,this.head)},to:function(){return ra(this.anchor,this.head)},empty:function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch}};var Qb,vc,wc,zb={left:0,right:0,top:0,bottom:0},Tb=null,Ub=0,Cc=0,Jc=0,Kc=null;d?Kc=-.53:a?Kc=15:h?Kc=-.7:j&&(Kc=-1/3);var Lc=function(a){var b=a.wheelDeltaX,c=a.wheelDeltaY;return null==b&&a.detail&&a.axis==a.HORIZONTAL_AXIS&&(b=a.detail),null==c&&a.detail&&a.axis==a.VERTICAL_AXIS?c=a.detail:null==c&&(c=a.wheelDelta),{x:b,y:c}};v.wheelEventPixels=function(a){var b=Lc(a);return b.x*=Kc,b.y*=Kc,b};var Pc=new Yf,Tc=null,bd=v.changeEnd=function(a){return a.text?oa(a.from.line+a.text.length-1,bg(a.text).length+(1==a.text.length?a.from.ch:0)):a.to};v.prototype={constructor:v,focus:function(){window.focus(),this.display.input.focus()},setOption:function(a,b){var c=this.options,d=c[a];(c[a]!=b||"mode"==a)&&(c[a]=b,Bd.hasOwnProperty(a)&&dc(this,Bd[a])(this,b,d))},getOption:function(a){return this.options[a]},getDoc:function(){return this.doc},addKeyMap:function(a,b){this.state.keyMaps[b?"push":"unshift"](Rd(a))},removeKeyMap:function(a){for(var b=this.state.keyMaps,c=0;cc&&(vd(this,e.head.line,a,!0),c=e.head.line,d==this.doc.sel.primIndex&&td(this));else{var f=e.from(),g=e.to(),h=Math.max(c,f.line);c=Math.min(this.lastLine(),g.line-(g.ch?0:1))+1;for(var i=h;c>i;++i)vd(this,i,a);var j=this.doc.sel.ranges;0==f.ch&&b.length==j.length&&j[d].from().ch>0&&Va(this.doc,d,new Ka(f,j[d].to()),Vf)}}}),getTokenAt:function(a,b){return Ie(this,a,b)},getLineTokens:function(a,b){return Ie(this,oa(a),b,!0)},getTokenTypeAt:function(a){a=Oa(this.doc,a);var f,b=Le(this,ff(this.doc,a.line)),c=0,d=(b.length-1)/2,e=a.ch;if(0==e)f=b[2];else for(;;){var g=c+d>>1;if((g?b[2*g-1]:0)>=e)d=g;else{if(!(b[2*g+1]h?f:0==h?null:f.slice(0,h-1)},getModeAt:function(a){var b=this.doc.mode;return b.innerMode?v.innerMode(b,this.getTokenAt(a).state).mode:b},getHelper:function(a,b){return this.getHelpers(a,b)[0]},getHelpers:function(a,b){var c=[];if(!Id.hasOwnProperty(b))return c;var d=Id[b],e=this.getModeAt(a);if("string"==typeof e[b])d[e[b]]&&c.push(d[e[b]]);else if(e[b])for(var f=0;fe&&(a=e,c=!0),d=ff(this.doc,a)}else d=a;return Ib(this,d,{top:0,left:0},b||"page").top+(c?this.doc.height-mf(d):0)},defaultTextHeight:function(){return Rb(this.display)},defaultCharWidth:function(){return Sb(this.display)},setGutterMarker:ec(function(a,b,c){return wd(this.doc,a,"gutter",function(a){var d=a.gutterMarkers||(a.gutterMarkers={});return d[b]=c,!c&&mg(d)&&(a.gutterMarkers=null),!0})}),clearGutter:ec(function(a){var b=this,c=b.doc,d=c.first;c.iter(function(c){c.gutterMarkers&&c.gutterMarkers[a]&&(c.gutterMarkers[a]=null,jc(b,d,"gutter"),mg(c.gutterMarkers)&&(c.gutterMarkers=null)),++d})}),lineInfo:function(a){if("number"==typeof a){if(!Qa(this.doc,a))return null;var b=a;if(a=ff(this.doc,a),!a)return null}else{var b=kf(a);if(null==b)return null}return{line:b,handle:a,text:a.text,gutterMarkers:a.gutterMarkers,textClass:a.textClass,bgClass:a.bgClass,wrapClass:a.wrapClass,widgets:a.widgets}},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(a,b,c,d,e){var f=this.display;a=Lb(this,Oa(this.doc,a));var g=a.bottom,h=a.left;if(b.style.position="absolute",b.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(b),f.sizer.appendChild(b),"over"==d)g=a.top;else if("above"==d||"near"==d){var i=Math.max(f.wrapper.clientHeight,this.doc.height),j=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);("above"==d||a.bottom+b.offsetHeight>i)&&a.top>b.offsetHeight?g=a.top-b.offsetHeight:a.bottom+b.offsetHeight<=i&&(g=a.bottom),h+b.offsetWidth>j&&(h=j-b.offsetWidth)}b.style.top=g+"px",b.style.left=b.style.right="","right"==e?(h=f.sizer.clientWidth-b.offsetWidth,b.style.right="0px"):("left"==e?h=0:"middle"==e&&(h=(f.sizer.clientWidth-b.offsetWidth)/2),b.style.left=h+"px"),c&&qd(this,h,g,h+b.offsetWidth,g+b.offsetHeight)},triggerOnKeyDown:ec(Uc),triggerOnKeyPress:ec(Xc),triggerOnKeyUp:Wc,execCommand:function(a){return Ld.hasOwnProperty(a)?Ld[a].call(null,this):void 0},triggerElectric:ec(function(a){ya(this,a)}),findPosH:function(a,b,c,d){var e=1;0>b&&(e=-1,b=-b);for(var f=0,g=Oa(this.doc,a);b>f&&(g=yd(this.doc,g,e,c,d),!g.hitSide);++f);return g},moveH:ec(function(a,b){var c=this;c.extendSelectionsBy(function(d){return c.display.shift||c.doc.extend||d.empty()?yd(c.doc,d.head,a,b,c.options.rtlMoveVisually):0>a?d.from():d.to()},Xf)}),deleteH:ec(function(a,b){var c=this.doc.sel,d=this.doc;c.somethingSelected()?d.replaceSelection("",null,"+delete"):xd(this,function(c){var e=yd(d,c.head,a,b,!1);return 0>a?{from:e,to:c.head}:{from:c.head,to:e}})}),findPosV:function(a,b,c,d){var e=1,f=d;0>b&&(e=-1,b=-b);for(var g=0,h=Oa(this.doc,a);b>g;++g){var i=Lb(this,h,"div");if(null==f?f=i.left:i.left=f,h=zd(this,i,e,c),h.hitSide)break}return h},moveV:ec(function(a,b){var c=this,d=this.doc,e=[],f=!c.display.shift&&!d.extend&&d.sel.somethingSelected();if(d.extendSelectionsBy(function(g){if(f)return 0>a?g.from():g.to();var h=Lb(c,g.head,"div");null!=g.goalColumn&&(h.left=g.goalColumn),e.push(h.left);var i=zd(c,h,a,b);return"page"==b&&g==d.sel.primary()&&sd(c,null,Kb(c,i,"div").top-h.top),i},Xf),e.length)for(var g=0;g0&&h(c.charAt(d-1));)--d;for(;e.5)&&B(this),Lf(this,"refresh",this)}),swapDoc:ec(function(a){var b=this.doc;return b.cm=null,ef(this,a),Fb(this),this.display.input.reset(),this.scrollTo(a.scrollLeft,a.scrollTop),this.curOp.forceScroll=!0,Nf(this,"swapDoc",this,b),b}),getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},Sf(v);var Ad=v.defaults={},Bd=v.optionHandlers={},Dd=v.Init={toString:function(){return"CodeMirror.Init"}};Cd("value","",function(a,b){a.setValue(b)},!0),Cd("mode",null,function(a,b){a.doc.modeOption=b,x(a)},!0),Cd("indentUnit",2,x,!0),Cd("indentWithTabs",!1),Cd("smartIndent",!0),Cd("tabSize",4,function(a){y(a),Fb(a),ic(a)},!0),Cd("lineSeparator",null,function(a,b){if(a.doc.lineSep=b,b){var c=[],d=a.doc.first;a.doc.iter(function(a){for(var e=0;;){var f=a.text.indexOf(b,e);if(-1==f)break;e=f+b.length,c.push(oa(d,f))}d++});for(var e=c.length-1;e>=0;e--)nd(a.doc,b,c[e],oa(c[e].line,c[e].ch+b.length))}}),Cd("specialChars",/[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g,function(a,b,c){a.state.specialChars=new RegExp(b.source+(b.test(" ")?"":"| "),"g"),c!=v.Init&&a.refresh()}),Cd("specialCharPlaceholder",Re,function(a){a.refresh()},!0),Cd("electricChars",!0),Cd("inputStyle",n?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},!0),Cd("rtlMoveVisually",!p),Cd("wholeLineUpdateBefore",!0),Cd("theme","default",function(a){C(a),D(a)},!0),Cd("keyMap","default",function(a,b,c){var d=Rd(b),e=c!=v.Init&&Rd(c);e&&e.detach&&e.detach(a,d),d.attach&&d.attach(a,e||null)}),Cd("extraKeys",null),Cd("lineWrapping",!1,z,!0),Cd("gutters",[],function(a){I(a.options),D(a)},!0),Cd("fixedGutter",!0,function(a,b){a.display.gutters.style.left=b?T(a.display)+"px":"0",a.refresh()},!0),Cd("coverGutterNextToScrollbar",!1,function(a){N(a)},!0),Cd("scrollbarStyle","native",function(a){M(a),N(a),a.display.scrollbars.setScrollTop(a.doc.scrollTop),a.display.scrollbars.setScrollLeft(a.doc.scrollLeft)},!0),Cd("lineNumbers",!1,function(a){I(a.options),D(a)},!0),Cd("firstLineNumber",1,D,!0),Cd("lineNumberFormatter",function(a){return a},D,!0),Cd("showCursorWhenSelecting",!1,db,!0),Cd("resetSelectionOnContextMenu",!0),Cd("lineWiseCopyCut",!0),Cd("readOnly",!1,function(a,b){"nocursor"==b?($c(a),a.display.input.blur(),a.display.disabled=!0):(a.display.disabled=!1,b||a.display.input.reset())}),Cd("disableInput",!1,function(a,b){b||a.display.input.reset()},!0),Cd("dragDrop",!0,qc),Cd("cursorBlinkRate",530),Cd("cursorScrollMargin",0),Cd("cursorHeight",1,db,!0),Cd("singleCursorHeightPerLine",!0,db,!0),Cd("workTime",100),Cd("workDelay",100),Cd("flattenSpans",!0,y,!0),Cd("addModeClass",!1,y,!0),Cd("pollInterval",100),Cd("undoDepth",200,function(a,b){a.doc.history.undoDepth=b}),Cd("historyEventDelay",1250),Cd("viewportMargin",10,function(a){a.refresh()},!0),Cd("maxHighlightLength",1e4,y,!0),Cd("moveInputWithCursor",!0,function(a,b){b||a.display.input.resetPosition()}),Cd("tabindex",null,function(a,b){a.display.input.getField().tabIndex=b||""}),Cd("autofocus",null);var Ed=v.modes={},Fd=v.mimeModes={};v.defineMode=function(a,b){v.defaults.mode||"null"==a||(v.defaults.mode=a),arguments.length>2&&(b.dependencies=Array.prototype.slice.call(arguments,2)),Ed[a]=b},v.defineMIME=function(a,b){Fd[a]=b},v.resolveMode=function(a){if("string"==typeof a&&Fd.hasOwnProperty(a))a=Fd[a];else if(a&&"string"==typeof a.name&&Fd.hasOwnProperty(a.name)){var b=Fd[a.name];"string"==typeof b&&(b={name:b}),a=gg(b,a),a.name=b.name}else if("string"==typeof a&&/^[\w\-]+\/[\w\-]+\+xml$/.test(a))return v.resolveMode("application/xml");return"string"==typeof a?{name:a}:a||{name:"null"}},v.getMode=function(a,b){var b=v.resolveMode(b),c=Ed[b.name];if(!c)return v.getMode(a,"text/plain");var d=c(a,b);if(Gd.hasOwnProperty(b.name)){var e=Gd[b.name];for(var f in e)e.hasOwnProperty(f)&&(d.hasOwnProperty(f)&&(d["_"+f]=d[f]),d[f]=e[f])}if(d.name=b.name,b.helperType&&(d.helperType=b.helperType),b.modeProps)for(var f in b.modeProps)d[f]=b.modeProps[f];return d},v.defineMode("null",function(){return{token:function(a){a.skipToEnd()}}}),v.defineMIME("text/plain","null");var Gd=v.modeExtensions={};v.extendMode=function(a,b){var c=Gd.hasOwnProperty(a)?Gd[a]:Gd[a]={};hg(b,c)},v.defineExtension=function(a,b){v.prototype[a]=b},v.defineDocExtension=function(a,b){af.prototype[a]=b},v.defineOption=Cd;var Hd=[];v.defineInitHook=function(a){Hd.push(a)};var Id=v.helpers={};v.registerHelper=function(a,b,c){Id.hasOwnProperty(a)||(Id[a]=v[a]={_global:[]}),Id[a][b]=c},v.registerGlobalHelper=function(a,b,c,d){v.registerHelper(a,b,d),Id[a]._global.push({pred:c,val:d})};var Jd=v.copyState=function(a,b){if(b===!0)return b;if(a.copyState)return a.copyState(b);var c={};for(var d in b){var e=b[d];e instanceof Array&&(e=e.concat([])),c[d]=e}return c},Kd=v.startState=function(a,b,c){return a.startState?a.startState(b,c):!0};v.innerMode=function(a,b){for(;a.innerMode;){var c=a.innerMode(b);if(!c||c.mode==a)break;b=c.state,a=c.mode}return c||{mode:a,state:b}};var Ld=v.commands={selectAll:function(a){a.setSelection(oa(a.firstLine(),0),oa(a.lastLine()),Vf)},singleSelection:function(a){a.setSelection(a.getCursor("anchor"),a.getCursor("head"),Vf)},killLine:function(a){xd(a,function(b){if(b.empty()){var c=ff(a.doc,b.head.line).text.length;return b.head.ch==c&&b.head.line0)e=new oa(e.line,e.ch+1),a.replaceRange(f.charAt(e.ch-1)+f.charAt(e.ch-2),oa(e.line,e.ch-2),e,"+transpose");else if(e.line>a.doc.first){var g=ff(a.doc,e.line-1).text;g&&a.replaceRange(f.charAt(0)+a.doc.lineSeparator()+g.charAt(g.length-1),oa(e.line-1,g.length-1),oa(e.line,1),"+transpose")}c.push(new Ka(e,e))}a.setSelections(c)})},newlineAndIndent:function(a){cc(a,function(){for(var b=a.listSelections().length,c=0;b>c;c++){var d=a.listSelections()[c];a.replaceRange(a.doc.lineSeparator(),d.anchor,d.head,"+input"),a.indentLine(d.from().line+1,null,!0),td(a)}})},toggleOverwrite:function(a){a.toggleOverwrite()}},Md=v.keyMap={};Md.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite",Esc:"singleSelection"},Md.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Up":"goLineUp","Ctrl-Down":"goLineDown","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore","Ctrl-U":"undoSelection","Shift-Ctrl-U":"redoSelection","Alt-U":"redoSelection",fallthrough:"basic"},Md.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Alt-F":"goWordRight","Alt-B":"goWordLeft","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-D":"delWordAfter","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars"},Md.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Home":"goDocStart","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineLeft","Cmd-Right":"goLineRight","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delWrappedLineLeft","Cmd-Delete":"delWrappedLineRight","Cmd-U":"undoSelection", -"Shift-Cmd-U":"redoSelection","Ctrl-Up":"goDocStart","Ctrl-Down":"goDocEnd",fallthrough:["basic","emacsy"]},Md["default"]=o?Md.macDefault:Md.pcDefault,v.normalizeKeyMap=function(a){var b={};for(var c in a)if(a.hasOwnProperty(c)){var d=a[c];if(/^(name|fallthrough|(de|at)tach)$/.test(c))continue;if("..."==d){delete a[c];continue}for(var e=eg(c.split(" "),Nd),f=0;f=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.posb},eatSpace:function(){for(var a=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);return b>-1?(this.pos=b,!0):void 0},backUp:function(a){this.pos-=a},column:function(){return this.lastColumnPos0?null:(f&&b!==!1&&(this.pos+=f[0].length),f)}var d=function(a){return c?a.toLowerCase():a},e=this.string.substr(this.pos,a.length);return d(e)==d(a)?(b!==!1&&(this.pos+=a.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(a,b){this.lineStart+=a;try{return b()}finally{this.lineStart-=a}}};var Td=0,Ud=v.TextMarker=function(a,b){this.lines=[],this.type=b,this.doc=a,this.id=++Td};Sf(Ud),Ud.prototype.clear=function(){if(!this.explicitlyCleared){var a=this.doc.cm,b=a&&!a.curOp;if(b&&Vb(a),Rf(this,"clear")){var c=this.find();c&&Nf(this,"clear",c.from,c.to)}for(var d=null,e=null,f=0;fa.display.maxLineLength&&(a.display.maxLine=i,a.display.maxLineLength=j,a.display.maxLineChanged=!0)}null!=d&&a&&this.collapsed&&ic(a,d,e+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,a&&ab(a.doc)),a&&Nf(a,"markerCleared",a,this),b&&Xb(a),this.parent&&this.parent.clear()}},Ud.prototype.find=function(a,b){null==a&&"bookmark"==this.type&&(a=1);for(var c,d,e=0;ec;++c){var e=this.lines[c];this.height-=e.height,Ee(e),Nf(e,"delete")}this.lines.splice(a,b)},collapse:function(a){a.push.apply(a,this.lines)},insertInner:function(a,b,c){this.height+=c,this.lines=this.lines.slice(0,a).concat(b).concat(this.lines.slice(a));for(var d=0;da;++a)if(c(this.lines[a]))return!0}},$e.prototype={chunkSize:function(){return this.size},removeInner:function(a,b){this.size-=b;for(var c=0;ca){var f=Math.min(b,e-a),g=d.height;if(d.removeInner(a,f),this.height-=g-d.height,e==f&&(this.children.splice(c--,1),d.parent=null),0==(b-=f))break;a=0}else a-=e}if(this.size-b<25&&(this.children.length>1||!(this.children[0]instanceof Ze))){var h=[];this.collapse(h),this.children=[new Ze(h)],this.children[0].parent=this}},collapse:function(a){for(var b=0;b=a){if(e.insertInner(a,b,c),e.lines&&e.lines.length>50){for(;e.lines.length>50;){var g=e.lines.splice(e.lines.length-25,25),h=new Ze(g);e.height-=h.height,this.children.splice(d+1,0,h),h.parent=this}this.maybeSpill()}break}a-=f}},maybeSpill:function(){if(!(this.children.length<=10)){var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new $e(b);if(a.parent){a.size-=c.size,a.height-=c.height;var e=dg(a.parent.children,a);a.parent.children.splice(e+1,0,c)}else{var d=new $e(a.children);d.parent=a,a.children=[d,c],a=d}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()}},iterN:function(a,b,c){for(var d=0;da){var g=Math.min(b,f-a);if(e.iterN(a,g,c))return!0;if(0==(b-=g))break;a=0}else a-=f}}};var _e=0,af=v.Doc=function(a,b,c,d){if(!(this instanceof af))return new af(a,b,c,d);null==c&&(c=0),$e.call(this,[new Ze([new Ce("",null)])]),this.first=c,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.cleanGeneration=1,this.frontier=c;var e=oa(c,0);this.sel=Ma(e),this.history=new of(null),this.id=++_e,this.modeOption=b,this.lineSep=d,"string"==typeof a&&(a=this.splitLines(a)),Ye(this,{from:e,to:e,text:a}),Za(this,Ma(e),Vf)};af.prototype=gg($e.prototype,{constructor:af,iter:function(a,b,c){c?this.iterN(a-this.first,b-a,c):this.iterN(this.first,this.first+this.size,a)},insert:function(a,b){for(var c=0,d=0;d=0;f--)hd(this,d[f]);h?Ya(this,h):this.cm&&td(this.cm)}),undo:fc(function(){jd(this,"undo")}),redo:fc(function(){jd(this,"redo")}),undoSelection:fc(function(){jd(this,"undo",!0)}),redoSelection:fc(function(){jd(this,"redo",!0)}),setExtending:function(a){this.extend=a},getExtending:function(){return this.extend},historySize:function(){for(var a=this.history,b=0,c=0,d=0;d=a.ch)&&b.push(e.marker.parent||e.marker)}return b},findMarks:function(a,b,c){a=Oa(this,a),b=Oa(this,b);var d=[],e=a.line;return this.iter(a.line,b.line+1,function(f){var g=f.markedSpans;if(g)for(var h=0;hi.to||null==i.from&&e!=a.line||e==b.line&&i.from>b.ch||c&&!c(i.marker)||d.push(i.marker.parent||i.marker)}++e}),d},getAllMarks:function(){var a=[];return this.iter(function(b){var c=b.markedSpans;if(c)for(var d=0;da?(b=a,!0):(a-=e,void++c)}),Oa(this,oa(c,b))},indexFromPos:function(a){a=Oa(this,a);var b=a.ch;return a.lineb&&(b=a.from),null!=a.to&&a.toh||h>=b)return g+(b-f);g+=h-f,g+=c-g%c,f=h+1}},$f=v.findColumn=function(a,b,c){for(var d=0,e=0;;){var f=a.indexOf(" ",d);-1==f&&(f=a.length);var g=f-d;if(f==a.length||e+g>=b)return d+Math.min(g,b-e);if(e+=f-d,e+=c-e%c,d=f+1,e>=b)return d}},_f=[""],cg=function(a){a.select()};m?cg=function(a){a.selectionStart=0,a.selectionEnd=a.value.length}:d&&(cg=function(a){try{a.select()}catch(b){}});var qg,jg=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,kg=v.isWordChar=function(a){return/\w/.test(a)||a>"\x80"&&(a.toUpperCase()!=a.toLowerCase()||jg.test(a))},ng=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;qg=document.createRange?function(a,b,c,d){var e=document.createRange();return e.setEnd(d||a,c),e.setStart(a,b),e}:function(a,b,c){var d=document.body.createTextRange();try{d.moveToElementText(a.parentNode)}catch(e){return d}return d.collapse(!0),d.moveEnd("character",c),d.moveStart("character",b),d};var tg=v.contains=function(a,b){if(3==b.nodeType&&(b=b.parentNode),a.contains)return a.contains(b);do if(11==b.nodeType&&(b=b.host),b==a)return!0;while(b=b.parentNode)};d&&11>e&&(ug=function(){try{return document.activeElement}catch(a){return document.body}});var Eg,Gg,wg=v.rmClass=function(a,b){var c=a.className,d=vg(b).exec(c);if(d){var e=c.slice(d.index+d[0].length);a.className=c.slice(0,d.index)+(e?d[1]+e:"")}},xg=v.addClass=function(a,b){var c=a.className;vg(b).test(c)||(a.className+=(c?" ":"")+b)},Ag=!1,Dg=function(){if(d&&9>e)return!1;var a=pg("div");return"draggable"in a||"dragDrop"in a}(),Ig=v.splitLines=3!="\n\nb".split(/\n/).length?function(a){for(var b=0,c=[],d=a.length;d>=b;){var e=a.indexOf("\n",b);-1==e&&(e=a.length);var f=a.slice(b,"\r"==a.charAt(e-1)?e-1:e),g=f.indexOf("\r");-1!=g?(c.push(f.slice(0,g)),b+=g+1):(c.push(f),b=e+1)}return c}:function(a){return a.split(/\r\n?|\n/)},Jg=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return b&&b.parentElement()==a?0!=b.compareEndPoints("StartToEnd",b):!1},Kg=function(){var a=pg("div");return"oncopy"in a?!0:(a.setAttribute("oncopy","return;"),"function"==typeof a.oncopy)}(),Lg=null,Ng=v.keyNames={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",61:"=",91:"Mod",92:"Mod",93:"Mod",106:"*",107:"=",109:"-",110:".",111:"/",127:"Delete",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63232:"Up",63233:"Down",63234:"Left",63235:"Right",63272:"Delete",63273:"Home",63275:"End",63276:"PageUp",63277:"PageDown",63302:"Insert"};!function(){for(var a=0;10>a;a++)Ng[a+48]=Ng[a+96]=String(a);for(var a=65;90>=a;a++)Ng[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)Ng[a+111]=Ng[a+63235]="F"+a}();var Xg,ah=function(){function c(c){return 247>=c?a.charAt(c):c>=1424&&1524>=c?"R":c>=1536&&1773>=c?b.charAt(c-1536):c>=1774&&2220>=c?"r":c>=8192&&8203>=c?"w":8204==c?"b":"L"}function j(a,b,c){this.level=a,this.from=b,this.to=c}var a="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",b="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm",d=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,e=/[stwN]/,f=/[LRr]/,g=/[Lb1n]/,h=/[1n]/,i="L";return function(a){if(!d.test(a))return!1;for(var m,b=a.length,k=[],l=0;b>l;++l)k.push(m=c(a.charCodeAt(l)));for(var l=0,n=i;b>l;++l){var m=k[l];"m"==m?k[l]=n:n=m}for(var l=0,o=i;b>l;++l){var m=k[l];"1"==m&&"r"==o?k[l]="n":f.test(m)&&(o=m,"r"==m&&(k[l]="R"))}for(var l=1,n=k[0];b-1>l;++l){var m=k[l];"+"==m&&"1"==n&&"1"==k[l+1]?k[l]="1":","!=m||n!=k[l+1]||"1"!=n&&"n"!=n||(k[l]=n),n=m}for(var l=0;b>l;++l){var m=k[l];if(","==m)k[l]="N";else if("%"==m){for(var p=l+1;b>p&&"%"==k[p];++p);for(var q=l&&"!"==k[l-1]||b>p&&"1"==k[p]?"1":"N",r=l;p>r;++r)k[r]=q;l=p-1}}for(var l=0,o=i;b>l;++l){var m=k[l];"L"==o&&"1"==m?k[l]="L":f.test(m)&&(o=m)}for(var l=0;b>l;++l)if(e.test(k[l])){for(var p=l+1;b>p&&e.test(k[p]);++p);for(var s="L"==(l?k[l-1]:i),t="L"==(b>p?k[p]:i),q=s||t?"L":"R",r=l;p>r;++r)k[r]=q;l=p-1}for(var v,u=[],l=0;b>l;)if(g.test(k[l])){var w=l;for(++l;b>l&&g.test(k[l]);++l);u.push(new j(0,w,l))}else{var x=l,y=u.length;for(++l;b>l&&"L"!=k[l];++l);for(var r=x;l>r;)if(h.test(k[r])){r>x&&u.splice(y,0,new j(1,x,r));var z=r;for(++r;l>r&&h.test(k[r]);++r);u.splice(y,0,new j(2,z,r)),x=r}else++r;l>x&&u.splice(y,0,new j(1,x,l))}return 1==u[0].level&&(v=a.match(/^\s+/))&&(u[0].from=v[0].length,u.unshift(new j(0,0,v[0].length))),1==bg(u).level&&(v=a.match(/\s+$/))&&(bg(u).to-=v[0].length,u.push(new j(0,b-v[0].length,b))),2==u[0].level&&u.unshift(new j(1,u[0].to,u[0].to)),u[0].level!=bg(u).level&&u.push(new j(u[0].level,b,b)),u}}();return v.version="5.6.1",v}),function(a){"object"==typeof exports&&"object"==typeof module?a(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],a):a(CodeMirror)}(function(a){"use strict";function c(a,b,c){var d=a.current(),e=d.search(b);return e>-1?a.backUp(d.length-e):d.match(/<\/?$/)&&(a.backUp(d.length),a.match(b,!1)||a.match(d)),c}function e(a){var b=d[a];return b?b:d[a]=new RegExp("\\s+"+a+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function f(a,b){for(var d,c=a.pos;c>=0&&"<"!==a.string.charAt(c);)c--;return 0>c?c:(d=a.string.slice(c,a.pos).match(e(b)))?d[2]:""}function g(a,b){return new RegExp((b?"^":"")+"","i")}function h(a,b){for(var c in a)for(var d=b[c]||(b[c]=[]),e=a[c],f=e.length-1;f>=0;f--)d.unshift(e[f])}function i(a,b){for(var c=0;c"===b.current()&&(m=i(k,b))){var o=a.getMode(d,m),p=g(h,!0),q=g(h,!1);e.token=function(a,b){return a.match(p,!1)?(b.token=n,b.localState=b.localMode=null,null):c(a,q,b.localMode.token(a,b.localState))},e.localMode=o,e.localState=a.startState(o,f.indent(e.htmlState,""))}return l}var f=a.getMode(d,{name:"xml",htmlMode:!0,multilineTagIndentFactor:e.multilineTagIndentFactor,multilineTagIndentPastTag:e.multilineTagIndentPastTag}),j={},k=e&&e.tags,l=e&&e.scriptTypes;if(h(b,j),k&&h(k,j),l)for(var m=l.length-1;m>=0;m--)j.script.unshift(["type",l[m].matches,l[m].mode]);return{startState:function(){var a=f.startState();return{token:n,localMode:null,localState:null,htmlState:a}},copyState:function(b){var c;return b.localState&&(c=a.copyState(b.localMode,b.localState)),{token:b.token,localMode:b.localMode,localState:c,htmlState:a.copyState(f,b.htmlState)}},token:function(a,b){return b.token(a,b)},indent:function(b,c){return!b.localMode||/^\s*<\//.test(c)?f.indent(b.htmlState,c):b.localMode.indent?b.localMode.indent(b.localState,c):a.Pass},innerMode:function(a){return{state:a.localState||a.htmlState,mode:a.localMode||f}}}},"xml","javascript","css"),a.defineMIME("text/html","htmlmixed")}),function(a){"object"==typeof exports&&"object"==typeof module?a(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],a):a(CodeMirror)}(function(a){"use strict";a.defineMode("javascript",function(b,c){function m(a){for(var c,b=!1,d=!1;null!=(c=a.next());){if(!b){if("/"==c&&!d)return;"["==c?d=!0:d&&"]"==c&&(d=!1)}b=!b&&"\\"==c}}function p(a,b,c){return n=a,o=c,b}function q(a,b){var c=a.next();if('"'==c||"'"==c)return b.tokenize=r(c),b.tokenize(a,b);if("."==c&&a.match(/^\d+(?:[eE][+\-]?\d+)?/))return p("number","number");if("."==c&&a.match(".."))return p("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(c))return p(c);if("="==c&&a.eat(">"))return p("=>","operator");if("0"==c&&a.eat(/x/i))return a.eatWhile(/[\da-f]/i),p("number","number");if("0"==c&&a.eat(/o/i))return a.eatWhile(/[0-7]/i),p("number","number");if("0"==c&&a.eat(/b/i))return a.eatWhile(/[01]/i),p("number","number");if(/\d/.test(c))return a.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),p("number","number");if("/"==c)return a.eat("*")?(b.tokenize=s,s(a,b)):a.eat("/")?(a.skipToEnd(),p("comment","comment")):"operator"==b.lastType||"keyword c"==b.lastType||"sof"==b.lastType||/^[\[{}\(,;:]$/.test(b.lastType)?(m(a),a.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/),p("regexp","string-2")):(a.eatWhile(k),p("operator","operator",a.current()));if("`"==c)return b.tokenize=t,t(a,b);if("#"==c)return a.skipToEnd(),p("error","error");if(k.test(c))return a.eatWhile(k),p("operator","operator",a.current());if(i.test(c)){a.eatWhile(i);var d=a.current(),e=j.propertyIsEnumerable(d)&&j[d];return e&&"."!=b.lastType?p(e.type,e.style,d):p("variable","variable",d)}}function r(a){return function(b,c){var e,d=!1;if(f&&"@"==b.peek()&&b.match(l))return c.tokenize=q,p("jsonld-keyword","meta");for(;null!=(e=b.next())&&(e!=a||d);)d=!d&&"\\"==e;return d||(c.tokenize=q),p("string","string")}}function s(a,b){for(var d,c=!1;d=a.next();){if("/"==d&&c){b.tokenize=q;break}c="*"==d}return p("comment","comment")}function t(a,b){for(var d,c=!1;null!=(d=a.next());){if(!c&&("`"==d||"$"==d&&a.eat("{"))){b.tokenize=q;break}c=!c&&"\\"==d}return p("quasi","string-2",a.current())}function v(a,b){b.fatArrowAt&&(b.fatArrowAt=null);var c=a.string.indexOf("=>",a.start);if(!(0>c)){for(var d=0,e=!1,f=c-1;f>=0;--f){var g=a.string.charAt(f),h=u.indexOf(g);if(h>=0&&3>h){if(!d){++f;break}if(0==--d)break}else if(h>=3&&6>h)++d;else if(i.test(g))e=!0;else{if(/["'\/]/.test(g))return;if(e&&!d){++f;break}}}e&&!d&&(b.fatArrowAt=f)}}function x(a,b,c,d,e,f){this.indented=a,this.column=b,this.type=c,this.prev=e,this.info=f,null!=d&&(this.align=d)}function y(a,b){for(var c=a.localVars;c;c=c.next)if(c.name==b)return!0;for(var d=a.context;d;d=d.prev)for(var c=d.vars;c;c=c.next)if(c.name==b)return!0}function z(a,b,c,d,e){var f=a.cc;for(A.state=a,A.stream=e,A.marked=null,A.cc=f,A.style=b,a.lexical.hasOwnProperty("align")||(a.lexical.align=!0);;){var h=f.length?f.pop():g?L:K;if(h(c,d)){for(;f.length&&f[f.length-1].lex;)f.pop()();return A.marked?A.marked:"variable"==c&&y(a,d)?"variable-2":b}}}function B(){for(var a=arguments.length-1;a>=0;a--)A.cc.push(arguments[a])}function C(){return B.apply(null,arguments),!0}function D(a){function b(b){for(var c=b;c;c=c.next)if(c.name==a)return!0;return!1}var d=A.state;if(d.context){if(A.marked="def",b(d.localVars))return;d.localVars={name:a,next:d.localVars}}else{if(b(d.globalVars))return;c.globalVars&&(d.globalVars={name:a,next:d.globalVars})}}function F(){A.state.context={prev:A.state.context,vars:A.state.localVars},A.state.localVars=E}function G(){A.state.localVars=A.state.context.vars,A.state.context=A.state.context.prev}function H(a,b){var c=function(){var c=A.state,d=c.indented;if("stat"==c.lexical.type)d=c.lexical.indented;else for(var e=c.lexical;e&&")"==e.type&&e.align;e=e.prev)d=e.indented; -c.lexical=new x(d,A.stream.column(),a,null,c.lexical,b)};return c.lex=!0,c}function I(){var a=A.state;a.lexical.prev&&(")"==a.lexical.type&&(a.indented=a.lexical.indented),a.lexical=a.lexical.prev)}function J(a){function b(c){return c==a?C():";"==a?B():C(b)}return b}function K(a,b){return"var"==a?C(H("vardef",b.length),fa,J(";"),I):"keyword a"==a?C(H("form"),L,K,I):"keyword b"==a?C(H("form"),K,I):"{"==a?C(H("}"),ba,I):";"==a?C():"if"==a?("else"==A.state.lexical.info&&A.state.cc[A.state.cc.length-1]==I&&A.state.cc.pop()(),C(H("form"),L,K,I,ka)):"function"==a?C(qa):"for"==a?C(H("form"),la,K,I):"variable"==a?C(H("stat"),W):"switch"==a?C(H("form"),L,H("}","switch"),J("{"),ba,I,I):"case"==a?C(L,J(":")):"default"==a?C(J(":")):"catch"==a?C(H("form"),F,J("("),ra,J(")"),K,I,G):"class"==a?C(H("form"),sa,I):"export"==a?C(H("stat"),wa,I):"import"==a?C(H("stat"),xa,I):B(H("stat"),L,J(";"),I)}function L(a){return N(a,!1)}function M(a){return N(a,!0)}function N(a,b){if(A.state.fatArrowAt==A.stream.start){var c=b?V:U;if("("==a)return C(F,H(")"),_(ga,")"),I,J("=>"),c,G);if("variable"==a)return B(F,ga,J("=>"),c,G)}var d=b?R:Q;return w.hasOwnProperty(a)?C(d):"async"==a?C(L):"function"==a?C(qa,d):"keyword c"==a?C(b?P:O):"("==a?C(H(")"),O,Da,J(")"),I,d):"operator"==a||"spread"==a?C(b?M:L):"["==a?C(H("]"),Ba,I,d):"{"==a?aa(Y,"}",null,d):"quasi"==a?B(S,d):C()}function O(a){return a.match(/[;\}\)\],]/)?B():B(L)}function P(a){return a.match(/[;\}\)\],]/)?B():B(M)}function Q(a,b){return","==a?C(L):R(a,b,!1)}function R(a,b,c){var d=0==c?Q:R,e=0==c?L:M;return"=>"==a?C(F,c?V:U,G):"operator"==a?/\+\+|--/.test(b)?C(d):"?"==b?C(L,J(":"),e):C(e):"quasi"==a?B(S,d):";"!=a?"("==a?aa(M,")","call",d):"."==a?C(X,d):"["==a?C(H("]"),O,J("]"),I,d):void 0:void 0}function S(a,b){return"quasi"!=a?B():"${"!=b.slice(b.length-2)?C(S):C(L,T)}function T(a){return"}"==a?(A.marked="string-2",A.state.tokenize=t,C(S)):void 0}function U(a){return v(A.stream,A.state),B("{"==a?K:L)}function V(a){return v(A.stream,A.state),B("{"==a?K:M)}function W(a){return":"==a?C(I,K):B(Q,J(";"),I)}function X(a){return"variable"==a?(A.marked="property",C()):void 0}function Y(a,b){return"async"==a?C(Y):"variable"==a||"keyword"==A.style?(A.marked="property",C("get"==b||"set"==b?Z:$)):"number"==a||"string"==a?(A.marked=f?"property":A.style+" property",C($)):"jsonld-keyword"==a?C($):"["==a?C(L,J("]"),$):void 0}function Z(a){return"variable"!=a?B($):(A.marked="property",C(qa))}function $(a){return":"==a?C(M):"("==a?B(qa):void 0}function _(a,b){function c(d){if(","==d){var e=A.state.lexical;return"call"==e.info&&(e.pos=(e.pos||0)+1),C(a,c)}return d==b?C():C(J(b))}return function(d){return d==b?C():B(a,c)}}function aa(a,b,c){for(var d=3;d!?|~^]/,l=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,u="([{}])",w={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,"this":!0,"jsonld-keyword":!0},A={state:null,column:null,marked:null,cc:null},E={name:"this",next:{name:"arguments"}};return I.lex=!0,{startState:function(a){var b={tokenize:q,lastType:"sof",cc:[],lexical:new x((a||0)-d,0,"block",!1),localVars:c.localVars,context:c.localVars&&{vars:c.localVars},indented:0};return c.globalVars&&"object"==typeof c.globalVars&&(b.globalVars=c.globalVars),b},token:function(a,b){if(a.sol()&&(b.lexical.hasOwnProperty("align")||(b.lexical.align=!1),b.indented=a.indentation(),v(a,b)),b.tokenize!=s&&a.eatSpace())return null;var c=b.tokenize(a,b);return"comment"==n?c:(b.lastType="operator"!=n||"++"!=o&&"--"!=o?n:"incdec",z(b,c,n,o,a))},indent:function(b,f){if(b.tokenize==s)return a.Pass;if(b.tokenize!=q)return 0;var g=f&&f.charAt(0),h=b.lexical;if(!/^\s*else\b/.test(f))for(var i=b.cc.length-1;i>=0;--i){var j=b.cc[i];if(j==I)h=h.prev;else if(j!=ka)break}"stat"==h.type&&"}"==g&&(h=h.prev),e&&")"==h.type&&"stat"==h.prev.type&&(h=h.prev);var k=h.type,l=g==k;return"vardef"==k?h.indented+("operator"==b.lastType||","==b.lastType?h.info+1:0):"form"==k&&"{"==g?h.indented:"form"==k?h.indented+d:"stat"==k?h.indented+(Ea(b,f)?e||d:0):"switch"!=h.info||l||0==c.doubleIndentSwitch?h.align?h.column+(l?0:1):h.indented+(l?0:d):h.indented+(/^(?:case|default)\b/.test(f)?d:2*d)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:g?null:"/*",blockCommentEnd:g?null:"*/",lineComment:g?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:g?"json":"javascript",jsonldMode:f,jsonMode:g}}),a.registerHelper("wordChars","javascript",/[\w$]/),a.defineMIME("text/javascript","javascript"),a.defineMIME("text/ecmascript","javascript"),a.defineMIME("application/javascript","javascript"),a.defineMIME("application/x-javascript","javascript"),a.defineMIME("application/ecmascript","javascript"),a.defineMIME("application/json",{name:"javascript",json:!0}),a.defineMIME("application/x-json",{name:"javascript",json:!0}),a.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),a.defineMIME("text/typescript",{name:"javascript",typescript:!0}),a.defineMIME("application/typescript",{name:"javascript",typescript:!0})}),function(a){"object"==typeof exports&&"object"==typeof module?a(require("../../lib/codemirror"),"cjs"):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],function(b){a(b,"amd")}):a(CodeMirror,"plain")}(function(a,b){function d(a,b){var c=b;return function(){0==--c&&a()}}function e(b,c){var e=a.modes[b].dependencies;if(!e)return c();for(var f=[],g=0;gf&&a.addOverlay(b.overlay=k(e.slice(f,g),c,b.style)))}var h=a.getCursor("from"),j=a.getCursor("to");if(h.line==j.line&&(!b.wordsOnly||i(a,h,j))){var l=a.getRange(h,j).replace(/^\s+|\s+$/g,"");l.length>=b.minChars&&a.addOverlay(b.overlay=k(l,!1,b.style))}})}function i(a,b,c){var d=a.getRange(b,c);if(null!==d.match(/^\w+$/)){if(b.ch>0){var e={line:b.line,ch:b.ch-1},f=a.getRange(e,b);if(null===f.match(/\W/))return!1}if(c.ch-1)return k=d(i,j,k),{from:b(f.line,k),to:b(f.line,k+g.length)}}else{var i=a.getLine(f.line).slice(f.ch),j=h(i),k=j.indexOf(c);if(k>-1)return k=d(i,j,k)+f.ch,{from:b(f.line,k),to:b(f.line,k+g.length)}}}:this.matches=function(){};else{var j=g.split("\n");this.matches=function(c,d){var e=i.length-1;if(c){if(d.line-(i.length-1)=1;--k,--g)if(i[k]!=h(a.getLine(g)))return;var l=a.getLine(g),m=l.length-j[0].length;if(h(l.slice(m))!=i[0])return;return{from:b(g,m),to:f}}if(!(d.line+(i.length-1)>a.lastLine())){var l=a.getLine(d.line),m=l.length-j[0].length;if(h(l.slice(m))==i[0]){for(var n=b(d.line,m),g=d.line+1,k=1;e>k;++k,++g)if(i[k]!=h(a.getLine(g)))return;if(h(a.getLine(g).slice(0,j[e].length))==i[e])return{from:n,to:b(g,j[e].length)}}}}}}}function d(a,b,c){if(a.length==b.length)return c;for(var d=Math.min(c,a.length);;){var e=a.slice(0,d).toLowerCase().length;if(c>e)++d;else{if(!(e>c))return d;--d}}}var b=a.Pos;c.prototype={findNext:function(){return this.find(!1)},findPrevious:function(){return this.find(!0)},find:function(a){function e(a){var d=b(a,0);return c.pos={from:d,to:d},c.atOccurrence=!1,!1}for(var c=this,d=this.doc.clipPos(a?this.pos.from:this.pos.to);;){if(this.pos=this.matches(a,d))return this.atOccurrence=!0,this.pos.match||!0;if(a){if(!d.line)return e(0);d=b(d.line-1,this.doc.getLine(d.line-1).length)}else{var f=this.doc.lineCount();if(d.line==f-1)return e(f);d=b(d.line+1,0)}}},from:function(){return this.atOccurrence?this.pos.from:void 0},to:function(){return this.atOccurrence?this.pos.to:void 0},replace:function(c,d){if(this.atOccurrence){var e=a.splitLines(c);this.doc.replaceRange(e,this.pos.from,this.pos.to,d),this.pos.to=b(this.pos.from.line+e.length-1,e[e.length-1].length+(1==e.length?this.pos.from.ch:0))}}},a.defineExtension("getSearchCursor",function(a,b,d){return new c(this.doc,a,b,d)}),a.defineDocExtension("getSearchCursor",function(a,b,d){return new c(this,a,b,d)}),a.defineExtension("selectMatches",function(b,c){for(var d=[],e=this.getSearchCursor(b,this.getCursor("from"),c);e.findNext()&&!(a.cmpPos(e.to(),this.getCursor("to"))>0);)d.push({anchor:e.from(),head:e.to()});d.length&&this.setSelections(d,0)})}); - - -'use strict';(function(S,N){N(S.esprima={})})(this,function(S){function N(a,b){if(!a)throw Error("ASSERT: "+b);}function L(a){return 48<=a&&57>=a}function ha(a){return 0<="0123456789abcdefABCDEF".indexOf(a)}function X(a){return 0<="01234567".indexOf(a)}function R(a){return 10===a||13===a||8232===a||8233===a}function T(a){return 36===a||95===a||65<=a&&90>=a||97<=a&&122>=a||92===a||128<=a&&ia.NonAsciiIdentifierStart.test(String.fromCharCode(a))}function aa(a){return 36===a||95===a||65<=a&&90>=a||97<= -a&&122>=a||48<=a&&57>=a||92===a||128<=a&&ia.NonAsciiIdentifierPart.test(String.fromCharCode(a))}function Y(a){switch(a){case "implements":case "interface":case "package":case "private":case "protected":case "public":case "static":case "yield":case "let":return!0;default:return!1}}function G(a){return"eval"===a||"arguments"===a}function Ha(a){if(w&&Y(a))return!0;switch(a.length){case 2:return"if"===a||"in"===a||"do"===a;case 3:return"var"===a||"for"===a||"new"===a||"try"===a||"let"===a;case 4:return"this"=== -a||"else"===a||"case"===a||"void"===a||"with"===a||"enum"===a;case 5:return"while"===a||"break"===a||"catch"===a||"throw"===a||"const"===a||"yield"===a||"class"===a||"super"===a;case 6:return"return"===a||"typeof"===a||"delete"===a||"switch"===a||"export"===a||"import"===a;case 7:return"default"===a||"finally"===a||"extends"===a;case 8:return"function"===a||"continue"===a||"debugger"===a;case 10:return"instanceof"===a;default:return!1}}function ja(a,b,e,d,c){N("number"===typeof e,"Comment must have valid position"); -k.lastCommentStart>=e||(k.lastCommentStart=e,a={type:a,value:b},f.range&&(a.range=[e,d]),f.loc&&(a.loc=c),f.comments.push(a),f.attachComment&&(f.leadingComments.push(a),f.trailingComments.push(a)))}function ka(a){var b,e,d;b=c-a;for(e={start:{line:q,column:c-n-a}};c=x&&t({},l.UnexpectedToken,"ILLEGAL");else{if(42===d&&47===g.charCodeAt(c+1)){++c;++c;f.comments&&(d=g.slice(a+2,c-2),e.end={line:q,column:c-n},ja("Block",d,a,c,e));break a}++c}t({},l.UnexpectedToken,"ILLEGAL")}}else break;else if(b&&45===a)if(45===g.charCodeAt(c+1)&&62===g.charCodeAt(c+2))c+=3,ka(3);else break;else if(60===a)if("!--"===g.slice(c+1,c+4))++c,++c,++c,++c,ka(4);else break;else break}function ba(a){var b,e,d=0;b="u"===a?4:2;for(a=0;a>>="===b)return c+=4,{type:h.Punctuator,value:b,lineNumber:q,lineStart:n,start:a,end:c};b=b.substr(0,3);if(">>>"===b||"<<="===b||">>="===b)return c+=3,{type:h.Punctuator,value:b,lineNumber:q,lineStart:n,start:a,end:c};b=b.substr(0,2); -if(d===b[1]&&0<="+-<>&|".indexOf(d)||"=>"===b)return c+=2,{type:h.Punctuator,value:b,lineNumber:q,lineStart:n,start:a,end:c};if(0<="<>=!+-*%&|^/".indexOf(d))return++c,{type:h.Punctuator,value:d,lineNumber:q,lineStart:n,start:a,end:c};t({},l.UnexpectedToken,"ILLEGAL")}function ra(){var a,b,e;e=g[c];N(L(e.charCodeAt(0))||"."===e,"Numeric literal must start with a decimal digit or a decimal point");b=c;a="";if("."!==e){a=g[c++];e=g[c];if("0"===a){if("x"===e||"X"===e){++c;for(a="";c=x)return{type:h.EOF,lineNumber:q,lineStart:n,start:c,end:c};a=g.charCodeAt(c);if(T(a)){var b;a=c;if(92===g.charCodeAt(c))b=qa();else a:{var e;for(b=c++;c=y?y=String.fromCharCode(y):(v=(y-65536>>10)+55296,y=String.fromCharCode(v,(y-65536&1023)+56320));d+=y}else k=c,(v=ba(y))?d+=v:(c=k,d+=y);break;case "n":d+="\n";break;case "r":d+="\r";break;case "t":d+="\t";break;case "b":d+="\b";break;case "f":d+="\f";break;case "v":d+="\x0B";break;default:X(y)?(v="01234567".indexOf(y), -0!==v&&(e=!0),c":case "<=":case ">=":case "instanceof":e=7;break;case "in":e=b?7:0;break;case "<<":case ">>":case ">>>":e=8;break;case "+":case "-":e=9;break;case "*":case "/":case "%":e=11}return e}function Ma(){var a, -b,e,d,c,f;a=p;b=W();if(b===V.ArrowParameterPlaceHolder)return b;e=p;d=Ea(e,k.allowIn);if(0===d)return b;e.prec=d;u();a=[a,p];f=W();for(c=[b,e,f];0<(d=Ea(p,k.allowIn));){for(;2"))if(k.parenthesisCount===a||k.parenthesisCount===a+1)if(e.type===m.Identifier?c=fa([e]):e.type===m.AssignmentExpression?c=fa([e]):e.type===m.SequenceExpression?c=fa(e.expressions):e===V.ArrowParameterPlaceHolder&&(c=fa([])),c)return b=c,f=new A(f),s("=>"),a=w,c=r("{")?da():F(),w&& -b.firstRestricted&&t(b.firstRestricted,b.message),w&&b.stricted&&z(b.stricted,b.message),w=a,f.finishArrowFunctionExpression(b.params,b.defaults,c,c.type!==m.BlockStatement);p.type!==h.Punctuator?a=!1:(a=p.value,a="="===a||"*="===a||"/="===a||"%="===a||"+="===a||"-="===a||"<<="===a||">>="===a||">>>="===a||"&="===a||"^="===a||"|="===a);a&&(ca(e)||z({},l.InvalidLHSInAssignment),w&&e.type===m.Identifier&&G(e.name)&&z(b,l.StrictLHSAssignment),b=u(),a=F(),e=(new A(f)).finishAssignmentExpression(b.value, -e,a));return e}function D(){var a,b=p;a=F();if(r(",")){for(a=[a];c";H[h.Identifier]="Identifier";H[h.Keyword]="Keyword";H[h.NullLiteral]="Null";H[h.NumericLiteral]="Numeric";H[h.Punctuator]="Punctuator"; -H[h.StringLiteral]="String";H[h.RegularExpression]="RegularExpression";ta="( { [ in typeof instanceof new return case delete throw void = += -= *= /= %= <<= >>= >>>= &= |= ^= , + - * / % ++ -- << >> >>> & | ^ ! ~ && || ? : === == >= <= < > != !==".split(" ");m={AssignmentExpression:"AssignmentExpression",ArrayExpression:"ArrayExpression",ArrowFunctionExpression:"ArrowFunctionExpression",BlockStatement:"BlockStatement",BinaryExpression:"BinaryExpression",BreakStatement:"BreakStatement",CallExpression:"CallExpression", -CatchClause:"CatchClause",ConditionalExpression:"ConditionalExpression",ContinueStatement:"ContinueStatement",DoWhileStatement:"DoWhileStatement",DebuggerStatement:"DebuggerStatement",EmptyStatement:"EmptyStatement",ExpressionStatement:"ExpressionStatement",ForStatement:"ForStatement",ForInStatement:"ForInStatement",FunctionDeclaration:"FunctionDeclaration",FunctionExpression:"FunctionExpression",Identifier:"Identifier",IfStatement:"IfStatement",Literal:"Literal",LabeledStatement:"LabeledStatement", -LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",NewExpression:"NewExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ReturnStatement:"ReturnStatement",SequenceExpression:"SequenceExpression",SwitchStatement:"SwitchStatement",SwitchCase:"SwitchCase",ThisExpression:"ThisExpression",ThrowStatement:"ThrowStatement",TryStatement:"TryStatement",UnaryExpression:"UnaryExpression",UpdateExpression:"UpdateExpression",VariableDeclaration:"VariableDeclaration", -VariableDeclarator:"VariableDeclarator",WhileStatement:"WhileStatement",WithStatement:"WithStatement"};V={ArrowParameterPlaceHolder:{type:"ArrowParameterPlaceHolder"}};Q={Data:1,Get:2,Set:4};l={UnexpectedToken:"Unexpected token %0",UnexpectedNumber:"Unexpected number",UnexpectedString:"Unexpected string",UnexpectedIdentifier:"Unexpected identifier",UnexpectedReserved:"Unexpected reserved word",UnexpectedEOS:"Unexpected end of input",NewlineAfterThrow:"Illegal newline after throw",InvalidRegExp:"Invalid regular expression", -UnterminatedRegExp:"Invalid regular expression: missing /",InvalidLHSInAssignment:"Invalid left-hand side in assignment",InvalidLHSInForIn:"Invalid left-hand side in for-in",MultipleDefaultsInSwitch:"More than one default clause in switch statement",NoCatchOrFinally:"Missing catch or finally after try",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared",IllegalContinue:"Illegal continue statement",IllegalBreak:"Illegal break statement",IllegalReturn:"Illegal return statement", -StrictModeWith:"Strict mode code may not include a with statement",StrictCatchVariable:"Catch variable may not be eval or arguments in strict mode",StrictVarName:"Variable name may not be eval or arguments in strict mode",StrictParamName:"Parameter name eval or arguments is not allowed in strict mode",StrictParamDupe:"Strict mode function may not have duplicate parameter names",StrictFunctionName:"Function name may not be eval or arguments in strict mode",StrictOctalLiteral:"Octal literals are not allowed in strict mode.", -StrictDelete:"Delete of an unqualified identifier in strict mode.",StrictDuplicateProperty:"Duplicate data property in object literal not allowed in strict mode",AccessorDataProperty:"Object literal may not have data and accessor property with the same name",AccessorGetSet:"Object literal may not have multiple get/set accessors with the same name",StrictLHSAssignment:"Assignment to eval or arguments is not allowed in strict mode",StrictLHSPostfix:"Postfix increment/decrement may not have eval or arguments operand in strict mode", -StrictLHSPrefix:"Prefix increment/decrement may not have eval or arguments operand in strict mode",StrictReservedWord:"Use of future reserved word in strict mode"};ia={NonAsciiIdentifierStart:RegExp("[\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0-\u08b2\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]"), -NonAsciiIdentifierPart:RegExp("[\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u0800-\u082d\u0840-\u085b\u08a0-\u08b2\u08e4-\u0963\u0966-\u096f\u0971-\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7\u09c8\u09cb-\u09ce\u09d7\u09dc\u09dd\u09df-\u09e3\u09e6-\u09f1\u0a01-\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a66-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5c\u0b5d\u0b5f-\u0b63\u0b66-\u0b6f\u0b71\u0b82\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0be6-\u0bef\u0c00-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c58\u0c59\u0c60-\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0cde\u0ce0-\u0ce3\u0ce6-\u0cef\u0cf1\u0cf2\u0d01-\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d57\u0d60-\u0d63\u0d66-\u0d6f\u0d7a-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e50-\u0e59\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0ed0-\u0ed9\u0edc-\u0edf\u0f00\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1049\u1050-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772\u1773\u1780-\u17d3\u17d7\u17dc\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1820-\u1877\u1880-\u18aa\u18b0-\u18f5\u1900-\u191e\u1920-\u192b\u1930-\u193b\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19d9\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1aa7\u1ab0-\u1abd\u1b00-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1bf3\u1c00-\u1c37\u1c40-\u1c49\u1c4d-\u1c7d\u1cd0-\u1cd2\u1cd4-\u1cf6\u1cf8\u1cf9\u1d00-\u1df5\u1dfc-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200c\u200d\u203f\u2040\u2054\u2071\u207f\u2090-\u209c\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u2e2f\u3005-\u3007\u3021-\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u3099\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua62b\ua640-\ua66f\ua674-\ua67d\ua67f-\ua69d\ua69f-\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua827\ua840-\ua873\ua880-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f7\ua8fb\ua900-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf-\ua9d9\ua9e0-\ua9fe\uaa00-\uaa36\uaa40-\uaa4d\uaa50-\uaa59\uaa60-\uaa76\uaa7a-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabea\uabec\uabed\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe00-\ufe0f\ufe20-\ufe2d\ufe33\ufe34\ufe4d-\ufe4f\ufe70-\ufe74\ufe76-\ufefc\uff10-\uff19\uff21-\uff3a\uff3f\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]")}; -A.prototype=B.prototype={processComment:function(){var a,b,c=f.bottomRightStack,d=c[c.length-1];if(!(this.type===m.Program&&0=this.range[1]?(b=f.trailingComments,f.trailingComments=[]):f.trailingComments.length=0:d&&d.trailingComments&&d.trailingComments[0].range[0]>=this.range[1]&&(b=d.trailingComments,delete d.trailingComments);if(d)for(;d&&d.range[0]>=this.range[0];)a=d,d=c.pop();a?a.leadingComments&&a.leadingComments[a.leadingComments.length- -1].range[1]<=this.range[0]&&(this.leadingComments=a.leadingComments,a.leadingComments=void 0):0 Date: Wed, 13 May 2026 14:58:58 -0700 Subject: [PATCH 14/22] site/README: cover mobile gates, multi-file IDE, Disqus, Playwright suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audit flagged the README as stale — all three recently-landed surfaces (mobile fallbacks, multi-file playground, Disqus comments) had no mention. Updated: - Replaced the desktop-only summary with a "Surface map" table covering hero / playground / blog / nav behavior on desktop vs <768 px. - Expanded the playground section: multi-file tab CRUD, ▶ run via MEMFS write loop, ↗ share (#z= compressed hash + is.gd shorten), 250 ms autosave to localStorage, splitter persistence. - Added a Playwright suite section: 28 specs, no-WASM in ~5 s, pointer to the CI workflow. - Refreshed the file tree under site/ — new playground-*.js files, the site/tests/playground/ suite, the relocated CodeMirror bundle at site/files/cm/, and lz-string.min.js. - Updated the "/playground/ blank page" gotcha now that CodeMirror lives in /files/cm/ rather than web/ui/src/. - Added a mobile-gate gotcha (hard-refresh required after toggling DevTools' device toolbar). Co-Authored-By: Claude Opus 4.7 (1M context) --- site/README.md | 105 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/site/README.md b/site/README.md index 6ff95d8d1a..79f3a5a786 100644 --- a/site/README.md +++ b/site/README.md @@ -12,10 +12,13 @@ site/ ├── index.html # landing — hand-authored ├── downloads.html # downloads & links — hand-authored ├── files/ -│ ├── forge.css # tokens + components (source of truth) -│ ├── forge.js # sample switcher · install tabs · bench · news +│ ├── forge.css # tokens + components (source of truth) — includes responsive rules + mobile nav +│ ├── forge.js # sample switcher · install tabs · bench · news (skips CM init on mobile) │ ├── highlight.js # daslang tokenizer (hero + blog code blocks) │ ├── runner.js # mini-playground WASM shim +│ ├── cm/ # CodeMirror bundle: codemirror.min.{js,css}, simple-mode.js, +│ │ # daslang-keywords.js, daslang-mode.js, cm-forge.css (Forge theme) +│ ├── lz-string.min.js # share URL compressor (used by /playground/) │ ├── forge-favicon.svg │ ├── profile_results.json # fetched by CI from borisbat/dasProfile (gitignored) │ ├── news.json # generated by build_blog.py (gitignored) @@ -31,11 +34,17 @@ site/ ├── changelist.html # GENERATED by build_blog.py — full news list ├── news/.html # GENERATED — micro-pages for news entries with bodies ├── playground/ -│ ├── index.html # Forge nav + IDE body (overlay for web/ui) -│ ├── forge-skin.css # CSS overrides on web/ui/src/main.css -│ ├── cm-forge.css # CodeMirror dark theme (replaces eclipse.css colors) -│ └── *.{js,css} # vendored from web/ui/src/ for local-dev (gitignored) -└── doc/ # Sphinx HTML output (gitignored, deployed by CI) +│ ├── index.html # Forge nav + IDE body (mobile gate at top of ) +│ ├── forge-skin.css # CSS overrides on web/ui/src/main.css + mobile notice + splitter +│ ├── playground-init.js # URL-hash dispatch (#code= legacy / #z= multi-file) +│ ├── playground-tabs.js # multi-file state, tab strip, autosave to localStorage +│ ├── playground-share.js # ↗ share popover, is.gd shortener +│ ├── playground-splitter.js # draggable code | output column splitter +│ ├── samples/ # multi-file sample bundles (gitignored, mirrored from web/ui/samples) +│ └── *.{js,css} # other vendored bits from web/ui/src/ for local-dev (gitignored) +├── tests/ +│ └── playground/ # Playwright e2e suite (28 specs, ~5 s no-WASM) +└── doc/ # Sphinx HTML output (gitignored, deployed by CI) ``` Sources (`forge.css`, `*.md`, `*.py`, `template.html`, `*.html` hand-authored) @@ -43,6 +52,20 @@ are committed. Build artifacts (`blog/.html`, `news/`, `changelist.html`, `files/wasm/`, `files/news.json`, `files/profile_results.json`, `doc/`) are generated and gitignored — see [`site/.gitignore`](.gitignore). +## Surface map (what runs where) + +| Surface | Desktop | Mobile (<768 px) | +|---|---|---| +| **Landing hero** | CodeMirror editor with sample picker, ▶ run, ↗ playground handoff | Static `
    ` block, no CM init. Hero buttons + sample tabs hidden via CSS. |
    +| **/playground/** | Full multi-file IDE — tabs, splitter, share via `#z=` hash, ▶ run via WASM | "Open this on a laptop" notice. `pageInit` is overridden before WASM fetch — `daslang_static.{js,wasm}` is never requested. |
    +| **/blog/** | Full post + Disqus comments at the bottom (shortname `https-borisbat-github-io-dascf-blog`, identifier = slug, Auto theme picks dark via `:root { color-scheme: dark }`) | Same. Disqus is responsive. |
    +| **Nav** | Inline links: docs · benchmarks · downloads · blog · community + `v0.6.2`, github ↗, install | ≡ hamburger toggles a dropdown panel with the same links. `v0.6.2` chip is hidden at <480 px. |
    +
    +The mobile fallbacks are pure CSS + a tiny synchronous gate script — no UA
    +sniffing, no JS feature-detection. Viewport-width is the only signal. Open
    +Chrome DevTools → device toolbar (Cmd+Shift+M) and reload to test, or browse
    +from a phone on the same WiFi using your LAN IP (`ipconfig getifaddr en0`).
    +
     ## Quick start (landing only)
     
     ```bash
    @@ -159,11 +182,14 @@ re-execute.
     ## Full playground (`/playground/`)
     
     The full IDE is `web/ui/` (CodeMirror, samples picker, multi-file). For local
    -preview, vendor its source files into `site/playground/`:
    +preview, vendor its source files into `site/playground/` and copy the sample
    +bundles:
     
     ```bash
     cp web/ui/src/* site/playground/
    -# Forge skin auto-loads from site/playground/forge-skin.css + cm-forge.css.
    +cp -r web/ui/samples site/playground/samples
    +# Forge skin auto-loads from site/playground/forge-skin.css.
    +# CodeMirror itself + the Forge CM theme load from /files/cm/ (already in site/).
     ```
     
     Plus the WASM artifact (same as the mini playground):
    @@ -172,9 +198,31 @@ Plus the WASM artifact (same as the mini playground):
     cp web/output/daslang_static.{js,wasm} site/playground/
     ```
     
    -Open `http://localhost:8000/playground/`. Forge nav at the top, CodeMirror
    -editor with Forge tokens (amber keywords, blue types, green strings, faint
    -comments), output panel below.
    +Open `http://localhost:8000/playground/`. Forge nav at the top, multi-file
    +tab strip + Select example + ↗ share + ▶ run on the left of a unified
    +toolbar, clear on the right. Below: code | output split by a draggable
    +handle (drag to resize, double-click to reset to 50/50). Layout is one
    +container — code and output share a single outer border.
    +
    +### Multi-file workflow
    +
    +- **Tabs**: `main.das` is the fixed entry point — non-renameable, non-deletable.
    +  `+` adds `untitled1.das`, `untitled2.das`, … Double-click a tab name to
    +  rename (validates `.das` suffix, rejects duplicates). `×` deletes (confirms
    +  if non-empty).
    +- **Run**: writes every open file to MEMFS, then `Module.callMain(['main.das'])`.
    +  `require utils` in `main.das` resolves against MEMFS as a normal filesystem
    +  lookup, so multi-file modules / macros work out of the box (see the
    +  "Macros (multi-file)" sample).
    +- **Share** (↗ share): builds `location + '#z=' + LZString.compressToEncodedURIComponent(JSON.stringify(state))`.
    +  The popover offers a clipboard copy and an is.gd shorten button. Opening a
    +  `#z=...` URL in a fresh browser restores the full multi-file state. The
    +  legacy `#code=` hash (emitted by the landing hero's ↗ playground
    +  handoff) is still accepted and routes the content into `main.das`.
    +- **Autosave**: 250 ms debounced write of the full state to
    +  `localStorage['daslang.playground.state.v1']`. Restored on next load only
    +  if neither `#code=` nor `#z=` is present in the URL.
    +- **Splitter position** is persisted to `localStorage['daslang.playground.splitLeftPct']`.
     
     ## Full `_site` preview (everything stitched together)
     
    @@ -223,6 +271,30 @@ curl -sSL -o site/files/profile_results.json \
     
     The benchmarks chart on the landing reads it directly — no rebuild needed.
     
    +## Playwright e2e suite (`site/tests/playground/`)
    +
    +28 specs covering: dropdowns, tab strip CRUD, multi-file persistence,
    +share-URL round-trip, splitter drag, hero ↗ playground handoff, mobile gate
    +(asserts WASM is *not* fetched at narrow viewports). 5 are tagged `@wasm`
    +and only run when the WASM artifact is present.
    +
    +```bash
    +# Start the dev server from site/ (so paths resolve like prod).
    +cd site && python3 -m http.server 8765 &
    +
    +# In another shell:
    +cd site/tests/playground
    +npm ci                                  # one-time
    +npx playwright install chromium         # one-time
    +npx playwright test --grep-invert "@wasm"   # ~5 s, no WASM needed
    +npx playwright test                     # full suite, requires WASM at site/playground/
    +```
    +
    +CI runs the no-WASM subset on every PR via
    +[`.github/workflows/playground-e2e.yml`](../.github/workflows/playground-e2e.yml).
    +The `@wasm`-tagged specs are gated on the WASM build being present locally —
    +no dedicated CI tier yet.
    +
     ## Common gotchas
     
     - **Hero `▶ run` shows "WASM runtime not deployed"** — you haven't built the
    @@ -230,7 +302,14 @@ The benchmarks chart on the landing reads it directly — no rebuild needed.
       the static landing without it (the run button is the only thing that won't
       work).
     - **`/playground/` shows a blank page** — you forgot to `cp web/ui/src/*
    -  site/playground/`. The CodeMirror + jQuery + `main.js` are needed.
    +  site/playground/`. `main.js`, `main.css`, and `jquery-3.6.0.min.js` are
    +  needed; CodeMirror itself lives at `site/files/cm/` and is already in
    +  the repo.
    +- **Mobile gate doesn't fire** — open Chrome DevTools, toggle the device
    +  toolbar (Cmd+Shift+M), pick a phone preset, then *hard-refresh*
    +  (Cmd+Shift+R). The mobile detector is a synchronous script that runs
    +  once on page load — toggling the toolbar after load doesn't retrigger
    +  it.
     - **Blog code blocks aren't syntax-highlighted** — make sure
       `site/files/highlight.js` is loaded. The blog template includes it; if you
       built with `--out /tmp/foo/` and serve from there, your asset paths may
    
    From 927e9147cdb73476a1f32fad679b47ffd6ac5cf2 Mon Sep 17 00:00:00 2001
    From: Boris Batkin 
    Date: Wed, 13 May 2026 15:12:25 -0700
    Subject: [PATCH 15/22] =?UTF-8?q?site:=20PR=20#2648=20review=20=E2=80=94?=
     =?UTF-8?q?=20staging=20globs,=20rename=20regex,=20escapeHtml=20quotes?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Round 1 from Copilot. All four findings were real.
    
    - pages.yml + playground-e2e.yml: stage every `playground-*.js` (init,
      tabs, share, splitter) not just init.js. CI's 404 storm on
      playground-tabs/share/splitter is the root cause of the failed
      playground-e2e run; same bug in pages.yml would have landed the
      regression in prod. Globbing also picks up future additions.
    - playground-tabs.js: drop `/` from the rename regex. The previous
      pattern accepted `dir/foo.das`, but Emscripten MEMFS's `FS.writeFile`
      doesn't auto-create parents — Run would silently ENOENT. Flat
      namespace is enough for the playground.
    - forge.js escapeHtml: also escape `"` and `'`. The news renderer
      interpolates `n.link` inside `href="..."`; a `"` in the URL would
      break out and inject markup. `_news/*.md` is committed so practical
      risk is near-zero, but the fix is one line and defensive.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) 
    ---
     .github/workflows/pages.yml          | 4 +++-
     .github/workflows/playground-e2e.yml | 4 +++-
     site/files/forge.js                  | 3 ++-
     site/playground/playground-tabs.js   | 8 ++++++--
     4 files changed, 14 insertions(+), 5 deletions(-)
    
    diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
    index 57d84a0835..9e664d1754 100644
    --- a/.github/workflows/pages.yml
    +++ b/.github/workflows/pages.yml
    @@ -136,7 +136,9 @@ jobs:
                 # earlier, so nothing extra to copy here.
                 cp site/playground/index.html _site/playground/index.html
                 cp site/playground/forge-skin.css _site/playground/
    -            cp site/playground/playground-init.js _site/playground/
    +            # Forge playground scripts (init shim + tabs + share + splitter).
    +            # Glob so future playground-*.js additions land automatically.
    +            cp site/playground/playground-*.js _site/playground/
             else
                 echo "WARNING: web/output not found — playground will be unavailable"
             fi
    diff --git a/.github/workflows/playground-e2e.yml b/.github/workflows/playground-e2e.yml
    index 6850a776b5..a9748b70e3 100644
    --- a/.github/workflows/playground-e2e.yml
    +++ b/.github/workflows/playground-e2e.yml
    @@ -53,7 +53,9 @@ jobs:
               cp -r web/ui/samples _site/playground/samples 2>/dev/null || true
               cp site/playground/index.html _site/playground/index.html
               cp site/playground/forge-skin.css _site/playground/
    -          cp site/playground/playground-init.js _site/playground/
    +          # Forge playground scripts (init shim + tabs + share + splitter).
    +          # Glob so future playground-*.js additions land automatically.
    +          cp site/playground/playground-*.js _site/playground/
     
               # Blog + news (template change validation)
               python3 site/blog/build_blog.py \
    diff --git a/site/files/forge.js b/site/files/forge.js
    index abadef08e9..26cfed1490 100644
    --- a/site/files/forge.js
    +++ b/site/files/forge.js
    @@ -113,7 +113,8 @@ def main() {
         }
     
         function escapeHtml(s) {
    -        return s.replace(/&/g, '&').replace(//g, '>');
    +        return s.replace(/&/g, '&').replace(//g, '>')
    +                .replace(/"/g, '"').replace(/'/g, ''');
         }
     
         let currentSample = 'hello';
    diff --git a/site/playground/playground-tabs.js b/site/playground/playground-tabs.js
    index 4fe5a4832e..6aff482fb6 100644
    --- a/site/playground/playground-tabs.js
    +++ b/site/playground/playground-tabs.js
    @@ -138,8 +138,12 @@
             if (oldName === ENTRY) return false;
             newName = (newName || '').trim();
             if (!newName) return false;
    -        if (!/^[A-Za-z0-9_./-]+\.das$/.test(newName)) {
    -            window.alert('Filename must end in .das and contain only [A-Za-z0-9_./-].');
    +        // Reject `/` — Emscripten MEMFS's FS.writeFile doesn't auto-create
    +        // parent dirs, so a nested name silently breaks Run. Flat namespace
    +        // is enough for the playground; users with multi-dir layouts can
    +        // copy a single-file daspkg or run locally.
    +        if (!/^[A-Za-z0-9_.-]+\.das$/.test(newName)) {
    +            window.alert('Filename must end in .das and contain only [A-Za-z0-9_.-] (no path separators).');
                 return false;
             }
             if (newName === oldName) return true;
    
    From db94d1a44c797244f1efa8e91d2d24bc2c1ae1d7 Mon Sep 17 00:00:00 2001
    From: Boris Batkin 
    Date: Wed, 13 May 2026 15:26:54 -0700
    Subject: [PATCH 16/22] =?UTF-8?q?site:=20PR=20#2648=20round=202=20?=
     =?UTF-8?q?=E2=80=94=20daslang.io=20feed=20+=20MEMFS=20stale-file=20cleanu?=
     =?UTF-8?q?p?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Two more from Copilot. Both real.
    
    - Atom feed default `--site-url` was `https://dascript.org`; neither
      workflow passed `--site-url`, so `blog/feed.xml` would have advertised
      old-domain URLs once deployed at daslang.io. Subscribers' clients
      follow ``/`` URLs to fetch posts — those would have hit a
      domain that no longer serves the site. Bumped the default to
      daslang.io AND pinned `--site-url https://daslang.io` from both
      workflows so the default can't silently drift again. Updated the
      stale README header that still pointed at dascript.org.
    - runCode() wrote every current pgState file to MEMFS but never
      unlinked files the user had deleted/renamed. Concrete bug: open
      utils.das, write `require utils` in main, run (writes utils.das to
      MEMFS), then delete utils.das tab, run again — still works from
      stale MEMFS state, executed program no longer matches visible tabs.
      Fixed by tracking a module-level Set of last-written names; each
      run unlinks stale-from-prior-run names (ENOENT tolerated) before
      writing current. Mirrored to site/playground/main.js.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) 
    ---
     .github/workflows/pages.yml          |  5 ++++-
     .github/workflows/playground-e2e.yml |  4 +++-
     site/README.md                       |  2 +-
     site/blog/build_blog.py              |  2 +-
     web/ui/src/main.js                   | 18 ++++++++++++++++--
     5 files changed, 25 insertions(+), 6 deletions(-)
    
    diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
    index 9e664d1754..2a739e71b8 100644
    --- a/.github/workflows/pages.yml
    +++ b/.github/workflows/pages.yml
    @@ -111,11 +111,14 @@ jobs:
             cp build/latex/daslang.pdf _site/doc/daslang.pdf || echo "WARNING: daslang.pdf not found"
             cp build/latex/daslangstdlib.pdf _site/doc/daslangstdlib.pdf || echo "WARNING: daslangstdlib.pdf not found"
     
    -        # Blog + news + changelist (regenerated each publish)
    +        # Blog + news + changelist (regenerated each publish). Pin --site-url
    +        # explicitly so feed.xml entries point at the production domain even
    +        # if the default in build_blog.py drifts.
             python3 site/blog/build_blog.py \
                 --posts site/blog/_posts \
                 --news site/_news \
                 --template site/blog/template.html \
    +            --site-url https://daslang.io \
                 --out _site/
     
             # Playground: vendor web/ui IDE + WASM artifacts
    diff --git a/.github/workflows/playground-e2e.yml b/.github/workflows/playground-e2e.yml
    index a9748b70e3..a7e65b4667 100644
    --- a/.github/workflows/playground-e2e.yml
    +++ b/.github/workflows/playground-e2e.yml
    @@ -57,11 +57,13 @@ jobs:
               # Glob so future playground-*.js additions land automatically.
               cp site/playground/playground-*.js _site/playground/
     
    -          # Blog + news (template change validation)
    +          # Blog + news (template change validation). Match the production
    +          # site-url so feed.xml output matches what pages.yml would emit.
               python3 site/blog/build_blog.py \
                   --posts site/blog/_posts \
                   --news site/_news \
                   --template site/blog/template.html \
    +              --site-url https://daslang.io \
                   --out _site/
     
               touch _site/.nojekyll
    diff --git a/site/README.md b/site/README.md
    index 79f3a5a786..1fe9ae463c 100644
    --- a/site/README.md
    +++ b/site/README.md
    @@ -1,6 +1,6 @@
     # daslang.io — local development
     
    -The site published at [dascript.org](https://dascript.org) and deployed by
    +The site published at [daslang.io](https://daslang.io) and deployed by
     [`.github/workflows/pages.yml`](../.github/workflows/pages.yml). This README is
     the canonical guide for running each surface locally — `pages.yml` references
     the same commands and treats this file as the source of truth.
    diff --git a/site/blog/build_blog.py b/site/blog/build_blog.py
    index 266330ec30..30e1f87eef 100644
    --- a/site/blog/build_blog.py
    +++ b/site/blog/build_blog.py
    @@ -470,7 +470,7 @@ def main():
         ap.add_argument('--news', type=Path)
         ap.add_argument('--template', required=True, type=Path)
         ap.add_argument('--out', required=True, type=Path)
    -    ap.add_argument('--site-url', default='https://dascript.org')
    +    ap.add_argument('--site-url', default='https://daslang.io')
         args = ap.parse_args()
     
         tpl = args.template.read_text(encoding='utf-8')
    diff --git a/web/ui/src/main.js b/web/ui/src/main.js
    index 3a714fedc5..02ae1f2ef2 100644
    --- a/web/ui/src/main.js
    +++ b/web/ui/src/main.js
    @@ -105,13 +105,27 @@ loadSample = function(filesByName) {
         window.__pendingSampleBundle = bundle;
     }
     
    +// Names of files we wrote to MEMFS on the previous run. Tracked so each new
    +// run can unlink files the user has since deleted or renamed — otherwise
    +// `require utils` would resolve stale code from the prior run, and the
    +// executed program no longer matches the visible tab state.
    +var __lastWrittenFiles = new Set();
    +
     runCode = function() {
    -    // Multi-file: write every file in pgState (if mounted) to MEMFS, then run
    -    // main.das. Falls back to the single-buffer path when pgState isn't up yet.
    +    // Multi-file: sync MEMFS with the current pgState (unlink stale, write
    +    // current), then run main.das. Falls back to the single-buffer path when
    +    // pgState isn't up yet.
         if (window.pgState && typeof FS !== 'undefined') {
    +        const current = new Set(Object.keys(window.pgState.files));
    +        for (const stale of __lastWrittenFiles) {
    +            if (!current.has(stale)) {
    +                try { FS.unlink(stale); } catch (e) { /* ENOENT — ignore */ }
    +            }
    +        }
             for (const [name, doc] of Object.entries(window.pgState.files)) {
                 FS.writeFile(name, doc.getValue());
             }
    +        __lastWrittenFiles = current;
             Module.callMain(['main.das']);
             return;
         }
    
    From 86d5a67abbfdb55de7f9f8b52c7da625ea8e5b5b Mon Sep 17 00:00:00 2001
    From: Boris Batkin 
    Date: Wed, 13 May 2026 15:45:16 -0700
    Subject: [PATCH 17/22] =?UTF-8?q?site:=20PR=20#2648=20round=203=20?=
     =?UTF-8?q?=E2=80=94=20docs=20@import,=20hero-handoff=20race,=20dead=20leg?=
     =?UTF-8?q?acy?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Three real, one false alarm.
    
    Real:
    - doc/source/_static/custom.css: `@import` for Inter Tight + JetBrains
      Mono lived AFTER a `:root` rule, so browsers silently drop it (CSS 2.1
      §6.3). Docs were quietly falling back to system fonts. Moved the
      import to the very top.
    - playground-init.js + hero-handoff.spec.js: the hash-payload dispatch
      stashed `__pendingSampleBundle` but never set `pgRestoredFromState`.
      main.js's default `selectSample("examples", 0)` then ran and the
      data.json fetch sometimes beat pgLoadFiles, overwriting the marker
      text with hello.das. Visible as a flake in CI (slower http.server)
      but a real correctness bug on cold-cache loads in prod too. Set the
      flag; also harden the spec to wait for the marker content, not just
      for pgState's structure to materialize. Three consecutive local runs
      green.
    - web/ui/index.html: legacy standalone IDE entry point referencing
      files we deleted (codemirror.{min.js,css}, eclipse.css). Nothing in
      CI/build references it. Removed.
    
    False alarm:
    - site/files/cm/daslang-keywords.js: Copilot flagged enum / typedef /
      with / aka / reinterpret / upcast / range64 / urange / urange64 as
      absent. They're all present (verified against
      src/parser/ds2_lexer.lpp's `DAS_*` returns). Reply on the thread —
      no code change.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) 
    ---
     doc/source/_static/custom.css              |   8 +-
     site/playground/playground-init.js         |   6 +-
     site/tests/playground/hero-handoff.spec.js |  10 +-
     web/ui/index.html                          | 153 ---------------------
     4 files changed, 18 insertions(+), 159 deletions(-)
     delete mode 100644 web/ui/index.html
    
    diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css
    index 238d5506a9..beb90e8b04 100644
    --- a/doc/source/_static/custom.css
    +++ b/doc/source/_static/custom.css
    @@ -5,6 +5,11 @@
      * no template overrides.
      * ────────────────────────────────────────────────────────────────── */
     
    +/* `@import` must precede all other rules (CSS 2.1 §6.3) — keeping this
    + * at the top of the file so browsers actually fetch the web fonts. A
    + * later `@import` after `:root` would be silently dropped. */
    +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
    +
     :root {
         --bg:        #0d0c0a;
         --bg-2:      #15130f;
    @@ -22,9 +27,6 @@
         --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace;
     }
     
    -/* ─── Web font import ───────────────────────────────────────────── */
    -@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
    -
     /* ─── Page-level ──────────────────────────────────────────────── */
     html, body, .wy-body-for-nav {
         background: var(--bg) !important;
    diff --git a/site/playground/playground-init.js b/site/playground/playground-init.js
    index 3a8b829bba..a465823f7b 100644
    --- a/site/playground/playground-init.js
    +++ b/site/playground/playground-init.js
    @@ -36,8 +36,12 @@ function applySharedCodeFromHash() {
         }
     
         // Stash bundle immediately so pgInit picks it up even if it polls in
    -    // before we do.
    +    // before we do. Also flag the page as "restored from URL state" so
    +    // main.js skips its default `selectSample("examples", 0)` — otherwise
    +    // the async data.json fetch occasionally beats pgLoadFiles and the
    +    // default hello.das overwrites the hash payload.
         window.__pendingSampleBundle = payload.files;
    +    window.pgRestoredFromState = true;
         const deadline = Date.now() + 5000;
         (function tryApply() {
             if (typeof window.pgLoadFiles === 'function') {
    diff --git a/site/tests/playground/hero-handoff.spec.js b/site/tests/playground/hero-handoff.spec.js
    index a22d2911cc..66357411eb 100644
    --- a/site/tests/playground/hero-handoff.spec.js
    +++ b/site/tests/playground/hero-handoff.spec.js
    @@ -37,9 +37,15 @@ test('hero ↗ playground hands off the current buffer to /playground/', async (
         expect(newPage.url()).toContain('/playground/');
         expect(newPage.url()).toContain('#code=');
     
    -    // Playground populates pgState with main.das holding the marker.
    +    // Wait for the marker text to actually land in main.das. pgState gets
    +    // created before pgLoadFiles applies the hash payload, so checking
    +    // structure alone races against the late-arriving content.
         await newPage.waitForFunction(
    -        () => !!window.pgState && 'main.das' in window.pgState.files,
    +        () => {
    +            const s = window.pgState;
    +            if (!s || !('main.das' in s.files)) return false;
    +            return s.files['main.das'].getValue().includes('HERO-HANDOFF-MARKER');
    +        },
             null,
             { timeout: 30_000 }
         );
    diff --git a/web/ui/index.html b/web/ui/index.html
    deleted file mode 100644
    index 0088bfa381..0000000000
    --- a/web/ui/index.html
    +++ /dev/null
    @@ -1,153 +0,0 @@
    -
    -
    -
    -
    -    Daslang - Web
    -
    -    
    -    
    -
    -    
    -    
    -    
    -    
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    - Daslang -
    - -
    - Overview -
    - - - - -
    - Source - -
    - - -
    - -
    - -
    - - -
    - -
    - -
    -
    Source
    -
    - -
    - - - -
    - -
    - -
    - - -
    - -
    - -
    -
    - -
    -
    -
    - -
    - - -
    - -
    - -
    - -
    - -
    -
    Output
    -
    - -
    - -
    - -
    - - -
    - - - -
    - - - -
    - -
    - -
    - -
    - -
    - - -
    -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file From 7ff317c748b238bfc34bdf9d9d9493fcfb1cedc1 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 15:56:23 -0700 Subject: [PATCH 18/22] site/playground: short-circuit mobile polling + gate prod playground stage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit playground-tabs.js: tryInit() polled every 30ms for `window.code` indefinitely. On mobile, pageInit is short-circuited so the variable never appears, leaving a 33Hz setTimeout chain running for the life of every mobile tab. Skip the init when the .is-pg-mobile gate class is present. pages.yml: the WASM build step keeps continue-on-error so docs/blog still ship through emsdk hiccups, but the staging block then accepted a half-built web/output and deployed a playground whose Run button 404'd the runtime. Tighten the gate to require both daslang_static.{js,wasm} on disk before staging any playground files — Pages preserves the prior deploy, so the visible /playground/ keeps working instead of going silently broken. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pages.yml | 22 ++++++++++++++-------- site/playground/playground-tabs.js | 4 ++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 2a739e71b8..dc34eb2f50 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -121,16 +121,22 @@ jobs: --site-url https://daslang.io \ --out _site/ - # Playground: vendor web/ui IDE + WASM artifacts - if [ -d web/output ]; then + # Playground: vendor web/ui IDE + WASM artifacts. + # The WASM build step has continue-on-error so docs/blog still publish + # when emsdk hiccups. The cost of that resilience is partial state: + # web/output may exist without the daslang_static.{js,wasm} artifacts. + # Refuse to stage a half-built playground in that case — Pages preserves + # the previously-deployed /playground/, so the visible Run button keeps + # working instead of 404'ing its runtime. + if [ -f web/output/daslang_static.js ] && [ -f web/output/daslang_static.wasm ]; then mkdir -p _site/playground _site/files/wasm # Copy IDE source files (CodeMirror, jQuery, main.js/css, etc.) cp -r web/ui/src/* _site/playground/ - # Copy WASM artifacts - cp web/output/daslang_static.js _site/playground/ || true - cp web/output/daslang_static.wasm _site/playground/ || true - cp web/output/daslang_static.js _site/files/wasm/ || true - cp web/output/daslang_static.wasm _site/files/wasm/ || true + # Copy WASM artifacts — known-present after the guard above. + cp web/output/daslang_static.js _site/playground/ + cp web/output/daslang_static.wasm _site/playground/ + cp web/output/daslang_static.js _site/files/wasm/ + cp web/output/daslang_static.wasm _site/files/wasm/ # Copy sample data if present cp -r web/ui/samples _site/playground/samples 2>/dev/null || true # Our Forge-skinned playground/index.html + skin CSS + init shim take precedence. @@ -143,7 +149,7 @@ jobs: # Glob so future playground-*.js additions land automatically. cp site/playground/playground-*.js _site/playground/ else - echo "WARNING: web/output not found — playground will be unavailable" + echo "WARNING: daslang_static.{js,wasm} missing under web/output — skipping playground stage. Prior /playground/ remains live." fi # GitHub Pages control files diff --git a/site/playground/playground-tabs.js b/site/playground/playground-tabs.js index 6aff482fb6..fca817220e 100644 --- a/site/playground/playground-tabs.js +++ b/site/playground/playground-tabs.js @@ -247,6 +247,10 @@ // ─── Init ───────────────────────────────────────────────────────── function tryInit() { + // Mobile gate short-circuits pageInit so `window.code` is never created. + // Without this guard, the poll loop fires ~33Hz forever on every mobile + // tab/background tab — wasted battery for no payoff. + if (document.documentElement.classList.contains('is-pg-mobile')) return; if (typeof CodeMirror === 'undefined' || typeof window.code === 'undefined' || window.code === null || typeof window.code.swapDoc !== 'function') { From 5a60a96dac4b643e9a119992a454834e0daaaf11 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 16:07:21 -0700 Subject: [PATCH 19/22] site/playground: preserve shared `active` across late tab init + WASM-miss placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit playground-init: the `#z=` hash decode stashed `__pendingSampleBundle = payload.files` but dropped `payload.active`. When playground-tabs.js's tryInit consumed the bundle before this script's tryApply loop resolved, `pgLoadFiles(bundle)` ran without an active argument and fell back to main.das, so a shared URL whose author was viewing utils.das landed on the recipient's screen with main.das in front. Stash `__pendingSampleActive` alongside the bundle and consume it in both tab-init paths. pages.yml: round-4 staging gate (require daslang_static.{js,wasm} before copying playground files) was based on an incorrect read of actions/deploy-pages — Pages publishes _site as a complete snapshot, not a layer over the prior deploy, so a missing _site/playground/ 404s the route instead of preserving the previous runtime. Stage site/playground/ placeholder.html into _site/playground/index.html on the missing-artifact branch so /playground/ keeps resolving to something useful while the next merge re-rolls the runtime. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pages.yml | 9 ++++- site/playground/placeholder.html | 65 ++++++++++++++++++++++++++++++ site/playground/playground-init.js | 7 ++++ site/playground/playground-tabs.js | 8 +++- 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 site/playground/placeholder.html diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index dc34eb2f50..09ffdb1097 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -149,7 +149,14 @@ jobs: # Glob so future playground-*.js additions land automatically. cp site/playground/playground-*.js _site/playground/ else - echo "WARNING: daslang_static.{js,wasm} missing under web/output — skipping playground stage. Prior /playground/ remains live." + # WASM artifacts missing. actions/deploy-pages publishes _site as + # a complete snapshot (no layering over the prior deploy), so an + # absent _site/playground/ would 404 the route. Stage a + # placeholder instead — the URL keeps resolving to something + # useful while we re-roll the runtime. + echo "WARNING: daslang_static.{js,wasm} missing under web/output — staging placeholder /playground/." + mkdir -p _site/playground + cp site/playground/placeholder.html _site/playground/index.html fi # GitHub Pages control files diff --git a/site/playground/placeholder.html b/site/playground/placeholder.html new file mode 100644 index 0000000000..405fa6b6e0 --- /dev/null +++ b/site/playground/placeholder.html @@ -0,0 +1,65 @@ + + + + + + Playground — Daslang + + + + + + + + + + + + + +
    + +

    Runtime rebuild in progress.

    +

    + The daslang playground compiles a WebAssembly build of the compiler + and standard library on every site deploy. That build didn't land + cleanly this round, so we're holding the IDE back rather than ship + an editor whose Run button 404s its runtime. The next merge to + master kicks off a fresh build — usually a few minutes. +

    + +
    + + + diff --git a/site/playground/playground-init.js b/site/playground/playground-init.js index a465823f7b..a1a806d0b1 100644 --- a/site/playground/playground-init.js +++ b/site/playground/playground-init.js @@ -40,12 +40,19 @@ function applySharedCodeFromHash() { // main.js skips its default `selectSample("examples", 0)` — otherwise // the async data.json fetch occasionally beats pgLoadFiles and the // default hello.das overwrites the hash payload. + // + // Stash `active` alongside the bundle: when playground-tabs.js's tryInit + // consumes the pending bundle before this tryApply loop resolves, it + // needs the active filename or it falls back to main.das and the shared + // URL's selected tab is silently lost. window.__pendingSampleBundle = payload.files; + window.__pendingSampleActive = payload.active; window.pgRestoredFromState = true; const deadline = Date.now() + 5000; (function tryApply() { if (typeof window.pgLoadFiles === 'function') { window.__pendingSampleBundle = null; + window.__pendingSampleActive = null; window.pgLoadFiles(payload.files, payload.active); return; } diff --git a/site/playground/playground-tabs.js b/site/playground/playground-tabs.js index fca817220e..be5ec841ac 100644 --- a/site/playground/playground-tabs.js +++ b/site/playground/playground-tabs.js @@ -291,8 +291,10 @@ if (hasHash && window.__pendingSampleBundle) { const bundle = window.__pendingSampleBundle; + const active = window.__pendingSampleActive; window.__pendingSampleBundle = null; - pgLoadFiles(bundle); + window.__pendingSampleActive = null; + pgLoadFiles(bundle, active); window.pgRestoredFromState = true; return; } @@ -307,8 +309,10 @@ // No autosave: let the pending default sample (if any) drop in. if (window.__pendingSampleBundle) { const bundle = window.__pendingSampleBundle; + const active = window.__pendingSampleActive; window.__pendingSampleBundle = null; - pgLoadFiles(bundle); + window.__pendingSampleActive = null; + pgLoadFiles(bundle, active); } } From 0ba65e89befea3002e8b5decd5204449df8f6910 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 16:20:06 -0700 Subject: [PATCH 20/22] =?UTF-8?q?site:=20fix=20MIT=E2=86=92BSD=203-Clause?= =?UTF-8?q?=20footer=20+=20autosave=20on=20tab=20switch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License footer in site/index.html, site/downloads.html, and site/blog/template.html displayed "MIT" but the repository LICENSE is BSD 3-Clause (Gaijin Entertainment, 2019-2023). The blog template fixes every generated blog page on the next build, so no per-page edits needed. playground-tabs.js: pgSwitchFile updated pgState.active and swapped the visible Doc but never called autosave(). User-initiated tab clicks therefore didn't persist; a page reload restored the previous active tab (or main.das if no content edit had triggered autosave on `change` yet). The other state-changing entry points (pgAddFile/Delete/Rename/LoadFiles) were already saving correctly — only the bare tab-click path was leaking. Co-Authored-By: Claude Opus 4.7 (1M context) --- site/blog/template.html | 2 +- site/downloads.html | 2 +- site/index.html | 2 +- site/playground/playground-tabs.js | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/site/blog/template.html b/site/blog/template.html index 6a7c6b7b09..e3cf7b6664 100644 --- a/site/blog/template.html +++ b/site/blog/template.html @@ -53,7 +53,7 @@
    diff --git a/site/downloads.html b/site/downloads.html index 353ae938ba..881ac8fe08 100644 --- a/site/downloads.html +++ b/site/downloads.html @@ -175,7 +175,7 @@

    Where to learn more.

    diff --git a/site/index.html b/site/index.html index a8532a79d3..c747440172 100644 --- a/site/index.html +++ b/site/index.html @@ -341,7 +341,7 @@

    Recent activity.

    diff --git a/site/playground/playground-tabs.js b/site/playground/playground-tabs.js index be5ec841ac..38ea8a6786 100644 --- a/site/playground/playground-tabs.js +++ b/site/playground/playground-tabs.js @@ -94,6 +94,10 @@ if (!(name in pgState.files)) return; pgState.active = name; window.code.swapDoc(pgState.files[name]); + // Persist the new active tab — without this, a tab click followed by + // a page reload restores the prior active file (or main.das if no + // content edits have fired the autosave on `change` yet). + autosave(); // Toggle classes in place — rebuilding the tab DOM here breaks // dblclick rename (second click lands on a freshly-mounted node). if (tabsEl) { From e49e6f35ceecd8d0912ff94c6924def6db8f0a29 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 17:26:41 -0700 Subject: [PATCH 21/22] docs: External modules section + dasImgui crosslink, news/cards rollup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation: introduce an "External modules" section in the docs TOC (after stdlib) with a single dasImgui entry. Cross-link points at borisbat.github.io/dasImgui/ where the matching theme + intersphinx-based backlink land in a sibling PR. News: 0.6.2 release announcement for the landing-page § 05 strip — the release was promoted on 2026-05-13. mouse-data: four cards from this session's wrap-up curation. GitHub Pages deploy-snapshot semantics, CSS @import ordering rule, !important-vs-inline debugging anchor, CodeMirror 5 multi-Doc autosave discipline. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/source/external_modules/dasimgui.rst | 34 ++++++++ doc/source/external_modules/index.rst | 11 +++ doc/source/index.rst | 1 + ...file-editor-swapdoc-autosave-discipline.md | 79 +++++++++++++++++++ ...ede-all-other-rules-or-silently-dropped.md | 55 +++++++++++++ ...ats-js-inline-style-debug-cascade-first.md | 40 ++++++++++ ...oy-pages-publishes-snapshot-not-overlay.md | 31 ++++++++ .../2026-05-13-daslang-0-6-2-released.md | 6 ++ 8 files changed, 257 insertions(+) create mode 100644 doc/source/external_modules/dasimgui.rst create mode 100644 doc/source/external_modules/index.rst create mode 100644 mouse-data/docs/codemirror-5-multi-file-editor-swapdoc-autosave-discipline.md create mode 100644 mouse-data/docs/css-import-must-precede-all-other-rules-or-silently-dropped.md create mode 100644 mouse-data/docs/css-important-beats-js-inline-style-debug-cascade-first.md create mode 100644 mouse-data/docs/github-pages-deploy-pages-publishes-snapshot-not-overlay.md create mode 100644 site/_news/2026-05-13-daslang-0-6-2-released.md diff --git a/doc/source/external_modules/dasimgui.rst b/doc/source/external_modules/dasimgui.rst new file mode 100644 index 0000000000..397e2477c6 --- /dev/null +++ b/doc/source/external_modules/dasimgui.rst @@ -0,0 +1,34 @@ +dasImgui +======== + +dasImgui is the daslang binding for `Dear ImGui `_. +It ships with the boost macro layer that compresses immediate-mode boilerplate +into block-style one-liners, full live-reload integration, and a snapshot-based +test harness that drives the widget tree headlessly under ``dastest``. + +Where to go +----------- + +* **Documentation**: https://borisbat.github.io/dasImgui/ +* **Repository**: https://github.com/borisbat/dasImgui + +What's there +------------ + +* Bindings covering the Dear ImGui surface, generated from the upstream + ``imgui.h`` via the in-repo binder. +* The ``imgui_boost`` macro layer — ``window`` / ``tab_bar`` / ``tree_node`` + / ``table`` and friends as block macros, so the typical ``Begin*`` / ``End*`` + pair becomes a single scoped block. +* ``imgui_test``: a Playwright-style widget walker that asserts against the + rendered tree without needing a GL context — usable from dastest, runnable + in CI. +* Examples and tutorials under ``examples/features/``, every one runnable both + as a standalone binary and live-reloaded under ``daslang-live``. + +Versioning +---------- + +The documentation tracks the **v2.0** surface. The legacy ``daslib/imgui_boost`` +(v1) is not documented at the new site; v1 users pin to the older daspkg +release. diff --git a/doc/source/external_modules/index.rst b/doc/source/external_modules/index.rst new file mode 100644 index 0000000000..1f3b2769ec --- /dev/null +++ b/doc/source/external_modules/index.rst @@ -0,0 +1,11 @@ +External modules +================ + +Daslang modules maintained outside the main repository. Each one ships its +own documentation; the entries below are short pointers describing what each +module does and where its docs live. + +.. toctree:: + :maxdepth: 1 + + dasimgui diff --git a/doc/source/index.rst b/doc/source/index.rst index a42307f8f3..d6539b35e9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -9,6 +9,7 @@ Contents: reference/index.rst stdlib/index.rst + external_modules/index.rst Indices and tables diff --git a/mouse-data/docs/codemirror-5-multi-file-editor-swapdoc-autosave-discipline.md b/mouse-data/docs/codemirror-5-multi-file-editor-swapdoc-autosave-discipline.md new file mode 100644 index 0000000000..0631104d84 --- /dev/null +++ b/mouse-data/docs/codemirror-5-multi-file-editor-swapdoc-autosave-discipline.md @@ -0,0 +1,79 @@ +--- +slug: codemirror-5-multi-file-editor-swapdoc-autosave-discipline +title: How do I implement a multi-file editor in CodeMirror 5 with per-tab undo history and localStorage autosave? +created: 2026-05-13 +last_verified: 2026-05-13 +links: [] +--- + +**Core pattern: one `CodeMirror.Doc` per file, `editor.swapDoc(doc)` to switch the visible buffer. The editor instance is shared; each Doc carries its own text, undo stack, and scroll position.** + +```js +const editor = CodeMirror(el, opts); // one editor + +const state = { + files: { // one Doc per file + 'main.das': editor.getDoc(), + 'utils.das': new CodeMirror.Doc('', 'daslang'), + }, + active: 'main.das', +}; + +function switchFile(name) { + state.active = name; + editor.swapDoc(state.files[name]); // history preserved + autosave(); // ← see below +} +``` + +## Autosave discipline — the trap + +The instinct is to autosave on content change only: +```js +doc.on('change', autosave); // covers content edits +``` + +But that misses **every other state mutation** — tab switch, add, delete, rename. If the user picks tab B, types nothing, and reloads, persistence restores tab A. Fix: call `autosave()` from *every* state-modifying entry point, not just `change`: + +- `switchFile` → `autosave()` +- `addFile` → `autosave()` +- `deleteFile` → `autosave()` +- `renameFile` → `autosave()` +- Initial `change` listener stays (per-Doc, attached when Doc is created) + +Debounce the writer (~250ms) so rapid edits coalesce. Redundant calls during rapid state changes are free — `clearTimeout(autosaveTimer)` then `setTimeout(..., 250)`. + +## Serialization + +Each Doc stringifies via `.getValue()`. Whole state: +```js +function getStateJson() { + return { + files: Object.fromEntries( + Object.entries(state.files).map(([k, d]) => [k, d.getValue()]) + ), + active: state.active, + }; +} +``` +Persist `JSON.stringify(getStateJson())` into `localStorage`. Restore by parsing then reconstructing fresh Docs. + +## URL share format + +LZ-string the JSON, encode URL-safely: +```js +const url = location.origin + location.pathname + + '#z=' + LZString.compressToEncodedURIComponent(JSON.stringify(getStateJson())); +``` +Decode with `LZString.decompressFromEncodedURIComponent`. ~50-line program → ~250B encoded → fits in any URL bar. + +## Where this came from + +PR #2648 (daslang.io playground): `web/ui` shipped a multi-file `files: [...]` JSON schema in samples since forever, but the loader only ever read `files[0]`. Adding tabs was a Doc map + `swapDoc` per click. The autosave-on-tab-switch bug landed in round 6 of review — `pgSwitchFile` updated state but didn't call `autosave()`. Three rounds of testing later, Copilot caught it. + +## Bonus + +`editor.refresh()` after layout changes (e.g. splitter resize) — CM caches gutter widths and won't redraw unless told. + +## Questions +- How do I implement a multi-file editor in CodeMirror 5 with per-tab undo history and localStorage autosave? diff --git a/mouse-data/docs/css-import-must-precede-all-other-rules-or-silently-dropped.md b/mouse-data/docs/css-import-must-precede-all-other-rules-or-silently-dropped.md new file mode 100644 index 0000000000..736fc4ea03 --- /dev/null +++ b/mouse-data/docs/css-import-must-precede-all-other-rules-or-silently-dropped.md @@ -0,0 +1,55 @@ +--- +slug: css-import-must-precede-all-other-rules-or-silently-dropped +title: My CSS @import isn't loading any fonts — what's wrong? +created: 2026-05-13 +last_verified: 2026-05-13 +links: [] +--- + +**`@import` rules must precede every other style rule in the stylesheet (except `@charset` / `@layer`). A browser silently drops any `@import` placed after a normal rule.** + +Per CSS 2.1 §6.3: +> Any @import rules must precede all other rules (except the @charset rule if present). + +Modern browsers honor this strictly — there is no console warning, the imported stylesheet just never loads. Symptoms: web fonts fall back to system serif, retoken colors revert to defaults, etc. + +## The trap + +```css +:root { + --bg: #0d0c0a; + --fg: #e8e2d2; +} + +/* SILENTLY IGNORED — comes after :root */ +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight&display=swap'); + +body { + background: var(--bg); /* works */ + font-family: 'Inter Tight', sans-serif; /* falls back to system */ +} +``` + +The whole file looks "right" — the rules work, just no Inter Tight. Easy to miss because the fallback chain renders something reasonable. + +## Fix + +Move the `@import` to the very top, before any rule (including `:root`): + +```css +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight&display=swap'); + +:root { ... } +body { ... } +``` + +You can also avoid `@import` entirely by linking the stylesheet via `` in HTML — same fonts, no ordering trap. + +## Where this bit me + +PR #2648 (daslang.io docs theme): `doc/source/_static/custom.css` had the Google Fonts `@import` after the `:root` block defining design tokens. Browsers dropped the import, Sphinx HTML rendered in system serif despite the file looking correct. Fix was hoisting the `@import` to line 11 of the file, before `:root` on line 13. + +Spec reference: https://www.w3.org/TR/CSS21/cascade.html#at-import + +## Questions +- My CSS @import isn't loading any fonts — what's wrong? diff --git a/mouse-data/docs/css-important-beats-js-inline-style-debug-cascade-first.md b/mouse-data/docs/css-important-beats-js-inline-style-debug-cascade-first.md new file mode 100644 index 0000000000..ba59df83c6 --- /dev/null +++ b/mouse-data/docs/css-important-beats-js-inline-style-debug-cascade-first.md @@ -0,0 +1,40 @@ +--- +slug: css-important-beats-js-inline-style-debug-cascade-first +title: My JavaScript element.style.X assignment has no visible effect — why doesn't the element move/resize? +created: 2026-05-13 +last_verified: 2026-05-13 +links: [] +--- + +**Suspect `!important` in the CSS cascade. Inline styles (set via `element.style.X = ...` or the `style="..."` attribute) lose to a stylesheet rule that has `!important`. Higher-specificity selectors without `!important` would normally win, but `!important` flips that order.** + +CSS specificity order: +1. Author `!important` declarations +2. Inline style attribute (including JS-assigned `element.style.X`) +3. Author normal declarations +4. User agent styles + +So `.main_col { flex: 1 1 0 !important; }` defeats `el.style.flex = '0 0 50%'`. + +## How to spot it + +1. Open DevTools → Elements → Styles panel. The "won" rule is the one not crossed out at the top, with the smaller specificity. Look for `!important` flags. +2. Inline `style="..."` shows at the top of the Styles panel — if it's crossed out and a stylesheet rule isn't, `!important` is the culprit. + +## Two fixes + +1. **Remove `!important` from the stylesheet rule, increase its specificity instead.** Use `.main_workspace > .main_col` rather than `.main_col` — same effect, no `!important` needed, JS inline overrides work normally. +2. **Set the JS-applied style as `!important` too.** `el.style.setProperty('flex', '0 0 50%', 'important')`. Use sparingly — `!important` arms races scale badly. + +Option 1 is almost always right. `!important` in source CSS is usually a code smell; replacing it with selector specificity makes the cascade predictable. + +## Where this bit me + +PR #2648 (daslang playground splitter): drag handle wouldn't move the columns — `applyPct` was setting `left.style.flex` correctly, but the user-visible 50/50 layout never changed. `.main_col { flex: 1 1 0 !important; }` in `forge-skin.css` was beating the inline assignment. Fix: drop `!important`, use `.main_workspace > .main_col` selector specificity instead. The first-instinct debugging move (assume JS is broken) wastes time when the actual problem is CSS specificity. + +## General lesson + +When a JS-set inline style appears to have no effect, **inspect the Styles panel in DevTools first** before reaching for `console.log` or rewriting the JS — the cascade tells you exactly which rule won. + +## Questions +- My JavaScript element.style.X assignment has no visible effect — why doesn't the element move/resize? diff --git a/mouse-data/docs/github-pages-deploy-pages-publishes-snapshot-not-overlay.md b/mouse-data/docs/github-pages-deploy-pages-publishes-snapshot-not-overlay.md new file mode 100644 index 0000000000..e332657aa2 --- /dev/null +++ b/mouse-data/docs/github-pages-deploy-pages-publishes-snapshot-not-overlay.md @@ -0,0 +1,31 @@ +--- +slug: github-pages-deploy-pages-publishes-snapshot-not-overlay +title: Does GitHub Pages preserve previously-deployed files when actions/deploy-pages publishes a new artifact? +created: 2026-05-13 +last_verified: 2026-05-13 +links: [] +--- + +**No — `actions/deploy-pages` publishes the artifact as a complete site snapshot, not a layer over the prior deploy.** + +If you upload `_site/` and it's missing a directory (e.g. `_site/playground/`) that was present in the previous deploy, that path will 404 on the live site after this deploy completes. Pages does not retain or merge files from earlier deploys. + +## Implication for CI staging + +You cannot use "skip this directory when the source artifacts are missing" as a graceful-degradation strategy — that will 404 the route. Two viable options when an artifact is unbuildable: + +1. **Stage a placeholder `index.html`** so the URL keeps resolving to something useful (status page, "rebuilding" notice, link back to the rest of the site). Keeps the route alive while you re-roll. +2. **Fail the deploy** when artifacts are missing. Couples the whole site publish to the failing component — only acceptable if the missing piece is critical. + +The "graceful skip" intuition (let docs/blog ship even when WASM is broken) is correct in principle, but the implementation must explicitly stage *something* under the route — silent omission is the bug. + +## Where this bit me + +PR #2648 (daslang.io playground): WASM build had `continue-on-error: true` so emsdk hiccups wouldn't block docs/blog. Round-4 fix gated the playground staging on `daslang_static.{js,wasm}` existing — but with no `else` branch, `_site/playground/` was absent on WASM failure, which would 404 `/playground/` on the next deploy. Round-5 added the `site/playground/placeholder.html` fallback that explains the runtime is rebuilding. + +## Quick check + +`actions/deploy-pages@v4` reference: https://github.com/actions/deploy-pages — "deploys an artifact" (singular, replaces). The artifact is whatever `actions/upload-pages-artifact` packaged, which is the full `_site/` you staged. + +## Questions +- Does GitHub Pages preserve previously-deployed files when actions/deploy-pages publishes a new artifact? diff --git a/site/_news/2026-05-13-daslang-0-6-2-released.md b/site/_news/2026-05-13-daslang-0-6-2-released.md new file mode 100644 index 0000000000..864804ee5c --- /dev/null +++ b/site/_news/2026-05-13-daslang-0-6-2-released.md @@ -0,0 +1,6 @@ +--- +date: 2026-05-13 +tag: 0.6.2 +title: Daslang 0.6.2 released. +link: https://github.com/GaijinEntertainment/daScript/releases/tag/v0.6.2-RC3 +--- From 52821bac8b202ea126ee63b2eb3b6613d39d871c Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 13 May 2026 17:41:16 -0700 Subject: [PATCH 22/22] pages.yml: actually install Emscripten before building WASM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit web/step0_emsdk_install.sh is a one-liner that just clones the emsdk wrapper repo. The toolchain itself — emsdk/upstream/emscripten/, which the cmake -DCMAKE_TOOLCHAIN_FILE arg points at — lands via the install+activate pair that step1_emsdk_activate_linux.sh does locally. pages.yml was calling step0 but never the install/activate steps, so the WASM build always failed at cmake-configure ("Could not find toolchain file"). continue-on-error: true on this step paints it green in the run summary, and the post-build staging silently skipped the missing-artifact copies — until the round-5 placeholder gate started serving the "Runtime rebuild in progress" page instead, which is what surfaced this. Add the two missing commands. The build should now actually produce web/output/daslang_static.{js,wasm}; the existing staging gate will then take the real path instead of the placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pages.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 09ffdb1097..372e227426 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -77,8 +77,14 @@ jobs: run: | set -eux cd web + # step0 only clones the emsdk wrapper — the toolchain itself + # (emsdk/upstream/emscripten/) lands via the install+activate pair + # that web/step1_emsdk_activate_linux.sh does locally. Without these, + # cmake's -DCMAKE_TOOLCHAIN_FILE points at a path that doesn't + # exist yet and the WASM build silently fails under continue-on-error. bash step0_emsdk_install.sh - # step1 sources emsdk_env.sh — re-export EMSDK for the configure step + ./emsdk/emsdk install latest + ./emsdk/emsdk activate latest bash -c "source emsdk/emsdk_env.sh && \ mkdir -p build && cd build && \ cmake -DCMAKE_BUILD_TYPE=Release -G Ninja \