@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.mjs
CHANGED
|
@@ -897,6 +897,39 @@ function makeTypeCheckHandler(negated) {
|
|
|
897
897
|
};
|
|
898
898
|
};
|
|
899
899
|
}
|
|
900
|
+
const OF_OPERAND_BP = 70;
|
|
901
|
+
function parseOfOperand(ctx, minBp = OF_OPERAND_BP) {
|
|
902
|
+
let right = ctx.parseExpr(minBp);
|
|
903
|
+
if (ctx.peek()?.value === 'in') {
|
|
904
|
+
const inToken = ctx.advance();
|
|
905
|
+
const scope = ctx.parseExpr(minBp);
|
|
906
|
+
right = {
|
|
907
|
+
type: 'binaryExpression',
|
|
908
|
+
operator: 'in',
|
|
909
|
+
left: right,
|
|
910
|
+
right: scope,
|
|
911
|
+
start: right.start,
|
|
912
|
+
end: scope.end ?? inToken.end,
|
|
913
|
+
line: right.line ?? inToken.line,
|
|
914
|
+
column: right.column ?? inToken.column,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
return right;
|
|
918
|
+
}
|
|
919
|
+
const THE_OF_TARGET_BP = OF_OPERAND_BP + 1;
|
|
920
|
+
function ofHandler(left, token, ctx) {
|
|
921
|
+
const right = parseOfOperand(ctx);
|
|
922
|
+
return {
|
|
923
|
+
type: 'binaryExpression',
|
|
924
|
+
operator: token.value,
|
|
925
|
+
left,
|
|
926
|
+
right,
|
|
927
|
+
start: left.start,
|
|
928
|
+
end: right.end ?? token.end,
|
|
929
|
+
line: left.line ?? token.line,
|
|
930
|
+
column: left.column ?? token.column,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
900
933
|
const PARSER_COMPARISON_FRAGMENT = new Map([
|
|
901
934
|
['===', leftAssoc(30)],
|
|
902
935
|
['!==', leftAssoc(30)],
|
|
@@ -941,7 +974,7 @@ const PARSER_COMPARISON_FRAGMENT = new Map([
|
|
|
941
974
|
['is greater than or equal to', leftAssoc(30)],
|
|
942
975
|
['is less than or equal to', leftAssoc(30)],
|
|
943
976
|
['in', leftAssoc(30)],
|
|
944
|
-
['of',
|
|
977
|
+
['of', { infix: { bp: [90, OF_OPERAND_BP], handler: ofHandler } }],
|
|
945
978
|
['really', leftAssoc(30)],
|
|
946
979
|
[
|
|
947
980
|
'exists',
|
|
@@ -1117,36 +1150,108 @@ function setElementVar(context, name, value) {
|
|
|
1117
1150
|
notifyLocalWrite(name, value, context);
|
|
1118
1151
|
}
|
|
1119
1152
|
|
|
1120
|
-
function
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1153
|
+
function isHTMLElement$2(value) {
|
|
1154
|
+
return (value !== null &&
|
|
1155
|
+
typeof value === 'object' &&
|
|
1156
|
+
'nodeType' in value &&
|
|
1157
|
+
value.nodeType === 1 &&
|
|
1158
|
+
'tagName' in value &&
|
|
1159
|
+
typeof value.tagName === 'string' &&
|
|
1160
|
+
'classList' in value);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function beepValues(me, values) {
|
|
1164
|
+
const outputs = values.map(describeBeepValue);
|
|
1165
|
+
const shown = outputs.filter(output => announce(me, output.value));
|
|
1166
|
+
if (shown.length === 0)
|
|
1167
|
+
return outputs;
|
|
1168
|
+
console.group('\ud83d\udd14 beep! Debug Output');
|
|
1169
|
+
for (const output of shown) {
|
|
1170
|
+
console.log(`Value:`, output.value);
|
|
1171
|
+
console.log(`Type:`, output.type);
|
|
1172
|
+
console.log(`Representation:`, output.representation);
|
|
1173
|
+
console.log('---');
|
|
1174
|
+
}
|
|
1175
|
+
console.groupEnd();
|
|
1176
|
+
return outputs;
|
|
1177
|
+
}
|
|
1178
|
+
function announce(me, value) {
|
|
1179
|
+
const target = me;
|
|
1180
|
+
if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
|
|
1181
|
+
return true;
|
|
1182
|
+
return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
|
|
1183
|
+
bubbles: true,
|
|
1184
|
+
cancelable: true,
|
|
1185
|
+
detail: { element: me, value },
|
|
1186
|
+
}));
|
|
1187
|
+
}
|
|
1188
|
+
function describeBeepValue(value) {
|
|
1189
|
+
return { value, type: typeOf(value), representation: representationOf(value) };
|
|
1190
|
+
}
|
|
1191
|
+
function typeOf(value) {
|
|
1192
|
+
if (value === null)
|
|
1193
|
+
return 'null';
|
|
1194
|
+
if (value === undefined)
|
|
1195
|
+
return 'undefined';
|
|
1196
|
+
if (Array.isArray(value))
|
|
1197
|
+
return 'array';
|
|
1198
|
+
if (isHTMLElement$2(value))
|
|
1199
|
+
return 'HTMLElement';
|
|
1200
|
+
if (typeof Element !== 'undefined' && value instanceof Element)
|
|
1201
|
+
return 'Element';
|
|
1202
|
+
if (typeof Node !== 'undefined' && value instanceof Node)
|
|
1203
|
+
return 'Node';
|
|
1204
|
+
if (value instanceof Error)
|
|
1205
|
+
return 'Error';
|
|
1206
|
+
if (value instanceof Date)
|
|
1207
|
+
return 'Date';
|
|
1208
|
+
if (value instanceof RegExp)
|
|
1209
|
+
return 'RegExp';
|
|
1210
|
+
return typeof value;
|
|
1211
|
+
}
|
|
1212
|
+
function representationOf(value) {
|
|
1213
|
+
if (value === null)
|
|
1214
|
+
return 'null';
|
|
1215
|
+
if (value === undefined)
|
|
1216
|
+
return 'undefined';
|
|
1134
1217
|
if (Array.isArray(value)) {
|
|
1135
|
-
return value.length
|
|
1136
|
-
|
|
1218
|
+
return `Array(${value.length}) [${value
|
|
1219
|
+
.slice(0, 3)
|
|
1220
|
+
.map(v => representationOf(v))
|
|
1221
|
+
.join(', ')}${value.length > 3 ? '...' : ''}]`;
|
|
1222
|
+
}
|
|
1223
|
+
if (isHTMLElement$2(value)) {
|
|
1224
|
+
const el = value;
|
|
1225
|
+
const tag = el.tagName.toLowerCase();
|
|
1226
|
+
const id = el.id ? `#${el.id}` : '';
|
|
1227
|
+
const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
|
|
1228
|
+
return `<${tag}${id}${classes}/>`;
|
|
1229
|
+
}
|
|
1230
|
+
if (value instanceof Error)
|
|
1231
|
+
return `Error: ${value.message}`;
|
|
1232
|
+
if (typeof value === 'string')
|
|
1233
|
+
return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
|
|
1137
1234
|
if (typeof value === 'object') {
|
|
1138
|
-
|
|
1139
|
-
|
|
1235
|
+
try {
|
|
1236
|
+
const keys = Object.keys(value);
|
|
1237
|
+
return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
|
|
1140
1238
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
if (typeof result === 'number') {
|
|
1144
|
-
return result;
|
|
1145
|
-
}
|
|
1239
|
+
catch {
|
|
1240
|
+
return '[Object]';
|
|
1146
1241
|
}
|
|
1147
|
-
return NaN;
|
|
1148
1242
|
}
|
|
1149
|
-
return
|
|
1243
|
+
return String(value);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
function counterAmount(value) {
|
|
1247
|
+
return typeof value === 'number' ? value : parseFloat(String(value));
|
|
1248
|
+
}
|
|
1249
|
+
function counterValue(value) {
|
|
1250
|
+
if (isHTMLElement$2(value)) {
|
|
1251
|
+
const element = value;
|
|
1252
|
+
return counterValue(element.value || element.textContent);
|
|
1253
|
+
}
|
|
1254
|
+
return value ? counterAmount(value) : 0;
|
|
1150
1255
|
}
|
|
1151
1256
|
|
|
1152
1257
|
function resolveTargetElements(matches, selector, fromQuery) {
|
|
@@ -1512,13 +1617,6 @@ function isFunction(value) {
|
|
|
1512
1617
|
function isInputElement(el) {
|
|
1513
1618
|
return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
|
|
1514
1619
|
}
|
|
1515
|
-
function isFormElement(el) {
|
|
1516
|
-
if (el === null || typeof el !== 'object' || !('tagName' in el)) {
|
|
1517
|
-
return false;
|
|
1518
|
-
}
|
|
1519
|
-
const tag = el.tagName;
|
|
1520
|
-
return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
|
|
1521
|
-
}
|
|
1522
1620
|
function isHTMLElement$1(el) {
|
|
1523
1621
|
return (el !== null &&
|
|
1524
1622
|
typeof el === 'object' &&
|
|
@@ -1578,7 +1676,7 @@ const SPECIAL_DOM_PROPERTIES = {
|
|
|
1578
1676
|
innertext: el => el.textContent?.trim(),
|
|
1579
1677
|
innerHTML: el => el.innerHTML,
|
|
1580
1678
|
outerhtml: el => el.outerHTML,
|
|
1581
|
-
value: el => (
|
|
1679
|
+
value: el => ('value' in el ? el.value : undefined),
|
|
1582
1680
|
checked: el => (isInputElement(el) ? el.checked : undefined),
|
|
1583
1681
|
disabled: el => ('disabled' in el ? el.disabled : undefined),
|
|
1584
1682
|
selected: el => (isOptionElement(el) ? el.selected : undefined),
|
|
@@ -2246,14 +2344,15 @@ const closestExpression = {
|
|
|
2246
2344
|
if (typeof selector !== 'string') {
|
|
2247
2345
|
throw new Error('closest requires a string selector');
|
|
2248
2346
|
}
|
|
2249
|
-
|
|
2347
|
+
const start = args.length > 1 ? args[1] : context.me;
|
|
2348
|
+
if (!(start instanceof Element)) {
|
|
2250
2349
|
return null;
|
|
2251
2350
|
}
|
|
2252
|
-
return
|
|
2351
|
+
return start.closest(selector);
|
|
2253
2352
|
},
|
|
2254
2353
|
validate(args) {
|
|
2255
|
-
if (args.length !== 1) {
|
|
2256
|
-
return 'closest requires
|
|
2354
|
+
if (args.length !== 1 && args.length !== 2) {
|
|
2355
|
+
return 'closest requires a selector and, optionally, the element to start from';
|
|
2257
2356
|
}
|
|
2258
2357
|
const arg = args[0];
|
|
2259
2358
|
if (typeof arg === 'string')
|
|
@@ -4639,7 +4738,7 @@ const isEmptyExpression = {
|
|
|
4639
4738
|
else if (value instanceof Node || value instanceof Element)
|
|
4640
4739
|
result = false;
|
|
4641
4740
|
else if (isObject(value))
|
|
4642
|
-
result =
|
|
4741
|
+
result = value.length === 0;
|
|
4643
4742
|
else
|
|
4644
4743
|
result = false;
|
|
4645
4744
|
return result;
|
|
@@ -5716,6 +5815,48 @@ function parseMaybeNamedArgument(ctx) {
|
|
|
5716
5815
|
const value = ctx.parseExpression();
|
|
5717
5816
|
return name !== undefined ? { name, value } : { value };
|
|
5718
5817
|
}
|
|
5818
|
+
function isNakedNamedArgStart(ctx) {
|
|
5819
|
+
if (ctx.check('{'))
|
|
5820
|
+
return false;
|
|
5821
|
+
if (!ctx.checkIdentifierLike())
|
|
5822
|
+
return false;
|
|
5823
|
+
const next = ctx.peekAt(1);
|
|
5824
|
+
return next !== null && next.value === ':';
|
|
5825
|
+
}
|
|
5826
|
+
function parseNakedNamedArgs(ctx, parseValue, command) {
|
|
5827
|
+
const properties = [];
|
|
5828
|
+
const startPos = ctx.getPosition();
|
|
5829
|
+
do {
|
|
5830
|
+
if (!ctx.checkIdentifierLike())
|
|
5831
|
+
break;
|
|
5832
|
+
const keyToken = ctx.advance();
|
|
5833
|
+
const key = {
|
|
5834
|
+
type: 'identifier',
|
|
5835
|
+
name: keyToken.value,
|
|
5836
|
+
start: keyToken.start,
|
|
5837
|
+
end: keyToken.end,
|
|
5838
|
+
line: keyToken.line,
|
|
5839
|
+
column: keyToken.column,
|
|
5840
|
+
};
|
|
5841
|
+
ctx.consume(':', `Expected ':' after property name in ${command} named arguments`);
|
|
5842
|
+
const value = parseValue();
|
|
5843
|
+
if (value) {
|
|
5844
|
+
properties.push({ key, value });
|
|
5845
|
+
}
|
|
5846
|
+
} while (ctx.match(',') && !ctx.isAtEnd());
|
|
5847
|
+
const endPos = ctx.getPosition();
|
|
5848
|
+
return {
|
|
5849
|
+
type: 'objectLiteral',
|
|
5850
|
+
properties,
|
|
5851
|
+
start: startPos.start,
|
|
5852
|
+
end: endPos.end,
|
|
5853
|
+
line: startPos.line,
|
|
5854
|
+
column: startPos.column,
|
|
5855
|
+
};
|
|
5856
|
+
}
|
|
5857
|
+
function isGluedBeepBang(beep, bang) {
|
|
5858
|
+
return beep?.value === 'beep' && bang?.value === '!' && bang.start === beep.end;
|
|
5859
|
+
}
|
|
5719
5860
|
|
|
5720
5861
|
class CommandNodeBuilder {
|
|
5721
5862
|
constructor(name) {
|
|
@@ -5917,6 +6058,8 @@ function parseTriggerCommand(ctx, identifierNode) {
|
|
|
5917
6058
|
const modifiers = {};
|
|
5918
6059
|
while (!isCommandBoundary(ctx)) {
|
|
5919
6060
|
if (ctx.check(KEYWORDS.ON) || ctx.check(KEYWORDS.TO)) {
|
|
6061
|
+
if (modifiers['on'])
|
|
6062
|
+
break;
|
|
5920
6063
|
ctx.advance();
|
|
5921
6064
|
const target = parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
5922
6065
|
if (target)
|
|
@@ -6057,6 +6200,9 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6057
6200
|
ctx.advance();
|
|
6058
6201
|
loopType = KEYWORDS.FOREVER;
|
|
6059
6202
|
}
|
|
6203
|
+
else if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
6204
|
+
loopType = KEYWORDS.FOREVER;
|
|
6205
|
+
}
|
|
6060
6206
|
else {
|
|
6061
6207
|
times = ctx.parseExpression();
|
|
6062
6208
|
if (ctx.check(KEYWORDS.TIMES)) {
|
|
@@ -6099,10 +6245,12 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6099
6245
|
loopType = result.terminator;
|
|
6100
6246
|
ctx.advance();
|
|
6101
6247
|
condition = ctx.parseExpression();
|
|
6102
|
-
if (
|
|
6248
|
+
if (ctx.check('end')) {
|
|
6249
|
+
ctx.advance();
|
|
6250
|
+
}
|
|
6251
|
+
else if (!ctx.atEndOfInput()) {
|
|
6103
6252
|
throw new Error('Expected "end" to close repeat block');
|
|
6104
6253
|
}
|
|
6105
|
-
ctx.advance();
|
|
6106
6254
|
}
|
|
6107
6255
|
}
|
|
6108
6256
|
else {
|
|
@@ -6348,7 +6496,7 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6348
6496
|
}));
|
|
6349
6497
|
}
|
|
6350
6498
|
}
|
|
6351
|
-
if (!consumedElseIf && !ctx.
|
|
6499
|
+
if (!consumedElseIf && !ctx.atEndOfInput()) {
|
|
6352
6500
|
ctx.consume(KEYWORDS.END, "Expected 'end' after if block");
|
|
6353
6501
|
}
|
|
6354
6502
|
}
|
|
@@ -6373,7 +6521,6 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6373
6521
|
.build();
|
|
6374
6522
|
}
|
|
6375
6523
|
function parseForCommand(ctx, commandToken) {
|
|
6376
|
-
const args = [];
|
|
6377
6524
|
let variable = null;
|
|
6378
6525
|
let collection = null;
|
|
6379
6526
|
if (ctx.check(KEYWORDS.EACH)) {
|
|
@@ -6416,31 +6563,22 @@ function parseForCommand(ctx, commandToken) {
|
|
|
6416
6563
|
}
|
|
6417
6564
|
}
|
|
6418
6565
|
const commands = ctx.parseCommandListUntilEnd('for');
|
|
6419
|
-
|
|
6420
|
-
type: 'identifier',
|
|
6421
|
-
name: 'for',
|
|
6422
|
-
start: commandToken.start,
|
|
6423
|
-
end: commandToken.end,
|
|
6424
|
-
line: commandToken.line,
|
|
6425
|
-
column: commandToken.column,
|
|
6426
|
-
});
|
|
6427
|
-
const forPos = {
|
|
6566
|
+
const pos = {
|
|
6428
6567
|
start: commandToken.start,
|
|
6429
6568
|
end: commandToken.end,
|
|
6430
6569
|
line: commandToken.line,
|
|
6431
6570
|
column: commandToken.column,
|
|
6432
6571
|
};
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
}
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
.withArgs(...args)
|
|
6572
|
+
const modifiers = {
|
|
6573
|
+
loopType: toLegacyExpression(createStringLiteral(KEYWORDS.FOR, pos)),
|
|
6574
|
+
for: toLegacyExpression(createStringLiteral(variable, pos)),
|
|
6575
|
+
in: collection,
|
|
6576
|
+
};
|
|
6577
|
+
if (indexVariable)
|
|
6578
|
+
modifiers['index'] = toLegacyExpression(createStringLiteral(indexVariable, pos));
|
|
6579
|
+
return CommandNodeBuilder.from({ ...commandToken, value: 'repeat' })
|
|
6580
|
+
.withArgs(createBlock(commands, { ...pos, end: pos.end || 0 }))
|
|
6581
|
+
.withModifiers(modifiers)
|
|
6444
6582
|
.endingAt(ctx.getPosition())
|
|
6445
6583
|
.build();
|
|
6446
6584
|
}
|
|
@@ -6496,86 +6634,159 @@ function parseMeasureCommand(ctx, identifierNode) {
|
|
|
6496
6634
|
}
|
|
6497
6635
|
return builder.build();
|
|
6498
6636
|
}
|
|
6499
|
-
const
|
|
6637
|
+
const CONTEXT_REFS = new Set(['me', 'it', 'you', 'result']);
|
|
6500
6638
|
function parseTransitionCommand(ctx, commandToken) {
|
|
6501
|
-
const
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
const
|
|
6506
|
-
const
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
if (
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
target = parsed.object;
|
|
6517
|
-
property = {
|
|
6518
|
-
type: 'string',
|
|
6519
|
-
value: parsed.property.name,
|
|
6520
|
-
start: leadToken.start || 0,
|
|
6521
|
-
end: ctx.getPosition().end,
|
|
6522
|
-
line: leadToken.line,
|
|
6523
|
-
column: leadToken.column,
|
|
6524
|
-
};
|
|
6639
|
+
const pairs = [];
|
|
6640
|
+
do {
|
|
6641
|
+
pairs.push(parseTransitionPair(ctx));
|
|
6642
|
+
} while (startsAnotherPair(ctx));
|
|
6643
|
+
const [first, ...rest] = pairs;
|
|
6644
|
+
const args = first.owner ? [first.owner, first.property] : [first.property];
|
|
6645
|
+
const modifiers = { to: first.to };
|
|
6646
|
+
if (first.from)
|
|
6647
|
+
modifiers['from'] = first.from;
|
|
6648
|
+
if (rest.length > 0)
|
|
6649
|
+
modifiers['pairs'] = pairsNode(rest);
|
|
6650
|
+
for (let i = 0; i < 3 && !ctx.isAtEnd(); i++) {
|
|
6651
|
+
if (ctx.check('over') && !modifiers['over']) {
|
|
6652
|
+
ctx.advance();
|
|
6653
|
+
modifiers['over'] = ctx.parseExpression();
|
|
6525
6654
|
}
|
|
6526
|
-
else {
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
ctx.advance();
|
|
6655
|
+
else if (ctx.check('using') && !modifiers['using']) {
|
|
6656
|
+
ctx.advance();
|
|
6657
|
+
modifiers['using'] = ctx.parseExpression();
|
|
6530
6658
|
}
|
|
6531
|
-
|
|
6532
|
-
const firstToken = ctx.peek();
|
|
6533
|
-
if (!property && (isIdentifierLike(firstToken) || firstToken.value === '*')) {
|
|
6534
|
-
let propertyValue = '';
|
|
6535
|
-
if (ctx.check('*')) {
|
|
6536
|
-
propertyValue = '*';
|
|
6659
|
+
else if (ctx.check(KEYWORDS.WITH) && !modifiers['with']) {
|
|
6537
6660
|
ctx.advance();
|
|
6661
|
+
modifiers['with'] = ctx.parsePrimary();
|
|
6538
6662
|
}
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
propertyValue += hyphenatedName;
|
|
6542
|
-
property = {
|
|
6543
|
-
type: 'string',
|
|
6544
|
-
value: propertyValue,
|
|
6545
|
-
start: firstToken.start || 0,
|
|
6546
|
-
end: ctx.getPosition().end,
|
|
6547
|
-
line: firstToken.line,
|
|
6548
|
-
column: firstToken.column,
|
|
6549
|
-
};
|
|
6663
|
+
else {
|
|
6664
|
+
break;
|
|
6550
6665
|
}
|
|
6551
6666
|
}
|
|
6552
|
-
|
|
6553
|
-
|
|
6667
|
+
return CommandNodeBuilder.from(commandToken)
|
|
6668
|
+
.withArgs(...args)
|
|
6669
|
+
.withModifiers(modifiers)
|
|
6670
|
+
.endingAt(ctx.getPosition())
|
|
6671
|
+
.build();
|
|
6672
|
+
}
|
|
6673
|
+
function parseTransitionPair(ctx) {
|
|
6674
|
+
const { owner, property } = parseTransitionProperty(ctx);
|
|
6675
|
+
let from = null;
|
|
6676
|
+
if (ctx.check('from')) {
|
|
6677
|
+
ctx.advance();
|
|
6678
|
+
from = ctx.parseExpression();
|
|
6554
6679
|
}
|
|
6555
|
-
if (target)
|
|
6556
|
-
args.push(target);
|
|
6557
|
-
args.push(property);
|
|
6558
6680
|
if (!ctx.check(KEYWORDS.TO)) {
|
|
6559
6681
|
throw new Error('Expected "to" keyword after property in transition command');
|
|
6560
6682
|
}
|
|
6561
6683
|
ctx.advance();
|
|
6562
|
-
const
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6684
|
+
const to = ctx.parseExpression();
|
|
6685
|
+
return { owner, property, from, to };
|
|
6686
|
+
}
|
|
6687
|
+
function parseTransitionProperty(ctx) {
|
|
6688
|
+
const lead = ctx.peek();
|
|
6689
|
+
if (!lead || isKeyword(lead, [KEYWORDS.TO, 'from'])) {
|
|
6690
|
+
throw new Error('Transition command requires a CSS property');
|
|
6568
6691
|
}
|
|
6569
|
-
|
|
6692
|
+
const expr = ctx.parseExpression();
|
|
6693
|
+
const split = splitStyleExpression(expr);
|
|
6694
|
+
if (split)
|
|
6695
|
+
return { owner: split.owner, property: propertyNode(split.name, lead, ctx) };
|
|
6696
|
+
let name = '';
|
|
6697
|
+
if (ctx.check('*')) {
|
|
6698
|
+
name = '*';
|
|
6570
6699
|
ctx.advance();
|
|
6571
|
-
const timingFunction = ctx.parsePrimary();
|
|
6572
|
-
modifiers['with'] = timingFunction;
|
|
6573
6700
|
}
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6701
|
+
const hyphenated = parseHyphenatedName(ctx);
|
|
6702
|
+
if (!hyphenated)
|
|
6703
|
+
throw new Error('Transition command requires a CSS property');
|
|
6704
|
+
return { owner: expr, property: propertyNode(name + hyphenated, lead, ctx) };
|
|
6705
|
+
}
|
|
6706
|
+
function splitStyleExpression(node) {
|
|
6707
|
+
const n = node;
|
|
6708
|
+
switch (n.type) {
|
|
6709
|
+
case 'selector':
|
|
6710
|
+
return typeof n.value === 'string' && n.value.startsWith('*')
|
|
6711
|
+
? { owner: null, name: n.value }
|
|
6712
|
+
: null;
|
|
6713
|
+
case 'identifier':
|
|
6714
|
+
return typeof n.name === 'string' && !CONTEXT_REFS.has(n.name)
|
|
6715
|
+
? { owner: null, name: n.name }
|
|
6716
|
+
: null;
|
|
6717
|
+
case 'possessiveExpression':
|
|
6718
|
+
case 'memberExpression':
|
|
6719
|
+
return !n.computed && n.object && typeof n.property?.name === 'string'
|
|
6720
|
+
? { owner: n.object, name: n.property.name }
|
|
6721
|
+
: null;
|
|
6722
|
+
case 'binaryExpression': {
|
|
6723
|
+
if (n.operator !== 'of' || !n.left || !n.right)
|
|
6724
|
+
return null;
|
|
6725
|
+
const inner = splitStyleExpression(n.left);
|
|
6726
|
+
return inner && !inner.owner ? { owner: n.right, name: inner.name } : null;
|
|
6727
|
+
}
|
|
6728
|
+
case 'propertyOfExpression':
|
|
6729
|
+
return n.target && typeof n.property?.name === 'string'
|
|
6730
|
+
? { owner: n.target, name: n.property.name }
|
|
6731
|
+
: null;
|
|
6732
|
+
default:
|
|
6733
|
+
return null;
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
function propertyNode(name, lead, ctx) {
|
|
6737
|
+
return {
|
|
6738
|
+
type: 'string',
|
|
6739
|
+
value: name,
|
|
6740
|
+
start: lead.start || 0,
|
|
6741
|
+
end: ctx.getPosition().end,
|
|
6742
|
+
line: lead.line,
|
|
6743
|
+
column: lead.column,
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
function startsAnotherPair(ctx) {
|
|
6747
|
+
if (ctx.isAtEnd() || isComment(ctx.peek()))
|
|
6748
|
+
return false;
|
|
6749
|
+
if (isKeyword(ctx.peek(), ['over', 'using', KEYWORDS.WITH]))
|
|
6750
|
+
return false;
|
|
6751
|
+
return !isCommandBoundary(ctx, ['catch', 'finally', 'on']);
|
|
6752
|
+
}
|
|
6753
|
+
function pairsNode(pairs) {
|
|
6754
|
+
const field = (name, value) => ({
|
|
6755
|
+
key: {
|
|
6756
|
+
type: 'identifier',
|
|
6757
|
+
name,
|
|
6758
|
+
start: value.start,
|
|
6759
|
+
end: value.end,
|
|
6760
|
+
line: value.line,
|
|
6761
|
+
column: value.column,
|
|
6762
|
+
},
|
|
6763
|
+
value,
|
|
6764
|
+
});
|
|
6765
|
+
const elements = pairs.map(pair => {
|
|
6766
|
+
const properties = [field('property', pair.property), field('to', pair.to)];
|
|
6767
|
+
if (pair.owner)
|
|
6768
|
+
properties.push(field('owner', pair.owner));
|
|
6769
|
+
if (pair.from)
|
|
6770
|
+
properties.push(field('from', pair.from));
|
|
6771
|
+
return {
|
|
6772
|
+
type: 'objectLiteral',
|
|
6773
|
+
properties,
|
|
6774
|
+
start: (pair.owner ?? pair.property).start,
|
|
6775
|
+
end: pair.to.end,
|
|
6776
|
+
line: (pair.owner ?? pair.property).line,
|
|
6777
|
+
column: (pair.owner ?? pair.property).column,
|
|
6778
|
+
};
|
|
6779
|
+
});
|
|
6780
|
+
const firstEl = elements[0];
|
|
6781
|
+
const lastEl = elements[elements.length - 1];
|
|
6782
|
+
return {
|
|
6783
|
+
type: 'arrayLiteral',
|
|
6784
|
+
elements,
|
|
6785
|
+
start: firstEl.start,
|
|
6786
|
+
end: lastEl.end,
|
|
6787
|
+
line: firstEl.line,
|
|
6788
|
+
column: firstEl.column,
|
|
6789
|
+
};
|
|
6579
6790
|
}
|
|
6580
6791
|
function parseStartCommand(ctx, identifierNode) {
|
|
6581
6792
|
if (!ctx.match('view')) {
|
|
@@ -6695,6 +6906,15 @@ function parseToggleCommand(ctx, identifierNode) {
|
|
|
6695
6906
|
args.push(classArg.expression);
|
|
6696
6907
|
modifiers['as'] = literalModifier(mode);
|
|
6697
6908
|
}
|
|
6909
|
+
else if (classArg?.type === 'binaryExpression' &&
|
|
6910
|
+
classArg.operator === 'of' &&
|
|
6911
|
+
classArg.left?.type === 'selector' &&
|
|
6912
|
+
typeof classArg.left.value === 'string' &&
|
|
6913
|
+
classArg.left.value.startsWith('*') &&
|
|
6914
|
+
classArg.right) {
|
|
6915
|
+
args.push(classArg.left);
|
|
6916
|
+
modifiers['on'] = classArg.right;
|
|
6917
|
+
}
|
|
6698
6918
|
else if (classArg) {
|
|
6699
6919
|
args.push(classArg);
|
|
6700
6920
|
}
|
|
@@ -6872,11 +7092,17 @@ function parseSwapCommand(ctx, identifierNode) {
|
|
|
6872
7092
|
const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
6873
7093
|
if (targetExpr)
|
|
6874
7094
|
args.push(targetExpr);
|
|
6875
|
-
|
|
7095
|
+
const isMorph = identifierNode.name === 'morph';
|
|
7096
|
+
if (!isDelete &&
|
|
7097
|
+
(consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
|
|
7098
|
+
(isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
|
|
6876
7099
|
const contentExpr = parseOneArgument(ctx, ['using']);
|
|
6877
7100
|
if (contentExpr)
|
|
6878
7101
|
modifiers['with'] = contentExpr;
|
|
6879
7102
|
}
|
|
7103
|
+
if (isMorph && modifiers['with'] === undefined) {
|
|
7104
|
+
ctx.addError("Expected 'to' and the content to morph into");
|
|
7105
|
+
}
|
|
6880
7106
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
6881
7107
|
.withArgs(...args)
|
|
6882
7108
|
.withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
|
|
@@ -7637,6 +7863,8 @@ function atArgumentBoundary(ctx, grammar) {
|
|
|
7637
7863
|
const next = ctx.peek();
|
|
7638
7864
|
if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
|
|
7639
7865
|
return false;
|
|
7866
|
+
if (isGluedBeepBang(next, ctx.peekAt(1)))
|
|
7867
|
+
return false;
|
|
7640
7868
|
if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
|
|
7641
7869
|
return true;
|
|
7642
7870
|
return isKeyword(next, [...grammar.markers]);
|
|
@@ -7668,10 +7896,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
|
|
|
7668
7896
|
}
|
|
7669
7897
|
}
|
|
7670
7898
|
const commaList = new Set(grammar.commaList ?? []);
|
|
7899
|
+
const namedArgs = new Set(grammar.namedArgs ?? []);
|
|
7671
7900
|
while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
|
|
7672
7901
|
const keyword = ctx.advance().value;
|
|
7673
7902
|
const key = ctx.resolveKeyword(keyword).toLowerCase();
|
|
7674
|
-
let value =
|
|
7903
|
+
let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
|
|
7904
|
+
? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
|
|
7905
|
+
: ctx.parseExpression();
|
|
7675
7906
|
if (value && commaList.has(key) && ctx.check(',')) {
|
|
7676
7907
|
const elements = [value];
|
|
7677
7908
|
while (ctx.match(',')) {
|
|
@@ -7836,8 +8067,8 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7836
8067
|
}
|
|
7837
8068
|
if (ctx.check('with') && !modifiers['with']) {
|
|
7838
8069
|
ctx.advance();
|
|
7839
|
-
if (
|
|
7840
|
-
modifiers['with'] =
|
|
8070
|
+
if (isNakedNamedArgStart(ctx)) {
|
|
8071
|
+
modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
|
|
7841
8072
|
}
|
|
7842
8073
|
else {
|
|
7843
8074
|
modifiers['with'] = ctx.parsePrimary();
|
|
@@ -7872,45 +8103,6 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7872
8103
|
}
|
|
7873
8104
|
return builder.build();
|
|
7874
8105
|
}
|
|
7875
|
-
function isFetchNakedNamedArgStart(ctx) {
|
|
7876
|
-
if (ctx.check('{'))
|
|
7877
|
-
return false;
|
|
7878
|
-
if (!ctx.checkIdentifierLike())
|
|
7879
|
-
return false;
|
|
7880
|
-
const next = ctx.peekAt(1);
|
|
7881
|
-
return next !== null && next.value === ':';
|
|
7882
|
-
}
|
|
7883
|
-
function parseFetchNakedNamedArgs(ctx) {
|
|
7884
|
-
const properties = [];
|
|
7885
|
-
const startPos = ctx.getPosition();
|
|
7886
|
-
do {
|
|
7887
|
-
if (!ctx.checkIdentifierLike())
|
|
7888
|
-
break;
|
|
7889
|
-
const keyToken = ctx.advance();
|
|
7890
|
-
const key = {
|
|
7891
|
-
type: 'identifier',
|
|
7892
|
-
name: keyToken.value,
|
|
7893
|
-
start: keyToken.start,
|
|
7894
|
-
end: keyToken.end,
|
|
7895
|
-
line: keyToken.line,
|
|
7896
|
-
column: keyToken.column,
|
|
7897
|
-
};
|
|
7898
|
-
ctx.consume(':', "Expected ':' after property name in fetch named arguments");
|
|
7899
|
-
const value = ctx.parsePrimary();
|
|
7900
|
-
if (value) {
|
|
7901
|
-
properties.push({ key, value });
|
|
7902
|
-
}
|
|
7903
|
-
} while (ctx.match(',') && !ctx.isAtEnd());
|
|
7904
|
-
const endPos = ctx.getPosition();
|
|
7905
|
-
return {
|
|
7906
|
-
type: 'objectLiteral',
|
|
7907
|
-
properties,
|
|
7908
|
-
start: startPos.start,
|
|
7909
|
-
end: endPos.end,
|
|
7910
|
-
line: startPos.line,
|
|
7911
|
-
column: startPos.column,
|
|
7912
|
-
};
|
|
7913
|
-
}
|
|
7914
8106
|
function findJsEndBoundary(ctx, startPos) {
|
|
7915
8107
|
const input = ctx.getInputSlice(startPos);
|
|
7916
8108
|
if (!input) {
|
|
@@ -8030,6 +8222,7 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8030
8222
|
const commands = [];
|
|
8031
8223
|
while (!ctx.isAtEnd()) {
|
|
8032
8224
|
if (ctx.checkIsCommand()) {
|
|
8225
|
+
const word = ctx.peek().value;
|
|
8033
8226
|
try {
|
|
8034
8227
|
ctx.advance();
|
|
8035
8228
|
const cmd = ctx.parseCommand();
|
|
@@ -8040,7 +8233,8 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8040
8233
|
break;
|
|
8041
8234
|
}
|
|
8042
8235
|
}
|
|
8043
|
-
catch {
|
|
8236
|
+
catch (error) {
|
|
8237
|
+
ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
8044
8238
|
break;
|
|
8045
8239
|
}
|
|
8046
8240
|
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
@@ -8189,7 +8383,6 @@ const ONE_EXPR = (syntax) => ({
|
|
|
8189
8383
|
markers: [],
|
|
8190
8384
|
syntax,
|
|
8191
8385
|
});
|
|
8192
|
-
const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
|
|
8193
8386
|
const COMMAND_GRAMMAR = {
|
|
8194
8387
|
append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
|
|
8195
8388
|
prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
|
|
@@ -8200,8 +8393,7 @@ const COMMAND_GRAMMAR = {
|
|
|
8200
8393
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
8201
8394
|
},
|
|
8202
8395
|
throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
|
|
8203
|
-
'beep!'
|
|
8204
|
-
beep: BEEP,
|
|
8396
|
+
beep: ONE_EXPR('beep! [<expression>, \u2026]'),
|
|
8205
8397
|
blur: ONE_EXPR('blur [<target>]'),
|
|
8206
8398
|
break: NONE,
|
|
8207
8399
|
breakpoint: NONE,
|
|
@@ -8225,7 +8417,8 @@ const COMMAND_GRAMMAR = {
|
|
|
8225
8417
|
render: {
|
|
8226
8418
|
positional: 'expression',
|
|
8227
8419
|
markers: ['with'],
|
|
8228
|
-
|
|
8420
|
+
namedArgs: ['with'],
|
|
8421
|
+
syntax: 'render <template> [with <name>: <value>, \u2026]',
|
|
8229
8422
|
},
|
|
8230
8423
|
reset: ONE_EXPR('reset [<form>]'),
|
|
8231
8424
|
return: ONE_EXPR('return [<value>]'),
|
|
@@ -8580,7 +8773,10 @@ class Parser {
|
|
|
8580
8773
|
const firstToken = this.peek();
|
|
8581
8774
|
const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
|
|
8582
8775
|
let left;
|
|
8583
|
-
if (
|
|
8776
|
+
if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
|
|
8777
|
+
left = this.parseBeepExpression();
|
|
8778
|
+
}
|
|
8779
|
+
else if (prefixEntry?.prefix) {
|
|
8584
8780
|
const token = this.advance();
|
|
8585
8781
|
if (this.isAtEnd()) {
|
|
8586
8782
|
this.addError(`Expected expression after '${token.value}' operator`);
|
|
@@ -8600,6 +8796,9 @@ class Parser {
|
|
|
8600
8796
|
continue;
|
|
8601
8797
|
}
|
|
8602
8798
|
}
|
|
8799
|
+
if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
|
|
8800
|
+
break;
|
|
8801
|
+
}
|
|
8603
8802
|
const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
|
|
8604
8803
|
if (!infixEntry?.infix) {
|
|
8605
8804
|
if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
|
|
@@ -8661,6 +8860,35 @@ class Parser {
|
|
|
8661
8860
|
this.prattCache.set(cacheKey, { node: left, endPos: this.current });
|
|
8662
8861
|
return left;
|
|
8663
8862
|
}
|
|
8863
|
+
parseBeepExpression() {
|
|
8864
|
+
const beepToken = this.advance();
|
|
8865
|
+
this.advance();
|
|
8866
|
+
if (this.isAtEnd()) {
|
|
8867
|
+
this.addError("Expected expression after 'beep!'");
|
|
8868
|
+
return this.createErrorNode();
|
|
8869
|
+
}
|
|
8870
|
+
const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
|
|
8871
|
+
return {
|
|
8872
|
+
type: 'unaryExpression',
|
|
8873
|
+
operator: 'beep!',
|
|
8874
|
+
operand,
|
|
8875
|
+
argument: operand,
|
|
8876
|
+
prefix: true,
|
|
8877
|
+
start: beepToken.start,
|
|
8878
|
+
end: this.previous().end,
|
|
8879
|
+
line: beepToken.line,
|
|
8880
|
+
column: beepToken.column,
|
|
8881
|
+
};
|
|
8882
|
+
}
|
|
8883
|
+
isStyleRefStar(i) {
|
|
8884
|
+
const star = this.tokens[i];
|
|
8885
|
+
const name = this.tokens[i + 1];
|
|
8886
|
+
return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
|
|
8887
|
+
}
|
|
8888
|
+
isSpacedStyleRef(i) {
|
|
8889
|
+
const prev = this.tokens[i - 1];
|
|
8890
|
+
return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
|
|
8891
|
+
}
|
|
8664
8892
|
tryParseStringPostfix(left, nextToken) {
|
|
8665
8893
|
const v = nextToken.value;
|
|
8666
8894
|
let unit = null;
|
|
@@ -8863,6 +9091,10 @@ class Parser {
|
|
|
8863
9091
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
8864
9092
|
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
8865
9093
|
while (!this.isAtEnd() && !isStop()) {
|
|
9094
|
+
if (this.checkComment()) {
|
|
9095
|
+
this.advance();
|
|
9096
|
+
continue;
|
|
9097
|
+
}
|
|
8866
9098
|
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
8867
9099
|
let parsedCommand = false;
|
|
8868
9100
|
const isCommandToken = this.checkIsCommand();
|
|
@@ -8946,7 +9178,7 @@ class Parser {
|
|
|
8946
9178
|
debug.parse('\u2705 Found "end", consuming it');
|
|
8947
9179
|
this.advance();
|
|
8948
9180
|
}
|
|
8949
|
-
else {
|
|
9181
|
+
else if (!this.atEndOfInput()) {
|
|
8950
9182
|
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
8951
9183
|
throw new Error(`Expected "end" to close ${construct} block`);
|
|
8952
9184
|
}
|
|
@@ -8959,7 +9191,7 @@ class Parser {
|
|
|
8959
9191
|
if (this.check('end')) {
|
|
8960
9192
|
this.advance();
|
|
8961
9193
|
}
|
|
8962
|
-
else {
|
|
9194
|
+
else if (!this.atEndOfInput()) {
|
|
8963
9195
|
throw new Error('Expected "end" to close repeat block');
|
|
8964
9196
|
}
|
|
8965
9197
|
}
|
|
@@ -8975,7 +9207,10 @@ class Parser {
|
|
|
8975
9207
|
this.advance();
|
|
8976
9208
|
}
|
|
8977
9209
|
else if (terminator === '') {
|
|
8978
|
-
|
|
9210
|
+
if (!this.atEndOfInput()) {
|
|
9211
|
+
throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
|
|
9212
|
+
}
|
|
9213
|
+
return { commands, terminator: 'end' };
|
|
8979
9214
|
}
|
|
8980
9215
|
return { commands, terminator };
|
|
8981
9216
|
}
|
|
@@ -9559,7 +9794,9 @@ class Parser {
|
|
|
9559
9794
|
parseTopLevelInitBlock() {
|
|
9560
9795
|
const pos = this.getPosition();
|
|
9561
9796
|
const initCommands = this.parseCommandBlock(['end']);
|
|
9562
|
-
this.
|
|
9797
|
+
if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
|
|
9798
|
+
this.consume('end', "Expected 'end' after init block");
|
|
9799
|
+
}
|
|
9563
9800
|
return {
|
|
9564
9801
|
type: 'initBlock',
|
|
9565
9802
|
commands: initCommands,
|
|
@@ -9735,10 +9972,29 @@ class Parser {
|
|
|
9735
9972
|
...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
|
|
9736
9973
|
};
|
|
9737
9974
|
}
|
|
9975
|
+
isArgumentListOnly(open) {
|
|
9976
|
+
let depth = 0;
|
|
9977
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
9978
|
+
const token = this.tokens[i];
|
|
9979
|
+
if (token.kind !== 'operator')
|
|
9980
|
+
continue;
|
|
9981
|
+
if (token.value === '(' || token.value === '[' || token.value === '{')
|
|
9982
|
+
depth++;
|
|
9983
|
+
else if (token.value === ')' || token.value === ']' || token.value === '}') {
|
|
9984
|
+
if (--depth === 0)
|
|
9985
|
+
return i === open + 1;
|
|
9986
|
+
}
|
|
9987
|
+
else if (token.value === ',' && depth === 1)
|
|
9988
|
+
return true;
|
|
9989
|
+
}
|
|
9990
|
+
return false;
|
|
9991
|
+
}
|
|
9738
9992
|
tryParsePseudoCommand(callExpr, sourceExpr) {
|
|
9739
9993
|
const methodName = callExpr.callee.name;
|
|
9740
9994
|
const nextToken = this.peek();
|
|
9741
|
-
const
|
|
9995
|
+
const nextIsArticle = nextToken.value.toLowerCase() === 'the';
|
|
9996
|
+
const hasPseudoCommandPattern = nextIsArticle ||
|
|
9997
|
+
Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
|
|
9742
9998
|
(isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
|
|
9743
9999
|
isContextVar(nextToken);
|
|
9744
10000
|
if (!hasPseudoCommandPattern) {
|
|
@@ -9764,7 +10020,10 @@ class Parser {
|
|
|
9764
10020
|
});
|
|
9765
10021
|
}
|
|
9766
10022
|
let preposition;
|
|
9767
|
-
if (
|
|
10023
|
+
if (nextIsArticle) {
|
|
10024
|
+
this.advance();
|
|
10025
|
+
}
|
|
10026
|
+
else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
|
|
9768
10027
|
preposition = this.advance().value.toLowerCase();
|
|
9769
10028
|
}
|
|
9770
10029
|
let targetExpr;
|
|
@@ -9928,10 +10187,22 @@ class Parser {
|
|
|
9928
10187
|
this.consume(']', "Expected ']' after event condition");
|
|
9929
10188
|
}
|
|
9930
10189
|
let target;
|
|
10190
|
+
let targetExpression;
|
|
9931
10191
|
if (this.match('from')) {
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
10192
|
+
if (this.check('the'))
|
|
10193
|
+
this.advance();
|
|
10194
|
+
if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
|
|
10195
|
+
const startIndex = this.current;
|
|
10196
|
+
targetExpression = this.parseExpression();
|
|
10197
|
+
target = this.tokens
|
|
10198
|
+
.slice(startIndex, this.current)
|
|
10199
|
+
.map(t => t.value)
|
|
10200
|
+
.join(' ');
|
|
10201
|
+
}
|
|
10202
|
+
else {
|
|
10203
|
+
target = this.advance().value;
|
|
10204
|
+
}
|
|
10205
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
|
|
9935
10206
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
9936
10207
|
}
|
|
9937
10208
|
let attributeName;
|
|
@@ -9977,7 +10248,7 @@ class Parser {
|
|
|
9977
10248
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
9978
10249
|
const commandName = this.peek().value.toLowerCase();
|
|
9979
10250
|
const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
|
|
9980
|
-
if (nextIsOpenParen && !isSpecialBodyCommand) {
|
|
10251
|
+
if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
|
|
9981
10252
|
let expr;
|
|
9982
10253
|
const savedError = this.error;
|
|
9983
10254
|
try {
|
|
@@ -10009,7 +10280,7 @@ class Parser {
|
|
|
10009
10280
|
}
|
|
10010
10281
|
}
|
|
10011
10282
|
}
|
|
10012
|
-
else if (this.checkIdentifier()) {
|
|
10283
|
+
else if (this.checkIdentifier() || this.checkEventNamedCall()) {
|
|
10013
10284
|
const token = this.peek();
|
|
10014
10285
|
if (this.isCommand(token.value)) {
|
|
10015
10286
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
@@ -10118,6 +10389,7 @@ class Parser {
|
|
|
10118
10389
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
10119
10390
|
...(condition && { condition: fromLegacyExpression(condition) }),
|
|
10120
10391
|
...(target && { target }),
|
|
10392
|
+
...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
|
|
10121
10393
|
...(attributeName && { attributeName }),
|
|
10122
10394
|
...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
|
|
10123
10395
|
...(customEventSource && { customEventSource }),
|
|
@@ -10373,10 +10645,9 @@ class Parser {
|
|
|
10373
10645
|
}
|
|
10374
10646
|
parseCommandCore() {
|
|
10375
10647
|
const commandToken = this.previous();
|
|
10376
|
-
|
|
10648
|
+
const commandName = commandToken.value;
|
|
10377
10649
|
if (commandName === 'beep' && this.check('!')) {
|
|
10378
10650
|
this.advance();
|
|
10379
|
-
commandName = 'beep!';
|
|
10380
10651
|
}
|
|
10381
10652
|
if (commandName === 'fetch') {
|
|
10382
10653
|
return parseFetchCommand(this.getContext(), commandToken);
|
|
@@ -10475,6 +10746,9 @@ class Parser {
|
|
|
10475
10746
|
args.push(this.parsePrimary());
|
|
10476
10747
|
}
|
|
10477
10748
|
const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
|
|
10749
|
+
if (funcName === 'closest' && args.length > 0 && this.match('to')) {
|
|
10750
|
+
callNode.closestTo = this.parseExpression();
|
|
10751
|
+
}
|
|
10478
10752
|
if (funcName === 'next' || funcName === 'previous') {
|
|
10479
10753
|
const relativePositional = this.parseRelativePositionalModifiers();
|
|
10480
10754
|
if (relativePositional) {
|
|
@@ -10514,6 +10788,9 @@ class Parser {
|
|
|
10514
10788
|
}
|
|
10515
10789
|
parseTheXofY() {
|
|
10516
10790
|
const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
|
|
10791
|
+
if (this.isStyleRefStar(this.current)) {
|
|
10792
|
+
return this.parsePrimary();
|
|
10793
|
+
}
|
|
10517
10794
|
if (!this.checkIdentifier()) {
|
|
10518
10795
|
return this.createIdentifier('the');
|
|
10519
10796
|
}
|
|
@@ -10529,7 +10806,7 @@ class Parser {
|
|
|
10529
10806
|
return this.createIdentifier(propertyName);
|
|
10530
10807
|
}
|
|
10531
10808
|
this.advance();
|
|
10532
|
-
const target = this.
|
|
10809
|
+
const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
|
|
10533
10810
|
return {
|
|
10534
10811
|
type: 'propertyOfExpression',
|
|
10535
10812
|
property: { type: 'identifier', name: propertyName },
|
|
@@ -10727,6 +11004,13 @@ class Parser {
|
|
|
10727
11004
|
checkLiteral() {
|
|
10728
11005
|
return this.checkPredicate(isLiteral);
|
|
10729
11006
|
}
|
|
11007
|
+
checkEventNamedCall() {
|
|
11008
|
+
if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
|
|
11009
|
+
return false;
|
|
11010
|
+
const token = this.peek();
|
|
11011
|
+
const next = this.tokens[this.current + 1];
|
|
11012
|
+
return next?.value === '(' && next.start === token.end;
|
|
11013
|
+
}
|
|
10730
11014
|
checkIdentifierLike() {
|
|
10731
11015
|
return this.checkPredicate(isIdentifierLike);
|
|
10732
11016
|
}
|
|
@@ -10835,6 +11119,22 @@ class Parser {
|
|
|
10835
11119
|
isAtEnd() {
|
|
10836
11120
|
return this.current >= this.tokens.length;
|
|
10837
11121
|
}
|
|
11122
|
+
atEndOfInput() {
|
|
11123
|
+
for (let i = this.current; i < this.tokens.length; i++) {
|
|
11124
|
+
if (!isComment(this.tokens[i]))
|
|
11125
|
+
return false;
|
|
11126
|
+
}
|
|
11127
|
+
return true;
|
|
11128
|
+
}
|
|
11129
|
+
checkFeatureStart() {
|
|
11130
|
+
if (this.isAtEnd())
|
|
11131
|
+
return false;
|
|
11132
|
+
const value = this.peek().value;
|
|
11133
|
+
return (value === 'init' ||
|
|
11134
|
+
value === 'on' ||
|
|
11135
|
+
value === 'def' ||
|
|
11136
|
+
getRegisteredFeature(value) !== undefined);
|
|
11137
|
+
}
|
|
10838
11138
|
peek() {
|
|
10839
11139
|
if (this.isAtEnd()) {
|
|
10840
11140
|
return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
|
|
@@ -11046,6 +11346,7 @@ class Parser {
|
|
|
11046
11346
|
match: this.match.bind(this),
|
|
11047
11347
|
matchOperator: this.matchOperator.bind(this),
|
|
11048
11348
|
isAtEnd: this.isAtEnd.bind(this),
|
|
11349
|
+
atEndOfInput: this.atEndOfInput.bind(this),
|
|
11049
11350
|
createIdentifier: this.createIdentifier.bind(this),
|
|
11050
11351
|
parseExpression: this.parseExpression.bind(this),
|
|
11051
11352
|
parsePrimary: this.parsePrimary.bind(this),
|
|
@@ -11096,7 +11397,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
|
|
|
11096
11397
|
'ignoring case',
|
|
11097
11398
|
]);
|
|
11098
11399
|
Parser.PRATT_TABLE = PARSER_TABLE;
|
|
11400
|
+
Parser.BEEP_OPERAND_BP = 80;
|
|
11099
11401
|
Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
|
|
11402
|
+
Parser.POSITIONAL_SOURCE_HEADS = new Set([
|
|
11403
|
+
'closest',
|
|
11404
|
+
'next',
|
|
11405
|
+
'previous',
|
|
11406
|
+
'first',
|
|
11407
|
+
'last',
|
|
11408
|
+
]);
|
|
11100
11409
|
function parse(input, options) {
|
|
11101
11410
|
const tokens = tokenize(input);
|
|
11102
11411
|
const parser = new Parser(tokens, options, input);
|
|
@@ -11538,11 +11847,16 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11538
11847
|
}
|
|
11539
11848
|
}
|
|
11540
11849
|
}
|
|
11541
|
-
if (leftNode?.type === 'selector' &&
|
|
11850
|
+
if (leftNode?.type === 'selector' &&
|
|
11851
|
+
typeof leftNode.value === 'string' &&
|
|
11852
|
+
(leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
|
|
11542
11853
|
const root = await evaluateAST(node.right, context);
|
|
11543
11854
|
const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
|
|
11544
11855
|
if (scope) {
|
|
11545
|
-
|
|
11856
|
+
const query = leftNode.fromQuery
|
|
11857
|
+
? leftNode.value
|
|
11858
|
+
: escapeSelectorForQuery(leftNode, leftNode.value);
|
|
11859
|
+
return Array.from(scope.querySelectorAll(query));
|
|
11546
11860
|
}
|
|
11547
11861
|
}
|
|
11548
11862
|
}
|
|
@@ -11554,6 +11868,12 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11554
11868
|
return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
|
|
11555
11869
|
}
|
|
11556
11870
|
if (operator === 'of') {
|
|
11871
|
+
if (leftNode?.type === 'attributeAccess') {
|
|
11872
|
+
const target = await evaluateAST(node.right, context);
|
|
11873
|
+
if (target == null)
|
|
11874
|
+
return undefined;
|
|
11875
|
+
return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
|
|
11876
|
+
}
|
|
11557
11877
|
const path = propertyPathOf(node.left);
|
|
11558
11878
|
if (path) {
|
|
11559
11879
|
const target = await evaluateAST(node.right, context);
|
|
@@ -11561,6 +11881,10 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11561
11881
|
for (const seg of path) {
|
|
11562
11882
|
if (cur == null)
|
|
11563
11883
|
return undefined;
|
|
11884
|
+
if (isMappableCollection(cur) && !(seg in cur)) {
|
|
11885
|
+
cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
|
|
11886
|
+
continue;
|
|
11887
|
+
}
|
|
11564
11888
|
cur = isElement(cur) ? getElementProperty(cur, seg) : cur[seg];
|
|
11565
11889
|
}
|
|
11566
11890
|
return typeof cur === 'function' ? cur.bind(target) : cur;
|
|
@@ -11585,26 +11909,21 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11585
11909
|
const R = node.ignoringCase ? applyCI(right) : right;
|
|
11586
11910
|
switch (operator) {
|
|
11587
11911
|
case '+': {
|
|
11588
|
-
let addLeft = left;
|
|
11589
|
-
let addRight = right;
|
|
11590
11912
|
if (node.coerceNumeric) {
|
|
11591
|
-
|
|
11592
|
-
addLeft = convertToNumber(addLeft);
|
|
11593
|
-
if (typeof addRight === 'string')
|
|
11594
|
-
addRight = convertToNumber(addRight);
|
|
11913
|
+
return counterValue(left) + counterAmount(right);
|
|
11595
11914
|
}
|
|
11596
|
-
if (typeof
|
|
11597
|
-
return String(
|
|
11915
|
+
if (typeof left === 'string' || typeof right === 'string') {
|
|
11916
|
+
return String(left ?? '') + String(right ?? '');
|
|
11598
11917
|
}
|
|
11599
|
-
if (Array.isArray(
|
|
11600
|
-
return Array.isArray(
|
|
11918
|
+
if (Array.isArray(left)) {
|
|
11919
|
+
return Array.isArray(right) ? [...left, ...right] : [...left, right];
|
|
11601
11920
|
}
|
|
11602
|
-
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
|
|
11603
|
-
left: addLeft,
|
|
11604
|
-
right: addRight,
|
|
11605
|
-
}));
|
|
11921
|
+
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
|
|
11606
11922
|
}
|
|
11607
11923
|
case '-':
|
|
11924
|
+
if (node.coerceNumeric) {
|
|
11925
|
+
return counterValue(left) - counterAmount(right);
|
|
11926
|
+
}
|
|
11608
11927
|
return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
|
|
11609
11928
|
case '*':
|
|
11610
11929
|
return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
|
|
@@ -11862,9 +12181,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
|
|
|
11862
12181
|
}
|
|
11863
12182
|
const propertyName = propertyNode.name;
|
|
11864
12183
|
const target = await evaluateAST(node.target, context);
|
|
11865
|
-
if (target == null)
|
|
11866
|
-
|
|
11867
|
-
}
|
|
12184
|
+
if (target == null)
|
|
12185
|
+
return undefined;
|
|
11868
12186
|
return getExpr(context, 'possessive').evaluate(context, target, propertyName);
|
|
11869
12187
|
}
|
|
11870
12188
|
async function evaluateTemplateLiteralNode(node, context) {
|
|
@@ -11982,6 +12300,9 @@ async function evaluateUnaryExpression(node, context) {
|
|
|
11982
12300
|
case 'not':
|
|
11983
12301
|
case '!':
|
|
11984
12302
|
return logicalExpressions.not.evaluate(context, value);
|
|
12303
|
+
case 'beep!':
|
|
12304
|
+
beepValues(context.me, [value]);
|
|
12305
|
+
return value;
|
|
11985
12306
|
case 'no':
|
|
11986
12307
|
return logicalExpressions.no.evaluate(context, value);
|
|
11987
12308
|
case '-':
|
|
@@ -12071,6 +12392,10 @@ async function evaluateCallExpression(node, context) {
|
|
|
12071
12392
|
const args = await resolveCallArgs(node.arguments, funcName, context);
|
|
12072
12393
|
switch (funcName) {
|
|
12073
12394
|
case 'closest':
|
|
12395
|
+
if (node.closestTo) {
|
|
12396
|
+
const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
|
|
12397
|
+
return referencesExpressions.closest.evaluate(context, args[0], start);
|
|
12398
|
+
}
|
|
12074
12399
|
return referencesExpressions.closest.evaluate(context, ...args);
|
|
12075
12400
|
case 'previous':
|
|
12076
12401
|
case 'next': {
|