This is page 130 of 137. Use http://codebase.md/xmlui-org/xmlui/xmlui-standalone.umd.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── cold-items-taste.md │ ├── config.json │ ├── empty-spiders-dress.md │ ├── shy-windows-allow.md │ ├── sour-coins-read.md │ ├── tame-zebras-invite.md │ ├── three-ideas-invent.md │ ├── twenty-jeans-watch.md │ ├── warm-spies-melt.md │ └── whole-ways-cry.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── actions.md │ ├── AppRoot.md │ ├── component-apis.md │ ├── component-rendering.md │ ├── component-review-checklist.md │ ├── containers.md │ ├── data-sources.md │ ├── e2e-summary.md │ ├── expression-evaluation.md │ ├── glossary.md │ ├── helper-components.md │ ├── index.md │ ├── loaders.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── rendering-fundamentals.md │ ├── reusable-components.md │ ├── standalone-apps.md │ ├── state-management.md │ └── xmlui-extensibility.xlsx ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ ├── BehaviorContext.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/Parser.ts: -------------------------------------------------------------------------------- ```typescript import { type ArrayDestructure, type ArrayLiteral, type ArrowExpression, type AssignmentExpression, type ScripNodeBase, type BinaryExpression, type BlockStatement, type BreakStatement, type CalculatedMemberAccessExpression, type ConditionalExpression, type ConstStatement, type ContinueStatement, type Destructure, type DoWhileStatement, type EmptyStatement, type Expression, type ExpressionStatement, type ForInStatement, type ForOfStatement, type ForStatement, type ForVarBinding, type FunctionDeclaration, type FunctionInvocationExpression, type Identifier, type IfStatement, type LetStatement, type Literal, type MemberAccessExpression, type NoArgExpression, type ObjectDestructure, type ObjectLiteral, type PostfixOpExpression, type PrefixOpExpression, type ReactiveVarDeclaration, type ReturnStatement, type SequenceExpression, type SpreadExpression, type Statement, type SwitchCase, type SwitchStatement, type ThrowStatement, type TryStatement, type UnaryExpression, type VarDeclaration, type VarStatement, type WhileStatement, type TemplateLiteralExpression, T_EMPTY_STATEMENT, T_BREAK_STATEMENT, T_CONTINUE_STATEMENT, T_EXPRESSION_STATEMENT, T_VAR_DECLARATION, T_LET_STATEMENT, T_CONST_STATEMENT, T_REACTIVE_VAR_DECLARATION, T_VAR_STATEMENT, T_ARRAY_DESTRUCTURE, T_BLOCK_STATEMENT, T_IF_STATEMENT, T_WHILE_STATEMENT, T_OBJECT_DESTRUCTURE, T_DO_WHILE_STATEMENT, T_RETURN_STATEMENT, T_FOR_STATEMENT, T_FOR_IN_STATEMENT, T_FOR_OF_STATEMENT, T_IDENTIFIER, T_THROW_STATEMENT, T_TRY_STATEMENT, T_SWITCH_CASE, T_SWITCH_STATEMENT, T_NO_ARG_EXPRESSION, T_SEQUENCE_EXPRESSION, T_OBJECT_LITERAL, T_ARRAY_LITERAL, T_SPREAD_EXPRESSION, T_FUNCTION_DECLARATION, T_ARROW_EXPRESSION, T_CONDITIONAL_EXPRESSION, T_ASSIGNMENT_EXPRESSION, T_BINARY_EXPRESSION, T_UNARY_EXPRESSION, T_FUNCTION_INVOCATION_EXPRESSION, T_MEMBER_ACCESS_EXPRESSION, T_CALCULATED_MEMBER_ACCESS_EXPRESSION, T_POSTFIX_OP_EXPRESSION, T_PREFIX_OP_EXPRESSION, T_LITERAL, T_TEMPLATE_LITERAL_EXPRESSION, T_DESTRUCTURE, } from "../../components-core/script-runner/ScriptingSourceTree"; import type { GenericToken } from "../common/GenericToken"; import { InputStream } from "../common/InputStream"; import { Lexer } from "./Lexer"; import { ParserError, errorMessages } from "./ParserError"; import { tokenTraits } from "./TokenTrait"; import { TokenType } from "./TokenType"; import type { ErrorCodes, ParserErrorMessage } from "./ParserError"; type Token = GenericToken<TokenType>; /** * States of the string parsing */ enum StrParseState { Normal, Backslash, X, Xh, UX1, UX2, UX3, UX4, Ucp1, Ucp2, Ucp3, Ucp4, Ucp5, Ucp6, UcpTail, } let lastNodeId = 0; export function createXmlUiTreeNodeId(): number { return ++lastNodeId; } /** * This class parses a binding expression and transforms it into an evaluable expression tree */ export class Parser { // --- Keep track of error messages private _parseErrors: ParserErrorMessage[] = []; // --- Use this lexer private _lexer: Lexer; // --- Indicates the parsing level private _statementLevel = 0; /** * Initializes the parser with the specified source code * @param source Source code to parse */ constructor(source?: string) { this._lexer = new Lexer(new InputStream(source ?? "")); } /** * Sets the source code to parse * @param source Source code to parse */ setSource(source: string): void { this._lexer = new Lexer(new InputStream(source)); } /** * The errors raised during the parse phase */ get errors(): ParserErrorMessage[] { return this._parseErrors; } /** * Gets the current token */ get current(): Token { return this._lexer.peek(); } /** * Checks if we're at the end of the expression */ get isEof(): boolean { return this._lexer.peek().type === TokenType.Eof; } /** * Gets the characters remaining after parsing */ getTail(): string { return this._lexer.getTail(); } // ========================================================================== // Statement parsing /** * Parses a list of statements: * * statements * : statement* * ; * * statement * : emptyStatement * | expressionStatement * | letStatement * | returnStatement * ; */ parseStatements(): Statement[] | null { this._statementLevel = 0; const statements: Statement[] = []; while (!this.isEof) { const statement = this.parseStatement(); if (!statement) return null; statements.push(statement); if (statement.type !== T_EMPTY_STATEMENT) { this.skipToken(TokenType.Semicolon); } } return statements; } /** * Parses a single statement */ private parseStatement(allowSequence = true): Statement | null { this._statementLevel++; try { const startToken = this._lexer.peek(); switch (startToken.type) { case TokenType.Semicolon: return this.parseEmptyStatement(); case TokenType.Let: return this.parseLetStatement(); case TokenType.Const: return this.parseConstStatement(); case TokenType.Var: return this.parseVarStatement(); case TokenType.LBrace: return this.parseBlockStatement(); case TokenType.If: return this.parseIfStatement(); case TokenType.Do: return this.parseDoWhileStatement(); case TokenType.While: return this.parseWhileStatement(); case TokenType.Return: return this.parseReturnStatement(); case TokenType.Break: this._lexer.get(); return this.createStatementNode<BreakStatement>( T_BREAK_STATEMENT, {}, startToken, startToken, ); case TokenType.Continue: this._lexer.get(); return this.createStatementNode<ContinueStatement>( T_CONTINUE_STATEMENT, {}, startToken, startToken, ); case TokenType.For: return this.parseForStatement(); case TokenType.Throw: return this.parseThrowStatement(); case TokenType.Try: return this.parseTryStatement(); case TokenType.Switch: return this.parseSwitchStatement(); case TokenType.Function: return this.parseFunctionDeclaration(); default: if (this.isExpressionStart(startToken)) { return this.parseExpressionStatement(allowSequence); } this.reportError("W002", startToken, startToken.text); return null; } } finally { this._statementLevel--; } } /** * Parses an empty statement * * emptyStatement * : ";" * ; */ private parseEmptyStatement(): EmptyStatement | null { const startToken = this._lexer.get(); return this.createStatementNode<EmptyStatement>(T_EMPTY_STATEMENT, {}, startToken, startToken); } /** * Parses an expression statement * * expressionStatement * : expression * ; */ private parseExpressionStatement(allowSequence = true): ExpressionStatement | null { const startToken = this._lexer.peek(); const expr = this.getExpression(allowSequence); return expr ? this.createStatementNode<ExpressionStatement>( T_EXPRESSION_STATEMENT, { expr, }, startToken, expr.endToken, ) : null; } /** * Parses a let statement * * letStatement * : "let" id ["=" expression] ("," id ["=" expression])* * ; */ private parseLetStatement(): LetStatement | null { const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; const decls: VarDeclaration[] = []; while (true) { const declStart = this._lexer.peek(); let declarationProps: any = {}; if (declStart.type === TokenType.LBrace) { // --- This is object destructure endToken = this._lexer.ahead(1); const oDestr = this.parseObjectDestructure(); if (oDestr === null) return null; declarationProps = { oDestr, }; endToken = oDestr.length > 0 ? oDestr[oDestr.length - 1].endToken : endToken; } else if (declStart.type === TokenType.LSquare) { // --- This is array destructure endToken = this._lexer.ahead(1); const aDestr = this.parseArrayDestructure(); if (aDestr === null) return null; declarationProps = { aDestr, }; endToken = aDestr.length > 0 ? aDestr[aDestr.length - 1].endToken : endToken; } else if (declStart.type === TokenType.Identifier) { if (declStart.text.startsWith("$")) { this.reportError("W031"); return null; } endToken = this._lexer.get(); declarationProps = { id: declStart.text, }; } else { this.reportError("W003"); return null; } // --- Optional initialization const initToken = this._lexer.peek(); let expr: Expression | null = null; if (initToken.type === TokenType.Assignment) { this._lexer.get(); expr = this.getExpression(false); if (expr === null) return null; declarationProps.expr = expr; endToken = expr.endToken; } else if (declarationProps.aDestr || declarationProps.oDestr) { this.reportError("W009", initToken); return null; } // --- New declaration reached decls.push( this.createExpressionNode<VarDeclaration>( T_VAR_DECLARATION, declarationProps, declStart, endToken, ), ); // --- Check for more declarations if (this._lexer.peek().type !== TokenType.Comma) break; this._lexer.get(); } // --- Done return this.createStatementNode<LetStatement>( T_LET_STATEMENT, { decls, }, startToken, endToken, ); } /** * Parses a const statement * * constStatement * : "const" id "=" expression * ; */ private parseConstStatement(): ConstStatement | null { const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; const decls: VarDeclaration[] = []; while (true) { const declStart = this._lexer.peek(); let declarationProps: any = {}; if (declStart.type === TokenType.LBrace) { // --- This is object destructure endToken = this._lexer.ahead(1); const oDestr = this.parseObjectDestructure(); if (oDestr === null) return null; declarationProps = { oDestr, }; endToken = oDestr.length > 0 ? oDestr[oDestr.length - 1].endToken : endToken; } else if (declStart.type === TokenType.LSquare) { // --- This is array destructure endToken = this._lexer.ahead(1); const aDestr = this.parseArrayDestructure(); if (aDestr === null) return null; declarationProps = { aDestr, }; endToken = aDestr.length > 0 ? aDestr[aDestr.length - 1].endToken : endToken; } else if (declStart.type === TokenType.Identifier) { if (declStart.text.startsWith("$")) { this.reportError("W031"); return null; } endToken = this._lexer.get(); declarationProps = { id: declStart.text, }; } else { this.reportError("W003"); return null; } this.expectToken(TokenType.Assignment); const expr = this.getExpression(false); if (expr === null) return null; declarationProps.expr = expr; endToken = expr.endToken; // --- New declaration reached decls.push( this.createExpressionNode<VarDeclaration>( T_VAR_DECLARATION, declarationProps, declStart, endToken, ), ); // --- Check for more declarations if (this._lexer.peek().type !== TokenType.Comma) break; this._lexer.get(); } // --- Done return this.createStatementNode<ConstStatement>( T_CONST_STATEMENT, { decls, }, startToken, endToken, ); } /** * Parses a var statement * * constStatement * : "var" id "=" expression * ; */ private parseVarStatement(): VarStatement | null { const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; const decls: ReactiveVarDeclaration[] = []; while (true) { const declStart = this._lexer.peek(); let declarationProps: any = {}; if (declStart.type === TokenType.Identifier) { if (declStart.text.startsWith("$")) { this.reportError("W031"); return null; } endToken = this._lexer.get(); declarationProps = { id: { type: T_IDENTIFIER, name: declStart.text, startToken: declStart, endToken, }, }; } else { this.reportError("W003"); return null; } // --- Mandatory initialization this.expectToken(TokenType.Assignment); const expr = this.getExpression(false); if (expr === null) return null; declarationProps.expr = expr; endToken = expr.endToken; // --- New declaration reached decls.push( this.createExpressionNode<ReactiveVarDeclaration>( T_REACTIVE_VAR_DECLARATION, declarationProps, declStart, endToken, ), ); // --- Check for more declarations if (this._lexer.peek().type !== TokenType.Comma) break; this._lexer.get(); } // --- Done return this.createStatementNode<VarStatement>( T_VAR_STATEMENT, { decls, }, startToken, endToken, ); } /** * Parses an object destructure expression */ private parseObjectDestructure(): ObjectDestructure[] | null { const result: ObjectDestructure[] = []; // --- Skip the starting left brace const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; let nextToken = this._lexer.peek(); while (nextToken.type === TokenType.Identifier) { // --- Obtain the ID const id = nextToken.text; if (id.startsWith("$")) { this.reportError("W031"); return null; } let alias: string | undefined; let aDestr: ArrayDestructure[] | undefined | null; let oDestr: ObjectDestructure[] | undefined | null; this._lexer.get(); nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Colon) { // --- Check the available cases this._lexer.get(); nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Identifier) { alias = nextToken.text; endToken = nextToken; this._lexer.get(); } else if (nextToken.type === TokenType.LSquare) { aDestr = this.parseArrayDestructure(); if (aDestr === null) return null; endToken = aDestr[aDestr.length - 1].endToken; } else if (nextToken.type === TokenType.LBrace) { oDestr = this.parseObjectDestructure(); if (oDestr === null) return null; endToken = oDestr[oDestr.length - 1].endToken; } } // --- Check for next segment nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Comma || nextToken.type === TokenType.RBrace) { result.push( this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id, alias, aDestr, oDestr }, startToken, endToken, ), ); if (nextToken.type === TokenType.Comma) { // --- Skip the delimiter comma this._lexer.get(); nextToken = this._lexer.peek(); } } } // --- Expect a closing right brace this.expectToken(TokenType.RBrace, "W004"); return result; } private parseArrayDestructure(): ArrayDestructure[] | null { const result: ArrayDestructure[] = []; // --- Skip the starting left square const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; do { let nextToken = this._lexer.peek(); let id: string | undefined; let aDestr: ArrayDestructure[] | undefined | null; let oDestr: ObjectDestructure[] | undefined | null; if (nextToken.type === TokenType.Identifier) { id = nextToken.text; if (id.startsWith("$")) { this.reportError("W031"); return null; } endToken = nextToken; nextToken = this._lexer.get(); } else if (nextToken.type === TokenType.LSquare) { aDestr = this.parseArrayDestructure(); if (aDestr === null) return null; endToken = aDestr[aDestr.length - 1].endToken; } else if (nextToken.type === TokenType.LBrace) { oDestr = this.parseObjectDestructure(); if (oDestr === null) return null; endToken = oDestr[oDestr.length - 1].endToken; } nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Comma) { // --- Store the segment result.push( this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { id, aDestr, oDestr }, startToken, endToken, ), ); this._lexer.get(); } else if (nextToken.type === TokenType.RSquare) { if (id || aDestr || oDestr) { // --- Store a non-empty the segment result.push( this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { id, aDestr, oDestr }, startToken, endToken, ), ); } break; } else { this.reportError("W002", nextToken); return null; } } while (true); // --- Expect a closing right square this.expectToken(TokenType.RSquare, "W005"); return result; } /** * Parses a block statement * * blockStatement * : "{" (statement [";"])* "}" * ; */ private parseBlockStatement(): BlockStatement | null { const startToken = this._lexer.get(); const stmts: Statement[] = []; while (this._lexer.peek().type !== TokenType.RBrace) { const statement = this.parseStatement(); if (!statement) return null; stmts.push(statement); if (statement.type !== T_EMPTY_STATEMENT) { this.skipToken(TokenType.Semicolon); } } const endToken = this._lexer.get(); return this.createStatementNode<BlockStatement>( T_BLOCK_STATEMENT, { stmts }, startToken, endToken, ); } /** * Parses an if statement * * ifStatement * : "if" "(" expression ")" statement ["else" statement] * ; */ private parseIfStatement(): IfStatement | null { const startToken = this._lexer.get(); let endToken: Token | undefined = startToken; this.expectToken(TokenType.LParent, "W014"); const cond = this.getExpression(); if (!cond) return null; this.expectToken(TokenType.RParent, "W006"); const thenB = this.parseStatement(); if (!thenB) return null; endToken = thenB.endToken; let elseCanFollow = true; if (thenB.type !== T_BLOCK_STATEMENT) { // --- We need a closing semicolon before else if (this._lexer.peek().type === TokenType.Semicolon) { this._lexer.get(); } else { elseCanFollow = false; } } let elseB: Statement | null = null; if (elseCanFollow && this._lexer.peek().type === TokenType.Else) { this._lexer.get(); elseB = this.parseStatement(); if (!elseB) return null; endToken = elseB.endToken; } return this.createStatementNode<IfStatement>( T_IF_STATEMENT, { cond, thenB, elseB, }, startToken, endToken, ); } /** * Parses a while statement * * whileStatement * : "while" "(" condition ")" statement * ; */ private parseWhileStatement(): WhileStatement | null { const startToken = this._lexer.get(); this.expectToken(TokenType.LParent, "W014"); const cond = this.getExpression(); if (!cond) return null; this.expectToken(TokenType.RParent, "W006"); const body = this.parseStatement(); if (!body) return null; return this.createStatementNode<WhileStatement>( T_WHILE_STATEMENT, { cond, body, }, startToken, body.endToken, ); } /** * Parses a do-while statement * * doWhileStatement * : "do" statement "while" "(" condition ")" * ; */ private parseDoWhileStatement(): DoWhileStatement | null { const startToken = this._lexer.get(); const body = this.parseStatement(); if (!body) return null; if (body.type !== T_BLOCK_STATEMENT && body.type !== T_EMPTY_STATEMENT) { this.expectToken(TokenType.Semicolon); } this.expectToken(TokenType.While); this.expectToken(TokenType.LParent, "W014"); const cond = this.getExpression(); if (!cond) return null; const endToken = this._lexer.peek(); this.expectToken(TokenType.RParent, "W006"); return this.createStatementNode<DoWhileStatement>( T_DO_WHILE_STATEMENT, { cond, body, }, startToken, endToken, ); } /** * Parses an expression statement * * returnStatement * : "return" expression? * ; */ private parseReturnStatement(): ReturnStatement | null { const startToken = this._lexer.peek(); let endToken: Token | undefined = this._lexer.get(); let expr: Expression | undefined | null; if (tokenTraits[this._lexer.peek().type].expressionStart) { expr = this.getExpression(); if (expr === null) return null; endToken = expr.endToken; } return this.createStatementNode<ReturnStatement>( T_RETURN_STATEMENT, { expr, }, startToken, endToken, ); } /** * forStatement * : "for" "(" initStatement? ";" expression? ";" expression? ")" statement * | forInOfStatement * ; */ private parseForStatement(): ForStatement | ForInStatement | ForOfStatement | null { const startToken = this._lexer.peek(); this._lexer.get(); this.expectToken(TokenType.LParent, "W014"); // --- Check for..in and for..of let nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Identifier) { if (this._lexer.ahead(1).type === TokenType.In) { return this.parseForInOfStatement(startToken, "none", nextToken, T_FOR_IN_STATEMENT); } else if (this._lexer.ahead(1).type === TokenType.Of) { return this.parseForInOfStatement(startToken, "none", nextToken, T_FOR_OF_STATEMENT); } } else if (nextToken.type === TokenType.Let) { const idToken = this._lexer.ahead(1); if (idToken.type === TokenType.Identifier) { const inOfToken = this._lexer.ahead(2); if (inOfToken.type === TokenType.In) { return this.parseForInOfStatement(startToken, "let", idToken, T_FOR_IN_STATEMENT); } else if (inOfToken.type === TokenType.Of) { return this.parseForInOfStatement(startToken, "let", idToken, T_FOR_OF_STATEMENT); } } } else if (nextToken.type === TokenType.Const) { const idToken = this._lexer.ahead(1); if (idToken.type === TokenType.Identifier) { const inOfToken = this._lexer.ahead(2); if (inOfToken.type === TokenType.In) { return this.parseForInOfStatement(startToken, "const", idToken, T_FOR_IN_STATEMENT); } else if (inOfToken.type === TokenType.Of) { return this.parseForInOfStatement(startToken, "const", idToken, T_FOR_OF_STATEMENT); } } } // --- We accept only let statement, empty statement, and expression let init: ExpressionStatement | LetStatement | undefined; nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Semicolon) { // --- Empty statement: no init in the for loop this._lexer.get(); } else if (nextToken.type === TokenType.Let) { // --- Let statement const letStmt = this.parseLetStatement(); if (letStmt === null) { return null; } init = letStmt; if (init.decls.some((d) => !d.expr)) { this.reportError("W011"); return null; } this.expectToken(TokenType.Semicolon); } else if (tokenTraits[nextToken.type].expressionStart) { // --- Expression statement const exprStmt = this.parseExpressionStatement(); if (exprStmt === null) { return null; } init = exprStmt; this.expectToken(TokenType.Semicolon); } // --- Parse the condition let cond: Expression | null | undefined; nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Semicolon) { // --- No condition this._lexer.get(); } else { cond = this.getExpression(); if (cond === null) { return null; } this.expectToken(TokenType.Semicolon); } // --- Parse the update expression let upd: Expression | null | undefined; nextToken = this._lexer.peek(); if (nextToken.type !== TokenType.RParent) { upd = this.getExpression(); if (upd === null) { return null; } } // --- Close the declaration part this.expectToken(TokenType.RParent, "W006"); // --- Get the body const body = this.parseStatement(); if (!body) return null; return this.createStatementNode<ForStatement>( T_FOR_STATEMENT, { init, cond, upd, body, }, startToken, body.endToken, ); } /** * forInOfStatement * : "for" "(" [ "let" | "const" ] identifier ( "in" | "of" ) expression? ")" statement * | forInOfStatement * ; * * @param startToken Statement start token * @param varB Variable binding of the for..in/of statement * @param id ID name * @param type Is it a for..in or a for..of? */ private parseForInOfStatement( startToken: Token, varB: ForVarBinding, idToken: Token, type: number, ): ForInStatement | ForOfStatement | null { if (varB !== "none") { // --- Skip variable binding type if (idToken.text.startsWith("$")) { this.reportError("W031"); return null; } this._lexer.get(); } // --- Skip variable name, in/of token this._lexer.get(); this._lexer.get(); // --- Get the expression const expr = this.getExpression(true); // --- Close the declaration part this.expectToken(TokenType.RParent, "W006"); // --- Get the body const body = this.parseStatement(); if (!body) return null; return type === T_FOR_IN_STATEMENT ? this.createStatementNode<ForInStatement>( T_FOR_IN_STATEMENT, { varB, id: { type: T_IDENTIFIER, name: idToken.text, startToken: idToken, endToken: idToken, }, expr, body, }, startToken, body.endToken, ) : this.createStatementNode<ForOfStatement>( T_FOR_OF_STATEMENT, { varB, id: { type: T_IDENTIFIER, name: idToken.text, startToken: idToken, endToken: idToken, }, expr, body, }, startToken, body.endToken, ); } /** * Parses a throw statement * * throwStatement * : "throw" expression * ; */ private parseThrowStatement(): ThrowStatement | null { const startToken = this._lexer.peek(); this._lexer.get(); let expr: Expression | undefined | null; expr = this.getExpression(); if (expr === null) return null; return this.createStatementNode<ThrowStatement>( T_THROW_STATEMENT, { expr, }, startToken, expr.endToken, ); } /** * Parses a try..catch..finally statement * * tryStatement * : "try" blockStatement catchClause finallyClause? * | "try" blockStatement catchClause? finallyClause * ; * * catchClause * : "catch" [ "(" identifier ") ]? blockStatement * ; * * finallyClause * : "finally" blockStatement */ private parseTryStatement(): TryStatement | null { const getBlock: () => BlockStatement | null = () => { const nextToken = this._lexer.peek(); if (nextToken.type !== TokenType.LBrace) { this.reportError("W012", nextToken); return null; } return this.parseBlockStatement(); }; const startToken = this._lexer.peek(); let endToken: Token | undefined = this._lexer.get(); // --- Get "try" block const tryB = getBlock()!; let catchB: BlockStatement | undefined; let catchV: Identifier | undefined; let finallyB: BlockStatement | undefined; let nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Catch) { // --- Catch found this._lexer.get(); nextToken = this._lexer.peek(); if (nextToken.type === TokenType.LParent) { // --- Catch variable found this._lexer.get(); nextToken = this._lexer.peek(); if (nextToken.type !== TokenType.Identifier) { this.reportError("W003", nextToken); return null; } catchV = { type: T_IDENTIFIER, nodeId: createXmlUiTreeNodeId(), name: nextToken.text, startToken: nextToken, endToken: nextToken, }; this._lexer.get(); this.expectToken(TokenType.RParent, "W006"); } // --- Get catch block catchB = getBlock()!; endToken = catchB.endToken; if (this._lexer.peek().type === TokenType.Finally) { // --- Finally after catch this._lexer.get(); finallyB = getBlock()!; endToken = finallyB.endToken; } } else if (nextToken.type === TokenType.Finally) { // --- Finally found this._lexer.get(); finallyB = getBlock()!; endToken = finallyB.endToken; } else { this.reportError("W013", nextToken); return null; } return this.createStatementNode<TryStatement>( T_TRY_STATEMENT, { tryB, catchB, catchV, finallyB, }, startToken, endToken, ); } /** * Parses a switch statement * * switchStatement * : "switch" "(" expression ")" "{" caseClauses "}" * ; * * caseClauses * : "case" expression ":" statement* * | "default" ":" statement* * ; */ private parseSwitchStatement(): SwitchStatement | null { const startToken = this._lexer.get(); // --- Parse the switch expression this.expectToken(TokenType.LParent, "W014"); const expr = this.getExpression(); if (!expr) return null; this.expectToken(TokenType.RParent, "W006"); // --- Parse the case blocks this.expectToken(TokenType.LBrace, "W012"); const cases: SwitchCase[] = []; let defaultCaseFound = false; while (true) { let nextToken = this._lexer.peek(); let caseE: Expression | null | undefined; if (nextToken.type === TokenType.Case) { // --- Process "case" this._lexer.get(); caseE = this.getExpression(); if (!caseE) return null; } else if (nextToken.type === TokenType.Default) { // --- Process "default" if (defaultCaseFound) { this.reportError("W016"); return null; } defaultCaseFound = true; this._lexer.get(); } else if (nextToken.type === TokenType.RBrace) { break; } else { this.reportError("W015"); return null; } // --- Process label statements this.expectToken(TokenType.Colon, "W008"); let stmts: Statement[] = []; let collected = false; while (!collected) { const stmtToken = this._lexer.peek(); switch (stmtToken.type) { case TokenType.Case: case TokenType.Default: case TokenType.RBrace: // --- No more case to process collected = true; break; default: // --- Try to get the next statement const stmt = this.parseStatement(); if (stmt === null) { collected = true; break; } stmts.push(stmt); if (stmt.type !== T_EMPTY_STATEMENT) { this.skipToken(TokenType.Semicolon); } } } cases.push( this.createNode( T_SWITCH_CASE, { caseE, stmts, }, startToken, ), ); } // --- Closing const endToken = this._lexer.peek(); this.expectToken(TokenType.RBrace, "W004"); return this.createStatementNode<SwitchStatement>( T_SWITCH_STATEMENT, { expr, cases, }, startToken, endToken, ); } /** * Parses a function declaration * * functionDeclaration * : "function" identifier "(" [parameterList] ")" blockStatement * ; */ private parseFunctionDeclaration(allowNoName = false): FunctionDeclaration | null { const startToken = this._lexer.get(); // --- Get the function name let functionName: string | undefined; const funcId = this._lexer.peek(); if (allowNoName) { if (funcId.type !== TokenType.LParent) { if (funcId.type !== TokenType.Identifier) { this.reportError("W003", funcId); return null; } functionName = funcId.text; this._lexer.get(); } } else { if (funcId.type !== TokenType.Identifier) { this.reportError("W003", funcId); return null; } functionName = funcId.text; this._lexer.get(); } // --- Get the parameter list; const nextToken = this._lexer.peek(); if (nextToken.type !== TokenType.LParent) { this.reportError("W014", nextToken); return null; } // --- Now, get the parameter list as an expression const exprList = this.getExpression(true); // --- We turn the expression into a parameter list let isValid: boolean; const args: Expression[] = []; switch (exprList!.type) { case T_NO_ARG_EXPRESSION: isValid = true; break; case T_IDENTIFIER: isValid = (exprList.parenthesized ?? 0) <= 1; args.push(exprList); break; case T_SEQUENCE_EXPRESSION: isValid = exprList.parenthesized === 1; let spreadFound = false; if (isValid) { for (const expr of exprList.exprs) { // --- Spread operator can be used only in the last position if (spreadFound) { isValid = false; break; } switch (expr.type) { case T_IDENTIFIER: isValid = !expr.parenthesized; args.push(expr); break; case T_OBJECT_LITERAL: { isValid = !expr.parenthesized; if (isValid) { const des = this.convertToObjectDestructure(expr); if (des) args.push(des); } break; } case T_ARRAY_LITERAL: { isValid = !expr.parenthesized; if (isValid) { const des = this.convertToArrayDestructure(expr); if (des) args.push(des); } break; } case T_SPREAD_EXPRESSION: { spreadFound = true; if (expr.expr.type !== T_IDENTIFIER) { isValid = false; break; } args.push(expr); break; } default: isValid = false; break; } } } break; case T_OBJECT_LITERAL: isValid = exprList.parenthesized === 1; if (isValid) { const des = this.convertToObjectDestructure(exprList); if (des) args.push(des); } break; case T_ARRAY_LITERAL: isValid = exprList.parenthesized === 1; if (isValid) { const des = this.convertToArrayDestructure(exprList); if (des) args.push(des); } break; case T_SPREAD_EXPRESSION: if (exprList.expr.type !== T_IDENTIFIER) { isValid = false; break; } isValid = true; args.push(exprList); break; default: isValid = false; } if (!isValid) { this.reportError("W010", startToken); return null; } // --- Get the function body (statement block) if (this._lexer.peek().type !== TokenType.LBrace) { this.reportError("W012", this._lexer.peek()); return null; } const stmt = this.parseBlockStatement(); if (!stmt) return null; // --- Done. return this.createStatementNode<FunctionDeclaration>( T_FUNCTION_DECLARATION, { id: { type: T_IDENTIFIER, name: functionName }, args, stmt, }, startToken, stmt.endToken, ); } // ========================================================================== // Expression parsing /** * Parses an expression: * * expr * : sequenceExpr * ; */ parseExpr(allowSequence = true): Expression | null { return allowSequence ? this.parseSequenceExpression() : this.parseCondOrSpreadOrAsgnOrArrowExpr(); } /** * sequenceExpr * : conditionalExpr ( "," conditionalExpr )? */ private parseSequenceExpression(): Expression | null { const start = this._lexer.peek(); let endToken: Token | undefined = start; let leftExpr = this.parseCondOrSpreadOrAsgnOrArrowExpr(); if (!leftExpr) { return null; } endToken = leftExpr.endToken; const exprs: Expression[] = []; let loose = false; if (this._lexer.peek().type === TokenType.Comma) { exprs.push(leftExpr); while (this.skipToken(TokenType.Comma)) { if (this._lexer.peek().type === TokenType.Comma) { loose = true; endToken = this._lexer.peek(); exprs.push( this.createExpressionNode<NoArgExpression>(T_NO_ARG_EXPRESSION, {}, endToken, endToken), ); } else { const nextExpr = this.parseCondOrSpreadOrAsgnOrArrowExpr(); if (!nextExpr) { break; } endToken = nextExpr.endToken; exprs.push(nextExpr); } } } if (!exprs.length) { // --- No sequence return leftExpr; } // --- Create the sequence expression leftExpr = this.createExpressionNode<SequenceExpression>( T_SEQUENCE_EXPRESSION, { exprs, loose, }, start, endToken, ); // --- Check for "loose" sequence expression if (loose) { leftExpr = this.convertToArrayDestructure(leftExpr); } // --- Done. return leftExpr; } /** * conditionalOrSpreadOrAsgnOrArrowExpr * : nullCoalescingExpr ( "?" expr ":" expr )? * | "..." nullCoalescingExpr * | identifier "=" expr * ; */ private parseCondOrSpreadOrAsgnOrArrowExpr(): Expression | null { const startToken = this._lexer.peek(); if (startToken.type === TokenType.Spread) { // --- Spread expression this._lexer.get(); const spreadOperand = this.parseNullCoalescingExpr(); return spreadOperand ? this.createExpressionNode<SpreadExpression>( T_SPREAD_EXPRESSION, { expr: spreadOperand, }, startToken, spreadOperand.endToken, ) : null; } if (startToken.type === TokenType.Function) { const funcDecl = this.parseFunctionDeclaration(true); return funcDecl ? this.createExpressionNode<ArrowExpression>( T_ARROW_EXPRESSION, { name: funcDecl.id.name, args: funcDecl.args, statement: funcDecl.stmt, }, startToken, funcDecl.endToken, ) : null; } const otherExpr = this.parseNullCoalescingExpr(); if (!otherExpr) { return null; } const nextToken = this._lexer.peek(); if (nextToken.type === TokenType.Arrow) { // --- It is an arrow expression return this.parseArrowExpression(startToken, otherExpr); } if (nextToken.type === TokenType.QuestionMark) { this._lexer.get(); // --- Conditional expression const trueExpr = this.getExpression(false); this.expectToken(TokenType.Colon); const falseExpr = this.getExpression(false); return this.createExpressionNode<ConditionalExpression>( T_CONDITIONAL_EXPRESSION, { cond: otherExpr, thenE: trueExpr, elseE: falseExpr, }, startToken, falseExpr!.endToken, ); } if (tokenTraits[nextToken.type].isAssignment) { // --- Assignment this._lexer.get(); const expr = this.getExpression(); return expr ? this.createExpressionNode<AssignmentExpression>( T_ASSIGNMENT_EXPRESSION, { leftValue: otherExpr, op: nextToken.text, expr, }, startToken, expr.endToken, ) : null; } return otherExpr; } /** * Parses an arrow expression * @param start Start token * @param left Expression to the left from the arrow */ private parseArrowExpression(start: Token, left: Expression): ArrowExpression | null { // --- Check the left expression let isValid: boolean; const args: Expression[] = []; switch (left.type) { case T_NO_ARG_EXPRESSION: isValid = true; break; case T_IDENTIFIER: isValid = (left.parenthesized ?? 0) <= 1; args.push(left); break; case T_SEQUENCE_EXPRESSION: isValid = left.parenthesized === 1; let spreadFound = false; if (isValid) { for (const expr of left.exprs) { if (spreadFound) { isValid = false; break; } switch (expr.type) { case T_IDENTIFIER: isValid = !expr.parenthesized; args.push(expr); break; case T_OBJECT_LITERAL: { isValid = !expr.parenthesized; if (isValid) { const des = this.convertToObjectDestructure(expr); if (des) args.push(des); } break; } case T_ARRAY_LITERAL: { isValid = !expr.parenthesized; if (isValid) { const des = this.convertToArrayDestructure(expr); if (des) args.push(des); } break; } case T_SPREAD_EXPRESSION: { spreadFound = true; if (expr.expr.type !== T_IDENTIFIER) { isValid = false; break; } args.push(expr); break; } default: isValid = false; break; } } } break; case T_OBJECT_LITERAL: isValid = left.parenthesized === 1; if (isValid) { const des = this.convertToObjectDestructure(left); if (des) args.push(des); } break; case T_ARRAY_LITERAL: isValid = left.parenthesized === 1; if (isValid) { const des = this.convertToArrayDestructure(left); if (des) args.push(des); } break; case T_SPREAD_EXPRESSION: isValid = left.expr.type === T_IDENTIFIER; if (isValid) { args.push(left); } break; default: isValid = false; } if (!isValid) { this.reportError("W010", start); return null; } // --- Skip the arrow token this._lexer.get(); // --- Get arrow expression statements const statement = this.parseStatement(false); return statement ? this.createExpressionNode<ArrowExpression>( T_ARROW_EXPRESSION, { args, statement, }, start, statement.endToken, ) : null; } /** * nullCoalescingExpr * : logicalOrExpr ( "??" logicalOrExpr )? * ; */ private parseNullCoalescingExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseLogicalOrExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.NullCoalesce)) { const rightExpr = this.parseLogicalOrExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "??", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * logicalOrExpr * : logicalAndExpr ( "||" logicalAndExpr )? * ; */ private parseLogicalOrExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseLogicalAndExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.LogicalOr)) { const rightExpr = this.parseLogicalAndExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "||", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * logicalAndExpr * : bitwiseOrExpr ( "&&" bitwiseOrExpr )? * ; */ private parseLogicalAndExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseBitwiseOrExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.LogicalAnd)) { const rightExpr = this.parseBitwiseOrExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "&&", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * bitwiseOrExpr * : bitwiseXorExpr ( "|" bitwiseXorExpr )? * ; */ private parseBitwiseOrExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseBitwiseXorExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.BitwiseOr)) { const rightExpr = this.parseBitwiseXorExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "|", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * bitwiseXorExpr * : bitwiseAndExpr ( "^" bitwiseAndExpr )? * ; */ private parseBitwiseXorExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseBitwiseAndExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.BitwiseXor)) { const rightExpr = this.parseBitwiseAndExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "^", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * bitwiseAndExpr * : equExpr ( "&" equExpr )? * ; */ private parseBitwiseAndExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseEquExpr(); if (!leftExpr) { return null; } while (this.skipToken(TokenType.BitwiseAnd)) { const rightExpr = this.parseEquExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: "&", left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * equExpr * : relOrInExpr ( ( "==" | "!=" | "===" | "!==" ) relOrInExpr )? * ; */ private parseEquExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseRelOrInExpr(); if (!leftExpr) { return null; } let opType: Token | null; while ( (opType = this.skipTokens( TokenType.Equal, TokenType.StrictEqual, TokenType.NotEqual, TokenType.StrictNotEqual, )) ) { const rightExpr = this.parseRelOrInExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * relOrInExpr * : shiftExpr ( ( "<" | "<=" | ">" | ">=", "in" ) shiftExpr )? * ; */ private parseRelOrInExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseShiftExpr(); if (!leftExpr) { return null; } let opType: Token | null; while ( (opType = this.skipTokens( TokenType.LessThan, TokenType.LessThanOrEqual, TokenType.GreaterThan, TokenType.GreaterThanOrEqual, TokenType.In, )) ) { const rightExpr = this.parseShiftExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * shiftExpr * : addExpr ( ( "<<" | ">>" | ">>>" ) addExpr )? * ; */ private parseShiftExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseAddExpr(); if (!leftExpr) { return null; } let opType: Token | null; while ( (opType = this.skipTokens( TokenType.ShiftLeft, TokenType.ShiftRight, TokenType.SignedShiftRight, )) ) { const rightExpr = this.parseAddExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * addExpr * : multExpr ( ( "+" | "-" ) multExpr )? * ; */ private parseAddExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseMultExpr(); if (!leftExpr) { return null; } let opType: Token | null; while ((opType = this.skipTokens(TokenType.Plus, TokenType.Minus))) { const rightExpr = this.parseMultExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * multExpr * : exponentialExpr ( ( "*" | "/" | "%") exponentialExpr )? * ; */ private parseMultExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseExponentialExpr(); if (!leftExpr) { return null; } let opType: Token | null; while ((opType = this.skipTokens(TokenType.Multiply, TokenType.Divide, TokenType.Remainder))) { const rightExpr = this.parseExponentialExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } return leftExpr; } /** * exponentialExpr * : unaryExpr ( "**" unaryExpr )? * ; */ private parseExponentialExpr(): Expression | null { const startToken = this._lexer.peek(); let leftExpr = this.parseUnaryOrPrefixExpr(); if (!leftExpr) { return null; } let opType: Token | null; let count = 0; while ((opType = this.skipToken(TokenType.Exponent))) { let rightExpr = this.parseUnaryOrPrefixExpr(); if (!rightExpr) { this.reportError("W001"); return null; } let endToken = rightExpr.endToken; if (count === 0) { leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: leftExpr, right: rightExpr, }, startToken, endToken, ); } else { const prevLeft = leftExpr as BinaryExpression; leftExpr = this.createExpressionNode<BinaryExpression>( T_BINARY_EXPRESSION, { op: opType.text, left: prevLeft.left, right: { type: T_BINARY_EXPRESSION, op: opType.text, left: prevLeft.right, right: rightExpr, }, }, startToken, endToken, ); } count++; } return leftExpr; } /** * unaryExpr * : ( "typeof" | "delete" | "+" | "-" | "~" | "!" ) memberOrInvocationExpr * | memberOrInvocationExpr * ; */ private parseUnaryOrPrefixExpr(): Expression | null { const startToken = this._lexer.peek(); if (tokenTraits[startToken.type].canBeUnary) { this._lexer.get(); const unaryOperand = this.parseUnaryOrPrefixExpr(); if (!unaryOperand) { return null; } return this.createExpressionNode<UnaryExpression>( T_UNARY_EXPRESSION, { op: startToken.text, expr: unaryOperand, }, startToken, unaryOperand.endToken, ); } if (startToken.type === TokenType.IncOp || startToken.type === TokenType.DecOp) { this._lexer.get(); const prefixOperand = this.parseMemberOrInvocationExpr(); if (!prefixOperand) { return null; } return this.createExpressionNode<PrefixOpExpression>( T_PREFIX_OP_EXPRESSION, { op: startToken.text, expr: prefixOperand, }, startToken, prefixOperand.endToken, ); } // --- Not unary or prefix return this.parseMemberOrInvocationExpr(); } /** * memberOrInvocationExpr * : primaryExpr "(" functionArgs ")" * | primaryExpr "." identifier * | primaryExpr "?." identifier * | primaryExpr "[" expr "]" * ; */ private parseMemberOrInvocationExpr(): Expression | null { const startToken = this._lexer.peek(); let primary = this.parsePrimaryExpr(); if (!primary) { return null; } let exitLoop = false; do { const currentStart = this._lexer.peek(); switch (currentStart.type) { case TokenType.LParent: { this._lexer.get(); let args: Expression[] = []; if (this._lexer.peek().type !== TokenType.RParent) { const expr = this.parseExpr(); if (!expr) { this.reportError("W001"); return null; } args = expr.type === T_SEQUENCE_EXPRESSION ? expr.exprs : [expr]; } const endToken = this._lexer.peek(); this.expectToken(TokenType.RParent, "W006"); primary = this.createExpressionNode<FunctionInvocationExpression>( T_FUNCTION_INVOCATION_EXPRESSION, { obj: primary, arguments: args, }, startToken, endToken, ); break; } case TokenType.Dot: case TokenType.OptionalChaining: this._lexer.get(); const member = this._lexer.get(); const memberTrait = tokenTraits[member.type]; if (!memberTrait.keywordLike) { this.reportError("W003"); return null; } primary = this.createExpressionNode<MemberAccessExpression>( T_MEMBER_ACCESS_EXPRESSION, { obj: primary, member: member.text, opt: currentStart.type === TokenType.OptionalChaining, }, startToken, member, ); break; case TokenType.LSquare: this._lexer.get(); const memberExpr = this.getExpression(); if (!memberExpr) { return null; } const endToken = this._lexer.peek(); this.expectToken(TokenType.RSquare, "W005"); primary = this.createExpressionNode<CalculatedMemberAccessExpression>( T_CALCULATED_MEMBER_ACCESS_EXPRESSION, { obj: primary, member: memberExpr, }, startToken, endToken, ); break; default: exitLoop = true; break; } } while (!exitLoop); // --- Check for postfix operators const nextToken = this._lexer.peek(); if (nextToken.type === TokenType.IncOp || nextToken.type === TokenType.DecOp) { this._lexer.get(); return this.createExpressionNode<PostfixOpExpression>( T_POSTFIX_OP_EXPRESSION, { op: nextToken.text, expr: primary, }, startToken, nextToken, ); } return primary; } /** * primaryExpr * : literal * | identifier * | "::" identifier * | "$item" * | "(" expr ")" * ; */ private parsePrimaryExpr(): Expression | null { const start = this._lexer.peek(); switch (start.type) { case TokenType.LParent: // --- Parenthesised or no-arg expression this._lexer.get(); if (this._lexer.peek().type === TokenType.RParent) { // --- No-arg const endToken = this._lexer.get(); return this.createExpressionNode<NoArgExpression>( T_NO_ARG_EXPRESSION, {}, start, endToken, ); } // --- Parenthesized let parenthesizedExpr = this.parseExpr(); if (!parenthesizedExpr) { return null; } const endToken = this._lexer.peek(); this.expectToken(TokenType.RParent, "W006"); return { ...parenthesizedExpr, parenthesized: (parenthesizedExpr.parenthesized ?? 0) + 1, startToken: start, endToken, }; case TokenType.Identifier: { const idToken = this._lexer.get(); return this.createExpressionNode<Identifier>( T_IDENTIFIER, { name: idToken.text, }, idToken, idToken, ); } case TokenType.Global: { this._lexer.get(); const idToken = this._lexer.get(); if (idToken.type !== TokenType.Identifier) { this.reportError("W003"); return null; } return this.createExpressionNode<Identifier>( T_IDENTIFIER, { name: idToken.text, isGlobal: true, }, idToken, idToken, ); } case TokenType.Backtick: return this.parseTemplateLiteral(); case TokenType.False: case TokenType.True: this._lexer.get(); return this.createExpressionNode<Literal>( T_LITERAL, { value: start.type === TokenType.True, }, start, start, ); case TokenType.BinaryLiteral: this._lexer.get(); return this.parseBinaryLiteral(start); case TokenType.DecimalLiteral: this._lexer.get(); return this.parseDecimalLiteral(start); case TokenType.HexadecimalLiteral: this._lexer.get(); return this.parseHexadecimalLiteral(start); case TokenType.RealLiteral: this._lexer.get(); return this.parseRealLiteral(start); case TokenType.StringLiteral: this._lexer.get(); return this.parseStringLiteral(start); case TokenType.Infinity: this._lexer.get(); return this.createExpressionNode<Literal>( T_LITERAL, { value: Infinity, }, start, start, ); case TokenType.NaN: this._lexer.get(); return this.createExpressionNode<Literal>( T_LITERAL, { value: NaN, }, start, start, ); case TokenType.Null: this._lexer.get(); return this.createExpressionNode<Literal>( T_LITERAL, { value: null, }, start, start, ); case TokenType.Undefined: this._lexer.get(); return this.createExpressionNode<Literal>( T_LITERAL, { value: undefined, }, start, start, ); case TokenType.LSquare: return this.parseArrayLiteral(); case TokenType.LBrace: return this.parseObjectLiteral(); case TokenType.Divide: return this.parseRegExpLiteral(); } return null; } private parseTemplateLiteral(): TemplateLiteralExpression { const startToken = this._lexer.get(); this._lexer.setStartingPhaseToTemplateLiteral(); const segments: (Literal | Expression)[] = []; loop: while (true) { let nextToken = this._lexer.peek(); switch (nextToken.type) { case TokenType.StringLiteral: this._lexer.get(); const str = this.parseStringLiteral(nextToken, false); segments.push(str); break; case TokenType.DollarLBrace: this._lexer.get(); const innerExpr = this.parseExpr(); segments.push(innerExpr); this.expectToken(TokenType.RBrace, "W004"); this._lexer.setStartingPhaseToTemplateLiteral(); break; case TokenType.Backtick: break loop; default: this.reportError("W004"); } } const endToken = this._lexer.get(); return this.createExpressionNode<TemplateLiteralExpression>( T_TEMPLATE_LITERAL_EXPRESSION, { segments }, startToken, endToken, ); } /** * Parses an array literal */ private parseArrayLiteral(): ArrayLiteral | null { const start = this._lexer.get(); let expressions: Expression[] = []; if (this._lexer.peek().type !== TokenType.RSquare) { const expr = this.getExpression(); if (expr) { expressions = expr.type === T_SEQUENCE_EXPRESSION ? expr.exprs : [expr]; } } const endToken = this._lexer.peek(); this.expectToken(TokenType.RSquare); return this.createExpressionNode<ArrayLiteral>( T_ARRAY_LITERAL, { items: expressions, }, start, endToken, ); } /** * Parses an object literal */ private parseObjectLiteral(): ObjectLiteral | null { const start = this._lexer.get(); let props: (SpreadExpression | [Expression, Expression])[] = []; if (this._lexer.peek().type !== TokenType.RBrace) { while (this._lexer.peek().type !== TokenType.RBrace) { // --- Check the next token const nextToken = this._lexer.peek(); const traits = tokenTraits[nextToken.type]; let nameExpr: Expression | null; // --- Get property name or calculated property name if (traits.expressionStart) { if (nextToken.type === TokenType.LSquare) { this._lexer.get(); nameExpr = this.getExpression(); if (!nameExpr) { return null; } this.expectToken(TokenType.RSquare, "W005"); nameExpr = this.createExpressionNode<SequenceExpression>( T_SEQUENCE_EXPRESSION, { exprs: [nameExpr], }, start, ); } else if (traits.isPropLiteral) { nameExpr = this.getExpression(false); if (!nameExpr) { return null; } if ( nameExpr.type !== T_IDENTIFIER && nameExpr.type !== T_LITERAL && nameExpr.type !== T_SPREAD_EXPRESSION ) { this.reportError("W007"); return null; } } else { this.reportError("W007"); return null; } } else if (traits.keywordLike) { nameExpr = { type: T_IDENTIFIER, nodeId: createXmlUiTreeNodeId(), name: nextToken.text, startToken: nextToken, endToken: nextToken, }; this._lexer.get(); } else { this.reportError("W001"); return null; } const nameType = nameExpr.type; if (nameType === T_SPREAD_EXPRESSION) { props.push(nameExpr); } else { if (nameType === T_LITERAL) { const val = nameExpr.value; if (typeof val !== "number" && typeof val !== "string") { this.expectToken(TokenType.RBrace, "W007"); return null; } } // --- Value is optional, when we have a name let valueExpr: Expression | null = null; if (nameType === T_IDENTIFIER) { const nameFollowerToken = this._lexer.peek(); if ( nameFollowerToken.type === TokenType.Comma || nameFollowerToken.type === TokenType.RBrace ) { valueExpr = { ...nameExpr }; } } // --- Move to property value if (!valueExpr) { this.expectToken(TokenType.Colon, "W008"); valueExpr = this.getExpression(false); if (!valueExpr) { return null; } } props.push([nameExpr, valueExpr]); } // --- Test property termination const next = this._lexer.peek().type; if (next === TokenType.Comma) { this._lexer.get(); } else { if (next !== TokenType.RBrace) { break; } } } } const endToken = this._lexer.peek(); this.expectToken(TokenType.RBrace, "W004"); return this.createExpressionNode<ObjectLiteral>( T_OBJECT_LITERAL, { props, }, start, endToken, ); } private parseRegExpLiteral(): Literal | null { const startToken = this._lexer.peek(); const result = this._lexer.getRegEx(); if (result.success) { return this.createExpressionNode<Literal>( T_LITERAL, { value: new RegExp(result.pattern!, result.flags), }, startToken, this._lexer.peek(), ); } this.reportError("W002", startToken, result.pattern ?? ""); return null; } /** * Gets an expression */ private getExpression(allowSequence = true): Expression | null { const expr = this.parseExpr(allowSequence); if (expr) { return expr; } this.reportError("W001"); return null; } // ========================================================================== // Helpers /** * Tests the type of the next token * @param type Expected token type * @param errorCode Error to raise if the next token is not expected * @param allowEof Allow an EOF instead of the expected token? */ private expectToken(type: TokenType, errorCode?: ErrorCodes, allowEof?: boolean): Token | null { const next = this._lexer.peek(); if (next.type === type || (allowEof && next.type === TokenType.Eof)) { // --- Skip the expected token return this._lexer.get(); } this.reportError(errorCode ?? "W002", next, next.text); return null; } /** * Skips the next token if the type is the specified one * @param type Token type to check */ private skipToken(type: TokenType): Token | null { const next = this._lexer.peek(); if (next.type === type) { this._lexer.get(); return next; } return null; } /** * Skips the next token if the type is the specified one * @param types Token types to check */ private skipTokens(...types: TokenType[]): Token | null { const next = this._lexer.peek(); for (const type of types) { if (next.type === type) { this._lexer.get(); return next; } } return null; } /** * Reports the specified error * @param errorCode Error code * @param token Token that represents the error's position * @param options Error message options */ private reportError(errorCode: ErrorCodes, token?: Token, ...options: any[]): void { let errorText: string = errorMessages[errorCode] ?? "Unkonwn error"; if (options) { options.forEach( (o, idx) => (errorText = replace(errorText, `{${idx}}`, options[idx].toString())), ); } if (!token) { token = this._lexer.peek(); } this._parseErrors.push({ code: errorCode, text: errorText, line: token.startLine, column: token.startColumn, }); throw new ParserError(errorText, errorCode); function replace(input: string, placeholder: string, replacement: string): string { do { input = input.replace(placeholder, replacement); } while (input.includes(placeholder)); return input; } } /** * Creates an expression node * @param type Expression type * @param stump Stump properties * @param startToken The token that starts the expression * @param endToken The token that ends the expression * @param source Expression source code to store to the node */ private createNode<T extends ScripNodeBase>( type: ScripNodeBase["type"], stump: any, startToken: Token, endToken?: Token, ): T { if (!endToken) { endToken = this._lexer.peek(); } return Object.assign({}, stump, { type, startToken, endToken, } as ScripNodeBase); } /** * Creates an expression node * @param type Expression type * @param stump Stump properties * @param startToken The token that starts the expression * @param endToken The token that ends the expression * @param source Expression source code to store to the node */ private createExpressionNode<T extends Expression>( type: Expression["type"], stump: any = {}, startToken?: Token, endToken?: Token, ): T { if (!endToken) { endToken = this._lexer.peek(); } if (!startToken) { startToken = endToken; } return Object.assign({}, stump, { type, nodeId: createXmlUiTreeNodeId(), startToken, endToken, }); } /** * Creates a statement node * @param type Statement type * @param stump Stump properties * @param startToken The token that starts the statement * @param endToken The token that ends the statement */ private createStatementNode<T extends Statement>( type: Statement["type"], stump: any, startToken?: Token, endToken?: Token, ): T { return Object.assign({}, stump, { type, nodeId: createXmlUiTreeNodeId(), startToken, endToken, } as Statement); } /** * Parses a binary literal * @param token Literal token */ private parseBinaryLiteral(token: Token): Literal { let value: number | bigint; const bigValue = BigInt(token.text.replace(/[_']/g, "")); if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { value = bigValue; } else { value = parseInt(token.text.substring(2).replace(/[_']/g, ""), 2); } return this.createExpressionNode<Literal>( T_LITERAL, { value, }, token, token, ); } /** * Parses a decimal literal * @param token Literal token */ private parseDecimalLiteral(token: Token): Literal { let value: number | bigint; const bigValue = BigInt(token.text.replace(/[_']/g, "")); if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { value = bigValue; } else { value = parseInt(token.text.replace(/[_']/g, ""), 10); } return this.createExpressionNode<Literal>( T_LITERAL, { value, }, token, token, ); } /** * Parses a hexadecimal literal * @param token Literal token */ private parseHexadecimalLiteral(token: Token): Literal { let value: number | bigint; const bigValue = BigInt(token.text.replace(/[_']/g, "")); if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { value = bigValue; } else { value = parseInt(token.text.substring(2).replace(/[_']/g, ""), 16); } return this.createExpressionNode<Literal>( T_LITERAL, { value, }, token, token, ); } /** * Parses a real literal * @param token Literal token */ private parseRealLiteral(token: Token): Literal { let value = parseFloat(token.text.replace(/[_']/g, "")); return this.createExpressionNode<Literal>( T_LITERAL, { value, }, token, token, ); } /** * Converts a string token to intrinsic string * @param token Literal token */ private parseStringLiteral(token: Token, quoteSurrounded: boolean = true): Literal { let input = token.text; if (quoteSurrounded) { input = token.text.length < 2 ? "" : input.substring(1, input.length - 1); } let result = ""; let state: StrParseState = StrParseState.Normal; let collect = 0; for (const ch of input) { switch (state) { case StrParseState.Normal: if (ch === "\\") { state = StrParseState.Backslash; } else { result += ch; } break; case StrParseState.Backslash: state = StrParseState.Normal; switch (ch) { case "b": result += "\b"; break; case "f": result += "\f"; break; case "n": result += "\n"; break; case "r": result += "\r"; break; case "t": result += "\t"; break; case "v": result += "\v"; break; case "S": result += "\xa0"; break; case "0": result += String.fromCharCode(0x00); break; case "'": result += "'"; break; case '"': result += '"'; break; case "\\": result += "\\"; break; case "x": state = StrParseState.X; break; case "u": state = StrParseState.UX1; break; default: result += ch; break; } break; case StrParseState.X: if (isHexaDecimal(ch)) { collect = parseInt(ch, 16); state = StrParseState.Xh; } else { result += "x"; state = StrParseState.Normal; } break; case StrParseState.Xh: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); result += String.fromCharCode(collect); state = StrParseState.Normal; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.UX1: if (ch === "{") { state = StrParseState.Ucp1; break; } if (isHexaDecimal(ch)) { collect = parseInt(ch, 16); state = StrParseState.UX2; } else { result += "x"; state = StrParseState.Normal; } break; case StrParseState.UX2: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.UX3; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.UX3: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.UX4; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.UX4: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); result += String.fromCharCode(collect); state = StrParseState.Normal; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.Ucp1: if (isHexaDecimal(ch)) { collect = parseInt(ch, 16); state = StrParseState.Ucp2; } else { result += "x"; state = StrParseState.Normal; } break; case StrParseState.Ucp2: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.Ucp3; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.Ucp3: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.Ucp4; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.Ucp4: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.Ucp5; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.Ucp5: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.Ucp6; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.Ucp6: if (isHexaDecimal(ch)) { collect = collect * 0x10 + parseInt(ch, 16); state = StrParseState.UcpTail; } else { result += String.fromCharCode(collect); result += ch; state = StrParseState.Normal; } break; case StrParseState.UcpTail: result += String.fromCharCode(collect); if (ch !== "}") { result += ch; } state = StrParseState.Normal; break; } } // --- Handle the final machine state switch (state) { case StrParseState.Backslash: result += "\\"; break; case StrParseState.X: result += "x"; break; case StrParseState.Xh: result += String.fromCharCode(collect); break; } // --- Done return this.createExpressionNode<Literal>( T_LITERAL, { value: result, }, token, token, ); function isHexaDecimal(ch: string): boolean { return (ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F"); } } private convertToArrayDestructure(seq: SequenceExpression | ArrayLiteral): Destructure | null { const items = seq.type === T_SEQUENCE_EXPRESSION ? seq.exprs : seq.items; const result = this.createExpressionNode<Destructure>( T_DESTRUCTURE, { aDestr: [] }, seq.startToken, seq.endToken, ); // --- Convert all items for (const item of items) { let arrayD: ArrayDestructure | undefined; switch (item.type) { case T_NO_ARG_EXPRESSION: arrayD = this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, {}, item.startToken, item.endToken, ); break; case T_IDENTIFIER: arrayD = this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { id: item.name }, item.startToken, item.endToken, ); break; case T_DESTRUCTURE: result.aDestr!.push(...item.aDestr!); break; case T_ARRAY_DESTRUCTURE: arrayD = item; break; case T_ARRAY_LITERAL: { const destructure = this.convertToArrayDestructure(item); if (destructure) { arrayD = this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { aDestr: destructure.aDestr, }, item.startToken, item.endToken, ); } break; } case T_OBJECT_DESTRUCTURE: arrayD = this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { oDestr: item, }, item.startToken, item.endToken, ); break; case T_OBJECT_LITERAL: { const destructure = this.convertToObjectDestructure(item); if (destructure) { arrayD = this.createExpressionNode<ArrayDestructure>( T_ARRAY_DESTRUCTURE, { oDestr: destructure.oDestr, }, item.startToken, item.endToken, ); } break; } default: this.reportError("W017"); return null; } if (arrayD) result.aDestr?.push(arrayD); } // --- Done. return result; } private convertToObjectDestructure(objLit: ObjectLiteral): Destructure | null { const result = this.createExpressionNode<Destructure>( T_DESTRUCTURE, { oDestr: [] }, objLit.startToken, objLit.endToken, ); // --- Convert all items for (const prop of objLit.props) { if (Array.isArray(prop)) { } else { reportError("W018"); return null; } const [propKey, propValue] = prop; if (propKey.type !== T_IDENTIFIER) { reportError("W018"); return null; } let objD: ObjectDestructure | undefined; switch (propValue.type) { case T_IDENTIFIER: if (propValue.name === propKey.name) { objD = this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id: propKey.name }, propValue.startToken, propValue.endToken, ); } else { objD = this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id: propKey.name, alias: propValue.name, }, propValue.startToken, propValue.endToken, ); } break; case T_ARRAY_DESTRUCTURE: { objD = this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id: propKey.name, aDestr: propValue, }, propKey.startToken, propValue.endToken, ); break; } case T_ARRAY_LITERAL: { const destructure = this.convertToArrayDestructure(propValue); if (destructure) { objD = this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id: propKey.name, aDestr: destructure.aDestr, }, propKey.startToken, propValue.endToken, ); } break; } case T_OBJECT_DESTRUCTURE: objD = propValue; break; case T_OBJECT_LITERAL: { const destructure = this.convertToObjectDestructure(propValue); if (destructure) { objD = this.createExpressionNode<ObjectDestructure>( T_OBJECT_DESTRUCTURE, { id: propKey.name, oDestr: destructure.oDestr, }, propKey.startToken, propValue.endToken, ); } break; } default: this.reportError("W018"); return null; } if (objD) result.oDestr?.push(objD); } // --- Done. return result; } /** * Tests if the specified token can be the start of an expression */ private isExpressionStart(token: Token): boolean { return tokenTraits[token.type]?.expressionStart ?? false; } } ```