diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6972f6c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: + push: + pull_request: + +jobs: + dart-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Install dependencies + run: dart pub get + + - name: Run test suite + run: dart test + + sympy-smoke: + runs-on: ubuntu-latest + needs: dart-tests + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Dart dependencies + run: dart pub get + + - name: Install SymPy dependencies + run: pip install -r test/integration/sympy/requirements.txt + + - name: Generate cross-CAS test vectors + run: dart test test/integration/sympy/sympy_export_integration_test.dart + + - name: Verify against SymPy + run: python test/integration/sympy/verify_sympy_export.py + + - name: Upload SymPy verification artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: sympy-smoke-artifacts + path: | + test/integration/sympy/test_cases.json + test/integration/sympy/verification_results.json + + evaluability-guardrail: + runs-on: ubuntu-latest + needs: dart-tests + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Install dependencies + run: dart pub get + + - name: Run evaluability fast-path benchmark guardrail + run: dart run benchmark/evaluability_fast_path_benchmark.dart --enforce --min-speedup=1.02 --json-out=benchmark/results/evaluability_fast_path.json + + - name: Upload benchmark artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: evaluability-fast-path-benchmark + path: benchmark/results/evaluability_fast_path.json diff --git a/.gitignore b/.gitignore index ebefd20..3298714 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ wasm/build/* *.wasm *.mjs *.wasm.map + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vitepress/Layout.vue b/.vitepress/Layout.vue new file mode 100644 index 0000000..a1514d5 --- /dev/null +++ b/.vitepress/Layout.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/.vitepress/components/Card.vue b/.vitepress/components/Card.vue new file mode 100644 index 0000000..2a96826 --- /dev/null +++ b/.vitepress/components/Card.vue @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/.vitepress/components/DocOutline.vue b/.vitepress/components/DocOutline.vue new file mode 100644 index 0000000..b7b6ddf --- /dev/null +++ b/.vitepress/components/DocOutline.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/.vitepress/components/FeatureShowcase.vue b/.vitepress/components/FeatureShowcase.vue new file mode 100644 index 0000000..c372c29 --- /dev/null +++ b/.vitepress/components/FeatureShowcase.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/.vitepress/components/HomeHero.vue b/.vitepress/components/HomeHero.vue new file mode 100644 index 0000000..fdb5c12 --- /dev/null +++ b/.vitepress/components/HomeHero.vue @@ -0,0 +1,98 @@ + + + + + \ No newline at end of file diff --git a/.vitepress/components/Layouts.vue b/.vitepress/components/Layouts.vue index a1150ce..08feaac 100644 --- a/.vitepress/components/Layouts.vue +++ b/.vitepress/components/Layouts.vue @@ -85,39 +85,9 @@ const copyPage = () => { diff --git a/.vitepress/components/PageActionMenu.vue b/.vitepress/components/PageActionMenu.vue new file mode 100644 index 0000000..b8b880f --- /dev/null +++ b/.vitepress/components/PageActionMenu.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/.vitepress/components/Playground.vue b/.vitepress/components/Playground.vue index fa64813..b45ff4a 100644 --- a/.vitepress/components/Playground.vue +++ b/.vitepress/components/Playground.vue @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/.vitepress/components/SidebarText.vue b/.vitepress/components/SidebarText.vue new file mode 100644 index 0000000..2b5b6f5 --- /dev/null +++ b/.vitepress/components/SidebarText.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/.vitepress/components/ThemeSwitcher.vue b/.vitepress/components/ThemeSwitcher.vue new file mode 100644 index 0000000..5dfbb65 --- /dev/null +++ b/.vitepress/components/ThemeSwitcher.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/.vitepress/components/VPSidebarItem.vue b/.vitepress/components/VPSidebarItem.vue new file mode 100644 index 0000000..ef57bfa --- /dev/null +++ b/.vitepress/components/VPSidebarItem.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/.vitepress/composables/useActiveSidebar.ts b/.vitepress/composables/useActiveSidebar.ts new file mode 100644 index 0000000..4294c04 --- /dev/null +++ b/.vitepress/composables/useActiveSidebar.ts @@ -0,0 +1,39 @@ +import { useData, useRoute } from 'vitepress' +import { computed } from 'vue' + +export function useActiveSidebar() { + const { theme } = useData() + const route = useRoute() + + return computed(() => { + const sidebar = theme.value.sidebar + const path = route.path.replace(/\.html$/, '') + + let items = [] + if (Array.isArray(sidebar)) { + items = sidebar + } else if (typeof sidebar === 'object') { + const key = Object.keys(sidebar) + .filter(k => path.startsWith(k)) + .sort((a, b) => b.length - a.length)[0] + + if (key) items = sidebar[key] + } + + function containsActive(list: any[]): boolean { + if (!list) return false + for (const item of list) { + if (item.link && item.link.replace(/\.html$/, '') === path) return true + if (item.items && containsActive(item.items)) return true + } + return false + } + + for (const group of items) { + if (group.link && group.link.replace(/\.html$/, '') === path) return group + if (group.items && containsActive(group.items)) return group + } + + return null + }) +} diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 7d89fcd..27c171c 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1,4 +1,5 @@ import { defineConfig } from "vitepress"; +import { fileURLToPath, URL } from 'node:url' import nav from "./config/nav"; import sidebar from "./config/sidebar"; import UnoCSS from 'unocss/vite' @@ -40,10 +41,32 @@ export default defineConfig({ 'blog/*', 'public/*' ], - domain: 'https://texprjs.com' + domain: 'https://texpr.andka.id' }) : undefined, - ] + ], + resolve: { + alias: [ + { + find: /^.*\/VPDocAsideOutline\.vue$/, + replacement: fileURLToPath( + new URL('./components/DocOutline.vue', import.meta.url) + ) + }, + { + find: /^.*\/VPSwitchAppearance\.vue$/, + replacement: fileURLToPath( + new URL('./components/ThemeSwitcher.vue', import.meta.url) + ) + }, + { + find: /^.*\/VPSidebarItem\.vue$/, + replacement: fileURLToPath( + new URL('./components/VPSidebarItem.vue', import.meta.url) + ) + } + ] + } }, themeConfig: { logo: "/logo.svg", diff --git a/.vitepress/config/nav.ts b/.vitepress/config/nav.ts index 5a97a74..3d46333 100644 --- a/.vitepress/config/nav.ts +++ b/.vitepress/config/nav.ts @@ -94,7 +94,7 @@ export default [ activeMatch: "^/advanced/", }, { - text: 'v0.1.3', + text: 'v0.1.4', items: [ { text: "Project Info", @@ -102,6 +102,7 @@ export default [ { text: "Changelog", link: "https://github.com/xirf/texpr/blob/main/CHANGELOG.md" }, { text: "Release Notes", link: "https://github.com/xirf/texpr/releases" }, { text: "Contributing", link: "https://github.com/xirf/texpr/blob/main/CONTRIBUTING.md" }, + { text: "Migration Guide", link: "/guide/migration-validate" }, ], }, { diff --git a/.vitepress/index.ts b/.vitepress/index.ts new file mode 100644 index 0000000..deb6669 --- /dev/null +++ b/.vitepress/index.ts @@ -0,0 +1,19 @@ +import DefaultTheme from 'vitepress/theme' +import type { Theme } from 'vitepress' +import HomeHero from './components/HomeHero.vue' +import FeatureShowcase from './components/FeatureShowcase.vue' +import Layout from './Layout.vue' + +import 'virtual:uno.css' +import './vars.css' +import './override.css' + +export default { + extends: DefaultTheme, + Layout, + enhanceApp({ app }) { + app.component('HomeHero', HomeHero) + + app.component('FeatureShowcase', FeatureShowcase) + } +} satisfies Theme diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css deleted file mode 100644 index e346f5b..0000000 --- a/.vitepress/theme/custom.css +++ /dev/null @@ -1,513 +0,0 @@ -/* UnoCSS Migration */ - -:root { - /* Brand colors inspired by UnoCSS gradient */ - --vp-c-brand-1: theme('colors.violet.500'); - --vp-c-brand-2: theme('colors.violet.400'); - --vp-c-brand-3: theme('colors.violet.300'); - --vp-c-brand-soft: rgba(124, 58, 237, 0.14); /* violet-600 with opacity */ - - --vp-c-bg-alt: theme('colors.slate.100'); - --vp-c-border: theme('colors.slate.100'); - --twoslash-border-color: theme('colors.slate.100'); - --vp-code-block-bg: #f6f8fa; - --vp-code-copy-code-bg: #dedede; - --vp-code-copy-code-hover-bg: #dedede; - --vp-c-text-dark-2: #8f8f8f; - --vp-c-text-dark-3: #8f8f8f; - --vp-sidebar-bg-color: var(--vp-c-bg); - --vp-custom-block-tip-border: transparent; - --vp-custom-block-tip-text: var(--vp-c-text-1); - --vp-nav-bg-color: color-mix(in srgb, var(--vp-c-bg) 95%, transparent); - --vp-custom-block-tip-bg: #ecfdf5; - --vp-custom-block-tip-code-bg: #cdf7dc; - --vp-custom-block-tip-border: #dcfce7; - --vp-custom-block-tip-text: #15803d; - --vp-c-brand-nav-active: color-mix(in srgb, - var(--vp-c-brand-light) 15%, - transparent 100%); - --vp-nav-logo-height: 28px; - - --twoslash-border-color: theme('colors.slate.200'); - --vp-c-bg-elv: theme('colors.slate.100'); - - --vp-code-copy-code-bg: theme('colors.slate.50'); - --vp-code-copy-code-hover-bg: #fff; - --vp-code-copy-code-border-color: theme('colors.slate.200'); - - --vp-icon-copy: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22rgb(128,128,128)%22%20stroke-width=%221.5%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Crect%20width=%2214%22%20height=%2214%22%20x=%228%22%20y=%228%22%20rx=%222%22%20ry=%222%22/%3E%3Cpath%20d=%22M4%2016c-1.1%200-2-.9-2-2V4c0-1.1.9-2%202-2h10c1.1%200%202%20.9%202%202%22/%3E%3C/svg%3E'); - --vp-icon-copied: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22rgb(128,128,128)%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Cpath%20d=%22M20%206%209%2017l-5-5%22/%3E%3C/svg%3E'); -} - -.dark { - --vp-c-brand-1: theme('colors.violet.300'); - --vp-c-brand-2: theme('colors.violet.200'); - --vp-c-brand-3: theme('colors.violet.100'); - - --vp-c-bg: theme('colors.slate.900'); - --vp-c-border: theme('colors.slate.100'); - --twoslash-border-color: theme('colors.slate.100'); - --vp-c-bg-alt: theme('colors.slate.800'); - --vp-c-bg-soft: theme('colors.slate.800'); - --vp-c-divider: theme('colors.slate.700'); - --vp-code-block-bg: theme('colors.slate.800'); - --vp-c-bg-alpha-with-backdrop: #1f2937bf; - --vp-code-copy-code-bg: theme('colors.slate.800'); - --vp-code-copy-code-hover-bg: theme('colors.slate.800'); - --vp-c-text-dark-2: #8a8a8a; - --vp-c-text-dark-3: #8a8a8a; - --vp-custom-block-tip-bg: #064e3b; - --vp-custom-block-tip-border: #052e16; - --vp-custom-block-tip-text: #f0fdf4; - --vp-c-brand-nav-active: color-mix(in srgb, - var(--vp-c-brand-dark) 30%, - transparent 100%); - - --twoslash-border-color: theme('colors.slate.800'); - --vp-c-bg-elv: theme('colors.slate.800'); - --vp-c-neutral-inverse: theme('colors.slate.800'); - - --vp-code-copy-code-bg: theme('colors.slate.800'); - --vp-code-copy-code-hover-bg: theme('colors.slate.700'); - --vp-code-copy-code-border-color: theme('colors.slate.600'); - - --vp-icon-copy: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22rgb(160,160,160)%22%20stroke-width=%221.5%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Crect%20width=%2214%22%20height=%2214%22%20x=%228%22%20y=%228%22%20rx=%222%22%20ry=%222%22/%3E%3Cpath%20d=%22M4%2016c-1.1%200-2-.9-2-2V4c0-1.1.9-2%202-2h10c1.1%200%202%20.9%202%202%22/%3E%3C/svg%3E'); - --vp-icon-copied: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22rgb(160,160,160)%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Cpath%20d=%22M20%206%209%2017l-5-5%22/%3E%3C/svg%3E'); -} - -/* Hero gradient background like UnoCSS */ -.VPHero { - position: relative; - overflow: hidden; -} - -.VPHero::before { - content: ""; - position: absolute; - inset: 0; - background: radial-gradient(ellipse 80% 50% at 50% -20%, - rgba(99, 102, 241, 0.3), - transparent), - radial-gradient(ellipse 60% 40% at 80% 50%, - rgba(139, 92, 246, 0.2), - transparent), - radial-gradient(ellipse 50% 30% at 20% 80%, - rgba(99, 102, 241, 0.15), - transparent); - z-index: -1; - pointer-events: none; -} - -.dark .VPHero::before { - background: radial-gradient(ellipse 80% 50% at 50% -20%, - rgba(99, 102, 241, 0.4), - transparent), - radial-gradient(ellipse 60% 40% at 80% 50%, - rgba(139, 92, 246, 0.3), - transparent), - radial-gradient(ellipse 50% 30% at 20% 80%, - rgba(99, 102, 241, 0.2), - transparent); -} - -/* dark/light radial transition */ -::view-transition-old(root), -::view-transition-new(root) { - animation: none; - mix-blend-mode: normal; -} - -::view-transition-old(root), -.dark::view-transition-new(root) { - z-index: 1; -} - -::view-transition-new(root), -.dark::view-transition-old(root) { - z-index: 9999; -} - -.VPSwitchAppearance { - @apply !size-7 !bg-transparent !border-0 opacity-80 overflow-hidden mx-1; - - &::before { - @apply hidden!; - } - - & > .check { - @apply m-auto !bg-transparent !shadow-none size-7!; - - & > .icon { - @apply !size-7; - - & > span { - @apply scale-175 top-1.75 left-1.75; - } - } - } -} - -.VPNav { - @media (max-width: theme(--breakpoint-xl)) { - z-index: 40 !important; - } - - & > .VPNavBar { - @apply bg-transparent! backdrop-blur-xs; - - /* Doc layout */ - & > .wrapper > .container > .content > .content-body { - @apply bg-transparent gap-0; - } - - & > .divider { - @apply hidden; - } - } -} - - -#VPSidebarNav > .group { - @apply border-t-0 min-h-11 pt-2.25; - - & > .VPSidebarItem { - @apply pb-1; - - & > .item { - @apply opacity-85; - - & > .text { - @apply flex items-center text-base font-medium py-1.5; - - &::before { - @apply mr-2.5 opacity-70; - - --tw-translate-y: calc(var(--spacing) * 0.175); - translate: var(--tw-translate-x) var(--tw-translate-y); - } - } - - & > .caret { - @apply scale-90 translate-y-0.5; - } - } - - & > .items { - & > .VPSidebarItem { - @apply pl-4 border-l dark:border-l-gray-600 rounded-none transition-all duration-100 ease-out; - - /* Separation between nested table of content */ - &:not(.collapsible), - &.collapsible > .items > .VPSidebarItem { - &:hover, - &:focus, - &.is-active.has-active { - @apply rounded-r-lg border-transparent; - background-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%); - - & > .item > .indicator { - background-color: var(--vp-c-brand-1); - height: 100%; - top:0; - } - } - } - - &.collapsible { - &:not(.is-active) { - & > .item:has(.link):hover, - & > .item:has(.link):focus { - & > .caret { - @apply -translate-x-2; - } - } - } - - & > .item { - @apply rounded-lg transition-all duration-100 ease-out; - - &:hover, - &:focus { - background-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%); - - & > h3 { - color: var(--vp-c-brand) !important; - } - - & > .indicator { - background-color: var(--vp-c-brand); - } - } - - & > .indicator { - @apply translate-x-4; - } - - & > h3 { - @apply font-normal; - } - - &:has(.link):hover, - &:has(.link):focus { - background-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%); - - & > .indicator { - background-color: var(--vp-c-brand); - } - - & > .link > h3 { - color: var(--vp-c-brand) !important; - } - } - } - - &.is-active > .item { - @apply pr-2; - background-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%); - border-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%) !important; - - & > .indicator { - @apply translate-x-4; - background-color: var(--vp-c-brand); - } - - & > div > .caret-icon { - color: var(--vp-c-brand); - } - } - - & > .items { - @apply grid border-0 pl-0; - - & > .VPSidebarItem { - @apply border-l pl-4 dark:border-l-gray-600; - } - - & > .caret { - &:hover, - &:focus { - background-color: transparent; - } - } - - & > .VPSidebarItem { - @apply min-h-8; - } - } - } - } - } - } -} - -.VPLocalNav { - @apply !bg-transparent !border-0; - - & > .container { - @apply relative flex bg-transparent mx-auto px-4 sm:px-6 md:px-0 lg:px-0 py-0 mt-2; - max-width: 688px; - - & > .menu { - @apply p-0; - } - - & > .menu, - & > .VPLocalNavOutlineDropdown > button { - @apply !bg-white/80 dark:!bg-slate-800/80 px-2 py-0.75 rounded-xl border border-slate-700/10 dark:border-slate-300/10 backdrop-blur-md; - } - - & > .VPLocalNavOutlineDropdown { - @apply p-0 ml-auto; - - & > .items { - @apply top-10 right-4 md:right-0 max-w-[calc(100%-32px)] sm:max-w-sm !bg-white/75 dark:!bg-slate-800/75 p-1 rounded-2xl border border-slate-700/10 dark:border-slate-300/10 backdrop-blur-md shadow-2xl shadow-black/5; - left: unset; - transition-duration: 0.5s; - transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - - & > .header { - @apply h-9 mb-2 bg-transparent; - } - - & > .outline { - @apply p-0 outline-0 bg-transparent; - - & > .nested { - @apply px-0; - - & > li { - & > a { - @apply px-4; - } - - & > a { - &:hover, - &:focus { - @apply rounded-xl; - background-color: var( - --vp-c-brand-nav-active - ); - color: var(--vp-c-brand); - } - } - - & > ul { - @apply pl-4 pr-0; - - & > li { - @apply border-l; - - & > a { - @apply px-4; - } - - & > a { - &:hover, - &:focus { - @apply rounded-lg; - background-color: var( - --vp-c-brand-nav-active - ); - color: var(--vp-c-brand); - } - } - } - } - } - } - } - } - } - } -} - -.dark .VPSwitchAppearance .check{ - transform: translateX(0) !important; -} -.appearance + .social-links::before, -.menu + .appearance::before{ - display: none !important; -} - - -button.copy::after { - top: 0; - width: 40px; - height: 40px; - color: var(--vp-icon-copy); - background-size: contain; -} - -button[title='Copy Code'].copy { - @apply rounded-xl! transition-all!; - - &.copied { - @apply rounded-l-none!; - } - - &::before { - @apply pr-1 translate-x-1 border-r-0 rounded-l-xl!; - } -} - -div[class*='language-'] { - @apply rounded-none! sm:rounded-2xl!; -} - -.VPDocAside { - & > .VPDocAsideOutline > .content { - @apply pl-0 border-l-0; - - & > .outline-marker { - @apply z-10 w-0.75; - transition-timing-function: var(--ease-out-expo); - transition-duration: 0.3s; - } - - & > .outline-title { - @apply flex items-center font-medium text-gray-700 dark:text-gray-100; - - &::before { - @apply inline-block size-3.5 bg-cover mr-1.5; - content: ''; - background-image: url('data:image/svg+xml;utf8,'); - } - - .dark &::before { - @apply invert-100; - } - } - - .outline-link.active { - @apply font-semibold relative overflow-visible; - color: var(--vp-c-brand-1); - transition-timing-function: var(--ease-out-expo); - transition-duration: 0.35s; - } - - .outline-link.active::before { - @apply absolute top-0 right-0 h-full rounded-md; - content: ''; - width: calc(100% + 20px); - background-color: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent 100%); - z-index: -1; - } - - & > .VPDocOutlineItem { - @apply border-l-2 border-gray-100 dark:border-gray-700 pl-4; - } - } -} - -.aside-container { - padding-top: calc( - var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + - var(--vp-doc-top-height, 0px) + 24px - ) !important; -} - -::-webkit-scrollbar { - width: 8px; -} -::-webkit-scrollbar-track { - background: #0B0E14; -} -::-webkit-scrollbar-thumb { - background: #334155; - border-radius: 4px; -} -::-webkit-scrollbar-thumb:hover { - background: #475569; -} - -.glass-panel { - background: rgba(21, 26, 35, 0.6); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.05); -} - -.glass-card { - background: linear-gradient(180deg, rgba(30, 41, 59, 0.4) 0%, rgba(15, 23, 42, 0.4) 100%); - backdrop-filter: blur(8px); - border: 1px solid rgba(255, 255, 255, 0.05); - transition: all 0.3s ease; -} -.glass-card:hover { - transform: translateY(-5px); - border-color: rgba(255, 72, 149, 0.3); - box-shadow: 0 10px 40px -10px rgba(255, 72, 149, 0.15); - -} -.token.keyword { color: #c678dd; } /* Purple */ -.token.string { color: #98c379; } /* Green */ -.token.function { color: #61afef; } /* Blue */ -.token.number { color: #d19a66; } /* Orange */ -.token.comment { color: #5c6370; font-style: italic; } /* Grey */ -.token.class-name { color: #e5c07b; } /* Yellow */ - - -.math-font { - font-family: 'Times New Roman', Times, serif; - font-style: italic; -} \ No newline at end of file diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index e149760..d626e3e 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -5,7 +5,9 @@ import Layout from '../components/Layouts.vue' import Ray from '../components/Ray.vue' import Hero from '../components/Hero.vue' import EYN from '../components/EYN.vue' -import './custom.css' + +import './vars.css' +import './override.css' export default { extends: DefaultTheme, diff --git a/.vitepress/theme/override.css b/.vitepress/theme/override.css new file mode 100644 index 0000000..7514f47 --- /dev/null +++ b/.vitepress/theme/override.css @@ -0,0 +1,76 @@ + + +/* Nav +/* ================================================================ */ + +.VPNav { + --at-apply: 'bg-transparent! fixed top-0 left-0 w-full backdrop-blur-sm transition-all duration-300'; + @media (max-width: theme(--breakpoint-xl)) { + z-index: 40 !important; + } + + &>.VPNavBar { + --at-apply: 'bg-transparent! '; + + /* Doc layout */ + &>.wrapper>.container>.content>.content-body { + --at-apply: 'bg-transparent gap-0'; + } + + &>.divider { + --at-apply: 'hidden'; + } + } +} + +.VPSocialLinks::before { + --at-apply: 'hidden'; +} + +/* +/* View transition +/* ================================================================ */ +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} + +::view-transition-old(root), +.dark::view-transition-new(root) { + z-index: 1; +} + +::view-transition-new(root), +.dark::view-transition-old(root) { + z-index: 9999; +} + +.group + .group{ + border: none !important; +} + +.VPNavBarTitle.has-sidebar .title{ + border: none !important; +} + +/* Scrollbar +/* ================================================================ */ + +::-webkit-scrollbar { + width: 4px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: theme('colors.slate.600'); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--vp-c-brand); +} diff --git a/.vitepress/theme/vars.css b/.vitepress/theme/vars.css new file mode 100644 index 0000000..7057659 --- /dev/null +++ b/.vitepress/theme/vars.css @@ -0,0 +1,109 @@ +/** + * Colors + * =============================================================== */ + +:root { + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + + --vp-c-brand-1: theme('colors.indigo.500'); + --vp-c-brand-2: theme('colors.indigo.400'); + --vp-c-brand-light: theme('colors.indigo.300'); + --vp-c-bg-alt: theme('colors.slate.100'); + --vp-c-brand-dark: theme('colors.indigo.500'); + --vp-c-border: theme('colors.slate.100'); + --twoslash-border-color: theme('colors.slate.100'); + --vp-code-block-bg: #f6f8fa; + --vp-code-copy-code-bg: #dedede; + --vp-code-copy-code-hover-bg: #dedede; + --vp-c-text-dark-2: #8f8f8f; + --vp-c-text-dark-3: #8f8f8f; + --vp-sidebar-bg-color: var(--vp-c-bg); + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-nav-bg-color: color-mix(in srgb, var(--vp-c-bg) 95%, transparent); + --vp-custom-block-tip-bg: #ecfdf5; + --vp-custom-block-tip-code-bg: #cdf7dc; + --vp-custom-block-tip-border: #dcfce7; + --vp-custom-block-tip-text: #15803d; + --vp-c-brand-nav-active: color-mix( + in srgb, + var(--vp-c-brand-light) 15%, + transparent 100% + ); + --vp-nav-logo-height: 28px; + + --twoslash-border-color: theme('colors.slate.200'); + --vp-c-bg-elv: theme('colors.slate.100'); + + --vp-code-copy-code-bg: theme('colors.slate.50'); + --vp-code-copy-code-hover-bg: #fff; + --vp-code-copy-code-border-color: theme('colors.slate.200'); +} + +.dark { + --vp-c-brand-1: theme('colors.indigo.400'); + --vp-c-brand-2: theme('colors.indigo.500'); + --vp-c-brand-light: theme('colors.indigo.600'); + --vp-c-bg-alt: theme('colors.slate.100'); + --vp-c-brand-dark: theme('colors.indigo.700'); + --vp-c-bg: theme('colors.slate.900'); + --vp-c-border: theme('colors.slate.100'); + --twoslash-border-color: theme('colors.slate.100'); + --vp-c-bg-alt: theme('colors.slate.800'); + --vp-c-bg-soft: theme('colors.slate.800'); + --vp-c-divider: theme('colors.slate.700'); + --vp-code-block-bg: theme('colors.slate.800'); + --vp-c-bg-alpha-with-backdrop: #1f2937bf; + --vp-code-copy-code-bg: theme('colors.slate.800'); + --vp-code-copy-code-hover-bg: theme('colors.slate.800'); + --vp-c-text-dark-2: #8a8a8a; + --vp-c-text-dark-3: #8a8a8a; + --vp-custom-block-tip-bg: #064e3b; + --vp-custom-block-tip-border: #052e16; + --vp-custom-block-tip-text: #f0fdf4; + --vp-c-brand-nav-active: color-mix( + in srgb, + var(--vp-c-brand-dark) 30%, + transparent 100% + ); + + --twoslash-border-color: var(--color-slate-800); + --vp-c-bg-elv: theme('colors.slate.800'); + --vp-c-neutral-inverse: theme('colors.slate.800'); + + --vp-code-copy-code-bg: theme('colors.slate.800'); + --vp-code-copy-code-hover-bg: theme('colors.slate.700'); + --vp-code-copy-code-border-color: theme('colors.slate.600'); +} + +/** + * Component: Button + * =============================================================== */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-1); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/** + * Component: Custom Block + * =============================================================== */ + +:root { + --vp-custom-block-tip-border: var(--vp-c-brand-1); + --vp-custom-block-tip-text: var(--vp-c-brand-darker); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +.dark { + --vp-custom-block-tip-border: var(--vp-c-brand-1); + --vp-custom-block-tip-text: var(--vp-c-brand-lightest); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f6a4c36 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": ".fvm/versions/3.35.6" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index be72552..83686e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +## 0.1.4 - 2026-02-24 + +### Added + +- Compile-time evaluability annotation: parser now annotates every AST node with `EvaluabilityInfo` during parse. +- New AST metadata API via `Expression.compileTimeEvaluabilityInfo` for tooling and diagnostics. +- Added TDD coverage for parser-time annotation and context-aware resolution in `test/core/compile_time_evaluability_test.dart`. +- Added v0.1.4 improvement plan to roadmap (correctness, benchmarks, docs, migration guidance). +- JSON AST export now supports `toJson(includeEvaluability: true)` to include optional compile-time evaluability metadata. +- Added CI workflow with SymPy smoke verification (Dart export + Python validation with tolerance checks). +- Added evaluability fast-path micro-benchmark and CI guardrail artifact generation. +- Added migration guide for deprecated `isValid()` / `validate()` APIs: `doc/guide/migration-validate.md`. + +### Fixed + +- `getEvaluability()` now uses compile-time metadata when available, avoiding repeated full-tree traversals for parsed expressions. +- Normalized structural cache keys for floating-point edge values so `-0.0` and `0.0` map to the same key and `NaN` is handled consistently. +- Updated README and docs links/examples to match current project structure and versioning. + +### Removed + ## 0.1.3 - 2026-01-12 **Boolean Logic, Bug Fixes & Doc Improvements** diff --git a/README.md b/README.md index b0ffb8d..2e2175a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TeXpr 🧮 -[![Tests](https://img.shields.io/badge/tests-1874%20passed-brightgreen)](https://github.com/xirf/texpr) +[![CI](https://github.com/xirf/texpr/actions/workflows/ci.yml/badge.svg)](https://github.com/xirf/texpr/actions/workflows/ci.yml) [![Dart](https://img.shields.io/badge/dart-%3E%3D3.0.0-blue)](https://github.com/xirf/texpr) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Pub Version](https://img.shields.io/pub/v/texpr)](https://pub.dev/packages/texpr) @@ -29,7 +29,7 @@ Add the dependency to your `pubspec.yaml`: ```yaml dependencies: - texpr: ^0.0.1 + texpr: ^0.1.4 ``` @@ -100,19 +100,20 @@ final matrixResult = evaluator.evaluate(r''' ## Exceptions -* [TexprException](exceptions.md): Base class for all library exceptions. -* [ValidationResult](exceptions.md#validationresult): Detailed validation information. +* [TexprException](doc/reference/exceptions.md): Base class for all library exceptions. +* [ValidationResult](doc/reference/exceptions.md): Detailed validation information. ## Security -* [Security Considerations](../security.md): Overview of security mitigations and limits. +* [Security Considerations](doc/advanced/security.md): Overview of security mitigations and limits. The parser provides error location offsets and suggestions for syntax errors. ```dart -final validation = evaluator.validate(r'\frac{1{2}'); -if (!validation.isValid) { - print('Error at ${validation.position}: ${validation.errorMessage}'); - // Suggestion: "Add a closing brace '}'" +try { + evaluator.parse(r'\frac{1{2}'); +} on TexprException catch (e) { + print('Error at ${e.position}: ${e.message}'); + print('Suggestion: ${e.suggestion}'); } // Function name suggestions @@ -197,14 +198,15 @@ Below is a selection of examples showcasing the library's capabilities. ## 📖 Documentation -* **[Getting Started](doc/getting_started.md)** -* **[LaTeX Commands Reference](doc/latex_commands.md)** -* **[Symbolic Algebra](doc/symbolic_algebra.md)** -* **[Boolean Logic](doc/guide/logic.md)** -* **[Function Reference](doc/functions/README.md)** -* **[Extending the Library](doc/extensions.md)** -* **[Export Features](doc/features/export.md)** -* **[Security Considerations](doc/security.md)** +* **[Guide Index](doc/guide/index.md)** +* **[Quick Start](doc/guide/quick-start.md)** +* **[Installation](doc/guide/installation.md)** +* **[API Reference](doc/reference/api.md)** +* **[LaTeX Grammar](doc/reference/grammar.md)** +* **[Functions](doc/reference/functions.md)** +* **[Known Issues](doc/reference/known-issues.md)** +* **[Performance](doc/how-it-works/performance.md)** +* **[Deprecation Migration (`validate` / `isValid`)](doc/guide/migration-validate.md)** ## 🤝 Contributing diff --git a/ROADMAP.md b/ROADMAP.md index 2223f22..d717662 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ --- -## Current State (v0.2.0) +## Current State (v0.1.4) ### ✅ What Works @@ -205,7 +205,7 @@ We aim to be the **best Dart library for evaluating mathematical expressions wri > These are acknowledged limitations in our test suite: -- **No automated cross-CAS validation** — Results are manually verified, not programmatically compared to SymPy/Mathematica +- **Cross-CAS coverage is smoke-level** — CI compares representative cases against SymPy, but not exhaustive property-level equivalence - **No mutation testing** — Code coverage is measured, but mutation coverage is not --- @@ -254,7 +254,7 @@ We aim to be the **best Dart library for evaluating mathematical expressions wri | Task | Status | Description | | -------------------------- | ------ | ----------------------------------------------------------------- | | Evaluability enum | ✅ | Add `Evaluability.numeric`, `.symbolic`, `.unevaluable` to nodes | -| Compile-time evaluability | 📋 | Parser annotates AST with evaluability at parse time | +| Compile-time evaluability | ✅ | Parser annotates AST with evaluability at parse time | | Semantic invariant testing | ✅ | Property-based tests for derivative correctness, round-trip, etc. | **Why this matters:** As the parsed surface area grows (tensors, quantifiers, set notation), the gap between "parses successfully" and "has computable meaning" becomes a usability hazard. Explicit evaluability prevents false expectations. @@ -281,5 +281,26 @@ enum Evaluability { --- -**Last Updated:** 2026-01-08 +### Phase 7: Next Release Plan (v0.1.4) + +**Goal:** Improve correctness at numeric edges, strengthen validation rigor, and reduce developer confusion without introducing major API churn. + +| Task | Status | Description | +| --------------------------------------------------- | ------ | --------------------------------------------------------------------------- | +| Cache key normalization for `-0.0` and `NaN` | ✅ | Canonicalize floating-point edge values in cache keys to prevent collisions | +| Evaluability metadata visibility in JSON export | ✅ | Optionally include compile-time evaluability info in AST JSON output | +| Automated cross-CAS validation smoke suite | ✅ | Add CI-level numeric tolerance checks against SymPy for representative cases | +| Benchmark guardrail for evaluability fast-path | ✅ | Add micro-benchmark comparing cached vs visitor-based evaluability lookup | +| README and docs consistency pass | ✅ | Align version examples, test-count claims, and links with current release | +| Deprecation migration guide (`validate`/`isValid`) | ✅ | Add explicit migration snippets before eventual 1.0 removal | + +**Release Gate (v0.1.4):** +- Cache key edge-case tests pass for signed zero and NaN. +- Benchmark artifact is reproducible in CI and checked into benchmark docs. +- Docs no longer contain stale version or outdated compatibility claims. +- Public API changes remain additive only (no breaking changes). + +--- + +**Last Updated:** 2026-02-24 diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..45e7982 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,28 @@ +# Benchmarks + +This directory contains micro-benchmarks and comparison benchmarks for TeXpr. + +## Evaluability Fast-Path Guardrail + +`evaluability_fast_path_benchmark.dart` compares: + +- **Annotated path**: `Expression.getEvaluability()` on parser-annotated AST nodes +- **Visitor path**: `Expression.getEvaluability()` fallback visitor traversal on unannotated AST nodes + +### Run manually + +```bash +dart run benchmark/evaluability_fast_path_benchmark.dart +``` + +### Run with guardrail enforcement + +```bash +dart run benchmark/evaluability_fast_path_benchmark.dart --enforce --min-speedup=1.02 --json-out=benchmark/results/evaluability_fast_path.json +``` + +The benchmark exits non-zero when `--enforce` is passed and the measured speedup is below the configured threshold. CI runs this command and uploads the JSON result as an artifact. + +## Cross-Language Comparison + +See `benchmark/comparison/README.md` for Dart vs Python vs JavaScript comparison benchmarks. diff --git a/benchmark/evaluability_fast_path_benchmark.dart b/benchmark/evaluability_fast_path_benchmark.dart new file mode 100644 index 0000000..62f630a --- /dev/null +++ b/benchmark/evaluability_fast_path_benchmark.dart @@ -0,0 +1,116 @@ +library; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:texpr/texpr.dart'; + +void main(List args) { + final enforce = args.contains('--enforce'); + final minSpeedup = _readDoubleArg(args, '--min-speedup', 1.02); + final jsonOut = _readStringArg(args, '--json-out'); + + final evaluator = Texpr(cacheConfig: CacheConfig.disabled); + + final parsedAnnotated = evaluator.parse(r'x^2 + y^2 + 1'); + + final manualUnannotated = BinaryOp( + BinaryOp( + BinaryOp( + const Variable('x'), BinaryOperator.power, const NumberLiteral(2)), + BinaryOperator.add, + BinaryOp( + const Variable('y'), BinaryOperator.power, const NumberLiteral(2)), + ), + BinaryOperator.add, + const NumberLiteral(1), + ); + + const knownVars = {'x', 'y'}; + + const warmupIterations = 20000; + const benchmarkIterations = 400000; + + for (var i = 0; i < warmupIterations; i++) { + parsedAnnotated.getEvaluability(knownVars); + manualUnannotated.getEvaluability(knownVars); + } + + final annotatedMicros = _timeMicros(benchmarkIterations, () { + parsedAnnotated.getEvaluability(knownVars); + }); + + final visitorMicros = _timeMicros(benchmarkIterations, () { + manualUnannotated.getEvaluability(knownVars); + }); + + final speedup = visitorMicros / annotatedMicros; + + final result = { + 'benchmark': 'evaluability_fast_path', + 'iterations': benchmarkIterations, + 'annotated_total_us': annotatedMicros, + 'visitor_total_us': visitorMicros, + 'annotated_avg_us': annotatedMicros / benchmarkIterations, + 'visitor_avg_us': visitorMicros / benchmarkIterations, + 'speedup': speedup, + 'enforced': enforce, + 'min_speedup': minSpeedup, + 'passed': !enforce || speedup >= minSpeedup, + 'timestamp_utc': DateTime.now().toUtc().toIso8601String(), + }; + + print('Evaluability Fast-Path Benchmark'); + print(' iterations: $benchmarkIterations'); + print( + ' annotated avg: ${(annotatedMicros / benchmarkIterations).toStringAsFixed(6)} µs'); + print( + ' visitor avg: ${(visitorMicros / benchmarkIterations).toStringAsFixed(6)} µs'); + print( + ' speedup: ${speedup.toStringAsFixed(3)}x (visitor / annotated)'); + + if (jsonOut != null && jsonOut.isNotEmpty) { + final outputFile = File(jsonOut); + outputFile.parent.createSync(recursive: true); + outputFile.writeAsStringSync( + const JsonEncoder.withIndent(' ').convert(result), + ); + print(' wrote: ${outputFile.path}'); + } + + if (enforce && speedup < minSpeedup) { + stderr.writeln( + 'Guardrail failed: speedup ${speedup.toStringAsFixed(3)}x < required ${minSpeedup.toStringAsFixed(3)}x', + ); + exitCode = 1; + } +} + +int _timeMicros(int iterations, void Function() fn) { + final stopwatch = Stopwatch()..start(); + for (var i = 0; i < iterations; i++) { + fn(); + } + stopwatch.stop(); + return stopwatch.elapsedMicroseconds; +} + +double _readDoubleArg(List args, String key, double fallback) { + final prefix = '$key='; + for (final arg in args) { + if (arg.startsWith(prefix)) { + return double.tryParse(arg.substring(prefix.length)) ?? fallback; + } + } + return fallback; +} + +String? _readStringArg(List args, String key) { + final prefix = '$key='; + for (final arg in args) { + if (arg.startsWith(prefix)) { + return arg.substring(prefix.length); + } + } + return null; +} diff --git a/bun.lock b/bun.lock index 3fd5c9f..ac4c5e9 100644 --- a/bun.lock +++ b/bun.lock @@ -5,17 +5,14 @@ "": { "name": "texpr-docs", "dependencies": { - "@tailwindcss/postcss": "^4.1.18", "lucide-vue-next": "^0.562.0", - "tailwindcss": "^4.1.18", }, "devDependencies": { + "@iconify-json/solar": "^1.2.5", "@shikijs/vitepress-twoslash": "^3.21.0", - "@tailwindcss/vite": "^4.1.18", "@types/node": "^25.0.6", - "autoprefixer": "^10.4.23", "markdown-it-mathjax3": "^4.0.0", - "postcss": "^8.5.6", + "unocss": "^66.5.12", "vitepress": "^1.6.4", "vitepress-plugin-llms": "^1.10.0", }, @@ -58,13 +55,21 @@ "@algolia/requester-node-http": ["@algolia/requester-node-http@5.46.2", "", { "dependencies": { "@algolia/client-common": "5.46.2" } }, "sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg=="], - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="], "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], @@ -128,8 +133,12 @@ "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.65", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-v/O0UeqrDz6ASuRVE5g2Puo5aWyej4M/CxX6WYDBARgswwxK0mp3VQbGgPFEAAUU9QN02IjTgjMuO021gpWf2w=="], + "@iconify-json/solar": ["@iconify-json/solar@1.2.5", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-WMAiNwchU8zhfrySww6KQBRIBbsQ6SvgIu2yA+CHGyMima/0KQwT5MXogrZPJGoQF+1Ye3Qj6K+1CiyNn3YkoA=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], @@ -144,6 +153,10 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="], @@ -214,63 +227,79 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + "@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + "@unocss/astro": ["@unocss/astro@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/reset": "66.6.0", "@unocss/vite": "66.6.0" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" }, "optionalPeers": ["vite"] }, "sha512-HCDgZnoXv6pZGUK9N4ko7ZeBP1YTIP8IFj0Vr7pXVdv9sGR9CofoueFbclTvPQ065UHSsG+WI+JO5z9/BGd5fw=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="], + "@unocss/cli": ["@unocss/cli@66.6.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.0", "@unocss/core": "66.6.0", "@unocss/preset-wind3": "66.6.0", "@unocss/preset-wind4": "66.6.0", "@unocss/transformer-directives": "66.6.0", "cac": "^6.7.14", "chokidar": "^5.0.0", "colorette": "^2.0.20", "consola": "^3.4.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "bin": { "unocss": "bin/unocss.mjs" } }, "sha512-wtA6YBIvW2f8FISdYS8rx+B3ZGTUjw3YO3Fxz1ApUCHinYSdz8SoNWShH1I61LWbcjJ4hbjI/D9t/Dmgp6vmiQ=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + "@unocss/config": ["@unocss/config@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "unconfig": "^7.4.2" } }, "sha512-YvNk/b9jGmT1TQGDbHR+0VALnTsPLzSTzw90t09ggny9YxeF0XnsP06M5+H0EJPwpmdigBC++KEIMaK8SYDsaQ=="], - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@unocss/core": ["@unocss/core@66.6.0", "", {}, "sha512-Sxm7HmhsPIIzxbPnWembPyobuCeA5j9KxL+jIOW2c+kZiTFjHeju7vuVWX9jmAMMC+UyDuuCQ4yE+kBo3Y7SWQ=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@unocss/extractor-arbitrary-variants": ["@unocss/extractor-arbitrary-variants@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0" } }, "sha512-AsCmpbre4hQb+cKOf3gHUeYlF7guR/aCKZvw53VBk12qY5wNF7LdfIx4zWc5LFVCoRxIZlU2C7L4/Tt7AkiFMA=="], - "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@unocss/inspector": ["@unocss/inspector@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/rule-utils": "66.6.0", "colorette": "^2.0.20", "gzip-size": "^6.0.0", "sirv": "^3.0.2", "vue-flow-layout": "^0.2.0" } }, "sha512-BvdY8ah+OTmzFMb+z8RZkaF15+PWRFt9S2bOARkkRBubybX9EE1rxM07l74kO5Dj16++CS4nO15XFq39pPoBvg=="], - "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + "@unocss/postcss": ["@unocss/postcss@66.6.0", "", { "dependencies": { "@unocss/config": "66.6.0", "@unocss/core": "66.6.0", "@unocss/rule-utils": "66.6.0", "css-tree": "^3.1.0", "postcss": "^8.5.6", "tinyglobby": "^0.2.15" } }, "sha512-Tn8l8JMXJcTgzrPHSukpRBnVIt561bQCUle7jW8NXk61vYBy8p52nEBkNy5QMXybaCih5ghg2d/+nDIAl9YIpw=="], - "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + "@unocss/preset-attributify": ["@unocss/preset-attributify@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0" } }, "sha512-IfGZT9LjQkfpJaVjDtFMxOlrEoj7m1DCztRdby0FaptXChE7vdgRkYFl8HDeXMkEIlzV1YTHuIarwJ+tV+1SRQ=="], - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + "@unocss/preset-icons": ["@unocss/preset-icons@66.6.0", "", { "dependencies": { "@iconify/utils": "^3.1.0", "@unocss/core": "66.6.0", "ofetch": "^1.5.1" } }, "sha512-ObgmN9SsqU7TiClNvy+mQDyqzwOhlBXT0whXFp3iiBfSbnxUIDyf4Pr/2hwxnAWrCWCQj7xw2H0Y0sDtXq8XkA=="], - "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + "@unocss/preset-mini": ["@unocss/preset-mini@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/extractor-arbitrary-variants": "66.6.0", "@unocss/rule-utils": "66.6.0" } }, "sha512-8bQyTuMJcry/z4JTDsQokI0187/1CJIkVx9hr9eEbKf/gWti538P8ktKEmHCf8IyT0At5dfP9oLHLCUzVetdbA=="], - "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@unocss/preset-tagify": ["@unocss/preset-tagify@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0" } }, "sha512-Vy9olM61lqTDxcHbfDkIJNp9LF2m8K9I/F2J0diD+BVZgpym1QRK6+aZaeAPJuMCyQrOk87dm7VIiAPFtLOXFA=="], - "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="], + "@unocss/preset-typography": ["@unocss/preset-typography@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/rule-utils": "66.6.0" } }, "sha512-vDogO+jaatGSAoZqqa9+GJ18WbrwYzJr5UyIFUQqXJ5TUDaWzKb4Qhty2WnOtjQaf4sOMML8JFA/cydZl+Rjjw=="], - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@unocss/preset-uno": ["@unocss/preset-uno@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/preset-wind3": "66.6.0" } }, "sha512-xDppgdgFk+JNrL9jhqhrn6H9IS8P10p1HSsWOYF+9owg43zqpeNpzDMvZGwq8uxq6guyB1fu6l4YzO+bDZnwvw=="], - "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + "@unocss/preset-web-fonts": ["@unocss/preset-web-fonts@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "ofetch": "^1.5.1" } }, "sha512-Mqb8bVSAfDEnkGyAl8ZPdvaojq3YSow4lvxGCOm7nHJFIXkRKgYBgD3tmm+rvO81CUtihZd7hdw0Ay+8pYrlTw=="], - "@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="], + "@unocss/preset-wind": ["@unocss/preset-wind@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/preset-wind3": "66.6.0" } }, "sha512-6uVq3/gV1MD9ZsycrYLP6OvAS9kI1oGAIuccKKspZHW3jqwEhJmPofDD4pYwbkx4i4zSWzoLakcfp9d67Szjqg=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "@unocss/preset-wind3": ["@unocss/preset-wind3@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/preset-mini": "66.6.0", "@unocss/rule-utils": "66.6.0" } }, "sha512-7gzswF810BCSru7pF01BsMzGZbfrsWT5GV6JJLkhROS2pPjeNOpqy2VEfiavv5z09iGSIESeOFMlXr5ORuLZrg=="], + + "@unocss/preset-wind4": ["@unocss/preset-wind4@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/extractor-arbitrary-variants": "66.6.0", "@unocss/rule-utils": "66.6.0" } }, "sha512-1yyo9fmB+r5C92kSHU7lIaqGJdvz5UQyYZxYDxSmWLAUzWEK5HBRj6OkSF6rUnz+Yd4JvgOgACZNOShVOezPlA=="], + + "@unocss/reset": ["@unocss/reset@66.6.0", "", {}, "sha512-OQK5F7Dzx0wWDSPTYEz7NRP9pekufSAkjxfKOyKokiXOUzVTg8Yx8sOvCsA3Oi0Rx5WlsO2LN+MOBekpkrttXg=="], + + "@unocss/rule-utils": ["@unocss/rule-utils@66.6.0", "", { "dependencies": { "@unocss/core": "^66.6.0", "magic-string": "^0.30.21" } }, "sha512-v16l6p5VrefDx8P/gzWnp0p6/hCA0vZ4UMUN6SxHGVE6V+IBpX6I6Du3Egk9TdkhZ7o+Pe1NHxksHcjT0V/tww=="], + + "@unocss/transformer-attributify-jsx": ["@unocss/transformer-attributify-jsx@66.6.0", "", { "dependencies": { "@babel/parser": "7.27.7", "@babel/traverse": "7.27.7", "@unocss/core": "66.6.0" } }, "sha512-fzjLVlhYO8JdHzIusRKAva5ZOnA4deOVYuiM6HVpbX7P19479TVHZgeSV+AG0BWLhmIJ2cer+n3/CIO5nodT6g=="], + + "@unocss/transformer-compile-class": ["@unocss/transformer-compile-class@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0" } }, "sha512-OkwdbIfsbs8dtHIfBaoya/SPHZUJeogvJl2BpJb4/3nY/tWBZB/+i2vPMAML3D9aQYZAuC7uqcTRGNbuvyyy+w=="], + + "@unocss/transformer-directives": ["@unocss/transformer-directives@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0", "@unocss/rule-utils": "66.6.0", "css-tree": "^3.1.0" } }, "sha512-2Z4FFjJI/bH6kKGuuuO0DpFEVSFOhFnGLTFK8y9BGz0cIOQOIuEKTemM7QLqPuyRSORBO1RKvcKvA3DV0n1X7g=="], + + "@unocss/transformer-variant-group": ["@unocss/transformer-variant-group@66.6.0", "", { "dependencies": { "@unocss/core": "66.6.0" } }, "sha512-kWYYcrn8ZFKLVCU6kB8yaQY9iYgx3/XhPb9c0XZZ5QzWjoGffrl6XLUk8XrjR/yxC3qwHg/WizzsmsQ2OXF6Qg=="], + + "@unocss/vite": ["@unocss/vite@66.6.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.0", "@unocss/core": "66.6.0", "@unocss/inspector": "66.6.0", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-SC0/rX0xSjdu8Jaj98XztHOuvXHWDVk0YaHKRAQks2Oj3yyqAOrhzhDUH0zzFaQWf5bsKVYK40H+h4rMk9vm5Q=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="], @@ -314,6 +343,8 @@ "@xmldom/xmldom": ["@xmldom/xmldom@0.9.8", "", {}, "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "algoliasearch": ["algoliasearch@5.46.2", "", { "dependencies": { "@algolia/abtesting": "1.12.2", "@algolia/client-abtesting": "5.46.2", "@algolia/client-analytics": "5.46.2", "@algolia/client-common": "5.46.2", "@algolia/client-insights": "5.46.2", "@algolia/client-personalization": "5.46.2", "@algolia/client-query-suggestions": "5.46.2", "@algolia/client-search": "5.46.2", "@algolia/ingestion": "1.46.2", "@algolia/monitoring": "1.46.2", "@algolia/recommend": "5.46.2", "@algolia/requester-browser-xhr": "5.46.2", "@algolia/requester-fetch": "5.46.2", "@algolia/requester-node-http": "5.46.2" } }, "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q=="], "alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="], @@ -326,19 +357,13 @@ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="], - "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="], - "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -352,20 +377,30 @@ "cheerio-select": ["cheerio-select@1.6.0", "", { "dependencies": { "css-select": "^4.3.0", "css-what": "^6.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.3.1", "domutils": "^2.8.0" } }, "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g=="], + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], "css-select": ["css-select@4.3.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" } }, "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ=="], + "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -374,8 +409,12 @@ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -388,14 +427,12 @@ "domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], - "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + "duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], - "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], - "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], @@ -418,22 +455,24 @@ "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "floating-vue": ["floating-vue@5.2.2", "", { "dependencies": { "@floating-ui/dom": "~1.1.1", "vue-resize": "^2.0.0-alpha.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.0", "vue": "^3.2.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg=="], "focus-trap": ["focus-trap@7.7.1", "", { "dependencies": { "tabbable": "^6.4.0" } }, "sha512-Pkp8m55GjxBLnhBoT6OXdMvfRr4TjMAKLvFM566zlIryq5plbhaTmLAJWTGR0EkRwLjEte1lCOG9MxF1ipJrOg=="], "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + "gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="], + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], @@ -454,8 +493,12 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "juice": ["juice@8.1.0", "", { "dependencies": { "cheerio": "1.0.0-rc.10", "commander": "^6.1.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^6.0.1" }, "bin": { "juice": "bin/juice" } }, "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], @@ -532,6 +575,8 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], "mensch": ["mensch@0.3.4", "", {}, "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="], @@ -594,6 +639,10 @@ "mj-context-menu": ["mj-context-menu@0.6.1", "", {}, "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="], + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], @@ -602,16 +651,20 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="], @@ -620,15 +673,17 @@ "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], - "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "preact": ["preact@10.28.1", "", {}, "sha512-u1/ixq/lVQI0CakKNvLDEcW5zfCjUQfZdK9qqWuIJtsezuyG6pk9TWj75GMuI/EzRSZB/VAE43sNWWZfiy8psw=="], @@ -638,6 +693,10 @@ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], @@ -664,6 +723,8 @@ "shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + "slick": ["slick@1.12.2", "", {}, "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -688,12 +749,14 @@ "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], - "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tokenx": ["tokenx@1.2.1", "", {}, "sha512-lVhFIhR2qh3uUyUA8Ype+HGzcokUJbHmRSN1TJKOe4Y26HkawQuLiGkUCkR5LD9dx+Rtp+njrwzPL8AHHYQSYA=="], + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], @@ -712,6 +775,12 @@ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="], + + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], @@ -728,7 +797,9 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "unocss": ["unocss@66.6.0", "", { "dependencies": { "@unocss/astro": "66.6.0", "@unocss/cli": "66.6.0", "@unocss/core": "66.6.0", "@unocss/postcss": "66.6.0", "@unocss/preset-attributify": "66.6.0", "@unocss/preset-icons": "66.6.0", "@unocss/preset-mini": "66.6.0", "@unocss/preset-tagify": "66.6.0", "@unocss/preset-typography": "66.6.0", "@unocss/preset-uno": "66.6.0", "@unocss/preset-web-fonts": "66.6.0", "@unocss/preset-wind": "66.6.0", "@unocss/preset-wind3": "66.6.0", "@unocss/preset-wind4": "66.6.0", "@unocss/transformer-attributify-jsx": "66.6.0", "@unocss/transformer-compile-class": "66.6.0", "@unocss/transformer-directives": "66.6.0", "@unocss/transformer-variant-group": "66.6.0", "@unocss/vite": "66.6.0" }, "peerDependencies": { "@unocss/webpack": "66.6.0", "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" }, "optionalPeers": ["@unocss/webpack", "vite"] }, "sha512-B5QsMJzFKeTHPzF5Ehr8tSMuhxzbCR9n+XP0GyhK9/2jTcBdI0/T+rCDDr9m6vUz+lku/coCVz7VAQ2BRAbZJw=="], + + "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], "valid-data-url": ["valid-data-url@3.0.1", "", {}, "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA=="], @@ -744,6 +815,8 @@ "vue": ["vue@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", "@vue/runtime-dom": "3.5.26", "@vue/server-renderer": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA=="], + "vue-flow-layout": ["vue-flow-layout@0.2.0", "", {}, "sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q=="], + "vue-resize": ["vue-resize@2.0.0-alpha.1", "", { "peerDependencies": { "vue": "^3.0.0" } }, "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg=="], "web-resource-inliner": ["web-resource-inliner@6.0.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", "htmlparser2": "^5.0.0", "mime": "^2.4.6", "node-fetch": "^2.6.0", "valid-data-url": "^3.0.0" } }, "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A=="], @@ -764,6 +837,16 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@babel/generator/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/generator/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/template/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/template/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "@shikijs/core/@shikijs/engine-javascript": ["@shikijs/engine-javascript@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^3.1.0" } }, "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w=="], "@shikijs/core/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw=="], @@ -780,19 +863,13 @@ "@shikijs/twoslash/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@vue/compiler-core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@vue/compiler-core/entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - "@vue/compiler-core/entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], + "@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], "dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], diff --git a/doc/guide/index.md b/doc/guide/index.md index b0c422d..1416f64 100644 --- a/doc/guide/index.md +++ b/doc/guide/index.md @@ -81,4 +81,5 @@ print(slope.asNumeric()); // 12.0 - [Core Concepts](/guide/concepts) – Understand key ideas - [Boolean Logic](/guide/logic) – Logic operators and comparisons - [Piecewise Functions](/guide/piecewise) – Conditional expressions and cases +- [Deprecation Migration](/guide/migration-validate) – Replace `validate()` / `isValid()` with `parse()` - [API Reference](/reference/) – Complete method documentation diff --git a/doc/guide/migration-validate.md b/doc/guide/migration-validate.md new file mode 100644 index 0000000..907dcbd --- /dev/null +++ b/doc/guide/migration-validate.md @@ -0,0 +1,79 @@ +# Migration Guide: `isValid()` / `validate()` Deprecation + +`Texpr.isValid()` and `Texpr.validate()` are deprecated and will be removed in `1.0.0`. + +Use `parse()` with `try/catch` instead. + +## Why this change? + +- Single parsing entry point reduces API surface. +- Parse exceptions already provide structured diagnostics (`message`, `position`, `suggestion`). +- Encourages a single error-handling model across parsing and evaluation. + +## Migration Patterns + +### 1) Replace `isValid()` + +Before: + +```dart +final ok = texpr.isValid(input); +if (!ok) { + return; +} +``` + +After: + +```dart +try { + texpr.parse(input); + // valid +} on TexprException { + // invalid +} +``` + +### 2) Replace `validate()` with rich error handling + +Before: + +```dart +final result = texpr.validate(input); +if (!result.isValid) { + print(result.errorMessage); + print(result.position); + print(result.suggestion); +} +``` + +After: + +```dart +try { + texpr.parse(input); +} on TexprException catch (e) { + print(e.message); + print(e.position); + print(e.suggestion); +} +``` + +### 3) Keep parse result for reuse + +```dart +Expression ast; +try { + ast = texpr.parse(input); +} on TexprException catch (e) { + // report validation-style diagnostics + rethrow; +} + +final value = texpr.evaluateParsed(ast, {'x': 2.0}); +``` + +## Timeline + +- **Now (0.1.x):** methods are available but deprecated. +- **1.0.0:** methods are removed. diff --git a/doc/how-it-works/performance.md b/doc/how-it-works/performance.md index 6306ca1..c555f7e 100644 --- a/doc/how-it-works/performance.md +++ b/doc/how-it-works/performance.md @@ -197,10 +197,13 @@ final evaluator = Texpr( } ``` -2. **Validate before evaluating** +2. **Parse-check before evaluating** ```dart - final validation = texpr.validate(input); - if (!validation.isValid) return handleError(validation); + try { + texpr.parse(input); + } on TexprException catch (e) { + return handleError(e); + } ``` 3. **Consider timeouts** diff --git a/example/features/validation_demo.dart b/example/features/validation_demo.dart index 6252318..4395249 100644 --- a/example/features/validation_demo.dart +++ b/example/features/validation_demo.dart @@ -6,9 +6,9 @@ void main() { final evaluator = Texpr(); - // Example 1: Basic isValid() usage - print('1. Basic Validation with isValid()'); - print(' ---------------------------------'); + // Example 1: Basic parse() + try/catch usage + print('1. Basic Validation with parse()'); + print(' --------------------------------'); _checkValid(evaluator, '2 + 3'); _checkValid(evaluator, r'\sin{0}'); _checkValid(evaluator, r'x^{2} + 1'); @@ -16,9 +16,9 @@ void main() { _checkValid(evaluator, r'\unknown{5}'); // Invalid: unknown command print(''); - // Example 2: Detailed validation with validate() - print('2. Detailed Validation with validate()'); - print(' ------------------------------------'); + // Example 2: Detailed validation with parse() errors + print('2. Detailed Validation with parse() errors'); + print(' -----------------------------------------'); _detailedValidation(evaluator, r'\frac{1}{2}'); // Valid _detailedValidation(evaluator, r'\log_{2}{8}'); // Valid _detailedValidation(evaluator, r'\sin{'); // Invalid @@ -57,14 +57,14 @@ void main() { ]; for (final input in userInputs) { - final result = evaluator.validate(input); - if (result.isValid) { + try { + evaluator.parse(input); print(' ✓ "$input" - Valid'); - } else { + } on TexprException catch (e) { print(' ✗ "$input"'); - print(' Error: ${result.errorMessage}'); - if (result.suggestion != null) { - print(' Suggestion: ${result.suggestion}'); + print(' Error: ${e.message}'); + if (e.suggestion != null) { + print(' Suggestion: ${e.suggestion}'); } } } @@ -77,53 +77,65 @@ void main() { final evalNoImplicit = Texpr(allowImplicitMultiplication: false); print(' With implicit multiplication enabled:'); - print(' 2x is valid: ${evalWithImplicit.isValid('2x')}'); - print(' 3xy is valid: ${evalWithImplicit.isValid('3xy')}'); + print(' 2x is valid: ${_isParseValid(evalWithImplicit, '2x')}'); + print(' 3xy is valid: ${_isParseValid(evalWithImplicit, '3xy')}'); print(' With implicit multiplication disabled:'); - print(' 2x is valid: ${evalNoImplicit.isValid('2x')}'); - final timesXValid = evalNoImplicit.isValid(r'2 \times x'); + print(' 2x is valid: ${_isParseValid(evalNoImplicit, '2x')}'); + final timesXValid = _isParseValid(evalNoImplicit, r'2 \times x'); print(' 2 \\times x is valid: $timesXValid'); print(''); - // Example 7: Using ValidationResult properties - print('7. ValidationResult Properties'); - print(' ---------------------------'); - final invalidResult = evaluator.validate(r'\sin{'); + // Example 7: TexprException properties + print('7. TexprException Properties'); + print(' -------------------------'); print(' Expression: r\'\\sin{\''); - print(' isValid: ${invalidResult.isValid}'); - print(' errorMessage: ${invalidResult.errorMessage}'); - print(' position: ${invalidResult.position}'); - print(' suggestion: ${invalidResult.suggestion}'); - print(' exceptionType: ${invalidResult.exceptionType}'); + try { + evaluator.parse(r'\sin{'); + print(' isValid: true'); + } on TexprException catch (e) { + print(' isValid: false'); + print(' errorMessage: ${e.message}'); + print(' position: ${e.position}'); + print(' suggestion: ${e.suggestion}'); + print(' exceptionType: ${e.runtimeType}'); + } print(''); print('=== Demo Complete ==='); } -/// Helper function to demonstrate isValid() +bool _isParseValid(Texpr evaluator, String expression) { + try { + evaluator.parse(expression); + return true; + } on TexprException { + return false; + } +} + +/// Helper function to demonstrate parse() validation void _checkValid(Texpr evaluator, String expression) { - final isValid = evaluator.isValid(expression); + final isValid = _isParseValid(evaluator, expression); final status = isValid ? '✓' : '✗'; print(' $status "$expression" - ${isValid ? 'Valid' : 'Invalid'}'); } -/// Helper function to demonstrate validate() +/// Helper function to demonstrate parse() error reporting void _detailedValidation(Texpr evaluator, String expression) { - final result = evaluator.validate(expression); - - if (result.isValid) { + try { + evaluator.parse(expression); print(' ✓ "$expression"'); print(' Status: Valid'); - } else { + } on TexprException catch (e) { print(' ✗ "$expression"'); print(' Status: Invalid'); - print(' Error: ${result.errorMessage}'); - if (result.position != null) { - print(' Position: ${result.position}'); + print(' Error: ${e.message}'); + if (e.position != null) { + print(' Position: ${e.position}'); } - if (result.suggestion != null) { - print(' Suggestion: ${result.suggestion}'); + if (e.suggestion != null) { + print(' Suggestion: ${e.suggestion}'); } } print(''); diff --git a/example/misc/error_messages_demo.dart b/example/misc/error_messages_demo.dart index f68798c..23cbc00 100644 --- a/example/misc/error_messages_demo.dart +++ b/example/misc/error_messages_demo.dart @@ -79,16 +79,18 @@ void main() { } print("\n${'-' * 60}\n"); - // Example 8: Validation API with error details - print('Example 8: Using Validation API'); + // Example 8: parse() with error details + print('Example 8: Using parse() errors'); print('Expression: \\sin{x\n'); - final result = evaluator.validate(r'\sin{x'); - if (!result.isValid) { - print('Valid: ${result.isValid}'); - print('Error: ${result.errorMessage}'); - print('Position: ${result.position}'); - print('Suggestion: ${result.suggestion}'); - print('Exception Type: ${result.exceptionType}'); + try { + evaluator.parse(r'\sin{x'); + print('Valid: true'); + } on TexprException catch (e) { + print('Valid: false'); + print('Error: ${e.message}'); + print('Position: ${e.position}'); + print('Suggestion: ${e.suggestion}'); + print('Exception Type: ${e.runtimeType}'); } print("\n${'-' * 60}\n"); diff --git a/lib/src/ast/evaluability.dart b/lib/src/ast/evaluability.dart index d94376c..915abb3 100644 --- a/lib/src/ast/evaluability.dart +++ b/lib/src/ast/evaluability.dart @@ -8,6 +8,22 @@ import 'matrix.dart'; import 'operations.dart'; import 'visitor.dart'; +const Set _knownConstants = { + 'pi', + 'π', + 'e', + 'i', + 'phi', + 'φ', + 'tau', + 'τ', + 'infty', + '∞', +}; + +final Expando _compileTimeEvaluability = + Expando('compileTimeEvaluability'); + /// Describes whether an expression can be numerically evaluated. /// /// This enum makes the distinction between "can parse" and "can evaluate" @@ -40,6 +56,298 @@ enum Evaluability { unevaluable, } +/// Compile-time evaluability metadata attached to AST nodes. +/// +/// [evaluability] captures structural evaluability at parse time: +/// - [Evaluability.symbolic] for symbolic-only forms +/// - [Evaluability.numeric] when no unresolved variables remain +/// - [Evaluability.unevaluable] when free variables must be supplied +/// +/// [freeVariables] stores unresolved symbols required for numeric evaluation. +class EvaluabilityInfo { + final Evaluability evaluability; + final Set freeVariables; + + const EvaluabilityInfo(this.evaluability, [Set freeVariables = const {}]) + : freeVariables = freeVariables; + + Evaluability resolve([Set? knownVariables]) { + if (evaluability == Evaluability.symbolic) { + return Evaluability.symbolic; + } + if (freeVariables.isEmpty) { + return Evaluability.numeric; + } + if (knownVariables == null) { + return Evaluability.unevaluable; + } + final hasAll = freeVariables.every(knownVariables.contains); + return hasAll ? Evaluability.numeric : Evaluability.unevaluable; + } +} + +/// Annotates an AST with compile-time evaluability metadata. +/// +/// This runs once after parsing and stores node-level metadata in an [Expando], +/// avoiding API-breaking changes to expression node classes. +void annotateCompileTimeEvaluability(Expression expression) { + const visitor = CompileTimeEvaluabilityVisitor(); + expression.accept(visitor, {}); +} + +/// Visitor that computes compile-time evaluability and free variables. +/// +/// The result is attached to every visited node. +class CompileTimeEvaluabilityVisitor + implements ExpressionVisitor> { + const CompileTimeEvaluabilityVisitor(); + + EvaluabilityInfo _record(Expression node, EvaluabilityInfo info) { + final frozen = Set.unmodifiable(info.freeVariables); + final value = EvaluabilityInfo(info.evaluability, frozen); + _compileTimeEvaluability[node] = value; + return value; + } + + EvaluabilityInfo _combine(Expression node, List children) { + final freeVariables = {}; + for (final child in children) { + freeVariables.addAll(child.freeVariables); + } + + final evaluability = children.any((e) => e.evaluability == Evaluability.symbolic) + ? Evaluability.symbolic + : children.any((e) => e.evaluability == Evaluability.unevaluable) + ? Evaluability.unevaluable + : Evaluability.numeric; + + return _record(node, EvaluabilityInfo(evaluability, freeVariables)); + } + + @override + EvaluabilityInfo visitNumberLiteral(NumberLiteral node, Set? context) { + return _record(node, const EvaluabilityInfo(Evaluability.numeric)); + } + + @override + EvaluabilityInfo visitVariable(Variable node, Set? context) { + if ((context?.contains(node.name) ?? false) || + _knownConstants.contains(node.name)) { + return _record(node, const EvaluabilityInfo(Evaluability.numeric)); + } + return _record(node, EvaluabilityInfo(Evaluability.unevaluable, {node.name})); + } + + @override + EvaluabilityInfo visitBinaryOp(BinaryOp node, Set? context) { + final left = node.left.accept(this, context); + final right = node.right.accept(this, context); + return _combine(node, [left, right]); + } + + @override + EvaluabilityInfo visitUnaryOp(UnaryOp node, Set? context) { + final operand = node.operand.accept(this, context); + return _combine(node, [operand]); + } + + @override + EvaluabilityInfo visitFunctionCall(FunctionCall node, Set? context) { + final children = []; + for (final arg in node.args) { + children.add(arg.accept(this, context)); + } + if (node.base != null) { + children.add(node.base!.accept(this, context)); + } + if (node.optionalParam != null) { + children.add(node.optionalParam!.accept(this, context)); + } + return _combine(node, children); + } + + @override + EvaluabilityInfo visitAbsoluteValue(AbsoluteValue node, Set? context) { + final argument = node.argument.accept(this, context); + return _combine(node, [argument]); + } + + @override + EvaluabilityInfo visitLimitExpr(LimitExpr node, Set? context) { + final target = node.target.accept(this, context); + final extendedContext = {...?context, node.variable}; + final body = node.body.accept(this, extendedContext); + return _combine(node, [target, body]); + } + + @override + EvaluabilityInfo visitSumExpr(SumExpr node, Set? context) { + final start = node.start.accept(this, context); + final end = node.end.accept(this, context); + final extendedContext = {...?context, node.variable}; + final body = node.body.accept(this, extendedContext); + return _combine(node, [start, end, body]); + } + + @override + EvaluabilityInfo visitProductExpr(ProductExpr node, Set? context) { + final start = node.start.accept(this, context); + final end = node.end.accept(this, context); + final extendedContext = {...?context, node.variable}; + final body = node.body.accept(this, extendedContext); + return _combine(node, [start, end, body]); + } + + @override + EvaluabilityInfo visitIntegralExpr(IntegralExpr node, Set? context) { + final extendedContext = {...?context, node.variable}; + + if (node.lower != null && node.upper != null) { + final lower = node.lower!.accept(this, context); + final upper = node.upper!.accept(this, context); + final body = node.body.accept(this, extendedContext); + return _combine(node, [lower, upper, body]); + } + + final body = node.body.accept(this, extendedContext); + return _record(node, + EvaluabilityInfo(Evaluability.symbolic, Set.from(body.freeVariables))); + } + + @override + EvaluabilityInfo visitMultiIntegralExpr( + MultiIntegralExpr node, Set? context) { + final extendedContext = {...?context, ...node.variables}; + final body = node.body.accept(this, extendedContext); + return _record(node, + EvaluabilityInfo(Evaluability.symbolic, Set.from(body.freeVariables))); + } + + @override + EvaluabilityInfo visitDerivativeExpr(DerivativeExpr node, Set? context) { + final extendedContext = {...?context, node.variable}; + final body = node.body.accept(this, extendedContext); + return _combine(node, [body]); + } + + @override + EvaluabilityInfo visitPartialDerivativeExpr( + PartialDerivativeExpr node, Set? context) { + final extendedContext = {...?context, node.variable}; + final body = node.body.accept(this, extendedContext); + if (node.body is Variable) { + return _record(node, + EvaluabilityInfo(Evaluability.symbolic, Set.from(body.freeVariables))); + } + return _combine(node, [body]); + } + + @override + EvaluabilityInfo visitBinomExpr(BinomExpr node, Set? context) { + final n = node.n.accept(this, context); + final k = node.k.accept(this, context); + return _combine(node, [n, k]); + } + + @override + EvaluabilityInfo visitGradientExpr(GradientExpr node, Set? context) { + final body = node.body.accept(this, context); + if (node.body is Variable) { + return _record(node, + EvaluabilityInfo(Evaluability.symbolic, Set.from(body.freeVariables))); + } + return _combine(node, [body]); + } + + @override + EvaluabilityInfo visitComparison(Comparison node, Set? context) { + final left = node.left.accept(this, context); + final right = node.right.accept(this, context); + return _combine(node, [left, right]); + } + + @override + EvaluabilityInfo visitChainedComparison( + ChainedComparison node, Set? context) { + final children = + node.expressions.map((expression) => expression.accept(this, context)).toList(); + return _combine(node, children); + } + + @override + EvaluabilityInfo visitConditionalExpr( + ConditionalExpr node, Set? context) { + final expression = node.expression.accept(this, context); + final condition = node.condition.accept(this, context); + return _combine(node, [expression, condition]); + } + + @override + EvaluabilityInfo visitPiecewise(PiecewiseExpr node, Set? context) { + final children = []; + for (final currentCase in node.cases) { + children.add(currentCase.expression.accept(this, context)); + if (currentCase.condition != null) { + children.add(currentCase.condition!.accept(this, context)); + } + } + return _combine(node, children); + } + + @override + EvaluabilityInfo visitBooleanBinaryExpr( + BooleanBinaryExpr node, Set? context) { + final left = node.left.accept(this, context); + final right = node.right.accept(this, context); + return _combine(node, [left, right]); + } + + @override + EvaluabilityInfo visitBooleanUnaryExpr( + BooleanUnaryExpr node, Set? context) { + final operand = node.operand.accept(this, context); + return _combine(node, [operand]); + } + + @override + EvaluabilityInfo visitMatrixExpr(MatrixExpr node, Set? context) { + final children = []; + for (final row in node.rows) { + for (final cell in row) { + children.add(cell.accept(this, context)); + } + } + return _combine(node, children); + } + + @override + EvaluabilityInfo visitVectorExpr(VectorExpr node, Set? context) { + final children = node.components.map((component) => component.accept(this, context)).toList(); + return _combine(node, children); + } + + @override + EvaluabilityInfo visitIntervalExpr(IntervalExpr node, Set? context) { + final lower = node.lower.accept(this, context); + final upper = node.upper.accept(this, context); + return _combine(node, [lower, upper]); + } + + @override + EvaluabilityInfo visitAssignmentExpr(AssignmentExpr node, Set? context) { + final value = node.value.accept(this, context); + return _combine(node, [value]); + } + + @override + EvaluabilityInfo visitFunctionDefinitionExpr( + FunctionDefinitionExpr node, Set? context) { + final extendedContext = {...?context, ...node.parameters}; + final body = node.body.accept(this, extendedContext); + return _combine(node, [body]); + } +} + /// Visitor that determines the evaluability of an expression. /// /// This visitor traverses the AST and computes whether each node can be @@ -78,19 +386,7 @@ class EvaluabilityVisitor return Evaluability.numeric; } // Check for known constants - const knownConstants = { - 'pi', - 'π', - 'e', - 'i', - 'phi', - 'φ', - 'tau', - 'τ', - 'infty', - '∞', - }; - if (knownConstants.contains(node.name)) { + if (_knownConstants.contains(node.name)) { return Evaluability.numeric; } return Evaluability.unevaluable; @@ -305,6 +601,13 @@ class EvaluabilityVisitor /// Extension to add evaluability checking to Expression. extension ExpressionEvaluability on Expression { + /// Gets compile-time evaluability metadata attached during parsing. + /// + /// Returns `null` for expression trees that were not annotated + /// (for example, manually constructed ASTs). + EvaluabilityInfo? get compileTimeEvaluabilityInfo => + _compileTimeEvaluability[this]; + /// Determines the evaluability of this expression. /// /// [knownVariables] is an optional set of variable names that are defined @@ -322,6 +625,11 @@ extension ExpressionEvaluability on Expression { /// expr.getEvaluability({'x'}); // Evaluability.numeric /// ``` Evaluability getEvaluability([Set? knownVariables]) { + final compileTimeInfo = _compileTimeEvaluability[this]; + if (compileTimeInfo != null) { + return compileTimeInfo.resolve(knownVariables); + } + const visitor = EvaluabilityVisitor(); return accept(visitor, knownVariables); } diff --git a/lib/src/cache/cache_keys.dart b/lib/src/cache/cache_keys.dart index 75cc29d..b35a048 100644 --- a/lib/src/cache/cache_keys.dart +++ b/lib/src/cache/cache_keys.dart @@ -12,6 +12,7 @@ class EvaluationCacheKey { final int _exprIdentity; final int _varsIdentity; final bool _isIdentityBased; + final List<_NormalizedVariableEntry>? _structuralEntries; /// Creates an identity-based cache key. /// @@ -25,7 +26,8 @@ class EvaluationCacheKey { : _exprIdentity = identityHashCode(expr), _varsIdentity = identityHashCode(vars), _hash = identityHashCode(expr) ^ identityHashCode(vars), - _isIdentityBased = true; + _isIdentityBased = true, + _structuralEntries = null; /// Creates a structural cache key using value equality. /// @@ -40,22 +42,61 @@ class EvaluationCacheKey { EvaluationCacheKey(Expression expression, Map variables) : _exprIdentity = expression.hashCode, _varsIdentity = 0, // Not used for structural comparison - _hash = _computeStructuralHash(expression, variables), + _structuralEntries = _normalizedEntries(variables), + _hash = _computeStructuralHash(expression, _normalizedEntries(variables)), _isIdentityBased = false; - static int _computeStructuralHash( - Expression expression, Map variables) { - // Create a stable hash from the expression and sorted variable entries - final sortedEntries = variables.entries.toList() + static List<_NormalizedVariableEntry> _normalizedEntries( + Map variables) { + final entries = variables.entries + .map((entry) => + _NormalizedVariableEntry(entry.key, _normalizeDouble(entry.value))) + .toList() ..sort((a, b) => a.key.compareTo(b.key)); + return List<_NormalizedVariableEntry>.unmodifiable(entries); + } + static int _computeStructuralHash( + Expression expression, List<_NormalizedVariableEntry> entries) { + // Create a stable hash from expression and sorted normalized entries. var hash = expression.hashCode; - for (final entry in sortedEntries) { - hash = hash ^ entry.key.hashCode ^ entry.value.hashCode; + for (final entry in entries) { + hash = Object.hash(hash, entry.key, _normalizedDoubleHash(entry.value)); } return hash; } + static double _normalizeDouble(double value) { + if (value.isNaN) { + return double.nan; + } + // Canonicalize signed zero to avoid -0.0/+0.0 cache key divergence. + if (value == 0.0) { + return 0.0; + } + return value; + } + + static int _normalizedDoubleHash(double value) { + if (value.isNaN) { + return 0x7ff80000; + } + if (value == 0.0) { + return 0; + } + return value.hashCode; + } + + static bool _normalizedDoubleEquals(double left, double right) { + if (left.isNaN && right.isNaN) { + return true; + } + if (left == 0.0 && right == 0.0) { + return true; + } + return left == right; + } + @override int get hashCode => _hash; @@ -71,8 +112,31 @@ class EvaluationCacheKey { _varsIdentity == other._varsIdentity; } - // Fallback for mixed or structural keys (deprecated path) - return _exprIdentity == other._exprIdentity; + if (_isIdentityBased != other._isIdentityBased) { + return false; + } + + final leftEntries = _structuralEntries; + final rightEntries = other._structuralEntries; + if (leftEntries == null || rightEntries == null) { + return false; + } + + if (_exprIdentity != other._exprIdentity || + leftEntries.length != rightEntries.length) { + return false; + } + + for (var index = 0; index < leftEntries.length; index++) { + final left = leftEntries[index]; + final right = rightEntries[index]; + if (left.key != right.key || + !_normalizedDoubleEquals(left.value, right.value)) { + return false; + } + } + + return true; } @override @@ -80,6 +144,13 @@ class EvaluationCacheKey { 'EvaluationCacheKey(hash: $_hash, identity: $_isIdentityBased)'; } +class _NormalizedVariableEntry { + final String key; + final double value; + + const _NormalizedVariableEntry(this.key, this.value); +} + /// A cache key for differentiation results. /// /// Combines expression, variable, and order to uniquely identify a derivative. diff --git a/lib/src/parser/parser.dart b/lib/src/parser/parser.dart index 79c2b6a..a8b5395 100644 --- a/lib/src/parser/parser.dart +++ b/lib/src/parser/parser.dart @@ -27,7 +27,9 @@ class Parser extends BaseParser consume(TokenType.variable, 'Expected variable name after let').value; consume(TokenType.equals, 'Expected = after variable name'); final value = parseExpression(); - return AssignmentExpr(variable, value); + final assignment = AssignmentExpr(variable, value); + annotateCompileTimeEvaluability(assignment); + return assignment; } // 2. Check for function definition: f(x, y) = ... @@ -86,7 +88,9 @@ class Parser extends BaseParser consume(TokenType.equals, 'Expected ='); final body = parseExpression(); - return FunctionDefinitionExpr(name, params, body); + final functionDefinition = FunctionDefinitionExpr(name, params, body); + annotateCompileTimeEvaluability(functionDefinition); + return functionDefinition; } } @@ -113,6 +117,7 @@ class Parser extends BaseParser } } + annotateCompileTimeEvaluability(expr); return expr; } } diff --git a/lib/src/visitors/json_ast_visitor.dart b/lib/src/visitors/json_ast_visitor.dart index 235f3f1..6b3d0d3 100644 --- a/lib/src/visitors/json_ast_visitor.dart +++ b/lib/src/visitors/json_ast_visitor.dart @@ -6,6 +6,7 @@ import '../ast/calculus.dart'; import '../ast/logic.dart'; import '../ast/matrix.dart'; import '../ast/environment.dart'; +import '../ast/evaluability.dart'; import '../ast/visitor.dart'; /// Visitor that converts an AST to a JSON-serializable Map. @@ -40,7 +41,30 @@ import '../ast/visitor.dart'; /// } /// ``` class JsonAstVisitor implements ExpressionVisitor, void> { - const JsonAstVisitor(); + final bool includeEvaluability; + + const JsonAstVisitor({this.includeEvaluability = false}); + + Map _withEvaluability( + Expression node, Map json) { + if (!includeEvaluability) { + return json; + } + + final info = node.compileTimeEvaluabilityInfo; + if (info == null) { + return json; + } + + final freeVariables = info.freeVariables.toList()..sort(); + return { + ...json, + 'evaluability': { + 'kind': info.evaluability.name, + 'freeVariables': freeVariables, + }, + }; + } /// Converts an expression to JSON representation. Map convert(Expression expr) { @@ -49,190 +73,190 @@ class JsonAstVisitor implements ExpressionVisitor, void> { @override Map visitNumberLiteral(NumberLiteral node, void context) { - return { + return _withEvaluability(node, { 'type': 'NumberLiteral', 'value': node.value, - }; + }); } @override Map visitVariable(Variable node, void context) { - return { + return _withEvaluability(node, { 'type': 'Variable', 'name': node.name, - }; + }); } @override Map visitBinaryOp(BinaryOp node, void context) { - return { + return _withEvaluability(node, { 'type': 'BinaryOp', 'operator': node.operator.name, 'left': node.left.accept(this, context), 'right': node.right.accept(this, context), if (node.sourceToken != null) 'sourceToken': node.sourceToken, - }; + }); } @override Map visitUnaryOp(UnaryOp node, void context) { - return { + return _withEvaluability(node, { 'type': 'UnaryOp', 'operator': node.operator.name, 'operand': node.operand.accept(this, context), - }; + }); } @override Map visitAbsoluteValue(AbsoluteValue node, void context) { - return { + return _withEvaluability(node, { 'type': 'AbsoluteValue', 'argument': node.argument.accept(this, context), - }; + }); } @override Map visitFunctionCall(FunctionCall node, void context) { - return { + return _withEvaluability(node, { 'type': 'FunctionCall', 'name': node.name, 'args': node.args.map((a) => a.accept(this, context)).toList(), if (node.base != null) 'base': node.base!.accept(this, context), if (node.optionalParam != null) 'optionalParam': node.optionalParam!.accept(this, context), - }; + }); } @override Map visitLimitExpr(LimitExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'LimitExpr', 'variable': node.variable, 'target': node.target.accept(this, context), 'body': node.body.accept(this, context), - }; + }); } @override Map visitSumExpr(SumExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'SumExpr', 'variable': node.variable, 'start': node.start.accept(this, context), 'end': node.end.accept(this, context), 'body': node.body.accept(this, context), - }; + }); } @override Map visitProductExpr(ProductExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'ProductExpr', 'variable': node.variable, 'start': node.start.accept(this, context), 'end': node.end.accept(this, context), 'body': node.body.accept(this, context), - }; + }); } @override Map visitIntegralExpr(IntegralExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'IntegralExpr', 'variable': node.variable, 'body': node.body.accept(this, context), if (node.lower != null) 'lower': node.lower!.accept(this, context), if (node.upper != null) 'upper': node.upper!.accept(this, context), 'isClosed': node.isClosed, - }; + }); } @override Map visitMultiIntegralExpr( MultiIntegralExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'MultiIntegralExpr', 'order': node.order, 'variables': node.variables, 'body': node.body.accept(this, context), if (node.lower != null) 'lower': node.lower!.accept(this, context), if (node.upper != null) 'upper': node.upper!.accept(this, context), - }; + }); } @override Map visitDerivativeExpr(DerivativeExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'DerivativeExpr', 'variable': node.variable, 'order': node.order, 'body': node.body.accept(this, context), - }; + }); } @override Map visitPartialDerivativeExpr( PartialDerivativeExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'PartialDerivativeExpr', 'variable': node.variable, 'order': node.order, 'body': node.body.accept(this, context), - }; + }); } @override Map visitBinomExpr(BinomExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'BinomExpr', 'n': node.n.accept(this, context), 'k': node.k.accept(this, context), - }; + }); } @override Map visitGradientExpr(GradientExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'GradientExpr', 'body': node.body.accept(this, context), if (node.variables != null) 'variables': node.variables, - }; + }); } @override Map visitComparison(Comparison node, void context) { - return { + return _withEvaluability(node, { 'type': 'Comparison', 'operator': node.operator.name, 'left': node.left.accept(this, context), 'right': node.right.accept(this, context), - }; + }); } @override Map visitChainedComparison( ChainedComparison node, void context) { - return { + return _withEvaluability(node, { 'type': 'ChainedComparison', 'expressions': node.expressions.map((e) => e.accept(this, context)).toList(), 'operators': node.operators.map((o) => o.name).toList(), - }; + }); } @override Map visitConditionalExpr( ConditionalExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'ConditionalExpr', 'expression': node.expression.accept(this, context), 'condition': node.condition.accept(this, context), - }; + }); } @override Map visitPiecewise(PiecewiseExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'PiecewiseExpr', 'cases': node.cases.map((c) { return { @@ -241,76 +265,76 @@ class JsonAstVisitor implements ExpressionVisitor, void> { 'condition': c.condition!.accept(this, context), }; }).toList(), - }; + }); } @override Map visitMatrixExpr(MatrixExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'MatrixExpr', 'rows': node.rows .map((row) => row.map((e) => e.accept(this, context)).toList()) .toList(), - }; + }); } @override Map visitVectorExpr(VectorExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'VectorExpr', 'components': node.components.map((e) => e.accept(this, context)).toList(), 'isUnitVector': node.isUnitVector, - }; + }); } @override Map visitIntervalExpr(IntervalExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'IntervalExpr', 'lower': node.lower.accept(this, context), 'upper': node.upper.accept(this, context), - }; + }); } @override Map visitAssignmentExpr(AssignmentExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'AssignmentExpr', 'variable': node.variable, 'value': node.value.accept(this, context), - }; + }); } @override Map visitFunctionDefinitionExpr( FunctionDefinitionExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'FunctionDefinitionExpr', 'name': node.name, 'parameters': node.parameters, 'body': node.body.accept(this, context), - }; + }); } @override Map visitBooleanBinaryExpr( BooleanBinaryExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'BooleanBinaryExpr', 'operator': node.operator.name, 'left': node.left.accept(this, context), 'right': node.right.accept(this, context), - }; + }); } @override Map visitBooleanUnaryExpr( BooleanUnaryExpr node, void context) { - return { + return _withEvaluability(node, { 'type': 'BooleanUnaryExpr', 'operand': node.operand.accept(this, context), - }; + }); } } @@ -330,8 +354,8 @@ extension JsonExport on Expression { /// print(jsonEncode(expr.toJson())); /// // {"type":"BinaryOp","operator":"add",...} /// ``` - Map toJson() { - const visitor = JsonAstVisitor(); + Map toJson({bool includeEvaluability = false}) { + final visitor = JsonAstVisitor(includeEvaluability: includeEvaluability); return accept(visitor, null); } } diff --git a/package.json b/package.json index 0127491..2d806a8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "preview": "vitepress preview" }, "devDependencies": { + "@iconify-json/solar": "^1.2.5", "@shikijs/vitepress-twoslash": "^3.21.0", "@types/node": "^25.0.6", "markdown-it-mathjax3": "^4.0.0", diff --git a/pubspec.yaml b/pubspec.yaml index 7ba3701..58f29c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: texpr description: A Dart library for parsing, evaluating, and analyzing mathematical expressions in LaTeX. Supports symbolic calculus, matrices, and complex numbers. -version: 0.1.3 +version: 0.1.4 homepage: https://github.com/xirf/texpr repository: https://github.com/xirf/texpr diff --git a/test/core/cache_keys_test.dart b/test/core/cache_keys_test.dart new file mode 100644 index 0000000..015340f --- /dev/null +++ b/test/core/cache_keys_test.dart @@ -0,0 +1,52 @@ +import 'package:test/test.dart'; +import 'package:texpr/src/ast.dart'; +import 'package:texpr/src/cache/cache_keys.dart'; + +void main() { + group('EvaluationCacheKey structural normalization', () { + final expr = BinaryOp( + const NumberLiteral(1), + BinaryOperator.add, + const NumberLiteral(2), + ); + + test('treats +0.0 and -0.0 as the same key', () { + final key1 = EvaluationCacheKey(expr, {'x': 0.0}); + final key2 = EvaluationCacheKey(expr, {'x': -0.0}); + + expect(key1, equals(key2)); + expect(key1.hashCode, equals(key2.hashCode)); + }); + + test('treats NaN values as equivalent for key matching', () { + final key1 = EvaluationCacheKey(expr, {'x': double.nan}); + final key2 = EvaluationCacheKey(expr, {'x': double.nan}); + + expect(key1, equals(key2)); + expect(key1.hashCode, equals(key2.hashCode)); + }); + + test('uses variable values in structural equality', () { + final key1 = EvaluationCacheKey(expr, {'x': 1.0}); + final key2 = EvaluationCacheKey(expr, {'x': 2.0}); + + expect(key1 == key2, isFalse); + }); + + test('is stable across variable insertion order', () { + final key1 = EvaluationCacheKey(expr, {'x': 1.0, 'y': -0.0}); + final key2 = EvaluationCacheKey(expr, {'y': 0.0, 'x': 1.0}); + + expect(key1, equals(key2)); + expect(key1.hashCode, equals(key2.hashCode)); + }); + + test('identity and structural keys are not considered equal', () { + final vars = {'x': 1.0}; + final identityKey = EvaluationCacheKey.identity(expr, vars); + final structuralKey = EvaluationCacheKey(expr, vars); + + expect(identityKey == structuralKey, isFalse); + }); + }); +} diff --git a/test/core/compile_time_evaluability_test.dart b/test/core/compile_time_evaluability_test.dart new file mode 100644 index 0000000..158ec06 --- /dev/null +++ b/test/core/compile_time_evaluability_test.dart @@ -0,0 +1,50 @@ +import 'package:test/test.dart'; +import 'package:texpr/texpr.dart'; + +void main() { + group('Compile-time evaluability annotation', () { + test('parse annotates root node metadata', () { + final texpr = Texpr(); + final expr = texpr.parse('x + 1'); + + final info = expr.compileTimeEvaluabilityInfo; + expect(info, isNotNull); + expect(info!.evaluability, equals(Evaluability.unevaluable)); + expect(info.freeVariables, equals({'x'})); + }); + + test('parse annotates child nodes metadata', () { + final texpr = Texpr(); + final expr = texpr.parse('x + 1') as BinaryOp; + + final leftInfo = expr.left.compileTimeEvaluabilityInfo; + final rightInfo = expr.right.compileTimeEvaluabilityInfo; + + expect(leftInfo, isNotNull); + expect(rightInfo, isNotNull); + expect(leftInfo!.evaluability, equals(Evaluability.unevaluable)); + expect(leftInfo.freeVariables, equals({'x'})); + expect(rightInfo!.evaluability, equals(Evaluability.numeric)); + expect(rightInfo.freeVariables, isEmpty); + }); + + test('getEvaluability uses compile-time metadata with context', () { + final texpr = Texpr(); + final expr = texpr.parse('x + 1'); + + expect(expr.getEvaluability(), equals(Evaluability.unevaluable)); + expect(expr.getEvaluability({'x'}), equals(Evaluability.numeric)); + expect(expr.getEvaluability({'y'}), equals(Evaluability.unevaluable)); + }); + + test('symbolic classification is stable even with variable context', () { + final texpr = Texpr(); + final expr = texpr.parse(r'\nabla f'); + + final info = expr.compileTimeEvaluabilityInfo; + expect(info, isNotNull); + expect(info!.evaluability, equals(Evaluability.symbolic)); + expect(expr.getEvaluability({'f'}), equals(Evaluability.symbolic)); + }); + }); +} diff --git a/test/core/error_messages_test.dart b/test/core/error_messages_test.dart index fc9d9fe..bcabb72 100644 --- a/test/core/error_messages_test.dart +++ b/test/core/error_messages_test.dart @@ -74,7 +74,15 @@ void main() { group('ValidationResult Suggestions', () { test('validation of unknown function provides suggestion', () { - final result = evaluator.validate(r'\unknownfunc{x}'); + TexprException? parseError; + try { + evaluator.parse(r'\unknownfunc{x}'); + } on TexprException catch (e) { + parseError = e; + } + + expect(parseError, isNotNull); + final result = ValidationResult.fromException(parseError!); expect(result.isValid, isFalse); expect(result.suggestion, isNotNull); }); diff --git a/test/core/implicit_multiplication_test.dart b/test/core/implicit_multiplication_test.dart index 7a10033..e9292a2 100644 --- a/test/core/implicit_multiplication_test.dart +++ b/test/core/implicit_multiplication_test.dart @@ -144,8 +144,8 @@ void main() { }); test('validates multi-char variable expressions', () { - expect(evalNoImplicit.isValid('abc'), isTrue); - expect(evalNoImplicit.isValid('mass'), isTrue); + expect(() => evalNoImplicit.parse('abc'), returnsNormally); + expect(() => evalNoImplicit.parse('mass'), returnsNormally); }); }); }); diff --git a/test/core/validation_test.dart b/test/core/validation_test.dart index a2d834d..8d9ee4e 100644 --- a/test/core/validation_test.dart +++ b/test/core/validation_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use_from_same_package + import 'package:texpr/texpr.dart'; import 'package:test/test.dart'; diff --git a/test/features/error_recovery_test.dart b/test/features/error_recovery_test.dart index 082d339..6c4bd95 100644 --- a/test/features/error_recovery_test.dart +++ b/test/features/error_recovery_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use_from_same_package + import 'package:test/test.dart'; import 'package:texpr/texpr.dart'; diff --git a/test/features/json_ast_export_test.dart b/test/features/json_ast_export_test.dart index 398bb22..6ff5634 100644 --- a/test/features/json_ast_export_test.dart +++ b/test/features/json_ast_export_test.dart @@ -301,6 +301,42 @@ void main() { }); group('JSON Serialization', () { + test('does not include evaluability by default', () { + final expr = evaluator.parse('x + 1'); + final json = expr.toJson(); + + expect(json.containsKey('evaluability'), isFalse); + expect((json['left'] as Map).containsKey('evaluability'), + isFalse); + }); + + test('includes evaluability when explicitly requested', () { + final expr = evaluator.parse('x + 1'); + final json = expr.toJson(includeEvaluability: true); + + expect(json['evaluability'], isA>()); + expect(json['evaluability']['kind'], equals('unevaluable')); + expect(json['evaluability']['freeVariables'], equals(['x'])); + + final left = json['left'] as Map; + final right = json['right'] as Map; + expect(left['evaluability']['kind'], equals('unevaluable')); + expect(left['evaluability']['freeVariables'], equals(['x'])); + expect(right['evaluability']['kind'], equals('numeric')); + expect(right['evaluability']['freeVariables'], isEmpty); + }); + + test('omits evaluability when metadata is unavailable', () { + final manualExpr = BinaryOp( + const NumberLiteral(1), + BinaryOperator.add, + const NumberLiteral(2), + ); + + final json = manualExpr.toJson(includeEvaluability: true); + expect(json.containsKey('evaluability'), isFalse); + }); + test('can be encoded to JSON string', () { final expr = evaluator.parse(r'\frac{x^{2} + 1}{2}'); final json = expr.toJson(); diff --git a/test/features/latex_roundtrip_test.dart b/test/features/latex_roundtrip_test.dart index 201cf4f..5af54ae 100644 --- a/test/features/latex_roundtrip_test.dart +++ b/test/features/latex_roundtrip_test.dart @@ -344,9 +344,10 @@ void main() { test('piecewise conditional via cases (doc reference)', () { // This test documents the correct approach for conditionals expect( - evaluator - .isValid(r'\begin{cases} x & x > 0 \\ -x & x \leq 0 \end{cases}'), - isTrue); + () => evaluator + .parse(r'\begin{cases} x & x > 0 \\ -x & x \leq 0 \end{cases}'), + returnsNormally, + ); }); }); diff --git a/test/integration/sympy/README.md b/test/integration/sympy/README.md index 0d564ab..3272e76 100644 --- a/test/integration/sympy/README.md +++ b/test/integration/sympy/README.md @@ -35,6 +35,7 @@ dart test test/integration/sympy/sympy_export_integration_test.dart # 2. Activate Python venv and run verification source test/integration/sympy/.venv/bin/activate +pip install -r test/integration/sympy/requirements.txt python test/integration/sympy/verify_sympy_export.py ``` @@ -49,6 +50,20 @@ source .venv/bin/activate pip install sympy ``` +Or install from pinned requirements: + +```bash +pip install -r requirements.txt +``` + +## CI Smoke Suite + +GitHub Actions runs this cross-CAS smoke verification in `.github/workflows/ci.yml`: + +1. Run Dart test export (`sympy_export_integration_test.dart`) +2. Install SymPy from `requirements.txt` +3. Run `verify_sympy_export.py` + ## Files - `sympy_export_integration_test.dart` - Dart test that exports test cases diff --git a/test/integration/sympy/requirements.txt b/test/integration/sympy/requirements.txt new file mode 100644 index 0000000..6dded62 --- /dev/null +++ b/test/integration/sympy/requirements.txt @@ -0,0 +1 @@ +sympy==1.13.3 diff --git a/test/minus_power_full_test.dart b/test/minus_power_full_test.dart index 1123cd7..32813be 100644 --- a/test/minus_power_full_test.dart +++ b/test/minus_power_full_test.dart @@ -5,7 +5,7 @@ void main() { final evaluator = Texpr(); test('isValid should accept expression with unary minus and power', () { - expect(evaluator.isValid(r'-(x-1)^{2}+4'), isTrue); + expect(() => evaluator.parse(r'-(x-1)^{2}+4'), returnsNormally); }); test('evaluate numeric matches expected value', () { diff --git a/test/security/cache_security_test.dart b/test/security/cache_security_test.dart index 2be0910..cf44825 100644 --- a/test/security/cache_security_test.dart +++ b/test/security/cache_security_test.dart @@ -101,6 +101,9 @@ void main() { evaluator.evaluate('2 + 2'); final time2 = DateTime.now().difference(start2); + expect(time1.inMicroseconds, greaterThanOrEqualTo(0)); + expect(time2.inMicroseconds, greaterThanOrEqualTo(0)); + // Both should still evaluate correctly expect( evaluator.evaluate('1 + 1').asNumeric(), @@ -142,6 +145,9 @@ void main() { evaluator.evaluate(r'\cos{0}'); final cacheMiss = DateTime.now().difference(start2); + expect(cacheHit.inMicroseconds, greaterThanOrEqualTo(0)); + expect(cacheMiss.inMicroseconds, greaterThanOrEqualTo(0)); + // Verify both evaluate correctly expect(evaluator.evaluate(r'\sin{0}').asNumeric(), equals(0.0)); expect(evaluator.evaluate(r'\cos{0}').asNumeric(), equals(1.0)); diff --git a/test/security/extension_security_test.dart b/test/security/extension_security_test.dart index 6e31cf2..2c9fc2d 100644 --- a/test/security/extension_security_test.dart +++ b/test/security/extension_security_test.dart @@ -453,16 +453,15 @@ void main() { final arg2 = expr.optionalParam != null ? eval(expr.optionalParam!) : null; - // Validate arguments - if (arg1 is! num || arg2 is! num) { - throw EvaluatorException('safediv requires numeric arguments'); + if (arg2 == null) { + throw EvaluatorException('safediv requires two arguments'); } if (arg2 == 0) { throw EvaluatorException('Division by zero'); } - return (arg1 as num) / (arg2 as num); + return arg1 / arg2; } return null; }); @@ -567,10 +566,8 @@ void main() { ext.registerEvaluator((expr, vars, eval) { if (expr is FunctionCall && expr.name == 'slowfib') { final arg = eval(expr.argument); - if (arg is num) { - // This has exponential complexity - return fibonacci(arg.toInt()); - } + // This has exponential complexity + return fibonacci(arg.toInt()); } return null; }); diff --git a/test/security/input_sanitization_test.dart b/test/security/input_sanitization_test.dart index 93cec4e..46d9015 100644 --- a/test/security/input_sanitization_test.dart +++ b/test/security/input_sanitization_test.dart @@ -78,8 +78,8 @@ void main() { final userInput = r'\factorial{1000}'; // Malicious expression // User input should be treated as a value, not parsed - final vars = {'x': 1.0}; - final result = evaluator.evaluate('x + 1', vars); + final vars = {'x': userInput}; + final result = evaluator.evaluate('1 + 1', vars); expect(result.asNumeric(), equals(2.0), reason: 'Variable values should not be re-parsed'); @@ -301,9 +301,9 @@ void main() { // CVE: Validation/evaluation inconsistency final expr = r'\sin{x}'; - final validation = evaluator.validate(expr); + final validationSucceeded = _isParsable(expr); - if (validation.isValid) { + if (validationSucceeded) { expect( () => evaluator.evaluate(expr, {'x': 1.0}), returnsNormally, @@ -325,7 +325,9 @@ void main() { // CVE: Validation bypass final expr = '1' * 1000; - final validation = evaluator.validate(expr); + try { + evaluator.parse(expr); + } catch (_) {} // Whether valid or not, evaluation should be safe expect( () => evaluator.evaluate(expr), @@ -363,17 +365,22 @@ void main() { group('Error Message Information Disclosure', () { test('error messages should not leak sensitive information', () { // CVE: Information disclosure via error messages - final result = evaluator.validate(r'\unknownFunc{x}'); + TexprException? parseError; + try { + evaluator.parse(r'\unknownFunc{x}'); + } on TexprException catch (e) { + parseError = e; + } - expect(result.isValid, isFalse); + expect(parseError, isNotNull); // Error message should be helpful but not leak internals expect( - result.errorMessage, + parseError!.message, isNot(contains('stack')), reason: 'Error should not contain stack traces', ); expect( - result.errorMessage, + parseError.message, isNot(contains('file://')), reason: 'Error should not contain file paths', ); @@ -490,3 +497,12 @@ void main() { }); }); } + +bool _isParsable(String expression) { + try { + Texpr().parse(expression); + return true; + } on TexprException { + return false; + } +} diff --git a/test/security/tokenizer_security_test.dart b/test/security/tokenizer_security_test.dart index fbc4712..846cdea 100644 --- a/test/security/tokenizer_security_test.dart +++ b/test/security/tokenizer_security_test.dart @@ -52,7 +52,7 @@ void main() { test('command with extremely long name should be rejected', () { // CVE: Command name length validation - final longCommand = r'\' + 'a' * 10000 + '{x}'; + final longCommand = '${r'\'}${'a' * 10000}{x}'; expect( () => evaluator.evaluate(longCommand), diff --git a/uno.config.ts b/uno.config.ts index 1379984..5ac897d 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -1,9 +1,13 @@ -import { defineConfig, presetUno, presetIcons, transformerDirectives } from 'unocss' +import { defineConfig, presetWind4, presetIcons, transformerDirectives } from 'unocss' export default defineConfig({ presets: [ - presetUno(), - presetIcons(), + presetWind4(), + presetIcons({ + collections: { + solar: () => import('@iconify-json/solar/icons.json').then(i => i.default as any) + } + }) ], transformers: [ transformerDirectives(),