This is page 172 of 179. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/xmlui-markup-syntax-highlighting.png?lines=true&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 1 | import { 2 | type ArrayDestructure, 3 | type ArrayLiteral, 4 | type ArrowExpression, 5 | type AssignmentExpression, 6 | type ScripNodeBase, 7 | type BinaryExpression, 8 | type BlockStatement, 9 | type BreakStatement, 10 | type CalculatedMemberAccessExpression, 11 | type ConditionalExpression, 12 | type ConstStatement, 13 | type ContinueStatement, 14 | type Destructure, 15 | type DoWhileStatement, 16 | type EmptyStatement, 17 | type Expression, 18 | type ExpressionStatement, 19 | type ForInStatement, 20 | type ForOfStatement, 21 | type ForStatement, 22 | type ForVarBinding, 23 | type FunctionDeclaration, 24 | type FunctionInvocationExpression, 25 | type Identifier, 26 | type IfStatement, 27 | type LetStatement, 28 | type Literal, 29 | type MemberAccessExpression, 30 | type NoArgExpression, 31 | type ObjectDestructure, 32 | type ObjectLiteral, 33 | type PostfixOpExpression, 34 | type PrefixOpExpression, 35 | type ReactiveVarDeclaration, 36 | type ReturnStatement, 37 | type SequenceExpression, 38 | type SpreadExpression, 39 | type Statement, 40 | type SwitchCase, 41 | type SwitchStatement, 42 | type ThrowStatement, 43 | type TryStatement, 44 | type UnaryExpression, 45 | type VarDeclaration, 46 | type VarStatement, 47 | type WhileStatement, 48 | type TemplateLiteralExpression, 49 | T_EMPTY_STATEMENT, 50 | T_BREAK_STATEMENT, 51 | T_CONTINUE_STATEMENT, 52 | T_EXPRESSION_STATEMENT, 53 | T_VAR_DECLARATION, 54 | T_LET_STATEMENT, 55 | T_CONST_STATEMENT, 56 | T_REACTIVE_VAR_DECLARATION, 57 | T_VAR_STATEMENT, 58 | T_ARRAY_DESTRUCTURE, 59 | T_BLOCK_STATEMENT, 60 | T_IF_STATEMENT, 61 | T_WHILE_STATEMENT, 62 | T_OBJECT_DESTRUCTURE, 63 | T_DO_WHILE_STATEMENT, 64 | T_RETURN_STATEMENT, 65 | T_FOR_STATEMENT, 66 | T_FOR_IN_STATEMENT, 67 | T_FOR_OF_STATEMENT, 68 | T_IDENTIFIER, 69 | T_THROW_STATEMENT, 70 | T_TRY_STATEMENT, 71 | T_SWITCH_CASE, 72 | T_SWITCH_STATEMENT, 73 | T_NO_ARG_EXPRESSION, 74 | T_SEQUENCE_EXPRESSION, 75 | T_OBJECT_LITERAL, 76 | T_ARRAY_LITERAL, 77 | T_SPREAD_EXPRESSION, 78 | T_FUNCTION_DECLARATION, 79 | T_ARROW_EXPRESSION, 80 | T_CONDITIONAL_EXPRESSION, 81 | T_ASSIGNMENT_EXPRESSION, 82 | T_BINARY_EXPRESSION, 83 | T_UNARY_EXPRESSION, 84 | T_FUNCTION_INVOCATION_EXPRESSION, 85 | T_MEMBER_ACCESS_EXPRESSION, 86 | T_CALCULATED_MEMBER_ACCESS_EXPRESSION, 87 | T_POSTFIX_OP_EXPRESSION, 88 | T_PREFIX_OP_EXPRESSION, 89 | T_LITERAL, 90 | T_TEMPLATE_LITERAL_EXPRESSION, 91 | T_DESTRUCTURE, 92 | } from "../../components-core/script-runner/ScriptingSourceTree"; 93 | import type { GenericToken } from "../common/GenericToken"; 94 | import { InputStream } from "../common/InputStream"; 95 | import { Lexer } from "./Lexer"; 96 | import { ParserError, errorMessages } from "./ParserError"; 97 | import { tokenTraits } from "./TokenTrait"; 98 | import { TokenType } from "./TokenType"; 99 | import type { ErrorCodes, ParserErrorMessage } from "./ParserError"; 100 | 101 | type Token = GenericToken<TokenType>; 102 | 103 | /** 104 | * States of the string parsing 105 | */ 106 | enum StrParseState { 107 | Normal, 108 | Backslash, 109 | X, 110 | Xh, 111 | UX1, 112 | UX2, 113 | UX3, 114 | UX4, 115 | Ucp1, 116 | Ucp2, 117 | Ucp3, 118 | Ucp4, 119 | Ucp5, 120 | Ucp6, 121 | UcpTail, 122 | } 123 | 124 | let lastNodeId = 0; 125 | 126 | export function createXmlUiTreeNodeId(): number { 127 | return ++lastNodeId; 128 | } 129 | 130 | /** 131 | * This class parses a binding expression and transforms it into an evaluable expression tree 132 | */ 133 | export class Parser { 134 | // --- Keep track of error messages 135 | private _parseErrors: ParserErrorMessage[] = []; 136 | 137 | // --- Use this lexer 138 | private _lexer: Lexer; 139 | 140 | // --- Indicates the parsing level 141 | private _statementLevel = 0; 142 | 143 | /** 144 | * Initializes the parser with the specified source code 145 | * @param source Source code to parse 146 | */ 147 | constructor(source?: string) { 148 | this._lexer = new Lexer(new InputStream(source ?? "")); 149 | } 150 | 151 | /** 152 | * Sets the source code to parse 153 | * @param source Source code to parse 154 | */ 155 | setSource(source: string): void { 156 | this._lexer = new Lexer(new InputStream(source)); 157 | } 158 | 159 | /** 160 | * The errors raised during the parse phase 161 | */ 162 | get errors(): ParserErrorMessage[] { 163 | return this._parseErrors; 164 | } 165 | 166 | /** 167 | * Gets the current token 168 | */ 169 | get current(): Token { 170 | return this._lexer.peek(); 171 | } 172 | 173 | /** 174 | * Checks if we're at the end of the expression 175 | */ 176 | get isEof(): boolean { 177 | return this._lexer.peek().type === TokenType.Eof; 178 | } 179 | 180 | /** 181 | * Gets the characters remaining after parsing 182 | */ 183 | getTail(): string { 184 | return this._lexer.getTail(); 185 | } 186 | 187 | // ========================================================================== 188 | // Statement parsing 189 | 190 | /** 191 | * Parses a list of statements: 192 | * 193 | * statements 194 | * : statement* 195 | * ; 196 | * 197 | * statement 198 | * : emptyStatement 199 | * | expressionStatement 200 | * | letStatement 201 | * | returnStatement 202 | * ; 203 | */ 204 | parseStatements(): Statement[] | null { 205 | this._statementLevel = 0; 206 | const statements: Statement[] = []; 207 | while (!this.isEof) { 208 | const statement = this.parseStatement(); 209 | if (!statement) return null; 210 | statements.push(statement); 211 | if (statement.type !== T_EMPTY_STATEMENT) { 212 | this.skipToken(TokenType.Semicolon); 213 | } 214 | } 215 | return statements; 216 | } 217 | 218 | /** 219 | * Parses a single statement 220 | */ 221 | private parseStatement(allowSequence = true): Statement | null { 222 | this._statementLevel++; 223 | try { 224 | const startToken = this._lexer.peek(); 225 | switch (startToken.type) { 226 | case TokenType.Semicolon: 227 | return this.parseEmptyStatement(); 228 | case TokenType.Let: 229 | return this.parseLetStatement(); 230 | case TokenType.Const: 231 | return this.parseConstStatement(); 232 | case TokenType.Var: 233 | return this.parseVarStatement(); 234 | case TokenType.LBrace: 235 | return this.parseBlockStatement(); 236 | case TokenType.If: 237 | return this.parseIfStatement(); 238 | case TokenType.Do: 239 | return this.parseDoWhileStatement(); 240 | case TokenType.While: 241 | return this.parseWhileStatement(); 242 | case TokenType.Return: 243 | return this.parseReturnStatement(); 244 | case TokenType.Break: 245 | this._lexer.get(); 246 | return this.createStatementNode<BreakStatement>( 247 | T_BREAK_STATEMENT, 248 | {}, 249 | startToken, 250 | startToken, 251 | ); 252 | case TokenType.Continue: 253 | this._lexer.get(); 254 | return this.createStatementNode<ContinueStatement>( 255 | T_CONTINUE_STATEMENT, 256 | {}, 257 | startToken, 258 | startToken, 259 | ); 260 | case TokenType.For: 261 | return this.parseForStatement(); 262 | case TokenType.Throw: 263 | return this.parseThrowStatement(); 264 | case TokenType.Try: 265 | return this.parseTryStatement(); 266 | case TokenType.Switch: 267 | return this.parseSwitchStatement(); 268 | case TokenType.Function: 269 | return this.parseFunctionDeclaration(); 270 | default: 271 | if (this.isExpressionStart(startToken)) { 272 | return this.parseExpressionStatement(allowSequence); 273 | } 274 | this.reportError("W002", startToken, startToken.text); 275 | return null; 276 | } 277 | } finally { 278 | this._statementLevel--; 279 | } 280 | } 281 | 282 | /** 283 | * Parses an empty statement 284 | * 285 | * emptyStatement 286 | * : ";" 287 | * ; 288 | */ 289 | private parseEmptyStatement(): EmptyStatement | null { 290 | const startToken = this._lexer.get(); 291 | return this.createStatementNode<EmptyStatement>(T_EMPTY_STATEMENT, {}, startToken, startToken); 292 | } 293 | 294 | /** 295 | * Parses an expression statement 296 | * 297 | * expressionStatement 298 | * : expression 299 | * ; 300 | */ 301 | private parseExpressionStatement(allowSequence = true): ExpressionStatement | null { 302 | const startToken = this._lexer.peek(); 303 | const expr = this.getExpression(allowSequence); 304 | return expr 305 | ? this.createStatementNode<ExpressionStatement>( 306 | T_EXPRESSION_STATEMENT, 307 | { 308 | expr, 309 | }, 310 | startToken, 311 | expr.endToken, 312 | ) 313 | : null; 314 | } 315 | 316 | /** 317 | * Parses a let statement 318 | * 319 | * letStatement 320 | * : "let" id ["=" expression] ("," id ["=" expression])* 321 | * ; 322 | */ 323 | private parseLetStatement(): LetStatement | null { 324 | const startToken = this._lexer.get(); 325 | let endToken: Token | undefined = startToken; 326 | const decls: VarDeclaration[] = []; 327 | while (true) { 328 | const declStart = this._lexer.peek(); 329 | let declarationProps: any = {}; 330 | if (declStart.type === TokenType.LBrace) { 331 | // --- This is object destructure 332 | endToken = this._lexer.ahead(1); 333 | const oDestr = this.parseObjectDestructure(); 334 | if (oDestr === null) return null; 335 | declarationProps = { 336 | oDestr, 337 | }; 338 | endToken = oDestr.length > 0 ? oDestr[oDestr.length - 1].endToken : endToken; 339 | } else if (declStart.type === TokenType.LSquare) { 340 | // --- This is array destructure 341 | endToken = this._lexer.ahead(1); 342 | const aDestr = this.parseArrayDestructure(); 343 | if (aDestr === null) return null; 344 | declarationProps = { 345 | aDestr, 346 | }; 347 | endToken = aDestr.length > 0 ? aDestr[aDestr.length - 1].endToken : endToken; 348 | } else if (declStart.type === TokenType.Identifier) { 349 | if (declStart.text.startsWith("$")) { 350 | this.reportError("W031"); 351 | return null; 352 | } 353 | endToken = this._lexer.get(); 354 | declarationProps = { 355 | id: declStart.text, 356 | }; 357 | } else { 358 | this.reportError("W003"); 359 | return null; 360 | } 361 | 362 | // --- Optional initialization 363 | const initToken = this._lexer.peek(); 364 | let expr: Expression | null = null; 365 | if (initToken.type === TokenType.Assignment) { 366 | this._lexer.get(); 367 | expr = this.getExpression(false); 368 | if (expr === null) return null; 369 | declarationProps.expr = expr; 370 | endToken = expr.endToken; 371 | } else if (declarationProps.aDestr || declarationProps.oDestr) { 372 | this.reportError("W009", initToken); 373 | return null; 374 | } 375 | 376 | // --- New declaration reached 377 | decls.push( 378 | this.createExpressionNode<VarDeclaration>( 379 | T_VAR_DECLARATION, 380 | declarationProps, 381 | declStart, 382 | endToken, 383 | ), 384 | ); 385 | 386 | // --- Check for more declarations 387 | if (this._lexer.peek().type !== TokenType.Comma) break; 388 | this._lexer.get(); 389 | } 390 | 391 | // --- Done 392 | return this.createStatementNode<LetStatement>( 393 | T_LET_STATEMENT, 394 | { 395 | decls, 396 | }, 397 | startToken, 398 | endToken, 399 | ); 400 | } 401 | 402 | /** 403 | * Parses a const statement 404 | * 405 | * constStatement 406 | * : "const" id "=" expression 407 | * ; 408 | */ 409 | private parseConstStatement(): ConstStatement | null { 410 | const startToken = this._lexer.get(); 411 | let endToken: Token | undefined = startToken; 412 | const decls: VarDeclaration[] = []; 413 | while (true) { 414 | const declStart = this._lexer.peek(); 415 | let declarationProps: any = {}; 416 | if (declStart.type === TokenType.LBrace) { 417 | // --- This is object destructure 418 | endToken = this._lexer.ahead(1); 419 | const oDestr = this.parseObjectDestructure(); 420 | if (oDestr === null) return null; 421 | declarationProps = { 422 | oDestr, 423 | }; 424 | endToken = oDestr.length > 0 ? oDestr[oDestr.length - 1].endToken : endToken; 425 | } else if (declStart.type === TokenType.LSquare) { 426 | // --- This is array destructure 427 | endToken = this._lexer.ahead(1); 428 | const aDestr = this.parseArrayDestructure(); 429 | if (aDestr === null) return null; 430 | declarationProps = { 431 | aDestr, 432 | }; 433 | endToken = aDestr.length > 0 ? aDestr[aDestr.length - 1].endToken : endToken; 434 | } else if (declStart.type === TokenType.Identifier) { 435 | if (declStart.text.startsWith("$")) { 436 | this.reportError("W031"); 437 | return null; 438 | } 439 | endToken = this._lexer.get(); 440 | declarationProps = { 441 | id: declStart.text, 442 | }; 443 | } else { 444 | this.reportError("W003"); 445 | return null; 446 | } 447 | 448 | this.expectToken(TokenType.Assignment); 449 | const expr = this.getExpression(false); 450 | if (expr === null) return null; 451 | declarationProps.expr = expr; 452 | endToken = expr.endToken; 453 | 454 | // --- New declaration reached 455 | decls.push( 456 | this.createExpressionNode<VarDeclaration>( 457 | T_VAR_DECLARATION, 458 | declarationProps, 459 | declStart, 460 | endToken, 461 | ), 462 | ); 463 | 464 | // --- Check for more declarations 465 | if (this._lexer.peek().type !== TokenType.Comma) break; 466 | this._lexer.get(); 467 | } 468 | 469 | // --- Done 470 | return this.createStatementNode<ConstStatement>( 471 | T_CONST_STATEMENT, 472 | { 473 | decls, 474 | }, 475 | startToken, 476 | endToken, 477 | ); 478 | } 479 | 480 | /** 481 | * Parses a var statement 482 | * 483 | * constStatement 484 | * : "var" id "=" expression 485 | * ; 486 | */ 487 | private parseVarStatement(): VarStatement | null { 488 | const startToken = this._lexer.get(); 489 | let endToken: Token | undefined = startToken; 490 | const decls: ReactiveVarDeclaration[] = []; 491 | while (true) { 492 | const declStart = this._lexer.peek(); 493 | let declarationProps: any = {}; 494 | if (declStart.type === TokenType.Identifier) { 495 | if (declStart.text.startsWith("$")) { 496 | this.reportError("W031"); 497 | return null; 498 | } 499 | endToken = this._lexer.get(); 500 | declarationProps = { 501 | id: { 502 | type: T_IDENTIFIER, 503 | name: declStart.text, 504 | startToken: declStart, 505 | endToken, 506 | }, 507 | }; 508 | } else { 509 | this.reportError("W003"); 510 | return null; 511 | } 512 | 513 | // --- Mandatory initialization 514 | this.expectToken(TokenType.Assignment); 515 | const expr = this.getExpression(false); 516 | if (expr === null) return null; 517 | declarationProps.expr = expr; 518 | endToken = expr.endToken; 519 | // --- New declaration reached 520 | decls.push( 521 | this.createExpressionNode<ReactiveVarDeclaration>( 522 | T_REACTIVE_VAR_DECLARATION, 523 | declarationProps, 524 | declStart, 525 | endToken, 526 | ), 527 | ); 528 | 529 | // --- Check for more declarations 530 | if (this._lexer.peek().type !== TokenType.Comma) break; 531 | this._lexer.get(); 532 | } 533 | 534 | // --- Done 535 | return this.createStatementNode<VarStatement>( 536 | T_VAR_STATEMENT, 537 | { 538 | decls, 539 | }, 540 | startToken, 541 | endToken, 542 | ); 543 | } 544 | 545 | /** 546 | * Parses an object destructure expression 547 | */ 548 | private parseObjectDestructure(): ObjectDestructure[] | null { 549 | const result: ObjectDestructure[] = []; 550 | 551 | // --- Skip the starting left brace 552 | const startToken = this._lexer.get(); 553 | let endToken: Token | undefined = startToken; 554 | 555 | let nextToken = this._lexer.peek(); 556 | while (nextToken.type === TokenType.Identifier) { 557 | // --- Obtain the ID 558 | const id = nextToken.text; 559 | if (id.startsWith("$")) { 560 | this.reportError("W031"); 561 | return null; 562 | } 563 | 564 | let alias: string | undefined; 565 | let aDestr: ArrayDestructure[] | undefined | null; 566 | let oDestr: ObjectDestructure[] | undefined | null; 567 | this._lexer.get(); 568 | 569 | nextToken = this._lexer.peek(); 570 | if (nextToken.type === TokenType.Colon) { 571 | // --- Check the available cases 572 | this._lexer.get(); 573 | nextToken = this._lexer.peek(); 574 | if (nextToken.type === TokenType.Identifier) { 575 | alias = nextToken.text; 576 | endToken = nextToken; 577 | this._lexer.get(); 578 | } else if (nextToken.type === TokenType.LSquare) { 579 | aDestr = this.parseArrayDestructure(); 580 | if (aDestr === null) return null; 581 | endToken = aDestr[aDestr.length - 1].endToken; 582 | } else if (nextToken.type === TokenType.LBrace) { 583 | oDestr = this.parseObjectDestructure(); 584 | if (oDestr === null) return null; 585 | endToken = oDestr[oDestr.length - 1].endToken; 586 | } 587 | } 588 | 589 | // --- Check for next segment 590 | nextToken = this._lexer.peek(); 591 | if (nextToken.type === TokenType.Comma || nextToken.type === TokenType.RBrace) { 592 | result.push( 593 | this.createExpressionNode<ObjectDestructure>( 594 | T_OBJECT_DESTRUCTURE, 595 | { id, alias, aDestr, oDestr }, 596 | startToken, 597 | endToken, 598 | ), 599 | ); 600 | 601 | if (nextToken.type === TokenType.Comma) { 602 | // --- Skip the delimiter comma 603 | this._lexer.get(); 604 | nextToken = this._lexer.peek(); 605 | } 606 | } 607 | } 608 | 609 | // --- Expect a closing right brace 610 | this.expectToken(TokenType.RBrace, "W004"); 611 | return result; 612 | } 613 | 614 | private parseArrayDestructure(): ArrayDestructure[] | null { 615 | const result: ArrayDestructure[] = []; 616 | 617 | // --- Skip the starting left square 618 | const startToken = this._lexer.get(); 619 | let endToken: Token | undefined = startToken; 620 | 621 | do { 622 | let nextToken = this._lexer.peek(); 623 | let id: string | undefined; 624 | let aDestr: ArrayDestructure[] | undefined | null; 625 | let oDestr: ObjectDestructure[] | undefined | null; 626 | if (nextToken.type === TokenType.Identifier) { 627 | id = nextToken.text; 628 | if (id.startsWith("$")) { 629 | this.reportError("W031"); 630 | return null; 631 | } 632 | 633 | endToken = nextToken; 634 | nextToken = this._lexer.get(); 635 | } else if (nextToken.type === TokenType.LSquare) { 636 | aDestr = this.parseArrayDestructure(); 637 | if (aDestr === null) return null; 638 | endToken = aDestr[aDestr.length - 1].endToken; 639 | } else if (nextToken.type === TokenType.LBrace) { 640 | oDestr = this.parseObjectDestructure(); 641 | if (oDestr === null) return null; 642 | endToken = oDestr[oDestr.length - 1].endToken; 643 | } 644 | 645 | nextToken = this._lexer.peek(); 646 | if (nextToken.type === TokenType.Comma) { 647 | // --- Store the segment 648 | result.push( 649 | this.createExpressionNode<ArrayDestructure>( 650 | T_ARRAY_DESTRUCTURE, 651 | { id, aDestr, oDestr }, 652 | startToken, 653 | endToken, 654 | ), 655 | ); 656 | this._lexer.get(); 657 | } else if (nextToken.type === TokenType.RSquare) { 658 | if (id || aDestr || oDestr) { 659 | // --- Store a non-empty the segment 660 | result.push( 661 | this.createExpressionNode<ArrayDestructure>( 662 | T_ARRAY_DESTRUCTURE, 663 | { id, aDestr, oDestr }, 664 | startToken, 665 | endToken, 666 | ), 667 | ); 668 | } 669 | break; 670 | } else { 671 | this.reportError("W002", nextToken); 672 | return null; 673 | } 674 | } while (true); 675 | 676 | // --- Expect a closing right square 677 | this.expectToken(TokenType.RSquare, "W005"); 678 | return result; 679 | } 680 | 681 | /** 682 | * Parses a block statement 683 | * 684 | * blockStatement 685 | * : "{" (statement [";"])* "}" 686 | * ; 687 | */ 688 | private parseBlockStatement(): BlockStatement | null { 689 | const startToken = this._lexer.get(); 690 | const stmts: Statement[] = []; 691 | while (this._lexer.peek().type !== TokenType.RBrace) { 692 | const statement = this.parseStatement(); 693 | if (!statement) return null; 694 | stmts.push(statement); 695 | if (statement.type !== T_EMPTY_STATEMENT) { 696 | this.skipToken(TokenType.Semicolon); 697 | } 698 | } 699 | const endToken = this._lexer.get(); 700 | return this.createStatementNode<BlockStatement>( 701 | T_BLOCK_STATEMENT, 702 | { stmts }, 703 | startToken, 704 | endToken, 705 | ); 706 | } 707 | 708 | /** 709 | * Parses an if statement 710 | * 711 | * ifStatement 712 | * : "if" "(" expression ")" statement ["else" statement] 713 | * ; 714 | */ 715 | private parseIfStatement(): IfStatement | null { 716 | const startToken = this._lexer.get(); 717 | let endToken: Token | undefined = startToken; 718 | this.expectToken(TokenType.LParent, "W014"); 719 | const cond = this.getExpression(); 720 | if (!cond) return null; 721 | this.expectToken(TokenType.RParent, "W006"); 722 | const thenB = this.parseStatement(); 723 | if (!thenB) return null; 724 | endToken = thenB.endToken; 725 | let elseCanFollow = true; 726 | if (thenB.type !== T_BLOCK_STATEMENT) { 727 | // --- We need a closing semicolon before else 728 | if (this._lexer.peek().type === TokenType.Semicolon) { 729 | this._lexer.get(); 730 | } else { 731 | elseCanFollow = false; 732 | } 733 | } 734 | let elseB: Statement | null = null; 735 | if (elseCanFollow && this._lexer.peek().type === TokenType.Else) { 736 | this._lexer.get(); 737 | elseB = this.parseStatement(); 738 | if (!elseB) return null; 739 | endToken = elseB.endToken; 740 | } 741 | return this.createStatementNode<IfStatement>( 742 | T_IF_STATEMENT, 743 | { 744 | cond, 745 | thenB, 746 | elseB, 747 | }, 748 | startToken, 749 | endToken, 750 | ); 751 | } 752 | 753 | /** 754 | * Parses a while statement 755 | * 756 | * whileStatement 757 | * : "while" "(" condition ")" statement 758 | * ; 759 | */ 760 | private parseWhileStatement(): WhileStatement | null { 761 | const startToken = this._lexer.get(); 762 | this.expectToken(TokenType.LParent, "W014"); 763 | const cond = this.getExpression(); 764 | if (!cond) return null; 765 | this.expectToken(TokenType.RParent, "W006"); 766 | const body = this.parseStatement(); 767 | if (!body) return null; 768 | 769 | return this.createStatementNode<WhileStatement>( 770 | T_WHILE_STATEMENT, 771 | { 772 | cond, 773 | body, 774 | }, 775 | startToken, 776 | body.endToken, 777 | ); 778 | } 779 | 780 | /** 781 | * Parses a do-while statement 782 | * 783 | * doWhileStatement 784 | * : "do" statement "while" "(" condition ")" 785 | * ; 786 | */ 787 | private parseDoWhileStatement(): DoWhileStatement | null { 788 | const startToken = this._lexer.get(); 789 | const body = this.parseStatement(); 790 | if (!body) return null; 791 | if (body.type !== T_BLOCK_STATEMENT && body.type !== T_EMPTY_STATEMENT) { 792 | this.expectToken(TokenType.Semicolon); 793 | } 794 | this.expectToken(TokenType.While); 795 | this.expectToken(TokenType.LParent, "W014"); 796 | const cond = this.getExpression(); 797 | if (!cond) return null; 798 | const endToken = this._lexer.peek(); 799 | this.expectToken(TokenType.RParent, "W006"); 800 | 801 | return this.createStatementNode<DoWhileStatement>( 802 | T_DO_WHILE_STATEMENT, 803 | { 804 | cond, 805 | body, 806 | }, 807 | startToken, 808 | endToken, 809 | ); 810 | } 811 | 812 | /** 813 | * Parses an expression statement 814 | * 815 | * returnStatement 816 | * : "return" expression? 817 | * ; 818 | */ 819 | private parseReturnStatement(): ReturnStatement | null { 820 | const startToken = this._lexer.peek(); 821 | let endToken: Token | undefined = this._lexer.get(); 822 | let expr: Expression | undefined | null; 823 | if (tokenTraits[this._lexer.peek().type].expressionStart) { 824 | expr = this.getExpression(); 825 | if (expr === null) return null; 826 | endToken = expr.endToken; 827 | } 828 | return this.createStatementNode<ReturnStatement>( 829 | T_RETURN_STATEMENT, 830 | { 831 | expr, 832 | }, 833 | startToken, 834 | endToken, 835 | ); 836 | } 837 | 838 | /** 839 | * forStatement 840 | * : "for" "(" initStatement? ";" expression? ";" expression? ")" statement 841 | * | forInOfStatement 842 | * ; 843 | */ 844 | private parseForStatement(): ForStatement | ForInStatement | ForOfStatement | null { 845 | const startToken = this._lexer.peek(); 846 | this._lexer.get(); 847 | this.expectToken(TokenType.LParent, "W014"); 848 | 849 | // --- Check for..in and for..of 850 | let nextToken = this._lexer.peek(); 851 | if (nextToken.type === TokenType.Identifier) { 852 | if (this._lexer.ahead(1).type === TokenType.In) { 853 | return this.parseForInOfStatement(startToken, "none", nextToken, T_FOR_IN_STATEMENT); 854 | } else if (this._lexer.ahead(1).type === TokenType.Of) { 855 | return this.parseForInOfStatement(startToken, "none", nextToken, T_FOR_OF_STATEMENT); 856 | } 857 | } else if (nextToken.type === TokenType.Let) { 858 | const idToken = this._lexer.ahead(1); 859 | if (idToken.type === TokenType.Identifier) { 860 | const inOfToken = this._lexer.ahead(2); 861 | if (inOfToken.type === TokenType.In) { 862 | return this.parseForInOfStatement(startToken, "let", idToken, T_FOR_IN_STATEMENT); 863 | } else if (inOfToken.type === TokenType.Of) { 864 | return this.parseForInOfStatement(startToken, "let", idToken, T_FOR_OF_STATEMENT); 865 | } 866 | } 867 | } else if (nextToken.type === TokenType.Const) { 868 | const idToken = this._lexer.ahead(1); 869 | if (idToken.type === TokenType.Identifier) { 870 | const inOfToken = this._lexer.ahead(2); 871 | if (inOfToken.type === TokenType.In) { 872 | return this.parseForInOfStatement(startToken, "const", idToken, T_FOR_IN_STATEMENT); 873 | } else if (inOfToken.type === TokenType.Of) { 874 | return this.parseForInOfStatement(startToken, "const", idToken, T_FOR_OF_STATEMENT); 875 | } 876 | } 877 | } 878 | 879 | // --- We accept only let statement, empty statement, and expression 880 | let init: ExpressionStatement | LetStatement | undefined; 881 | nextToken = this._lexer.peek(); 882 | if (nextToken.type === TokenType.Semicolon) { 883 | // --- Empty statement: no init in the for loop 884 | this._lexer.get(); 885 | } else if (nextToken.type === TokenType.Let) { 886 | // --- Let statement 887 | const letStmt = this.parseLetStatement(); 888 | if (letStmt === null) { 889 | return null; 890 | } 891 | init = letStmt; 892 | if (init.decls.some((d) => !d.expr)) { 893 | this.reportError("W011"); 894 | return null; 895 | } 896 | this.expectToken(TokenType.Semicolon); 897 | } else if (tokenTraits[nextToken.type].expressionStart) { 898 | // --- Expression statement 899 | const exprStmt = this.parseExpressionStatement(); 900 | if (exprStmt === null) { 901 | return null; 902 | } 903 | init = exprStmt; 904 | this.expectToken(TokenType.Semicolon); 905 | } 906 | 907 | // --- Parse the condition 908 | let cond: Expression | null | undefined; 909 | nextToken = this._lexer.peek(); 910 | if (nextToken.type === TokenType.Semicolon) { 911 | // --- No condition 912 | this._lexer.get(); 913 | } else { 914 | cond = this.getExpression(); 915 | if (cond === null) { 916 | return null; 917 | } 918 | this.expectToken(TokenType.Semicolon); 919 | } 920 | 921 | // --- Parse the update expression 922 | let upd: Expression | null | undefined; 923 | nextToken = this._lexer.peek(); 924 | if (nextToken.type !== TokenType.RParent) { 925 | upd = this.getExpression(); 926 | if (upd === null) { 927 | return null; 928 | } 929 | } 930 | 931 | // --- Close the declaration part 932 | this.expectToken(TokenType.RParent, "W006"); 933 | 934 | // --- Get the body 935 | const body = this.parseStatement(); 936 | if (!body) return null; 937 | 938 | return this.createStatementNode<ForStatement>( 939 | T_FOR_STATEMENT, 940 | { 941 | init, 942 | cond, 943 | upd, 944 | body, 945 | }, 946 | startToken, 947 | body.endToken, 948 | ); 949 | } 950 | 951 | /** 952 | * forInOfStatement 953 | * : "for" "(" [ "let" | "const" ] identifier ( "in" | "of" ) expression? ")" statement 954 | * | forInOfStatement 955 | * ; 956 | * 957 | * @param startToken Statement start token 958 | * @param varB Variable binding of the for..in/of statement 959 | * @param id ID name 960 | * @param type Is it a for..in or a for..of? 961 | */ 962 | private parseForInOfStatement( 963 | startToken: Token, 964 | varB: ForVarBinding, 965 | idToken: Token, 966 | type: number, 967 | ): ForInStatement | ForOfStatement | null { 968 | if (varB !== "none") { 969 | // --- Skip variable binding type 970 | if (idToken.text.startsWith("$")) { 971 | this.reportError("W031"); 972 | return null; 973 | } 974 | this._lexer.get(); 975 | } 976 | 977 | // --- Skip variable name, in/of token 978 | this._lexer.get(); 979 | this._lexer.get(); 980 | 981 | // --- Get the expression 982 | const expr = this.getExpression(true); 983 | 984 | // --- Close the declaration part 985 | this.expectToken(TokenType.RParent, "W006"); 986 | 987 | // --- Get the body 988 | const body = this.parseStatement(); 989 | if (!body) return null; 990 | 991 | return type === T_FOR_IN_STATEMENT 992 | ? this.createStatementNode<ForInStatement>( 993 | T_FOR_IN_STATEMENT, 994 | { 995 | varB, 996 | id: { 997 | type: T_IDENTIFIER, 998 | name: idToken.text, 999 | startToken: idToken, 1000 | endToken: idToken, 1001 | }, 1002 | expr, 1003 | body, 1004 | }, 1005 | startToken, 1006 | body.endToken, 1007 | ) 1008 | : this.createStatementNode<ForOfStatement>( 1009 | T_FOR_OF_STATEMENT, 1010 | { 1011 | varB, 1012 | id: { 1013 | type: T_IDENTIFIER, 1014 | name: idToken.text, 1015 | startToken: idToken, 1016 | endToken: idToken, 1017 | }, 1018 | expr, 1019 | body, 1020 | }, 1021 | startToken, 1022 | body.endToken, 1023 | ); 1024 | } 1025 | 1026 | /** 1027 | * Parses a throw statement 1028 | * 1029 | * throwStatement 1030 | * : "throw" expression 1031 | * ; 1032 | */ 1033 | private parseThrowStatement(): ThrowStatement | null { 1034 | const startToken = this._lexer.peek(); 1035 | this._lexer.get(); 1036 | let expr: Expression | undefined | null; 1037 | expr = this.getExpression(); 1038 | if (expr === null) return null; 1039 | return this.createStatementNode<ThrowStatement>( 1040 | T_THROW_STATEMENT, 1041 | { 1042 | expr, 1043 | }, 1044 | startToken, 1045 | expr.endToken, 1046 | ); 1047 | } 1048 | 1049 | /** 1050 | * Parses a try..catch..finally statement 1051 | * 1052 | * tryStatement 1053 | * : "try" blockStatement catchClause finallyClause? 1054 | * | "try" blockStatement catchClause? finallyClause 1055 | * ; 1056 | * 1057 | * catchClause 1058 | * : "catch" [ "(" identifier ") ]? blockStatement 1059 | * ; 1060 | * 1061 | * finallyClause 1062 | * : "finally" blockStatement 1063 | */ 1064 | private parseTryStatement(): TryStatement | null { 1065 | const getBlock: () => BlockStatement | null = () => { 1066 | const nextToken = this._lexer.peek(); 1067 | if (nextToken.type !== TokenType.LBrace) { 1068 | this.reportError("W012", nextToken); 1069 | return null; 1070 | } 1071 | return this.parseBlockStatement(); 1072 | }; 1073 | 1074 | const startToken = this._lexer.peek(); 1075 | let endToken: Token | undefined = this._lexer.get(); 1076 | 1077 | // --- Get "try" block 1078 | const tryB = getBlock()!; 1079 | let catchB: BlockStatement | undefined; 1080 | let catchV: Identifier | undefined; 1081 | let finallyB: BlockStatement | undefined; 1082 | let nextToken = this._lexer.peek(); 1083 | if (nextToken.type === TokenType.Catch) { 1084 | // --- Catch found 1085 | this._lexer.get(); 1086 | nextToken = this._lexer.peek(); 1087 | if (nextToken.type === TokenType.LParent) { 1088 | // --- Catch variable found 1089 | this._lexer.get(); 1090 | nextToken = this._lexer.peek(); 1091 | if (nextToken.type !== TokenType.Identifier) { 1092 | this.reportError("W003", nextToken); 1093 | return null; 1094 | } 1095 | catchV = { 1096 | type: T_IDENTIFIER, 1097 | nodeId: createXmlUiTreeNodeId(), 1098 | name: nextToken.text, 1099 | startToken: nextToken, 1100 | endToken: nextToken, 1101 | }; 1102 | this._lexer.get(); 1103 | this.expectToken(TokenType.RParent, "W006"); 1104 | } 1105 | 1106 | // --- Get catch block 1107 | catchB = getBlock()!; 1108 | endToken = catchB.endToken; 1109 | if (this._lexer.peek().type === TokenType.Finally) { 1110 | // --- Finally after catch 1111 | this._lexer.get(); 1112 | finallyB = getBlock()!; 1113 | endToken = finallyB.endToken; 1114 | } 1115 | } else if (nextToken.type === TokenType.Finally) { 1116 | // --- Finally found 1117 | this._lexer.get(); 1118 | finallyB = getBlock()!; 1119 | endToken = finallyB.endToken; 1120 | } else { 1121 | this.reportError("W013", nextToken); 1122 | return null; 1123 | } 1124 | 1125 | return this.createStatementNode<TryStatement>( 1126 | T_TRY_STATEMENT, 1127 | { 1128 | tryB, 1129 | catchB, 1130 | catchV, 1131 | finallyB, 1132 | }, 1133 | startToken, 1134 | endToken, 1135 | ); 1136 | } 1137 | 1138 | /** 1139 | * Parses a switch statement 1140 | * 1141 | * switchStatement 1142 | * : "switch" "(" expression ")" "{" caseClauses "}" 1143 | * ; 1144 | * 1145 | * caseClauses 1146 | * : "case" expression ":" statement* 1147 | * | "default" ":" statement* 1148 | * ; 1149 | */ 1150 | private parseSwitchStatement(): SwitchStatement | null { 1151 | const startToken = this._lexer.get(); 1152 | 1153 | // --- Parse the switch expression 1154 | this.expectToken(TokenType.LParent, "W014"); 1155 | const expr = this.getExpression(); 1156 | if (!expr) return null; 1157 | this.expectToken(TokenType.RParent, "W006"); 1158 | 1159 | // --- Parse the case blocks 1160 | this.expectToken(TokenType.LBrace, "W012"); 1161 | const cases: SwitchCase[] = []; 1162 | let defaultCaseFound = false; 1163 | while (true) { 1164 | let nextToken = this._lexer.peek(); 1165 | let caseE: Expression | null | undefined; 1166 | if (nextToken.type === TokenType.Case) { 1167 | // --- Process "case" 1168 | this._lexer.get(); 1169 | caseE = this.getExpression(); 1170 | if (!caseE) return null; 1171 | } else if (nextToken.type === TokenType.Default) { 1172 | // --- Process "default" 1173 | if (defaultCaseFound) { 1174 | this.reportError("W016"); 1175 | return null; 1176 | } 1177 | defaultCaseFound = true; 1178 | this._lexer.get(); 1179 | } else if (nextToken.type === TokenType.RBrace) { 1180 | break; 1181 | } else { 1182 | this.reportError("W015"); 1183 | return null; 1184 | } 1185 | 1186 | // --- Process label statements 1187 | this.expectToken(TokenType.Colon, "W008"); 1188 | let stmts: Statement[] = []; 1189 | let collected = false; 1190 | while (!collected) { 1191 | const stmtToken = this._lexer.peek(); 1192 | switch (stmtToken.type) { 1193 | case TokenType.Case: 1194 | case TokenType.Default: 1195 | case TokenType.RBrace: 1196 | // --- No more case to process 1197 | collected = true; 1198 | break; 1199 | default: 1200 | // --- Try to get the next statement 1201 | const stmt = this.parseStatement(); 1202 | if (stmt === null) { 1203 | collected = true; 1204 | break; 1205 | } 1206 | stmts.push(stmt); 1207 | if (stmt.type !== T_EMPTY_STATEMENT) { 1208 | this.skipToken(TokenType.Semicolon); 1209 | } 1210 | } 1211 | } 1212 | 1213 | cases.push( 1214 | this.createNode( 1215 | T_SWITCH_CASE, 1216 | { 1217 | caseE, 1218 | stmts, 1219 | }, 1220 | startToken, 1221 | ), 1222 | ); 1223 | } 1224 | 1225 | // --- Closing 1226 | const endToken = this._lexer.peek(); 1227 | this.expectToken(TokenType.RBrace, "W004"); 1228 | return this.createStatementNode<SwitchStatement>( 1229 | T_SWITCH_STATEMENT, 1230 | { 1231 | expr, 1232 | cases, 1233 | }, 1234 | startToken, 1235 | endToken, 1236 | ); 1237 | } 1238 | 1239 | /** 1240 | * Parses a function declaration 1241 | * 1242 | * functionDeclaration 1243 | * : "function" identifier "(" [parameterList] ")" blockStatement 1244 | * ; 1245 | */ 1246 | private parseFunctionDeclaration(allowNoName = false): FunctionDeclaration | null { 1247 | const startToken = this._lexer.get(); 1248 | 1249 | // --- Get the function name 1250 | let functionName: string | undefined; 1251 | const funcId = this._lexer.peek(); 1252 | if (allowNoName) { 1253 | if (funcId.type !== TokenType.LParent) { 1254 | if (funcId.type !== TokenType.Identifier) { 1255 | this.reportError("W003", funcId); 1256 | return null; 1257 | } 1258 | functionName = funcId.text; 1259 | this._lexer.get(); 1260 | } 1261 | } else { 1262 | if (funcId.type !== TokenType.Identifier) { 1263 | this.reportError("W003", funcId); 1264 | return null; 1265 | } 1266 | functionName = funcId.text; 1267 | this._lexer.get(); 1268 | } 1269 | 1270 | // --- Get the parameter list; 1271 | const nextToken = this._lexer.peek(); 1272 | if (nextToken.type !== TokenType.LParent) { 1273 | this.reportError("W014", nextToken); 1274 | return null; 1275 | } 1276 | 1277 | // --- Now, get the parameter list as an expression 1278 | const exprList = this.getExpression(true); 1279 | 1280 | // --- We turn the expression into a parameter list 1281 | let isValid: boolean; 1282 | const args: Expression[] = []; 1283 | switch (exprList!.type) { 1284 | case T_NO_ARG_EXPRESSION: 1285 | isValid = true; 1286 | break; 1287 | case T_IDENTIFIER: 1288 | isValid = (exprList.parenthesized ?? 0) <= 1; 1289 | args.push(exprList); 1290 | break; 1291 | case T_SEQUENCE_EXPRESSION: 1292 | isValid = exprList.parenthesized === 1; 1293 | let spreadFound = false; 1294 | if (isValid) { 1295 | for (const expr of exprList.exprs) { 1296 | // --- Spread operator can be used only in the last position 1297 | if (spreadFound) { 1298 | isValid = false; 1299 | break; 1300 | } 1301 | switch (expr.type) { 1302 | case T_IDENTIFIER: 1303 | isValid = !expr.parenthesized; 1304 | args.push(expr); 1305 | break; 1306 | case T_OBJECT_LITERAL: { 1307 | isValid = !expr.parenthesized; 1308 | if (isValid) { 1309 | const des = this.convertToObjectDestructure(expr); 1310 | if (des) args.push(des); 1311 | } 1312 | break; 1313 | } 1314 | case T_ARRAY_LITERAL: { 1315 | isValid = !expr.parenthesized; 1316 | if (isValid) { 1317 | const des = this.convertToArrayDestructure(expr); 1318 | if (des) args.push(des); 1319 | } 1320 | break; 1321 | } 1322 | case T_SPREAD_EXPRESSION: { 1323 | spreadFound = true; 1324 | if (expr.expr.type !== T_IDENTIFIER) { 1325 | isValid = false; 1326 | break; 1327 | } 1328 | args.push(expr); 1329 | break; 1330 | } 1331 | default: 1332 | isValid = false; 1333 | break; 1334 | } 1335 | } 1336 | } 1337 | break; 1338 | case T_OBJECT_LITERAL: 1339 | isValid = exprList.parenthesized === 1; 1340 | if (isValid) { 1341 | const des = this.convertToObjectDestructure(exprList); 1342 | if (des) args.push(des); 1343 | } 1344 | break; 1345 | case T_ARRAY_LITERAL: 1346 | isValid = exprList.parenthesized === 1; 1347 | if (isValid) { 1348 | const des = this.convertToArrayDestructure(exprList); 1349 | if (des) args.push(des); 1350 | } 1351 | break; 1352 | case T_SPREAD_EXPRESSION: 1353 | if (exprList.expr.type !== T_IDENTIFIER) { 1354 | isValid = false; 1355 | break; 1356 | } 1357 | isValid = true; 1358 | args.push(exprList); 1359 | break; 1360 | default: 1361 | isValid = false; 1362 | } 1363 | if (!isValid) { 1364 | this.reportError("W010", startToken); 1365 | return null; 1366 | } 1367 | 1368 | // --- Get the function body (statement block) 1369 | if (this._lexer.peek().type !== TokenType.LBrace) { 1370 | this.reportError("W012", this._lexer.peek()); 1371 | return null; 1372 | } 1373 | 1374 | const stmt = this.parseBlockStatement(); 1375 | if (!stmt) return null; 1376 | 1377 | // --- Done. 1378 | return this.createStatementNode<FunctionDeclaration>( 1379 | T_FUNCTION_DECLARATION, 1380 | { 1381 | id: { type: T_IDENTIFIER, name: functionName }, 1382 | args, 1383 | stmt, 1384 | }, 1385 | startToken, 1386 | stmt.endToken, 1387 | ); 1388 | } 1389 | 1390 | // ========================================================================== 1391 | // Expression parsing 1392 | 1393 | /** 1394 | * Parses an expression: 1395 | * 1396 | * expr 1397 | * : sequenceExpr 1398 | * ; 1399 | */ 1400 | parseExpr(allowSequence = true): Expression | null { 1401 | return allowSequence 1402 | ? this.parseSequenceExpression() 1403 | : this.parseCondOrSpreadOrAsgnOrArrowExpr(); 1404 | } 1405 | 1406 | /** 1407 | * sequenceExpr 1408 | * : conditionalExpr ( "," conditionalExpr )? 1409 | */ 1410 | private parseSequenceExpression(): Expression | null { 1411 | const start = this._lexer.peek(); 1412 | let endToken: Token | undefined = start; 1413 | let leftExpr = this.parseCondOrSpreadOrAsgnOrArrowExpr(); 1414 | if (!leftExpr) { 1415 | return null; 1416 | } 1417 | endToken = leftExpr.endToken; 1418 | const exprs: Expression[] = []; 1419 | let loose = false; 1420 | 1421 | if (this._lexer.peek().type === TokenType.Comma) { 1422 | exprs.push(leftExpr); 1423 | while (this.skipToken(TokenType.Comma)) { 1424 | if (this._lexer.peek().type === TokenType.Comma) { 1425 | loose = true; 1426 | endToken = this._lexer.peek(); 1427 | exprs.push( 1428 | this.createExpressionNode<NoArgExpression>(T_NO_ARG_EXPRESSION, {}, endToken, endToken), 1429 | ); 1430 | } else { 1431 | const nextExpr = this.parseCondOrSpreadOrAsgnOrArrowExpr(); 1432 | if (!nextExpr) { 1433 | break; 1434 | } 1435 | endToken = nextExpr.endToken; 1436 | exprs.push(nextExpr); 1437 | } 1438 | } 1439 | } 1440 | 1441 | if (!exprs.length) { 1442 | // --- No sequence 1443 | return leftExpr; 1444 | } 1445 | 1446 | // --- Create the sequence expression 1447 | leftExpr = this.createExpressionNode<SequenceExpression>( 1448 | T_SEQUENCE_EXPRESSION, 1449 | { 1450 | exprs, 1451 | loose, 1452 | }, 1453 | start, 1454 | endToken, 1455 | ); 1456 | 1457 | // --- Check for "loose" sequence expression 1458 | if (loose) { 1459 | leftExpr = this.convertToArrayDestructure(leftExpr); 1460 | } 1461 | 1462 | // --- Done. 1463 | return leftExpr; 1464 | } 1465 | 1466 | /** 1467 | * conditionalOrSpreadOrAsgnOrArrowExpr 1468 | * : nullCoalescingExpr ( "?" expr ":" expr )? 1469 | * | "..." nullCoalescingExpr 1470 | * | identifier "=" expr 1471 | * ; 1472 | */ 1473 | private parseCondOrSpreadOrAsgnOrArrowExpr(): Expression | null { 1474 | const startToken = this._lexer.peek(); 1475 | if (startToken.type === TokenType.Spread) { 1476 | // --- Spread expression 1477 | this._lexer.get(); 1478 | const spreadOperand = this.parseNullCoalescingExpr(); 1479 | return spreadOperand 1480 | ? this.createExpressionNode<SpreadExpression>( 1481 | T_SPREAD_EXPRESSION, 1482 | { 1483 | expr: spreadOperand, 1484 | }, 1485 | startToken, 1486 | spreadOperand.endToken, 1487 | ) 1488 | : null; 1489 | } 1490 | 1491 | if (startToken.type === TokenType.Function) { 1492 | const funcDecl = this.parseFunctionDeclaration(true); 1493 | return funcDecl 1494 | ? this.createExpressionNode<ArrowExpression>( 1495 | T_ARROW_EXPRESSION, 1496 | { 1497 | name: funcDecl.id.name, 1498 | args: funcDecl.args, 1499 | statement: funcDecl.stmt, 1500 | }, 1501 | startToken, 1502 | funcDecl.endToken, 1503 | ) 1504 | : null; 1505 | } 1506 | 1507 | const otherExpr = this.parseNullCoalescingExpr(); 1508 | if (!otherExpr) { 1509 | return null; 1510 | } 1511 | 1512 | const nextToken = this._lexer.peek(); 1513 | if (nextToken.type === TokenType.Arrow) { 1514 | // --- It is an arrow expression 1515 | return this.parseArrowExpression(startToken, otherExpr); 1516 | } 1517 | 1518 | if (nextToken.type === TokenType.QuestionMark) { 1519 | this._lexer.get(); 1520 | // --- Conditional expression 1521 | const trueExpr = this.getExpression(false); 1522 | this.expectToken(TokenType.Colon); 1523 | const falseExpr = this.getExpression(false); 1524 | 1525 | return this.createExpressionNode<ConditionalExpression>( 1526 | T_CONDITIONAL_EXPRESSION, 1527 | { 1528 | cond: otherExpr, 1529 | thenE: trueExpr, 1530 | elseE: falseExpr, 1531 | }, 1532 | startToken, 1533 | falseExpr!.endToken, 1534 | ); 1535 | } 1536 | 1537 | if (tokenTraits[nextToken.type].isAssignment) { 1538 | // --- Assignment 1539 | this._lexer.get(); 1540 | const expr = this.getExpression(); 1541 | return expr 1542 | ? this.createExpressionNode<AssignmentExpression>( 1543 | T_ASSIGNMENT_EXPRESSION, 1544 | { 1545 | leftValue: otherExpr, 1546 | op: nextToken.text, 1547 | expr, 1548 | }, 1549 | startToken, 1550 | expr.endToken, 1551 | ) 1552 | : null; 1553 | } 1554 | 1555 | return otherExpr; 1556 | } 1557 | 1558 | /** 1559 | * Parses an arrow expression 1560 | * @param start Start token 1561 | * @param left Expression to the left from the arrow 1562 | */ 1563 | private parseArrowExpression(start: Token, left: Expression): ArrowExpression | null { 1564 | // --- Check the left expression 1565 | let isValid: boolean; 1566 | const args: Expression[] = []; 1567 | switch (left.type) { 1568 | case T_NO_ARG_EXPRESSION: 1569 | isValid = true; 1570 | break; 1571 | case T_IDENTIFIER: 1572 | isValid = (left.parenthesized ?? 0) <= 1; 1573 | args.push(left); 1574 | break; 1575 | case T_SEQUENCE_EXPRESSION: 1576 | isValid = left.parenthesized === 1; 1577 | let spreadFound = false; 1578 | if (isValid) { 1579 | for (const expr of left.exprs) { 1580 | if (spreadFound) { 1581 | isValid = false; 1582 | break; 1583 | } 1584 | switch (expr.type) { 1585 | case T_IDENTIFIER: 1586 | isValid = !expr.parenthesized; 1587 | args.push(expr); 1588 | break; 1589 | case T_OBJECT_LITERAL: { 1590 | isValid = !expr.parenthesized; 1591 | if (isValid) { 1592 | const des = this.convertToObjectDestructure(expr); 1593 | if (des) args.push(des); 1594 | } 1595 | break; 1596 | } 1597 | case T_ARRAY_LITERAL: { 1598 | isValid = !expr.parenthesized; 1599 | if (isValid) { 1600 | const des = this.convertToArrayDestructure(expr); 1601 | if (des) args.push(des); 1602 | } 1603 | break; 1604 | } 1605 | case T_SPREAD_EXPRESSION: { 1606 | spreadFound = true; 1607 | if (expr.expr.type !== T_IDENTIFIER) { 1608 | isValid = false; 1609 | break; 1610 | } 1611 | args.push(expr); 1612 | break; 1613 | } 1614 | default: 1615 | isValid = false; 1616 | break; 1617 | } 1618 | } 1619 | } 1620 | break; 1621 | case T_OBJECT_LITERAL: 1622 | isValid = left.parenthesized === 1; 1623 | if (isValid) { 1624 | const des = this.convertToObjectDestructure(left); 1625 | if (des) args.push(des); 1626 | } 1627 | break; 1628 | case T_ARRAY_LITERAL: 1629 | isValid = left.parenthesized === 1; 1630 | if (isValid) { 1631 | const des = this.convertToArrayDestructure(left); 1632 | if (des) args.push(des); 1633 | } 1634 | break; 1635 | case T_SPREAD_EXPRESSION: 1636 | isValid = left.expr.type === T_IDENTIFIER; 1637 | if (isValid) { 1638 | args.push(left); 1639 | } 1640 | break; 1641 | default: 1642 | isValid = false; 1643 | } 1644 | if (!isValid) { 1645 | this.reportError("W010", start); 1646 | return null; 1647 | } 1648 | 1649 | // --- Skip the arrow token 1650 | this._lexer.get(); 1651 | 1652 | // --- Get arrow expression statements 1653 | const statement = this.parseStatement(false); 1654 | return statement 1655 | ? this.createExpressionNode<ArrowExpression>( 1656 | T_ARROW_EXPRESSION, 1657 | { 1658 | args, 1659 | statement, 1660 | }, 1661 | start, 1662 | statement.endToken, 1663 | ) 1664 | : null; 1665 | } 1666 | 1667 | /** 1668 | * nullCoalescingExpr 1669 | * : logicalOrExpr ( "??" logicalOrExpr )? 1670 | * ; 1671 | */ 1672 | private parseNullCoalescingExpr(): Expression | null { 1673 | const startToken = this._lexer.peek(); 1674 | let leftExpr = this.parseLogicalOrExpr(); 1675 | if (!leftExpr) { 1676 | return null; 1677 | } 1678 | while (this.skipToken(TokenType.NullCoalesce)) { 1679 | const rightExpr = this.parseLogicalOrExpr(); 1680 | if (!rightExpr) { 1681 | this.reportError("W001"); 1682 | return null; 1683 | } 1684 | let endToken = rightExpr.endToken; 1685 | leftExpr = this.createExpressionNode<BinaryExpression>( 1686 | T_BINARY_EXPRESSION, 1687 | { 1688 | op: "??", 1689 | left: leftExpr, 1690 | right: rightExpr, 1691 | }, 1692 | startToken, 1693 | endToken, 1694 | ); 1695 | } 1696 | return leftExpr; 1697 | } 1698 | 1699 | /** 1700 | * logicalOrExpr 1701 | * : logicalAndExpr ( "||" logicalAndExpr )? 1702 | * ; 1703 | */ 1704 | private parseLogicalOrExpr(): Expression | null { 1705 | const startToken = this._lexer.peek(); 1706 | let leftExpr = this.parseLogicalAndExpr(); 1707 | if (!leftExpr) { 1708 | return null; 1709 | } 1710 | 1711 | while (this.skipToken(TokenType.LogicalOr)) { 1712 | const rightExpr = this.parseLogicalAndExpr(); 1713 | if (!rightExpr) { 1714 | this.reportError("W001"); 1715 | return null; 1716 | } 1717 | let endToken = rightExpr.endToken; 1718 | leftExpr = this.createExpressionNode<BinaryExpression>( 1719 | T_BINARY_EXPRESSION, 1720 | { 1721 | op: "||", 1722 | left: leftExpr, 1723 | right: rightExpr, 1724 | }, 1725 | startToken, 1726 | endToken, 1727 | ); 1728 | } 1729 | return leftExpr; 1730 | } 1731 | 1732 | /** 1733 | * logicalAndExpr 1734 | * : bitwiseOrExpr ( "&&" bitwiseOrExpr )? 1735 | * ; 1736 | */ 1737 | private parseLogicalAndExpr(): Expression | null { 1738 | const startToken = this._lexer.peek(); 1739 | let leftExpr = this.parseBitwiseOrExpr(); 1740 | if (!leftExpr) { 1741 | return null; 1742 | } 1743 | 1744 | while (this.skipToken(TokenType.LogicalAnd)) { 1745 | const rightExpr = this.parseBitwiseOrExpr(); 1746 | if (!rightExpr) { 1747 | this.reportError("W001"); 1748 | return null; 1749 | } 1750 | let endToken = rightExpr.endToken; 1751 | leftExpr = this.createExpressionNode<BinaryExpression>( 1752 | T_BINARY_EXPRESSION, 1753 | { 1754 | op: "&&", 1755 | left: leftExpr, 1756 | right: rightExpr, 1757 | }, 1758 | startToken, 1759 | endToken, 1760 | ); 1761 | } 1762 | return leftExpr; 1763 | } 1764 | 1765 | /** 1766 | * bitwiseOrExpr 1767 | * : bitwiseXorExpr ( "|" bitwiseXorExpr )? 1768 | * ; 1769 | */ 1770 | private parseBitwiseOrExpr(): Expression | null { 1771 | const startToken = this._lexer.peek(); 1772 | let leftExpr = this.parseBitwiseXorExpr(); 1773 | if (!leftExpr) { 1774 | return null; 1775 | } 1776 | 1777 | while (this.skipToken(TokenType.BitwiseOr)) { 1778 | const rightExpr = this.parseBitwiseXorExpr(); 1779 | if (!rightExpr) { 1780 | this.reportError("W001"); 1781 | return null; 1782 | } 1783 | let endToken = rightExpr.endToken; 1784 | leftExpr = this.createExpressionNode<BinaryExpression>( 1785 | T_BINARY_EXPRESSION, 1786 | { 1787 | op: "|", 1788 | left: leftExpr, 1789 | right: rightExpr, 1790 | }, 1791 | startToken, 1792 | endToken, 1793 | ); 1794 | } 1795 | return leftExpr; 1796 | } 1797 | 1798 | /** 1799 | * bitwiseXorExpr 1800 | * : bitwiseAndExpr ( "^" bitwiseAndExpr )? 1801 | * ; 1802 | */ 1803 | private parseBitwiseXorExpr(): Expression | null { 1804 | const startToken = this._lexer.peek(); 1805 | let leftExpr = this.parseBitwiseAndExpr(); 1806 | if (!leftExpr) { 1807 | return null; 1808 | } 1809 | 1810 | while (this.skipToken(TokenType.BitwiseXor)) { 1811 | const rightExpr = this.parseBitwiseAndExpr(); 1812 | if (!rightExpr) { 1813 | this.reportError("W001"); 1814 | return null; 1815 | } 1816 | let endToken = rightExpr.endToken; 1817 | leftExpr = this.createExpressionNode<BinaryExpression>( 1818 | T_BINARY_EXPRESSION, 1819 | { 1820 | op: "^", 1821 | left: leftExpr, 1822 | right: rightExpr, 1823 | }, 1824 | startToken, 1825 | endToken, 1826 | ); 1827 | } 1828 | return leftExpr; 1829 | } 1830 | 1831 | /** 1832 | * bitwiseAndExpr 1833 | * : equExpr ( "&" equExpr )? 1834 | * ; 1835 | */ 1836 | private parseBitwiseAndExpr(): Expression | null { 1837 | const startToken = this._lexer.peek(); 1838 | let leftExpr = this.parseEquExpr(); 1839 | if (!leftExpr) { 1840 | return null; 1841 | } 1842 | 1843 | while (this.skipToken(TokenType.BitwiseAnd)) { 1844 | const rightExpr = this.parseEquExpr(); 1845 | if (!rightExpr) { 1846 | this.reportError("W001"); 1847 | return null; 1848 | } 1849 | let endToken = rightExpr.endToken; 1850 | leftExpr = this.createExpressionNode<BinaryExpression>( 1851 | T_BINARY_EXPRESSION, 1852 | { 1853 | op: "&", 1854 | left: leftExpr, 1855 | right: rightExpr, 1856 | }, 1857 | startToken, 1858 | endToken, 1859 | ); 1860 | } 1861 | return leftExpr; 1862 | } 1863 | 1864 | /** 1865 | * equExpr 1866 | * : relOrInExpr ( ( "==" | "!=" | "===" | "!==" ) relOrInExpr )? 1867 | * ; 1868 | */ 1869 | private parseEquExpr(): Expression | null { 1870 | const startToken = this._lexer.peek(); 1871 | let leftExpr = this.parseRelOrInExpr(); 1872 | if (!leftExpr) { 1873 | return null; 1874 | } 1875 | 1876 | let opType: Token | null; 1877 | while ( 1878 | (opType = this.skipTokens( 1879 | TokenType.Equal, 1880 | TokenType.StrictEqual, 1881 | TokenType.NotEqual, 1882 | TokenType.StrictNotEqual, 1883 | )) 1884 | ) { 1885 | const rightExpr = this.parseRelOrInExpr(); 1886 | if (!rightExpr) { 1887 | this.reportError("W001"); 1888 | return null; 1889 | } 1890 | let endToken = rightExpr.endToken; 1891 | leftExpr = this.createExpressionNode<BinaryExpression>( 1892 | T_BINARY_EXPRESSION, 1893 | { 1894 | op: opType.text, 1895 | left: leftExpr, 1896 | right: rightExpr, 1897 | }, 1898 | startToken, 1899 | endToken, 1900 | ); 1901 | } 1902 | return leftExpr; 1903 | } 1904 | 1905 | /** 1906 | * relOrInExpr 1907 | * : shiftExpr ( ( "<" | "<=" | ">" | ">=", "in" ) shiftExpr )? 1908 | * ; 1909 | */ 1910 | private parseRelOrInExpr(): Expression | null { 1911 | const startToken = this._lexer.peek(); 1912 | let leftExpr = this.parseShiftExpr(); 1913 | if (!leftExpr) { 1914 | return null; 1915 | } 1916 | 1917 | let opType: Token | null; 1918 | while ( 1919 | (opType = this.skipTokens( 1920 | TokenType.LessThan, 1921 | TokenType.LessThanOrEqual, 1922 | TokenType.GreaterThan, 1923 | TokenType.GreaterThanOrEqual, 1924 | TokenType.In, 1925 | )) 1926 | ) { 1927 | const rightExpr = this.parseShiftExpr(); 1928 | if (!rightExpr) { 1929 | this.reportError("W001"); 1930 | return null; 1931 | } 1932 | let endToken = rightExpr.endToken; 1933 | leftExpr = this.createExpressionNode<BinaryExpression>( 1934 | T_BINARY_EXPRESSION, 1935 | { 1936 | op: opType.text, 1937 | left: leftExpr, 1938 | right: rightExpr, 1939 | }, 1940 | startToken, 1941 | endToken, 1942 | ); 1943 | } 1944 | return leftExpr; 1945 | } 1946 | 1947 | /** 1948 | * shiftExpr 1949 | * : addExpr ( ( "<<" | ">>" | ">>>" ) addExpr )? 1950 | * ; 1951 | */ 1952 | private parseShiftExpr(): Expression | null { 1953 | const startToken = this._lexer.peek(); 1954 | let leftExpr = this.parseAddExpr(); 1955 | if (!leftExpr) { 1956 | return null; 1957 | } 1958 | 1959 | let opType: Token | null; 1960 | while ( 1961 | (opType = this.skipTokens( 1962 | TokenType.ShiftLeft, 1963 | TokenType.ShiftRight, 1964 | TokenType.SignedShiftRight, 1965 | )) 1966 | ) { 1967 | const rightExpr = this.parseAddExpr(); 1968 | if (!rightExpr) { 1969 | this.reportError("W001"); 1970 | return null; 1971 | } 1972 | let endToken = rightExpr.endToken; 1973 | leftExpr = this.createExpressionNode<BinaryExpression>( 1974 | T_BINARY_EXPRESSION, 1975 | { 1976 | op: opType.text, 1977 | left: leftExpr, 1978 | right: rightExpr, 1979 | }, 1980 | startToken, 1981 | endToken, 1982 | ); 1983 | } 1984 | return leftExpr; 1985 | } 1986 | 1987 | /** 1988 | * addExpr 1989 | * : multExpr ( ( "+" | "-" ) multExpr )? 1990 | * ; 1991 | */ 1992 | private parseAddExpr(): Expression | null { 1993 | const startToken = this._lexer.peek(); 1994 | let leftExpr = this.parseMultExpr(); 1995 | if (!leftExpr) { 1996 | return null; 1997 | } 1998 | 1999 | let opType: Token | null; 2000 | while ((opType = this.skipTokens(TokenType.Plus, TokenType.Minus))) { 2001 | const rightExpr = this.parseMultExpr(); 2002 | if (!rightExpr) { 2003 | this.reportError("W001"); 2004 | return null; 2005 | } 2006 | let endToken = rightExpr.endToken; 2007 | leftExpr = this.createExpressionNode<BinaryExpression>( 2008 | T_BINARY_EXPRESSION, 2009 | { 2010 | op: opType.text, 2011 | left: leftExpr, 2012 | right: rightExpr, 2013 | }, 2014 | startToken, 2015 | endToken, 2016 | ); 2017 | } 2018 | return leftExpr; 2019 | } 2020 | 2021 | /** 2022 | * multExpr 2023 | * : exponentialExpr ( ( "*" | "/" | "%") exponentialExpr )? 2024 | * ; 2025 | */ 2026 | private parseMultExpr(): Expression | null { 2027 | const startToken = this._lexer.peek(); 2028 | let leftExpr = this.parseExponentialExpr(); 2029 | if (!leftExpr) { 2030 | return null; 2031 | } 2032 | 2033 | let opType: Token | null; 2034 | while ((opType = this.skipTokens(TokenType.Multiply, TokenType.Divide, TokenType.Remainder))) { 2035 | const rightExpr = this.parseExponentialExpr(); 2036 | if (!rightExpr) { 2037 | this.reportError("W001"); 2038 | return null; 2039 | } 2040 | let endToken = rightExpr.endToken; 2041 | leftExpr = this.createExpressionNode<BinaryExpression>( 2042 | T_BINARY_EXPRESSION, 2043 | { 2044 | op: opType.text, 2045 | left: leftExpr, 2046 | right: rightExpr, 2047 | }, 2048 | startToken, 2049 | endToken, 2050 | ); 2051 | } 2052 | return leftExpr; 2053 | } 2054 | 2055 | /** 2056 | * exponentialExpr 2057 | * : unaryExpr ( "**" unaryExpr )? 2058 | * ; 2059 | */ 2060 | private parseExponentialExpr(): Expression | null { 2061 | const startToken = this._lexer.peek(); 2062 | let leftExpr = this.parseUnaryOrPrefixExpr(); 2063 | if (!leftExpr) { 2064 | return null; 2065 | } 2066 | 2067 | let opType: Token | null; 2068 | let count = 0; 2069 | while ((opType = this.skipToken(TokenType.Exponent))) { 2070 | let rightExpr = this.parseUnaryOrPrefixExpr(); 2071 | if (!rightExpr) { 2072 | this.reportError("W001"); 2073 | return null; 2074 | } 2075 | let endToken = rightExpr.endToken; 2076 | if (count === 0) { 2077 | leftExpr = this.createExpressionNode<BinaryExpression>( 2078 | T_BINARY_EXPRESSION, 2079 | { 2080 | op: opType.text, 2081 | left: leftExpr, 2082 | right: rightExpr, 2083 | }, 2084 | startToken, 2085 | endToken, 2086 | ); 2087 | } else { 2088 | const prevLeft = leftExpr as BinaryExpression; 2089 | leftExpr = this.createExpressionNode<BinaryExpression>( 2090 | T_BINARY_EXPRESSION, 2091 | { 2092 | op: opType.text, 2093 | left: prevLeft.left, 2094 | right: { 2095 | type: T_BINARY_EXPRESSION, 2096 | op: opType.text, 2097 | left: prevLeft.right, 2098 | right: rightExpr, 2099 | }, 2100 | }, 2101 | startToken, 2102 | endToken, 2103 | ); 2104 | } 2105 | count++; 2106 | } 2107 | return leftExpr; 2108 | } 2109 | 2110 | /** 2111 | * unaryExpr 2112 | * : ( "typeof" | "delete" | "+" | "-" | "~" | "!" ) memberOrInvocationExpr 2113 | * | memberOrInvocationExpr 2114 | * ; 2115 | */ 2116 | private parseUnaryOrPrefixExpr(): Expression | null { 2117 | const startToken = this._lexer.peek(); 2118 | if (tokenTraits[startToken.type].canBeUnary) { 2119 | this._lexer.get(); 2120 | const unaryOperand = this.parseUnaryOrPrefixExpr(); 2121 | if (!unaryOperand) { 2122 | return null; 2123 | } 2124 | return this.createExpressionNode<UnaryExpression>( 2125 | T_UNARY_EXPRESSION, 2126 | { 2127 | op: startToken.text, 2128 | expr: unaryOperand, 2129 | }, 2130 | startToken, 2131 | unaryOperand.endToken, 2132 | ); 2133 | } 2134 | 2135 | if (startToken.type === TokenType.IncOp || startToken.type === TokenType.DecOp) { 2136 | this._lexer.get(); 2137 | const prefixOperand = this.parseMemberOrInvocationExpr(); 2138 | if (!prefixOperand) { 2139 | return null; 2140 | } 2141 | return this.createExpressionNode<PrefixOpExpression>( 2142 | T_PREFIX_OP_EXPRESSION, 2143 | { 2144 | op: startToken.text, 2145 | expr: prefixOperand, 2146 | }, 2147 | startToken, 2148 | prefixOperand.endToken, 2149 | ); 2150 | } 2151 | 2152 | // --- Not unary or prefix 2153 | return this.parseMemberOrInvocationExpr(); 2154 | } 2155 | 2156 | /** 2157 | * memberOrInvocationExpr 2158 | * : primaryExpr "(" functionArgs ")" 2159 | * | primaryExpr "." identifier 2160 | * | primaryExpr "?." identifier 2161 | * | primaryExpr "[" expr "]" 2162 | * ; 2163 | */ 2164 | private parseMemberOrInvocationExpr(): Expression | null { 2165 | const startToken = this._lexer.peek(); 2166 | let primary = this.parsePrimaryExpr(); 2167 | if (!primary) { 2168 | return null; 2169 | } 2170 | 2171 | let exitLoop = false; 2172 | do { 2173 | const currentStart = this._lexer.peek(); 2174 | 2175 | switch (currentStart.type) { 2176 | case TokenType.LParent: { 2177 | this._lexer.get(); 2178 | let args: Expression[] = []; 2179 | if (this._lexer.peek().type !== TokenType.RParent) { 2180 | const expr = this.parseExpr(); 2181 | if (!expr) { 2182 | this.reportError("W001"); 2183 | return null; 2184 | } 2185 | args = expr.type === T_SEQUENCE_EXPRESSION ? expr.exprs : [expr]; 2186 | } 2187 | const endToken = this._lexer.peek(); 2188 | this.expectToken(TokenType.RParent, "W006"); 2189 | primary = this.createExpressionNode<FunctionInvocationExpression>( 2190 | T_FUNCTION_INVOCATION_EXPRESSION, 2191 | { 2192 | obj: primary, 2193 | arguments: args, 2194 | }, 2195 | startToken, 2196 | endToken, 2197 | ); 2198 | break; 2199 | } 2200 | 2201 | case TokenType.Dot: 2202 | case TokenType.OptionalChaining: 2203 | this._lexer.get(); 2204 | const member = this._lexer.get(); 2205 | const memberTrait = tokenTraits[member.type]; 2206 | if (!memberTrait.keywordLike) { 2207 | this.reportError("W003"); 2208 | return null; 2209 | } 2210 | primary = this.createExpressionNode<MemberAccessExpression>( 2211 | T_MEMBER_ACCESS_EXPRESSION, 2212 | { 2213 | obj: primary, 2214 | member: member.text, 2215 | opt: currentStart.type === TokenType.OptionalChaining, 2216 | }, 2217 | startToken, 2218 | member, 2219 | ); 2220 | break; 2221 | 2222 | case TokenType.LSquare: 2223 | this._lexer.get(); 2224 | const memberExpr = this.getExpression(); 2225 | if (!memberExpr) { 2226 | return null; 2227 | } 2228 | const endToken = this._lexer.peek(); 2229 | this.expectToken(TokenType.RSquare, "W005"); 2230 | primary = this.createExpressionNode<CalculatedMemberAccessExpression>( 2231 | T_CALCULATED_MEMBER_ACCESS_EXPRESSION, 2232 | { 2233 | obj: primary, 2234 | member: memberExpr, 2235 | }, 2236 | startToken, 2237 | endToken, 2238 | ); 2239 | break; 2240 | 2241 | default: 2242 | exitLoop = true; 2243 | break; 2244 | } 2245 | } while (!exitLoop); 2246 | 2247 | // --- Check for postfix operators 2248 | const nextToken = this._lexer.peek(); 2249 | if (nextToken.type === TokenType.IncOp || nextToken.type === TokenType.DecOp) { 2250 | this._lexer.get(); 2251 | return this.createExpressionNode<PostfixOpExpression>( 2252 | T_POSTFIX_OP_EXPRESSION, 2253 | { 2254 | op: nextToken.text, 2255 | expr: primary, 2256 | }, 2257 | startToken, 2258 | nextToken, 2259 | ); 2260 | } 2261 | 2262 | return primary; 2263 | } 2264 | 2265 | /** 2266 | * primaryExpr 2267 | * : literal 2268 | * | identifier 2269 | * | "::" identifier 2270 | * | "$item" 2271 | * | "(" expr ")" 2272 | * ; 2273 | */ 2274 | private parsePrimaryExpr(): Expression | null { 2275 | const start = this._lexer.peek(); 2276 | switch (start.type) { 2277 | case TokenType.LParent: 2278 | // --- Parenthesised or no-arg expression 2279 | this._lexer.get(); 2280 | if (this._lexer.peek().type === TokenType.RParent) { 2281 | // --- No-arg 2282 | const endToken = this._lexer.get(); 2283 | return this.createExpressionNode<NoArgExpression>( 2284 | T_NO_ARG_EXPRESSION, 2285 | {}, 2286 | start, 2287 | endToken, 2288 | ); 2289 | } 2290 | 2291 | // --- Parenthesized 2292 | let parenthesizedExpr = this.parseExpr(); 2293 | if (!parenthesizedExpr) { 2294 | return null; 2295 | } 2296 | const endToken = this._lexer.peek(); 2297 | this.expectToken(TokenType.RParent, "W006"); 2298 | return { 2299 | ...parenthesizedExpr, 2300 | parenthesized: (parenthesizedExpr.parenthesized ?? 0) + 1, 2301 | startToken: start, 2302 | endToken, 2303 | }; 2304 | 2305 | case TokenType.Identifier: { 2306 | const idToken = this._lexer.get(); 2307 | return this.createExpressionNode<Identifier>( 2308 | T_IDENTIFIER, 2309 | { 2310 | name: idToken.text, 2311 | }, 2312 | idToken, 2313 | idToken, 2314 | ); 2315 | } 2316 | 2317 | case TokenType.Global: { 2318 | this._lexer.get(); 2319 | const idToken = this._lexer.get(); 2320 | if (idToken.type !== TokenType.Identifier) { 2321 | this.reportError("W003"); 2322 | return null; 2323 | } 2324 | return this.createExpressionNode<Identifier>( 2325 | T_IDENTIFIER, 2326 | { 2327 | name: idToken.text, 2328 | isGlobal: true, 2329 | }, 2330 | idToken, 2331 | idToken, 2332 | ); 2333 | } 2334 | case TokenType.Backtick: 2335 | return this.parseTemplateLiteral(); 2336 | 2337 | case TokenType.False: 2338 | case TokenType.True: 2339 | this._lexer.get(); 2340 | return this.createExpressionNode<Literal>( 2341 | T_LITERAL, 2342 | { 2343 | value: start.type === TokenType.True, 2344 | }, 2345 | start, 2346 | start, 2347 | ); 2348 | 2349 | case TokenType.BinaryLiteral: 2350 | this._lexer.get(); 2351 | return this.parseBinaryLiteral(start); 2352 | 2353 | case TokenType.DecimalLiteral: 2354 | this._lexer.get(); 2355 | return this.parseDecimalLiteral(start); 2356 | 2357 | case TokenType.HexadecimalLiteral: 2358 | this._lexer.get(); 2359 | return this.parseHexadecimalLiteral(start); 2360 | 2361 | case TokenType.RealLiteral: 2362 | this._lexer.get(); 2363 | return this.parseRealLiteral(start); 2364 | 2365 | case TokenType.StringLiteral: 2366 | this._lexer.get(); 2367 | return this.parseStringLiteral(start); 2368 | 2369 | case TokenType.Infinity: 2370 | this._lexer.get(); 2371 | return this.createExpressionNode<Literal>( 2372 | T_LITERAL, 2373 | { 2374 | value: Infinity, 2375 | }, 2376 | start, 2377 | start, 2378 | ); 2379 | 2380 | case TokenType.NaN: 2381 | this._lexer.get(); 2382 | return this.createExpressionNode<Literal>( 2383 | T_LITERAL, 2384 | { 2385 | value: NaN, 2386 | }, 2387 | start, 2388 | start, 2389 | ); 2390 | 2391 | case TokenType.Null: 2392 | this._lexer.get(); 2393 | return this.createExpressionNode<Literal>( 2394 | T_LITERAL, 2395 | { 2396 | value: null, 2397 | }, 2398 | start, 2399 | start, 2400 | ); 2401 | 2402 | case TokenType.Undefined: 2403 | this._lexer.get(); 2404 | return this.createExpressionNode<Literal>( 2405 | T_LITERAL, 2406 | { 2407 | value: undefined, 2408 | }, 2409 | start, 2410 | start, 2411 | ); 2412 | 2413 | case TokenType.LSquare: 2414 | return this.parseArrayLiteral(); 2415 | 2416 | case TokenType.LBrace: 2417 | return this.parseObjectLiteral(); 2418 | 2419 | case TokenType.Divide: 2420 | return this.parseRegExpLiteral(); 2421 | } 2422 | 2423 | return null; 2424 | } 2425 | 2426 | private parseTemplateLiteral(): TemplateLiteralExpression { 2427 | const startToken = this._lexer.get(); 2428 | this._lexer.setStartingPhaseToTemplateLiteral(); 2429 | const segments: (Literal | Expression)[] = []; 2430 | loop: while (true) { 2431 | let nextToken = this._lexer.peek(); 2432 | switch (nextToken.type) { 2433 | case TokenType.StringLiteral: 2434 | this._lexer.get(); 2435 | const str = this.parseStringLiteral(nextToken, false); 2436 | segments.push(str); 2437 | break; 2438 | case TokenType.DollarLBrace: 2439 | this._lexer.get(); 2440 | const innerExpr = this.parseExpr(); 2441 | segments.push(innerExpr); 2442 | this.expectToken(TokenType.RBrace, "W004"); 2443 | this._lexer.setStartingPhaseToTemplateLiteral(); 2444 | break; 2445 | case TokenType.Backtick: 2446 | break loop; 2447 | default: 2448 | this.reportError("W004"); 2449 | } 2450 | } 2451 | const endToken = this._lexer.get(); 2452 | return this.createExpressionNode<TemplateLiteralExpression>( 2453 | T_TEMPLATE_LITERAL_EXPRESSION, 2454 | { segments }, 2455 | startToken, 2456 | endToken, 2457 | ); 2458 | } 2459 | 2460 | /** 2461 | * Parses an array literal 2462 | */ 2463 | private parseArrayLiteral(): ArrayLiteral | null { 2464 | const start = this._lexer.get(); 2465 | let expressions: Expression[] = []; 2466 | if (this._lexer.peek().type !== TokenType.RSquare) { 2467 | const expr = this.getExpression(); 2468 | if (expr) { 2469 | expressions = expr.type === T_SEQUENCE_EXPRESSION ? expr.exprs : [expr]; 2470 | } 2471 | } 2472 | const endToken = this._lexer.peek(); 2473 | this.expectToken(TokenType.RSquare); 2474 | return this.createExpressionNode<ArrayLiteral>( 2475 | T_ARRAY_LITERAL, 2476 | { 2477 | items: expressions, 2478 | }, 2479 | start, 2480 | endToken, 2481 | ); 2482 | } 2483 | 2484 | /** 2485 | * Parses an object literal 2486 | */ 2487 | private parseObjectLiteral(): ObjectLiteral | null { 2488 | const start = this._lexer.get(); 2489 | let props: (SpreadExpression | [Expression, Expression])[] = []; 2490 | if (this._lexer.peek().type !== TokenType.RBrace) { 2491 | while (this._lexer.peek().type !== TokenType.RBrace) { 2492 | // --- Check the next token 2493 | const nextToken = this._lexer.peek(); 2494 | const traits = tokenTraits[nextToken.type]; 2495 | let nameExpr: Expression | null; 2496 | 2497 | // --- Get property name or calculated property name 2498 | if (traits.expressionStart) { 2499 | if (nextToken.type === TokenType.LSquare) { 2500 | this._lexer.get(); 2501 | nameExpr = this.getExpression(); 2502 | if (!nameExpr) { 2503 | return null; 2504 | } 2505 | this.expectToken(TokenType.RSquare, "W005"); 2506 | nameExpr = this.createExpressionNode<SequenceExpression>( 2507 | T_SEQUENCE_EXPRESSION, 2508 | { 2509 | exprs: [nameExpr], 2510 | }, 2511 | start, 2512 | ); 2513 | } else if (traits.isPropLiteral) { 2514 | nameExpr = this.getExpression(false); 2515 | if (!nameExpr) { 2516 | return null; 2517 | } 2518 | if ( 2519 | nameExpr.type !== T_IDENTIFIER && 2520 | nameExpr.type !== T_LITERAL && 2521 | nameExpr.type !== T_SPREAD_EXPRESSION 2522 | ) { 2523 | this.reportError("W007"); 2524 | return null; 2525 | } 2526 | } else { 2527 | this.reportError("W007"); 2528 | return null; 2529 | } 2530 | } else if (traits.keywordLike) { 2531 | nameExpr = { 2532 | type: T_IDENTIFIER, 2533 | nodeId: createXmlUiTreeNodeId(), 2534 | name: nextToken.text, 2535 | startToken: nextToken, 2536 | endToken: nextToken, 2537 | }; 2538 | this._lexer.get(); 2539 | } else { 2540 | this.reportError("W001"); 2541 | return null; 2542 | } 2543 | 2544 | const nameType = nameExpr.type; 2545 | if (nameType === T_SPREAD_EXPRESSION) { 2546 | props.push(nameExpr); 2547 | } else { 2548 | if (nameType === T_LITERAL) { 2549 | const val = nameExpr.value; 2550 | if (typeof val !== "number" && typeof val !== "string") { 2551 | this.expectToken(TokenType.RBrace, "W007"); 2552 | return null; 2553 | } 2554 | } 2555 | 2556 | // --- Value is optional, when we have a name 2557 | let valueExpr: Expression | null = null; 2558 | 2559 | if (nameType === T_IDENTIFIER) { 2560 | const nameFollowerToken = this._lexer.peek(); 2561 | if ( 2562 | nameFollowerToken.type === TokenType.Comma || 2563 | nameFollowerToken.type === TokenType.RBrace 2564 | ) { 2565 | valueExpr = { ...nameExpr }; 2566 | } 2567 | } 2568 | 2569 | // --- Move to property value 2570 | if (!valueExpr) { 2571 | this.expectToken(TokenType.Colon, "W008"); 2572 | valueExpr = this.getExpression(false); 2573 | if (!valueExpr) { 2574 | return null; 2575 | } 2576 | } 2577 | 2578 | props.push([nameExpr, valueExpr]); 2579 | } 2580 | 2581 | // --- Test property termination 2582 | const next = this._lexer.peek().type; 2583 | if (next === TokenType.Comma) { 2584 | this._lexer.get(); 2585 | } else { 2586 | if (next !== TokenType.RBrace) { 2587 | break; 2588 | } 2589 | } 2590 | } 2591 | } 2592 | 2593 | const endToken = this._lexer.peek(); 2594 | this.expectToken(TokenType.RBrace, "W004"); 2595 | return this.createExpressionNode<ObjectLiteral>( 2596 | T_OBJECT_LITERAL, 2597 | { 2598 | props, 2599 | }, 2600 | start, 2601 | endToken, 2602 | ); 2603 | } 2604 | 2605 | private parseRegExpLiteral(): Literal | null { 2606 | const startToken = this._lexer.peek(); 2607 | const result = this._lexer.getRegEx(); 2608 | if (result.success) { 2609 | return this.createExpressionNode<Literal>( 2610 | T_LITERAL, 2611 | { 2612 | value: new RegExp(result.pattern!, result.flags), 2613 | }, 2614 | startToken, 2615 | this._lexer.peek(), 2616 | ); 2617 | } 2618 | this.reportError("W002", startToken, result.pattern ?? ""); 2619 | return null; 2620 | } 2621 | 2622 | /** 2623 | * Gets an expression 2624 | */ 2625 | private getExpression(allowSequence = true): Expression | null { 2626 | const expr = this.parseExpr(allowSequence); 2627 | if (expr) { 2628 | return expr; 2629 | } 2630 | this.reportError("W001"); 2631 | return null; 2632 | } 2633 | 2634 | // ========================================================================== 2635 | // Helpers 2636 | 2637 | /** 2638 | * Tests the type of the next token 2639 | * @param type Expected token type 2640 | * @param errorCode Error to raise if the next token is not expected 2641 | * @param allowEof Allow an EOF instead of the expected token? 2642 | */ 2643 | private expectToken(type: TokenType, errorCode?: ErrorCodes, allowEof?: boolean): Token | null { 2644 | const next = this._lexer.peek(); 2645 | if (next.type === type || (allowEof && next.type === TokenType.Eof)) { 2646 | // --- Skip the expected token 2647 | return this._lexer.get(); 2648 | } 2649 | this.reportError(errorCode ?? "W002", next, next.text); 2650 | return null; 2651 | } 2652 | 2653 | /** 2654 | * Skips the next token if the type is the specified one 2655 | * @param type Token type to check 2656 | */ 2657 | private skipToken(type: TokenType): Token | null { 2658 | const next = this._lexer.peek(); 2659 | if (next.type === type) { 2660 | this._lexer.get(); 2661 | return next; 2662 | } 2663 | return null; 2664 | } 2665 | 2666 | /** 2667 | * Skips the next token if the type is the specified one 2668 | * @param types Token types to check 2669 | */ 2670 | private skipTokens(...types: TokenType[]): Token | null { 2671 | const next = this._lexer.peek(); 2672 | for (const type of types) { 2673 | if (next.type === type) { 2674 | this._lexer.get(); 2675 | return next; 2676 | } 2677 | } 2678 | return null; 2679 | } 2680 | 2681 | /** 2682 | * Reports the specified error 2683 | * @param errorCode Error code 2684 | * @param token Token that represents the error's position 2685 | * @param options Error message options 2686 | */ 2687 | private reportError(errorCode: ErrorCodes, token?: Token, ...options: any[]): void { 2688 | let errorText: string = errorMessages[errorCode] ?? "Unkonwn error"; 2689 | if (options) { 2690 | options.forEach( 2691 | (o, idx) => (errorText = replace(errorText, `{${idx}}`, options[idx].toString())), 2692 | ); 2693 | } 2694 | if (!token) { 2695 | token = this._lexer.peek(); 2696 | } 2697 | this._parseErrors.push({ 2698 | code: errorCode, 2699 | text: errorText, 2700 | line: token.startLine, 2701 | column: token.startColumn, 2702 | }); 2703 | throw new ParserError(errorText, errorCode); 2704 | 2705 | function replace(input: string, placeholder: string, replacement: string): string { 2706 | do { 2707 | input = input.replace(placeholder, replacement); 2708 | } while (input.includes(placeholder)); 2709 | return input; 2710 | } 2711 | } 2712 | 2713 | /** 2714 | * Creates an expression node 2715 | * @param type Expression type 2716 | * @param stump Stump properties 2717 | * @param startToken The token that starts the expression 2718 | * @param endToken The token that ends the expression 2719 | * @param source Expression source code to store to the node 2720 | */ 2721 | private createNode<T extends ScripNodeBase>( 2722 | type: ScripNodeBase["type"], 2723 | stump: any, 2724 | startToken: Token, 2725 | endToken?: Token, 2726 | ): T { 2727 | if (!endToken) { 2728 | endToken = this._lexer.peek(); 2729 | } 2730 | return Object.assign({}, stump, { 2731 | type, 2732 | startToken, 2733 | endToken, 2734 | } as ScripNodeBase); 2735 | } 2736 | 2737 | /** 2738 | * Creates an expression node 2739 | * @param type Expression type 2740 | * @param stump Stump properties 2741 | * @param startToken The token that starts the expression 2742 | * @param endToken The token that ends the expression 2743 | * @param source Expression source code to store to the node 2744 | */ 2745 | private createExpressionNode<T extends Expression>( 2746 | type: Expression["type"], 2747 | stump: any = {}, 2748 | startToken?: Token, 2749 | endToken?: Token, 2750 | ): T { 2751 | if (!endToken) { 2752 | endToken = this._lexer.peek(); 2753 | } 2754 | if (!startToken) { 2755 | startToken = endToken; 2756 | } 2757 | return Object.assign({}, stump, { 2758 | type, 2759 | nodeId: createXmlUiTreeNodeId(), 2760 | startToken, 2761 | endToken, 2762 | }); 2763 | } 2764 | 2765 | /** 2766 | * Creates a statement node 2767 | * @param type Statement type 2768 | * @param stump Stump properties 2769 | * @param startToken The token that starts the statement 2770 | * @param endToken The token that ends the statement 2771 | */ 2772 | private createStatementNode<T extends Statement>( 2773 | type: Statement["type"], 2774 | stump: any, 2775 | startToken?: Token, 2776 | endToken?: Token, 2777 | ): T { 2778 | return Object.assign({}, stump, { 2779 | type, 2780 | nodeId: createXmlUiTreeNodeId(), 2781 | startToken, 2782 | endToken, 2783 | } as Statement); 2784 | } 2785 | 2786 | /** 2787 | * Parses a binary literal 2788 | * @param token Literal token 2789 | */ 2790 | private parseBinaryLiteral(token: Token): Literal { 2791 | let value: number | bigint; 2792 | const bigValue = BigInt(token.text.replace(/[_']/g, "")); 2793 | if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { 2794 | value = bigValue; 2795 | } else { 2796 | value = parseInt(token.text.substring(2).replace(/[_']/g, ""), 2); 2797 | } 2798 | return this.createExpressionNode<Literal>( 2799 | T_LITERAL, 2800 | { 2801 | value, 2802 | }, 2803 | token, 2804 | token, 2805 | ); 2806 | } 2807 | 2808 | /** 2809 | * Parses a decimal literal 2810 | * @param token Literal token 2811 | */ 2812 | private parseDecimalLiteral(token: Token): Literal { 2813 | let value: number | bigint; 2814 | const bigValue = BigInt(token.text.replace(/[_']/g, "")); 2815 | if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { 2816 | value = bigValue; 2817 | } else { 2818 | value = parseInt(token.text.replace(/[_']/g, ""), 10); 2819 | } 2820 | return this.createExpressionNode<Literal>( 2821 | T_LITERAL, 2822 | { 2823 | value, 2824 | }, 2825 | token, 2826 | token, 2827 | ); 2828 | } 2829 | 2830 | /** 2831 | * Parses a hexadecimal literal 2832 | * @param token Literal token 2833 | */ 2834 | private parseHexadecimalLiteral(token: Token): Literal { 2835 | let value: number | bigint; 2836 | const bigValue = BigInt(token.text.replace(/[_']/g, "")); 2837 | if (bigValue < Number.MIN_SAFE_INTEGER || bigValue > Number.MAX_SAFE_INTEGER) { 2838 | value = bigValue; 2839 | } else { 2840 | value = parseInt(token.text.substring(2).replace(/[_']/g, ""), 16); 2841 | } 2842 | return this.createExpressionNode<Literal>( 2843 | T_LITERAL, 2844 | { 2845 | value, 2846 | }, 2847 | token, 2848 | token, 2849 | ); 2850 | } 2851 | 2852 | /** 2853 | * Parses a real literal 2854 | * @param token Literal token 2855 | */ 2856 | private parseRealLiteral(token: Token): Literal { 2857 | let value = parseFloat(token.text.replace(/[_']/g, "")); 2858 | return this.createExpressionNode<Literal>( 2859 | T_LITERAL, 2860 | { 2861 | value, 2862 | }, 2863 | token, 2864 | token, 2865 | ); 2866 | } 2867 | 2868 | /** 2869 | * Converts a string token to intrinsic string 2870 | * @param token Literal token 2871 | */ 2872 | private parseStringLiteral(token: Token, quoteSurrounded: boolean = true): Literal { 2873 | let input = token.text; 2874 | if (quoteSurrounded) { 2875 | input = token.text.length < 2 ? "" : input.substring(1, input.length - 1); 2876 | } 2877 | let result = ""; 2878 | let state: StrParseState = StrParseState.Normal; 2879 | let collect = 0; 2880 | for (const ch of input) { 2881 | switch (state) { 2882 | case StrParseState.Normal: 2883 | if (ch === "\\") { 2884 | state = StrParseState.Backslash; 2885 | } else { 2886 | result += ch; 2887 | } 2888 | break; 2889 | 2890 | case StrParseState.Backslash: 2891 | state = StrParseState.Normal; 2892 | switch (ch) { 2893 | case "b": 2894 | result += "\b"; 2895 | break; 2896 | case "f": 2897 | result += "\f"; 2898 | break; 2899 | case "n": 2900 | result += "\n"; 2901 | break; 2902 | case "r": 2903 | result += "\r"; 2904 | break; 2905 | case "t": 2906 | result += "\t"; 2907 | break; 2908 | case "v": 2909 | result += "\v"; 2910 | break; 2911 | case "S": 2912 | result += "\xa0"; 2913 | break; 2914 | case "0": 2915 | result += String.fromCharCode(0x00); 2916 | break; 2917 | case "'": 2918 | result += "'"; 2919 | break; 2920 | case '"': 2921 | result += '"'; 2922 | break; 2923 | case "\\": 2924 | result += "\\"; 2925 | break; 2926 | case "x": 2927 | state = StrParseState.X; 2928 | break; 2929 | case "u": 2930 | state = StrParseState.UX1; 2931 | break; 2932 | default: 2933 | result += ch; 2934 | break; 2935 | } 2936 | break; 2937 | 2938 | case StrParseState.X: 2939 | if (isHexaDecimal(ch)) { 2940 | collect = parseInt(ch, 16); 2941 | state = StrParseState.Xh; 2942 | } else { 2943 | result += "x"; 2944 | state = StrParseState.Normal; 2945 | } 2946 | break; 2947 | 2948 | case StrParseState.Xh: 2949 | if (isHexaDecimal(ch)) { 2950 | collect = collect * 0x10 + parseInt(ch, 16); 2951 | result += String.fromCharCode(collect); 2952 | state = StrParseState.Normal; 2953 | } else { 2954 | result += String.fromCharCode(collect); 2955 | result += ch; 2956 | state = StrParseState.Normal; 2957 | } 2958 | break; 2959 | 2960 | case StrParseState.UX1: 2961 | if (ch === "{") { 2962 | state = StrParseState.Ucp1; 2963 | break; 2964 | } 2965 | if (isHexaDecimal(ch)) { 2966 | collect = parseInt(ch, 16); 2967 | state = StrParseState.UX2; 2968 | } else { 2969 | result += "x"; 2970 | state = StrParseState.Normal; 2971 | } 2972 | break; 2973 | 2974 | case StrParseState.UX2: 2975 | if (isHexaDecimal(ch)) { 2976 | collect = collect * 0x10 + parseInt(ch, 16); 2977 | state = StrParseState.UX3; 2978 | } else { 2979 | result += String.fromCharCode(collect); 2980 | result += ch; 2981 | state = StrParseState.Normal; 2982 | } 2983 | break; 2984 | 2985 | case StrParseState.UX3: 2986 | if (isHexaDecimal(ch)) { 2987 | collect = collect * 0x10 + parseInt(ch, 16); 2988 | state = StrParseState.UX4; 2989 | } else { 2990 | result += String.fromCharCode(collect); 2991 | result += ch; 2992 | state = StrParseState.Normal; 2993 | } 2994 | break; 2995 | 2996 | case StrParseState.UX4: 2997 | if (isHexaDecimal(ch)) { 2998 | collect = collect * 0x10 + parseInt(ch, 16); 2999 | result += String.fromCharCode(collect); 3000 | state = StrParseState.Normal; 3001 | } else { 3002 | result += String.fromCharCode(collect); 3003 | result += ch; 3004 | state = StrParseState.Normal; 3005 | } 3006 | break; 3007 | 3008 | case StrParseState.Ucp1: 3009 | if (isHexaDecimal(ch)) { 3010 | collect = parseInt(ch, 16); 3011 | state = StrParseState.Ucp2; 3012 | } else { 3013 | result += "x"; 3014 | state = StrParseState.Normal; 3015 | } 3016 | break; 3017 | 3018 | case StrParseState.Ucp2: 3019 | if (isHexaDecimal(ch)) { 3020 | collect = collect * 0x10 + parseInt(ch, 16); 3021 | state = StrParseState.Ucp3; 3022 | } else { 3023 | result += String.fromCharCode(collect); 3024 | result += ch; 3025 | state = StrParseState.Normal; 3026 | } 3027 | break; 3028 | 3029 | case StrParseState.Ucp3: 3030 | if (isHexaDecimal(ch)) { 3031 | collect = collect * 0x10 + parseInt(ch, 16); 3032 | state = StrParseState.Ucp4; 3033 | } else { 3034 | result += String.fromCharCode(collect); 3035 | result += ch; 3036 | state = StrParseState.Normal; 3037 | } 3038 | break; 3039 | 3040 | case StrParseState.Ucp4: 3041 | if (isHexaDecimal(ch)) { 3042 | collect = collect * 0x10 + parseInt(ch, 16); 3043 | state = StrParseState.Ucp5; 3044 | } else { 3045 | result += String.fromCharCode(collect); 3046 | result += ch; 3047 | state = StrParseState.Normal; 3048 | } 3049 | break; 3050 | 3051 | case StrParseState.Ucp5: 3052 | if (isHexaDecimal(ch)) { 3053 | collect = collect * 0x10 + parseInt(ch, 16); 3054 | state = StrParseState.Ucp6; 3055 | } else { 3056 | result += String.fromCharCode(collect); 3057 | result += ch; 3058 | state = StrParseState.Normal; 3059 | } 3060 | break; 3061 | 3062 | case StrParseState.Ucp6: 3063 | if (isHexaDecimal(ch)) { 3064 | collect = collect * 0x10 + parseInt(ch, 16); 3065 | state = StrParseState.UcpTail; 3066 | } else { 3067 | result += String.fromCharCode(collect); 3068 | result += ch; 3069 | state = StrParseState.Normal; 3070 | } 3071 | break; 3072 | 3073 | case StrParseState.UcpTail: 3074 | result += String.fromCharCode(collect); 3075 | if (ch !== "}") { 3076 | result += ch; 3077 | } 3078 | state = StrParseState.Normal; 3079 | break; 3080 | } 3081 | } 3082 | 3083 | // --- Handle the final machine state 3084 | switch (state) { 3085 | case StrParseState.Backslash: 3086 | result += "\\"; 3087 | break; 3088 | case StrParseState.X: 3089 | result += "x"; 3090 | break; 3091 | case StrParseState.Xh: 3092 | result += String.fromCharCode(collect); 3093 | break; 3094 | } 3095 | 3096 | // --- Done 3097 | return this.createExpressionNode<Literal>( 3098 | T_LITERAL, 3099 | { 3100 | value: result, 3101 | }, 3102 | token, 3103 | token, 3104 | ); 3105 | 3106 | function isHexaDecimal(ch: string): boolean { 3107 | return (ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F"); 3108 | } 3109 | } 3110 | 3111 | private convertToArrayDestructure(seq: SequenceExpression | ArrayLiteral): Destructure | null { 3112 | const items = seq.type === T_SEQUENCE_EXPRESSION ? seq.exprs : seq.items; 3113 | const result = this.createExpressionNode<Destructure>( 3114 | T_DESTRUCTURE, 3115 | { aDestr: [] }, 3116 | seq.startToken, 3117 | seq.endToken, 3118 | ); 3119 | 3120 | // --- Convert all items 3121 | for (const item of items) { 3122 | let arrayD: ArrayDestructure | undefined; 3123 | switch (item.type) { 3124 | case T_NO_ARG_EXPRESSION: 3125 | arrayD = this.createExpressionNode<ArrayDestructure>( 3126 | T_ARRAY_DESTRUCTURE, 3127 | {}, 3128 | item.startToken, 3129 | item.endToken, 3130 | ); 3131 | break; 3132 | case T_IDENTIFIER: 3133 | arrayD = this.createExpressionNode<ArrayDestructure>( 3134 | T_ARRAY_DESTRUCTURE, 3135 | { id: item.name }, 3136 | item.startToken, 3137 | item.endToken, 3138 | ); 3139 | break; 3140 | case T_DESTRUCTURE: 3141 | result.aDestr!.push(...item.aDestr!); 3142 | break; 3143 | case T_ARRAY_DESTRUCTURE: 3144 | arrayD = item; 3145 | break; 3146 | case T_ARRAY_LITERAL: { 3147 | const destructure = this.convertToArrayDestructure(item); 3148 | if (destructure) { 3149 | arrayD = this.createExpressionNode<ArrayDestructure>( 3150 | T_ARRAY_DESTRUCTURE, 3151 | { 3152 | aDestr: destructure.aDestr, 3153 | }, 3154 | item.startToken, 3155 | item.endToken, 3156 | ); 3157 | } 3158 | break; 3159 | } 3160 | case T_OBJECT_DESTRUCTURE: 3161 | arrayD = this.createExpressionNode<ArrayDestructure>( 3162 | T_ARRAY_DESTRUCTURE, 3163 | { 3164 | oDestr: item, 3165 | }, 3166 | item.startToken, 3167 | item.endToken, 3168 | ); 3169 | break; 3170 | case T_OBJECT_LITERAL: { 3171 | const destructure = this.convertToObjectDestructure(item); 3172 | if (destructure) { 3173 | arrayD = this.createExpressionNode<ArrayDestructure>( 3174 | T_ARRAY_DESTRUCTURE, 3175 | { 3176 | oDestr: destructure.oDestr, 3177 | }, 3178 | item.startToken, 3179 | item.endToken, 3180 | ); 3181 | } 3182 | break; 3183 | } 3184 | 3185 | default: 3186 | this.reportError("W017"); 3187 | return null; 3188 | } 3189 | if (arrayD) result.aDestr?.push(arrayD); 3190 | } 3191 | 3192 | // --- Done. 3193 | return result; 3194 | } 3195 | 3196 | private convertToObjectDestructure(objLit: ObjectLiteral): Destructure | null { 3197 | const result = this.createExpressionNode<Destructure>( 3198 | T_DESTRUCTURE, 3199 | { oDestr: [] }, 3200 | objLit.startToken, 3201 | objLit.endToken, 3202 | ); 3203 | 3204 | // --- Convert all items 3205 | for (const prop of objLit.props) { 3206 | if (Array.isArray(prop)) { 3207 | } else { 3208 | reportError("W018"); 3209 | return null; 3210 | } 3211 | 3212 | const [propKey, propValue] = prop; 3213 | if (propKey.type !== T_IDENTIFIER) { 3214 | reportError("W018"); 3215 | return null; 3216 | } 3217 | 3218 | let objD: ObjectDestructure | undefined; 3219 | switch (propValue.type) { 3220 | case T_IDENTIFIER: 3221 | if (propValue.name === propKey.name) { 3222 | objD = this.createExpressionNode<ObjectDestructure>( 3223 | T_OBJECT_DESTRUCTURE, 3224 | { id: propKey.name }, 3225 | propValue.startToken, 3226 | propValue.endToken, 3227 | ); 3228 | } else { 3229 | objD = this.createExpressionNode<ObjectDestructure>( 3230 | T_OBJECT_DESTRUCTURE, 3231 | { 3232 | id: propKey.name, 3233 | alias: propValue.name, 3234 | }, 3235 | propValue.startToken, 3236 | propValue.endToken, 3237 | ); 3238 | } 3239 | break; 3240 | case T_ARRAY_DESTRUCTURE: { 3241 | objD = this.createExpressionNode<ObjectDestructure>( 3242 | T_OBJECT_DESTRUCTURE, 3243 | { 3244 | id: propKey.name, 3245 | aDestr: propValue, 3246 | }, 3247 | propKey.startToken, 3248 | propValue.endToken, 3249 | ); 3250 | break; 3251 | } 3252 | case T_ARRAY_LITERAL: { 3253 | const destructure = this.convertToArrayDestructure(propValue); 3254 | if (destructure) { 3255 | objD = this.createExpressionNode<ObjectDestructure>( 3256 | T_OBJECT_DESTRUCTURE, 3257 | { 3258 | id: propKey.name, 3259 | aDestr: destructure.aDestr, 3260 | }, 3261 | propKey.startToken, 3262 | propValue.endToken, 3263 | ); 3264 | } 3265 | break; 3266 | } 3267 | case T_OBJECT_DESTRUCTURE: 3268 | objD = propValue; 3269 | break; 3270 | case T_OBJECT_LITERAL: { 3271 | const destructure = this.convertToObjectDestructure(propValue); 3272 | if (destructure) { 3273 | objD = this.createExpressionNode<ObjectDestructure>( 3274 | T_OBJECT_DESTRUCTURE, 3275 | { 3276 | id: propKey.name, 3277 | oDestr: destructure.oDestr, 3278 | }, 3279 | propKey.startToken, 3280 | propValue.endToken, 3281 | ); 3282 | } 3283 | break; 3284 | } 3285 | default: 3286 | this.reportError("W018"); 3287 | return null; 3288 | } 3289 | if (objD) result.oDestr?.push(objD); 3290 | } 3291 | 3292 | // --- Done. 3293 | return result; 3294 | } 3295 | 3296 | /** 3297 | * Tests if the specified token can be the start of an expression 3298 | */ 3299 | private isExpressionStart(token: Token): boolean { 3300 | return tokenTraits[token.type]?.expressionStart ?? false; 3301 | } 3302 | } 3303 | ```