@hyperfixi/core 3.3.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -179
- package/dist/ast-utils/index.cjs +74 -806
- package/dist/ast-utils/index.d.ts +1 -1
- package/dist/ast-utils/index.mjs +74 -805
- package/dist/ast-utils/interchange/engine-positions.d.ts +3 -0
- package/dist/ast-utils/interchange/index.d.ts +1 -2
- package/dist/hyperfixi.js +19 -2
- package/dist/index.d.ts +2 -22
- package/dist/index.mjs +3 -29128
- package/dist/lsp-metadata.cjs +77 -73
- package/dist/lsp-metadata.d.ts +4 -4
- package/dist/lsp-metadata.mjs +77 -73
- package/dist/metadata.cjs +34 -266
- package/dist/metadata.d.ts +31 -26
- package/dist/metadata.mjs +34 -266
- package/dist/multilingual/index.cjs +16 -382
- package/dist/multilingual/index.d.ts +4 -32
- package/dist/multilingual/index.mjs +15 -377
- package/dist/reference/index.cjs +67 -216
- package/dist/reference/index.d.ts +0 -3
- package/dist/reference/index.mjs +68 -216
- package/dist/version.d.ts +1 -1
- package/package.json +13 -179
- package/dist/__test-utils__/context-builders.d.ts +0 -42
- package/dist/__test-utils__/error-testing.d.ts +0 -18
- package/dist/__test-utils__/index.d.ts +0 -6
- package/dist/__test-utils__/mock-types.d.ts +0 -57
- package/dist/__test-utils__/parser-context-mock.d.ts +0 -7
- package/dist/__test-utils__/parser-helpers.d.ts +0 -38
- package/dist/api/hyperscript-api.d.ts +0 -90
- package/dist/api/lokascript-api.d.ts +0 -9
- package/dist/ast/command-slots.d.ts +0 -140
- package/dist/ast/guards.d.ts +0 -15
- package/dist/ast/legacy.d.ts +0 -15
- package/dist/ast/nodes.d.ts +0 -243
- package/dist/ast-utils/interchange/from-core.d.ts +0 -12
- package/dist/ast-utils/interchange/to-core.d.ts +0 -8
- package/dist/behaviors/boosted.d.ts +0 -29
- package/dist/behaviors/history-swap.d.ts +0 -24
- package/dist/behaviors/index.cjs +0 -1155
- package/dist/behaviors/index.d.ts +0 -4
- package/dist/behaviors/index.mjs +0 -1145
- package/dist/bundle-generator/executor-core.d.ts +0 -4
- package/dist/bundle-generator/generator.d.ts +0 -4
- package/dist/bundle-generator/index.cjs +0 -2804
- package/dist/bundle-generator/index.d.ts +0 -6
- package/dist/bundle-generator/index.mjs +0 -2779
- package/dist/bundle-generator/parser-templates.d.ts +0 -6
- package/dist/bundle-generator/template-capabilities.d.ts +0 -12
- package/dist/bundle-generator/templates.d.ts +0 -12
- package/dist/bundle-generator/types.d.ts +0 -34
- package/dist/chunks/bridge-CI9SOl8u.js +0 -2
- package/dist/chunks/browser-modular-7CpI5VF2.js +0 -2
- package/dist/chunks/index-BwXDEK0m.js +0 -2
- package/dist/chunks/index-DQi2o8Lk.js +0 -2
- package/dist/chunks/index-uCOQ8Qn0.js +0 -2
- package/dist/commands/advanced/js.d.ts +0 -40
- package/dist/commands/animation/measure.d.ts +0 -43
- package/dist/commands/animation/settle.d.ts +0 -38
- package/dist/commands/animation/start-view-transition.d.ts +0 -38
- package/dist/commands/animation/take.d.ts +0 -41
- package/dist/commands/animation/transition.d.ts +0 -49
- package/dist/commands/async/fetch.d.ts +0 -61
- package/dist/commands/async/wait.d.ts +0 -51
- package/dist/commands/behaviors/install.d.ts +0 -40
- package/dist/commands/content/append.d.ts +0 -26
- package/dist/commands/content/insertion-base.d.ts +0 -60
- package/dist/commands/content/prepend.d.ts +0 -26
- package/dist/commands/control-flow/break.d.ts +0 -29
- package/dist/commands/control-flow/continue.d.ts +0 -29
- package/dist/commands/control-flow/exit.d.ts +0 -29
- package/dist/commands/control-flow/halt.d.ts +0 -39
- package/dist/commands/control-flow/if.d.ts +0 -58
- package/dist/commands/control-flow/repeat.d.ts +0 -50
- package/dist/commands/control-flow/return.d.ts +0 -33
- package/dist/commands/control-flow/signal-base.d.ts +0 -18
- package/dist/commands/control-flow/throw.d.ts +0 -34
- package/dist/commands/control-flow/unless.d.ts +0 -3
- package/dist/commands/data/clear.d.ts +0 -37
- package/dist/commands/data/decrement.d.ts +0 -3
- package/dist/commands/data/default.d.ts +0 -60
- package/dist/commands/data/get.d.ts +0 -35
- package/dist/commands/data/increment.d.ts +0 -42
- package/dist/commands/data/set.d.ts +0 -76
- package/dist/commands/decorators/index.d.ts +0 -14
- package/dist/commands/dom/add.d.ts +0 -44
- package/dist/commands/dom/close.d.ts +0 -32
- package/dist/commands/dom/dom-modification-base.d.ts +0 -48
- package/dist/commands/dom/empty.d.ts +0 -32
- package/dist/commands/dom/hide.d.ts +0 -28
- package/dist/commands/dom/make.d.ts +0 -34
- package/dist/commands/dom/open.d.ts +0 -34
- package/dist/commands/dom/process-partials.d.ts +0 -51
- package/dist/commands/dom/put.d.ts +0 -42
- package/dist/commands/dom/remove.d.ts +0 -46
- package/dist/commands/dom/reset.d.ts +0 -32
- package/dist/commands/dom/select.d.ts +0 -32
- package/dist/commands/dom/show.d.ts +0 -29
- package/dist/commands/dom/swap.d.ts +0 -65
- package/dist/commands/dom/toggle.d.ts +0 -69
- package/dist/commands/dom/visibility-base.d.ts +0 -22
- package/dist/commands/events/send.d.ts +0 -3
- package/dist/commands/events/trigger.d.ts +0 -49
- package/dist/commands/execution/blur.d.ts +0 -32
- package/dist/commands/execution/call.d.ts +0 -35
- package/dist/commands/execution/focus.d.ts +0 -32
- package/dist/commands/execution/pseudo-command.d.ts +0 -41
- package/dist/commands/helpers/attribute-manipulation.d.ts +0 -11
- package/dist/commands/helpers/attribute-target.d.ts +0 -9
- package/dist/commands/helpers/batch-dom-operations.d.ts +0 -12
- package/dist/commands/helpers/body-ops.d.ts +0 -10
- package/dist/commands/helpers/class-manipulation.d.ts +0 -11
- package/dist/commands/helpers/condition-helpers.d.ts +0 -4
- package/dist/commands/helpers/dom-mutation.d.ts +0 -16
- package/dist/commands/helpers/duration-parsing.d.ts +0 -8
- package/dist/commands/helpers/element-property-access.d.ts +0 -7
- package/dist/commands/helpers/element-resolution.d.ts +0 -16
- package/dist/commands/helpers/error-helpers.d.ts +0 -17
- package/dist/commands/helpers/event-helpers.d.ts +0 -13
- package/dist/commands/helpers/event-waiting.d.ts +0 -37
- package/dist/commands/helpers/index.d.ts +0 -32
- package/dist/commands/helpers/input-validator.d.ts +0 -23
- package/dist/commands/helpers/loop-executor.d.ts +0 -56
- package/dist/commands/helpers/numeric-target-parser.d.ts +0 -21
- package/dist/commands/helpers/property-target.d.ts +0 -20
- package/dist/commands/helpers/selector-type-detection.d.ts +0 -24
- package/dist/commands/helpers/slots.d.ts +0 -5
- package/dist/commands/helpers/smart-element.d.ts +0 -16
- package/dist/commands/helpers/style-manipulation.d.ts +0 -16
- package/dist/commands/helpers/target-elements.d.ts +0 -5
- package/dist/commands/helpers/temporal-modifiers.d.ts +0 -15
- package/dist/commands/helpers/url-argument-parser.d.ts +0 -13
- package/dist/commands/helpers/url-validation.d.ts +0 -7
- package/dist/commands/helpers/variable-access.d.ts +0 -13
- package/dist/commands/helpers/visibility-target-parser.d.ts +0 -15
- package/dist/commands/helpers/write-target.d.ts +0 -42
- package/dist/commands/index.cjs +0 -24168
- package/dist/commands/index.d.ts +0 -171
- package/dist/commands/index.mjs +0 -23981
- package/dist/commands/manifest.d.ts +0 -17
- package/dist/commands/navigation/go.d.ts +0 -58
- package/dist/commands/navigation/push-url.d.ts +0 -48
- package/dist/commands/navigation/replace-url.d.ts +0 -3
- package/dist/commands/navigation/scroll-to.d.ts +0 -43
- package/dist/commands/templates/render.d.ts +0 -47
- package/dist/commands/utility/beep.d.ts +0 -41
- package/dist/commands/utility/breakpoint.d.ts +0 -32
- package/dist/commands/utility/copy.d.ts +0 -41
- package/dist/commands/utility/log.d.ts +0 -36
- package/dist/commands/utility/pick.d.ts +0 -47
- package/dist/commands/utility/tell.d.ts +0 -39
- package/dist/compatibility/browser-bundle-classic.d.ts +0 -17
- package/dist/compatibility/browser-bundle-hybrid-complete.d.ts +0 -22
- package/dist/compatibility/browser-bundle-hybrid-hx.d.ts +0 -43
- package/dist/compatibility/browser-bundle-lite.d.ts +0 -15
- package/dist/compatibility/browser-bundle-modular.d.ts +0 -18
- package/dist/compatibility/browser-bundle-multilingual.d.ts +0 -19
- package/dist/compatibility/browser-bundle-semantic-complete.d.ts +0 -24
- package/dist/compatibility/browser-bundle-textshelf-minimal.d.ts +0 -16
- package/dist/compatibility/browser-bundle-textshelf-profile.d.ts +0 -18
- package/dist/compatibility/browser-modular.d.ts +0 -42
- package/dist/compatibility/browser-tests/helpers/semantic-page.d.ts +0 -3
- package/dist/compatibility/browser-tests/test-utils.d.ts +0 -21
- package/dist/compatibility/bundle-shell.d.ts +0 -13
- package/dist/compatibility/bundle-sources.d.ts +0 -4
- package/dist/compatibility/eval-hyperscript.d.ts +0 -16
- package/dist/compatibility/hyperscript-adapter.d.ts +0 -38
- package/dist/core/ast-property-utils.d.ts +0 -2
- package/dist/core/context.d.ts +0 -19
- package/dist/core/dom.d.ts +0 -15
- package/dist/core/evaluator-types.d.ts +0 -5
- package/dist/core/events.d.ts +0 -48
- package/dist/core/executor.d.ts +0 -34
- package/dist/core/expression-evaluator.d.ts +0 -6
- package/dist/core/expression-registry.d.ts +0 -8
- package/dist/core/scope-hooks.d.ts +0 -24
- package/dist/debug/debug-controller.d.ts +0 -62
- package/dist/debug/index.d.ts +0 -5
- package/dist/dom/attribute-processor.d.ts +0 -75
- package/dist/dom/detect-language.d.ts +0 -3
- package/dist/dom/minimal-attribute-processor.d.ts +0 -20
- package/dist/expressions/base-expression.d.ts +0 -27
- package/dist/expressions/bundles/common-expressions.d.ts +0 -10
- package/dist/expressions/bundles/core-expressions.d.ts +0 -8
- package/dist/expressions/bundles/full-expressions.d.ts +0 -11
- package/dist/expressions/bundles/index.d.ts +0 -7
- package/dist/expressions/collection/index.d.ts +0 -10
- package/dist/expressions/conversion/index.d.ts +0 -31
- package/dist/expressions/expression-tiers.d.ts +0 -13
- package/dist/expressions/index.cjs +0 -4331
- package/dist/expressions/index.d.ts +0 -13
- package/dist/expressions/index.mjs +0 -4312
- package/dist/expressions/logical/index.d.ts +0 -61
- package/dist/expressions/mathematical/index.d.ts +0 -80
- package/dist/expressions/positional/index.d.ts +0 -31
- package/dist/expressions/properties/index.d.ts +0 -27
- package/dist/expressions/property-access-utils.d.ts +0 -23
- package/dist/expressions/references/index.d.ts +0 -39
- package/dist/expressions/shared/comparison-utils.d.ts +0 -10
- package/dist/expressions/shared/index.d.ts +0 -10
- package/dist/expressions/shared/number-utils.d.ts +0 -8
- package/dist/expressions/shared/tracking.d.ts +0 -20
- package/dist/expressions/shared/validation-utils.d.ts +0 -13
- package/dist/expressions/special/index.d.ts +0 -78
- package/dist/expressions/type-helpers.d.ts +0 -11
- package/dist/expressions/type-registry.d.ts +0 -57
- package/dist/expressions/validation-helpers.d.ts +0 -15
- package/dist/htmx/hcon.d.ts +0 -9
- package/dist/htmx/htmx-attribute-processor.d.ts +0 -120
- package/dist/htmx/htmx-translator.d.ts +0 -22
- package/dist/htmx/i18n-hooks.d.ts +0 -18
- package/dist/htmx/i18n-orchestrator.d.ts +0 -16
- package/dist/htmx/index.d.ts +0 -3
- package/dist/htmx/lang-resolver.d.ts +0 -3
- package/dist/htmx/sse.d.ts +0 -60
- package/dist/htmx/ws.d.ts +0 -59
- package/dist/hyperfixi-browser-classic-i18n.js +0 -2
- package/dist/hyperfixi-browser.js +0 -2
- package/dist/hyperfixi-classic-i18n.js +0 -2
- package/dist/hyperfixi-hx-v4.js +0 -1
- package/dist/hyperfixi-hx.js +0 -1
- package/dist/hyperfixi-hybrid-complete.js +0 -1
- package/dist/hyperfixi-hybrid-hx.js +0 -1
- package/dist/hyperfixi-multilingual.js +0 -2
- package/dist/hyperfixi.mjs +0 -2
- package/dist/i18n/error-catalog.d.ts +0 -12
- package/dist/index.cjs +0 -29159
- package/dist/index.min.js +0 -2
- package/dist/lib/dom-globals-shim.d.ts +0 -2
- package/dist/lib/index.d.ts +0 -4
- package/dist/lib/morph-adapter.d.ts +0 -9
- package/dist/lib/swap-executor.d.ts +0 -12
- package/dist/lib/view-transitions.d.ts +0 -7
- package/dist/lokascript-browser-classic-i18n.js +0 -2
- package/dist/lokascript-browser.js +0 -2
- package/dist/lokascript-hybrid-complete.js +0 -1
- package/dist/lokascript-hybrid-hx.js +0 -1
- package/dist/lokascript-multilingual.js +0 -2
- package/dist/lse/index.cjs +0 -84
- package/dist/lse/index.d.ts +0 -15
- package/dist/lse/index.mjs +0 -71
- package/dist/multilingual/bridge.d.ts +0 -38
- package/dist/multilingual/schema-roles.d.ts +0 -3
- package/dist/parser/__types__/test-helpers.d.ts +0 -25
- package/dist/parser/command-grammar.d.ts +0 -14
- package/dist/parser/command-node-builder.d.ts +0 -46
- package/dist/parser/command-parsers/animation-commands.d.ts +0 -6
- package/dist/parser/command-parsers/async-commands.d.ts +0 -5
- package/dist/parser/command-parsers/control-flow-commands.d.ts +0 -7
- package/dist/parser/command-parsers/declared-commands.d.ts +0 -6
- package/dist/parser/command-parsers/dom-commands.d.ts +0 -11
- package/dist/parser/command-parsers/event-commands.d.ts +0 -4
- package/dist/parser/command-parsers/navigation-commands.d.ts +0 -6
- package/dist/parser/command-parsers/utility-commands.d.ts +0 -14
- package/dist/parser/command-parsers/variable-commands.d.ts +0 -12
- package/dist/parser/error-handler.d.ts +0 -34
- package/dist/parser/extensions.d.ts +0 -43
- package/dist/parser/full-parser.cjs +0 -7943
- package/dist/parser/full-parser.d.ts +0 -4
- package/dist/parser/full-parser.mjs +0 -7940
- package/dist/parser/helpers/ast-helpers.d.ts +0 -24
- package/dist/parser/helpers/parsing-helpers.d.ts +0 -19
- package/dist/parser/helpers/token-helpers.d.ts +0 -28
- package/dist/parser/hybrid/aliases.cjs +0 -44
- package/dist/parser/hybrid/aliases.d.ts +0 -7
- package/dist/parser/hybrid/aliases.mjs +0 -37
- package/dist/parser/hybrid/ast-types.cjs +0 -3
- package/dist/parser/hybrid/ast-types.d.ts +0 -97
- package/dist/parser/hybrid/ast-types.mjs +0 -2
- package/dist/parser/hybrid/index.cjs +0 -1185
- package/dist/parser/hybrid/index.d.ts +0 -6
- package/dist/parser/hybrid/index.mjs +0 -1175
- package/dist/parser/hybrid/parser-core.cjs +0 -1171
- package/dist/parser/hybrid/parser-core.d.ts +0 -69
- package/dist/parser/hybrid/parser-core.mjs +0 -1169
- package/dist/parser/hybrid/tokenizer.cjs +0 -255
- package/dist/parser/hybrid/tokenizer.d.ts +0 -9
- package/dist/parser/hybrid/tokenizer.mjs +0 -252
- package/dist/parser/hybrid-parser.cjs +0 -1249
- package/dist/parser/hybrid-parser.d.ts +0 -10
- package/dist/parser/hybrid-parser.mjs +0 -1242
- package/dist/parser/parser-constants.d.ts +0 -100
- package/dist/parser/parser-interface.d.ts +0 -12
- package/dist/parser/parser-types.d.ts +0 -164
- package/dist/parser/parser.d.ts +0 -165
- package/dist/parser/pratt-parser.d.ts +0 -41
- package/dist/parser/regex-parser.cjs +0 -422
- package/dist/parser/regex-parser.d.ts +0 -4
- package/dist/parser/regex-parser.mjs +0 -419
- package/dist/parser/runtime.d.ts +0 -14
- package/dist/parser/semantic-integration.d.ts +0 -17
- package/dist/parser/token-consumer.d.ts +0 -34
- package/dist/parser/token-predicates.d.ts +0 -77
- package/dist/parser/tokenizer.d.ts +0 -28
- package/dist/parser/types.d.ts +0 -105
- package/dist/performance/expression-cache.d.ts +0 -78
- package/dist/performance/integration.d.ts +0 -40
- package/dist/performance/production-monitor.d.ts +0 -67
- package/dist/registry/browser-types.cjs +0 -81
- package/dist/registry/browser-types.d.ts +0 -20
- package/dist/registry/browser-types.mjs +0 -76
- package/dist/registry/environment.cjs +0 -36
- package/dist/registry/environment.d.ts +0 -19
- package/dist/registry/environment.mjs +0 -30
- package/dist/registry/event-source-registry.d.ts +0 -54
- package/dist/registry/examples/server-commands.d.ts +0 -6
- package/dist/registry/examples/server-event-source.d.ts +0 -32
- package/dist/registry/index.cjs +0 -14055
- package/dist/registry/index.d.ts +0 -39
- package/dist/registry/index.mjs +0 -14039
- package/dist/registry/runtime-integration.d.ts +0 -23
- package/dist/registry/universal-types.cjs +0 -91
- package/dist/registry/universal-types.d.ts +0 -35
- package/dist/registry/universal-types.mjs +0 -86
- package/dist/runtime/cleanup-registry.d.ts +0 -47
- package/dist/runtime/command-adapter.d.ts +0 -69
- package/dist/runtime/environment.d.ts +0 -75
- package/dist/runtime/plugin.d.ts +0 -14
- package/dist/runtime/runtime-base.d.ts +0 -100
- package/dist/runtime/runtime-factory.d.ts +0 -29
- package/dist/runtime/runtime.d.ts +0 -20
- package/dist/runtime/temporal-modifiers.d.ts +0 -37
- package/dist/test-setup.d.ts +0 -28
- package/dist/test-utilities.d.ts +0 -39
- package/dist/types/base-types.d.ts +0 -231
- package/dist/types/code-fix.d.ts +0 -39
- package/dist/types/command-metadata.d.ts +0 -57
- package/dist/types/command-types.d.ts +0 -262
- package/dist/types/context-types.d.ts +0 -106
- package/dist/types/core-context.d.ts +0 -22
- package/dist/types/core.d.ts +0 -143
- package/dist/types/error-codes.d.ts +0 -207
- package/dist/types/expression-types.d.ts +0 -155
- package/dist/types/hooks.d.ts +0 -45
- package/dist/types/index.d.ts +0 -32
- package/dist/types/program.d.ts +0 -8
- package/dist/types/result.d.ts +0 -73
- package/dist/types/template-types.d.ts +0 -162
- package/dist/types/type-guards.d.ts +0 -24
- package/dist/types/unified-types.d.ts +0 -91
- package/dist/utils/beep.d.ts +0 -8
- package/dist/utils/debug-events.d.ts +0 -41
- package/dist/utils/debug.d.ts +0 -35
- package/dist/utils/dom-utils.d.ts +0 -16
- package/dist/utils/element-check.d.ts +0 -8
- package/dist/utils/keyboard-shortcuts.d.ts +0 -18
- package/dist/utils/performance.d.ts +0 -40
- package/dist/validation/command-pattern-validator.d.ts +0 -53
- package/dist/validation/common-validators.d.ts +0 -24
- package/dist/validation/lightweight-validators.d.ts +0 -113
- package/dist/validation/partial-validation-types.d.ts +0 -50
- package/dist/validation/partial-validator.d.ts +0 -6
- package/dist/validation/partial-warning-formatter.d.ts +0 -6
- package/dist/validation/validate-cli.d.ts +0 -41
- package/vocab/htmx/ar.js +0 -62
- package/vocab/htmx/bn.js +0 -65
- package/vocab/htmx/de.js +0 -83
- package/vocab/htmx/en.js +0 -24
- package/vocab/htmx/es.js +0 -79
- package/vocab/htmx/fr.js +0 -79
- package/vocab/htmx/he.js +0 -61
- package/vocab/htmx/hi.js +0 -81
- package/vocab/htmx/id.js +0 -76
- package/vocab/htmx/it.js +0 -79
- package/vocab/htmx/ja.js +0 -85
- package/vocab/htmx/ko.js +0 -81
- package/vocab/htmx/ms.js +0 -58
- package/vocab/htmx/pl.js +0 -80
- package/vocab/htmx/pt.js +0 -81
- package/vocab/htmx/qu.js +0 -83
- package/vocab/htmx/ru.js +0 -82
- package/vocab/htmx/sw.js +0 -79
- package/vocab/htmx/th.js +0 -67
- package/vocab/htmx/tl.js +0 -56
- package/vocab/htmx/tr.js +0 -86
- package/vocab/htmx/uk.js +0 -82
- package/vocab/htmx/vi.js +0 -59
- package/vocab/htmx/zh.js +0 -79
|
@@ -1,2804 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function stripTypes(code, format) {
|
|
4
|
-
if (format === 'ts')
|
|
5
|
-
return code;
|
|
6
|
-
return (code
|
|
7
|
-
.replace(/\s+as\s+\w+(?:\[\])?/g, '')
|
|
8
|
-
.replace(/\((\w+):\s*\w+\)/g, '($1)')
|
|
9
|
-
.replace(/\((\w+):\s*any\)/g, '($1)')
|
|
10
|
-
.replace(/Promise<void>\[\]/g, 'Promise[]')
|
|
11
|
-
.replace(/Promise<void>/g, 'Promise')
|
|
12
|
-
.replace(/:\s*TransitionEvent/g, ''));
|
|
13
|
-
}
|
|
14
|
-
const COMMAND_IMPLEMENTATIONS_TS = {
|
|
15
|
-
toggle: `
|
|
16
|
-
case 'toggle': {
|
|
17
|
-
const raw = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
|
|
18
|
-
const targets = await getTarget();
|
|
19
|
-
if (raw.startsWith('@')) {
|
|
20
|
-
const attr = raw.slice(1);
|
|
21
|
-
for (const el of targets) {
|
|
22
|
-
if (el.hasAttribute(attr)) el.removeAttribute(attr);
|
|
23
|
-
else el.setAttribute(attr, '');
|
|
24
|
-
}
|
|
25
|
-
} else {
|
|
26
|
-
for (const el of targets) el.classList.toggle(raw);
|
|
27
|
-
}
|
|
28
|
-
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
29
|
-
return ctx.it;
|
|
30
|
-
}`,
|
|
31
|
-
add: `
|
|
32
|
-
case 'add': {
|
|
33
|
-
const raw = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
|
|
34
|
-
const targets = await getTarget();
|
|
35
|
-
if (raw.startsWith('@')) {
|
|
36
|
-
const attr = raw.slice(1);
|
|
37
|
-
for (const el of targets) el.setAttribute(attr, '');
|
|
38
|
-
} else {
|
|
39
|
-
for (const el of targets) el.classList.add(raw);
|
|
40
|
-
}
|
|
41
|
-
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
42
|
-
return ctx.it;
|
|
43
|
-
}`,
|
|
44
|
-
remove: `
|
|
45
|
-
case 'remove': {
|
|
46
|
-
const targets = await getTarget();
|
|
47
|
-
for (const el of targets) el.remove();
|
|
48
|
-
return null;
|
|
49
|
-
}`,
|
|
50
|
-
removeClass: `
|
|
51
|
-
case 'removeClass': {
|
|
52
|
-
const raw = getClassName(cmd.args[0]) || String(await evaluate(cmd.args[0], ctx));
|
|
53
|
-
const targets = await getTarget();
|
|
54
|
-
if (raw.startsWith('@')) {
|
|
55
|
-
const attr = raw.slice(1);
|
|
56
|
-
for (const el of targets) el.removeAttribute(attr);
|
|
57
|
-
} else {
|
|
58
|
-
for (const el of targets) el.classList.remove(raw);
|
|
59
|
-
}
|
|
60
|
-
return targets;
|
|
61
|
-
}`,
|
|
62
|
-
show: `
|
|
63
|
-
case 'show': {
|
|
64
|
-
const targets = await getTarget();
|
|
65
|
-
for (const el of targets) {
|
|
66
|
-
(el as HTMLElement).style.display = '';
|
|
67
|
-
el.classList.remove('hidden');
|
|
68
|
-
}
|
|
69
|
-
return targets;
|
|
70
|
-
}`,
|
|
71
|
-
hide: `
|
|
72
|
-
case 'hide': {
|
|
73
|
-
const targets = await getTarget();
|
|
74
|
-
for (const el of targets) (el as HTMLElement).style.display = 'none';
|
|
75
|
-
return targets;
|
|
76
|
-
}`,
|
|
77
|
-
set: `
|
|
78
|
-
case 'set': {
|
|
79
|
-
const target = cmd.args[0];
|
|
80
|
-
const value = await evaluate(cmd.args[1], ctx);
|
|
81
|
-
|
|
82
|
-
if (target.type === 'variable') {
|
|
83
|
-
const varName = target.name.slice(1);
|
|
84
|
-
const map = target.scope === 'local' ? elemScope(ctx.me) : globalVars;
|
|
85
|
-
map.set(varName, value);
|
|
86
|
-
ctx.it = value;
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (target.type === 'possessive' || target.type === 'member') {
|
|
91
|
-
const obj = await evaluate(target.object, ctx);
|
|
92
|
-
if (obj) {
|
|
93
|
-
if (obj instanceof Element && isStyleProp(target.property)) {
|
|
94
|
-
setStyleProp(obj, target.property, value);
|
|
95
|
-
} else {
|
|
96
|
-
(obj as any)[target.property] = value;
|
|
97
|
-
}
|
|
98
|
-
ctx.it = value;
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
ctx.it = value;
|
|
104
|
-
return value;
|
|
105
|
-
}`,
|
|
106
|
-
get: `
|
|
107
|
-
case 'get': {
|
|
108
|
-
const value = await evaluate(cmd.args[0], ctx);
|
|
109
|
-
ctx.it = value;
|
|
110
|
-
return value;
|
|
111
|
-
}`,
|
|
112
|
-
wait: `
|
|
113
|
-
case 'wait': {
|
|
114
|
-
const duration = await evaluate(cmd.args[0], ctx);
|
|
115
|
-
const ms = typeof duration === 'number' ? duration : parseInt(String(duration));
|
|
116
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
117
|
-
return ms;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
case 'waitFor': {
|
|
121
|
-
const eventName = await evaluate(cmd.args[0], ctx);
|
|
122
|
-
const targets = await getTarget();
|
|
123
|
-
const target = targets[0] || ctx.me;
|
|
124
|
-
return new Promise(resolve => {
|
|
125
|
-
target.addEventListener(
|
|
126
|
-
String(eventName),
|
|
127
|
-
e => {
|
|
128
|
-
ctx.it = e;
|
|
129
|
-
resolve(e);
|
|
130
|
-
},
|
|
131
|
-
{ once: true }
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
}`,
|
|
135
|
-
transition: `
|
|
136
|
-
case 'transition': {
|
|
137
|
-
const property = String(await evaluate(cmd.args[0], ctx)).replace(/^\\*/, '');
|
|
138
|
-
const toValue = await evaluate(cmd.args[1], ctx);
|
|
139
|
-
const durationVal = await evaluate(cmd.args[2], ctx);
|
|
140
|
-
const duration = typeof durationVal === 'number' ? durationVal :
|
|
141
|
-
String(durationVal).endsWith('ms') ? parseInt(String(durationVal)) :
|
|
142
|
-
String(durationVal).endsWith('s') ? parseFloat(String(durationVal)) * 1000 :
|
|
143
|
-
parseInt(String(durationVal)) || 300;
|
|
144
|
-
|
|
145
|
-
const targets = await getTarget();
|
|
146
|
-
const promises: Promise<void>[] = [];
|
|
147
|
-
|
|
148
|
-
for (const el of targets) {
|
|
149
|
-
const htmlEl = el as HTMLElement;
|
|
150
|
-
const kebabProp = property.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
151
|
-
|
|
152
|
-
const oldTransition = htmlEl.style.transition;
|
|
153
|
-
htmlEl.style.transition = \`\${kebabProp} \${duration}ms ease\`;
|
|
154
|
-
htmlEl.style.setProperty(kebabProp, String(toValue));
|
|
155
|
-
|
|
156
|
-
promises.push(new Promise<void>(resolve => {
|
|
157
|
-
const cleanup = () => {
|
|
158
|
-
htmlEl.style.transition = oldTransition;
|
|
159
|
-
resolve();
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const onEnd = (e: TransitionEvent) => {
|
|
163
|
-
if (e.propertyName === kebabProp) {
|
|
164
|
-
htmlEl.removeEventListener('transitionend', onEnd);
|
|
165
|
-
cleanup();
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
htmlEl.addEventListener('transitionend', onEnd);
|
|
170
|
-
setTimeout(cleanup, duration + 50);
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
await Promise.all(promises);
|
|
175
|
-
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
176
|
-
return ctx.it;
|
|
177
|
-
}`,
|
|
178
|
-
go: `
|
|
179
|
-
case 'go': {
|
|
180
|
-
const dest = await evaluate(cmd.args[0], ctx);
|
|
181
|
-
const d = String(dest).toLowerCase();
|
|
182
|
-
if (d === 'back') history.back();
|
|
183
|
-
else if (d === 'forward') history.forward();
|
|
184
|
-
else if (d === 'bottom') ctx.me.scrollIntoView({ block: 'end', behavior: 'smooth' });
|
|
185
|
-
else if (d === 'top') ctx.me.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
186
|
-
else window.location.href = String(dest);
|
|
187
|
-
return null;
|
|
188
|
-
}`,
|
|
189
|
-
call: `
|
|
190
|
-
case 'call': {
|
|
191
|
-
const result = await evaluate(cmd.args[0], ctx);
|
|
192
|
-
ctx.it = result;
|
|
193
|
-
return result;
|
|
194
|
-
}`,
|
|
195
|
-
log: `
|
|
196
|
-
case 'log': {
|
|
197
|
-
const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
|
|
198
|
-
console.log(...values);
|
|
199
|
-
return values[0];
|
|
200
|
-
}`,
|
|
201
|
-
send: `
|
|
202
|
-
case 'send': {
|
|
203
|
-
const eventName = await evaluate(cmd.args[0], ctx);
|
|
204
|
-
const targets = await getTarget();
|
|
205
|
-
const event = new CustomEvent(String(eventName), { bubbles: true, detail: ctx.it });
|
|
206
|
-
for (const el of targets) el.dispatchEvent(event);
|
|
207
|
-
ctx.it = event;
|
|
208
|
-
return event;
|
|
209
|
-
}`,
|
|
210
|
-
put: `
|
|
211
|
-
case 'put': {
|
|
212
|
-
const content = await evaluate(cmd.args[0], ctx);
|
|
213
|
-
const modifier = cmd.modifier || 'into';
|
|
214
|
-
|
|
215
|
-
if (cmd.target?.type === 'possessive' && isStyleProp(cmd.target.property)) {
|
|
216
|
-
const obj = await evaluate(cmd.target.object, ctx);
|
|
217
|
-
const elements = toElementArray(obj);
|
|
218
|
-
for (const el of elements) {
|
|
219
|
-
setStyleProp(el, cmd.target.property, content);
|
|
220
|
-
}
|
|
221
|
-
ctx.it = content;
|
|
222
|
-
return content;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const targets = await getTarget();
|
|
226
|
-
for (const el of targets) {
|
|
227
|
-
const html = String(content);
|
|
228
|
-
if (modifier === 'into') el.innerHTML = html;
|
|
229
|
-
else if (modifier === 'before') el.insertAdjacentHTML('beforebegin', html);
|
|
230
|
-
else if (modifier === 'after') el.insertAdjacentHTML('afterend', html);
|
|
231
|
-
else if (modifier === 'at start of') el.insertAdjacentHTML('afterbegin', html);
|
|
232
|
-
else if (modifier === 'at end of') el.insertAdjacentHTML('beforeend', html);
|
|
233
|
-
}
|
|
234
|
-
ctx.it = content;
|
|
235
|
-
return content;
|
|
236
|
-
}`,
|
|
237
|
-
append: `
|
|
238
|
-
case 'append': {
|
|
239
|
-
const content = await evaluate(cmd.args[0], ctx);
|
|
240
|
-
const targets = await getTarget();
|
|
241
|
-
for (const el of targets) el.insertAdjacentHTML('beforeend', String(content));
|
|
242
|
-
ctx.it = content;
|
|
243
|
-
return content;
|
|
244
|
-
}`,
|
|
245
|
-
prepend: `
|
|
246
|
-
case 'prepend': {
|
|
247
|
-
const content = await evaluate(cmd.args[0], ctx);
|
|
248
|
-
const targets = await getTarget();
|
|
249
|
-
for (const el of targets) el.insertAdjacentHTML('afterbegin', String(content));
|
|
250
|
-
ctx.it = content;
|
|
251
|
-
return content;
|
|
252
|
-
}`,
|
|
253
|
-
morph: `
|
|
254
|
-
case 'morph': {
|
|
255
|
-
const targets = await getTarget();
|
|
256
|
-
const content = await evaluate(cmd.args[0], ctx);
|
|
257
|
-
const contentStr = String(content);
|
|
258
|
-
const isOuter = cmd.modifier === 'over';
|
|
259
|
-
|
|
260
|
-
for (const target of targets) {
|
|
261
|
-
try {
|
|
262
|
-
if (isOuter) {
|
|
263
|
-
morphlexMorph(target, contentStr);
|
|
264
|
-
} else {
|
|
265
|
-
// Upstream's morph MERGES a lone root element that has the
|
|
266
|
-
// target's tag (attributes and children); anything else morphs the
|
|
267
|
-
// children \u2014 the full runtime's 'morphMerge' (lib/swap-executor.ts).
|
|
268
|
-
const template = document.createElement('template');
|
|
269
|
-
template.innerHTML = contentStr;
|
|
270
|
-
const root = template.content.firstElementChild;
|
|
271
|
-
if (root && !root.nextElementSibling && root.tagName === target.tagName) {
|
|
272
|
-
morphlexMorph(target, root);
|
|
273
|
-
} else {
|
|
274
|
-
// morphlex's morphInner takes an ELEMENT, not a string \u2014 the same
|
|
275
|
-
// wrapping src/lib/morph-adapter.ts does. Passing the string threw
|
|
276
|
-
// straight into the fallback below, so inner morph never actually
|
|
277
|
-
// morphed (it silently degraded to innerHTML on every call).
|
|
278
|
-
const wrapper = document.createElement(target.tagName);
|
|
279
|
-
wrapper.innerHTML = contentStr;
|
|
280
|
-
morphlexMorphInner(target, wrapper);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
} catch (error) {
|
|
284
|
-
// Fallback to innerHTML/outerHTML if morph fails
|
|
285
|
-
console.warn('[LokaScript] Morph failed, falling back:', error);
|
|
286
|
-
if (isOuter) {
|
|
287
|
-
target.outerHTML = contentStr;
|
|
288
|
-
} else {
|
|
289
|
-
target.innerHTML = contentStr;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
294
|
-
return ctx.it;
|
|
295
|
-
}`,
|
|
296
|
-
take: `
|
|
297
|
-
case 'take': {
|
|
298
|
-
// getClassName reads a NODE (as toggle/add/removeClass all do). Passing the
|
|
299
|
-
// EVALUATED value handed it a NodeList, which yielded '' and then threw
|
|
300
|
-
// 'Invalid selector .' \u2014 a command the parse-level reachability check
|
|
301
|
-
// certified while every generated bundle carrying it crashed.
|
|
302
|
-
const className = getClassName(cmd.args[0]);
|
|
303
|
-
const from = cmd.target ? await getTarget() : [ctx.me.parentElement!];
|
|
304
|
-
for (const container of from) {
|
|
305
|
-
const siblings = container.querySelectorAll('.' + className);
|
|
306
|
-
siblings.forEach(el => el.classList.remove(className));
|
|
307
|
-
}
|
|
308
|
-
ctx.me.classList.add(className);
|
|
309
|
-
return ctx.me;
|
|
310
|
-
}`,
|
|
311
|
-
increment: `
|
|
312
|
-
case 'increment': {
|
|
313
|
-
const target = cmd.args[0];
|
|
314
|
-
const amount = cmd.args[1] ? await evaluate(cmd.args[1], ctx) : 1;
|
|
315
|
-
|
|
316
|
-
if (target.type === 'variable') {
|
|
317
|
-
const varName = target.name.slice(1);
|
|
318
|
-
const map = target.scope === 'local' ? elemScope(ctx.me) : globalVars;
|
|
319
|
-
const current = map.get(varName) || 0;
|
|
320
|
-
const newVal = current + amount;
|
|
321
|
-
map.set(varName, newVal);
|
|
322
|
-
ctx.it = newVal;
|
|
323
|
-
return newVal;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (target.type === 'possessive') {
|
|
327
|
-
const style = isStyleProp(target.property);
|
|
328
|
-
const obj = await evaluate(target.object, ctx);
|
|
329
|
-
const elements = toElementArray(obj);
|
|
330
|
-
for (const el of elements) {
|
|
331
|
-
const was = style ? getStyleProp(el, target.property) : Reflect.get(el, target.property);
|
|
332
|
-
const newVal = (parseFloat(was || '0') || 0) + amount;
|
|
333
|
-
if (style) setStyleProp(el, target.property, newVal);
|
|
334
|
-
else Reflect.set(el, target.property, newVal);
|
|
335
|
-
ctx.it = newVal;
|
|
336
|
-
}
|
|
337
|
-
return ctx.it;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const elements = toElementArray(await evaluate(target, ctx));
|
|
341
|
-
for (const el of elements) {
|
|
342
|
-
const current = parseFloat(el.textContent || '0') || 0;
|
|
343
|
-
const newVal = current + amount;
|
|
344
|
-
el.textContent = String(newVal);
|
|
345
|
-
ctx.it = newVal;
|
|
346
|
-
}
|
|
347
|
-
return ctx.it;
|
|
348
|
-
}`,
|
|
349
|
-
decrement: `
|
|
350
|
-
case 'decrement': {
|
|
351
|
-
const target = cmd.args[0];
|
|
352
|
-
const amount = cmd.args[1] ? await evaluate(cmd.args[1], ctx) : 1;
|
|
353
|
-
|
|
354
|
-
if (target.type === 'variable') {
|
|
355
|
-
const varName = target.name.slice(1);
|
|
356
|
-
const map = target.scope === 'local' ? elemScope(ctx.me) : globalVars;
|
|
357
|
-
const current = map.get(varName) || 0;
|
|
358
|
-
const newVal = current - amount;
|
|
359
|
-
map.set(varName, newVal);
|
|
360
|
-
ctx.it = newVal;
|
|
361
|
-
return newVal;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (target.type === 'possessive') {
|
|
365
|
-
const style = isStyleProp(target.property);
|
|
366
|
-
const obj = await evaluate(target.object, ctx);
|
|
367
|
-
const elements = toElementArray(obj);
|
|
368
|
-
for (const el of elements) {
|
|
369
|
-
const was = style ? getStyleProp(el, target.property) : Reflect.get(el, target.property);
|
|
370
|
-
const newVal = (parseFloat(was || '0') || 0) - amount;
|
|
371
|
-
if (style) setStyleProp(el, target.property, newVal);
|
|
372
|
-
else Reflect.set(el, target.property, newVal);
|
|
373
|
-
ctx.it = newVal;
|
|
374
|
-
}
|
|
375
|
-
return ctx.it;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const elements = toElementArray(await evaluate(target, ctx));
|
|
379
|
-
for (const el of elements) {
|
|
380
|
-
const current = parseFloat(el.textContent || '0') || 0;
|
|
381
|
-
const newVal = current - amount;
|
|
382
|
-
el.textContent = String(newVal);
|
|
383
|
-
ctx.it = newVal;
|
|
384
|
-
}
|
|
385
|
-
return ctx.it;
|
|
386
|
-
}`,
|
|
387
|
-
focus: `
|
|
388
|
-
case 'focus': {
|
|
389
|
-
const targets = await getTarget();
|
|
390
|
-
for (const el of targets) (el as HTMLElement).focus();
|
|
391
|
-
return targets;
|
|
392
|
-
}`,
|
|
393
|
-
blur: `
|
|
394
|
-
case 'blur': {
|
|
395
|
-
const targets = await getTarget();
|
|
396
|
-
for (const el of targets) (el as HTMLElement).blur();
|
|
397
|
-
return targets;
|
|
398
|
-
}`,
|
|
399
|
-
empty: `
|
|
400
|
-
case 'empty': {
|
|
401
|
-
const targets = await getTarget();
|
|
402
|
-
for (const el of targets) (el as HTMLElement).innerHTML = '';
|
|
403
|
-
return targets;
|
|
404
|
-
}`,
|
|
405
|
-
return: `
|
|
406
|
-
case 'return': {
|
|
407
|
-
const value = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : ctx.it;
|
|
408
|
-
throw { type: 'return', value };
|
|
409
|
-
}`,
|
|
410
|
-
break: `
|
|
411
|
-
case 'break': {
|
|
412
|
-
throw { type: 'break' };
|
|
413
|
-
}`,
|
|
414
|
-
continue: `
|
|
415
|
-
case 'continue': {
|
|
416
|
-
throw { type: 'continue' };
|
|
417
|
-
}`,
|
|
418
|
-
halt: `
|
|
419
|
-
case 'halt': {
|
|
420
|
-
// Check for "halt the event" pattern
|
|
421
|
-
const firstArg = cmd.args[0];
|
|
422
|
-
let targetEvent = ctx.event;
|
|
423
|
-
if (firstArg?.type === 'identifier' && firstArg.name === 'the' && cmd.args[1]?.name === 'event') {
|
|
424
|
-
targetEvent = ctx.event;
|
|
425
|
-
} else if (firstArg) {
|
|
426
|
-
const evaluated = await evaluate(firstArg, ctx);
|
|
427
|
-
if (evaluated?.preventDefault) targetEvent = evaluated;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (targetEvent && typeof targetEvent.preventDefault === 'function') {
|
|
431
|
-
targetEvent.preventDefault();
|
|
432
|
-
targetEvent.stopPropagation();
|
|
433
|
-
return { halted: true, eventHalted: true };
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Regular halt - stop execution
|
|
437
|
-
const haltError = new Error('HALT_EXECUTION');
|
|
438
|
-
(haltError as any).isHalt = true;
|
|
439
|
-
throw haltError;
|
|
440
|
-
}`,
|
|
441
|
-
exit: `
|
|
442
|
-
case 'exit': {
|
|
443
|
-
const exitError = new Error('EXIT_COMMAND');
|
|
444
|
-
(exitError as any).isExit = true;
|
|
445
|
-
throw exitError;
|
|
446
|
-
}`,
|
|
447
|
-
throw: `
|
|
448
|
-
case 'throw': {
|
|
449
|
-
const message = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : 'Error';
|
|
450
|
-
const errorToThrow = message instanceof Error ? message : new Error(String(message));
|
|
451
|
-
throw errorToThrow;
|
|
452
|
-
}`,
|
|
453
|
-
beep: `
|
|
454
|
-
case 'beep': {
|
|
455
|
-
const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
|
|
456
|
-
const displayValues = values.length > 0 ? values : [ctx.it];
|
|
457
|
-
|
|
458
|
-
for (const val of displayValues) {
|
|
459
|
-
const typeInfo = val === null ? 'null' :
|
|
460
|
-
val === undefined ? 'undefined' :
|
|
461
|
-
Array.isArray(val) ? \`Array[\${val.length}]\` :
|
|
462
|
-
val instanceof Element ? \`Element<\${val.tagName.toLowerCase()}>\` :
|
|
463
|
-
typeof val;
|
|
464
|
-
console.log('[beep]', typeInfo + ':', val);
|
|
465
|
-
}
|
|
466
|
-
return displayValues[0];
|
|
467
|
-
}`,
|
|
468
|
-
js: `
|
|
469
|
-
case 'js': {
|
|
470
|
-
const codeArg = cmd.args[0];
|
|
471
|
-
let jsCode = '';
|
|
472
|
-
|
|
473
|
-
if (codeArg.type === 'string') {
|
|
474
|
-
jsCode = codeArg.value;
|
|
475
|
-
} else if (codeArg.type === 'template') {
|
|
476
|
-
jsCode = await evaluate(codeArg, ctx);
|
|
477
|
-
} else {
|
|
478
|
-
jsCode = String(await evaluate(codeArg, ctx));
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Build context object for the Function
|
|
482
|
-
//
|
|
483
|
-
// \`target\` is \`ctx.me\` outright, not \`ctx.target || ctx.me\`. No Context
|
|
484
|
-
// in any bundle declares a \`target\` field and no executor assigns one, so
|
|
485
|
-
// the left operand was always \`undefined\` and the expression always
|
|
486
|
-
// yielded \`ctx.me\` \u2014 same value, and a type error the moment the template
|
|
487
|
-
// lands in a file that is actually typechecked (Arc E step 4, where it
|
|
488
|
-
// did). Generated bundles are written, imported and executed but never
|
|
489
|
-
// \`tsc\`'d, which is how a phantom property survived here.
|
|
490
|
-
const jsContext = {
|
|
491
|
-
me: ctx.me,
|
|
492
|
-
it: ctx.it,
|
|
493
|
-
event: ctx.event,
|
|
494
|
-
target: ctx.me,
|
|
495
|
-
locals: Object.fromEntries(ctx.locals),
|
|
496
|
-
globals: Object.fromEntries(globalVars),
|
|
497
|
-
document: typeof document !== 'undefined' ? document : undefined,
|
|
498
|
-
window: typeof window !== 'undefined' ? window : undefined,
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
const fn = new Function('ctx', \`with(ctx) { return (async () => { \${jsCode} })(); }\`);
|
|
503
|
-
const result = await fn(jsContext);
|
|
504
|
-
ctx.it = result;
|
|
505
|
-
return result;
|
|
506
|
-
} catch (error) {
|
|
507
|
-
console.error('[js] Execution error:', error);
|
|
508
|
-
throw error;
|
|
509
|
-
}
|
|
510
|
-
}`,
|
|
511
|
-
copy: `
|
|
512
|
-
case 'copy': {
|
|
513
|
-
const source = await evaluate(cmd.args[0], ctx);
|
|
514
|
-
let textToCopy = '';
|
|
515
|
-
|
|
516
|
-
if (typeof source === 'string') {
|
|
517
|
-
textToCopy = source;
|
|
518
|
-
} else if (source instanceof Element) {
|
|
519
|
-
textToCopy = source.textContent || '';
|
|
520
|
-
} else {
|
|
521
|
-
textToCopy = String(source);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
try {
|
|
525
|
-
if (navigator.clipboard) {
|
|
526
|
-
await navigator.clipboard.writeText(textToCopy);
|
|
527
|
-
} else {
|
|
528
|
-
// Fallback for older browsers
|
|
529
|
-
const textarea = document.createElement('textarea');
|
|
530
|
-
textarea.value = textToCopy;
|
|
531
|
-
textarea.style.cssText = 'position:fixed;top:0;left:-9999px';
|
|
532
|
-
document.body.appendChild(textarea);
|
|
533
|
-
textarea.select();
|
|
534
|
-
document.execCommand('copy');
|
|
535
|
-
document.body.removeChild(textarea);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (ctx.me instanceof Element) {
|
|
539
|
-
ctx.me.dispatchEvent(new CustomEvent('copy:success', {
|
|
540
|
-
bubbles: true,
|
|
541
|
-
detail: { text: textToCopy }
|
|
542
|
-
}));
|
|
543
|
-
}
|
|
544
|
-
ctx.it = textToCopy;
|
|
545
|
-
return textToCopy;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
if (ctx.me instanceof Element) {
|
|
548
|
-
ctx.me.dispatchEvent(new CustomEvent('copy:error', {
|
|
549
|
-
bubbles: true,
|
|
550
|
-
detail: { error }
|
|
551
|
-
}));
|
|
552
|
-
}
|
|
553
|
-
throw error;
|
|
554
|
-
}
|
|
555
|
-
}`,
|
|
556
|
-
push: `
|
|
557
|
-
case 'push': {
|
|
558
|
-
const url = String(await evaluate(cmd.args[0], ctx));
|
|
559
|
-
let title = '';
|
|
560
|
-
|
|
561
|
-
// Check for "with title" modifier
|
|
562
|
-
if (cmd.modifiers?.title) {
|
|
563
|
-
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
window.history.pushState(null, '', url);
|
|
567
|
-
if (title) document.title = title;
|
|
568
|
-
|
|
569
|
-
window.dispatchEvent(new CustomEvent('lokascript:pushurl', {
|
|
570
|
-
detail: { url, title }
|
|
571
|
-
}));
|
|
572
|
-
|
|
573
|
-
return { url, title, mode: 'push' };
|
|
574
|
-
}`,
|
|
575
|
-
replace: `
|
|
576
|
-
case 'replace': {
|
|
577
|
-
const url = String(await evaluate(cmd.args[0], ctx));
|
|
578
|
-
let title = '';
|
|
579
|
-
|
|
580
|
-
// Check for "with title" modifier
|
|
581
|
-
if (cmd.modifiers?.title) {
|
|
582
|
-
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
window.history.replaceState(null, '', url);
|
|
586
|
-
if (title) document.title = title;
|
|
587
|
-
|
|
588
|
-
window.dispatchEvent(new CustomEvent('lokascript:replaceurl', {
|
|
589
|
-
detail: { url, title }
|
|
590
|
-
}));
|
|
591
|
-
|
|
592
|
-
return { url, title, mode: 'replace' };
|
|
593
|
-
}`,
|
|
594
|
-
};
|
|
595
|
-
const BLOCK_IMPLEMENTATIONS_TS = {
|
|
596
|
-
if: `
|
|
597
|
-
case 'if': {
|
|
598
|
-
const condition = await evaluate(block.condition!, ctx);
|
|
599
|
-
if (condition) {
|
|
600
|
-
return executeSequenceWithBlocks(block.body, ctx);
|
|
601
|
-
} else if (block.elseBody) {
|
|
602
|
-
return executeSequenceWithBlocks(block.elseBody, ctx);
|
|
603
|
-
}
|
|
604
|
-
return null;
|
|
605
|
-
}`,
|
|
606
|
-
repeat: `
|
|
607
|
-
case 'repeat': {
|
|
608
|
-
const count = await evaluate(block.condition!, ctx);
|
|
609
|
-
// A count reads as upstream's \`index < times\` does, and core's loopCount:
|
|
610
|
-
// "6.5" loops 7 times, "6abc" none (parseInt read 6 for both), true once.
|
|
611
|
-
const n = Number(count) || 0;
|
|
612
|
-
for (let i = 0; i < n && i < MAX_LOOP_ITERATIONS; i++) {
|
|
613
|
-
ctx.locals.set('__loop_index__', i);
|
|
614
|
-
ctx.locals.set('__loop_count__', i + 1);
|
|
615
|
-
try {
|
|
616
|
-
await executeSequenceWithBlocks(block.body, ctx);
|
|
617
|
-
} catch (e: any) {
|
|
618
|
-
if (e?.type === 'break') break;
|
|
619
|
-
if (e?.type === 'continue') continue;
|
|
620
|
-
throw e;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return null;
|
|
624
|
-
}`,
|
|
625
|
-
for: `
|
|
626
|
-
case 'for': {
|
|
627
|
-
const { variable, iterable } = block.condition as any;
|
|
628
|
-
const items = await evaluate(iterable, ctx);
|
|
629
|
-
const arr = Array.isArray(items) ? items : items instanceof NodeList ? Array.from(items) : [items];
|
|
630
|
-
const varName = variable.startsWith(':') ? variable.slice(1) : variable;
|
|
631
|
-
for (let i = 0; i < arr.length && i < MAX_LOOP_ITERATIONS; i++) {
|
|
632
|
-
ctx.locals.set(varName, arr[i]);
|
|
633
|
-
ctx.locals.set('__loop_index__', i);
|
|
634
|
-
ctx.locals.set('__loop_count__', i + 1);
|
|
635
|
-
try {
|
|
636
|
-
await executeSequenceWithBlocks(block.body, ctx);
|
|
637
|
-
} catch (e: any) {
|
|
638
|
-
if (e?.type === 'break') break;
|
|
639
|
-
if (e?.type === 'continue') continue;
|
|
640
|
-
throw e;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return null;
|
|
644
|
-
}`,
|
|
645
|
-
while: `
|
|
646
|
-
case 'while': {
|
|
647
|
-
let iterations = 0;
|
|
648
|
-
while (await evaluate(block.condition!, ctx) && iterations < MAX_LOOP_ITERATIONS) {
|
|
649
|
-
ctx.locals.set('__loop_index__', iterations);
|
|
650
|
-
try {
|
|
651
|
-
await executeSequenceWithBlocks(block.body, ctx);
|
|
652
|
-
} catch (e: any) {
|
|
653
|
-
if (e?.type === 'break') break;
|
|
654
|
-
if (e?.type === 'continue') { iterations++; continue; }
|
|
655
|
-
throw e;
|
|
656
|
-
}
|
|
657
|
-
iterations++;
|
|
658
|
-
}
|
|
659
|
-
return null;
|
|
660
|
-
}`,
|
|
661
|
-
fetch: `
|
|
662
|
-
case 'fetch': {
|
|
663
|
-
const { url, responseType, options, method } = block.condition as any;
|
|
664
|
-
try {
|
|
665
|
-
const urlVal = await evaluate(url, ctx);
|
|
666
|
-
const fetchInit = {} as RequestInit;
|
|
667
|
-
|
|
668
|
-
if (method) {
|
|
669
|
-
const methodVal = await evaluate(method, ctx);
|
|
670
|
-
fetchInit.method = String(methodVal).toUpperCase();
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (options) {
|
|
674
|
-
const optionsVal = await evaluate(options, ctx);
|
|
675
|
-
if (optionsVal instanceof FormData) {
|
|
676
|
-
fetchInit.body = optionsVal;
|
|
677
|
-
} else if (optionsVal && typeof optionsVal === 'object' && !(optionsVal instanceof Element)) {
|
|
678
|
-
Object.assign(fetchInit, optionsVal);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const response = await fetch(String(urlVal), fetchInit);
|
|
683
|
-
if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
|
|
684
|
-
|
|
685
|
-
const resType = await evaluate(responseType, ctx);
|
|
686
|
-
let data: any;
|
|
687
|
-
if (resType === 'json') data = await response.json();
|
|
688
|
-
else if (resType === 'html') {
|
|
689
|
-
const text = await response.text();
|
|
690
|
-
const doc = new DOMParser().parseFromString(text, 'text/html');
|
|
691
|
-
data = doc.body.innerHTML;
|
|
692
|
-
} else data = await response.text();
|
|
693
|
-
|
|
694
|
-
ctx.it = data;
|
|
695
|
-
ctx.locals.set('it', data);
|
|
696
|
-
ctx.locals.set('result', data);
|
|
697
|
-
ctx.locals.set('response', response);
|
|
698
|
-
|
|
699
|
-
await executeSequenceWithBlocks(block.body, ctx);
|
|
700
|
-
} catch (error: any) {
|
|
701
|
-
if (error?.type === 'return') throw error;
|
|
702
|
-
ctx.locals.set('error', error);
|
|
703
|
-
console.error('Fetch error:', error);
|
|
704
|
-
}
|
|
705
|
-
return null;
|
|
706
|
-
}`,
|
|
707
|
-
};
|
|
708
|
-
const STYLE_COMMANDS = ['set', 'put', 'increment', 'decrement'];
|
|
709
|
-
const STYLE_READ_COMMANDS = ['increment', 'decrement'];
|
|
710
|
-
const ELEMENT_ARRAY_COMMANDS = ['put', 'increment', 'decrement'];
|
|
711
|
-
const MORPH_COMMANDS = ['morph'];
|
|
712
|
-
function getCommandImplementations(format = 'ts') {
|
|
713
|
-
const result = {};
|
|
714
|
-
for (const [name, code] of Object.entries(COMMAND_IMPLEMENTATIONS_TS)) {
|
|
715
|
-
result[name] = stripTypes(code, format);
|
|
716
|
-
}
|
|
717
|
-
return result;
|
|
718
|
-
}
|
|
719
|
-
function getBlockImplementations(format = 'ts') {
|
|
720
|
-
const result = {};
|
|
721
|
-
for (const [name, code] of Object.entries(BLOCK_IMPLEMENTATIONS_TS)) {
|
|
722
|
-
result[name] = stripTypes(code, format);
|
|
723
|
-
}
|
|
724
|
-
return result;
|
|
725
|
-
}
|
|
726
|
-
function getAvailableCommands() {
|
|
727
|
-
return Object.keys(COMMAND_IMPLEMENTATIONS_TS);
|
|
728
|
-
}
|
|
729
|
-
function getAvailableBlocks() {
|
|
730
|
-
return Object.keys(BLOCK_IMPLEMENTATIONS_TS);
|
|
731
|
-
}
|
|
732
|
-
const COMMAND_IMPLEMENTATIONS = COMMAND_IMPLEMENTATIONS_TS;
|
|
733
|
-
const BLOCK_IMPLEMENTATIONS = BLOCK_IMPLEMENTATIONS_TS;
|
|
734
|
-
|
|
735
|
-
const AVAILABLE_COMMANDS = [
|
|
736
|
-
'toggle',
|
|
737
|
-
'add',
|
|
738
|
-
'remove',
|
|
739
|
-
'removeClass',
|
|
740
|
-
'show',
|
|
741
|
-
'hide',
|
|
742
|
-
'put',
|
|
743
|
-
'append',
|
|
744
|
-
'prepend',
|
|
745
|
-
'take',
|
|
746
|
-
'empty',
|
|
747
|
-
'set',
|
|
748
|
-
'get',
|
|
749
|
-
'increment',
|
|
750
|
-
'decrement',
|
|
751
|
-
'wait',
|
|
752
|
-
'transition',
|
|
753
|
-
'send',
|
|
754
|
-
'trigger',
|
|
755
|
-
'log',
|
|
756
|
-
'call',
|
|
757
|
-
'copy',
|
|
758
|
-
'beep',
|
|
759
|
-
'go',
|
|
760
|
-
'push',
|
|
761
|
-
'push-url',
|
|
762
|
-
'replace',
|
|
763
|
-
'replace-url',
|
|
764
|
-
'focus',
|
|
765
|
-
'blur',
|
|
766
|
-
'return',
|
|
767
|
-
'break',
|
|
768
|
-
'continue',
|
|
769
|
-
'halt',
|
|
770
|
-
'exit',
|
|
771
|
-
'throw',
|
|
772
|
-
'js',
|
|
773
|
-
'morph',
|
|
774
|
-
];
|
|
775
|
-
const AVAILABLE_BLOCKS = ['if', 'repeat', 'for', 'while', 'fetch'];
|
|
776
|
-
const FULL_RUNTIME_ONLY_COMMANDS = [
|
|
777
|
-
'make',
|
|
778
|
-
'swap',
|
|
779
|
-
'process',
|
|
780
|
-
'default',
|
|
781
|
-
'tell',
|
|
782
|
-
'pick',
|
|
783
|
-
'unless',
|
|
784
|
-
'settle',
|
|
785
|
-
'measure',
|
|
786
|
-
'install',
|
|
787
|
-
'breakpoint',
|
|
788
|
-
'clear',
|
|
789
|
-
'close',
|
|
790
|
-
'open',
|
|
791
|
-
'pseudo-command',
|
|
792
|
-
'render',
|
|
793
|
-
'reset',
|
|
794
|
-
'scroll',
|
|
795
|
-
'select',
|
|
796
|
-
'start',
|
|
797
|
-
];
|
|
798
|
-
const COMMAND_ALIASES = {
|
|
799
|
-
'push-url': 'push',
|
|
800
|
-
'replace-url': 'replace',
|
|
801
|
-
trigger: 'send',
|
|
802
|
-
};
|
|
803
|
-
function resolveCommandKey(command) {
|
|
804
|
-
return COMMAND_ALIASES[command] ?? command;
|
|
805
|
-
}
|
|
806
|
-
function isAvailableCommand(command) {
|
|
807
|
-
return AVAILABLE_COMMANDS.includes(command);
|
|
808
|
-
}
|
|
809
|
-
function isAvailableBlock(block) {
|
|
810
|
-
return AVAILABLE_BLOCKS.includes(block);
|
|
811
|
-
}
|
|
812
|
-
function requiresFullRuntime(command) {
|
|
813
|
-
return FULL_RUNTIME_ONLY_COMMANDS.includes(command);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function emitCommandCases(commands, format = 'ts') {
|
|
817
|
-
const impls = getCommandImplementations(format);
|
|
818
|
-
return [...new Set(commands.map(resolveCommandKey))]
|
|
819
|
-
.filter(key => impls[key])
|
|
820
|
-
.map(key => impls[key])
|
|
821
|
-
.join('\n');
|
|
822
|
-
}
|
|
823
|
-
function emitBlockCases(blocks, format = 'ts') {
|
|
824
|
-
const impls = getBlockImplementations(format);
|
|
825
|
-
return blocks
|
|
826
|
-
.filter(key => impls[key])
|
|
827
|
-
.map(key => impls[key])
|
|
828
|
-
.join('\n');
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function generateBundleCode(config) {
|
|
832
|
-
const { name, commands, blocks = [], htmxIntegration = false, globalName = 'hyperfixi', positionalExpressions = false, parserImportPath = '../parser/hybrid', autoInit = true, esModule = true, format = 'ts', maxLoopIterations = 1000, } = config;
|
|
833
|
-
const needsStyleHelpers = commands.some(cmd => STYLE_COMMANDS.includes(cmd));
|
|
834
|
-
const needsStyleReadHelper = commands.some(cmd => STYLE_READ_COMMANDS.includes(cmd));
|
|
835
|
-
const needsElementArrayHelper = commands.some(cmd => ELEMENT_ARRAY_COMMANDS.includes(cmd));
|
|
836
|
-
const needsMorphlex = commands.some(cmd => MORPH_COMMANDS.includes(cmd));
|
|
837
|
-
const hasBlocks = blocks.length > 0;
|
|
838
|
-
const hasReturn = commands.includes('return');
|
|
839
|
-
const commandCases = emitCommandCases(commands, format);
|
|
840
|
-
const blockCases = emitBlockCases(blocks, format);
|
|
841
|
-
return `/**
|
|
842
|
-
* HyperFixi ${name} Bundle (Auto-Generated)
|
|
843
|
-
*
|
|
844
|
-
* Generated by: @hyperfixi/core bundle-generator
|
|
845
|
-
* Commands: ${commands.join(', ')}${blocks.length > 0 ? `\n * Blocks: ${blocks.join(', ')}` : ''}${positionalExpressions ? '\n * Positional expressions: enabled' : ''}
|
|
846
|
-
*
|
|
847
|
-
* DO NOT EDIT - Regenerate with the build script.
|
|
848
|
-
*/
|
|
849
|
-
|
|
850
|
-
// =============================================================================
|
|
851
|
-
// MODULAR PARSER IMPORTS
|
|
852
|
-
// =============================================================================
|
|
853
|
-
|
|
854
|
-
import { HybridParser } from '${parserImportPath}/parser-core';
|
|
855
|
-
import type { ASTNode, CommandNode, EventNode${hasBlocks ? ', BlockNode' : ''} } from '${parserImportPath}/ast-types';
|
|
856
|
-
${needsMorphlex ? "import { morph as morphlexMorph, morphInner as morphlexMorphInner } from 'morphlex';" : ''}
|
|
857
|
-
|
|
858
|
-
// =============================================================================
|
|
859
|
-
// RUNTIME
|
|
860
|
-
// =============================================================================
|
|
861
|
-
|
|
862
|
-
interface Context {
|
|
863
|
-
me: Element;
|
|
864
|
-
event?: Event;
|
|
865
|
-
it?: any;
|
|
866
|
-
you?: Element;
|
|
867
|
-
locals: Map<string, any>;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const globalVars = new Map<string, any>();
|
|
871
|
-
// Per-element store for \`:name\` variables (scope 'local' from the mini-parser).
|
|
872
|
-
// Persists per-element across event firings, isolated per element.
|
|
873
|
-
const elementVars = new WeakMap<Element, Map<string, any>>();
|
|
874
|
-
function elemScope(el: Element): Map<string, any> {
|
|
875
|
-
let m = elementVars.get(el);
|
|
876
|
-
if (!m) { m = new Map<string, any>(); elementVars.set(el, m); }
|
|
877
|
-
return m;
|
|
878
|
-
}
|
|
879
|
-
${hasBlocks ? `const MAX_LOOP_ITERATIONS = ${maxLoopIterations};` : ''}
|
|
880
|
-
|
|
881
|
-
async function evaluate(node: ASTNode, ctx: Context): Promise<any> {
|
|
882
|
-
switch (node.type) {
|
|
883
|
-
case 'literal': return node.value;
|
|
884
|
-
|
|
885
|
-
case 'identifier':
|
|
886
|
-
if (node.value === 'me' || node.value === 'my') return ctx.me;
|
|
887
|
-
if (node.value === 'it') return ctx.it;
|
|
888
|
-
if (node.value === 'you') return ctx.you;
|
|
889
|
-
if (node.value === 'event') return ctx.event;
|
|
890
|
-
if (node.value === 'body') return document.body;
|
|
891
|
-
if (node.value === 'document') return document;
|
|
892
|
-
if (node.value === 'window') return window;
|
|
893
|
-
if (ctx.locals.has(node.value)) return ctx.locals.get(node.value);
|
|
894
|
-
if (node.value in (ctx.me as any)) return (ctx.me as any)[node.value];
|
|
895
|
-
return node.value;
|
|
896
|
-
|
|
897
|
-
case 'variable':
|
|
898
|
-
if (node.scope === 'local') return elemScope(ctx.me).get(node.name.slice(1));
|
|
899
|
-
const gName = node.name.slice(1);
|
|
900
|
-
if (globalVars.has(gName)) return globalVars.get(gName);
|
|
901
|
-
return (window as any)[node.name];
|
|
902
|
-
|
|
903
|
-
case 'selector':
|
|
904
|
-
const elements = document.querySelectorAll(node.value);
|
|
905
|
-
return elements.length === 1 ? elements[0] : Array.from(elements);
|
|
906
|
-
|
|
907
|
-
case 'binary':
|
|
908
|
-
return evaluateBinary(node, ctx);
|
|
909
|
-
|
|
910
|
-
case 'possessive':
|
|
911
|
-
case 'member':
|
|
912
|
-
const obj = await evaluate(node.object, ctx);
|
|
913
|
-
if (obj == null) return undefined;
|
|
914
|
-
const prop = node.computed ? await evaluate(node.property, ctx) : node.property;
|
|
915
|
-
if (prop === 'values' && obj instanceof Element) {
|
|
916
|
-
if (obj instanceof HTMLFormElement) return new FormData(obj);
|
|
917
|
-
const fd = new FormData();
|
|
918
|
-
obj.querySelectorAll('input, select, textarea').forEach(input => {
|
|
919
|
-
const name = input.getAttribute('name');
|
|
920
|
-
if (name && 'value' in input) fd.append(name, (input as HTMLInputElement).value);
|
|
921
|
-
});
|
|
922
|
-
return fd;
|
|
923
|
-
}
|
|
924
|
-
return obj[prop];
|
|
925
|
-
|
|
926
|
-
case 'call': {
|
|
927
|
-
let callContext: any = null;
|
|
928
|
-
let callee: any;
|
|
929
|
-
|
|
930
|
-
if (node.callee.type === 'member' || node.callee.type === 'possessive') {
|
|
931
|
-
callContext = await evaluate(node.callee.object, ctx);
|
|
932
|
-
const p = node.callee.computed
|
|
933
|
-
? await evaluate(node.callee.property, ctx)
|
|
934
|
-
: node.callee.property;
|
|
935
|
-
callee = callContext?.[p];
|
|
936
|
-
} else {
|
|
937
|
-
callee = await evaluate(node.callee, ctx);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
const args = await Promise.all(node.args.map((a: ASTNode) => evaluate(a, ctx)));
|
|
941
|
-
if (typeof callee === 'function') return callee.apply(callContext, args);
|
|
942
|
-
return undefined;
|
|
943
|
-
}
|
|
944
|
-
${positionalExpressions
|
|
945
|
-
? `
|
|
946
|
-
case 'positional':
|
|
947
|
-
return evaluatePositional(node, ctx);
|
|
948
|
-
`
|
|
949
|
-
: ''}
|
|
950
|
-
default: return undefined;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
async function evaluateBinary(node: ASTNode, ctx: Context): Promise<any> {
|
|
955
|
-
if (node.operator === 'has') {
|
|
956
|
-
const left = await evaluate(node.left, ctx);
|
|
957
|
-
if (left instanceof Element && node.right.type === 'selector' && node.right.value.startsWith('.')) {
|
|
958
|
-
return left.classList.contains(node.right.value.slice(1));
|
|
959
|
-
}
|
|
960
|
-
return false;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
const left = await evaluate(node.left, ctx);
|
|
964
|
-
const right = await evaluate(node.right, ctx);
|
|
965
|
-
|
|
966
|
-
switch (node.operator) {
|
|
967
|
-
case '+': return left + right;
|
|
968
|
-
case '-': return left - right;
|
|
969
|
-
case '*': return left * right;
|
|
970
|
-
case '/': return left / right;
|
|
971
|
-
case '%': return left % right;
|
|
972
|
-
case '==': case 'is': return left == right;
|
|
973
|
-
case '!=': case 'is not': return left != right;
|
|
974
|
-
case '<': return left < right;
|
|
975
|
-
case '>': return left > right;
|
|
976
|
-
case '<=': return left <= right;
|
|
977
|
-
case '>=': return left >= right;
|
|
978
|
-
case 'and': case '&&': return left && right;
|
|
979
|
-
case 'or': case '||': return left || right;
|
|
980
|
-
case 'contains': case 'includes':
|
|
981
|
-
if (typeof left === 'string') return left.includes(right);
|
|
982
|
-
if (Array.isArray(left)) return left.includes(right);
|
|
983
|
-
if (left instanceof Element) return left.contains(right);
|
|
984
|
-
return false;
|
|
985
|
-
case 'matches':
|
|
986
|
-
if (left instanceof Element) return left.matches(right);
|
|
987
|
-
if (typeof left === 'string') return new RegExp(right).test(left);
|
|
988
|
-
return false;
|
|
989
|
-
default: return undefined;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
${positionalExpressions
|
|
993
|
-
? `
|
|
994
|
-
function evaluatePositional(node: ASTNode, ctx: Context): Element | null {
|
|
995
|
-
const target = node.target;
|
|
996
|
-
let elements: Element[] = [];
|
|
997
|
-
|
|
998
|
-
let selector: string | null = null;
|
|
999
|
-
if (target.type === 'selector') {
|
|
1000
|
-
selector = target.value;
|
|
1001
|
-
} else if (target.type === 'identifier') {
|
|
1002
|
-
selector = target.value;
|
|
1003
|
-
} else if (target.type === 'htmlSelector') {
|
|
1004
|
-
selector = target.value;
|
|
1005
|
-
}
|
|
1006
|
-
if (selector) {
|
|
1007
|
-
elements = Array.from(document.querySelectorAll(selector));
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
switch (node.position) {
|
|
1011
|
-
case 'first': return elements[0] || null;
|
|
1012
|
-
case 'last': return elements[elements.length - 1] || null;
|
|
1013
|
-
case 'next': {
|
|
1014
|
-
if (selector) {
|
|
1015
|
-
let el = ctx.me.nextElementSibling;
|
|
1016
|
-
while (el) { if (el.matches(selector)) return el; el = el.nextElementSibling; }
|
|
1017
|
-
return null;
|
|
1018
|
-
}
|
|
1019
|
-
return ctx.me.nextElementSibling;
|
|
1020
|
-
}
|
|
1021
|
-
case 'previous': {
|
|
1022
|
-
if (selector) {
|
|
1023
|
-
let el = ctx.me.previousElementSibling;
|
|
1024
|
-
while (el) { if (el.matches(selector)) return el; el = el.previousElementSibling; }
|
|
1025
|
-
return null;
|
|
1026
|
-
}
|
|
1027
|
-
return ctx.me.previousElementSibling;
|
|
1028
|
-
}
|
|
1029
|
-
case 'closest': return selector ? ctx.me.closest(selector) : null;
|
|
1030
|
-
case 'parent': {
|
|
1031
|
-
if (selector) {
|
|
1032
|
-
let el = ctx.me.parentElement;
|
|
1033
|
-
while (el) { if (el.matches(selector)) return el; el = el.parentElement; }
|
|
1034
|
-
return null;
|
|
1035
|
-
}
|
|
1036
|
-
return ctx.me.parentElement;
|
|
1037
|
-
}
|
|
1038
|
-
default: return elements[0] || null;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
`
|
|
1042
|
-
: ''}
|
|
1043
|
-
// =============================================================================
|
|
1044
|
-
// COMMAND EXECUTOR
|
|
1045
|
-
// =============================================================================
|
|
1046
|
-
|
|
1047
|
-
${needsStyleHelpers
|
|
1048
|
-
? `
|
|
1049
|
-
const isStyleProp = (prop: string) => prop?.startsWith('*');
|
|
1050
|
-
const getStyleName = (prop: string) => prop.substring(1);
|
|
1051
|
-
const setStyleProp = (el: Element, prop: string, value: any): boolean => {
|
|
1052
|
-
if (!isStyleProp(prop)) return false;
|
|
1053
|
-
(el as HTMLElement).style.setProperty(getStyleName(prop), String(value));
|
|
1054
|
-
return true;
|
|
1055
|
-
};
|
|
1056
|
-
${needsStyleReadHelper
|
|
1057
|
-
? `const getStyleProp = (el: Element, prop: string): string | undefined => {
|
|
1058
|
-
if (!isStyleProp(prop)) return undefined;
|
|
1059
|
-
return getComputedStyle(el as HTMLElement).getPropertyValue(getStyleName(prop));
|
|
1060
|
-
};
|
|
1061
|
-
`
|
|
1062
|
-
: ''}`
|
|
1063
|
-
: ''}
|
|
1064
|
-
|
|
1065
|
-
${needsElementArrayHelper
|
|
1066
|
-
? `
|
|
1067
|
-
const toElementArray = (val: any): Element[] => {
|
|
1068
|
-
if (Array.isArray(val)) return val.filter(e => e instanceof Element);
|
|
1069
|
-
if (val instanceof Element) return [val];
|
|
1070
|
-
if (typeof val === 'string') return Array.from(document.querySelectorAll(val));
|
|
1071
|
-
return [];
|
|
1072
|
-
};
|
|
1073
|
-
`
|
|
1074
|
-
: ''}
|
|
1075
|
-
|
|
1076
|
-
async function executeCommand(cmd: CommandNode, ctx: Context): Promise<any> {
|
|
1077
|
-
const getTarget = async (): Promise<Element[]> => {
|
|
1078
|
-
if (!cmd.target) return [ctx.me];
|
|
1079
|
-
const t = await evaluate(cmd.target, ctx);
|
|
1080
|
-
if (Array.isArray(t)) return t;
|
|
1081
|
-
if (t instanceof Element) return [t];
|
|
1082
|
-
if (typeof t === 'string') return Array.from(document.querySelectorAll(t));
|
|
1083
|
-
return [ctx.me];
|
|
1084
|
-
};
|
|
1085
|
-
|
|
1086
|
-
const getClassName = (node: any): string => {
|
|
1087
|
-
if (!node) return '';
|
|
1088
|
-
if (node.type === 'selector') {
|
|
1089
|
-
// \`@attr\` passes through unsliced; .class / #id lose their prefix.
|
|
1090
|
-
if (node.value.startsWith('@')) return node.value;
|
|
1091
|
-
return node.value.slice(1);
|
|
1092
|
-
}
|
|
1093
|
-
if (node.type === 'string' || node.type === 'literal') {
|
|
1094
|
-
const val = node.value;
|
|
1095
|
-
return typeof val === 'string' && val.startsWith('.') ? val.slice(1) : String(val);
|
|
1096
|
-
}
|
|
1097
|
-
if (node.type === 'identifier') return node.value;
|
|
1098
|
-
return '';
|
|
1099
|
-
};
|
|
1100
|
-
|
|
1101
|
-
switch (cmd.name) {
|
|
1102
|
-
${commandCases}
|
|
1103
|
-
|
|
1104
|
-
default:
|
|
1105
|
-
console.warn(\`${name} bundle: Unknown command '\${cmd.name}'\`);
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
${hasBlocks
|
|
1110
|
-
? `
|
|
1111
|
-
// =============================================================================
|
|
1112
|
-
// BLOCK EXECUTOR
|
|
1113
|
-
// =============================================================================
|
|
1114
|
-
|
|
1115
|
-
async function executeBlock(block: BlockNode, ctx: Context): Promise<any> {
|
|
1116
|
-
switch (block.type) {
|
|
1117
|
-
${blockCases}
|
|
1118
|
-
|
|
1119
|
-
default:
|
|
1120
|
-
console.warn(\`${name} bundle: Unknown block '\${block.type}'\`);
|
|
1121
|
-
return null;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
`
|
|
1125
|
-
: ''}
|
|
1126
|
-
// =============================================================================
|
|
1127
|
-
// SEQUENCE EXECUTOR
|
|
1128
|
-
// =============================================================================
|
|
1129
|
-
|
|
1130
|
-
async function executeSequence(nodes: ASTNode[], ctx: Context): Promise<any> {
|
|
1131
|
-
let result: any;
|
|
1132
|
-
for (const node of nodes) {
|
|
1133
|
-
if (node.type === 'command') {
|
|
1134
|
-
result = await executeCommand(node as CommandNode, ctx);
|
|
1135
|
-
}${hasBlocks
|
|
1136
|
-
? ` else if (['if', 'repeat', 'for', 'while', 'fetch'].includes(node.type)) {
|
|
1137
|
-
result = await executeBlock(node as BlockNode, ctx);
|
|
1138
|
-
}`
|
|
1139
|
-
: ''}
|
|
1140
|
-
}
|
|
1141
|
-
return result;
|
|
1142
|
-
}
|
|
1143
|
-
${hasBlocks
|
|
1144
|
-
? `
|
|
1145
|
-
async function executeSequenceWithBlocks(nodes: ASTNode[], ctx: Context): Promise<any> {
|
|
1146
|
-
try {
|
|
1147
|
-
return await executeSequence(nodes, ctx);
|
|
1148
|
-
} catch (e: any) {
|
|
1149
|
-
if (e?.type === 'return') throw e;
|
|
1150
|
-
throw e;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
`
|
|
1154
|
-
: ''}
|
|
1155
|
-
async function executeAST(ast: ASTNode, me: Element, event?: Event): Promise<any> {
|
|
1156
|
-
const ctx: Context = { me, event, locals: new Map() };
|
|
1157
|
-
|
|
1158
|
-
if (ast.type === 'sequence') {
|
|
1159
|
-
${hasReturn || hasBlocks ? "try { return await executeSequence(ast.commands, ctx); } catch (e: any) { if (e?.type === 'return') return e.value; throw e; }" : 'return executeSequence(ast.commands, ctx);'}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (ast.type === 'event') {
|
|
1163
|
-
const eventNode = ast as EventNode;
|
|
1164
|
-
const eventName = eventNode.event;
|
|
1165
|
-
|
|
1166
|
-
if (eventName === 'init') {
|
|
1167
|
-
${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, ctx); } catch (e: any) { if (e?.type !== 'return') throw e; }" : 'await executeSequence(eventNode.body, ctx);'}
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
const target = eventNode.filter ? await evaluate(eventNode.filter, ctx) : me;
|
|
1172
|
-
const targetEl = target instanceof Element ? target : me;
|
|
1173
|
-
const mods = eventNode.modifiers;
|
|
1174
|
-
|
|
1175
|
-
let handler = async (e: Event) => {
|
|
1176
|
-
if (mods.prevent) e.preventDefault();
|
|
1177
|
-
if (mods.stop) e.stopPropagation();
|
|
1178
|
-
|
|
1179
|
-
const handlerCtx: Context = {
|
|
1180
|
-
me, event: e,
|
|
1181
|
-
you: e.target instanceof Element ? e.target : undefined,
|
|
1182
|
-
locals: new Map(),
|
|
1183
|
-
};
|
|
1184
|
-
${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, handlerCtx); } catch (err: any) { if (err?.type !== 'return') throw err; }" : 'await executeSequence(eventNode.body, handlerCtx);'}
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
if (mods.debounce) {
|
|
1188
|
-
let timeout: any;
|
|
1189
|
-
const orig = handler;
|
|
1190
|
-
handler = async (e: Event) => {
|
|
1191
|
-
clearTimeout(timeout);
|
|
1192
|
-
timeout = setTimeout(() => orig(e), mods.debounce);
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (mods.throttle) {
|
|
1197
|
-
let lastCall = 0;
|
|
1198
|
-
const orig = handler;
|
|
1199
|
-
handler = async (e: Event) => {
|
|
1200
|
-
const now = Date.now();
|
|
1201
|
-
if (now - lastCall >= mods.throttle!) {
|
|
1202
|
-
lastCall = now;
|
|
1203
|
-
await orig(e);
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
targetEl.addEventListener(eventName, handler, { once: !!mods.once });
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
return null;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// =============================================================================
|
|
1216
|
-
// DOM PROCESSOR
|
|
1217
|
-
// =============================================================================
|
|
1218
|
-
|
|
1219
|
-
function processElement(el: Element): void {
|
|
1220
|
-
const code = el.getAttribute('_');
|
|
1221
|
-
if (!code) return;
|
|
1222
|
-
|
|
1223
|
-
try {
|
|
1224
|
-
const parser = new HybridParser(code);
|
|
1225
|
-
const ast = parser.parse();
|
|
1226
|
-
executeAST(ast, el);
|
|
1227
|
-
} catch (err) {
|
|
1228
|
-
console.error('HyperFixi ${name} error:', err, 'Code:', code);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
function processElements(root: Element | Document = document): void {
|
|
1233
|
-
const elements = root.querySelectorAll('[_]');
|
|
1234
|
-
elements.forEach(processElement);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// =============================================================================
|
|
1238
|
-
// PUBLIC API
|
|
1239
|
-
// =============================================================================
|
|
1240
|
-
// Shape mirrors \`compatibility/bundle-shell.ts\` (BundleShellApi). The two are
|
|
1241
|
-
// separate copies on purpose \u2014 a generated bundle is self-contained and must
|
|
1242
|
-
// not import from @hyperfixi/core \u2014 so \`bundle-shell.test.ts\` asserts the
|
|
1243
|
-
// emitted key set matches the handwritten one, the way Oracle 2 pins the two
|
|
1244
|
-
// cmdMaps. \`run\`/\`eval\`/\`parserName\` are generated-only extras; see the
|
|
1245
|
-
// witnessed-extras table in bundle-shell.ts for why they stay here and are not
|
|
1246
|
-
// added to the shipped handwritten bundles.
|
|
1247
|
-
|
|
1248
|
-
const api = {
|
|
1249
|
-
version: '1.0.0-${name.toLowerCase().replace(/\s+/g, '-')}',
|
|
1250
|
-
|
|
1251
|
-
parse(code: string): ASTNode {
|
|
1252
|
-
const parser = new HybridParser(code);
|
|
1253
|
-
return parser.parse();
|
|
1254
|
-
},
|
|
1255
|
-
|
|
1256
|
-
async execute(code: string, element?: Element): Promise<any> {
|
|
1257
|
-
const me = element || document.body;
|
|
1258
|
-
const parser = new HybridParser(code);
|
|
1259
|
-
const ast = parser.parse();
|
|
1260
|
-
return executeAST(ast, me);
|
|
1261
|
-
},
|
|
1262
|
-
|
|
1263
|
-
run: async (code: string, element?: Element) => api.execute(code, element),
|
|
1264
|
-
eval: async (code: string, element?: Element) => api.execute(code, element),
|
|
1265
|
-
|
|
1266
|
-
init: processElements,
|
|
1267
|
-
process: processElements,
|
|
1268
|
-
|
|
1269
|
-
commands: ${JSON.stringify(commands)},
|
|
1270
|
-
${blocks.length > 0 ? `blocks: ${JSON.stringify(blocks)},` : ''}
|
|
1271
|
-
parserName: 'hybrid',
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
// =============================================================================
|
|
1275
|
-
// AUTO-INITIALIZE
|
|
1276
|
-
// =============================================================================
|
|
1277
|
-
${autoInit
|
|
1278
|
-
? `
|
|
1279
|
-
if (typeof window !== 'undefined') {
|
|
1280
|
-
(window as any).${globalName} = api;
|
|
1281
|
-
|
|
1282
|
-
if (document.readyState === 'loading') {
|
|
1283
|
-
document.addEventListener('DOMContentLoaded', () => processElements());
|
|
1284
|
-
} else {
|
|
1285
|
-
processElements();
|
|
1286
|
-
}
|
|
1287
|
-
${htmxIntegration
|
|
1288
|
-
? `
|
|
1289
|
-
// HTMX integration
|
|
1290
|
-
document.addEventListener('htmx:afterSettle', (e: Event) => {
|
|
1291
|
-
const target = (e as CustomEvent).detail?.target;
|
|
1292
|
-
if (target) processElements(target);
|
|
1293
|
-
});
|
|
1294
|
-
`
|
|
1295
|
-
: ''}
|
|
1296
|
-
}
|
|
1297
|
-
`
|
|
1298
|
-
: ''}
|
|
1299
|
-
${esModule
|
|
1300
|
-
? `
|
|
1301
|
-
export default api;
|
|
1302
|
-
export { api, processElements };
|
|
1303
|
-
`
|
|
1304
|
-
: ''}`;
|
|
1305
|
-
}
|
|
1306
|
-
function generateBundle(config) {
|
|
1307
|
-
const warnings = [];
|
|
1308
|
-
const errors = [];
|
|
1309
|
-
const strict = config.validation?.strict ?? false;
|
|
1310
|
-
const unknownCommands = config.commands.filter(cmd => !COMMAND_IMPLEMENTATIONS[resolveCommandKey(cmd)]);
|
|
1311
|
-
for (const cmd of unknownCommands) {
|
|
1312
|
-
const error = {
|
|
1313
|
-
type: 'unknown-command',
|
|
1314
|
-
message: `Unknown command '${cmd}' will not be included`,
|
|
1315
|
-
name: cmd,
|
|
1316
|
-
};
|
|
1317
|
-
errors.push(error);
|
|
1318
|
-
warnings.push(error.message);
|
|
1319
|
-
}
|
|
1320
|
-
const unknownBlocks = (config.blocks || []).filter(block => !BLOCK_IMPLEMENTATIONS[block]);
|
|
1321
|
-
for (const block of unknownBlocks) {
|
|
1322
|
-
const error = {
|
|
1323
|
-
type: 'unknown-block',
|
|
1324
|
-
message: `Unknown block '${block}' will not be included`,
|
|
1325
|
-
name: block,
|
|
1326
|
-
};
|
|
1327
|
-
errors.push(error);
|
|
1328
|
-
warnings.push(error.message);
|
|
1329
|
-
}
|
|
1330
|
-
if (strict && errors.length > 0) {
|
|
1331
|
-
const errorMessages = errors.map(e => e.message).join('; ');
|
|
1332
|
-
throw new Error(`Bundle generation failed (strict mode): ${errorMessages}`);
|
|
1333
|
-
}
|
|
1334
|
-
const code = generateBundleCode(config);
|
|
1335
|
-
return {
|
|
1336
|
-
code,
|
|
1337
|
-
commands: config.commands.filter(cmd => COMMAND_IMPLEMENTATIONS[resolveCommandKey(cmd)]),
|
|
1338
|
-
blocks: (config.blocks || []).filter(block => BLOCK_IMPLEMENTATIONS[block]),
|
|
1339
|
-
positional: config.positionalExpressions || false,
|
|
1340
|
-
warnings,
|
|
1341
|
-
errors,
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
const LITE_PARSER_TEMPLATE = `
|
|
1346
|
-
// Lite Parser - Regex-based for minimal bundle size
|
|
1347
|
-
|
|
1348
|
-
function parseLite(code) {
|
|
1349
|
-
const trimmed = code.trim();
|
|
1350
|
-
|
|
1351
|
-
// Handle event handlers: "on click toggle .active"
|
|
1352
|
-
const onMatch = trimmed.match(/^on\\s+(\\w+)(?:\\s+from\\s+([^\\s]+))?\\s+(.+)$/i);
|
|
1353
|
-
if (onMatch) {
|
|
1354
|
-
return {
|
|
1355
|
-
type: 'event',
|
|
1356
|
-
event: onMatch[1],
|
|
1357
|
-
filter: onMatch[2] ? { type: 'selector', value: onMatch[2] } : undefined,
|
|
1358
|
-
modifiers: {},
|
|
1359
|
-
body: parseCommands(onMatch[3]),
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// Handle "every Nms" event pattern
|
|
1364
|
-
const everyMatch = trimmed.match(/^every\\s+(\\d+)(ms|s)?\\s+(.+)$/i);
|
|
1365
|
-
if (everyMatch) {
|
|
1366
|
-
const ms = everyMatch[2] === 's' ? parseInt(everyMatch[1]) * 1000 : parseInt(everyMatch[1]);
|
|
1367
|
-
return {
|
|
1368
|
-
type: 'event',
|
|
1369
|
-
event: 'interval:' + ms,
|
|
1370
|
-
modifiers: {},
|
|
1371
|
-
body: parseCommands(everyMatch[3]),
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
// Handle "init" pattern
|
|
1376
|
-
const initMatch = trimmed.match(/^init\\s+(.+)$/i);
|
|
1377
|
-
if (initMatch) {
|
|
1378
|
-
return {
|
|
1379
|
-
type: 'event',
|
|
1380
|
-
event: 'init',
|
|
1381
|
-
modifiers: {},
|
|
1382
|
-
body: parseCommands(initMatch[1]),
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
return { type: 'sequence', commands: parseCommands(trimmed) };
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
function parseCommands(code) {
|
|
1390
|
-
const parts = code.split(/\\s+(?:then|and)\\s+/i);
|
|
1391
|
-
return parts.map(parseCommand).filter(Boolean);
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
function parseCommand(code) {
|
|
1395
|
-
const trimmed = code.trim();
|
|
1396
|
-
if (!trimmed) return null;
|
|
1397
|
-
|
|
1398
|
-
let match;
|
|
1399
|
-
|
|
1400
|
-
// toggle .class [on target]
|
|
1401
|
-
match = trimmed.match(/^toggle\\s+(\\.\\w+|\\w+)(?:\\s+on\\s+(.+))?$/i);
|
|
1402
|
-
if (match) {
|
|
1403
|
-
return {
|
|
1404
|
-
type: 'command',
|
|
1405
|
-
name: 'toggle',
|
|
1406
|
-
args: [{ type: 'selector', value: match[1] }],
|
|
1407
|
-
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// add .class [to target]
|
|
1412
|
-
match = trimmed.match(/^add\\s+(\\.\\w+|\\w+)(?:\\s+to\\s+(.+))?$/i);
|
|
1413
|
-
if (match) {
|
|
1414
|
-
return {
|
|
1415
|
-
type: 'command',
|
|
1416
|
-
name: 'add',
|
|
1417
|
-
args: [{ type: 'selector', value: match[1] }],
|
|
1418
|
-
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// remove .class [from target] | remove [target]
|
|
1423
|
-
match = trimmed.match(/^remove\\s+(\\.\\w+)(?:\\s+from\\s+(.+))?$/i);
|
|
1424
|
-
if (match) {
|
|
1425
|
-
return {
|
|
1426
|
-
type: 'command',
|
|
1427
|
-
name: 'removeClass',
|
|
1428
|
-
args: [{ type: 'selector', value: match[1] }],
|
|
1429
|
-
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
match = trimmed.match(/^remove\\s+(.+)$/i);
|
|
1433
|
-
if (match) {
|
|
1434
|
-
return {
|
|
1435
|
-
type: 'command',
|
|
1436
|
-
name: 'remove',
|
|
1437
|
-
args: [],
|
|
1438
|
-
target: parseTarget(match[1]),
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// put "content" into target
|
|
1443
|
-
match = trimmed.match(/^put\\s+(?:"([^"]+)"|'([^']+)'|(\\S+))\\s+(into|before|after)\\s+(.+)$/i);
|
|
1444
|
-
if (match) {
|
|
1445
|
-
const content = match[1] || match[2] || match[3];
|
|
1446
|
-
return {
|
|
1447
|
-
type: 'command',
|
|
1448
|
-
name: 'put',
|
|
1449
|
-
args: [{ type: 'literal', value: content }],
|
|
1450
|
-
modifier: match[4],
|
|
1451
|
-
target: parseTarget(match[5]),
|
|
1452
|
-
};
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
// set target to value | set :var to value
|
|
1456
|
-
match = trimmed.match(/^set\\s+(.+?)\\s+to\\s+(.+)$/i);
|
|
1457
|
-
if (match) {
|
|
1458
|
-
return {
|
|
1459
|
-
type: 'command',
|
|
1460
|
-
name: 'set',
|
|
1461
|
-
args: [parseTarget(match[1]), parseLiteValue(match[2])],
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// log message
|
|
1466
|
-
match = trimmed.match(/^log\\s+(.+)$/i);
|
|
1467
|
-
if (match) {
|
|
1468
|
-
return {
|
|
1469
|
-
type: 'command',
|
|
1470
|
-
name: 'log',
|
|
1471
|
-
args: [parseLiteValue(match[1])],
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// send event [to target]
|
|
1476
|
-
match = trimmed.match(/^send\\s+(\\w+)(?:\\s+to\\s+(.+))?$/i);
|
|
1477
|
-
if (match) {
|
|
1478
|
-
return {
|
|
1479
|
-
type: 'command',
|
|
1480
|
-
name: 'send',
|
|
1481
|
-
args: [{ type: 'literal', value: match[1] }],
|
|
1482
|
-
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// wait Nms | wait Ns
|
|
1487
|
-
match = trimmed.match(/^wait\\s+(\\d+)(ms|s)?$/i);
|
|
1488
|
-
if (match) {
|
|
1489
|
-
const ms = match[2] === 's' ? parseInt(match[1]) * 1000 : parseInt(match[1]);
|
|
1490
|
-
return {
|
|
1491
|
-
type: 'command',
|
|
1492
|
-
name: 'wait',
|
|
1493
|
-
args: [{ type: 'literal', value: ms }],
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// show/hide shortcuts
|
|
1498
|
-
match = trimmed.match(/^(show|hide)(?:\\s+(.+))?$/i);
|
|
1499
|
-
if (match) {
|
|
1500
|
-
return {
|
|
1501
|
-
type: 'command',
|
|
1502
|
-
name: match[1].toLowerCase(),
|
|
1503
|
-
args: [],
|
|
1504
|
-
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
// Unknown command - try generic parsing
|
|
1509
|
-
const parts = trimmed.split(/\\s+/);
|
|
1510
|
-
if (parts.length > 0) {
|
|
1511
|
-
return {
|
|
1512
|
-
type: 'command',
|
|
1513
|
-
name: parts[0],
|
|
1514
|
-
args: parts.slice(1).map(p => ({ type: 'literal', value: p })),
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
return null;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function parseTarget(str) {
|
|
1522
|
-
const s = str.trim();
|
|
1523
|
-
if (s === 'me') return { type: 'identifier', value: 'me' };
|
|
1524
|
-
if (s === 'body') return { type: 'identifier', value: 'body' };
|
|
1525
|
-
if (s.startsWith('#') || s.startsWith('.') || s.startsWith('[')) {
|
|
1526
|
-
return { type: 'selector', value: s };
|
|
1527
|
-
}
|
|
1528
|
-
if (s.startsWith(':')) {
|
|
1529
|
-
return { type: 'variable', name: s, scope: 'local' };
|
|
1530
|
-
}
|
|
1531
|
-
return { type: 'identifier', value: s };
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
function parseLiteValue(str) {
|
|
1535
|
-
const s = str.trim();
|
|
1536
|
-
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
1537
|
-
return { type: 'literal', value: s.slice(1, -1) };
|
|
1538
|
-
}
|
|
1539
|
-
if (/^-?\\d+(\\.\\d+)?$/.test(s)) {
|
|
1540
|
-
return { type: 'literal', value: parseFloat(s) };
|
|
1541
|
-
}
|
|
1542
|
-
if (s === 'true') return { type: 'literal', value: true };
|
|
1543
|
-
if (s === 'false') return { type: 'literal', value: false };
|
|
1544
|
-
if (s === 'null') return { type: 'literal', value: null };
|
|
1545
|
-
if (s.startsWith(':')) return { type: 'variable', name: s, scope: 'local' };
|
|
1546
|
-
if (s === 'me') return { type: 'identifier', value: 'me' };
|
|
1547
|
-
return { type: 'identifier', value: s };
|
|
1548
|
-
}
|
|
1549
|
-
`;
|
|
1550
|
-
const HYBRID_PARSER_TEMPLATE = `
|
|
1551
|
-
// #region generated:hybrid-parser \u2014 DO NOT EDIT (npm run generate:bundles)
|
|
1552
|
-
// Hybrid Parser \u2014 generated from parser/hybrid/{aliases,tokenizer,parser-core}.ts
|
|
1553
|
-
|
|
1554
|
-
const COMMAND_ALIASES = {
|
|
1555
|
-
flip: "toggle",
|
|
1556
|
-
switch: "toggle",
|
|
1557
|
-
display: "show",
|
|
1558
|
-
reveal: "show",
|
|
1559
|
-
conceal: "hide",
|
|
1560
|
-
increase: "increment",
|
|
1561
|
-
decrease: "decrement",
|
|
1562
|
-
fire: "trigger",
|
|
1563
|
-
dispatch: "send",
|
|
1564
|
-
navigate: "go",
|
|
1565
|
-
goto: "go"
|
|
1566
|
-
};
|
|
1567
|
-
const EVENT_ALIASES = {
|
|
1568
|
-
clicked: "click",
|
|
1569
|
-
pressed: "keydown",
|
|
1570
|
-
changed: "change",
|
|
1571
|
-
submitted: "submit",
|
|
1572
|
-
loaded: "load"
|
|
1573
|
-
};
|
|
1574
|
-
function normalizeCommand(name) {
|
|
1575
|
-
const lower = name.toLowerCase();
|
|
1576
|
-
return COMMAND_ALIASES[lower] || lower;
|
|
1577
|
-
}
|
|
1578
|
-
function normalizeEvent(name) {
|
|
1579
|
-
const lower = name.toLowerCase();
|
|
1580
|
-
return EVENT_ALIASES[lower] || lower;
|
|
1581
|
-
}
|
|
1582
|
-
function addCommandAliases(aliases) {
|
|
1583
|
-
Object.assign(COMMAND_ALIASES, aliases);
|
|
1584
|
-
}
|
|
1585
|
-
function addEventAliases(aliases) {
|
|
1586
|
-
Object.assign(EVENT_ALIASES, aliases);
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
const KEYWORDS = /* @__PURE__ */ new Set([
|
|
1590
|
-
"on",
|
|
1591
|
-
"from",
|
|
1592
|
-
"to",
|
|
1593
|
-
"into",
|
|
1594
|
-
"before",
|
|
1595
|
-
"after",
|
|
1596
|
-
"in",
|
|
1597
|
-
"of",
|
|
1598
|
-
"at",
|
|
1599
|
-
"with",
|
|
1600
|
-
"if",
|
|
1601
|
-
"else",
|
|
1602
|
-
"unless",
|
|
1603
|
-
"end",
|
|
1604
|
-
"then",
|
|
1605
|
-
"and",
|
|
1606
|
-
"or",
|
|
1607
|
-
"not",
|
|
1608
|
-
"repeat",
|
|
1609
|
-
"times",
|
|
1610
|
-
"for",
|
|
1611
|
-
"each",
|
|
1612
|
-
"while",
|
|
1613
|
-
"until",
|
|
1614
|
-
"toggle",
|
|
1615
|
-
"add",
|
|
1616
|
-
"remove",
|
|
1617
|
-
"put",
|
|
1618
|
-
"set",
|
|
1619
|
-
"get",
|
|
1620
|
-
"call",
|
|
1621
|
-
"return",
|
|
1622
|
-
"append",
|
|
1623
|
-
"prepend",
|
|
1624
|
-
"log",
|
|
1625
|
-
"send",
|
|
1626
|
-
"trigger",
|
|
1627
|
-
"wait",
|
|
1628
|
-
"settle",
|
|
1629
|
-
"fetch",
|
|
1630
|
-
"as",
|
|
1631
|
-
"show",
|
|
1632
|
-
"hide",
|
|
1633
|
-
"take",
|
|
1634
|
-
"increment",
|
|
1635
|
-
"decrement",
|
|
1636
|
-
"focus",
|
|
1637
|
-
"blur",
|
|
1638
|
-
"go",
|
|
1639
|
-
"transition",
|
|
1640
|
-
"over",
|
|
1641
|
-
"the",
|
|
1642
|
-
"a",
|
|
1643
|
-
"an",
|
|
1644
|
-
"my",
|
|
1645
|
-
"its",
|
|
1646
|
-
"me",
|
|
1647
|
-
"it",
|
|
1648
|
-
"you",
|
|
1649
|
-
"first",
|
|
1650
|
-
"last",
|
|
1651
|
-
"next",
|
|
1652
|
-
"previous",
|
|
1653
|
-
"closest",
|
|
1654
|
-
"parent",
|
|
1655
|
-
"true",
|
|
1656
|
-
"false",
|
|
1657
|
-
"null",
|
|
1658
|
-
"undefined",
|
|
1659
|
-
"is",
|
|
1660
|
-
"matches",
|
|
1661
|
-
"contains",
|
|
1662
|
-
"includes",
|
|
1663
|
-
"exists",
|
|
1664
|
-
"has",
|
|
1665
|
-
"init",
|
|
1666
|
-
"every",
|
|
1667
|
-
"by",
|
|
1668
|
-
"halt",
|
|
1669
|
-
"via",
|
|
1670
|
-
"values",
|
|
1671
|
-
"default",
|
|
1672
|
-
"event"
|
|
1673
|
-
]);
|
|
1674
|
-
function tokenize(code) {
|
|
1675
|
-
const tokens = [];
|
|
1676
|
-
let pos = 0;
|
|
1677
|
-
while (pos < code.length) {
|
|
1678
|
-
if (/\\s/.test(code[pos])) {
|
|
1679
|
-
pos++;
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
if (code.slice(pos, pos + 2) === "--") {
|
|
1683
|
-
while (pos < code.length && code[pos] !== "\\n") pos++;
|
|
1684
|
-
continue;
|
|
1685
|
-
}
|
|
1686
|
-
const start = pos;
|
|
1687
|
-
if (code[pos] === "<" && /[a-zA-Z]/.test(code[pos + 1] || "")) {
|
|
1688
|
-
pos++;
|
|
1689
|
-
while (pos < code.length && code[pos] !== ">") pos++;
|
|
1690
|
-
if (code[pos] === ">") pos++;
|
|
1691
|
-
const val = code.slice(start, pos);
|
|
1692
|
-
if (val.endsWith("/>") || val.endsWith(">")) {
|
|
1693
|
-
const normalized = val.slice(1).replace(/\\/?>$/, "");
|
|
1694
|
-
tokens.push({ type: "selector", value: normalized, pos: start });
|
|
1695
|
-
continue;
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
if (code.slice(pos, pos + 2) === "'s" && !/[a-zA-Z]/.test(code[pos + 2] || "")) {
|
|
1699
|
-
tokens.push({ type: "operator", value: "'s", pos: start });
|
|
1700
|
-
pos += 2;
|
|
1701
|
-
continue;
|
|
1702
|
-
}
|
|
1703
|
-
if (code[pos] === '"' || code[pos] === "'") {
|
|
1704
|
-
const quote = code[pos++];
|
|
1705
|
-
while (pos < code.length && code[pos] !== quote) {
|
|
1706
|
-
if (code[pos] === "\\\\") pos++;
|
|
1707
|
-
pos++;
|
|
1708
|
-
}
|
|
1709
|
-
pos++;
|
|
1710
|
-
tokens.push({ type: "string", value: code.slice(start, pos), pos: start });
|
|
1711
|
-
continue;
|
|
1712
|
-
}
|
|
1713
|
-
if (/\\d/.test(code[pos]) || code[pos] === "-" && /\\d/.test(code[pos + 1] || "")) {
|
|
1714
|
-
if (code[pos] === "-") pos++;
|
|
1715
|
-
while (pos < code.length && /[\\d.]/.test(code[pos])) pos++;
|
|
1716
|
-
if (code.slice(pos, pos + 2) === "ms") pos += 2;
|
|
1717
|
-
else if (code[pos] === "s" && !/[a-zA-Z]/.test(code[pos + 1] || "")) pos++;
|
|
1718
|
-
else if (code.slice(pos, pos + 2) === "px") pos += 2;
|
|
1719
|
-
tokens.push({ type: "number", value: code.slice(start, pos), pos: start });
|
|
1720
|
-
continue;
|
|
1721
|
-
}
|
|
1722
|
-
if (code[pos] === ":") {
|
|
1723
|
-
pos++;
|
|
1724
|
-
while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
|
|
1725
|
-
tokens.push({ type: "localVar", value: code.slice(start, pos), pos: start });
|
|
1726
|
-
continue;
|
|
1727
|
-
}
|
|
1728
|
-
if (code[pos] === "$") {
|
|
1729
|
-
pos++;
|
|
1730
|
-
while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
|
|
1731
|
-
tokens.push({ type: "globalVar", value: code.slice(start, pos), pos: start });
|
|
1732
|
-
continue;
|
|
1733
|
-
}
|
|
1734
|
-
if (code[pos] === "@") {
|
|
1735
|
-
pos++;
|
|
1736
|
-
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1737
|
-
tokens.push({ type: "selector", value: code.slice(start, pos), pos: start });
|
|
1738
|
-
continue;
|
|
1739
|
-
}
|
|
1740
|
-
if (code[pos] === "#" || code[pos] === ".") {
|
|
1741
|
-
if (code[pos] === ".") {
|
|
1742
|
-
const afterDot = code.slice(pos + 1).match(/^(once|prevent|stop|debounce|throttle)\\b/i);
|
|
1743
|
-
if (afterDot) {
|
|
1744
|
-
tokens.push({ type: "symbol", value: ".", pos: start });
|
|
1745
|
-
pos++;
|
|
1746
|
-
continue;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
pos++;
|
|
1750
|
-
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1751
|
-
tokens.push({ type: "selector", value: code.slice(start, pos), pos: start });
|
|
1752
|
-
continue;
|
|
1753
|
-
}
|
|
1754
|
-
if (code[pos] === "[") {
|
|
1755
|
-
let lookahead = pos + 1;
|
|
1756
|
-
while (lookahead < code.length && /\\s/.test(code[lookahead])) lookahead++;
|
|
1757
|
-
const nextChar = code[lookahead] || "";
|
|
1758
|
-
const isArrayLiteral = /['"\\d\\[\\]:\\$\\-]/.test(nextChar) || nextChar === "";
|
|
1759
|
-
if (isArrayLiteral) {
|
|
1760
|
-
tokens.push({ type: "symbol", value: "[", pos: start });
|
|
1761
|
-
pos++;
|
|
1762
|
-
continue;
|
|
1763
|
-
} else {
|
|
1764
|
-
pos++;
|
|
1765
|
-
let depth = 1;
|
|
1766
|
-
while (pos < code.length && depth > 0) {
|
|
1767
|
-
if (code[pos] === "[") depth++;
|
|
1768
|
-
if (code[pos] === "]") depth--;
|
|
1769
|
-
pos++;
|
|
1770
|
-
}
|
|
1771
|
-
tokens.push({ type: "selector", value: code.slice(start, pos), pos: start });
|
|
1772
|
-
continue;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
if (code[pos] === "]") {
|
|
1776
|
-
tokens.push({ type: "symbol", value: "]", pos: start });
|
|
1777
|
-
pos++;
|
|
1778
|
-
continue;
|
|
1779
|
-
}
|
|
1780
|
-
if (code.slice(pos, pos + 2) === "'s") {
|
|
1781
|
-
tokens.push({ type: "operator", value: "'s", pos: start });
|
|
1782
|
-
pos += 2;
|
|
1783
|
-
continue;
|
|
1784
|
-
}
|
|
1785
|
-
const twoChar = code.slice(pos, pos + 2);
|
|
1786
|
-
if (["==", "!=", "<=", ">=", "&&", "||"].includes(twoChar)) {
|
|
1787
|
-
tokens.push({ type: "operator", value: twoChar, pos: start });
|
|
1788
|
-
pos += 2;
|
|
1789
|
-
continue;
|
|
1790
|
-
}
|
|
1791
|
-
if (code[pos] === "*" && /[a-zA-Z]/.test(code[pos + 1] || "")) {
|
|
1792
|
-
pos++;
|
|
1793
|
-
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1794
|
-
tokens.push({ type: "styleProperty", value: code.slice(start, pos), pos: start });
|
|
1795
|
-
continue;
|
|
1796
|
-
}
|
|
1797
|
-
if ("+-*/%<>!".includes(code[pos])) {
|
|
1798
|
-
tokens.push({ type: "operator", value: code[pos], pos: start });
|
|
1799
|
-
pos++;
|
|
1800
|
-
continue;
|
|
1801
|
-
}
|
|
1802
|
-
if ("()[]{},.".includes(code[pos])) {
|
|
1803
|
-
tokens.push({ type: "symbol", value: code[pos], pos: start });
|
|
1804
|
-
pos++;
|
|
1805
|
-
continue;
|
|
1806
|
-
}
|
|
1807
|
-
if (/[a-zA-Z_]/.test(code[pos])) {
|
|
1808
|
-
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1809
|
-
const value = code.slice(start, pos);
|
|
1810
|
-
const type = KEYWORDS.has(value.toLowerCase()) ? "keyword" : "identifier";
|
|
1811
|
-
tokens.push({ type, value, pos: start });
|
|
1812
|
-
continue;
|
|
1813
|
-
}
|
|
1814
|
-
pos++;
|
|
1815
|
-
}
|
|
1816
|
-
tokens.push({ type: "eof", value: "", pos: code.length });
|
|
1817
|
-
return tokens;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
var __defProp = Object.defineProperty;
|
|
1821
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1822
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1823
|
-
class HybridParser {
|
|
1824
|
-
constructor(code) {
|
|
1825
|
-
__publicField(this, "tokens");
|
|
1826
|
-
__publicField(this, "pos", 0);
|
|
1827
|
-
/**
|
|
1828
|
-
* The raw source. Kept because \`js \u2026 end\` must be sliced from it verbatim \u2014
|
|
1829
|
-
* the tokenizer reads a JS body as hyperscript and mangles regexes, template
|
|
1830
|
-
* literals and single quotes. Same approach as the full parser's
|
|
1831
|
-
* \`parseJsCommand\`, which slices between token offsets for the same reason.
|
|
1832
|
-
*/
|
|
1833
|
-
__publicField(this, "source");
|
|
1834
|
-
/**
|
|
1835
|
-
* Command keyword \u2192 parse rule. Built once per parse rather than once per
|
|
1836
|
-
* command, and the single place the parser's command set is written down:
|
|
1837
|
-
* \`isCommandKeyword()\` reads these keys instead of carrying its own copy.
|
|
1838
|
-
*
|
|
1839
|
-
* MUST stay in sync with \`bundle-generator/parser-templates.ts\`'s
|
|
1840
|
-
* \`HYBRID_PARSER_TEMPLATE\` \u2014 core's \`generateBundle()\` imports this file
|
|
1841
|
-
* while the vite-plugin's generator embeds that template, so a name in only
|
|
1842
|
-
* one of them makes the reachable command set depend on which generator the
|
|
1843
|
-
* user went through. Gated by \`capability-emission.test.ts\` \u00a73.
|
|
1844
|
-
*/
|
|
1845
|
-
__publicField(this, "cmdMap", {
|
|
1846
|
-
toggle: () => this.parseToggle(),
|
|
1847
|
-
add: () => this.parseAdd(),
|
|
1848
|
-
remove: () => this.parseRemove(),
|
|
1849
|
-
put: () => this.parsePut(),
|
|
1850
|
-
append: () => this.parseInsertion("append"),
|
|
1851
|
-
prepend: () => this.parseInsertion("prepend"),
|
|
1852
|
-
set: () => this.parseSet(),
|
|
1853
|
-
get: () => this.parseGet(),
|
|
1854
|
-
call: () => this.parseCall(),
|
|
1855
|
-
log: () => this.parseLog(),
|
|
1856
|
-
send: () => this.parseSend("to"),
|
|
1857
|
-
// \`trigger <event> on <target>\` \u2014 same node as \`send\`, per the runtime,
|
|
1858
|
-
// where \`trigger\` is a consolidation alias sharing \`send\`'s implementation.
|
|
1859
|
-
// The target marker differs, which is why it is a parameter and not a
|
|
1860
|
-
// second entry pointing at the same rule (that dropped the target).
|
|
1861
|
-
trigger: () => this.parseSend("on"),
|
|
1862
|
-
wait: () => this.parseWait(),
|
|
1863
|
-
show: () => this.parseShow(),
|
|
1864
|
-
hide: () => this.parseHide(),
|
|
1865
|
-
take: () => this.parseTake(),
|
|
1866
|
-
empty: () => this.parseEmpty(),
|
|
1867
|
-
increment: () => this.parseIncDec("increment"),
|
|
1868
|
-
decrement: () => this.parseIncDec("decrement"),
|
|
1869
|
-
focus: () => this.parseFocusBlur("focus"),
|
|
1870
|
-
blur: () => this.parseFocusBlur("blur"),
|
|
1871
|
-
go: () => this.parseGo(),
|
|
1872
|
-
return: () => this.parseReturn(),
|
|
1873
|
-
transition: () => this.parseTransition(),
|
|
1874
|
-
halt: () => this.parseHalt(),
|
|
1875
|
-
copy: () => this.parseCopy(),
|
|
1876
|
-
beep: () => this.parseBeep(),
|
|
1877
|
-
push: () => this.parseUrlCommand("push"),
|
|
1878
|
-
replace: () => this.parseUrlCommand("replace"),
|
|
1879
|
-
morph: () => this.parseMorph(),
|
|
1880
|
-
js: () => this.parseJs(),
|
|
1881
|
-
throw: () => this.parseThrow(),
|
|
1882
|
-
break: () => this.parseBare("break"),
|
|
1883
|
-
continue: () => this.parseBare("continue"),
|
|
1884
|
-
exit: () => this.parseBare("exit")
|
|
1885
|
-
});
|
|
1886
|
-
this.source = code;
|
|
1887
|
-
this.tokens = tokenize(code);
|
|
1888
|
-
}
|
|
1889
|
-
peek(offset = 0) {
|
|
1890
|
-
return this.tokens[Math.min(this.pos + offset, this.tokens.length - 1)];
|
|
1891
|
-
}
|
|
1892
|
-
advance() {
|
|
1893
|
-
return this.tokens[this.pos++];
|
|
1894
|
-
}
|
|
1895
|
-
match(...values) {
|
|
1896
|
-
const token = this.peek();
|
|
1897
|
-
return values.some((v) => token.value.toLowerCase() === v.toLowerCase());
|
|
1898
|
-
}
|
|
1899
|
-
matchType(...types) {
|
|
1900
|
-
return types.includes(this.peek().type);
|
|
1901
|
-
}
|
|
1902
|
-
expect(value) {
|
|
1903
|
-
if (!this.match(value) && normalizeCommand(this.peek().value) !== value) {
|
|
1904
|
-
throw new Error(\`Expected '\${value}', got '\${this.peek().value}'\`);
|
|
1905
|
-
}
|
|
1906
|
-
return this.advance();
|
|
1907
|
-
}
|
|
1908
|
-
isAtEnd() {
|
|
1909
|
-
return this.peek().type === "eof";
|
|
1910
|
-
}
|
|
1911
|
-
parse() {
|
|
1912
|
-
if (this.match("on")) return this.parseEventHandler();
|
|
1913
|
-
if (this.match("init")) {
|
|
1914
|
-
this.advance();
|
|
1915
|
-
return { type: "event", event: "init", modifiers: {}, body: this.parseCommandSequence() };
|
|
1916
|
-
}
|
|
1917
|
-
if (this.match("every")) return this.parseEveryHandler();
|
|
1918
|
-
return { type: "sequence", commands: this.parseCommandSequence() };
|
|
1919
|
-
}
|
|
1920
|
-
parseEventHandler() {
|
|
1921
|
-
this.expect("on");
|
|
1922
|
-
const eventName = this.advance().value;
|
|
1923
|
-
const modifiers = {};
|
|
1924
|
-
let filter;
|
|
1925
|
-
while (this.peek().value === ".") {
|
|
1926
|
-
this.advance();
|
|
1927
|
-
const mod = this.advance().value.toLowerCase();
|
|
1928
|
-
if (mod === "once") modifiers.once = true;
|
|
1929
|
-
else if (mod === "prevent") modifiers.prevent = true;
|
|
1930
|
-
else if (mod === "stop") modifiers.stop = true;
|
|
1931
|
-
else if (mod === "debounce" || mod === "throttle") {
|
|
1932
|
-
if (this.peek().value === "(") {
|
|
1933
|
-
this.advance();
|
|
1934
|
-
const num = this.advance().value;
|
|
1935
|
-
this.expect(")");
|
|
1936
|
-
if (mod === "debounce") modifiers.debounce = parseInt(num) || 100;
|
|
1937
|
-
else modifiers.throttle = parseInt(num) || 100;
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
if (this.match("from")) {
|
|
1942
|
-
this.advance();
|
|
1943
|
-
filter = this.parseExpression();
|
|
1944
|
-
}
|
|
1945
|
-
return {
|
|
1946
|
-
type: "event",
|
|
1947
|
-
event: normalizeEvent(eventName),
|
|
1948
|
-
filter,
|
|
1949
|
-
modifiers,
|
|
1950
|
-
body: this.parseCommandSequence()
|
|
1951
|
-
};
|
|
1952
|
-
}
|
|
1953
|
-
parseEveryHandler() {
|
|
1954
|
-
this.expect("every");
|
|
1955
|
-
const interval = this.advance().value;
|
|
1956
|
-
return {
|
|
1957
|
-
type: "event",
|
|
1958
|
-
event: \`interval:\${interval}\`,
|
|
1959
|
-
modifiers: {},
|
|
1960
|
-
body: this.parseCommandSequence()
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
parseCommandSequence() {
|
|
1964
|
-
const commands = [];
|
|
1965
|
-
while (!this.isAtEnd() && !this.match("end", "else")) {
|
|
1966
|
-
const cmd = this.parseCommand();
|
|
1967
|
-
if (cmd) commands.push(cmd);
|
|
1968
|
-
if (this.match("then", "and")) this.advance();
|
|
1969
|
-
}
|
|
1970
|
-
return commands;
|
|
1971
|
-
}
|
|
1972
|
-
parseCommand() {
|
|
1973
|
-
if (this.match("if", "unless")) return this.parseIf();
|
|
1974
|
-
if (this.match("repeat")) return this.parseRepeat();
|
|
1975
|
-
if (this.match("for")) return this.parseFor();
|
|
1976
|
-
if (this.match("while")) return this.parseWhile();
|
|
1977
|
-
if (this.match("fetch")) return this.parseFetchBlock();
|
|
1978
|
-
const normalized = normalizeCommand(this.peek().value);
|
|
1979
|
-
if (this.cmdMap[normalized]) {
|
|
1980
|
-
return this.cmdMap[normalized]();
|
|
1981
|
-
}
|
|
1982
|
-
if (this.match("catch", "finally")) {
|
|
1983
|
-
throw new Error(\`'\${this.peek().value}' needs the full parser (use hyperfixi.js)\`);
|
|
1984
|
-
}
|
|
1985
|
-
const stray = this.peek();
|
|
1986
|
-
if (!this.isAtEnd() && (stray.type === "identifier" || stray.type === "keyword") && !this.match("then", "and", "end", "else")) {
|
|
1987
|
-
throw new Error(\`'\${stray.value}' needs the full parser (use hyperfixi.js)\`);
|
|
1988
|
-
}
|
|
1989
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
1990
|
-
this.advance();
|
|
1991
|
-
}
|
|
1992
|
-
return null;
|
|
1993
|
-
}
|
|
1994
|
-
// Control flow parsing
|
|
1995
|
-
parseIf() {
|
|
1996
|
-
const isUnless = this.match("unless");
|
|
1997
|
-
this.advance();
|
|
1998
|
-
const condition = this.parseExpression();
|
|
1999
|
-
const body = this.parseCommandSequence();
|
|
2000
|
-
let elseBody;
|
|
2001
|
-
if (this.match("else")) {
|
|
2002
|
-
this.advance();
|
|
2003
|
-
elseBody = this.parseCommandSequence();
|
|
2004
|
-
}
|
|
2005
|
-
if (this.match("end")) this.advance();
|
|
2006
|
-
return {
|
|
2007
|
-
type: "if",
|
|
2008
|
-
condition: isUnless ? { type: "unary", operator: "not", operand: condition } : condition,
|
|
2009
|
-
body,
|
|
2010
|
-
elseBody
|
|
2011
|
-
};
|
|
2012
|
-
}
|
|
2013
|
-
parseRepeat() {
|
|
2014
|
-
this.expect("repeat");
|
|
2015
|
-
let count;
|
|
2016
|
-
if (!this.match("until", "while", "forever")) {
|
|
2017
|
-
count = this.parseExpression();
|
|
2018
|
-
if (this.match("times")) this.advance();
|
|
2019
|
-
}
|
|
2020
|
-
const body = this.parseCommandSequence();
|
|
2021
|
-
if (this.match("end")) this.advance();
|
|
2022
|
-
return { type: "repeat", condition: count, body };
|
|
2023
|
-
}
|
|
2024
|
-
parseFor() {
|
|
2025
|
-
this.expect("for");
|
|
2026
|
-
if (this.match("each")) this.advance();
|
|
2027
|
-
const variable = this.advance().value;
|
|
2028
|
-
this.expect("in");
|
|
2029
|
-
const iterable = this.parseExpression();
|
|
2030
|
-
const body = this.parseCommandSequence();
|
|
2031
|
-
if (this.match("end")) this.advance();
|
|
2032
|
-
return { type: "for", condition: { type: "forCondition", variable, iterable }, body };
|
|
2033
|
-
}
|
|
2034
|
-
parseWhile() {
|
|
2035
|
-
this.expect("while");
|
|
2036
|
-
const condition = this.parseExpression();
|
|
2037
|
-
const body = this.parseCommandSequence();
|
|
2038
|
-
if (this.match("end")) this.advance();
|
|
2039
|
-
return { type: "while", condition, body };
|
|
2040
|
-
}
|
|
2041
|
-
/**
|
|
2042
|
-
* A naked URL \u2014 \`fetch /api/data \u2026\`, \`fetch https://x/y \u2026\` \u2014 the way upstream
|
|
2043
|
-
* reads one: everything up to the next whitespace, verbatim. The tokenizer
|
|
2044
|
-
* splits \`/api/data\` into \`/\`, \`api\`, \`/\`, \`data\`, and \`parseExpression\` used
|
|
2045
|
-
* to return the first \`/\` as the URL; the skip fallback then dropped the rest
|
|
2046
|
-
* silently, so every unquoted \`fetch /path\` in a hybrid bundle fetched \`/\`.
|
|
2047
|
-
* Measured 2026-09-04 when the fallback stopped being silent.
|
|
2048
|
-
*/
|
|
2049
|
-
parseNakedUrl() {
|
|
2050
|
-
const tok = this.peek();
|
|
2051
|
-
const isSlash = tok.type === "operator" && tok.value === "/";
|
|
2052
|
-
const isScheme = (tok.value === "http" || tok.value === "https") && this.source.slice(tok.pos + tok.value.length, tok.pos + tok.value.length + 3) === "://";
|
|
2053
|
-
if (!isSlash && !isScheme) return null;
|
|
2054
|
-
let end = tok.pos;
|
|
2055
|
-
while (end < this.source.length && !/\\s/.test(this.source[end])) end++;
|
|
2056
|
-
while (!this.isAtEnd() && this.peek().pos < end) this.advance();
|
|
2057
|
-
return { type: "literal", value: this.source.slice(tok.pos, end) };
|
|
2058
|
-
}
|
|
2059
|
-
parseFetchBlock() {
|
|
2060
|
-
this.expect("fetch");
|
|
2061
|
-
const url = this.parseNakedUrl() ?? this.parseExpression();
|
|
2062
|
-
let responseType = { type: "literal", value: "text" };
|
|
2063
|
-
let options;
|
|
2064
|
-
let method;
|
|
2065
|
-
if (this.match("{")) {
|
|
2066
|
-
this.pos--;
|
|
2067
|
-
options = this.parseExpression();
|
|
2068
|
-
}
|
|
2069
|
-
for (let i = 0; i < 3; i++) {
|
|
2070
|
-
if (this.match("via") && !method) {
|
|
2071
|
-
this.advance();
|
|
2072
|
-
method = this.parseExpression();
|
|
2073
|
-
continue;
|
|
2074
|
-
}
|
|
2075
|
-
if (this.match("as")) {
|
|
2076
|
-
this.advance();
|
|
2077
|
-
if (this.match("a") || this.match("an")) this.advance();
|
|
2078
|
-
responseType = this.parseExpression();
|
|
2079
|
-
continue;
|
|
2080
|
-
}
|
|
2081
|
-
if (this.match("with") && !options) {
|
|
2082
|
-
this.advance();
|
|
2083
|
-
options = this.parseExpression();
|
|
2084
|
-
continue;
|
|
2085
|
-
}
|
|
2086
|
-
break;
|
|
2087
|
-
}
|
|
2088
|
-
if (this.match("then")) this.advance();
|
|
2089
|
-
const body = this.parseCommandSequence();
|
|
2090
|
-
return {
|
|
2091
|
-
type: "fetch",
|
|
2092
|
-
condition: { type: "fetchConfig", url, responseType, options, method },
|
|
2093
|
-
body
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
// Command parsing
|
|
2097
|
-
parseToggle() {
|
|
2098
|
-
this.expect("toggle");
|
|
2099
|
-
const what = this.parseExpression();
|
|
2100
|
-
let target;
|
|
2101
|
-
if (this.match("on")) {
|
|
2102
|
-
this.advance();
|
|
2103
|
-
target = this.parseExpression();
|
|
2104
|
-
}
|
|
2105
|
-
return { type: "command", name: "toggle", args: [what], target };
|
|
2106
|
-
}
|
|
2107
|
-
parseAdd() {
|
|
2108
|
-
this.expect("add");
|
|
2109
|
-
const what = this.parseExpression();
|
|
2110
|
-
let target;
|
|
2111
|
-
if (this.match("to")) {
|
|
2112
|
-
this.advance();
|
|
2113
|
-
target = this.parseExpression();
|
|
2114
|
-
}
|
|
2115
|
-
return { type: "command", name: "add", args: [what], target };
|
|
2116
|
-
}
|
|
2117
|
-
parseRemove() {
|
|
2118
|
-
this.expect("remove");
|
|
2119
|
-
if (this.matchType("selector") && /^[.@]/.test(this.peek().value)) {
|
|
2120
|
-
const what = this.parseExpression();
|
|
2121
|
-
let target2;
|
|
2122
|
-
if (this.match("from")) {
|
|
2123
|
-
this.advance();
|
|
2124
|
-
target2 = this.parseExpression();
|
|
2125
|
-
}
|
|
2126
|
-
return { type: "command", name: "removeClass", args: [what], target: target2 };
|
|
2127
|
-
}
|
|
2128
|
-
const target = this.parseExpression();
|
|
2129
|
-
return { type: "command", name: "remove", args: [], target };
|
|
2130
|
-
}
|
|
2131
|
-
parsePut() {
|
|
2132
|
-
this.expect("put");
|
|
2133
|
-
const content = this.parseExpression();
|
|
2134
|
-
let modifier = "into";
|
|
2135
|
-
if (this.match("into", "before", "after", "at")) {
|
|
2136
|
-
modifier = this.advance().value;
|
|
2137
|
-
if (modifier === "at") {
|
|
2138
|
-
const pos = this.advance().value;
|
|
2139
|
-
this.expect("of");
|
|
2140
|
-
modifier = \`at \${pos} of\`;
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
const target = this.parseExpression();
|
|
2144
|
-
return { type: "command", name: "put", args: [content], target, modifier };
|
|
2145
|
-
}
|
|
2146
|
-
/** \`append\`/\`prepend <content> [to <target>]\` \u2014 identical shape, both ends. */
|
|
2147
|
-
parseInsertion(name) {
|
|
2148
|
-
this.expect(name);
|
|
2149
|
-
const content = this.parseExpression();
|
|
2150
|
-
let target;
|
|
2151
|
-
if (this.match("to")) {
|
|
2152
|
-
this.advance();
|
|
2153
|
-
target = this.parseExpression();
|
|
2154
|
-
}
|
|
2155
|
-
return { type: "command", name, args: [content], target };
|
|
2156
|
-
}
|
|
2157
|
-
parseSet() {
|
|
2158
|
-
this.expect("set");
|
|
2159
|
-
const target = this.parseExpression();
|
|
2160
|
-
if (this.match("to")) {
|
|
2161
|
-
this.advance();
|
|
2162
|
-
const value = this.parseExpression();
|
|
2163
|
-
return { type: "command", name: "set", args: [target, value] };
|
|
2164
|
-
}
|
|
2165
|
-
return { type: "command", name: "set", args: [target] };
|
|
2166
|
-
}
|
|
2167
|
-
parseGet() {
|
|
2168
|
-
this.expect("get");
|
|
2169
|
-
return { type: "command", name: "get", args: [this.parseExpression()] };
|
|
2170
|
-
}
|
|
2171
|
-
parseCall() {
|
|
2172
|
-
this.expect("call");
|
|
2173
|
-
return { type: "command", name: "call", args: [this.parseExpression()] };
|
|
2174
|
-
}
|
|
2175
|
-
parseLog() {
|
|
2176
|
-
this.expect("log");
|
|
2177
|
-
const args = [];
|
|
2178
|
-
while (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2179
|
-
args.push(this.parseExpression());
|
|
2180
|
-
if (this.match(",")) this.advance();
|
|
2181
|
-
else break;
|
|
2182
|
-
}
|
|
2183
|
-
return { type: "command", name: "log", args };
|
|
2184
|
-
}
|
|
2185
|
-
/**
|
|
2186
|
-
* \`send <event> to <target>\` / \`trigger <event> on <target>\`.
|
|
2187
|
-
*
|
|
2188
|
-
* Both emit a \`send\` node \u2014 the runtime registers \`trigger\` as a
|
|
2189
|
-
* consolidation alias sharing \`send\`'s implementation, so the bundle
|
|
2190
|
-
* generator carries one template for both. The marker is a parameter
|
|
2191
|
-
* because it is the ONLY difference: \`trigger\` used to reuse this rule with
|
|
2192
|
-
* \`to\` hardcoded, which parsed the event name and then silently abandoned
|
|
2193
|
-
* \`on #target\`, leaving the dispatch to land on \`me\`.
|
|
2194
|
-
*/
|
|
2195
|
-
parseSend(marker) {
|
|
2196
|
-
this.advance();
|
|
2197
|
-
const first = this.advance();
|
|
2198
|
-
let event = first.value;
|
|
2199
|
-
let end = first.pos + first.value.length;
|
|
2200
|
-
while (!this.isAtEnd() && this.peek().type === "localVar" && this.peek().pos === end) {
|
|
2201
|
-
const piece = this.advance();
|
|
2202
|
-
event += piece.value;
|
|
2203
|
-
end = piece.pos + piece.value.length;
|
|
2204
|
-
}
|
|
2205
|
-
let target;
|
|
2206
|
-
if (this.match(marker)) {
|
|
2207
|
-
this.advance();
|
|
2208
|
-
target = this.parseExpression();
|
|
2209
|
-
}
|
|
2210
|
-
return { type: "command", name: "send", args: [{ type: "literal", value: event }], target };
|
|
2211
|
-
}
|
|
2212
|
-
parseWait() {
|
|
2213
|
-
this.expect("wait");
|
|
2214
|
-
if (this.match("for")) {
|
|
2215
|
-
this.advance();
|
|
2216
|
-
const event = this.advance().value;
|
|
2217
|
-
let target;
|
|
2218
|
-
if (this.match("from")) {
|
|
2219
|
-
this.advance();
|
|
2220
|
-
target = this.parseExpression();
|
|
2221
|
-
}
|
|
2222
|
-
return {
|
|
2223
|
-
type: "command",
|
|
2224
|
-
name: "waitFor",
|
|
2225
|
-
args: [{ type: "literal", value: event }],
|
|
2226
|
-
target
|
|
2227
|
-
};
|
|
2228
|
-
}
|
|
2229
|
-
return { type: "command", name: "wait", args: [this.parseExpression()] };
|
|
2230
|
-
}
|
|
2231
|
-
parseShow() {
|
|
2232
|
-
this.expect("show");
|
|
2233
|
-
let target;
|
|
2234
|
-
const modifiers = {};
|
|
2235
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else", "when", "where")) {
|
|
2236
|
-
target = this.parseExpression();
|
|
2237
|
-
}
|
|
2238
|
-
if (!this.isAtEnd() && this.match("when", "where")) {
|
|
2239
|
-
const keyword = this.advance().value;
|
|
2240
|
-
modifiers[keyword] = this.parseExpression();
|
|
2241
|
-
}
|
|
2242
|
-
return { type: "command", name: "show", args: [], target, modifiers };
|
|
2243
|
-
}
|
|
2244
|
-
parseHide() {
|
|
2245
|
-
this.expect("hide");
|
|
2246
|
-
let target;
|
|
2247
|
-
const modifiers = {};
|
|
2248
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else", "when", "where")) {
|
|
2249
|
-
target = this.parseExpression();
|
|
2250
|
-
}
|
|
2251
|
-
if (!this.isAtEnd() && this.match("when", "where")) {
|
|
2252
|
-
const keyword = this.advance().value;
|
|
2253
|
-
modifiers[keyword] = this.parseExpression();
|
|
2254
|
-
}
|
|
2255
|
-
return { type: "command", name: "hide", args: [], target, modifiers };
|
|
2256
|
-
}
|
|
2257
|
-
parseTake() {
|
|
2258
|
-
this.expect("take");
|
|
2259
|
-
const what = this.parseExpression();
|
|
2260
|
-
let from;
|
|
2261
|
-
if (this.match("from")) {
|
|
2262
|
-
this.advance();
|
|
2263
|
-
from = this.parseExpression();
|
|
2264
|
-
}
|
|
2265
|
-
return { type: "command", name: "take", args: [what], target: from };
|
|
2266
|
-
}
|
|
2267
|
-
parseIncDec(name) {
|
|
2268
|
-
this.advance();
|
|
2269
|
-
const target = this.parseExpression();
|
|
2270
|
-
let amount = { type: "literal", value: 1 };
|
|
2271
|
-
if (this.match("by")) {
|
|
2272
|
-
this.advance();
|
|
2273
|
-
amount = this.parseExpression();
|
|
2274
|
-
}
|
|
2275
|
-
return { type: "command", name, args: [target, amount] };
|
|
2276
|
-
}
|
|
2277
|
-
parseFocusBlur(name) {
|
|
2278
|
-
this.advance();
|
|
2279
|
-
let target;
|
|
2280
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2281
|
-
target = this.parseExpression();
|
|
2282
|
-
}
|
|
2283
|
-
return { type: "command", name, args: [], target };
|
|
2284
|
-
}
|
|
2285
|
-
parseGo() {
|
|
2286
|
-
this.expect("go");
|
|
2287
|
-
if (this.match("to")) this.advance();
|
|
2288
|
-
if (this.match("url")) this.advance();
|
|
2289
|
-
const dest = this.parseExpression();
|
|
2290
|
-
return { type: "command", name: "go", args: [dest] };
|
|
2291
|
-
}
|
|
2292
|
-
parseReturn() {
|
|
2293
|
-
this.expect("return");
|
|
2294
|
-
let value;
|
|
2295
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2296
|
-
value = this.parseExpression();
|
|
2297
|
-
}
|
|
2298
|
-
return { type: "command", name: "return", args: value ? [value] : [] };
|
|
2299
|
-
}
|
|
2300
|
-
// ---------------------------------------------------------------------------
|
|
2301
|
-
// Rules restoring the commands the capability list advertised but this parser
|
|
2302
|
-
// could not reach (Finding 13). Every one already had a working template in
|
|
2303
|
-
// \`bundle-generator/templates.ts\`; only the parse rule was missing, so the
|
|
2304
|
-
// emitted \`case\` label was dead and the user's source silently no-opped.
|
|
2305
|
-
// ---------------------------------------------------------------------------
|
|
2306
|
-
/** \`empty [<target>]\` \u2014 clears children. Defaults to \`me\`. */
|
|
2307
|
-
parseEmpty() {
|
|
2308
|
-
this.expect("empty");
|
|
2309
|
-
let target;
|
|
2310
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2311
|
-
target = this.parseExpression();
|
|
2312
|
-
}
|
|
2313
|
-
return { type: "command", name: "empty", args: [], target };
|
|
2314
|
-
}
|
|
2315
|
-
/** \`copy <value>\` \u2014 clipboard write. */
|
|
2316
|
-
parseCopy() {
|
|
2317
|
-
this.expect("copy");
|
|
2318
|
-
return { type: "command", name: "copy", args: [this.parseExpression()] };
|
|
2319
|
-
}
|
|
2320
|
-
/**
|
|
2321
|
-
* \`beep[!] [<value>, \u2026]\` \u2014 debug echo.
|
|
2322
|
-
*
|
|
2323
|
-
* Upstream spells it \`beep!\`; hyperfixi accepts both (see the tier list's
|
|
2324
|
-
* note on the same nuance). The \`!\` is consumed here because the tokenizer
|
|
2325
|
-
* emits it as a separate operator, and \`parseUnary\` would otherwise read it
|
|
2326
|
-
* as logical negation of the first argument.
|
|
2327
|
-
*/
|
|
2328
|
-
parseBeep() {
|
|
2329
|
-
this.advance();
|
|
2330
|
-
if (this.match("!")) this.advance();
|
|
2331
|
-
const args = [];
|
|
2332
|
-
while (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2333
|
-
args.push(this.parseExpression());
|
|
2334
|
-
if (this.match(",")) this.advance();
|
|
2335
|
-
else break;
|
|
2336
|
-
}
|
|
2337
|
-
return { type: "command", name: "beep", args };
|
|
2338
|
-
}
|
|
2339
|
-
/**
|
|
2340
|
-
* \`push|replace url <url> [with title <title>]\` \u2014 History API navigation.
|
|
2341
|
-
*
|
|
2342
|
-
* The \`url\` keyword is consumed here rather than left for the executor, the
|
|
2343
|
-
* way \`parseGo\` already does it. Note \`push-url\` / \`replace-url\` are NOT
|
|
2344
|
-
* source spellings \u2014 the full parser rejects both \u2014 they exist only as
|
|
2345
|
-
* bundle-config aliases resolving to these templates.
|
|
2346
|
-
*/
|
|
2347
|
-
parseUrlCommand(name) {
|
|
2348
|
-
this.advance();
|
|
2349
|
-
if (this.match("url")) this.advance();
|
|
2350
|
-
const url = this.parseExpression();
|
|
2351
|
-
const modifiers = {};
|
|
2352
|
-
if (this.match("with")) {
|
|
2353
|
-
this.advance();
|
|
2354
|
-
if (this.match("title")) {
|
|
2355
|
-
this.advance();
|
|
2356
|
-
modifiers.title = this.parseExpression();
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
return { type: "command", name, args: [url], modifiers };
|
|
2360
|
-
}
|
|
2361
|
-
/** \`morph [over] <target> to|with|into <content>\` \u2014 upstream spells it \`to\`; the full parser also takes \`with\`; \`into\` is this parser's own. */
|
|
2362
|
-
parseMorph() {
|
|
2363
|
-
this.expect("morph");
|
|
2364
|
-
let modifier;
|
|
2365
|
-
if (this.match("over")) {
|
|
2366
|
-
this.advance();
|
|
2367
|
-
modifier = "over";
|
|
2368
|
-
}
|
|
2369
|
-
const target = this.parseExpression();
|
|
2370
|
-
if (this.match("with", "to", "into")) this.advance();
|
|
2371
|
-
const content = this.parseExpression();
|
|
2372
|
-
return { type: "command", name: "morph", args: [content], target, modifier };
|
|
2373
|
-
}
|
|
2374
|
-
/**
|
|
2375
|
-
* \`js[(<params>)] <raw JavaScript> end\`.
|
|
2376
|
-
*
|
|
2377
|
-
* The body is sliced from the ORIGINAL SOURCE between token offsets, never
|
|
2378
|
-
* reassembled from tokens \u2014 the tokenizer reads it as hyperscript, which
|
|
2379
|
-
* mangles regexes, template literals and possessive-looking quotes. This
|
|
2380
|
-
* mirrors the full parser's \`parseJsCommand\`.
|
|
2381
|
-
*
|
|
2382
|
-
* Known limit: the terminator scan matches the \`end\` TOKEN, so \`end\` inside a
|
|
2383
|
-
* JS *string* is safe (the tokenizer keeps a string as one token, quotes
|
|
2384
|
-
* included) but a bare JS identifier named \`end\` would cut the body early.
|
|
2385
|
-
* The full parser's \`findJsEndBoundary\` handles that; replicating it is not
|
|
2386
|
-
* worth the bytes in a bundle this size.
|
|
2387
|
-
*/
|
|
2388
|
-
parseJs() {
|
|
2389
|
-
this.advance();
|
|
2390
|
-
if (this.match("(")) {
|
|
2391
|
-
while (!this.isAtEnd() && !this.match(")")) this.advance();
|
|
2392
|
-
if (this.match(")")) this.advance();
|
|
2393
|
-
}
|
|
2394
|
-
const start = this.peek().pos;
|
|
2395
|
-
while (!this.isAtEnd() && !this.match("end")) this.advance();
|
|
2396
|
-
const stop = this.peek().pos;
|
|
2397
|
-
if (this.match("end")) this.advance();
|
|
2398
|
-
const code = this.source.slice(start, stop).trim();
|
|
2399
|
-
return { type: "command", name: "js", args: [{ type: "literal", value: code }] };
|
|
2400
|
-
}
|
|
2401
|
-
/** \`throw <value>\` */
|
|
2402
|
-
parseThrow() {
|
|
2403
|
-
this.expect("throw");
|
|
2404
|
-
let value;
|
|
2405
|
-
if (!this.isAtEnd() && !this.match("then", "and", "end", "else")) {
|
|
2406
|
-
value = this.parseExpression();
|
|
2407
|
-
}
|
|
2408
|
-
return { type: "command", name: "throw", args: value ? [value] : [] };
|
|
2409
|
-
}
|
|
2410
|
-
/** \`break\` / \`continue\` / \`exit\` \u2014 bare control-flow signals, no arguments. */
|
|
2411
|
-
parseBare(name) {
|
|
2412
|
-
this.advance();
|
|
2413
|
-
return { type: "command", name, args: [] };
|
|
2414
|
-
}
|
|
2415
|
-
parseHalt() {
|
|
2416
|
-
this.expect("halt");
|
|
2417
|
-
if (this.match("the")) this.advance();
|
|
2418
|
-
if (this.match("event", "default")) this.advance();
|
|
2419
|
-
return { type: "command", name: "halt", args: [] };
|
|
2420
|
-
}
|
|
2421
|
-
// transition <property> to <value> [over <duration>]
|
|
2422
|
-
parseTransition() {
|
|
2423
|
-
this.expect("transition");
|
|
2424
|
-
let target;
|
|
2425
|
-
if (this.match("my", "its")) {
|
|
2426
|
-
const ref = this.advance().value;
|
|
2427
|
-
target = { type: "identifier", value: ref === "my" ? "me" : "it" };
|
|
2428
|
-
} else if (this.matchType("selector")) {
|
|
2429
|
-
const expr = this.parseExpression();
|
|
2430
|
-
if (expr.type === "possessive") {
|
|
2431
|
-
return this.parseTransitionRest(expr.object, expr.property);
|
|
2432
|
-
}
|
|
2433
|
-
target = expr;
|
|
2434
|
-
}
|
|
2435
|
-
const propToken = this.peek();
|
|
2436
|
-
let property;
|
|
2437
|
-
if (propToken.type === "styleProperty") {
|
|
2438
|
-
property = this.advance().value;
|
|
2439
|
-
} else if (propToken.type === "identifier" || propToken.type === "keyword") {
|
|
2440
|
-
property = this.advance().value;
|
|
2441
|
-
} else {
|
|
2442
|
-
property = "opacity";
|
|
2443
|
-
}
|
|
2444
|
-
return this.parseTransitionRest(target, property);
|
|
2445
|
-
}
|
|
2446
|
-
parseTransitionRest(target, property) {
|
|
2447
|
-
let toValue = { type: "literal", value: 1 };
|
|
2448
|
-
if (this.match("to")) {
|
|
2449
|
-
this.advance();
|
|
2450
|
-
toValue = this.parseExpression();
|
|
2451
|
-
}
|
|
2452
|
-
let duration = { type: "literal", value: 300 };
|
|
2453
|
-
if (this.match("over")) {
|
|
2454
|
-
this.advance();
|
|
2455
|
-
duration = this.parseExpression();
|
|
2456
|
-
}
|
|
2457
|
-
return {
|
|
2458
|
-
type: "command",
|
|
2459
|
-
name: "transition",
|
|
2460
|
-
args: [{ type: "literal", value: property }, toValue, duration],
|
|
2461
|
-
target
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
// Expression parsing with operator precedence
|
|
2465
|
-
parseExpression() {
|
|
2466
|
-
return this.parseOr();
|
|
2467
|
-
}
|
|
2468
|
-
parseOr() {
|
|
2469
|
-
let left = this.parseAnd();
|
|
2470
|
-
while (this.match("or", "||")) {
|
|
2471
|
-
this.advance();
|
|
2472
|
-
left = { type: "binary", operator: "or", left, right: this.parseAnd() };
|
|
2473
|
-
}
|
|
2474
|
-
return left;
|
|
2475
|
-
}
|
|
2476
|
-
parseAnd() {
|
|
2477
|
-
let left = this.parseEquality();
|
|
2478
|
-
while (this.match("and", "&&") && !this.isCommandKeyword(this.peek(1))) {
|
|
2479
|
-
this.advance();
|
|
2480
|
-
left = { type: "binary", operator: "and", left, right: this.parseEquality() };
|
|
2481
|
-
}
|
|
2482
|
-
return left;
|
|
2483
|
-
}
|
|
2484
|
-
/**
|
|
2485
|
-
* Does this token start a command rather than continue an expression?
|
|
2486
|
-
*
|
|
2487
|
-
* Used in two places with different stakes: terminating an \`and\` chain
|
|
2488
|
-
* (\`toggle .x and add .y\`), and refusing to read a command name as a
|
|
2489
|
-
* possessive property (\`my show\`).
|
|
2490
|
-
*
|
|
2491
|
-
* This is a deliberate SUBSET of \`cmdMap\`'s keys, not a second copy of it \u2014
|
|
2492
|
-
* asserted as a subset in \`capability-emission.test.ts\` \u00a74 so it cannot
|
|
2493
|
-
* name something the parser does not dispatch. It is not the full set
|
|
2494
|
-
* because several of the newer keys are ordinary English or JS words
|
|
2495
|
-
* (\`copy\`, \`empty\`, \`push\`, \`replace\`, \`break\`) that appear as property
|
|
2496
|
-
* names and operands; promoting them here would change expression parsing,
|
|
2497
|
-
* which is a separate decision from command reachability. The cost is that
|
|
2498
|
-
* \`\u2026 and break\` reads \`break\` as an operand; \`then\` is the canonical
|
|
2499
|
-
* separator and is handled by \`parseCommandSequence\` directly.
|
|
2500
|
-
*/
|
|
2501
|
-
isCommandKeyword(token) {
|
|
2502
|
-
const cmds = [
|
|
2503
|
-
"toggle",
|
|
2504
|
-
"add",
|
|
2505
|
-
"remove",
|
|
2506
|
-
"set",
|
|
2507
|
-
"put",
|
|
2508
|
-
"log",
|
|
2509
|
-
"send",
|
|
2510
|
-
"wait",
|
|
2511
|
-
"show",
|
|
2512
|
-
"hide",
|
|
2513
|
-
"increment",
|
|
2514
|
-
"decrement",
|
|
2515
|
-
"focus",
|
|
2516
|
-
"blur",
|
|
2517
|
-
"go"
|
|
2518
|
-
];
|
|
2519
|
-
return cmds.includes(normalizeCommand(token.value));
|
|
2520
|
-
}
|
|
2521
|
-
parseEquality() {
|
|
2522
|
-
let left = this.parseComparison();
|
|
2523
|
-
while (this.match("==", "!=", "is", "matches", "contains", "includes", "has")) {
|
|
2524
|
-
const op = this.advance().value;
|
|
2525
|
-
if (op.toLowerCase() === "is" && this.match("not")) {
|
|
2526
|
-
this.advance();
|
|
2527
|
-
left = { type: "binary", operator: "is not", left, right: this.parseComparison() };
|
|
2528
|
-
} else {
|
|
2529
|
-
left = { type: "binary", operator: op, left, right: this.parseComparison() };
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
return left;
|
|
2533
|
-
}
|
|
2534
|
-
parseComparison() {
|
|
2535
|
-
let left = this.parseAdditive();
|
|
2536
|
-
while (this.match("<", ">", "<=", ">=")) {
|
|
2537
|
-
const op = this.advance().value;
|
|
2538
|
-
left = { type: "binary", operator: op, left, right: this.parseAdditive() };
|
|
2539
|
-
}
|
|
2540
|
-
return left;
|
|
2541
|
-
}
|
|
2542
|
-
parseAdditive() {
|
|
2543
|
-
let left = this.parseMultiplicative();
|
|
2544
|
-
while (this.match("+", "-")) {
|
|
2545
|
-
const op = this.advance().value;
|
|
2546
|
-
left = { type: "binary", operator: op, left, right: this.parseMultiplicative() };
|
|
2547
|
-
}
|
|
2548
|
-
return left;
|
|
2549
|
-
}
|
|
2550
|
-
parseMultiplicative() {
|
|
2551
|
-
let left = this.parseUnary();
|
|
2552
|
-
while (this.match("*", "/", "%")) {
|
|
2553
|
-
const op = this.advance().value;
|
|
2554
|
-
left = { type: "binary", operator: op, left, right: this.parseUnary() };
|
|
2555
|
-
}
|
|
2556
|
-
return left;
|
|
2557
|
-
}
|
|
2558
|
-
parseUnary() {
|
|
2559
|
-
if (this.match("beep") && this.peek(1).value === "!") {
|
|
2560
|
-
throw new Error(\`'beep!' needs the full parser (use hyperfixi.js)\`);
|
|
2561
|
-
}
|
|
2562
|
-
if (this.match("not", "!")) {
|
|
2563
|
-
this.advance();
|
|
2564
|
-
return { type: "unary", operator: "not", operand: this.parseUnary() };
|
|
2565
|
-
}
|
|
2566
|
-
if (this.match("-") && this.peek(1).type === "number") {
|
|
2567
|
-
this.advance();
|
|
2568
|
-
const num = this.advance();
|
|
2569
|
-
return { type: "literal", value: -parseFloat(num.value) };
|
|
2570
|
-
}
|
|
2571
|
-
return this.parsePostfix();
|
|
2572
|
-
}
|
|
2573
|
-
parsePostfix() {
|
|
2574
|
-
let left = this.parsePrimary();
|
|
2575
|
-
while (true) {
|
|
2576
|
-
if (this.match("'s")) {
|
|
2577
|
-
this.advance();
|
|
2578
|
-
const next = this.peek();
|
|
2579
|
-
const prop = next.type === "styleProperty" ? this.advance().value : this.advance().value;
|
|
2580
|
-
left = { type: "possessive", object: left, property: prop };
|
|
2581
|
-
} else if (this.peek().type === "styleProperty") {
|
|
2582
|
-
const prop = this.advance().value;
|
|
2583
|
-
left = { type: "possessive", object: left, property: prop };
|
|
2584
|
-
} else if (this.peek().value === ".") {
|
|
2585
|
-
this.advance();
|
|
2586
|
-
const prop = this.advance().value;
|
|
2587
|
-
left = { type: "member", object: left, property: prop };
|
|
2588
|
-
} else if (this.peek().type === "selector" && this.peek().value.startsWith(".")) {
|
|
2589
|
-
const prop = this.advance().value.slice(1);
|
|
2590
|
-
left = { type: "member", object: left, property: prop };
|
|
2591
|
-
} else if (this.peek().value === "(") {
|
|
2592
|
-
this.advance();
|
|
2593
|
-
const args = [];
|
|
2594
|
-
while (!this.match(")")) {
|
|
2595
|
-
args.push(this.parseExpression());
|
|
2596
|
-
if (this.match(",")) this.advance();
|
|
2597
|
-
}
|
|
2598
|
-
this.expect(")");
|
|
2599
|
-
left = { type: "call", callee: left, args };
|
|
2600
|
-
} else if (this.peek().value === "[" && left.type !== "selector") {
|
|
2601
|
-
this.advance();
|
|
2602
|
-
const index = this.parseExpression();
|
|
2603
|
-
this.expect("]");
|
|
2604
|
-
left = { type: "member", object: left, property: index, computed: true };
|
|
2605
|
-
} else {
|
|
2606
|
-
break;
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
return left;
|
|
2610
|
-
}
|
|
2611
|
-
parsePrimary() {
|
|
2612
|
-
const token = this.peek();
|
|
2613
|
-
if (token.value === "(") {
|
|
2614
|
-
this.advance();
|
|
2615
|
-
const expr = this.parseExpression();
|
|
2616
|
-
this.expect(")");
|
|
2617
|
-
return expr;
|
|
2618
|
-
}
|
|
2619
|
-
if (token.value === "{") return this.parseObjectLiteral();
|
|
2620
|
-
if (token.value === "[") return this.parseArrayLiteral();
|
|
2621
|
-
if (token.type === "number") {
|
|
2622
|
-
this.advance();
|
|
2623
|
-
const val = token.value;
|
|
2624
|
-
if (val.endsWith("ms")) return { type: "literal", value: parseInt(val), unit: "ms" };
|
|
2625
|
-
if (val.endsWith("s")) return { type: "literal", value: parseFloat(val) * 1e3, unit: "ms" };
|
|
2626
|
-
return { type: "literal", value: parseFloat(val) };
|
|
2627
|
-
}
|
|
2628
|
-
if (token.type === "string") {
|
|
2629
|
-
this.advance();
|
|
2630
|
-
return { type: "literal", value: token.value.slice(1, -1) };
|
|
2631
|
-
}
|
|
2632
|
-
if (this.match("true")) {
|
|
2633
|
-
this.advance();
|
|
2634
|
-
return { type: "literal", value: true };
|
|
2635
|
-
}
|
|
2636
|
-
if (this.match("false")) {
|
|
2637
|
-
this.advance();
|
|
2638
|
-
return { type: "literal", value: false };
|
|
2639
|
-
}
|
|
2640
|
-
if (this.match("null")) {
|
|
2641
|
-
this.advance();
|
|
2642
|
-
return { type: "literal", value: null };
|
|
2643
|
-
}
|
|
2644
|
-
if (this.match("undefined")) {
|
|
2645
|
-
this.advance();
|
|
2646
|
-
return { type: "literal", value: void 0 };
|
|
2647
|
-
}
|
|
2648
|
-
if (token.type === "localVar") {
|
|
2649
|
-
this.advance();
|
|
2650
|
-
return { type: "variable", name: token.value, scope: "local" };
|
|
2651
|
-
}
|
|
2652
|
-
if (token.type === "globalVar") {
|
|
2653
|
-
this.advance();
|
|
2654
|
-
return { type: "variable", name: token.value, scope: "global" };
|
|
2655
|
-
}
|
|
2656
|
-
if (token.type === "selector") {
|
|
2657
|
-
this.advance();
|
|
2658
|
-
return { type: "selector", value: token.value };
|
|
2659
|
-
}
|
|
2660
|
-
if (this.match("my")) {
|
|
2661
|
-
this.advance();
|
|
2662
|
-
const next = this.peek();
|
|
2663
|
-
if ((next.type === "identifier" || next.type === "keyword") && !this.isCommandKeyword(next)) {
|
|
2664
|
-
const prop = this.advance().value;
|
|
2665
|
-
return { type: "possessive", object: { type: "identifier", value: "me" }, property: prop };
|
|
2666
|
-
}
|
|
2667
|
-
return { type: "identifier", value: "me" };
|
|
2668
|
-
}
|
|
2669
|
-
if (this.match("its")) {
|
|
2670
|
-
this.advance();
|
|
2671
|
-
const next = this.peek();
|
|
2672
|
-
if ((next.type === "identifier" || next.type === "keyword") && !this.isCommandKeyword(next)) {
|
|
2673
|
-
const prop = this.advance().value;
|
|
2674
|
-
return { type: "possessive", object: { type: "identifier", value: "it" }, property: prop };
|
|
2675
|
-
}
|
|
2676
|
-
return { type: "identifier", value: "it" };
|
|
2677
|
-
}
|
|
2678
|
-
if (this.match("me")) {
|
|
2679
|
-
this.advance();
|
|
2680
|
-
return { type: "identifier", value: "me" };
|
|
2681
|
-
}
|
|
2682
|
-
if (this.match("it")) {
|
|
2683
|
-
this.advance();
|
|
2684
|
-
return { type: "identifier", value: "it" };
|
|
2685
|
-
}
|
|
2686
|
-
if (this.match("you")) {
|
|
2687
|
-
this.advance();
|
|
2688
|
-
return { type: "identifier", value: "you" };
|
|
2689
|
-
}
|
|
2690
|
-
if (this.match("the", "a", "an")) {
|
|
2691
|
-
this.advance();
|
|
2692
|
-
if (this.match("first", "last", "next", "previous", "closest", "parent")) {
|
|
2693
|
-
const position = this.advance().value;
|
|
2694
|
-
const target = this.parsePositionalTarget();
|
|
2695
|
-
return { type: "positional", position, target };
|
|
2696
|
-
}
|
|
2697
|
-
return this.parsePrimary();
|
|
2698
|
-
}
|
|
2699
|
-
if (this.match("first", "last", "next", "previous", "closest", "parent")) {
|
|
2700
|
-
const position = this.advance().value;
|
|
2701
|
-
const target = this.parsePositionalTarget();
|
|
2702
|
-
return { type: "positional", position, target };
|
|
2703
|
-
}
|
|
2704
|
-
if (this.match("values")) {
|
|
2705
|
-
this.advance();
|
|
2706
|
-
if (this.match("of")) {
|
|
2707
|
-
this.advance();
|
|
2708
|
-
const target = this.parseExpression();
|
|
2709
|
-
return { type: "valuesOf", target };
|
|
2710
|
-
}
|
|
2711
|
-
return { type: "identifier", value: token.value };
|
|
2712
|
-
}
|
|
2713
|
-
if (token.type === "identifier" || token.type === "keyword") {
|
|
2714
|
-
this.advance();
|
|
2715
|
-
return { type: "identifier", value: token.value };
|
|
2716
|
-
}
|
|
2717
|
-
this.advance();
|
|
2718
|
-
return { type: "identifier", value: token.value };
|
|
2719
|
-
}
|
|
2720
|
-
parseObjectLiteral() {
|
|
2721
|
-
this.expect("{");
|
|
2722
|
-
const properties = [];
|
|
2723
|
-
while (!this.match("}")) {
|
|
2724
|
-
const key = this.advance().value;
|
|
2725
|
-
this.expect(":");
|
|
2726
|
-
const value = this.parseExpression();
|
|
2727
|
-
properties.push({ key, value });
|
|
2728
|
-
if (this.match(",")) this.advance();
|
|
2729
|
-
}
|
|
2730
|
-
this.expect("}");
|
|
2731
|
-
return { type: "object", properties };
|
|
2732
|
-
}
|
|
2733
|
-
parseArrayLiteral() {
|
|
2734
|
-
this.expect("[");
|
|
2735
|
-
const elements = [];
|
|
2736
|
-
while (!this.match("]")) {
|
|
2737
|
-
elements.push(this.parseExpression());
|
|
2738
|
-
if (this.match(",")) this.advance();
|
|
2739
|
-
}
|
|
2740
|
-
this.expect("]");
|
|
2741
|
-
return { type: "array", elements };
|
|
2742
|
-
}
|
|
2743
|
-
parsePositionalTarget() {
|
|
2744
|
-
const token = this.peek();
|
|
2745
|
-
if (token.type === "selector") {
|
|
2746
|
-
return { type: "selector", value: this.advance().value };
|
|
2747
|
-
}
|
|
2748
|
-
if (token.type === "identifier" || token.type === "keyword") {
|
|
2749
|
-
return { type: "identifier", value: this.advance().value };
|
|
2750
|
-
}
|
|
2751
|
-
return this.parseExpression();
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
// #endregion generated:hybrid-parser
|
|
2755
|
-
`;
|
|
2756
|
-
function getParserTemplate(type) {
|
|
2757
|
-
return type === 'lite' ? LITE_PARSER_TEMPLATE : HYBRID_PARSER_TEMPLATE;
|
|
2758
|
-
}
|
|
2759
|
-
const LITE_PARSER_COMMANDS = [
|
|
2760
|
-
'toggle',
|
|
2761
|
-
'add',
|
|
2762
|
-
'remove',
|
|
2763
|
-
'put',
|
|
2764
|
-
'set',
|
|
2765
|
-
'log',
|
|
2766
|
-
'send',
|
|
2767
|
-
'wait',
|
|
2768
|
-
'show',
|
|
2769
|
-
'hide',
|
|
2770
|
-
];
|
|
2771
|
-
function canUseLiteParser(commands, blocks, positional) {
|
|
2772
|
-
if (blocks.length > 0)
|
|
2773
|
-
return false;
|
|
2774
|
-
if (positional)
|
|
2775
|
-
return false;
|
|
2776
|
-
const liteCommands = new Set(LITE_PARSER_COMMANDS);
|
|
2777
|
-
return commands.every(cmd => liteCommands.has(cmd));
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
exports.AVAILABLE_BLOCKS = AVAILABLE_BLOCKS;
|
|
2781
|
-
exports.AVAILABLE_COMMANDS = AVAILABLE_COMMANDS;
|
|
2782
|
-
exports.BLOCK_IMPLEMENTATIONS = BLOCK_IMPLEMENTATIONS;
|
|
2783
|
-
exports.COMMAND_ALIASES = COMMAND_ALIASES;
|
|
2784
|
-
exports.COMMAND_IMPLEMENTATIONS = COMMAND_IMPLEMENTATIONS;
|
|
2785
|
-
exports.ELEMENT_ARRAY_COMMANDS = ELEMENT_ARRAY_COMMANDS;
|
|
2786
|
-
exports.FULL_RUNTIME_ONLY_COMMANDS = FULL_RUNTIME_ONLY_COMMANDS;
|
|
2787
|
-
exports.HYBRID_PARSER_TEMPLATE = HYBRID_PARSER_TEMPLATE;
|
|
2788
|
-
exports.LITE_PARSER_COMMANDS = LITE_PARSER_COMMANDS;
|
|
2789
|
-
exports.LITE_PARSER_TEMPLATE = LITE_PARSER_TEMPLATE;
|
|
2790
|
-
exports.MORPH_COMMANDS = MORPH_COMMANDS;
|
|
2791
|
-
exports.STYLE_COMMANDS = STYLE_COMMANDS;
|
|
2792
|
-
exports.canUseLiteParser = canUseLiteParser;
|
|
2793
|
-
exports.generateBundle = generateBundle;
|
|
2794
|
-
exports.generateBundleCode = generateBundleCode;
|
|
2795
|
-
exports.getAvailableBlocks = getAvailableBlocks;
|
|
2796
|
-
exports.getAvailableCommands = getAvailableCommands;
|
|
2797
|
-
exports.getBlockImplementations = getBlockImplementations;
|
|
2798
|
-
exports.getCommandImplementations = getCommandImplementations;
|
|
2799
|
-
exports.getParserTemplate = getParserTemplate;
|
|
2800
|
-
exports.isAvailableBlock = isAvailableBlock;
|
|
2801
|
-
exports.isAvailableCommand = isAvailableCommand;
|
|
2802
|
-
exports.requiresFullRuntime = requiresFullRuntime;
|
|
2803
|
-
exports.resolveCommandKey = resolveCommandKey;
|
|
2804
|
-
//# sourceMappingURL=index.cjs.map
|