@hyperfixi/core 3.1.1 → 3.3.0
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 +28 -36
- package/dist/ast/command-slots.d.ts +1 -1
- package/dist/ast/nodes.d.ts +2 -0
- package/dist/ast-utils/index.cjs +193 -48
- package/dist/ast-utils/index.mjs +193 -48
- package/dist/ast-utils/interchange/types.d.ts +19 -1
- package/dist/behaviors/index.cjs +19 -0
- package/dist/behaviors/index.mjs +19 -0
- package/dist/bundle-generator/index.cjs +36 -17
- package/dist/bundle-generator/index.mjs +36 -17
- package/dist/bundle-generator/parser-templates.d.ts +1 -1
- package/dist/chunks/bridge-CI9SOl8u.js +2 -0
- package/dist/chunks/browser-modular-7CpI5VF2.js +2 -0
- package/dist/chunks/index-BwXDEK0m.js +2 -0
- package/dist/chunks/index-DQi2o8Lk.js +2 -0
- package/dist/chunks/index-uCOQ8Qn0.js +2 -0
- package/dist/commands/animation/transition.d.ts +12 -6
- package/dist/commands/helpers/loop-executor.d.ts +2 -2
- package/dist/commands/helpers/numeric-target-parser.d.ts +1 -1
- package/dist/commands/helpers/variable-access.d.ts +5 -2
- package/dist/commands/helpers/write-target.d.ts +5 -0
- package/dist/commands/index.cjs +938 -425
- package/dist/commands/index.mjs +938 -425
- package/dist/commands/templates/render.d.ts +4 -4
- package/dist/commands/utility/beep.d.ts +0 -3
- package/dist/expressions/index.cjs +7 -13
- package/dist/expressions/index.mjs +7 -13
- package/dist/htmx/i18n-hooks.d.ts +4 -1
- package/dist/hyperfixi-browser-classic-i18n.js +1 -1
- package/dist/hyperfixi-browser.js +1 -1
- package/dist/hyperfixi-classic-i18n.js +1 -1
- package/dist/hyperfixi-hx-v4.js +1 -1
- package/dist/hyperfixi-hx.js +1 -1
- package/dist/hyperfixi-hybrid-complete.js +1 -1
- package/dist/hyperfixi-hybrid-hx.js +1 -1
- package/dist/hyperfixi-multilingual.js +1 -1
- package/dist/hyperfixi.js +1 -1
- package/dist/hyperfixi.mjs +1 -1
- package/dist/index.cjs +1149 -485
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +1149 -485
- package/dist/lib/swap-executor.d.ts +1 -1
- package/dist/lokascript-browser-classic-i18n.js +1 -1
- package/dist/lokascript-browser.js +1 -1
- package/dist/lokascript-hybrid-complete.js +1 -1
- package/dist/lokascript-hybrid-hx.js +1 -1
- package/dist/lokascript-multilingual.js +1 -1
- package/dist/metadata.cjs +9 -9
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.mjs +9 -9
- package/dist/parser/command-grammar.d.ts +1 -0
- package/dist/parser/full-parser.cjs +396 -153
- package/dist/parser/full-parser.mjs +396 -153
- package/dist/parser/helpers/parsing-helpers.d.ts +3 -0
- package/dist/parser/hybrid/index.cjs +3 -0
- package/dist/parser/hybrid/index.mjs +3 -0
- package/dist/parser/hybrid/parser-core.cjs +3 -0
- package/dist/parser/hybrid/parser-core.mjs +3 -0
- package/dist/parser/hybrid-parser.cjs +3 -0
- package/dist/parser/hybrid-parser.mjs +3 -0
- package/dist/parser/parser-types.d.ts +2 -0
- package/dist/parser/parser.d.ts +9 -0
- package/dist/parser/pratt-parser.d.ts +2 -0
- package/dist/registry/index.cjs +535 -210
- package/dist/registry/index.mjs +535 -210
- package/dist/utils/beep.d.ts +8 -0
- package/dist/version.d.ts +1 -1
- package/package.json +21 -18
- package/vocab/htmx/ar.js +22 -20
- package/vocab/htmx/bn.js +21 -5
- package/vocab/htmx/de.js +33 -10
- package/vocab/htmx/en.js +4 -1
- package/vocab/htmx/es.js +28 -9
- package/vocab/htmx/fr.js +28 -8
- package/vocab/htmx/he.js +22 -1
- package/vocab/htmx/hi.js +28 -7
- package/vocab/htmx/id.js +29 -10
- package/vocab/htmx/it.js +26 -5
- package/vocab/htmx/ja.js +36 -11
- package/vocab/htmx/ko.js +27 -6
- package/vocab/htmx/ms.js +24 -1
- package/vocab/htmx/pl.js +27 -7
- package/vocab/htmx/pt.js +30 -9
- package/vocab/htmx/qu.js +32 -9
- package/vocab/htmx/ru.js +28 -6
- package/vocab/htmx/sw.js +28 -8
- package/vocab/htmx/th.js +19 -1
- package/vocab/htmx/tl.js +24 -1
- package/vocab/htmx/tr.js +34 -8
- package/vocab/htmx/uk.js +28 -6
- package/vocab/htmx/vi.js +27 -19
- package/vocab/htmx/zh.js +28 -9
- package/dist/chunks/bridge-D7qrHhV0.js +0 -2
- package/dist/chunks/browser-modular-C4LFxfoS.js +0 -2
- package/dist/chunks/index-C7lqD4mo.js +0 -2
- package/dist/chunks/index-CAPhqA6k.js +0 -2
- package/dist/chunks/index-CLdRyZaP.js +0 -2
package/dist/registry/index.cjs
CHANGED
|
@@ -899,6 +899,39 @@ function makeTypeCheckHandler(negated) {
|
|
|
899
899
|
};
|
|
900
900
|
};
|
|
901
901
|
}
|
|
902
|
+
const OF_OPERAND_BP = 70;
|
|
903
|
+
function parseOfOperand(ctx, minBp = OF_OPERAND_BP) {
|
|
904
|
+
let right = ctx.parseExpr(minBp);
|
|
905
|
+
if (ctx.peek()?.value === 'in') {
|
|
906
|
+
const inToken = ctx.advance();
|
|
907
|
+
const scope = ctx.parseExpr(minBp);
|
|
908
|
+
right = {
|
|
909
|
+
type: 'binaryExpression',
|
|
910
|
+
operator: 'in',
|
|
911
|
+
left: right,
|
|
912
|
+
right: scope,
|
|
913
|
+
start: right.start,
|
|
914
|
+
end: scope.end ?? inToken.end,
|
|
915
|
+
line: right.line ?? inToken.line,
|
|
916
|
+
column: right.column ?? inToken.column,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
return right;
|
|
920
|
+
}
|
|
921
|
+
const THE_OF_TARGET_BP = OF_OPERAND_BP + 1;
|
|
922
|
+
function ofHandler(left, token, ctx) {
|
|
923
|
+
const right = parseOfOperand(ctx);
|
|
924
|
+
return {
|
|
925
|
+
type: 'binaryExpression',
|
|
926
|
+
operator: token.value,
|
|
927
|
+
left,
|
|
928
|
+
right,
|
|
929
|
+
start: left.start,
|
|
930
|
+
end: right.end ?? token.end,
|
|
931
|
+
line: left.line ?? token.line,
|
|
932
|
+
column: left.column ?? token.column,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
902
935
|
const PARSER_COMPARISON_FRAGMENT = new Map([
|
|
903
936
|
['===', leftAssoc(30)],
|
|
904
937
|
['!==', leftAssoc(30)],
|
|
@@ -943,7 +976,7 @@ const PARSER_COMPARISON_FRAGMENT = new Map([
|
|
|
943
976
|
['is greater than or equal to', leftAssoc(30)],
|
|
944
977
|
['is less than or equal to', leftAssoc(30)],
|
|
945
978
|
['in', leftAssoc(30)],
|
|
946
|
-
['of',
|
|
979
|
+
['of', { infix: { bp: [90, OF_OPERAND_BP], handler: ofHandler } }],
|
|
947
980
|
['really', leftAssoc(30)],
|
|
948
981
|
[
|
|
949
982
|
'exists',
|
|
@@ -1119,36 +1152,108 @@ function setElementVar(context, name, value) {
|
|
|
1119
1152
|
notifyLocalWrite(name, value, context);
|
|
1120
1153
|
}
|
|
1121
1154
|
|
|
1122
|
-
function
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1155
|
+
function isHTMLElement$2(value) {
|
|
1156
|
+
return (value !== null &&
|
|
1157
|
+
typeof value === 'object' &&
|
|
1158
|
+
'nodeType' in value &&
|
|
1159
|
+
value.nodeType === 1 &&
|
|
1160
|
+
'tagName' in value &&
|
|
1161
|
+
typeof value.tagName === 'string' &&
|
|
1162
|
+
'classList' in value);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function beepValues(me, values) {
|
|
1166
|
+
const outputs = values.map(describeBeepValue);
|
|
1167
|
+
const shown = outputs.filter(output => announce(me, output.value));
|
|
1168
|
+
if (shown.length === 0)
|
|
1169
|
+
return outputs;
|
|
1170
|
+
console.group('\ud83d\udd14 beep! Debug Output');
|
|
1171
|
+
for (const output of shown) {
|
|
1172
|
+
console.log(`Value:`, output.value);
|
|
1173
|
+
console.log(`Type:`, output.type);
|
|
1174
|
+
console.log(`Representation:`, output.representation);
|
|
1175
|
+
console.log('---');
|
|
1176
|
+
}
|
|
1177
|
+
console.groupEnd();
|
|
1178
|
+
return outputs;
|
|
1179
|
+
}
|
|
1180
|
+
function announce(me, value) {
|
|
1181
|
+
const target = me;
|
|
1182
|
+
if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
|
|
1183
|
+
return true;
|
|
1184
|
+
return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
|
|
1185
|
+
bubbles: true,
|
|
1186
|
+
cancelable: true,
|
|
1187
|
+
detail: { element: me, value },
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
function describeBeepValue(value) {
|
|
1191
|
+
return { value, type: typeOf(value), representation: representationOf(value) };
|
|
1192
|
+
}
|
|
1193
|
+
function typeOf(value) {
|
|
1194
|
+
if (value === null)
|
|
1195
|
+
return 'null';
|
|
1196
|
+
if (value === undefined)
|
|
1197
|
+
return 'undefined';
|
|
1198
|
+
if (Array.isArray(value))
|
|
1199
|
+
return 'array';
|
|
1200
|
+
if (isHTMLElement$2(value))
|
|
1201
|
+
return 'HTMLElement';
|
|
1202
|
+
if (typeof Element !== 'undefined' && value instanceof Element)
|
|
1203
|
+
return 'Element';
|
|
1204
|
+
if (typeof Node !== 'undefined' && value instanceof Node)
|
|
1205
|
+
return 'Node';
|
|
1206
|
+
if (value instanceof Error)
|
|
1207
|
+
return 'Error';
|
|
1208
|
+
if (value instanceof Date)
|
|
1209
|
+
return 'Date';
|
|
1210
|
+
if (value instanceof RegExp)
|
|
1211
|
+
return 'RegExp';
|
|
1212
|
+
return typeof value;
|
|
1213
|
+
}
|
|
1214
|
+
function representationOf(value) {
|
|
1215
|
+
if (value === null)
|
|
1216
|
+
return 'null';
|
|
1217
|
+
if (value === undefined)
|
|
1218
|
+
return 'undefined';
|
|
1136
1219
|
if (Array.isArray(value)) {
|
|
1137
|
-
return value.length
|
|
1138
|
-
|
|
1220
|
+
return `Array(${value.length}) [${value
|
|
1221
|
+
.slice(0, 3)
|
|
1222
|
+
.map(v => representationOf(v))
|
|
1223
|
+
.join(', ')}${value.length > 3 ? '...' : ''}]`;
|
|
1224
|
+
}
|
|
1225
|
+
if (isHTMLElement$2(value)) {
|
|
1226
|
+
const el = value;
|
|
1227
|
+
const tag = el.tagName.toLowerCase();
|
|
1228
|
+
const id = el.id ? `#${el.id}` : '';
|
|
1229
|
+
const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
|
|
1230
|
+
return `<${tag}${id}${classes}/>`;
|
|
1231
|
+
}
|
|
1232
|
+
if (value instanceof Error)
|
|
1233
|
+
return `Error: ${value.message}`;
|
|
1234
|
+
if (typeof value === 'string')
|
|
1235
|
+
return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
|
|
1139
1236
|
if (typeof value === 'object') {
|
|
1140
|
-
|
|
1141
|
-
|
|
1237
|
+
try {
|
|
1238
|
+
const keys = Object.keys(value);
|
|
1239
|
+
return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
|
|
1142
1240
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
if (typeof result === 'number') {
|
|
1146
|
-
return result;
|
|
1147
|
-
}
|
|
1241
|
+
catch {
|
|
1242
|
+
return '[Object]';
|
|
1148
1243
|
}
|
|
1149
|
-
return NaN;
|
|
1150
1244
|
}
|
|
1151
|
-
return
|
|
1245
|
+
return String(value);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function counterAmount(value) {
|
|
1249
|
+
return typeof value === 'number' ? value : parseFloat(String(value));
|
|
1250
|
+
}
|
|
1251
|
+
function counterValue(value) {
|
|
1252
|
+
if (isHTMLElement$2(value)) {
|
|
1253
|
+
const element = value;
|
|
1254
|
+
return counterValue(element.value || element.textContent);
|
|
1255
|
+
}
|
|
1256
|
+
return value ? counterAmount(value) : 0;
|
|
1152
1257
|
}
|
|
1153
1258
|
|
|
1154
1259
|
function resolveTargetElements(matches, selector, fromQuery) {
|
|
@@ -1514,13 +1619,6 @@ function isFunction(value) {
|
|
|
1514
1619
|
function isInputElement(el) {
|
|
1515
1620
|
return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
|
|
1516
1621
|
}
|
|
1517
|
-
function isFormElement(el) {
|
|
1518
|
-
if (el === null || typeof el !== 'object' || !('tagName' in el)) {
|
|
1519
|
-
return false;
|
|
1520
|
-
}
|
|
1521
|
-
const tag = el.tagName;
|
|
1522
|
-
return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
|
|
1523
|
-
}
|
|
1524
1622
|
function isHTMLElement$1(el) {
|
|
1525
1623
|
return (el !== null &&
|
|
1526
1624
|
typeof el === 'object' &&
|
|
@@ -1580,7 +1678,7 @@ const SPECIAL_DOM_PROPERTIES = {
|
|
|
1580
1678
|
innertext: el => el.textContent?.trim(),
|
|
1581
1679
|
innerHTML: el => el.innerHTML,
|
|
1582
1680
|
outerhtml: el => el.outerHTML,
|
|
1583
|
-
value: el => (
|
|
1681
|
+
value: el => ('value' in el ? el.value : undefined),
|
|
1584
1682
|
checked: el => (isInputElement(el) ? el.checked : undefined),
|
|
1585
1683
|
disabled: el => ('disabled' in el ? el.disabled : undefined),
|
|
1586
1684
|
selected: el => (isOptionElement(el) ? el.selected : undefined),
|
|
@@ -2248,14 +2346,15 @@ const closestExpression = {
|
|
|
2248
2346
|
if (typeof selector !== 'string') {
|
|
2249
2347
|
throw new Error('closest requires a string selector');
|
|
2250
2348
|
}
|
|
2251
|
-
|
|
2349
|
+
const start = args.length > 1 ? args[1] : context.me;
|
|
2350
|
+
if (!(start instanceof Element)) {
|
|
2252
2351
|
return null;
|
|
2253
2352
|
}
|
|
2254
|
-
return
|
|
2353
|
+
return start.closest(selector);
|
|
2255
2354
|
},
|
|
2256
2355
|
validate(args) {
|
|
2257
|
-
if (args.length !== 1) {
|
|
2258
|
-
return 'closest requires
|
|
2356
|
+
if (args.length !== 1 && args.length !== 2) {
|
|
2357
|
+
return 'closest requires a selector and, optionally, the element to start from';
|
|
2259
2358
|
}
|
|
2260
2359
|
const arg = args[0];
|
|
2261
2360
|
if (typeof arg === 'string')
|
|
@@ -4641,7 +4740,7 @@ const isEmptyExpression = {
|
|
|
4641
4740
|
else if (value instanceof Node || value instanceof Element)
|
|
4642
4741
|
result = false;
|
|
4643
4742
|
else if (isObject(value))
|
|
4644
|
-
result =
|
|
4743
|
+
result = value.length === 0;
|
|
4645
4744
|
else
|
|
4646
4745
|
result = false;
|
|
4647
4746
|
return result;
|
|
@@ -5718,6 +5817,48 @@ function parseMaybeNamedArgument(ctx) {
|
|
|
5718
5817
|
const value = ctx.parseExpression();
|
|
5719
5818
|
return name !== undefined ? { name, value } : { value };
|
|
5720
5819
|
}
|
|
5820
|
+
function isNakedNamedArgStart(ctx) {
|
|
5821
|
+
if (ctx.check('{'))
|
|
5822
|
+
return false;
|
|
5823
|
+
if (!ctx.checkIdentifierLike())
|
|
5824
|
+
return false;
|
|
5825
|
+
const next = ctx.peekAt(1);
|
|
5826
|
+
return next !== null && next.value === ':';
|
|
5827
|
+
}
|
|
5828
|
+
function parseNakedNamedArgs(ctx, parseValue, command) {
|
|
5829
|
+
const properties = [];
|
|
5830
|
+
const startPos = ctx.getPosition();
|
|
5831
|
+
do {
|
|
5832
|
+
if (!ctx.checkIdentifierLike())
|
|
5833
|
+
break;
|
|
5834
|
+
const keyToken = ctx.advance();
|
|
5835
|
+
const key = {
|
|
5836
|
+
type: 'identifier',
|
|
5837
|
+
name: keyToken.value,
|
|
5838
|
+
start: keyToken.start,
|
|
5839
|
+
end: keyToken.end,
|
|
5840
|
+
line: keyToken.line,
|
|
5841
|
+
column: keyToken.column,
|
|
5842
|
+
};
|
|
5843
|
+
ctx.consume(':', `Expected ':' after property name in ${command} named arguments`);
|
|
5844
|
+
const value = parseValue();
|
|
5845
|
+
if (value) {
|
|
5846
|
+
properties.push({ key, value });
|
|
5847
|
+
}
|
|
5848
|
+
} while (ctx.match(',') && !ctx.isAtEnd());
|
|
5849
|
+
const endPos = ctx.getPosition();
|
|
5850
|
+
return {
|
|
5851
|
+
type: 'objectLiteral',
|
|
5852
|
+
properties,
|
|
5853
|
+
start: startPos.start,
|
|
5854
|
+
end: endPos.end,
|
|
5855
|
+
line: startPos.line,
|
|
5856
|
+
column: startPos.column,
|
|
5857
|
+
};
|
|
5858
|
+
}
|
|
5859
|
+
function isGluedBeepBang(beep, bang) {
|
|
5860
|
+
return beep?.value === 'beep' && bang?.value === '!' && bang.start === beep.end;
|
|
5861
|
+
}
|
|
5721
5862
|
|
|
5722
5863
|
class CommandNodeBuilder {
|
|
5723
5864
|
constructor(name) {
|
|
@@ -5919,6 +6060,8 @@ function parseTriggerCommand(ctx, identifierNode) {
|
|
|
5919
6060
|
const modifiers = {};
|
|
5920
6061
|
while (!isCommandBoundary(ctx)) {
|
|
5921
6062
|
if (ctx.check(KEYWORDS.ON) || ctx.check(KEYWORDS.TO)) {
|
|
6063
|
+
if (modifiers['on'])
|
|
6064
|
+
break;
|
|
5922
6065
|
ctx.advance();
|
|
5923
6066
|
const target = parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
5924
6067
|
if (target)
|
|
@@ -6059,6 +6202,9 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6059
6202
|
ctx.advance();
|
|
6060
6203
|
loopType = KEYWORDS.FOREVER;
|
|
6061
6204
|
}
|
|
6205
|
+
else if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
6206
|
+
loopType = KEYWORDS.FOREVER;
|
|
6207
|
+
}
|
|
6062
6208
|
else {
|
|
6063
6209
|
times = ctx.parseExpression();
|
|
6064
6210
|
if (ctx.check(KEYWORDS.TIMES)) {
|
|
@@ -6101,10 +6247,12 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6101
6247
|
loopType = result.terminator;
|
|
6102
6248
|
ctx.advance();
|
|
6103
6249
|
condition = ctx.parseExpression();
|
|
6104
|
-
if (
|
|
6250
|
+
if (ctx.check('end')) {
|
|
6251
|
+
ctx.advance();
|
|
6252
|
+
}
|
|
6253
|
+
else if (!ctx.atEndOfInput()) {
|
|
6105
6254
|
throw new Error('Expected "end" to close repeat block');
|
|
6106
6255
|
}
|
|
6107
|
-
ctx.advance();
|
|
6108
6256
|
}
|
|
6109
6257
|
}
|
|
6110
6258
|
else {
|
|
@@ -6350,7 +6498,7 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6350
6498
|
}));
|
|
6351
6499
|
}
|
|
6352
6500
|
}
|
|
6353
|
-
if (!consumedElseIf && !ctx.
|
|
6501
|
+
if (!consumedElseIf && !ctx.atEndOfInput()) {
|
|
6354
6502
|
ctx.consume(KEYWORDS.END, "Expected 'end' after if block");
|
|
6355
6503
|
}
|
|
6356
6504
|
}
|
|
@@ -6375,7 +6523,6 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6375
6523
|
.build();
|
|
6376
6524
|
}
|
|
6377
6525
|
function parseForCommand(ctx, commandToken) {
|
|
6378
|
-
const args = [];
|
|
6379
6526
|
let variable = null;
|
|
6380
6527
|
let collection = null;
|
|
6381
6528
|
if (ctx.check(KEYWORDS.EACH)) {
|
|
@@ -6418,31 +6565,22 @@ function parseForCommand(ctx, commandToken) {
|
|
|
6418
6565
|
}
|
|
6419
6566
|
}
|
|
6420
6567
|
const commands = ctx.parseCommandListUntilEnd('for');
|
|
6421
|
-
|
|
6422
|
-
type: 'identifier',
|
|
6423
|
-
name: 'for',
|
|
6424
|
-
start: commandToken.start,
|
|
6425
|
-
end: commandToken.end,
|
|
6426
|
-
line: commandToken.line,
|
|
6427
|
-
column: commandToken.column,
|
|
6428
|
-
});
|
|
6429
|
-
const forPos = {
|
|
6568
|
+
const pos = {
|
|
6430
6569
|
start: commandToken.start,
|
|
6431
6570
|
end: commandToken.end,
|
|
6432
6571
|
line: commandToken.line,
|
|
6433
6572
|
column: commandToken.column,
|
|
6434
6573
|
};
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
}
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
.withArgs(...args)
|
|
6574
|
+
const modifiers = {
|
|
6575
|
+
loopType: toLegacyExpression(createStringLiteral(KEYWORDS.FOR, pos)),
|
|
6576
|
+
for: toLegacyExpression(createStringLiteral(variable, pos)),
|
|
6577
|
+
in: collection,
|
|
6578
|
+
};
|
|
6579
|
+
if (indexVariable)
|
|
6580
|
+
modifiers['index'] = toLegacyExpression(createStringLiteral(indexVariable, pos));
|
|
6581
|
+
return CommandNodeBuilder.from({ ...commandToken, value: 'repeat' })
|
|
6582
|
+
.withArgs(createBlock(commands, { ...pos, end: pos.end || 0 }))
|
|
6583
|
+
.withModifiers(modifiers)
|
|
6446
6584
|
.endingAt(ctx.getPosition())
|
|
6447
6585
|
.build();
|
|
6448
6586
|
}
|
|
@@ -6498,86 +6636,159 @@ function parseMeasureCommand(ctx, identifierNode) {
|
|
|
6498
6636
|
}
|
|
6499
6637
|
return builder.build();
|
|
6500
6638
|
}
|
|
6501
|
-
const
|
|
6639
|
+
const CONTEXT_REFS = new Set(['me', 'it', 'you', 'result']);
|
|
6502
6640
|
function parseTransitionCommand(ctx, commandToken) {
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
const
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
if (
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
target = parsed.object;
|
|
6519
|
-
property = {
|
|
6520
|
-
type: 'string',
|
|
6521
|
-
value: parsed.property.name,
|
|
6522
|
-
start: leadToken.start || 0,
|
|
6523
|
-
end: ctx.getPosition().end,
|
|
6524
|
-
line: leadToken.line,
|
|
6525
|
-
column: leadToken.column,
|
|
6526
|
-
};
|
|
6641
|
+
const pairs = [];
|
|
6642
|
+
do {
|
|
6643
|
+
pairs.push(parseTransitionPair(ctx));
|
|
6644
|
+
} while (startsAnotherPair(ctx));
|
|
6645
|
+
const [first, ...rest] = pairs;
|
|
6646
|
+
const args = first.owner ? [first.owner, first.property] : [first.property];
|
|
6647
|
+
const modifiers = { to: first.to };
|
|
6648
|
+
if (first.from)
|
|
6649
|
+
modifiers['from'] = first.from;
|
|
6650
|
+
if (rest.length > 0)
|
|
6651
|
+
modifiers['pairs'] = pairsNode(rest);
|
|
6652
|
+
for (let i = 0; i < 3 && !ctx.isAtEnd(); i++) {
|
|
6653
|
+
if (ctx.check('over') && !modifiers['over']) {
|
|
6654
|
+
ctx.advance();
|
|
6655
|
+
modifiers['over'] = ctx.parseExpression();
|
|
6527
6656
|
}
|
|
6528
|
-
else {
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
ctx.advance();
|
|
6657
|
+
else if (ctx.check('using') && !modifiers['using']) {
|
|
6658
|
+
ctx.advance();
|
|
6659
|
+
modifiers['using'] = ctx.parseExpression();
|
|
6532
6660
|
}
|
|
6533
|
-
|
|
6534
|
-
const firstToken = ctx.peek();
|
|
6535
|
-
if (!property && (isIdentifierLike(firstToken) || firstToken.value === '*')) {
|
|
6536
|
-
let propertyValue = '';
|
|
6537
|
-
if (ctx.check('*')) {
|
|
6538
|
-
propertyValue = '*';
|
|
6661
|
+
else if (ctx.check(KEYWORDS.WITH) && !modifiers['with']) {
|
|
6539
6662
|
ctx.advance();
|
|
6663
|
+
modifiers['with'] = ctx.parsePrimary();
|
|
6540
6664
|
}
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
propertyValue += hyphenatedName;
|
|
6544
|
-
property = {
|
|
6545
|
-
type: 'string',
|
|
6546
|
-
value: propertyValue,
|
|
6547
|
-
start: firstToken.start || 0,
|
|
6548
|
-
end: ctx.getPosition().end,
|
|
6549
|
-
line: firstToken.line,
|
|
6550
|
-
column: firstToken.column,
|
|
6551
|
-
};
|
|
6665
|
+
else {
|
|
6666
|
+
break;
|
|
6552
6667
|
}
|
|
6553
6668
|
}
|
|
6554
|
-
|
|
6555
|
-
|
|
6669
|
+
return CommandNodeBuilder.from(commandToken)
|
|
6670
|
+
.withArgs(...args)
|
|
6671
|
+
.withModifiers(modifiers)
|
|
6672
|
+
.endingAt(ctx.getPosition())
|
|
6673
|
+
.build();
|
|
6674
|
+
}
|
|
6675
|
+
function parseTransitionPair(ctx) {
|
|
6676
|
+
const { owner, property } = parseTransitionProperty(ctx);
|
|
6677
|
+
let from = null;
|
|
6678
|
+
if (ctx.check('from')) {
|
|
6679
|
+
ctx.advance();
|
|
6680
|
+
from = ctx.parseExpression();
|
|
6556
6681
|
}
|
|
6557
|
-
if (target)
|
|
6558
|
-
args.push(target);
|
|
6559
|
-
args.push(property);
|
|
6560
6682
|
if (!ctx.check(KEYWORDS.TO)) {
|
|
6561
6683
|
throw new Error('Expected "to" keyword after property in transition command');
|
|
6562
6684
|
}
|
|
6563
6685
|
ctx.advance();
|
|
6564
|
-
const
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6686
|
+
const to = ctx.parseExpression();
|
|
6687
|
+
return { owner, property, from, to };
|
|
6688
|
+
}
|
|
6689
|
+
function parseTransitionProperty(ctx) {
|
|
6690
|
+
const lead = ctx.peek();
|
|
6691
|
+
if (!lead || isKeyword(lead, [KEYWORDS.TO, 'from'])) {
|
|
6692
|
+
throw new Error('Transition command requires a CSS property');
|
|
6570
6693
|
}
|
|
6571
|
-
|
|
6694
|
+
const expr = ctx.parseExpression();
|
|
6695
|
+
const split = splitStyleExpression(expr);
|
|
6696
|
+
if (split)
|
|
6697
|
+
return { owner: split.owner, property: propertyNode(split.name, lead, ctx) };
|
|
6698
|
+
let name = '';
|
|
6699
|
+
if (ctx.check('*')) {
|
|
6700
|
+
name = '*';
|
|
6572
6701
|
ctx.advance();
|
|
6573
|
-
const timingFunction = ctx.parsePrimary();
|
|
6574
|
-
modifiers['with'] = timingFunction;
|
|
6575
6702
|
}
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6703
|
+
const hyphenated = parseHyphenatedName(ctx);
|
|
6704
|
+
if (!hyphenated)
|
|
6705
|
+
throw new Error('Transition command requires a CSS property');
|
|
6706
|
+
return { owner: expr, property: propertyNode(name + hyphenated, lead, ctx) };
|
|
6707
|
+
}
|
|
6708
|
+
function splitStyleExpression(node) {
|
|
6709
|
+
const n = node;
|
|
6710
|
+
switch (n.type) {
|
|
6711
|
+
case 'selector':
|
|
6712
|
+
return typeof n.value === 'string' && n.value.startsWith('*')
|
|
6713
|
+
? { owner: null, name: n.value }
|
|
6714
|
+
: null;
|
|
6715
|
+
case 'identifier':
|
|
6716
|
+
return typeof n.name === 'string' && !CONTEXT_REFS.has(n.name)
|
|
6717
|
+
? { owner: null, name: n.name }
|
|
6718
|
+
: null;
|
|
6719
|
+
case 'possessiveExpression':
|
|
6720
|
+
case 'memberExpression':
|
|
6721
|
+
return !n.computed && n.object && typeof n.property?.name === 'string'
|
|
6722
|
+
? { owner: n.object, name: n.property.name }
|
|
6723
|
+
: null;
|
|
6724
|
+
case 'binaryExpression': {
|
|
6725
|
+
if (n.operator !== 'of' || !n.left || !n.right)
|
|
6726
|
+
return null;
|
|
6727
|
+
const inner = splitStyleExpression(n.left);
|
|
6728
|
+
return inner && !inner.owner ? { owner: n.right, name: inner.name } : null;
|
|
6729
|
+
}
|
|
6730
|
+
case 'propertyOfExpression':
|
|
6731
|
+
return n.target && typeof n.property?.name === 'string'
|
|
6732
|
+
? { owner: n.target, name: n.property.name }
|
|
6733
|
+
: null;
|
|
6734
|
+
default:
|
|
6735
|
+
return null;
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
function propertyNode(name, lead, ctx) {
|
|
6739
|
+
return {
|
|
6740
|
+
type: 'string',
|
|
6741
|
+
value: name,
|
|
6742
|
+
start: lead.start || 0,
|
|
6743
|
+
end: ctx.getPosition().end,
|
|
6744
|
+
line: lead.line,
|
|
6745
|
+
column: lead.column,
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
function startsAnotherPair(ctx) {
|
|
6749
|
+
if (ctx.isAtEnd() || isComment(ctx.peek()))
|
|
6750
|
+
return false;
|
|
6751
|
+
if (isKeyword(ctx.peek(), ['over', 'using', KEYWORDS.WITH]))
|
|
6752
|
+
return false;
|
|
6753
|
+
return !isCommandBoundary(ctx, ['catch', 'finally', 'on']);
|
|
6754
|
+
}
|
|
6755
|
+
function pairsNode(pairs) {
|
|
6756
|
+
const field = (name, value) => ({
|
|
6757
|
+
key: {
|
|
6758
|
+
type: 'identifier',
|
|
6759
|
+
name,
|
|
6760
|
+
start: value.start,
|
|
6761
|
+
end: value.end,
|
|
6762
|
+
line: value.line,
|
|
6763
|
+
column: value.column,
|
|
6764
|
+
},
|
|
6765
|
+
value,
|
|
6766
|
+
});
|
|
6767
|
+
const elements = pairs.map(pair => {
|
|
6768
|
+
const properties = [field('property', pair.property), field('to', pair.to)];
|
|
6769
|
+
if (pair.owner)
|
|
6770
|
+
properties.push(field('owner', pair.owner));
|
|
6771
|
+
if (pair.from)
|
|
6772
|
+
properties.push(field('from', pair.from));
|
|
6773
|
+
return {
|
|
6774
|
+
type: 'objectLiteral',
|
|
6775
|
+
properties,
|
|
6776
|
+
start: (pair.owner ?? pair.property).start,
|
|
6777
|
+
end: pair.to.end,
|
|
6778
|
+
line: (pair.owner ?? pair.property).line,
|
|
6779
|
+
column: (pair.owner ?? pair.property).column,
|
|
6780
|
+
};
|
|
6781
|
+
});
|
|
6782
|
+
const firstEl = elements[0];
|
|
6783
|
+
const lastEl = elements[elements.length - 1];
|
|
6784
|
+
return {
|
|
6785
|
+
type: 'arrayLiteral',
|
|
6786
|
+
elements,
|
|
6787
|
+
start: firstEl.start,
|
|
6788
|
+
end: lastEl.end,
|
|
6789
|
+
line: firstEl.line,
|
|
6790
|
+
column: firstEl.column,
|
|
6791
|
+
};
|
|
6581
6792
|
}
|
|
6582
6793
|
function parseStartCommand(ctx, identifierNode) {
|
|
6583
6794
|
if (!ctx.match('view')) {
|
|
@@ -6697,6 +6908,15 @@ function parseToggleCommand(ctx, identifierNode) {
|
|
|
6697
6908
|
args.push(classArg.expression);
|
|
6698
6909
|
modifiers['as'] = literalModifier(mode);
|
|
6699
6910
|
}
|
|
6911
|
+
else if (classArg?.type === 'binaryExpression' &&
|
|
6912
|
+
classArg.operator === 'of' &&
|
|
6913
|
+
classArg.left?.type === 'selector' &&
|
|
6914
|
+
typeof classArg.left.value === 'string' &&
|
|
6915
|
+
classArg.left.value.startsWith('*') &&
|
|
6916
|
+
classArg.right) {
|
|
6917
|
+
args.push(classArg.left);
|
|
6918
|
+
modifiers['on'] = classArg.right;
|
|
6919
|
+
}
|
|
6700
6920
|
else if (classArg) {
|
|
6701
6921
|
args.push(classArg);
|
|
6702
6922
|
}
|
|
@@ -6874,11 +7094,17 @@ function parseSwapCommand(ctx, identifierNode) {
|
|
|
6874
7094
|
const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
6875
7095
|
if (targetExpr)
|
|
6876
7096
|
args.push(targetExpr);
|
|
6877
|
-
|
|
7097
|
+
const isMorph = identifierNode.name === 'morph';
|
|
7098
|
+
if (!isDelete &&
|
|
7099
|
+
(consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
|
|
7100
|
+
(isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
|
|
6878
7101
|
const contentExpr = parseOneArgument(ctx, ['using']);
|
|
6879
7102
|
if (contentExpr)
|
|
6880
7103
|
modifiers['with'] = contentExpr;
|
|
6881
7104
|
}
|
|
7105
|
+
if (isMorph && modifiers['with'] === undefined) {
|
|
7106
|
+
ctx.addError("Expected 'to' and the content to morph into");
|
|
7107
|
+
}
|
|
6882
7108
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
6883
7109
|
.withArgs(...args)
|
|
6884
7110
|
.withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
|
|
@@ -7639,6 +7865,8 @@ function atArgumentBoundary(ctx, grammar) {
|
|
|
7639
7865
|
const next = ctx.peek();
|
|
7640
7866
|
if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
|
|
7641
7867
|
return false;
|
|
7868
|
+
if (isGluedBeepBang(next, ctx.peekAt(1)))
|
|
7869
|
+
return false;
|
|
7642
7870
|
if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
|
|
7643
7871
|
return true;
|
|
7644
7872
|
return isKeyword(next, [...grammar.markers]);
|
|
@@ -7670,10 +7898,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
|
|
|
7670
7898
|
}
|
|
7671
7899
|
}
|
|
7672
7900
|
const commaList = new Set(grammar.commaList ?? []);
|
|
7901
|
+
const namedArgs = new Set(grammar.namedArgs ?? []);
|
|
7673
7902
|
while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
|
|
7674
7903
|
const keyword = ctx.advance().value;
|
|
7675
7904
|
const key = ctx.resolveKeyword(keyword).toLowerCase();
|
|
7676
|
-
let value =
|
|
7905
|
+
let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
|
|
7906
|
+
? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
|
|
7907
|
+
: ctx.parseExpression();
|
|
7677
7908
|
if (value && commaList.has(key) && ctx.check(',')) {
|
|
7678
7909
|
const elements = [value];
|
|
7679
7910
|
while (ctx.match(',')) {
|
|
@@ -7838,8 +8069,8 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7838
8069
|
}
|
|
7839
8070
|
if (ctx.check('with') && !modifiers['with']) {
|
|
7840
8071
|
ctx.advance();
|
|
7841
|
-
if (
|
|
7842
|
-
modifiers['with'] =
|
|
8072
|
+
if (isNakedNamedArgStart(ctx)) {
|
|
8073
|
+
modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
|
|
7843
8074
|
}
|
|
7844
8075
|
else {
|
|
7845
8076
|
modifiers['with'] = ctx.parsePrimary();
|
|
@@ -7874,45 +8105,6 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7874
8105
|
}
|
|
7875
8106
|
return builder.build();
|
|
7876
8107
|
}
|
|
7877
|
-
function isFetchNakedNamedArgStart(ctx) {
|
|
7878
|
-
if (ctx.check('{'))
|
|
7879
|
-
return false;
|
|
7880
|
-
if (!ctx.checkIdentifierLike())
|
|
7881
|
-
return false;
|
|
7882
|
-
const next = ctx.peekAt(1);
|
|
7883
|
-
return next !== null && next.value === ':';
|
|
7884
|
-
}
|
|
7885
|
-
function parseFetchNakedNamedArgs(ctx) {
|
|
7886
|
-
const properties = [];
|
|
7887
|
-
const startPos = ctx.getPosition();
|
|
7888
|
-
do {
|
|
7889
|
-
if (!ctx.checkIdentifierLike())
|
|
7890
|
-
break;
|
|
7891
|
-
const keyToken = ctx.advance();
|
|
7892
|
-
const key = {
|
|
7893
|
-
type: 'identifier',
|
|
7894
|
-
name: keyToken.value,
|
|
7895
|
-
start: keyToken.start,
|
|
7896
|
-
end: keyToken.end,
|
|
7897
|
-
line: keyToken.line,
|
|
7898
|
-
column: keyToken.column,
|
|
7899
|
-
};
|
|
7900
|
-
ctx.consume(':', "Expected ':' after property name in fetch named arguments");
|
|
7901
|
-
const value = ctx.parsePrimary();
|
|
7902
|
-
if (value) {
|
|
7903
|
-
properties.push({ key, value });
|
|
7904
|
-
}
|
|
7905
|
-
} while (ctx.match(',') && !ctx.isAtEnd());
|
|
7906
|
-
const endPos = ctx.getPosition();
|
|
7907
|
-
return {
|
|
7908
|
-
type: 'objectLiteral',
|
|
7909
|
-
properties,
|
|
7910
|
-
start: startPos.start,
|
|
7911
|
-
end: endPos.end,
|
|
7912
|
-
line: startPos.line,
|
|
7913
|
-
column: startPos.column,
|
|
7914
|
-
};
|
|
7915
|
-
}
|
|
7916
8108
|
function findJsEndBoundary(ctx, startPos) {
|
|
7917
8109
|
const input = ctx.getInputSlice(startPos);
|
|
7918
8110
|
if (!input) {
|
|
@@ -8032,6 +8224,7 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8032
8224
|
const commands = [];
|
|
8033
8225
|
while (!ctx.isAtEnd()) {
|
|
8034
8226
|
if (ctx.checkIsCommand()) {
|
|
8227
|
+
const word = ctx.peek().value;
|
|
8035
8228
|
try {
|
|
8036
8229
|
ctx.advance();
|
|
8037
8230
|
const cmd = ctx.parseCommand();
|
|
@@ -8042,7 +8235,8 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8042
8235
|
break;
|
|
8043
8236
|
}
|
|
8044
8237
|
}
|
|
8045
|
-
catch {
|
|
8238
|
+
catch (error) {
|
|
8239
|
+
ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
8046
8240
|
break;
|
|
8047
8241
|
}
|
|
8048
8242
|
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
@@ -8191,7 +8385,6 @@ const ONE_EXPR = (syntax) => ({
|
|
|
8191
8385
|
markers: [],
|
|
8192
8386
|
syntax,
|
|
8193
8387
|
});
|
|
8194
|
-
const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
|
|
8195
8388
|
const COMMAND_GRAMMAR = {
|
|
8196
8389
|
append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
|
|
8197
8390
|
prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
|
|
@@ -8202,8 +8395,7 @@ const COMMAND_GRAMMAR = {
|
|
|
8202
8395
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
8203
8396
|
},
|
|
8204
8397
|
throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
|
|
8205
|
-
'beep!'
|
|
8206
|
-
beep: BEEP,
|
|
8398
|
+
beep: ONE_EXPR('beep! [<expression>, \u2026]'),
|
|
8207
8399
|
blur: ONE_EXPR('blur [<target>]'),
|
|
8208
8400
|
break: NONE,
|
|
8209
8401
|
breakpoint: NONE,
|
|
@@ -8227,7 +8419,8 @@ const COMMAND_GRAMMAR = {
|
|
|
8227
8419
|
render: {
|
|
8228
8420
|
positional: 'expression',
|
|
8229
8421
|
markers: ['with'],
|
|
8230
|
-
|
|
8422
|
+
namedArgs: ['with'],
|
|
8423
|
+
syntax: 'render <template> [with <name>: <value>, \u2026]',
|
|
8231
8424
|
},
|
|
8232
8425
|
reset: ONE_EXPR('reset [<form>]'),
|
|
8233
8426
|
return: ONE_EXPR('return [<value>]'),
|
|
@@ -8582,7 +8775,10 @@ class Parser {
|
|
|
8582
8775
|
const firstToken = this.peek();
|
|
8583
8776
|
const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
|
|
8584
8777
|
let left;
|
|
8585
|
-
if (
|
|
8778
|
+
if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
|
|
8779
|
+
left = this.parseBeepExpression();
|
|
8780
|
+
}
|
|
8781
|
+
else if (prefixEntry?.prefix) {
|
|
8586
8782
|
const token = this.advance();
|
|
8587
8783
|
if (this.isAtEnd()) {
|
|
8588
8784
|
this.addError(`Expected expression after '${token.value}' operator`);
|
|
@@ -8602,6 +8798,9 @@ class Parser {
|
|
|
8602
8798
|
continue;
|
|
8603
8799
|
}
|
|
8604
8800
|
}
|
|
8801
|
+
if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
|
|
8802
|
+
break;
|
|
8803
|
+
}
|
|
8605
8804
|
const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
|
|
8606
8805
|
if (!infixEntry?.infix) {
|
|
8607
8806
|
if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
|
|
@@ -8663,6 +8862,35 @@ class Parser {
|
|
|
8663
8862
|
this.prattCache.set(cacheKey, { node: left, endPos: this.current });
|
|
8664
8863
|
return left;
|
|
8665
8864
|
}
|
|
8865
|
+
parseBeepExpression() {
|
|
8866
|
+
const beepToken = this.advance();
|
|
8867
|
+
this.advance();
|
|
8868
|
+
if (this.isAtEnd()) {
|
|
8869
|
+
this.addError("Expected expression after 'beep!'");
|
|
8870
|
+
return this.createErrorNode();
|
|
8871
|
+
}
|
|
8872
|
+
const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
|
|
8873
|
+
return {
|
|
8874
|
+
type: 'unaryExpression',
|
|
8875
|
+
operator: 'beep!',
|
|
8876
|
+
operand,
|
|
8877
|
+
argument: operand,
|
|
8878
|
+
prefix: true,
|
|
8879
|
+
start: beepToken.start,
|
|
8880
|
+
end: this.previous().end,
|
|
8881
|
+
line: beepToken.line,
|
|
8882
|
+
column: beepToken.column,
|
|
8883
|
+
};
|
|
8884
|
+
}
|
|
8885
|
+
isStyleRefStar(i) {
|
|
8886
|
+
const star = this.tokens[i];
|
|
8887
|
+
const name = this.tokens[i + 1];
|
|
8888
|
+
return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
|
|
8889
|
+
}
|
|
8890
|
+
isSpacedStyleRef(i) {
|
|
8891
|
+
const prev = this.tokens[i - 1];
|
|
8892
|
+
return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
|
|
8893
|
+
}
|
|
8666
8894
|
tryParseStringPostfix(left, nextToken) {
|
|
8667
8895
|
const v = nextToken.value;
|
|
8668
8896
|
let unit = null;
|
|
@@ -8865,6 +9093,10 @@ class Parser {
|
|
|
8865
9093
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
8866
9094
|
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
8867
9095
|
while (!this.isAtEnd() && !isStop()) {
|
|
9096
|
+
if (this.checkComment()) {
|
|
9097
|
+
this.advance();
|
|
9098
|
+
continue;
|
|
9099
|
+
}
|
|
8868
9100
|
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
8869
9101
|
let parsedCommand = false;
|
|
8870
9102
|
const isCommandToken = this.checkIsCommand();
|
|
@@ -8948,7 +9180,7 @@ class Parser {
|
|
|
8948
9180
|
debug.parse('\u2705 Found "end", consuming it');
|
|
8949
9181
|
this.advance();
|
|
8950
9182
|
}
|
|
8951
|
-
else {
|
|
9183
|
+
else if (!this.atEndOfInput()) {
|
|
8952
9184
|
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
8953
9185
|
throw new Error(`Expected "end" to close ${construct} block`);
|
|
8954
9186
|
}
|
|
@@ -8961,7 +9193,7 @@ class Parser {
|
|
|
8961
9193
|
if (this.check('end')) {
|
|
8962
9194
|
this.advance();
|
|
8963
9195
|
}
|
|
8964
|
-
else {
|
|
9196
|
+
else if (!this.atEndOfInput()) {
|
|
8965
9197
|
throw new Error('Expected "end" to close repeat block');
|
|
8966
9198
|
}
|
|
8967
9199
|
}
|
|
@@ -8977,7 +9209,10 @@ class Parser {
|
|
|
8977
9209
|
this.advance();
|
|
8978
9210
|
}
|
|
8979
9211
|
else if (terminator === '') {
|
|
8980
|
-
|
|
9212
|
+
if (!this.atEndOfInput()) {
|
|
9213
|
+
throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
|
|
9214
|
+
}
|
|
9215
|
+
return { commands, terminator: 'end' };
|
|
8981
9216
|
}
|
|
8982
9217
|
return { commands, terminator };
|
|
8983
9218
|
}
|
|
@@ -9561,7 +9796,9 @@ class Parser {
|
|
|
9561
9796
|
parseTopLevelInitBlock() {
|
|
9562
9797
|
const pos = this.getPosition();
|
|
9563
9798
|
const initCommands = this.parseCommandBlock(['end']);
|
|
9564
|
-
this.
|
|
9799
|
+
if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
|
|
9800
|
+
this.consume('end', "Expected 'end' after init block");
|
|
9801
|
+
}
|
|
9565
9802
|
return {
|
|
9566
9803
|
type: 'initBlock',
|
|
9567
9804
|
commands: initCommands,
|
|
@@ -9737,10 +9974,29 @@ class Parser {
|
|
|
9737
9974
|
...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
|
|
9738
9975
|
};
|
|
9739
9976
|
}
|
|
9977
|
+
isArgumentListOnly(open) {
|
|
9978
|
+
let depth = 0;
|
|
9979
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
9980
|
+
const token = this.tokens[i];
|
|
9981
|
+
if (token.kind !== 'operator')
|
|
9982
|
+
continue;
|
|
9983
|
+
if (token.value === '(' || token.value === '[' || token.value === '{')
|
|
9984
|
+
depth++;
|
|
9985
|
+
else if (token.value === ')' || token.value === ']' || token.value === '}') {
|
|
9986
|
+
if (--depth === 0)
|
|
9987
|
+
return i === open + 1;
|
|
9988
|
+
}
|
|
9989
|
+
else if (token.value === ',' && depth === 1)
|
|
9990
|
+
return true;
|
|
9991
|
+
}
|
|
9992
|
+
return false;
|
|
9993
|
+
}
|
|
9740
9994
|
tryParsePseudoCommand(callExpr, sourceExpr) {
|
|
9741
9995
|
const methodName = callExpr.callee.name;
|
|
9742
9996
|
const nextToken = this.peek();
|
|
9743
|
-
const
|
|
9997
|
+
const nextIsArticle = nextToken.value.toLowerCase() === 'the';
|
|
9998
|
+
const hasPseudoCommandPattern = nextIsArticle ||
|
|
9999
|
+
Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
|
|
9744
10000
|
(isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
|
|
9745
10001
|
isContextVar(nextToken);
|
|
9746
10002
|
if (!hasPseudoCommandPattern) {
|
|
@@ -9766,7 +10022,10 @@ class Parser {
|
|
|
9766
10022
|
});
|
|
9767
10023
|
}
|
|
9768
10024
|
let preposition;
|
|
9769
|
-
if (
|
|
10025
|
+
if (nextIsArticle) {
|
|
10026
|
+
this.advance();
|
|
10027
|
+
}
|
|
10028
|
+
else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
|
|
9770
10029
|
preposition = this.advance().value.toLowerCase();
|
|
9771
10030
|
}
|
|
9772
10031
|
let targetExpr;
|
|
@@ -9930,10 +10189,22 @@ class Parser {
|
|
|
9930
10189
|
this.consume(']', "Expected ']' after event condition");
|
|
9931
10190
|
}
|
|
9932
10191
|
let target;
|
|
10192
|
+
let targetExpression;
|
|
9933
10193
|
if (this.match('from')) {
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
10194
|
+
if (this.check('the'))
|
|
10195
|
+
this.advance();
|
|
10196
|
+
if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
|
|
10197
|
+
const startIndex = this.current;
|
|
10198
|
+
targetExpression = this.parseExpression();
|
|
10199
|
+
target = this.tokens
|
|
10200
|
+
.slice(startIndex, this.current)
|
|
10201
|
+
.map(t => t.value)
|
|
10202
|
+
.join(' ');
|
|
10203
|
+
}
|
|
10204
|
+
else {
|
|
10205
|
+
target = this.advance().value;
|
|
10206
|
+
}
|
|
10207
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
|
|
9937
10208
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
9938
10209
|
}
|
|
9939
10210
|
let attributeName;
|
|
@@ -9979,7 +10250,7 @@ class Parser {
|
|
|
9979
10250
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
9980
10251
|
const commandName = this.peek().value.toLowerCase();
|
|
9981
10252
|
const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
|
|
9982
|
-
if (nextIsOpenParen && !isSpecialBodyCommand) {
|
|
10253
|
+
if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
|
|
9983
10254
|
let expr;
|
|
9984
10255
|
const savedError = this.error;
|
|
9985
10256
|
try {
|
|
@@ -10011,7 +10282,7 @@ class Parser {
|
|
|
10011
10282
|
}
|
|
10012
10283
|
}
|
|
10013
10284
|
}
|
|
10014
|
-
else if (this.checkIdentifier()) {
|
|
10285
|
+
else if (this.checkIdentifier() || this.checkEventNamedCall()) {
|
|
10015
10286
|
const token = this.peek();
|
|
10016
10287
|
if (this.isCommand(token.value)) {
|
|
10017
10288
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
@@ -10120,6 +10391,7 @@ class Parser {
|
|
|
10120
10391
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
10121
10392
|
...(condition && { condition: fromLegacyExpression(condition) }),
|
|
10122
10393
|
...(target && { target }),
|
|
10394
|
+
...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
|
|
10123
10395
|
...(attributeName && { attributeName }),
|
|
10124
10396
|
...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
|
|
10125
10397
|
...(customEventSource && { customEventSource }),
|
|
@@ -10375,10 +10647,9 @@ class Parser {
|
|
|
10375
10647
|
}
|
|
10376
10648
|
parseCommandCore() {
|
|
10377
10649
|
const commandToken = this.previous();
|
|
10378
|
-
|
|
10650
|
+
const commandName = commandToken.value;
|
|
10379
10651
|
if (commandName === 'beep' && this.check('!')) {
|
|
10380
10652
|
this.advance();
|
|
10381
|
-
commandName = 'beep!';
|
|
10382
10653
|
}
|
|
10383
10654
|
if (commandName === 'fetch') {
|
|
10384
10655
|
return parseFetchCommand(this.getContext(), commandToken);
|
|
@@ -10477,6 +10748,9 @@ class Parser {
|
|
|
10477
10748
|
args.push(this.parsePrimary());
|
|
10478
10749
|
}
|
|
10479
10750
|
const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
|
|
10751
|
+
if (funcName === 'closest' && args.length > 0 && this.match('to')) {
|
|
10752
|
+
callNode.closestTo = this.parseExpression();
|
|
10753
|
+
}
|
|
10480
10754
|
if (funcName === 'next' || funcName === 'previous') {
|
|
10481
10755
|
const relativePositional = this.parseRelativePositionalModifiers();
|
|
10482
10756
|
if (relativePositional) {
|
|
@@ -10516,6 +10790,9 @@ class Parser {
|
|
|
10516
10790
|
}
|
|
10517
10791
|
parseTheXofY() {
|
|
10518
10792
|
const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
|
|
10793
|
+
if (this.isStyleRefStar(this.current)) {
|
|
10794
|
+
return this.parsePrimary();
|
|
10795
|
+
}
|
|
10519
10796
|
if (!this.checkIdentifier()) {
|
|
10520
10797
|
return this.createIdentifier('the');
|
|
10521
10798
|
}
|
|
@@ -10531,7 +10808,7 @@ class Parser {
|
|
|
10531
10808
|
return this.createIdentifier(propertyName);
|
|
10532
10809
|
}
|
|
10533
10810
|
this.advance();
|
|
10534
|
-
const target = this.
|
|
10811
|
+
const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
|
|
10535
10812
|
return {
|
|
10536
10813
|
type: 'propertyOfExpression',
|
|
10537
10814
|
property: { type: 'identifier', name: propertyName },
|
|
@@ -10729,6 +11006,13 @@ class Parser {
|
|
|
10729
11006
|
checkLiteral() {
|
|
10730
11007
|
return this.checkPredicate(isLiteral);
|
|
10731
11008
|
}
|
|
11009
|
+
checkEventNamedCall() {
|
|
11010
|
+
if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
|
|
11011
|
+
return false;
|
|
11012
|
+
const token = this.peek();
|
|
11013
|
+
const next = this.tokens[this.current + 1];
|
|
11014
|
+
return next?.value === '(' && next.start === token.end;
|
|
11015
|
+
}
|
|
10732
11016
|
checkIdentifierLike() {
|
|
10733
11017
|
return this.checkPredicate(isIdentifierLike);
|
|
10734
11018
|
}
|
|
@@ -10837,6 +11121,22 @@ class Parser {
|
|
|
10837
11121
|
isAtEnd() {
|
|
10838
11122
|
return this.current >= this.tokens.length;
|
|
10839
11123
|
}
|
|
11124
|
+
atEndOfInput() {
|
|
11125
|
+
for (let i = this.current; i < this.tokens.length; i++) {
|
|
11126
|
+
if (!isComment(this.tokens[i]))
|
|
11127
|
+
return false;
|
|
11128
|
+
}
|
|
11129
|
+
return true;
|
|
11130
|
+
}
|
|
11131
|
+
checkFeatureStart() {
|
|
11132
|
+
if (this.isAtEnd())
|
|
11133
|
+
return false;
|
|
11134
|
+
const value = this.peek().value;
|
|
11135
|
+
return (value === 'init' ||
|
|
11136
|
+
value === 'on' ||
|
|
11137
|
+
value === 'def' ||
|
|
11138
|
+
getRegisteredFeature(value) !== undefined);
|
|
11139
|
+
}
|
|
10840
11140
|
peek() {
|
|
10841
11141
|
if (this.isAtEnd()) {
|
|
10842
11142
|
return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
|
|
@@ -11048,6 +11348,7 @@ class Parser {
|
|
|
11048
11348
|
match: this.match.bind(this),
|
|
11049
11349
|
matchOperator: this.matchOperator.bind(this),
|
|
11050
11350
|
isAtEnd: this.isAtEnd.bind(this),
|
|
11351
|
+
atEndOfInput: this.atEndOfInput.bind(this),
|
|
11051
11352
|
createIdentifier: this.createIdentifier.bind(this),
|
|
11052
11353
|
parseExpression: this.parseExpression.bind(this),
|
|
11053
11354
|
parsePrimary: this.parsePrimary.bind(this),
|
|
@@ -11098,7 +11399,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
|
|
|
11098
11399
|
'ignoring case',
|
|
11099
11400
|
]);
|
|
11100
11401
|
Parser.PRATT_TABLE = PARSER_TABLE;
|
|
11402
|
+
Parser.BEEP_OPERAND_BP = 80;
|
|
11101
11403
|
Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
|
|
11404
|
+
Parser.POSITIONAL_SOURCE_HEADS = new Set([
|
|
11405
|
+
'closest',
|
|
11406
|
+
'next',
|
|
11407
|
+
'previous',
|
|
11408
|
+
'first',
|
|
11409
|
+
'last',
|
|
11410
|
+
]);
|
|
11102
11411
|
function parse(input, options) {
|
|
11103
11412
|
const tokens = tokenize(input);
|
|
11104
11413
|
const parser = new Parser(tokens, options, input);
|
|
@@ -11540,11 +11849,16 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11540
11849
|
}
|
|
11541
11850
|
}
|
|
11542
11851
|
}
|
|
11543
|
-
if (leftNode?.type === 'selector' &&
|
|
11852
|
+
if (leftNode?.type === 'selector' &&
|
|
11853
|
+
typeof leftNode.value === 'string' &&
|
|
11854
|
+
(leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
|
|
11544
11855
|
const root = await evaluateAST(node.right, context);
|
|
11545
11856
|
const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
|
|
11546
11857
|
if (scope) {
|
|
11547
|
-
|
|
11858
|
+
const query = leftNode.fromQuery
|
|
11859
|
+
? leftNode.value
|
|
11860
|
+
: escapeSelectorForQuery(leftNode, leftNode.value);
|
|
11861
|
+
return Array.from(scope.querySelectorAll(query));
|
|
11548
11862
|
}
|
|
11549
11863
|
}
|
|
11550
11864
|
}
|
|
@@ -11556,6 +11870,12 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11556
11870
|
return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
|
|
11557
11871
|
}
|
|
11558
11872
|
if (operator === 'of') {
|
|
11873
|
+
if (leftNode?.type === 'attributeAccess') {
|
|
11874
|
+
const target = await evaluateAST(node.right, context);
|
|
11875
|
+
if (target == null)
|
|
11876
|
+
return undefined;
|
|
11877
|
+
return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
|
|
11878
|
+
}
|
|
11559
11879
|
const path = propertyPathOf(node.left);
|
|
11560
11880
|
if (path) {
|
|
11561
11881
|
const target = await evaluateAST(node.right, context);
|
|
@@ -11563,6 +11883,10 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11563
11883
|
for (const seg of path) {
|
|
11564
11884
|
if (cur == null)
|
|
11565
11885
|
return undefined;
|
|
11886
|
+
if (isMappableCollection(cur) && !(seg in cur)) {
|
|
11887
|
+
cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
|
|
11888
|
+
continue;
|
|
11889
|
+
}
|
|
11566
11890
|
cur = isElement(cur) ? getElementProperty(cur, seg) : cur[seg];
|
|
11567
11891
|
}
|
|
11568
11892
|
return typeof cur === 'function' ? cur.bind(target) : cur;
|
|
@@ -11587,26 +11911,21 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11587
11911
|
const R = node.ignoringCase ? applyCI(right) : right;
|
|
11588
11912
|
switch (operator) {
|
|
11589
11913
|
case '+': {
|
|
11590
|
-
let addLeft = left;
|
|
11591
|
-
let addRight = right;
|
|
11592
11914
|
if (node.coerceNumeric) {
|
|
11593
|
-
|
|
11594
|
-
addLeft = convertToNumber(addLeft);
|
|
11595
|
-
if (typeof addRight === 'string')
|
|
11596
|
-
addRight = convertToNumber(addRight);
|
|
11915
|
+
return counterValue(left) + counterAmount(right);
|
|
11597
11916
|
}
|
|
11598
|
-
if (typeof
|
|
11599
|
-
return String(
|
|
11917
|
+
if (typeof left === 'string' || typeof right === 'string') {
|
|
11918
|
+
return String(left ?? '') + String(right ?? '');
|
|
11600
11919
|
}
|
|
11601
|
-
if (Array.isArray(
|
|
11602
|
-
return Array.isArray(
|
|
11920
|
+
if (Array.isArray(left)) {
|
|
11921
|
+
return Array.isArray(right) ? [...left, ...right] : [...left, right];
|
|
11603
11922
|
}
|
|
11604
|
-
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
|
|
11605
|
-
left: addLeft,
|
|
11606
|
-
right: addRight,
|
|
11607
|
-
}));
|
|
11923
|
+
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
|
|
11608
11924
|
}
|
|
11609
11925
|
case '-':
|
|
11926
|
+
if (node.coerceNumeric) {
|
|
11927
|
+
return counterValue(left) - counterAmount(right);
|
|
11928
|
+
}
|
|
11610
11929
|
return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
|
|
11611
11930
|
case '*':
|
|
11612
11931
|
return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
|
|
@@ -11864,9 +12183,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
|
|
|
11864
12183
|
}
|
|
11865
12184
|
const propertyName = propertyNode.name;
|
|
11866
12185
|
const target = await evaluateAST(node.target, context);
|
|
11867
|
-
if (target == null)
|
|
11868
|
-
|
|
11869
|
-
}
|
|
12186
|
+
if (target == null)
|
|
12187
|
+
return undefined;
|
|
11870
12188
|
return getExpr(context, 'possessive').evaluate(context, target, propertyName);
|
|
11871
12189
|
}
|
|
11872
12190
|
async function evaluateTemplateLiteralNode(node, context) {
|
|
@@ -11984,6 +12302,9 @@ async function evaluateUnaryExpression(node, context) {
|
|
|
11984
12302
|
case 'not':
|
|
11985
12303
|
case '!':
|
|
11986
12304
|
return logicalExpressions.not.evaluate(context, value);
|
|
12305
|
+
case 'beep!':
|
|
12306
|
+
beepValues(context.me, [value]);
|
|
12307
|
+
return value;
|
|
11987
12308
|
case 'no':
|
|
11988
12309
|
return logicalExpressions.no.evaluate(context, value);
|
|
11989
12310
|
case '-':
|
|
@@ -12073,6 +12394,10 @@ async function evaluateCallExpression(node, context) {
|
|
|
12073
12394
|
const args = await resolveCallArgs(node.arguments, funcName, context);
|
|
12074
12395
|
switch (funcName) {
|
|
12075
12396
|
case 'closest':
|
|
12397
|
+
if (node.closestTo) {
|
|
12398
|
+
const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
|
|
12399
|
+
return referencesExpressions.closest.evaluate(context, args[0], start);
|
|
12400
|
+
}
|
|
12076
12401
|
return referencesExpressions.closest.evaluate(context, ...args);
|
|
12077
12402
|
case 'previous':
|
|
12078
12403
|
case 'next': {
|