@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.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')) {
|
|
@@ -6872,11 +7083,17 @@ function parseSwapCommand(ctx, identifierNode) {
|
|
|
6872
7083
|
const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
|
|
6873
7084
|
if (targetExpr)
|
|
6874
7085
|
args.push(targetExpr);
|
|
6875
|
-
|
|
7086
|
+
const isMorph = identifierNode.name === 'morph';
|
|
7087
|
+
if (!isDelete &&
|
|
7088
|
+
(consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
|
|
7089
|
+
(isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
|
|
6876
7090
|
const contentExpr = parseOneArgument(ctx, ['using']);
|
|
6877
7091
|
if (contentExpr)
|
|
6878
7092
|
modifiers['with'] = contentExpr;
|
|
6879
7093
|
}
|
|
7094
|
+
if (isMorph && modifiers['with'] === undefined) {
|
|
7095
|
+
ctx.addError("Expected 'to' and the content to morph into");
|
|
7096
|
+
}
|
|
6880
7097
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
6881
7098
|
.withArgs(...args)
|
|
6882
7099
|
.withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
|
|
@@ -7637,6 +7854,8 @@ function atArgumentBoundary(ctx, grammar) {
|
|
|
7637
7854
|
const next = ctx.peek();
|
|
7638
7855
|
if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
|
|
7639
7856
|
return false;
|
|
7857
|
+
if (isGluedBeepBang(next, ctx.peekAt(1)))
|
|
7858
|
+
return false;
|
|
7640
7859
|
if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
|
|
7641
7860
|
return true;
|
|
7642
7861
|
return isKeyword(next, [...grammar.markers]);
|
|
@@ -7668,10 +7887,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
|
|
|
7668
7887
|
}
|
|
7669
7888
|
}
|
|
7670
7889
|
const commaList = new Set(grammar.commaList ?? []);
|
|
7890
|
+
const namedArgs = new Set(grammar.namedArgs ?? []);
|
|
7671
7891
|
while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
|
|
7672
7892
|
const keyword = ctx.advance().value;
|
|
7673
7893
|
const key = ctx.resolveKeyword(keyword).toLowerCase();
|
|
7674
|
-
let value =
|
|
7894
|
+
let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
|
|
7895
|
+
? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
|
|
7896
|
+
: ctx.parseExpression();
|
|
7675
7897
|
if (value && commaList.has(key) && ctx.check(',')) {
|
|
7676
7898
|
const elements = [value];
|
|
7677
7899
|
while (ctx.match(',')) {
|
|
@@ -7836,8 +8058,8 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7836
8058
|
}
|
|
7837
8059
|
if (ctx.check('with') && !modifiers['with']) {
|
|
7838
8060
|
ctx.advance();
|
|
7839
|
-
if (
|
|
7840
|
-
modifiers['with'] =
|
|
8061
|
+
if (isNakedNamedArgStart(ctx)) {
|
|
8062
|
+
modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
|
|
7841
8063
|
}
|
|
7842
8064
|
else {
|
|
7843
8065
|
modifiers['with'] = ctx.parsePrimary();
|
|
@@ -7872,45 +8094,6 @@ function parseFetchCommand(ctx, commandToken) {
|
|
|
7872
8094
|
}
|
|
7873
8095
|
return builder.build();
|
|
7874
8096
|
}
|
|
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
8097
|
function findJsEndBoundary(ctx, startPos) {
|
|
7915
8098
|
const input = ctx.getInputSlice(startPos);
|
|
7916
8099
|
if (!input) {
|
|
@@ -8030,6 +8213,7 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8030
8213
|
const commands = [];
|
|
8031
8214
|
while (!ctx.isAtEnd()) {
|
|
8032
8215
|
if (ctx.checkIsCommand()) {
|
|
8216
|
+
const word = ctx.peek().value;
|
|
8033
8217
|
try {
|
|
8034
8218
|
ctx.advance();
|
|
8035
8219
|
const cmd = ctx.parseCommand();
|
|
@@ -8040,7 +8224,8 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
8040
8224
|
break;
|
|
8041
8225
|
}
|
|
8042
8226
|
}
|
|
8043
|
-
catch {
|
|
8227
|
+
catch (error) {
|
|
8228
|
+
ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
8044
8229
|
break;
|
|
8045
8230
|
}
|
|
8046
8231
|
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
@@ -8189,7 +8374,6 @@ const ONE_EXPR = (syntax) => ({
|
|
|
8189
8374
|
markers: [],
|
|
8190
8375
|
syntax,
|
|
8191
8376
|
});
|
|
8192
|
-
const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
|
|
8193
8377
|
const COMMAND_GRAMMAR = {
|
|
8194
8378
|
append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
|
|
8195
8379
|
prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
|
|
@@ -8200,8 +8384,7 @@ const COMMAND_GRAMMAR = {
|
|
|
8200
8384
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
8201
8385
|
},
|
|
8202
8386
|
throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
|
|
8203
|
-
'beep!'
|
|
8204
|
-
beep: BEEP,
|
|
8387
|
+
beep: ONE_EXPR('beep! [<expression>, \u2026]'),
|
|
8205
8388
|
blur: ONE_EXPR('blur [<target>]'),
|
|
8206
8389
|
break: NONE,
|
|
8207
8390
|
breakpoint: NONE,
|
|
@@ -8225,7 +8408,8 @@ const COMMAND_GRAMMAR = {
|
|
|
8225
8408
|
render: {
|
|
8226
8409
|
positional: 'expression',
|
|
8227
8410
|
markers: ['with'],
|
|
8228
|
-
|
|
8411
|
+
namedArgs: ['with'],
|
|
8412
|
+
syntax: 'render <template> [with <name>: <value>, \u2026]',
|
|
8229
8413
|
},
|
|
8230
8414
|
reset: ONE_EXPR('reset [<form>]'),
|
|
8231
8415
|
return: ONE_EXPR('return [<value>]'),
|
|
@@ -8580,7 +8764,10 @@ class Parser {
|
|
|
8580
8764
|
const firstToken = this.peek();
|
|
8581
8765
|
const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
|
|
8582
8766
|
let left;
|
|
8583
|
-
if (
|
|
8767
|
+
if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
|
|
8768
|
+
left = this.parseBeepExpression();
|
|
8769
|
+
}
|
|
8770
|
+
else if (prefixEntry?.prefix) {
|
|
8584
8771
|
const token = this.advance();
|
|
8585
8772
|
if (this.isAtEnd()) {
|
|
8586
8773
|
this.addError(`Expected expression after '${token.value}' operator`);
|
|
@@ -8600,6 +8787,9 @@ class Parser {
|
|
|
8600
8787
|
continue;
|
|
8601
8788
|
}
|
|
8602
8789
|
}
|
|
8790
|
+
if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
|
|
8791
|
+
break;
|
|
8792
|
+
}
|
|
8603
8793
|
const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
|
|
8604
8794
|
if (!infixEntry?.infix) {
|
|
8605
8795
|
if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
|
|
@@ -8661,6 +8851,35 @@ class Parser {
|
|
|
8661
8851
|
this.prattCache.set(cacheKey, { node: left, endPos: this.current });
|
|
8662
8852
|
return left;
|
|
8663
8853
|
}
|
|
8854
|
+
parseBeepExpression() {
|
|
8855
|
+
const beepToken = this.advance();
|
|
8856
|
+
this.advance();
|
|
8857
|
+
if (this.isAtEnd()) {
|
|
8858
|
+
this.addError("Expected expression after 'beep!'");
|
|
8859
|
+
return this.createErrorNode();
|
|
8860
|
+
}
|
|
8861
|
+
const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
|
|
8862
|
+
return {
|
|
8863
|
+
type: 'unaryExpression',
|
|
8864
|
+
operator: 'beep!',
|
|
8865
|
+
operand,
|
|
8866
|
+
argument: operand,
|
|
8867
|
+
prefix: true,
|
|
8868
|
+
start: beepToken.start,
|
|
8869
|
+
end: this.previous().end,
|
|
8870
|
+
line: beepToken.line,
|
|
8871
|
+
column: beepToken.column,
|
|
8872
|
+
};
|
|
8873
|
+
}
|
|
8874
|
+
isStyleRefStar(i) {
|
|
8875
|
+
const star = this.tokens[i];
|
|
8876
|
+
const name = this.tokens[i + 1];
|
|
8877
|
+
return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
|
|
8878
|
+
}
|
|
8879
|
+
isSpacedStyleRef(i) {
|
|
8880
|
+
const prev = this.tokens[i - 1];
|
|
8881
|
+
return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
|
|
8882
|
+
}
|
|
8664
8883
|
tryParseStringPostfix(left, nextToken) {
|
|
8665
8884
|
const v = nextToken.value;
|
|
8666
8885
|
let unit = null;
|
|
@@ -8863,6 +9082,10 @@ class Parser {
|
|
|
8863
9082
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
8864
9083
|
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
8865
9084
|
while (!this.isAtEnd() && !isStop()) {
|
|
9085
|
+
if (this.checkComment()) {
|
|
9086
|
+
this.advance();
|
|
9087
|
+
continue;
|
|
9088
|
+
}
|
|
8866
9089
|
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
8867
9090
|
let parsedCommand = false;
|
|
8868
9091
|
const isCommandToken = this.checkIsCommand();
|
|
@@ -8946,7 +9169,7 @@ class Parser {
|
|
|
8946
9169
|
debug.parse('\u2705 Found "end", consuming it');
|
|
8947
9170
|
this.advance();
|
|
8948
9171
|
}
|
|
8949
|
-
else {
|
|
9172
|
+
else if (!this.atEndOfInput()) {
|
|
8950
9173
|
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
8951
9174
|
throw new Error(`Expected "end" to close ${construct} block`);
|
|
8952
9175
|
}
|
|
@@ -8959,7 +9182,7 @@ class Parser {
|
|
|
8959
9182
|
if (this.check('end')) {
|
|
8960
9183
|
this.advance();
|
|
8961
9184
|
}
|
|
8962
|
-
else {
|
|
9185
|
+
else if (!this.atEndOfInput()) {
|
|
8963
9186
|
throw new Error('Expected "end" to close repeat block');
|
|
8964
9187
|
}
|
|
8965
9188
|
}
|
|
@@ -8975,7 +9198,10 @@ class Parser {
|
|
|
8975
9198
|
this.advance();
|
|
8976
9199
|
}
|
|
8977
9200
|
else if (terminator === '') {
|
|
8978
|
-
|
|
9201
|
+
if (!this.atEndOfInput()) {
|
|
9202
|
+
throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
|
|
9203
|
+
}
|
|
9204
|
+
return { commands, terminator: 'end' };
|
|
8979
9205
|
}
|
|
8980
9206
|
return { commands, terminator };
|
|
8981
9207
|
}
|
|
@@ -9559,7 +9785,9 @@ class Parser {
|
|
|
9559
9785
|
parseTopLevelInitBlock() {
|
|
9560
9786
|
const pos = this.getPosition();
|
|
9561
9787
|
const initCommands = this.parseCommandBlock(['end']);
|
|
9562
|
-
this.
|
|
9788
|
+
if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
|
|
9789
|
+
this.consume('end', "Expected 'end' after init block");
|
|
9790
|
+
}
|
|
9563
9791
|
return {
|
|
9564
9792
|
type: 'initBlock',
|
|
9565
9793
|
commands: initCommands,
|
|
@@ -9735,10 +9963,29 @@ class Parser {
|
|
|
9735
9963
|
...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
|
|
9736
9964
|
};
|
|
9737
9965
|
}
|
|
9966
|
+
isArgumentListOnly(open) {
|
|
9967
|
+
let depth = 0;
|
|
9968
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
9969
|
+
const token = this.tokens[i];
|
|
9970
|
+
if (token.kind !== 'operator')
|
|
9971
|
+
continue;
|
|
9972
|
+
if (token.value === '(' || token.value === '[' || token.value === '{')
|
|
9973
|
+
depth++;
|
|
9974
|
+
else if (token.value === ')' || token.value === ']' || token.value === '}') {
|
|
9975
|
+
if (--depth === 0)
|
|
9976
|
+
return i === open + 1;
|
|
9977
|
+
}
|
|
9978
|
+
else if (token.value === ',' && depth === 1)
|
|
9979
|
+
return true;
|
|
9980
|
+
}
|
|
9981
|
+
return false;
|
|
9982
|
+
}
|
|
9738
9983
|
tryParsePseudoCommand(callExpr, sourceExpr) {
|
|
9739
9984
|
const methodName = callExpr.callee.name;
|
|
9740
9985
|
const nextToken = this.peek();
|
|
9741
|
-
const
|
|
9986
|
+
const nextIsArticle = nextToken.value.toLowerCase() === 'the';
|
|
9987
|
+
const hasPseudoCommandPattern = nextIsArticle ||
|
|
9988
|
+
Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
|
|
9742
9989
|
(isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
|
|
9743
9990
|
isContextVar(nextToken);
|
|
9744
9991
|
if (!hasPseudoCommandPattern) {
|
|
@@ -9764,7 +10011,10 @@ class Parser {
|
|
|
9764
10011
|
});
|
|
9765
10012
|
}
|
|
9766
10013
|
let preposition;
|
|
9767
|
-
if (
|
|
10014
|
+
if (nextIsArticle) {
|
|
10015
|
+
this.advance();
|
|
10016
|
+
}
|
|
10017
|
+
else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
|
|
9768
10018
|
preposition = this.advance().value.toLowerCase();
|
|
9769
10019
|
}
|
|
9770
10020
|
let targetExpr;
|
|
@@ -9928,10 +10178,22 @@ class Parser {
|
|
|
9928
10178
|
this.consume(']', "Expected ']' after event condition");
|
|
9929
10179
|
}
|
|
9930
10180
|
let target;
|
|
10181
|
+
let targetExpression;
|
|
9931
10182
|
if (this.match('from')) {
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
10183
|
+
if (this.check('the'))
|
|
10184
|
+
this.advance();
|
|
10185
|
+
if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
|
|
10186
|
+
const startIndex = this.current;
|
|
10187
|
+
targetExpression = this.parseExpression();
|
|
10188
|
+
target = this.tokens
|
|
10189
|
+
.slice(startIndex, this.current)
|
|
10190
|
+
.map(t => t.value)
|
|
10191
|
+
.join(' ');
|
|
10192
|
+
}
|
|
10193
|
+
else {
|
|
10194
|
+
target = this.advance().value;
|
|
10195
|
+
}
|
|
10196
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
|
|
9935
10197
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
9936
10198
|
}
|
|
9937
10199
|
let attributeName;
|
|
@@ -9977,7 +10239,7 @@ class Parser {
|
|
|
9977
10239
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
9978
10240
|
const commandName = this.peek().value.toLowerCase();
|
|
9979
10241
|
const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
|
|
9980
|
-
if (nextIsOpenParen && !isSpecialBodyCommand) {
|
|
10242
|
+
if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
|
|
9981
10243
|
let expr;
|
|
9982
10244
|
const savedError = this.error;
|
|
9983
10245
|
try {
|
|
@@ -10009,7 +10271,7 @@ class Parser {
|
|
|
10009
10271
|
}
|
|
10010
10272
|
}
|
|
10011
10273
|
}
|
|
10012
|
-
else if (this.checkIdentifier()) {
|
|
10274
|
+
else if (this.checkIdentifier() || this.checkEventNamedCall()) {
|
|
10013
10275
|
const token = this.peek();
|
|
10014
10276
|
if (this.isCommand(token.value)) {
|
|
10015
10277
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
@@ -10118,6 +10380,7 @@ class Parser {
|
|
|
10118
10380
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
10119
10381
|
...(condition && { condition: fromLegacyExpression(condition) }),
|
|
10120
10382
|
...(target && { target }),
|
|
10383
|
+
...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
|
|
10121
10384
|
...(attributeName && { attributeName }),
|
|
10122
10385
|
...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
|
|
10123
10386
|
...(customEventSource && { customEventSource }),
|
|
@@ -10373,10 +10636,9 @@ class Parser {
|
|
|
10373
10636
|
}
|
|
10374
10637
|
parseCommandCore() {
|
|
10375
10638
|
const commandToken = this.previous();
|
|
10376
|
-
|
|
10639
|
+
const commandName = commandToken.value;
|
|
10377
10640
|
if (commandName === 'beep' && this.check('!')) {
|
|
10378
10641
|
this.advance();
|
|
10379
|
-
commandName = 'beep!';
|
|
10380
10642
|
}
|
|
10381
10643
|
if (commandName === 'fetch') {
|
|
10382
10644
|
return parseFetchCommand(this.getContext(), commandToken);
|
|
@@ -10475,6 +10737,9 @@ class Parser {
|
|
|
10475
10737
|
args.push(this.parsePrimary());
|
|
10476
10738
|
}
|
|
10477
10739
|
const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
|
|
10740
|
+
if (funcName === 'closest' && args.length > 0 && this.match('to')) {
|
|
10741
|
+
callNode.closestTo = this.parseExpression();
|
|
10742
|
+
}
|
|
10478
10743
|
if (funcName === 'next' || funcName === 'previous') {
|
|
10479
10744
|
const relativePositional = this.parseRelativePositionalModifiers();
|
|
10480
10745
|
if (relativePositional) {
|
|
@@ -10514,6 +10779,9 @@ class Parser {
|
|
|
10514
10779
|
}
|
|
10515
10780
|
parseTheXofY() {
|
|
10516
10781
|
const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
|
|
10782
|
+
if (this.isStyleRefStar(this.current)) {
|
|
10783
|
+
return this.parsePrimary();
|
|
10784
|
+
}
|
|
10517
10785
|
if (!this.checkIdentifier()) {
|
|
10518
10786
|
return this.createIdentifier('the');
|
|
10519
10787
|
}
|
|
@@ -10529,7 +10797,7 @@ class Parser {
|
|
|
10529
10797
|
return this.createIdentifier(propertyName);
|
|
10530
10798
|
}
|
|
10531
10799
|
this.advance();
|
|
10532
|
-
const target = this.
|
|
10800
|
+
const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
|
|
10533
10801
|
return {
|
|
10534
10802
|
type: 'propertyOfExpression',
|
|
10535
10803
|
property: { type: 'identifier', name: propertyName },
|
|
@@ -10727,6 +10995,13 @@ class Parser {
|
|
|
10727
10995
|
checkLiteral() {
|
|
10728
10996
|
return this.checkPredicate(isLiteral);
|
|
10729
10997
|
}
|
|
10998
|
+
checkEventNamedCall() {
|
|
10999
|
+
if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
|
|
11000
|
+
return false;
|
|
11001
|
+
const token = this.peek();
|
|
11002
|
+
const next = this.tokens[this.current + 1];
|
|
11003
|
+
return next?.value === '(' && next.start === token.end;
|
|
11004
|
+
}
|
|
10730
11005
|
checkIdentifierLike() {
|
|
10731
11006
|
return this.checkPredicate(isIdentifierLike);
|
|
10732
11007
|
}
|
|
@@ -10835,6 +11110,22 @@ class Parser {
|
|
|
10835
11110
|
isAtEnd() {
|
|
10836
11111
|
return this.current >= this.tokens.length;
|
|
10837
11112
|
}
|
|
11113
|
+
atEndOfInput() {
|
|
11114
|
+
for (let i = this.current; i < this.tokens.length; i++) {
|
|
11115
|
+
if (!isComment(this.tokens[i]))
|
|
11116
|
+
return false;
|
|
11117
|
+
}
|
|
11118
|
+
return true;
|
|
11119
|
+
}
|
|
11120
|
+
checkFeatureStart() {
|
|
11121
|
+
if (this.isAtEnd())
|
|
11122
|
+
return false;
|
|
11123
|
+
const value = this.peek().value;
|
|
11124
|
+
return (value === 'init' ||
|
|
11125
|
+
value === 'on' ||
|
|
11126
|
+
value === 'def' ||
|
|
11127
|
+
getRegisteredFeature(value) !== undefined);
|
|
11128
|
+
}
|
|
10838
11129
|
peek() {
|
|
10839
11130
|
if (this.isAtEnd()) {
|
|
10840
11131
|
return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
|
|
@@ -11046,6 +11337,7 @@ class Parser {
|
|
|
11046
11337
|
match: this.match.bind(this),
|
|
11047
11338
|
matchOperator: this.matchOperator.bind(this),
|
|
11048
11339
|
isAtEnd: this.isAtEnd.bind(this),
|
|
11340
|
+
atEndOfInput: this.atEndOfInput.bind(this),
|
|
11049
11341
|
createIdentifier: this.createIdentifier.bind(this),
|
|
11050
11342
|
parseExpression: this.parseExpression.bind(this),
|
|
11051
11343
|
parsePrimary: this.parsePrimary.bind(this),
|
|
@@ -11096,7 +11388,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
|
|
|
11096
11388
|
'ignoring case',
|
|
11097
11389
|
]);
|
|
11098
11390
|
Parser.PRATT_TABLE = PARSER_TABLE;
|
|
11391
|
+
Parser.BEEP_OPERAND_BP = 80;
|
|
11099
11392
|
Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
|
|
11393
|
+
Parser.POSITIONAL_SOURCE_HEADS = new Set([
|
|
11394
|
+
'closest',
|
|
11395
|
+
'next',
|
|
11396
|
+
'previous',
|
|
11397
|
+
'first',
|
|
11398
|
+
'last',
|
|
11399
|
+
]);
|
|
11100
11400
|
function parse(input, options) {
|
|
11101
11401
|
const tokens = tokenize(input);
|
|
11102
11402
|
const parser = new Parser(tokens, options, input);
|
|
@@ -11538,11 +11838,16 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11538
11838
|
}
|
|
11539
11839
|
}
|
|
11540
11840
|
}
|
|
11541
|
-
if (leftNode?.type === 'selector' &&
|
|
11841
|
+
if (leftNode?.type === 'selector' &&
|
|
11842
|
+
typeof leftNode.value === 'string' &&
|
|
11843
|
+
(leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
|
|
11542
11844
|
const root = await evaluateAST(node.right, context);
|
|
11543
11845
|
const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
|
|
11544
11846
|
if (scope) {
|
|
11545
|
-
|
|
11847
|
+
const query = leftNode.fromQuery
|
|
11848
|
+
? leftNode.value
|
|
11849
|
+
: escapeSelectorForQuery(leftNode, leftNode.value);
|
|
11850
|
+
return Array.from(scope.querySelectorAll(query));
|
|
11546
11851
|
}
|
|
11547
11852
|
}
|
|
11548
11853
|
}
|
|
@@ -11554,6 +11859,12 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11554
11859
|
return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
|
|
11555
11860
|
}
|
|
11556
11861
|
if (operator === 'of') {
|
|
11862
|
+
if (leftNode?.type === 'attributeAccess') {
|
|
11863
|
+
const target = await evaluateAST(node.right, context);
|
|
11864
|
+
if (target == null)
|
|
11865
|
+
return undefined;
|
|
11866
|
+
return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
|
|
11867
|
+
}
|
|
11557
11868
|
const path = propertyPathOf(node.left);
|
|
11558
11869
|
if (path) {
|
|
11559
11870
|
const target = await evaluateAST(node.right, context);
|
|
@@ -11561,6 +11872,10 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11561
11872
|
for (const seg of path) {
|
|
11562
11873
|
if (cur == null)
|
|
11563
11874
|
return undefined;
|
|
11875
|
+
if (isMappableCollection(cur) && !(seg in cur)) {
|
|
11876
|
+
cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
|
|
11877
|
+
continue;
|
|
11878
|
+
}
|
|
11564
11879
|
cur = isElement(cur) ? getElementProperty(cur, seg) : cur[seg];
|
|
11565
11880
|
}
|
|
11566
11881
|
return typeof cur === 'function' ? cur.bind(target) : cur;
|
|
@@ -11585,26 +11900,21 @@ async function evaluateBinaryExpression(node, context) {
|
|
|
11585
11900
|
const R = node.ignoringCase ? applyCI(right) : right;
|
|
11586
11901
|
switch (operator) {
|
|
11587
11902
|
case '+': {
|
|
11588
|
-
let addLeft = left;
|
|
11589
|
-
let addRight = right;
|
|
11590
11903
|
if (node.coerceNumeric) {
|
|
11591
|
-
|
|
11592
|
-
addLeft = convertToNumber(addLeft);
|
|
11593
|
-
if (typeof addRight === 'string')
|
|
11594
|
-
addRight = convertToNumber(addRight);
|
|
11904
|
+
return counterValue(left) + counterAmount(right);
|
|
11595
11905
|
}
|
|
11596
|
-
if (typeof
|
|
11597
|
-
return String(
|
|
11906
|
+
if (typeof left === 'string' || typeof right === 'string') {
|
|
11907
|
+
return String(left ?? '') + String(right ?? '');
|
|
11598
11908
|
}
|
|
11599
|
-
if (Array.isArray(
|
|
11600
|
-
return Array.isArray(
|
|
11909
|
+
if (Array.isArray(left)) {
|
|
11910
|
+
return Array.isArray(right) ? [...left, ...right] : [...left, right];
|
|
11601
11911
|
}
|
|
11602
|
-
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
|
|
11603
|
-
left: addLeft,
|
|
11604
|
-
right: addRight,
|
|
11605
|
-
}));
|
|
11912
|
+
return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
|
|
11606
11913
|
}
|
|
11607
11914
|
case '-':
|
|
11915
|
+
if (node.coerceNumeric) {
|
|
11916
|
+
return counterValue(left) - counterAmount(right);
|
|
11917
|
+
}
|
|
11608
11918
|
return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
|
|
11609
11919
|
case '*':
|
|
11610
11920
|
return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
|
|
@@ -11862,9 +12172,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
|
|
|
11862
12172
|
}
|
|
11863
12173
|
const propertyName = propertyNode.name;
|
|
11864
12174
|
const target = await evaluateAST(node.target, context);
|
|
11865
|
-
if (target == null)
|
|
11866
|
-
|
|
11867
|
-
}
|
|
12175
|
+
if (target == null)
|
|
12176
|
+
return undefined;
|
|
11868
12177
|
return getExpr(context, 'possessive').evaluate(context, target, propertyName);
|
|
11869
12178
|
}
|
|
11870
12179
|
async function evaluateTemplateLiteralNode(node, context) {
|
|
@@ -11982,6 +12291,9 @@ async function evaluateUnaryExpression(node, context) {
|
|
|
11982
12291
|
case 'not':
|
|
11983
12292
|
case '!':
|
|
11984
12293
|
return logicalExpressions.not.evaluate(context, value);
|
|
12294
|
+
case 'beep!':
|
|
12295
|
+
beepValues(context.me, [value]);
|
|
12296
|
+
return value;
|
|
11985
12297
|
case 'no':
|
|
11986
12298
|
return logicalExpressions.no.evaluate(context, value);
|
|
11987
12299
|
case '-':
|
|
@@ -12071,6 +12383,10 @@ async function evaluateCallExpression(node, context) {
|
|
|
12071
12383
|
const args = await resolveCallArgs(node.arguments, funcName, context);
|
|
12072
12384
|
switch (funcName) {
|
|
12073
12385
|
case 'closest':
|
|
12386
|
+
if (node.closestTo) {
|
|
12387
|
+
const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
|
|
12388
|
+
return referencesExpressions.closest.evaluate(context, args[0], start);
|
|
12389
|
+
}
|
|
12074
12390
|
return referencesExpressions.closest.evaluate(context, ...args);
|
|
12075
12391
|
case 'previous':
|
|
12076
12392
|
case 'next': {
|