@lokascript/core 1.1.4 → 1.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/dist/bundle-generator/index.d.ts +2 -1
- package/dist/bundle-generator/index.d.ts.map +1 -1
- package/dist/bundle-generator/index.js +1335 -9
- package/dist/bundle-generator/index.js.map +1 -1
- package/dist/bundle-generator/index.mjs +1330 -10
- package/dist/bundle-generator/index.mjs.map +1 -1
- package/dist/bundle-generator/parser-templates.d.ts +6 -0
- package/dist/bundle-generator/parser-templates.d.ts.map +1 -0
- package/dist/bundle-generator/template-capabilities.d.ts +2 -2
- package/dist/bundle-generator/template-capabilities.d.ts.map +1 -1
- package/dist/bundle-generator/templates.d.ts +1 -0
- package/dist/bundle-generator/templates.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/bundle-generator/index.ts +10 -0
- package/src/bundle-generator/parser-templates.ts +1158 -0
- package/src/bundle-generator/template-capabilities.ts +17 -14
- package/src/bundle-generator/templates.ts +228 -0
- package/src/bundle-generator/validation.test.ts +25 -3
|
@@ -217,6 +217,33 @@ const COMMAND_IMPLEMENTATIONS_TS = {
|
|
|
217
217
|
ctx.it = content;
|
|
218
218
|
return content;
|
|
219
219
|
}`,
|
|
220
|
+
morph: `
|
|
221
|
+
case 'morph': {
|
|
222
|
+
const targets = await getTarget();
|
|
223
|
+
const content = await evaluate(cmd.args[0], ctx);
|
|
224
|
+
const contentStr = String(content);
|
|
225
|
+
const isOuter = cmd.modifier === 'over';
|
|
226
|
+
|
|
227
|
+
for (const target of targets) {
|
|
228
|
+
try {
|
|
229
|
+
if (isOuter) {
|
|
230
|
+
morphlexMorph(target, contentStr);
|
|
231
|
+
} else {
|
|
232
|
+
morphlexMorphInner(target, contentStr);
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
// Fallback to innerHTML/outerHTML if morph fails
|
|
236
|
+
console.warn('[LokaScript] Morph failed, falling back:', error);
|
|
237
|
+
if (isOuter) {
|
|
238
|
+
target.outerHTML = contentStr;
|
|
239
|
+
} else {
|
|
240
|
+
target.innerHTML = contentStr;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
245
|
+
return ctx.it;
|
|
246
|
+
}`,
|
|
220
247
|
take: `
|
|
221
248
|
case 'take': {
|
|
222
249
|
const className = getClassName(await evaluate(cmd.args[0], ctx));
|
|
@@ -301,6 +328,188 @@ const COMMAND_IMPLEMENTATIONS_TS = {
|
|
|
301
328
|
case 'continue': {
|
|
302
329
|
throw { type: 'continue' };
|
|
303
330
|
}`,
|
|
331
|
+
halt: `
|
|
332
|
+
case 'halt': {
|
|
333
|
+
// Check for "halt the event" pattern
|
|
334
|
+
const firstArg = cmd.args[0];
|
|
335
|
+
let targetEvent = ctx.event;
|
|
336
|
+
if (firstArg?.type === 'identifier' && firstArg.name === 'the' && cmd.args[1]?.name === 'event') {
|
|
337
|
+
targetEvent = ctx.event;
|
|
338
|
+
} else if (firstArg) {
|
|
339
|
+
const evaluated = await evaluate(firstArg, ctx);
|
|
340
|
+
if (evaluated?.preventDefault) targetEvent = evaluated;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (targetEvent && typeof targetEvent.preventDefault === 'function') {
|
|
344
|
+
targetEvent.preventDefault();
|
|
345
|
+
targetEvent.stopPropagation();
|
|
346
|
+
return { halted: true, eventHalted: true };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Regular halt - stop execution
|
|
350
|
+
const haltError = new Error('HALT_EXECUTION');
|
|
351
|
+
(haltError as any).isHalt = true;
|
|
352
|
+
throw haltError;
|
|
353
|
+
}`,
|
|
354
|
+
exit: `
|
|
355
|
+
case 'exit': {
|
|
356
|
+
const exitError = new Error('EXIT_COMMAND');
|
|
357
|
+
(exitError as any).isExit = true;
|
|
358
|
+
throw exitError;
|
|
359
|
+
}`,
|
|
360
|
+
throw: `
|
|
361
|
+
case 'throw': {
|
|
362
|
+
const message = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : 'Error';
|
|
363
|
+
const errorToThrow = message instanceof Error ? message : new Error(String(message));
|
|
364
|
+
throw errorToThrow;
|
|
365
|
+
}`,
|
|
366
|
+
beep: `
|
|
367
|
+
case 'beep': {
|
|
368
|
+
const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
|
|
369
|
+
const displayValues = values.length > 0 ? values : [ctx.it];
|
|
370
|
+
|
|
371
|
+
for (const val of displayValues) {
|
|
372
|
+
const typeInfo = val === null ? 'null' :
|
|
373
|
+
val === undefined ? 'undefined' :
|
|
374
|
+
Array.isArray(val) ? \`Array[\${val.length}]\` :
|
|
375
|
+
val instanceof Element ? \`Element<\${val.tagName.toLowerCase()}>\` :
|
|
376
|
+
typeof val;
|
|
377
|
+
console.log('[beep]', typeInfo + ':', val);
|
|
378
|
+
}
|
|
379
|
+
return displayValues[0];
|
|
380
|
+
}`,
|
|
381
|
+
js: `
|
|
382
|
+
case 'js': {
|
|
383
|
+
const codeArg = cmd.args[0];
|
|
384
|
+
let jsCode = '';
|
|
385
|
+
|
|
386
|
+
if (codeArg.type === 'string') {
|
|
387
|
+
jsCode = codeArg.value;
|
|
388
|
+
} else if (codeArg.type === 'template') {
|
|
389
|
+
jsCode = await evaluate(codeArg, ctx);
|
|
390
|
+
} else {
|
|
391
|
+
jsCode = String(await evaluate(codeArg, ctx));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Build context object for the Function
|
|
395
|
+
const jsContext = {
|
|
396
|
+
me: ctx.me,
|
|
397
|
+
it: ctx.it,
|
|
398
|
+
event: ctx.event,
|
|
399
|
+
target: ctx.target || ctx.me,
|
|
400
|
+
locals: Object.fromEntries(ctx.locals),
|
|
401
|
+
globals: Object.fromEntries(globalVars),
|
|
402
|
+
document: typeof document !== 'undefined' ? document : undefined,
|
|
403
|
+
window: typeof window !== 'undefined' ? window : undefined,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const fn = new Function('ctx', \`with(ctx) { return (async () => { \${jsCode} })(); }\`);
|
|
408
|
+
const result = await fn(jsContext);
|
|
409
|
+
ctx.it = result;
|
|
410
|
+
return result;
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error('[js] Execution error:', error);
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
}`,
|
|
416
|
+
copy: `
|
|
417
|
+
case 'copy': {
|
|
418
|
+
const source = await evaluate(cmd.args[0], ctx);
|
|
419
|
+
let textToCopy = '';
|
|
420
|
+
|
|
421
|
+
if (typeof source === 'string') {
|
|
422
|
+
textToCopy = source;
|
|
423
|
+
} else if (source instanceof Element) {
|
|
424
|
+
textToCopy = source.textContent || '';
|
|
425
|
+
} else {
|
|
426
|
+
textToCopy = String(source);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
if (navigator.clipboard) {
|
|
431
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
432
|
+
} else {
|
|
433
|
+
// Fallback for older browsers
|
|
434
|
+
const textarea = document.createElement('textarea');
|
|
435
|
+
textarea.value = textToCopy;
|
|
436
|
+
textarea.style.cssText = 'position:fixed;top:0;left:-9999px';
|
|
437
|
+
document.body.appendChild(textarea);
|
|
438
|
+
textarea.select();
|
|
439
|
+
document.execCommand('copy');
|
|
440
|
+
document.body.removeChild(textarea);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (ctx.me instanceof Element) {
|
|
444
|
+
ctx.me.dispatchEvent(new CustomEvent('copy:success', {
|
|
445
|
+
bubbles: true,
|
|
446
|
+
detail: { text: textToCopy }
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
ctx.it = textToCopy;
|
|
450
|
+
return textToCopy;
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (ctx.me instanceof Element) {
|
|
453
|
+
ctx.me.dispatchEvent(new CustomEvent('copy:error', {
|
|
454
|
+
bubbles: true,
|
|
455
|
+
detail: { error }
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}`,
|
|
461
|
+
push: `
|
|
462
|
+
case 'push':
|
|
463
|
+
case 'push-url': {
|
|
464
|
+
// Handle "push url '/path'" pattern
|
|
465
|
+
let urlArg = cmd.args[0];
|
|
466
|
+
if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
|
|
467
|
+
urlArg = cmd.args[1];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const url = String(await evaluate(urlArg, ctx));
|
|
471
|
+
let title = '';
|
|
472
|
+
|
|
473
|
+
// Check for "with title" modifier
|
|
474
|
+
if (cmd.modifiers?.title) {
|
|
475
|
+
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
window.history.pushState(null, '', url);
|
|
479
|
+
if (title) document.title = title;
|
|
480
|
+
|
|
481
|
+
window.dispatchEvent(new CustomEvent('lokascript:pushurl', {
|
|
482
|
+
detail: { url, title }
|
|
483
|
+
}));
|
|
484
|
+
|
|
485
|
+
return { url, title, mode: 'push' };
|
|
486
|
+
}`,
|
|
487
|
+
replace: `
|
|
488
|
+
case 'replace':
|
|
489
|
+
case 'replace-url': {
|
|
490
|
+
// Handle "replace url '/path'" pattern
|
|
491
|
+
let urlArg = cmd.args[0];
|
|
492
|
+
if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
|
|
493
|
+
urlArg = cmd.args[1];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const url = String(await evaluate(urlArg, ctx));
|
|
497
|
+
let title = '';
|
|
498
|
+
|
|
499
|
+
// Check for "with title" modifier
|
|
500
|
+
if (cmd.modifiers?.title) {
|
|
501
|
+
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
window.history.replaceState(null, '', url);
|
|
505
|
+
if (title) document.title = title;
|
|
506
|
+
|
|
507
|
+
window.dispatchEvent(new CustomEvent('lokascript:replaceurl', {
|
|
508
|
+
detail: { url, title }
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
return { url, title, mode: 'replace' };
|
|
512
|
+
}`,
|
|
304
513
|
};
|
|
305
514
|
const BLOCK_IMPLEMENTATIONS_TS = {
|
|
306
515
|
if: `
|
|
@@ -399,6 +608,7 @@ const BLOCK_IMPLEMENTATIONS_TS = {
|
|
|
399
608
|
};
|
|
400
609
|
const STYLE_COMMANDS = ['set', 'put', 'increment', 'decrement'];
|
|
401
610
|
const ELEMENT_ARRAY_COMMANDS = ['put', 'increment', 'decrement'];
|
|
611
|
+
const MORPH_COMMANDS = ['morph'];
|
|
402
612
|
function getCommandImplementations(format = 'ts') {
|
|
403
613
|
const result = {};
|
|
404
614
|
for (const [name, code] of Object.entries(COMMAND_IMPLEMENTATIONS_TS)) {
|
|
@@ -887,6 +1097,1114 @@ function generateBundle(config) {
|
|
|
887
1097
|
};
|
|
888
1098
|
}
|
|
889
1099
|
|
|
1100
|
+
const LITE_PARSER_TEMPLATE = `
|
|
1101
|
+
// Lite Parser - Regex-based for minimal bundle size
|
|
1102
|
+
|
|
1103
|
+
function parseLite(code) {
|
|
1104
|
+
const trimmed = code.trim();
|
|
1105
|
+
|
|
1106
|
+
// Handle event handlers: "on click toggle .active"
|
|
1107
|
+
const onMatch = trimmed.match(/^on\\s+(\\w+)(?:\\s+from\\s+([^\\s]+))?\\s+(.+)$/i);
|
|
1108
|
+
if (onMatch) {
|
|
1109
|
+
return {
|
|
1110
|
+
type: 'event',
|
|
1111
|
+
event: onMatch[1],
|
|
1112
|
+
filter: onMatch[2] ? { type: 'selector', value: onMatch[2] } : undefined,
|
|
1113
|
+
modifiers: {},
|
|
1114
|
+
body: parseCommands(onMatch[3]),
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Handle "every Nms" event pattern
|
|
1119
|
+
const everyMatch = trimmed.match(/^every\\s+(\\d+)(ms|s)?\\s+(.+)$/i);
|
|
1120
|
+
if (everyMatch) {
|
|
1121
|
+
const ms = everyMatch[2] === 's' ? parseInt(everyMatch[1]) * 1000 : parseInt(everyMatch[1]);
|
|
1122
|
+
return {
|
|
1123
|
+
type: 'event',
|
|
1124
|
+
event: 'interval:' + ms,
|
|
1125
|
+
modifiers: {},
|
|
1126
|
+
body: parseCommands(everyMatch[3]),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Handle "init" pattern
|
|
1131
|
+
const initMatch = trimmed.match(/^init\\s+(.+)$/i);
|
|
1132
|
+
if (initMatch) {
|
|
1133
|
+
return {
|
|
1134
|
+
type: 'event',
|
|
1135
|
+
event: 'init',
|
|
1136
|
+
modifiers: {},
|
|
1137
|
+
body: parseCommands(initMatch[1]),
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return { type: 'sequence', commands: parseCommands(trimmed) };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function parseCommands(code) {
|
|
1145
|
+
const parts = code.split(/\\s+(?:then|and)\\s+/i);
|
|
1146
|
+
return parts.map(parseCommand).filter(Boolean);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function parseCommand(code) {
|
|
1150
|
+
const trimmed = code.trim();
|
|
1151
|
+
if (!trimmed) return null;
|
|
1152
|
+
|
|
1153
|
+
let match;
|
|
1154
|
+
|
|
1155
|
+
// toggle .class [on target]
|
|
1156
|
+
match = trimmed.match(/^toggle\\s+(\\.\\w+|\\w+)(?:\\s+on\\s+(.+))?$/i);
|
|
1157
|
+
if (match) {
|
|
1158
|
+
return {
|
|
1159
|
+
type: 'command',
|
|
1160
|
+
name: 'toggle',
|
|
1161
|
+
args: [{ type: 'selector', value: match[1] }],
|
|
1162
|
+
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// add .class [to target]
|
|
1167
|
+
match = trimmed.match(/^add\\s+(\\.\\w+|\\w+)(?:\\s+to\\s+(.+))?$/i);
|
|
1168
|
+
if (match) {
|
|
1169
|
+
return {
|
|
1170
|
+
type: 'command',
|
|
1171
|
+
name: 'add',
|
|
1172
|
+
args: [{ type: 'selector', value: match[1] }],
|
|
1173
|
+
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// remove .class [from target] | remove [target]
|
|
1178
|
+
match = trimmed.match(/^remove\\s+(\\.\\w+)(?:\\s+from\\s+(.+))?$/i);
|
|
1179
|
+
if (match) {
|
|
1180
|
+
return {
|
|
1181
|
+
type: 'command',
|
|
1182
|
+
name: 'removeClass',
|
|
1183
|
+
args: [{ type: 'selector', value: match[1] }],
|
|
1184
|
+
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
match = trimmed.match(/^remove\\s+(.+)$/i);
|
|
1188
|
+
if (match) {
|
|
1189
|
+
return {
|
|
1190
|
+
type: 'command',
|
|
1191
|
+
name: 'remove',
|
|
1192
|
+
args: [],
|
|
1193
|
+
target: parseTarget(match[1]),
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// put "content" into target
|
|
1198
|
+
match = trimmed.match(/^put\\s+(?:"([^"]+)"|'([^']+)'|(\\S+))\\s+(into|before|after)\\s+(.+)$/i);
|
|
1199
|
+
if (match) {
|
|
1200
|
+
const content = match[1] || match[2] || match[3];
|
|
1201
|
+
return {
|
|
1202
|
+
type: 'command',
|
|
1203
|
+
name: 'put',
|
|
1204
|
+
args: [{ type: 'literal', value: content }],
|
|
1205
|
+
modifier: match[4],
|
|
1206
|
+
target: parseTarget(match[5]),
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// set target to value | set :var to value
|
|
1211
|
+
match = trimmed.match(/^set\\s+(.+?)\\s+to\\s+(.+)$/i);
|
|
1212
|
+
if (match) {
|
|
1213
|
+
return {
|
|
1214
|
+
type: 'command',
|
|
1215
|
+
name: 'set',
|
|
1216
|
+
args: [parseTarget(match[1]), parseLiteValue(match[2])],
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// log message
|
|
1221
|
+
match = trimmed.match(/^log\\s+(.+)$/i);
|
|
1222
|
+
if (match) {
|
|
1223
|
+
return {
|
|
1224
|
+
type: 'command',
|
|
1225
|
+
name: 'log',
|
|
1226
|
+
args: [parseLiteValue(match[1])],
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// send event [to target]
|
|
1231
|
+
match = trimmed.match(/^send\\s+(\\w+)(?:\\s+to\\s+(.+))?$/i);
|
|
1232
|
+
if (match) {
|
|
1233
|
+
return {
|
|
1234
|
+
type: 'command',
|
|
1235
|
+
name: 'send',
|
|
1236
|
+
args: [{ type: 'literal', value: match[1] }],
|
|
1237
|
+
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// wait Nms | wait Ns
|
|
1242
|
+
match = trimmed.match(/^wait\\s+(\\d+)(ms|s)?$/i);
|
|
1243
|
+
if (match) {
|
|
1244
|
+
const ms = match[2] === 's' ? parseInt(match[1]) * 1000 : parseInt(match[1]);
|
|
1245
|
+
return {
|
|
1246
|
+
type: 'command',
|
|
1247
|
+
name: 'wait',
|
|
1248
|
+
args: [{ type: 'literal', value: ms }],
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// show/hide shortcuts
|
|
1253
|
+
match = trimmed.match(/^(show|hide)(?:\\s+(.+))?$/i);
|
|
1254
|
+
if (match) {
|
|
1255
|
+
return {
|
|
1256
|
+
type: 'command',
|
|
1257
|
+
name: match[1].toLowerCase(),
|
|
1258
|
+
args: [],
|
|
1259
|
+
target: match[2] ? parseTarget(match[2]) : undefined,
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Unknown command - try generic parsing
|
|
1264
|
+
const parts = trimmed.split(/\\s+/);
|
|
1265
|
+
if (parts.length > 0) {
|
|
1266
|
+
return {
|
|
1267
|
+
type: 'command',
|
|
1268
|
+
name: parts[0],
|
|
1269
|
+
args: parts.slice(1).map(p => ({ type: 'literal', value: p })),
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function parseTarget(str) {
|
|
1277
|
+
const s = str.trim();
|
|
1278
|
+
if (s === 'me') return { type: 'identifier', value: 'me' };
|
|
1279
|
+
if (s === 'body') return { type: 'identifier', value: 'body' };
|
|
1280
|
+
if (s.startsWith('#') || s.startsWith('.') || s.startsWith('[')) {
|
|
1281
|
+
return { type: 'selector', value: s };
|
|
1282
|
+
}
|
|
1283
|
+
if (s.startsWith(':')) {
|
|
1284
|
+
return { type: 'variable', name: s, scope: 'local' };
|
|
1285
|
+
}
|
|
1286
|
+
return { type: 'identifier', value: s };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function parseLiteValue(str) {
|
|
1290
|
+
const s = str.trim();
|
|
1291
|
+
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
1292
|
+
return { type: 'literal', value: s.slice(1, -1) };
|
|
1293
|
+
}
|
|
1294
|
+
if (/^-?\\d+(\\.\\d+)?$/.test(s)) {
|
|
1295
|
+
return { type: 'literal', value: parseFloat(s) };
|
|
1296
|
+
}
|
|
1297
|
+
if (s === 'true') return { type: 'literal', value: true };
|
|
1298
|
+
if (s === 'false') return { type: 'literal', value: false };
|
|
1299
|
+
if (s === 'null') return { type: 'literal', value: null };
|
|
1300
|
+
if (s.startsWith(':')) return { type: 'variable', name: s, scope: 'local' };
|
|
1301
|
+
if (s === 'me') return { type: 'identifier', value: 'me' };
|
|
1302
|
+
return { type: 'identifier', value: s };
|
|
1303
|
+
}
|
|
1304
|
+
`;
|
|
1305
|
+
const HYBRID_PARSER_TEMPLATE = `
|
|
1306
|
+
// Hybrid Parser - Full AST with operator precedence
|
|
1307
|
+
|
|
1308
|
+
// Tokenizer
|
|
1309
|
+
const KEYWORDS = new Set([
|
|
1310
|
+
'on', 'from', 'to', 'into', 'before', 'after', 'in', 'of', 'at', 'with',
|
|
1311
|
+
'if', 'else', 'unless', 'end', 'then', 'and', 'or', 'not',
|
|
1312
|
+
'repeat', 'times', 'for', 'each', 'while', 'until',
|
|
1313
|
+
'toggle', 'add', 'remove', 'put', 'set', 'get', 'call', 'return', 'append',
|
|
1314
|
+
'log', 'send', 'trigger', 'wait', 'settle', 'fetch', 'as',
|
|
1315
|
+
'show', 'hide', 'take', 'increment', 'decrement', 'focus', 'blur', 'go', 'transition', 'over',
|
|
1316
|
+
'the', 'a', 'an', 'my', 'its', 'me', 'it', 'you',
|
|
1317
|
+
'first', 'last', 'next', 'previous', 'closest', 'parent',
|
|
1318
|
+
'true', 'false', 'null', 'undefined',
|
|
1319
|
+
'is', 'matches', 'contains', 'includes', 'exists', 'has', 'init', 'every', 'by',
|
|
1320
|
+
]);
|
|
1321
|
+
|
|
1322
|
+
const COMMAND_ALIASES = {
|
|
1323
|
+
flip: 'toggle', switch: 'toggle', display: 'show', reveal: 'show',
|
|
1324
|
+
conceal: 'hide', increase: 'increment', decrease: 'decrement',
|
|
1325
|
+
fire: 'trigger', dispatch: 'send', navigate: 'go', goto: 'go',
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const EVENT_ALIASES = {
|
|
1329
|
+
clicked: 'click', pressed: 'keydown', changed: 'change',
|
|
1330
|
+
submitted: 'submit', loaded: 'load',
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
function normalizeCommand(name) {
|
|
1334
|
+
const lower = name.toLowerCase();
|
|
1335
|
+
return COMMAND_ALIASES[lower] || lower;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function normalizeEvent(name) {
|
|
1339
|
+
const lower = name.toLowerCase();
|
|
1340
|
+
return EVENT_ALIASES[lower] || lower;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function tokenize(code) {
|
|
1344
|
+
const tokens = [];
|
|
1345
|
+
let pos = 0;
|
|
1346
|
+
|
|
1347
|
+
while (pos < code.length) {
|
|
1348
|
+
if (/\\s/.test(code[pos])) { pos++; continue; }
|
|
1349
|
+
if (code.slice(pos, pos + 2) === '--') {
|
|
1350
|
+
while (pos < code.length && code[pos] !== '\\n') pos++;
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const start = pos;
|
|
1355
|
+
|
|
1356
|
+
// HTML selector <tag/>
|
|
1357
|
+
if (code[pos] === '<' && /[a-zA-Z]/.test(code[pos + 1] || '')) {
|
|
1358
|
+
pos++;
|
|
1359
|
+
while (pos < code.length && code[pos] !== '>') pos++;
|
|
1360
|
+
if (code[pos] === '>') pos++;
|
|
1361
|
+
const val = code.slice(start, pos);
|
|
1362
|
+
if (val.endsWith('/>') || val.endsWith('>')) {
|
|
1363
|
+
const normalized = val.slice(1).replace(/\\/?>$/, '');
|
|
1364
|
+
tokens.push({ type: 'selector', value: normalized, pos: start });
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Possessive 's
|
|
1370
|
+
if (code.slice(pos, pos + 2) === "'s" && !/[a-zA-Z]/.test(code[pos + 2] || '')) {
|
|
1371
|
+
tokens.push({ type: 'operator', value: "'s", pos: start });
|
|
1372
|
+
pos += 2;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// String literals
|
|
1377
|
+
if (code[pos] === '"' || code[pos] === "'") {
|
|
1378
|
+
const quote = code[pos++];
|
|
1379
|
+
while (pos < code.length && code[pos] !== quote) {
|
|
1380
|
+
if (code[pos] === '\\\\') pos++;
|
|
1381
|
+
pos++;
|
|
1382
|
+
}
|
|
1383
|
+
pos++;
|
|
1384
|
+
tokens.push({ type: 'string', value: code.slice(start, pos), pos: start });
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Numbers with units
|
|
1389
|
+
if (/\\d/.test(code[pos]) || (code[pos] === '-' && /\\d/.test(code[pos + 1] || ''))) {
|
|
1390
|
+
if (code[pos] === '-') pos++;
|
|
1391
|
+
while (pos < code.length && /[\\d.]/.test(code[pos])) pos++;
|
|
1392
|
+
if (code.slice(pos, pos + 2) === 'ms') pos += 2;
|
|
1393
|
+
else if (code[pos] === 's' && !/[a-zA-Z]/.test(code[pos + 1] || '')) pos++;
|
|
1394
|
+
else if (code.slice(pos, pos + 2) === 'px') pos += 2;
|
|
1395
|
+
tokens.push({ type: 'number', value: code.slice(start, pos), pos: start });
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Local variable :name
|
|
1400
|
+
if (code[pos] === ':') {
|
|
1401
|
+
pos++;
|
|
1402
|
+
while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
|
|
1403
|
+
tokens.push({ type: 'localVar', value: code.slice(start, pos), pos: start });
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Global variable $name
|
|
1408
|
+
if (code[pos] === '$') {
|
|
1409
|
+
pos++;
|
|
1410
|
+
while (pos < code.length && /[\\w]/.test(code[pos])) pos++;
|
|
1411
|
+
tokens.push({ type: 'globalVar', value: code.slice(start, pos), pos: start });
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// CSS selectors: #id, .class
|
|
1416
|
+
if (code[pos] === '#' || code[pos] === '.') {
|
|
1417
|
+
if (code[pos] === '.') {
|
|
1418
|
+
const afterDot = code.slice(pos + 1).match(/^(once|prevent|stop|debounce|throttle)\\b/i);
|
|
1419
|
+
if (afterDot) {
|
|
1420
|
+
tokens.push({ type: 'symbol', value: '.', pos: start });
|
|
1421
|
+
pos++;
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
pos++;
|
|
1426
|
+
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1427
|
+
tokens.push({ type: 'selector', value: code.slice(start, pos), pos: start });
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Array literal vs Attribute selector
|
|
1432
|
+
if (code[pos] === '[') {
|
|
1433
|
+
let lookahead = pos + 1;
|
|
1434
|
+
while (lookahead < code.length && /\\s/.test(code[lookahead])) lookahead++;
|
|
1435
|
+
const nextChar = code[lookahead] || '';
|
|
1436
|
+
const isArrayLiteral = /['"\\d\\[\\]:\\$\\-]/.test(nextChar) || nextChar === '';
|
|
1437
|
+
if (isArrayLiteral) {
|
|
1438
|
+
tokens.push({ type: 'symbol', value: '[', pos: start });
|
|
1439
|
+
pos++;
|
|
1440
|
+
continue;
|
|
1441
|
+
} else {
|
|
1442
|
+
pos++;
|
|
1443
|
+
let depth = 1;
|
|
1444
|
+
while (pos < code.length && depth > 0) {
|
|
1445
|
+
if (code[pos] === '[') depth++;
|
|
1446
|
+
if (code[pos] === ']') depth--;
|
|
1447
|
+
pos++;
|
|
1448
|
+
}
|
|
1449
|
+
tokens.push({ type: 'selector', value: code.slice(start, pos), pos: start });
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (code[pos] === ']') {
|
|
1455
|
+
tokens.push({ type: 'symbol', value: ']', pos: start });
|
|
1456
|
+
pos++;
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Multi-char operators
|
|
1461
|
+
const twoChar = code.slice(pos, pos + 2);
|
|
1462
|
+
if (['==', '!=', '<=', '>=', '&&', '||'].includes(twoChar)) {
|
|
1463
|
+
tokens.push({ type: 'operator', value: twoChar, pos: start });
|
|
1464
|
+
pos += 2;
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Style property *opacity
|
|
1469
|
+
if (code[pos] === '*' && /[a-zA-Z]/.test(code[pos + 1] || '')) {
|
|
1470
|
+
pos++;
|
|
1471
|
+
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1472
|
+
tokens.push({ type: 'styleProperty', value: code.slice(start, pos), pos: start });
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if ('+-*/%<>!'.includes(code[pos])) {
|
|
1477
|
+
tokens.push({ type: 'operator', value: code[pos], pos: start });
|
|
1478
|
+
pos++;
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
if ('()[]{},.'.includes(code[pos])) {
|
|
1483
|
+
tokens.push({ type: 'symbol', value: code[pos], pos: start });
|
|
1484
|
+
pos++;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (/[a-zA-Z_]/.test(code[pos])) {
|
|
1489
|
+
while (pos < code.length && /[\\w-]/.test(code[pos])) pos++;
|
|
1490
|
+
const value = code.slice(start, pos);
|
|
1491
|
+
const type = KEYWORDS.has(value.toLowerCase()) ? 'keyword' : 'identifier';
|
|
1492
|
+
tokens.push({ type, value, pos: start });
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
pos++;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
tokens.push({ type: 'eof', value: '', pos: code.length });
|
|
1500
|
+
return tokens;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// Parser
|
|
1504
|
+
class HybridParser {
|
|
1505
|
+
constructor(code) {
|
|
1506
|
+
this.tokens = tokenize(code);
|
|
1507
|
+
this.pos = 0;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
peek(offset = 0) {
|
|
1511
|
+
return this.tokens[Math.min(this.pos + offset, this.tokens.length - 1)];
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
advance() {
|
|
1515
|
+
return this.tokens[this.pos++];
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
match(...values) {
|
|
1519
|
+
const token = this.peek();
|
|
1520
|
+
return values.some(v => token.value.toLowerCase() === v.toLowerCase());
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
matchType(...types) {
|
|
1524
|
+
return types.includes(this.peek().type);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
expect(value) {
|
|
1528
|
+
if (!this.match(value) && normalizeCommand(this.peek().value) !== value) {
|
|
1529
|
+
throw new Error("Expected '" + value + "', got '" + this.peek().value + "'");
|
|
1530
|
+
}
|
|
1531
|
+
return this.advance();
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
isAtEnd() {
|
|
1535
|
+
return this.peek().type === 'eof';
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
parse() {
|
|
1539
|
+
if (this.match('on')) return this.parseEventHandler();
|
|
1540
|
+
if (this.match('init')) {
|
|
1541
|
+
this.advance();
|
|
1542
|
+
return { type: 'event', event: 'init', modifiers: {}, body: this.parseCommandSequence() };
|
|
1543
|
+
}
|
|
1544
|
+
if (this.match('every')) return this.parseEveryHandler();
|
|
1545
|
+
return { type: 'sequence', commands: this.parseCommandSequence() };
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
parseEventHandler() {
|
|
1549
|
+
this.expect('on');
|
|
1550
|
+
const eventName = this.advance().value;
|
|
1551
|
+
const modifiers = {};
|
|
1552
|
+
let filter;
|
|
1553
|
+
|
|
1554
|
+
while (this.peek().value === '.') {
|
|
1555
|
+
this.advance();
|
|
1556
|
+
const mod = this.advance().value.toLowerCase();
|
|
1557
|
+
if (mod === 'once') modifiers.once = true;
|
|
1558
|
+
else if (mod === 'prevent') modifiers.prevent = true;
|
|
1559
|
+
else if (mod === 'stop') modifiers.stop = true;
|
|
1560
|
+
else if (mod === 'debounce' || mod === 'throttle') {
|
|
1561
|
+
if (this.peek().value === '(') {
|
|
1562
|
+
this.advance();
|
|
1563
|
+
const num = this.advance().value;
|
|
1564
|
+
this.expect(')');
|
|
1565
|
+
if (mod === 'debounce') modifiers.debounce = parseInt(num) || 100;
|
|
1566
|
+
else modifiers.throttle = parseInt(num) || 100;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (this.match('from')) {
|
|
1572
|
+
this.advance();
|
|
1573
|
+
filter = this.parseExpression();
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return { type: 'event', event: normalizeEvent(eventName), filter, modifiers, body: this.parseCommandSequence() };
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
parseEveryHandler() {
|
|
1580
|
+
this.expect('every');
|
|
1581
|
+
const interval = this.advance().value;
|
|
1582
|
+
return { type: 'event', event: 'interval:' + interval, modifiers: {}, body: this.parseCommandSequence() };
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
parseCommandSequence() {
|
|
1586
|
+
const commands = [];
|
|
1587
|
+
while (!this.isAtEnd() && !this.match('end', 'else')) {
|
|
1588
|
+
const cmd = this.parseCommand();
|
|
1589
|
+
if (cmd) commands.push(cmd);
|
|
1590
|
+
if (this.match('then', 'and')) this.advance();
|
|
1591
|
+
}
|
|
1592
|
+
return commands;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
parseCommand() {
|
|
1596
|
+
if (this.match('if', 'unless')) return this.parseIf();
|
|
1597
|
+
if (this.match('repeat')) return this.parseRepeat();
|
|
1598
|
+
if (this.match('for')) return this.parseFor();
|
|
1599
|
+
if (this.match('while')) return this.parseWhile();
|
|
1600
|
+
if (this.match('fetch')) return this.parseFetchBlock();
|
|
1601
|
+
|
|
1602
|
+
const cmdMap = {
|
|
1603
|
+
toggle: () => this.parseToggle(),
|
|
1604
|
+
add: () => this.parseAdd(),
|
|
1605
|
+
remove: () => this.parseRemove(),
|
|
1606
|
+
put: () => this.parsePut(),
|
|
1607
|
+
append: () => this.parseAppend(),
|
|
1608
|
+
set: () => this.parseSet(),
|
|
1609
|
+
get: () => this.parseGet(),
|
|
1610
|
+
call: () => this.parseCall(),
|
|
1611
|
+
log: () => this.parseLog(),
|
|
1612
|
+
send: () => this.parseSend(),
|
|
1613
|
+
trigger: () => this.parseSend(),
|
|
1614
|
+
wait: () => this.parseWait(),
|
|
1615
|
+
show: () => this.parseShow(),
|
|
1616
|
+
hide: () => this.parseHide(),
|
|
1617
|
+
take: () => this.parseTake(),
|
|
1618
|
+
increment: () => this.parseIncDec('increment'),
|
|
1619
|
+
decrement: () => this.parseIncDec('decrement'),
|
|
1620
|
+
focus: () => this.parseFocusBlur('focus'),
|
|
1621
|
+
blur: () => this.parseFocusBlur('blur'),
|
|
1622
|
+
go: () => this.parseGo(),
|
|
1623
|
+
return: () => this.parseReturn(),
|
|
1624
|
+
transition: () => this.parseTransition(),
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
const normalized = normalizeCommand(this.peek().value);
|
|
1628
|
+
if (cmdMap[normalized]) return cmdMap[normalized]();
|
|
1629
|
+
|
|
1630
|
+
if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) this.advance();
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
parseIf() {
|
|
1635
|
+
const isUnless = this.match('unless');
|
|
1636
|
+
this.advance();
|
|
1637
|
+
const condition = this.parseExpression();
|
|
1638
|
+
const body = this.parseCommandSequence();
|
|
1639
|
+
let elseBody;
|
|
1640
|
+
|
|
1641
|
+
if (this.match('else')) {
|
|
1642
|
+
this.advance();
|
|
1643
|
+
elseBody = this.parseCommandSequence();
|
|
1644
|
+
}
|
|
1645
|
+
if (this.match('end')) this.advance();
|
|
1646
|
+
|
|
1647
|
+
return {
|
|
1648
|
+
type: 'if',
|
|
1649
|
+
condition: isUnless ? { type: 'unary', operator: 'not', operand: condition } : condition,
|
|
1650
|
+
body,
|
|
1651
|
+
elseBody,
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
parseRepeat() {
|
|
1656
|
+
this.expect('repeat');
|
|
1657
|
+
let count;
|
|
1658
|
+
if (!this.match('until', 'while', 'forever')) {
|
|
1659
|
+
count = this.parseExpression();
|
|
1660
|
+
if (this.match('times')) this.advance();
|
|
1661
|
+
}
|
|
1662
|
+
const body = this.parseCommandSequence();
|
|
1663
|
+
if (this.match('end')) this.advance();
|
|
1664
|
+
return { type: 'repeat', condition: count, body };
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
parseFor() {
|
|
1668
|
+
this.expect('for');
|
|
1669
|
+
if (this.match('each')) this.advance();
|
|
1670
|
+
const variable = this.advance().value;
|
|
1671
|
+
this.expect('in');
|
|
1672
|
+
const iterable = this.parseExpression();
|
|
1673
|
+
const body = this.parseCommandSequence();
|
|
1674
|
+
if (this.match('end')) this.advance();
|
|
1675
|
+
return { type: 'for', condition: { type: 'forCondition', variable, iterable }, body };
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
parseWhile() {
|
|
1679
|
+
this.expect('while');
|
|
1680
|
+
const condition = this.parseExpression();
|
|
1681
|
+
const body = this.parseCommandSequence();
|
|
1682
|
+
if (this.match('end')) this.advance();
|
|
1683
|
+
return { type: 'while', condition, body };
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
parseFetchBlock() {
|
|
1687
|
+
this.expect('fetch');
|
|
1688
|
+
const url = this.parseExpression();
|
|
1689
|
+
let responseType = { type: 'literal', value: 'text' };
|
|
1690
|
+
if (this.match('as')) {
|
|
1691
|
+
this.advance();
|
|
1692
|
+
responseType = this.parseExpression();
|
|
1693
|
+
}
|
|
1694
|
+
if (this.match('then')) this.advance();
|
|
1695
|
+
const body = this.parseCommandSequence();
|
|
1696
|
+
return { type: 'fetch', condition: { type: 'fetchConfig', url, responseType }, body };
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
parseToggle() {
|
|
1700
|
+
this.expect('toggle');
|
|
1701
|
+
const what = this.parseExpression();
|
|
1702
|
+
let target;
|
|
1703
|
+
if (this.match('on')) {
|
|
1704
|
+
this.advance();
|
|
1705
|
+
target = this.parseExpression();
|
|
1706
|
+
}
|
|
1707
|
+
return { type: 'command', name: 'toggle', args: [what], target };
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
parseAdd() {
|
|
1711
|
+
this.expect('add');
|
|
1712
|
+
const what = this.parseExpression();
|
|
1713
|
+
let target;
|
|
1714
|
+
if (this.match('to')) {
|
|
1715
|
+
this.advance();
|
|
1716
|
+
target = this.parseExpression();
|
|
1717
|
+
}
|
|
1718
|
+
return { type: 'command', name: 'add', args: [what], target };
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
parseRemove() {
|
|
1722
|
+
this.expect('remove');
|
|
1723
|
+
if (this.matchType('selector')) {
|
|
1724
|
+
const what = this.parseExpression();
|
|
1725
|
+
let target;
|
|
1726
|
+
if (this.match('from')) {
|
|
1727
|
+
this.advance();
|
|
1728
|
+
target = this.parseExpression();
|
|
1729
|
+
}
|
|
1730
|
+
return { type: 'command', name: 'removeClass', args: [what], target };
|
|
1731
|
+
}
|
|
1732
|
+
const target = this.parseExpression();
|
|
1733
|
+
return { type: 'command', name: 'remove', args: [], target };
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
parsePut() {
|
|
1737
|
+
this.expect('put');
|
|
1738
|
+
const content = this.parseExpression();
|
|
1739
|
+
let modifier = 'into';
|
|
1740
|
+
if (this.match('into', 'before', 'after', 'at')) {
|
|
1741
|
+
modifier = this.advance().value;
|
|
1742
|
+
if (modifier === 'at') {
|
|
1743
|
+
const pos = this.advance().value;
|
|
1744
|
+
this.expect('of');
|
|
1745
|
+
modifier = 'at ' + pos + ' of';
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
const target = this.parseExpression();
|
|
1749
|
+
return { type: 'command', name: 'put', args: [content], target, modifier };
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
parseAppend() {
|
|
1753
|
+
this.expect('append');
|
|
1754
|
+
const content = this.parseExpression();
|
|
1755
|
+
let target;
|
|
1756
|
+
if (this.match('to')) {
|
|
1757
|
+
this.advance();
|
|
1758
|
+
target = this.parseExpression();
|
|
1759
|
+
}
|
|
1760
|
+
return { type: 'command', name: 'append', args: [content], target };
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
parseSet() {
|
|
1764
|
+
this.expect('set');
|
|
1765
|
+
const target = this.parseExpression();
|
|
1766
|
+
if (this.match('to')) {
|
|
1767
|
+
this.advance();
|
|
1768
|
+
const value = this.parseExpression();
|
|
1769
|
+
return { type: 'command', name: 'set', args: [target, value] };
|
|
1770
|
+
}
|
|
1771
|
+
return { type: 'command', name: 'set', args: [target] };
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
parseGet() {
|
|
1775
|
+
this.expect('get');
|
|
1776
|
+
return { type: 'command', name: 'get', args: [this.parseExpression()] };
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
parseCall() {
|
|
1780
|
+
this.expect('call');
|
|
1781
|
+
return { type: 'command', name: 'call', args: [this.parseExpression()] };
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
parseLog() {
|
|
1785
|
+
this.expect('log');
|
|
1786
|
+
const args = [];
|
|
1787
|
+
while (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
|
|
1788
|
+
args.push(this.parseExpression());
|
|
1789
|
+
if (this.match(',')) this.advance();
|
|
1790
|
+
else break;
|
|
1791
|
+
}
|
|
1792
|
+
return { type: 'command', name: 'log', args };
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
parseSend() {
|
|
1796
|
+
this.advance();
|
|
1797
|
+
const event = this.advance().value;
|
|
1798
|
+
let target;
|
|
1799
|
+
if (this.match('to')) {
|
|
1800
|
+
this.advance();
|
|
1801
|
+
target = this.parseExpression();
|
|
1802
|
+
}
|
|
1803
|
+
return { type: 'command', name: 'send', args: [{ type: 'literal', value: event }], target };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
parseWait() {
|
|
1807
|
+
this.expect('wait');
|
|
1808
|
+
if (this.match('for')) {
|
|
1809
|
+
this.advance();
|
|
1810
|
+
const event = this.advance().value;
|
|
1811
|
+
let target;
|
|
1812
|
+
if (this.match('from')) {
|
|
1813
|
+
this.advance();
|
|
1814
|
+
target = this.parseExpression();
|
|
1815
|
+
}
|
|
1816
|
+
return { type: 'command', name: 'waitFor', args: [{ type: 'literal', value: event }], target };
|
|
1817
|
+
}
|
|
1818
|
+
return { type: 'command', name: 'wait', args: [this.parseExpression()] };
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
parseShow() {
|
|
1822
|
+
this.expect('show');
|
|
1823
|
+
let target;
|
|
1824
|
+
const modifiers = {};
|
|
1825
|
+
if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else', 'when', 'where')) {
|
|
1826
|
+
target = this.parseExpression();
|
|
1827
|
+
}
|
|
1828
|
+
if (!this.isAtEnd() && this.match('when', 'where')) {
|
|
1829
|
+
const keyword = this.advance().value;
|
|
1830
|
+
modifiers[keyword] = this.parseExpression();
|
|
1831
|
+
}
|
|
1832
|
+
return { type: 'command', name: 'show', args: [], target, modifiers };
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
parseHide() {
|
|
1836
|
+
this.expect('hide');
|
|
1837
|
+
let target;
|
|
1838
|
+
const modifiers = {};
|
|
1839
|
+
if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else', 'when', 'where')) {
|
|
1840
|
+
target = this.parseExpression();
|
|
1841
|
+
}
|
|
1842
|
+
if (!this.isAtEnd() && this.match('when', 'where')) {
|
|
1843
|
+
const keyword = this.advance().value;
|
|
1844
|
+
modifiers[keyword] = this.parseExpression();
|
|
1845
|
+
}
|
|
1846
|
+
return { type: 'command', name: 'hide', args: [], target, modifiers };
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
parseTake() {
|
|
1850
|
+
this.expect('take');
|
|
1851
|
+
const what = this.parseExpression();
|
|
1852
|
+
let from;
|
|
1853
|
+
if (this.match('from')) {
|
|
1854
|
+
this.advance();
|
|
1855
|
+
from = this.parseExpression();
|
|
1856
|
+
}
|
|
1857
|
+
return { type: 'command', name: 'take', args: [what], target: from };
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
parseIncDec(name) {
|
|
1861
|
+
this.advance();
|
|
1862
|
+
const target = this.parseExpression();
|
|
1863
|
+
let amount = { type: 'literal', value: 1 };
|
|
1864
|
+
if (this.match('by')) {
|
|
1865
|
+
this.advance();
|
|
1866
|
+
amount = this.parseExpression();
|
|
1867
|
+
}
|
|
1868
|
+
return { type: 'command', name, args: [target, amount] };
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
parseFocusBlur(name) {
|
|
1872
|
+
this.advance();
|
|
1873
|
+
let target;
|
|
1874
|
+
if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
|
|
1875
|
+
target = this.parseExpression();
|
|
1876
|
+
}
|
|
1877
|
+
return { type: 'command', name, args: [], target };
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
parseGo() {
|
|
1881
|
+
this.expect('go');
|
|
1882
|
+
if (this.match('to')) this.advance();
|
|
1883
|
+
if (this.match('url')) this.advance();
|
|
1884
|
+
const dest = this.parseExpression();
|
|
1885
|
+
return { type: 'command', name: 'go', args: [dest] };
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
parseReturn() {
|
|
1889
|
+
this.expect('return');
|
|
1890
|
+
let value;
|
|
1891
|
+
if (!this.isAtEnd() && !this.match('then', 'and', 'end', 'else')) {
|
|
1892
|
+
value = this.parseExpression();
|
|
1893
|
+
}
|
|
1894
|
+
return { type: 'command', name: 'return', args: value ? [value] : [] };
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
parseTransition() {
|
|
1898
|
+
this.expect('transition');
|
|
1899
|
+
let target;
|
|
1900
|
+
if (this.match('my', 'its')) {
|
|
1901
|
+
const ref = this.advance().value;
|
|
1902
|
+
target = { type: 'identifier', value: ref === 'my' ? 'me' : 'it' };
|
|
1903
|
+
} else if (this.matchType('selector')) {
|
|
1904
|
+
const expr = this.parseExpression();
|
|
1905
|
+
if (expr.type === 'possessive') {
|
|
1906
|
+
return this.parseTransitionRest(expr.object, expr.property);
|
|
1907
|
+
}
|
|
1908
|
+
target = expr;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const propToken = this.peek();
|
|
1912
|
+
let property;
|
|
1913
|
+
if (propToken.type === 'styleProperty') {
|
|
1914
|
+
property = this.advance().value;
|
|
1915
|
+
} else if (propToken.type === 'identifier' || propToken.type === 'keyword') {
|
|
1916
|
+
property = this.advance().value;
|
|
1917
|
+
} else {
|
|
1918
|
+
property = 'opacity';
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
return this.parseTransitionRest(target, property);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
parseTransitionRest(target, property) {
|
|
1925
|
+
let toValue = { type: 'literal', value: 1 };
|
|
1926
|
+
if (this.match('to')) {
|
|
1927
|
+
this.advance();
|
|
1928
|
+
toValue = this.parseExpression();
|
|
1929
|
+
}
|
|
1930
|
+
let duration = { type: 'literal', value: 300 };
|
|
1931
|
+
if (this.match('over')) {
|
|
1932
|
+
this.advance();
|
|
1933
|
+
duration = this.parseExpression();
|
|
1934
|
+
}
|
|
1935
|
+
return { type: 'command', name: 'transition', args: [{ type: 'literal', value: property }, toValue, duration], target };
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
parseExpression() { return this.parseOr(); }
|
|
1939
|
+
|
|
1940
|
+
parseOr() {
|
|
1941
|
+
let left = this.parseAnd();
|
|
1942
|
+
while (this.match('or', '||')) {
|
|
1943
|
+
this.advance();
|
|
1944
|
+
left = { type: 'binary', operator: 'or', left, right: this.parseAnd() };
|
|
1945
|
+
}
|
|
1946
|
+
return left;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
parseAnd() {
|
|
1950
|
+
let left = this.parseEquality();
|
|
1951
|
+
while (this.match('and', '&&') && !this.isCommandKeyword(this.peek(1))) {
|
|
1952
|
+
this.advance();
|
|
1953
|
+
left = { type: 'binary', operator: 'and', left, right: this.parseEquality() };
|
|
1954
|
+
}
|
|
1955
|
+
return left;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
isCommandKeyword(token) {
|
|
1959
|
+
const cmds = ['toggle', 'add', 'remove', 'set', 'put', 'log', 'send', 'wait', 'show', 'hide', 'increment', 'decrement', 'focus', 'blur', 'go'];
|
|
1960
|
+
return cmds.includes(normalizeCommand(token.value));
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
parseEquality() {
|
|
1964
|
+
let left = this.parseComparison();
|
|
1965
|
+
while (this.match('==', '!=', 'is', 'matches', 'contains', 'includes', 'has')) {
|
|
1966
|
+
const op = this.advance().value;
|
|
1967
|
+
if (op.toLowerCase() === 'is' && this.match('not')) {
|
|
1968
|
+
this.advance();
|
|
1969
|
+
left = { type: 'binary', operator: 'is not', left, right: this.parseComparison() };
|
|
1970
|
+
} else {
|
|
1971
|
+
left = { type: 'binary', operator: op, left, right: this.parseComparison() };
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return left;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
parseComparison() {
|
|
1978
|
+
let left = this.parseAdditive();
|
|
1979
|
+
while (this.match('<', '>', '<=', '>=')) {
|
|
1980
|
+
const op = this.advance().value;
|
|
1981
|
+
left = { type: 'binary', operator: op, left, right: this.parseAdditive() };
|
|
1982
|
+
}
|
|
1983
|
+
return left;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
parseAdditive() {
|
|
1987
|
+
let left = this.parseMultiplicative();
|
|
1988
|
+
while (this.match('+', '-')) {
|
|
1989
|
+
const op = this.advance().value;
|
|
1990
|
+
left = { type: 'binary', operator: op, left, right: this.parseMultiplicative() };
|
|
1991
|
+
}
|
|
1992
|
+
return left;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
parseMultiplicative() {
|
|
1996
|
+
let left = this.parseUnary();
|
|
1997
|
+
while (this.match('*', '/', '%')) {
|
|
1998
|
+
const op = this.advance().value;
|
|
1999
|
+
left = { type: 'binary', operator: op, left, right: this.parseUnary() };
|
|
2000
|
+
}
|
|
2001
|
+
return left;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
parseUnary() {
|
|
2005
|
+
if (this.match('not', '!')) {
|
|
2006
|
+
this.advance();
|
|
2007
|
+
return { type: 'unary', operator: 'not', operand: this.parseUnary() };
|
|
2008
|
+
}
|
|
2009
|
+
if (this.match('-') && this.peek(1).type === 'number') {
|
|
2010
|
+
this.advance();
|
|
2011
|
+
const num = this.advance();
|
|
2012
|
+
return { type: 'literal', value: -parseFloat(num.value) };
|
|
2013
|
+
}
|
|
2014
|
+
return this.parsePostfix();
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
parsePostfix() {
|
|
2018
|
+
let left = this.parsePrimary();
|
|
2019
|
+
while (true) {
|
|
2020
|
+
if (this.match("'s")) {
|
|
2021
|
+
this.advance();
|
|
2022
|
+
const next = this.peek();
|
|
2023
|
+
const prop = next.type === 'styleProperty' ? this.advance().value : this.advance().value;
|
|
2024
|
+
left = { type: 'possessive', object: left, property: prop };
|
|
2025
|
+
} else if (this.peek().type === 'styleProperty') {
|
|
2026
|
+
const prop = this.advance().value;
|
|
2027
|
+
left = { type: 'possessive', object: left, property: prop };
|
|
2028
|
+
} else if (this.peek().value === '.') {
|
|
2029
|
+
this.advance();
|
|
2030
|
+
const prop = this.advance().value;
|
|
2031
|
+
left = { type: 'member', object: left, property: prop };
|
|
2032
|
+
} else if (this.peek().type === 'selector' && this.peek().value.startsWith('.')) {
|
|
2033
|
+
const prop = this.advance().value.slice(1);
|
|
2034
|
+
left = { type: 'member', object: left, property: prop };
|
|
2035
|
+
} else if (this.peek().value === '(') {
|
|
2036
|
+
this.advance();
|
|
2037
|
+
const args = [];
|
|
2038
|
+
while (!this.match(')')) {
|
|
2039
|
+
args.push(this.parseExpression());
|
|
2040
|
+
if (this.match(',')) this.advance();
|
|
2041
|
+
}
|
|
2042
|
+
this.expect(')');
|
|
2043
|
+
left = { type: 'call', callee: left, args };
|
|
2044
|
+
} else if (this.peek().value === '[' && left.type !== 'selector') {
|
|
2045
|
+
this.advance();
|
|
2046
|
+
const index = this.parseExpression();
|
|
2047
|
+
this.expect(']');
|
|
2048
|
+
left = { type: 'member', object: left, property: index, computed: true };
|
|
2049
|
+
} else {
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return left;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
parsePrimary() {
|
|
2057
|
+
const token = this.peek();
|
|
2058
|
+
|
|
2059
|
+
if (token.value === '(') {
|
|
2060
|
+
this.advance();
|
|
2061
|
+
const expr = this.parseExpression();
|
|
2062
|
+
this.expect(')');
|
|
2063
|
+
return expr;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
if (token.value === '{') return this.parseObjectLiteral();
|
|
2067
|
+
if (token.value === '[') return this.parseArrayLiteral();
|
|
2068
|
+
|
|
2069
|
+
if (token.type === 'number') {
|
|
2070
|
+
this.advance();
|
|
2071
|
+
const val = token.value;
|
|
2072
|
+
if (val.endsWith('ms')) return { type: 'literal', value: parseInt(val), unit: 'ms' };
|
|
2073
|
+
if (val.endsWith('s')) return { type: 'literal', value: parseFloat(val) * 1000, unit: 'ms' };
|
|
2074
|
+
return { type: 'literal', value: parseFloat(val) };
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
if (token.type === 'string') {
|
|
2078
|
+
this.advance();
|
|
2079
|
+
return { type: 'literal', value: token.value.slice(1, -1) };
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (this.match('true')) { this.advance(); return { type: 'literal', value: true }; }
|
|
2083
|
+
if (this.match('false')) { this.advance(); return { type: 'literal', value: false }; }
|
|
2084
|
+
if (this.match('null')) { this.advance(); return { type: 'literal', value: null }; }
|
|
2085
|
+
if (this.match('undefined')) { this.advance(); return { type: 'literal', value: undefined }; }
|
|
2086
|
+
|
|
2087
|
+
if (token.type === 'localVar') {
|
|
2088
|
+
this.advance();
|
|
2089
|
+
return { type: 'variable', name: token.value, scope: 'local' };
|
|
2090
|
+
}
|
|
2091
|
+
if (token.type === 'globalVar') {
|
|
2092
|
+
this.advance();
|
|
2093
|
+
return { type: 'variable', name: token.value, scope: 'global' };
|
|
2094
|
+
}
|
|
2095
|
+
if (token.type === 'selector') {
|
|
2096
|
+
this.advance();
|
|
2097
|
+
return { type: 'selector', value: token.value };
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
if (this.match('my')) {
|
|
2101
|
+
this.advance();
|
|
2102
|
+
const next = this.peek();
|
|
2103
|
+
if ((next.type === 'identifier' || next.type === 'keyword') && !this.isCommandKeyword(next)) {
|
|
2104
|
+
const prop = this.advance().value;
|
|
2105
|
+
return { type: 'possessive', object: { type: 'identifier', value: 'me' }, property: prop };
|
|
2106
|
+
}
|
|
2107
|
+
return { type: 'identifier', value: 'me' };
|
|
2108
|
+
}
|
|
2109
|
+
if (this.match('its')) {
|
|
2110
|
+
this.advance();
|
|
2111
|
+
const next = this.peek();
|
|
2112
|
+
if ((next.type === 'identifier' || next.type === 'keyword') && !this.isCommandKeyword(next)) {
|
|
2113
|
+
const prop = this.advance().value;
|
|
2114
|
+
return { type: 'possessive', object: { type: 'identifier', value: 'it' }, property: prop };
|
|
2115
|
+
}
|
|
2116
|
+
return { type: 'identifier', value: 'it' };
|
|
2117
|
+
}
|
|
2118
|
+
if (this.match('me')) { this.advance(); return { type: 'identifier', value: 'me' }; }
|
|
2119
|
+
if (this.match('it')) { this.advance(); return { type: 'identifier', value: 'it' }; }
|
|
2120
|
+
if (this.match('you')) { this.advance(); return { type: 'identifier', value: 'you' }; }
|
|
2121
|
+
|
|
2122
|
+
if (this.match('the', 'a', 'an')) {
|
|
2123
|
+
this.advance();
|
|
2124
|
+
if (this.match('first', 'last', 'next', 'previous', 'closest', 'parent')) {
|
|
2125
|
+
const position = this.advance().value;
|
|
2126
|
+
const target = this.parsePositionalTarget();
|
|
2127
|
+
return { type: 'positional', position, target };
|
|
2128
|
+
}
|
|
2129
|
+
return this.parsePrimary();
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
if (this.match('first', 'last', 'next', 'previous', 'closest', 'parent')) {
|
|
2133
|
+
const position = this.advance().value;
|
|
2134
|
+
const target = this.parsePositionalTarget();
|
|
2135
|
+
return { type: 'positional', position, target };
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
if (token.type === 'identifier' || token.type === 'keyword') {
|
|
2139
|
+
this.advance();
|
|
2140
|
+
return { type: 'identifier', value: token.value };
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
this.advance();
|
|
2144
|
+
return { type: 'identifier', value: token.value };
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
parseObjectLiteral() {
|
|
2148
|
+
this.expect('{');
|
|
2149
|
+
const properties = [];
|
|
2150
|
+
while (!this.match('}')) {
|
|
2151
|
+
const key = this.advance().value;
|
|
2152
|
+
this.expect(':');
|
|
2153
|
+
const value = this.parseExpression();
|
|
2154
|
+
properties.push({ key, value });
|
|
2155
|
+
if (this.match(',')) this.advance();
|
|
2156
|
+
}
|
|
2157
|
+
this.expect('}');
|
|
2158
|
+
return { type: 'object', properties };
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
parseArrayLiteral() {
|
|
2162
|
+
this.expect('[');
|
|
2163
|
+
const elements = [];
|
|
2164
|
+
while (!this.match(']')) {
|
|
2165
|
+
elements.push(this.parseExpression());
|
|
2166
|
+
if (this.match(',')) this.advance();
|
|
2167
|
+
}
|
|
2168
|
+
this.expect(']');
|
|
2169
|
+
return { type: 'array', elements };
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
parsePositionalTarget() {
|
|
2173
|
+
const token = this.peek();
|
|
2174
|
+
if (token.type === 'selector') {
|
|
2175
|
+
return { type: 'selector', value: this.advance().value };
|
|
2176
|
+
}
|
|
2177
|
+
if (token.type === 'identifier' || token.type === 'keyword') {
|
|
2178
|
+
return { type: 'identifier', value: this.advance().value };
|
|
2179
|
+
}
|
|
2180
|
+
return this.parseExpression();
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
`;
|
|
2184
|
+
function getParserTemplate(type) {
|
|
2185
|
+
return type === 'lite' ? LITE_PARSER_TEMPLATE : HYBRID_PARSER_TEMPLATE;
|
|
2186
|
+
}
|
|
2187
|
+
const LITE_PARSER_COMMANDS = [
|
|
2188
|
+
'toggle',
|
|
2189
|
+
'add',
|
|
2190
|
+
'remove',
|
|
2191
|
+
'put',
|
|
2192
|
+
'set',
|
|
2193
|
+
'log',
|
|
2194
|
+
'send',
|
|
2195
|
+
'wait',
|
|
2196
|
+
'show',
|
|
2197
|
+
'hide',
|
|
2198
|
+
];
|
|
2199
|
+
function canUseLiteParser(commands, blocks, positional) {
|
|
2200
|
+
if (blocks.length > 0)
|
|
2201
|
+
return false;
|
|
2202
|
+
if (positional)
|
|
2203
|
+
return false;
|
|
2204
|
+
const liteCommands = new Set(LITE_PARSER_COMMANDS);
|
|
2205
|
+
return commands.every(cmd => liteCommands.has(cmd));
|
|
2206
|
+
}
|
|
2207
|
+
|
|
890
2208
|
const AVAILABLE_COMMANDS = [
|
|
891
2209
|
'toggle',
|
|
892
2210
|
'add',
|
|
@@ -907,33 +2225,35 @@ const AVAILABLE_COMMANDS = [
|
|
|
907
2225
|
'trigger',
|
|
908
2226
|
'log',
|
|
909
2227
|
'call',
|
|
2228
|
+
'copy',
|
|
2229
|
+
'beep',
|
|
910
2230
|
'go',
|
|
2231
|
+
'push',
|
|
2232
|
+
'push-url',
|
|
2233
|
+
'replace',
|
|
2234
|
+
'replace-url',
|
|
911
2235
|
'focus',
|
|
912
2236
|
'blur',
|
|
913
2237
|
'return',
|
|
914
2238
|
'break',
|
|
915
2239
|
'continue',
|
|
2240
|
+
'halt',
|
|
2241
|
+
'exit',
|
|
2242
|
+
'throw',
|
|
2243
|
+
'js',
|
|
2244
|
+
'morph',
|
|
916
2245
|
];
|
|
917
2246
|
const AVAILABLE_BLOCKS = ['if', 'repeat', 'for', 'while', 'fetch'];
|
|
918
2247
|
const FULL_RUNTIME_ONLY_COMMANDS = [
|
|
919
2248
|
'async',
|
|
920
|
-
'js',
|
|
921
2249
|
'make',
|
|
922
2250
|
'swap',
|
|
923
|
-
'morph',
|
|
924
2251
|
'process-partials',
|
|
925
2252
|
'bind',
|
|
926
2253
|
'persist',
|
|
927
2254
|
'default',
|
|
928
|
-
'beep',
|
|
929
2255
|
'tell',
|
|
930
|
-
'copy',
|
|
931
2256
|
'pick',
|
|
932
|
-
'push-url',
|
|
933
|
-
'replace-url',
|
|
934
|
-
'halt',
|
|
935
|
-
'exit',
|
|
936
|
-
'throw',
|
|
937
2257
|
'unless',
|
|
938
2258
|
'settle',
|
|
939
2259
|
'measure',
|
|
@@ -955,13 +2275,19 @@ exports.BLOCK_IMPLEMENTATIONS = BLOCK_IMPLEMENTATIONS;
|
|
|
955
2275
|
exports.COMMAND_IMPLEMENTATIONS = COMMAND_IMPLEMENTATIONS;
|
|
956
2276
|
exports.ELEMENT_ARRAY_COMMANDS = ELEMENT_ARRAY_COMMANDS;
|
|
957
2277
|
exports.FULL_RUNTIME_ONLY_COMMANDS = FULL_RUNTIME_ONLY_COMMANDS;
|
|
2278
|
+
exports.HYBRID_PARSER_TEMPLATE = HYBRID_PARSER_TEMPLATE;
|
|
2279
|
+
exports.LITE_PARSER_COMMANDS = LITE_PARSER_COMMANDS;
|
|
2280
|
+
exports.LITE_PARSER_TEMPLATE = LITE_PARSER_TEMPLATE;
|
|
2281
|
+
exports.MORPH_COMMANDS = MORPH_COMMANDS;
|
|
958
2282
|
exports.STYLE_COMMANDS = STYLE_COMMANDS;
|
|
2283
|
+
exports.canUseLiteParser = canUseLiteParser;
|
|
959
2284
|
exports.generateBundle = generateBundle;
|
|
960
2285
|
exports.generateBundleCode = generateBundleCode;
|
|
961
2286
|
exports.getAvailableBlocks = getAvailableBlocks;
|
|
962
2287
|
exports.getAvailableCommands = getAvailableCommands;
|
|
963
2288
|
exports.getBlockImplementations = getBlockImplementations;
|
|
964
2289
|
exports.getCommandImplementations = getCommandImplementations;
|
|
2290
|
+
exports.getParserTemplate = getParserTemplate;
|
|
965
2291
|
exports.isAvailableBlock = isAvailableBlock;
|
|
966
2292
|
exports.isAvailableCommand = isAvailableCommand;
|
|
967
2293
|
exports.requiresFullRuntime = requiresFullRuntime;
|