@hyperfixi/core 3.1.0 → 3.2.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 +24 -9
- package/dist/bundle-generator/index.mjs +24 -9
- package/dist/bundle-generator/parser-templates.d.ts +1 -1
- package/dist/chunks/bridge-Cnj8dQFV.js +2 -0
- package/dist/chunks/browser-modular-BNDYF6Dq.js +2 -0
- package/dist/chunks/index-DQi2o8Lk.js +2 -0
- package/dist/chunks/index-LLuwEQp0.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 +929 -425
- package/dist/commands/index.mjs +929 -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 +1140 -485
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +1140 -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 +387 -153
- package/dist/parser/full-parser.mjs +387 -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 +526 -210
- package/dist/registry/index.mjs +526 -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-DylVo1oZ.js +0 -2
- package/dist/chunks/browser-modular-sLPMdxR7.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')) {
|
|
@@ -6874,11 +7085,17 @@ function parseSwapCommand(ctx, identifierNode) {
|
|
|
6874
7085
|
const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
6875
7086
|
if (targetExpr)
|
|
6876
7087
|
args.push(targetExpr);
|
|
6877
|
-
|
|
7088
|
+
const isMorph = identifierNode.name === 'morph';
|
|
7089
|
+
if (!isDelete &&
|
|
7090
|
+
(consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
|
|
7091
|
+
(isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
|
|
6878
7092
|
const contentExpr = parseOneArgument(ctx, ['using']);
|
|
6879
7093
|
if (contentExpr)
|
|
6880
7094
|
modifiers['with'] = contentExpr;
|
|
6881
7095
|
}
|
|
7096
|
+
if (isMorph && modifiers['with'] === undefined) {
|
|
7097
|
+
ctx.addError("Expected 'to' and the content to morph into");
|
|
7098
|
+
}
|
|
6882
7099
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
6883
7100
|
.withArgs(...args)
|
|
6884
7101
|
.withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
|
|
@@ -7639,6 +7856,8 @@ function atArgumentBoundary(ctx, grammar) {
|
|
|
7639
7856
|
const next = ctx.peek();
|
|
7640
7857
|
if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
|
|
7641
7858
|
return false;
|
|
7859
|
+
if (isGluedBeepBang(next, ctx.peekAt(1)))
|
|
7860
|
+
return false;
|
|
7642
7861
|
if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
|
|
7643
7862
|
return true;
|
|
7644
7863
|
return isKeyword(next, [...grammar.markers]);
|
|
@@ -7670,10 +7889,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
|
|
|
7670
7889
|
}
|
|
7671
7890
|
}
|
|
7672
7891
|
const commaList = new Set(grammar.commaList ?? []);
|
|
7892
|
+
const namedArgs = new Set(grammar.namedArgs ?? []);
|
|
7673
7893
|
while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
|
|
7674
7894
|
const keyword = ctx.advance().value;
|
|
7675
7895
|
const key = ctx.resolveKeyword(keyword).toLowerCase();
|
|
7676
|
-
let value =
|
|
7896
|
+
let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
|
|
7897
|
+
? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
|
|
7898
|
+
: ctx.parseExpression();
|
|
7677
7899
|
if (value && commaList.has(key) && ctx.check(',')) {
|
|
7678
7900
|
const elements = [value];
|
|
7679
7901
|
while (ctx.match(',')) {
|
|
@@ -7838,8 +8060,8 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7838
8060
|
}
|
|
7839
8061
|
if (ctx.check('with') && !modifiers['with']) {
|
|
7840
8062
|
ctx.advance();
|
|
7841
|
-
if (
|
|
7842
|
-
modifiers['with'] =
|
|
8063
|
+
if (isNakedNamedArgStart(ctx)) {
|
|
8064
|
+
modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
|
|
7843
8065
|
}
|
|
7844
8066
|
else {
|
|
7845
8067
|
modifiers['with'] = ctx.parsePrimary();
|
|
@@ -7874,45 +8096,6 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7874
8096
|
}
|
|
7875
8097
|
return builder.build();
|
|
7876
8098
|
}
|
|
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
8099
|
function findJsEndBoundary(ctx, startPos) {
|
|
7917
8100
|
const input = ctx.getInputSlice(startPos);
|
|
7918
8101
|
if (!input) {
|
|
@@ -8032,6 +8215,7 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8032
8215
|
const commands = [];
|
|
8033
8216
|
while (!ctx.isAtEnd()) {
|
|
8034
8217
|
if (ctx.checkIsCommand()) {
|
|
8218
|
+
const word = ctx.peek().value;
|
|
8035
8219
|
try {
|
|
8036
8220
|
ctx.advance();
|
|
8037
8221
|
const cmd = ctx.parseCommand();
|
|
@@ -8042,7 +8226,8 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8042
8226
|
break;
|
|
8043
8227
|
}
|
|
8044
8228
|
}
|
|
8045
|
-
catch {
|
|
8229
|
+
catch (error) {
|
|
8230
|
+
ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
8046
8231
|
break;
|
|
8047
8232
|
}
|
|
8048
8233
|
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
@@ -8191,7 +8376,6 @@ const ONE_EXPR = (syntax) => ({
|
|
|
8191
8376
|
markers: [],
|
|
8192
8377
|
syntax,
|
|
8193
8378
|
});
|
|
8194
|
-
const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
|
|
8195
8379
|
const COMMAND_GRAMMAR = {
|
|
8196
8380
|
append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
|
|
8197
8381
|
prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
|
|
@@ -8202,8 +8386,7 @@ const COMMAND_GRAMMAR = {
|
|
|
8202
8386
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
8203
8387
|
},
|
|
8204
8388
|
throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
|
|
8205
|
-
'beep!'
|
|
8206
|
-
beep: BEEP,
|
|
8389
|
+
beep: ONE_EXPR('beep! [<expression>, \u2026]'),
|
|
8207
8390
|
blur: ONE_EXPR('blur [<target>]'),
|
|
8208
8391
|
break: NONE,
|
|
8209
8392
|
breakpoint: NONE,
|
|
@@ -8227,7 +8410,8 @@ const COMMAND_GRAMMAR = {
|
|
|
8227
8410
|
render: {
|
|
8228
8411
|
positional: 'expression',
|
|
8229
8412
|
markers: ['with'],
|
|
8230
|
-
|
|
8413
|
+
namedArgs: ['with'],
|
|
8414
|
+
syntax: 'render <template> [with <name>: <value>, \u2026]',
|
|
8231
8415
|
},
|
|
8232
8416
|
reset: ONE_EXPR('reset [<form>]'),
|
|
8233
8417
|
return: ONE_EXPR('return [<value>]'),
|
|
@@ -8582,7 +8766,10 @@ class Parser {
|
|
|
8582
8766
|
const firstToken = this.peek();
|
|
8583
8767
|
const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
|
|
8584
8768
|
let left;
|
|
8585
|
-
if (
|
|
8769
|
+
if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
|
|
8770
|
+
left = this.parseBeepExpression();
|
|
8771
|
+
}
|
|
8772
|
+
else if (prefixEntry?.prefix) {
|
|
8586
8773
|
const token = this.advance();
|
|
8587
8774
|
if (this.isAtEnd()) {
|
|
8588
8775
|
this.addError(`Expected expression after '${token.value}' operator`);
|
|
@@ -8602,6 +8789,9 @@ class Parser {
|
|
|
8602
8789
|
continue;
|
|
8603
8790
|
}
|
|
8604
8791
|
}
|
|
8792
|
+
if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
|
|
8793
|
+
break;
|
|
8794
|
+
}
|
|
8605
8795
|
const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
|
|
8606
8796
|
if (!infixEntry?.infix) {
|
|
8607
8797
|
if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
|
|
@@ -8663,6 +8853,35 @@ class Parser {
|
|
|
8663
8853
|
this.prattCache.set(cacheKey, { node: left, endPos: this.current });
|
|
8664
8854
|
return left;
|
|
8665
8855
|
}
|
|
8856
|
+
parseBeepExpression() {
|
|
8857
|
+
const beepToken = this.advance();
|
|
8858
|
+
this.advance();
|
|
8859
|
+
if (this.isAtEnd()) {
|
|
8860
|
+
this.addError("Expected expression after 'beep!'");
|
|
8861
|
+
return this.createErrorNode();
|
|
8862
|
+
}
|
|
8863
|
+
const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
|
|
8864
|
+
return {
|
|
8865
|
+
type: 'unaryExpression',
|
|
8866
|
+
operator: 'beep!',
|
|
8867
|
+
operand,
|
|
8868
|
+
argument: operand,
|
|
8869
|
+
prefix: true,
|
|
8870
|
+
start: beepToken.start,
|
|
8871
|
+
end: this.previous().end,
|
|
8872
|
+
line: beepToken.line,
|
|
8873
|
+
column: beepToken.column,
|
|
8874
|
+
};
|
|
8875
|
+
}
|
|
8876
|
+
isStyleRefStar(i) {
|
|
8877
|
+
const star = this.tokens[i];
|
|
8878
|
+
const name = this.tokens[i + 1];
|
|
8879
|
+
return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
|
|
8880
|
+
}
|
|
8881
|
+
isSpacedStyleRef(i) {
|
|
8882
|
+
const prev = this.tokens[i - 1];
|
|
8883
|
+
return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
|
|
8884
|
+
}
|
|
8666
8885
|
tryParseStringPostfix(left, nextToken) {
|
|
8667
8886
|
const v = nextToken.value;
|
|
8668
8887
|
let unit = null;
|
|
@@ -8865,6 +9084,10 @@ class Parser {
|
|
|
8865
9084
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
8866
9085
|
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
8867
9086
|
while (!this.isAtEnd() && !isStop()) {
|
|
9087
|
+
if (this.checkComment()) {
|
|
9088
|
+
this.advance();
|
|
9089
|
+
continue;
|
|
9090
|
+
}
|
|
8868
9091
|
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
8869
9092
|
let parsedCommand = false;
|
|
8870
9093
|
const isCommandToken = this.checkIsCommand();
|
|
@@ -8948,7 +9171,7 @@ class Parser {
|
|
|
8948
9171
|
debug.parse('\u2705 Found "end", consuming it');
|
|
8949
9172
|
this.advance();
|
|
8950
9173
|
}
|
|
8951
|
-
else {
|
|
9174
|
+
else if (!this.atEndOfInput()) {
|
|
8952
9175
|
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
8953
9176
|
throw new Error(`Expected "end" to close ${construct} block`);
|
|
8954
9177
|
}
|
|
@@ -8961,7 +9184,7 @@ class Parser {
|
|
|
8961
9184
|
if (this.check('end')) {
|
|
8962
9185
|
this.advance();
|
|
8963
9186
|
}
|
|
8964
|
-
else {
|
|
9187
|
+
else if (!this.atEndOfInput()) {
|
|
8965
9188
|
throw new Error('Expected "end" to close repeat block');
|
|
8966
9189
|
}
|
|
8967
9190
|
}
|
|
@@ -8977,7 +9200,10 @@ class Parser {
|
|
|
8977
9200
|
this.advance();
|
|
8978
9201
|
}
|
|
8979
9202
|
else if (terminator === '') {
|
|
8980
|
-
|
|
9203
|
+
if (!this.atEndOfInput()) {
|
|
9204
|
+
throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
|
|
9205
|
+
}
|
|
9206
|
+
return { commands, terminator: 'end' };
|
|
8981
9207
|
}
|
|
8982
9208
|
return { commands, terminator };
|
|
8983
9209
|
}
|
|
@@ -9561,7 +9787,9 @@ class Parser {
|
|
|
9561
9787
|
parseTopLevelInitBlock() {
|
|
9562
9788
|
const pos = this.getPosition();
|
|
9563
9789
|
const initCommands = this.parseCommandBlock(['end']);
|
|
9564
|
-
this.
|
|
9790
|
+
if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
|
|
9791
|
+
this.consume('end', "Expected 'end' after init block");
|
|
9792
|
+
}
|
|
9565
9793
|
return {
|
|
9566
9794
|
type: 'initBlock',
|
|
9567
9795
|
commands: initCommands,
|
|
@@ -9737,10 +9965,29 @@ class Parser {
|
|
|
9737
9965
|
...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
|
|
9738
9966
|
};
|
|
9739
9967
|
}
|
|
9968
|
+
isArgumentListOnly(open) {
|
|
9969
|
+
let depth = 0;
|
|
9970
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
9971
|
+
const token = this.tokens[i];
|
|
9972
|
+
if (token.kind !== 'operator')
|
|
9973
|
+
continue;
|
|
9974
|
+
if (token.value === '(' || token.value === '[' || token.value === '{')
|
|
9975
|
+
depth++;
|
|
9976
|
+
else if (token.value === ')' || token.value === ']' || token.value === '}') {
|
|
9977
|
+
if (--depth === 0)
|
|
9978
|
+
return i === open + 1;
|
|
9979
|
+
}
|
|
9980
|
+
else if (token.value === ',' && depth === 1)
|
|
9981
|
+
return true;
|
|
9982
|
+
}
|
|
9983
|
+
return false;
|
|
9984
|
+
}
|
|
9740
9985
|
tryParsePseudoCommand(callExpr, sourceExpr) {
|
|
9741
9986
|
const methodName = callExpr.callee.name;
|
|
9742
9987
|
const nextToken = this.peek();
|
|
9743
|
-
const
|
|
9988
|
+
const nextIsArticle = nextToken.value.toLowerCase() === 'the';
|
|
9989
|
+
const hasPseudoCommandPattern = nextIsArticle ||
|
|
9990
|
+
Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
|
|
9744
9991
|
(isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
|
|
9745
9992
|
isContextVar(nextToken);
|
|
9746
9993
|
if (!hasPseudoCommandPattern) {
|
|
@@ -9766,7 +10013,10 @@ class Parser {
|
|
|
9766
10013
|
});
|
|
9767
10014
|
}
|
|
9768
10015
|
let preposition;
|
|
9769
|
-
if (
|
|
10016
|
+
if (nextIsArticle) {
|
|
10017
|
+
this.advance();
|
|
10018
|
+
}
|
|
10019
|
+
else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
|
|
9770
10020
|
preposition = this.advance().value.toLowerCase();
|
|
9771
10021
|
}
|
|
9772
10022
|
let targetExpr;
|
|
@@ -9930,10 +10180,22 @@ class Parser {
|
|
|
9930
10180
|
this.consume(']', "Expected ']' after event condition");
|
|
9931
10181
|
}
|
|
9932
10182
|
let target;
|
|
10183
|
+
let targetExpression;
|
|
9933
10184
|
if (this.match('from')) {
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
10185
|
+
if (this.check('the'))
|
|
10186
|
+
this.advance();
|
|
10187
|
+
if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
|
|
10188
|
+
const startIndex = this.current;
|
|
10189
|
+
targetExpression = this.parseExpression();
|
|
10190
|
+
target = this.tokens
|
|
10191
|
+
.slice(startIndex, this.current)
|
|
10192
|
+
.map(t => t.value)
|
|
10193
|
+
.join(' ');
|
|
10194
|
+
}
|
|
10195
|
+
else {
|
|
10196
|
+
target = this.advance().value;
|
|
10197
|
+
}
|
|
10198
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
|
|
9937
10199
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
9938
10200
|
}
|
|
9939
10201
|
let attributeName;
|
|
@@ -9979,7 +10241,7 @@ class Parser {
|
|
|
9979
10241
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
9980
10242
|
const commandName = this.peek().value.toLowerCase();
|
|
9981
10243
|
const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
|
|
9982
|
-
if (nextIsOpenParen && !isSpecialBodyCommand) {
|
|
10244
|
+
if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
|
|
9983
10245
|
let expr;
|
|
9984
10246
|
const savedError = this.error;
|
|
9985
10247
|
try {
|
|
@@ -10011,7 +10273,7 @@ class Parser {
|
|
|
10011
10273
|
}
|
|
10012
10274
|
}
|
|
10013
10275
|
}
|
|
10014
|
-
else if (this.checkIdentifier()) {
|
|
10276
|
+
else if (this.checkIdentifier() || this.checkEventNamedCall()) {
|
|
10015
10277
|
const token = this.peek();
|
|
10016
10278
|
if (this.isCommand(token.value)) {
|
|
10017
10279
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
@@ -10120,6 +10382,7 @@ class Parser {
|
|
|
10120
10382
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
10121
10383
|
...(condition && { condition: fromLegacyExpression(condition) }),
|
|
10122
10384
|
...(target && { target }),
|
|
10385
|
+
...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
|
|
10123
10386
|
...(attributeName && { attributeName }),
|
|
10124
10387
|
...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
|
|
10125
10388
|
...(customEventSource && { customEventSource }),
|
|
@@ -10375,10 +10638,9 @@ class Parser {
|
|
|
10375
10638
|
}
|
|
10376
10639
|
parseCommandCore() {
|
|
10377
10640
|
const commandToken = this.previous();
|
|
10378
|
-
|
|
10641
|
+
const commandName = commandToken.value;
|
|
10379
10642
|
if (commandName === 'beep' && this.check('!')) {
|
|
10380
10643
|
this.advance();
|
|
10381
|
-
commandName = 'beep!';
|
|
10382
10644
|
}
|
|
10383
10645
|
if (commandName === 'fetch') {
|
|
10384
10646
|
return parseFetchCommand(this.getContext(), commandToken);
|
|
@@ -10477,6 +10739,9 @@ class Parser {
|
|
|
10477
10739
|
args.push(this.parsePrimary());
|
|
10478
10740
|
}
|
|
10479
10741
|
const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
|
|
10742
|
+
if (funcName === 'closest' && args.length > 0 && this.match('to')) {
|
|
10743
|
+
callNode.closestTo = this.parseExpression();
|
|
10744
|
+
}
|
|
10480
10745
|
if (funcName === 'next' || funcName === 'previous') {
|
|
10481
10746
|
const relativePositional = this.parseRelativePositionalModifiers();
|
|
10482
10747
|
if (relativePositional) {
|
|
@@ -10516,6 +10781,9 @@ class Parser {
|
|
|
10516
10781
|
}
|
|
10517
10782
|
parseTheXofY() {
|
|
10518
10783
|
const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
|
|
10784
|
+
if (this.isStyleRefStar(this.current)) {
|
|
10785
|
+
return this.parsePrimary();
|
|
10786
|
+
}
|
|
10519
10787
|
if (!this.checkIdentifier()) {
|
|
10520
10788
|
return this.createIdentifier('the');
|
|
10521
10789
|
}
|
|
@@ -10531,7 +10799,7 @@ class Parser {
|
|
|
10531
10799
|
return this.createIdentifier(propertyName);
|
|
10532
10800
|
}
|
|
10533
10801
|
this.advance();
|
|
10534
|
-
const target = this.
|
|
10802
|
+
const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
|
|
10535
10803
|
return {
|
|
10536
10804
|
type: 'propertyOfExpression',
|
|
10537
10805
|
property: { type: 'identifier', name: propertyName },
|
|
@@ -10729,6 +10997,13 @@ class Parser {
|
|
|
10729
10997
|
checkLiteral() {
|
|
10730
10998
|
return this.checkPredicate(isLiteral);
|
|
10731
10999
|
}
|
|
11000
|
+
checkEventNamedCall() {
|
|
11001
|
+
if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
|
|
11002
|
+
return false;
|
|
11003
|
+
const token = this.peek();
|
|
11004
|
+
const next = this.tokens[this.current + 1];
|
|
11005
|
+
return next?.value === '(' && next.start === token.end;
|
|
11006
|
+
}
|
|
10732
11007
|
checkIdentifierLike() {
|
|
10733
11008
|
return this.checkPredicate(isIdentifierLike);
|
|
10734
11009
|
}
|
|
@@ -10837,6 +11112,22 @@ class Parser {
|
|
|
10837
11112
|
isAtEnd() {
|
|
10838
11113
|
return this.current >= this.tokens.length;
|
|
10839
11114
|
}
|
|
11115
|
+
atEndOfInput() {
|
|
11116
|
+
for (let i = this.current; i < this.tokens.length; i++) {
|
|
11117
|
+
if (!isComment(this.tokens[i]))
|
|
11118
|
+
return false;
|
|
11119
|
+
}
|
|
11120
|
+
return true;
|
|
11121
|
+
}
|
|
11122
|
+
checkFeatureStart() {
|
|
11123
|
+
if (this.isAtEnd())
|
|
11124
|
+
return false;
|
|
11125
|
+
const value = this.peek().value;
|
|
11126
|
+
return (value === 'init' ||
|
|
11127
|
+
value === 'on' ||
|
|
11128
|
+
value === 'def' ||
|
|
11129
|
+
getRegisteredFeature(value) !== undefined);
|
|
11130
|
+
}
|
|
10840
11131
|
peek() {
|
|
10841
11132
|
if (this.isAtEnd()) {
|
|
10842
11133
|
return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
|
|
@@ -11048,6 +11339,7 @@ class Parser {
|
|
|
11048
11339
|
match: this.match.bind(this),
|
|
11049
11340
|
matchOperator: this.matchOperator.bind(this),
|
|
11050
11341
|
isAtEnd: this.isAtEnd.bind(this),
|
|
11342
|
+
atEndOfInput: this.atEndOfInput.bind(this),
|
|
11051
11343
|
createIdentifier: this.createIdentifier.bind(this),
|
|
11052
11344
|
parseExpression: this.parseExpression.bind(this),
|
|
11053
11345
|
parsePrimary: this.parsePrimary.bind(this),
|
|
@@ -11098,7 +11390,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
|
|
|
11098
11390
|
'ignoring case',
|
|
11099
11391
|
]);
|
|
11100
11392
|
Parser.PRATT_TABLE = PARSER_TABLE;
|
|
11393
|
+
Parser.BEEP_OPERAND_BP = 80;
|
|
11101
11394
|
Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
|
|
11395
|
+
Parser.POSITIONAL_SOURCE_HEADS = new Set([
|
|
11396
|
+
'closest',
|
|
11397
|
+
'next',
|
|
11398
|
+
'previous',
|
|
11399
|
+
'first',
|
|
11400
|
+
'last',
|
|
11401
|
+
]);
|
|
11102
11402
|
function parse(input, options) {
|
|
11103
11403
|
const tokens = tokenize(input);
|
|
11104
11404
|
const parser = new Parser(tokens, options, input);
|
|
@@ -11540,11 +11840,16 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11540
11840
|
}
|
|
11541
11841
|
}
|
|
11542
11842
|
}
|
|
11543
|
-
if (leftNode?.type === 'selector' &&
|
|
11843
|
+
if (leftNode?.type === 'selector' &&
|
|
11844
|
+
typeof leftNode.value === 'string' &&
|
|
11845
|
+
(leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
|
|
11544
11846
|
const root = await evaluateAST(node.right, context);
|
|
11545
11847
|
const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
|
|
11546
11848
|
if (scope) {
|
|
11547
|
-
|
|
11849
|
+
const query = leftNode.fromQuery
|
|
11850
|
+
? leftNode.value
|
|
11851
|
+
: escapeSelectorForQuery(leftNode, leftNode.value);
|
|
11852
|
+
return Array.from(scope.querySelectorAll(query));
|
|
11548
11853
|
}
|
|
11549
11854
|
}
|
|
11550
11855
|
}
|
|
@@ -11556,6 +11861,12 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11556
11861
|
return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
|
|
11557
11862
|
}
|
|
11558
11863
|
if (operator === 'of') {
|
|
11864
|
+
if (leftNode?.type === 'attributeAccess') {
|
|
11865
|
+
const target = await evaluateAST(node.right, context);
|
|
11866
|
+
if (target == null)
|
|
11867
|
+
return undefined;
|
|
11868
|
+
return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
|
|
11869
|
+
}
|
|
11559
11870
|
const path = propertyPathOf(node.left);
|
|
11560
11871
|
if (path) {
|
|
11561
11872
|
const target = await evaluateAST(node.right, context);
|
|
@@ -11563,6 +11874,10 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11563
11874
|
for (const seg of path) {
|
|
11564
11875
|
if (cur == null)
|
|
11565
11876
|
return undefined;
|
|
11877
|
+
if (isMappableCollection(cur) && !(seg in cur)) {
|
|
11878
|
+
cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
|
|
11879
|
+
continue;
|
|
11880
|
+
}
|
|
11566
11881
|
cur = isElement(cur) ? getElementProperty(cur, seg) : cur[seg];
|
|
11567
11882
|
}
|
|
11568
11883
|
return typeof cur === 'function' ? cur.bind(target) : cur;
|
|
@@ -11587,26 +11902,21 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11587
11902
|
const R = node.ignoringCase ? applyCI(right) : right;
|
|
11588
11903
|
switch (operator) {
|
|
11589
11904
|
case '+': {
|
|
11590
|
-
let addLeft = left;
|
|
11591
|
-
let addRight = right;
|
|
11592
11905
|
if (node.coerceNumeric) {
|
|
11593
|
-
|
|
11594
|
-
addLeft = convertToNumber(addLeft);
|
|
11595
|
-
if (typeof addRight === 'string')
|
|
11596
|
-
addRight = convertToNumber(addRight);
|
|
11906
|
+
return counterValue(left) + counterAmount(right);
|
|
11597
11907
|
}
|
|
11598
|
-
if (typeof
|
|
11599
|
-
return String(
|
|
11908
|
+
if (typeof left === 'string' || typeof right === 'string') {
|
|
11909
|
+
return String(left ?? '') + String(right ?? '');
|
|
11600
11910
|
}
|
|
11601
|
-
if (Array.isArray(
|
|
11602
|
-
return Array.isArray(
|
|
11911
|
+
if (Array.isArray(left)) {
|
|
11912
|
+
return Array.isArray(right) ? [...left, ...right] : [...left, right];
|
|
11603
11913
|
}
|
|
11604
|
-
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
|
|
11605
|
-
left: addLeft,
|
|
11606
|
-
right: addRight,
|
|
11607
|
-
}));
|
|
11914
|
+
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
|
|
11608
11915
|
}
|
|
11609
11916
|
case '-':
|
|
11917
|
+
if (node.coerceNumeric) {
|
|
11918
|
+
return counterValue(left) - counterAmount(right);
|
|
11919
|
+
}
|
|
11610
11920
|
return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
|
|
11611
11921
|
case '*':
|
|
11612
11922
|
return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
|
|
@@ -11864,9 +12174,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
|
|
|
11864
12174
|
}
|
|
11865
12175
|
const propertyName = propertyNode.name;
|
|
11866
12176
|
const target = await evaluateAST(node.target, context);
|
|
11867
|
-
if (target == null)
|
|
11868
|
-
|
|
11869
|
-
}
|
|
12177
|
+
if (target == null)
|
|
12178
|
+
return undefined;
|
|
11870
12179
|
return getExpr(context, 'possessive').evaluate(context, target, propertyName);
|
|
11871
12180
|
}
|
|
11872
12181
|
async function evaluateTemplateLiteralNode(node, context) {
|
|
@@ -11984,6 +12293,9 @@ async function evaluateUnaryExpression(node, context) {
|
|
|
11984
12293
|
case 'not':
|
|
11985
12294
|
case '!':
|
|
11986
12295
|
return logicalExpressions.not.evaluate(context, value);
|
|
12296
|
+
case 'beep!':
|
|
12297
|
+
beepValues(context.me, [value]);
|
|
12298
|
+
return value;
|
|
11987
12299
|
case 'no':
|
|
11988
12300
|
return logicalExpressions.no.evaluate(context, value);
|
|
11989
12301
|
case '-':
|
|
@@ -12073,6 +12385,10 @@ async function evaluateCallExpression(node, context) {
|
|
|
12073
12385
|
const args = await resolveCallArgs(node.arguments, funcName, context);
|
|
12074
12386
|
switch (funcName) {
|
|
12075
12387
|
case 'closest':
|
|
12388
|
+
if (node.closestTo) {
|
|
12389
|
+
const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
|
|
12390
|
+
return referencesExpressions.closest.evaluate(context, args[0], start);
|
|
12391
|
+
}
|
|
12076
12392
|
return referencesExpressions.closest.evaluate(context, ...args);
|
|
12077
12393
|
case 'previous':
|
|
12078
12394
|
case 'next': {
|