UNPKG

305 kBJavaScriptView Raw
1/**
2 * @typedef {Object} Hyperscript
3 */
4
5(function (self, factory) {
6 const _hyperscript = factory(self)
7
8 if (typeof exports === 'object' && typeof exports['nodeName'] !== 'string') {
9 module.exports = _hyperscript
10 } else {
11 self['_hyperscript'] = _hyperscript
12 if ('document' in self) self['_hyperscript'].browserInit()
13 }
14})(typeof self !== 'undefined' ? self : this, (globalScope) => {
15
16 'use strict';
17
18 /**
19 * @type {Object}
20 * @property {DynamicConverter[]} dynamicResolvers
21 *
22 * @callback DynamicConverter
23 * @param {String} str
24 * @param {*} value
25 * @returns {*}
26 */
27 const conversions = {
28 dynamicResolvers: [
29 function(str, value){
30 if (str === "Fixed") {
31 return Number(value).toFixed();
32 } else if (str.indexOf("Fixed:") === 0) {
33 let num = str.split(":")[1];
34 return Number(value).toFixed(parseInt(num));
35 }
36 }
37 ],
38 String: function (val) {
39 if (val.toString) {
40 return val.toString();
41 } else {
42 return "" + val;
43 }
44 },
45 Int: function (val) {
46 return parseInt(val);
47 },
48 Float: function (val) {
49 return parseFloat(val);
50 },
51 Number: function (val) {
52 return Number(val);
53 },
54 Date: function (val) {
55 return new Date(val);
56 },
57 Array: function (val) {
58 return Array.from(val);
59 },
60 JSON: function (val) {
61 return JSON.stringify(val);
62 },
63 Object: function (val) {
64 if (val instanceof String) {
65 val = val.toString();
66 }
67 if (typeof val === "string") {
68 return JSON.parse(val);
69 } else {
70 return Object.assign({}, val);
71 }
72 },
73 }
74
75 const config = {
76 attributes: "_, script, data-script",
77 defaultTransition: "all 500ms ease-in",
78 disableSelector: "[disable-scripting], [data-disable-scripting]",
79 hideShowStrategies: {},
80 conversions,
81 }
82
83 class Lexer {
84 static OP_TABLE = {
85 "+": "PLUS",
86 "-": "MINUS",
87 "*": "MULTIPLY",
88 "/": "DIVIDE",
89 ".": "PERIOD",
90 "..": "ELLIPSIS",
91 "\\": "BACKSLASH",
92 ":": "COLON",
93 "%": "PERCENT",
94 "|": "PIPE",
95 "!": "EXCLAMATION",
96 "?": "QUESTION",
97 "#": "POUND",
98 "&": "AMPERSAND",
99 $: "DOLLAR",
100 ";": "SEMI",
101 ",": "COMMA",
102 "(": "L_PAREN",
103 ")": "R_PAREN",
104 "<": "L_ANG",
105 ">": "R_ANG",
106 "<=": "LTE_ANG",
107 ">=": "GTE_ANG",
108 "==": "EQ",
109 "===": "EQQ",
110 "!=": "NEQ",
111 "!==": "NEQQ",
112 "{": "L_BRACE",
113 "}": "R_BRACE",
114 "[": "L_BRACKET",
115 "]": "R_BRACKET",
116 "=": "EQUALS",
117 };
118
119 /**
120 * isValidCSSClassChar returns `true` if the provided character is valid in a CSS class.
121 * @param {string} c
122 * @returns boolean
123 */
124 static isValidCSSClassChar(c) {
125 return Lexer.isAlpha(c) || Lexer.isNumeric(c) || c === "-" || c === "_" || c === ":";
126 }
127
128 /**
129 * isValidCSSIDChar returns `true` if the provided character is valid in a CSS ID
130 * @param {string} c
131 * @returns boolean
132 */
133 static isValidCSSIDChar(c) {
134 return Lexer.isAlpha(c) || Lexer.isNumeric(c) || c === "-" || c === "_" || c === ":";
135 }
136
137 /**
138 * isWhitespace returns `true` if the provided character is whitespace.
139 * @param {string} c
140 * @returns boolean
141 */
142 static isWhitespace(c) {
143 return c === " " || c === "\t" || Lexer.isNewline(c);
144 }
145
146 /**
147 * positionString returns a string representation of a Token's line and column details.
148 * @param {Token} token
149 * @returns string
150 */
151 static positionString(token) {
152 return "[Line: " + token.line + ", Column: " + token.column + "]";
153 }
154
155 /**
156 * isNewline returns `true` if the provided character is a carriage return or newline
157 * @param {string} c
158 * @returns boolean
159 */
160 static isNewline(c) {
161 return c === "\r" || c === "\n";
162 }
163
164 /**
165 * isNumeric returns `true` if the provided character is a number (0-9)
166 * @param {string} c
167 * @returns boolean
168 */
169 static isNumeric(c) {
170 return c >= "0" && c <= "9";
171 }
172
173 /**
174 * isAlpha returns `true` if the provided character is a letter in the alphabet
175 * @param {string} c
176 * @returns boolean
177 */
178 static isAlpha(c) {
179 return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z");
180 }
181
182 /**
183 * @param {string} c
184 * @param {boolean} [dollarIsOp]
185 * @returns boolean
186 */
187 static isIdentifierChar(c, dollarIsOp) {
188 return c === "_" || c === "$";
189 }
190
191 /**
192 * @param {string} c
193 * @returns boolean
194 */
195 static isReservedChar(c) {
196 return c === "`" || c === "^";
197 }
198
199 /**
200 * @param {Token[]} tokens
201 * @returns {boolean}
202 */
203 static isValidSingleQuoteStringStart(tokens) {
204 if (tokens.length > 0) {
205 var previousToken = tokens[tokens.length - 1];
206 if (
207 previousToken.type === "IDENTIFIER" ||
208 previousToken.type === "CLASS_REF" ||
209 previousToken.type === "ID_REF"
210 ) {
211 return false;
212 }
213 if (previousToken.op && (previousToken.value === ">" || previousToken.value === ")")) {
214 return false;
215 }
216 }
217 return true;
218 }
219
220 /**
221 * @param {string} string
222 * @param {boolean} [template]
223 * @returns {Tokens}
224 */
225 static tokenize(string, template) {
226 var tokens = /** @type {Token[]}*/ [];
227 var source = string;
228 var position = 0;
229 var column = 0;
230 var line = 1;
231 var lastToken = "<START>";
232 var templateBraceCount = 0;
233
234 function inTemplate() {
235 return template && templateBraceCount === 0;
236 }
237
238 while (position < source.length) {
239 if ((currentChar() === "-" && nextChar() === "-" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "-"))
240 || (currentChar() === "/" && nextChar() === "/" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "/"))) {
241 consumeComment();
242 } else if (currentChar() === "/" && nextChar() === "*" && (Lexer.isWhitespace(nextCharAt(2)) || nextCharAt(2) === "" || nextCharAt(2) === "*")) {
243 consumeCommentMultiline();
244 } else {
245 if (Lexer.isWhitespace(currentChar())) {
246 tokens.push(consumeWhitespace());
247 } else if (
248 !possiblePrecedingSymbol() &&
249 currentChar() === "." &&
250 (Lexer.isAlpha(nextChar()) || nextChar() === "{" || nextChar() === "-")
251 ) {
252 tokens.push(consumeClassReference());
253 } else if (
254 !possiblePrecedingSymbol() &&
255 currentChar() === "#" &&
256 (Lexer.isAlpha(nextChar()) || nextChar() === "{")
257 ) {
258 tokens.push(consumeIdReference());
259 } else if (currentChar() === "[" && nextChar() === "@") {
260 tokens.push(consumeAttributeReference());
261 } else if (currentChar() === "@") {
262 tokens.push(consumeShortAttributeReference());
263 } else if (currentChar() === "*" && Lexer.isAlpha(nextChar())) {
264 tokens.push(consumeStyleReference());
265 } else if (Lexer.isAlpha(currentChar()) || (!inTemplate() && Lexer.isIdentifierChar(currentChar()))) {
266 tokens.push(consumeIdentifier());
267 } else if (Lexer.isNumeric(currentChar())) {
268 tokens.push(consumeNumber());
269 } else if (!inTemplate() && (currentChar() === '"' || currentChar() === "`")) {
270 tokens.push(consumeString());
271 } else if (!inTemplate() && currentChar() === "'") {
272 if (Lexer.isValidSingleQuoteStringStart(tokens)) {
273 tokens.push(consumeString());
274 } else {
275 tokens.push(consumeOp());
276 }
277 } else if (Lexer.OP_TABLE[currentChar()]) {
278 if (lastToken === "$" && currentChar() === "{") {
279 templateBraceCount++;
280 }
281 if (currentChar() === "}") {
282 templateBraceCount--;
283 }
284 tokens.push(consumeOp());
285 } else if (inTemplate() || Lexer.isReservedChar(currentChar())) {
286 tokens.push(makeToken("RESERVED", consumeChar()));
287 } else {
288 if (position < source.length) {
289 throw Error("Unknown token: " + currentChar() + " ");
290 }
291 }
292 }
293 }
294
295 return new Tokens(tokens, [], source);
296
297 /**
298 * @param {string} [type]
299 * @param {string} [value]
300 * @returns {Token}
301 */
302 function makeOpToken(type, value) {
303 var token = makeToken(type, value);
304 token.op = true;
305 return token;
306 }
307
308 /**
309 * @param {string} [type]
310 * @param {string} [value]
311 * @returns {Token}
312 */
313 function makeToken(type, value) {
314 return {
315 type: type,
316 value: value || "",
317 start: position,
318 end: position + 1,
319 column: column,
320 line: line,
321 };
322 }
323
324 function consumeComment() {
325 while (currentChar() && !Lexer.isNewline(currentChar())) {
326 consumeChar();
327 }
328 consumeChar(); // Consume newline
329 }
330
331 function consumeCommentMultiline() {
332 while (currentChar() && !(currentChar() === '*' && nextChar() === '/')) {
333 consumeChar();
334 }
335 consumeChar(); // Consume "*/"
336 consumeChar();
337 }
338
339 /**
340 * @returns Token
341 */
342 function consumeClassReference() {
343 var classRef = makeToken("CLASS_REF");
344 var value = consumeChar();
345 if (currentChar() === "{") {
346 classRef.template = true;
347 value += consumeChar();
348 while (currentChar() && currentChar() !== "}") {
349 value += consumeChar();
350 }
351 if (currentChar() !== "}") {
352 throw Error("Unterminated class reference");
353 } else {
354 value += consumeChar(); // consume final curly
355 }
356 } else {
357 while (Lexer.isValidCSSClassChar(currentChar())) {
358 value += consumeChar();
359 }
360 }
361 classRef.value = value;
362 classRef.end = position;
363 return classRef;
364 }
365
366 /**
367 * @returns Token
368 */
369 function consumeAttributeReference() {
370 var attributeRef = makeToken("ATTRIBUTE_REF");
371 var value = consumeChar();
372 while (position < source.length && currentChar() !== "]") {
373 value += consumeChar();
374 }
375 if (currentChar() === "]") {
376 value += consumeChar();
377 }
378 attributeRef.value = value;
379 attributeRef.end = position;
380 return attributeRef;
381 }
382
383 function consumeShortAttributeReference() {
384 var attributeRef = makeToken("ATTRIBUTE_REF");
385 var value = consumeChar();
386 while (Lexer.isValidCSSIDChar(currentChar())) {
387 value += consumeChar();
388 }
389 if (currentChar() === '=') {
390 value += consumeChar();
391 if (currentChar() === '"' || currentChar() === "'") {
392 let stringValue = consumeString();
393 value += stringValue.value;
394 } else if(Lexer.isAlpha(currentChar()) ||
395 Lexer.isNumeric(currentChar()) ||
396 Lexer.isIdentifierChar(currentChar())) {
397 let id = consumeIdentifier();
398 value += id.value;
399 }
400 }
401 attributeRef.value = value;
402 attributeRef.end = position;
403 return attributeRef;
404 }
405
406 function consumeStyleReference() {
407 var styleRef = makeToken("STYLE_REF");
408 var value = consumeChar();
409 while (Lexer.isAlpha(currentChar()) || currentChar() === "-") {
410 value += consumeChar();
411 }
412 styleRef.value = value;
413 styleRef.end = position;
414 return styleRef;
415 }
416
417 /**
418 * @returns Token
419 */
420 function consumeIdReference() {
421 var idRef = makeToken("ID_REF");
422 var value = consumeChar();
423 if (currentChar() === "{") {
424 idRef.template = true;
425 value += consumeChar();
426 while (currentChar() && currentChar() !== "}") {
427 value += consumeChar();
428 }
429 if (currentChar() !== "}") {
430 throw Error("Unterminated id reference");
431 } else {
432 consumeChar(); // consume final quote
433 }
434 } else {
435 while (Lexer.isValidCSSIDChar(currentChar())) {
436 value += consumeChar();
437 }
438 }
439 idRef.value = value;
440 idRef.end = position;
441 return idRef;
442 }
443
444 /**
445 * @returns Token
446 */
447 function consumeIdentifier() {
448 var identifier = makeToken("IDENTIFIER");
449 var value = consumeChar();
450 while (Lexer.isAlpha(currentChar()) ||
451 Lexer.isNumeric(currentChar()) ||
452 Lexer.isIdentifierChar(currentChar())) {
453 value += consumeChar();
454 }
455 if (currentChar() === "!" && value === "beep") {
456 value += consumeChar();
457 }
458 identifier.value = value;
459 identifier.end = position;
460 return identifier;
461 }
462
463 /**
464 * @returns Token
465 */
466 function consumeNumber() {
467 var number = makeToken("NUMBER");
468 var value = consumeChar();
469
470 // given possible XXX.YYY(e|E)[-]ZZZ consume XXX
471 while (Lexer.isNumeric(currentChar())) {
472 value += consumeChar();
473 }
474
475 // consume .YYY
476 if (currentChar() === "." && Lexer.isNumeric(nextChar())) {
477 value += consumeChar();
478 }
479 while (Lexer.isNumeric(currentChar())) {
480 value += consumeChar();
481 }
482
483 // consume (e|E)[-]
484 if (currentChar() === "e" || currentChar() === "E") {
485 // possible scientific notation, e.g. 1e6 or 1e-6
486 if (Lexer.isNumeric(nextChar())) {
487 // e.g. 1e6
488 value += consumeChar();
489 } else if (nextChar() === "-") {
490 // e.g. 1e-6
491 value += consumeChar();
492 // consume the - as well since otherwise we would stop on the next loop
493 value += consumeChar();
494 }
495 }
496
497 // consume ZZZ
498 while (Lexer.isNumeric(currentChar())) {
499 value += consumeChar();
500 }
501 number.value = value;
502 number.end = position;
503 return number;
504 }
505
506 /**
507 * @returns Token
508 */
509 function consumeOp() {
510 var op = makeOpToken();
511 var value = consumeChar(); // consume leading char
512 while (currentChar() && Lexer.OP_TABLE[value + currentChar()]) {
513 value += consumeChar();
514 }
515 op.type = Lexer.OP_TABLE[value];
516 op.value = value;
517 op.end = position;
518 return op;
519 }
520
521 /**
522 * @returns Token
523 */
524 function consumeString() {
525 var string = makeToken("STRING");
526 var startChar = consumeChar(); // consume leading quote
527 var value = "";
528 while (currentChar() && currentChar() !== startChar) {
529 if (currentChar() === "\\") {
530 consumeChar(); // consume escape char and get the next one
531 let nextChar = consumeChar();
532 if (nextChar === "b") {
533 value += "\b";
534 } else if (nextChar === "f") {
535 value += "\f";
536 } else if (nextChar === "n") {
537 value += "\n";
538 } else if (nextChar === "r") {
539 value += "\r";
540 } else if (nextChar === "t") {
541 value += "\t";
542 } else if (nextChar === "v") {
543 value += "\v";
544 } else if (nextChar === "x") {
545 const hex = consumeHexEscape();
546 if (Number.isNaN(hex)) {
547 throw Error("Invalid hexadecimal escape at " + Lexer.positionString(string));
548 }
549 value += String.fromCharCode(hex);
550 } else {
551 value += nextChar;
552 }
553 } else {
554 value += consumeChar();
555 }
556 }
557 if (currentChar() !== startChar) {
558 throw Error("Unterminated string at " + Lexer.positionString(string));
559 } else {
560 consumeChar(); // consume final quote
561 }
562 string.value = value;
563 string.end = position;
564 string.template = startChar === "`";
565 return string;
566 }
567
568 /**
569 * @returns number
570 */
571 function consumeHexEscape() {
572 const BASE = 16;
573 if (!currentChar()) {
574 return NaN;
575 }
576 let result = BASE * Number.parseInt(consumeChar(), BASE);
577 if (!currentChar()) {
578 return NaN;
579 }
580 result += Number.parseInt(consumeChar(), BASE);
581
582 return result;
583 }
584
585 /**
586 * @returns string
587 */
588 function currentChar() {
589 return source.charAt(position);
590 }
591
592 /**
593 * @returns string
594 */
595 function nextChar() {
596 return source.charAt(position + 1);
597 }
598
599 function nextCharAt(number = 1) {
600 return source.charAt(position + number);
601 }
602
603 /**
604 * @returns string
605 */
606 function consumeChar() {
607 lastToken = currentChar();
608 position++;
609 column++;
610 return lastToken;
611 }
612
613 /**
614 * @returns boolean
615 */
616 function possiblePrecedingSymbol() {
617 return (
618 Lexer.isAlpha(lastToken) ||
619 Lexer.isNumeric(lastToken) ||
620 lastToken === ")" ||
621 lastToken === "\"" ||
622 lastToken === "'" ||
623 lastToken === "`" ||
624 lastToken === "}" ||
625 lastToken === "]"
626 );
627 }
628
629 /**
630 * @returns Token
631 */
632 function consumeWhitespace() {
633 var whitespace = makeToken("WHITESPACE");
634 var value = "";
635 while (currentChar() && Lexer.isWhitespace(currentChar())) {
636 if (Lexer.isNewline(currentChar())) {
637 column = 0;
638 line++;
639 }
640 value += consumeChar();
641 }
642 whitespace.value = value;
643 whitespace.end = position;
644 return whitespace;
645 }
646 }
647
648 /**
649 * @param {string} string
650 * @param {boolean} [template]
651 * @returns {Tokens}
652 */
653 tokenize(string, template) {
654 return Lexer.tokenize(string, template)
655 }
656 }
657
658 /**
659 * @typedef {Object} Token
660 * @property {string} [type]
661 * @property {string} value
662 * @property {number} [start]
663 * @property {number} [end]
664 * @property {number} [column]
665 * @property {number} [line]
666 * @property {boolean} [op] `true` if this token represents an operator
667 * @property {boolean} [template] `true` if this token is a template, for class refs, id refs, strings
668 */
669
670 class Tokens {
671 constructor(tokens, consumed, source) {
672 this.tokens = tokens
673 this.consumed = consumed
674 this.source = source
675
676 this.consumeWhitespace(); // consume initial whitespace
677 }
678
679 get list() {
680 return this.tokens
681 }
682
683 /** @type Token | null */
684 _lastConsumed = null;
685
686 consumeWhitespace() {
687 while (this.token(0, true).type === "WHITESPACE") {
688 this.consumed.push(this.tokens.shift());
689 }
690 }
691
692 /**
693 * @param {Tokens} tokens
694 * @param {*} error
695 * @returns {never}
696 */
697 raiseError(tokens, error) {
698 Parser.raiseParseError(tokens, error);
699 }
700
701 /**
702 * @param {string} value
703 * @returns {Token}
704 */
705 requireOpToken(value) {
706 var token = this.matchOpToken(value);
707 if (token) {
708 return token;
709 } else {
710 this.raiseError(this, "Expected '" + value + "' but found '" + this.currentToken().value + "'");
711 }
712 }
713
714 /**
715 * @param {string} op1
716 * @param {string} [op2]
717 * @param {string} [op3]
718 * @returns {Token | void}
719 */
720 matchAnyOpToken(op1, op2, op3) {
721 for (var i = 0; i < arguments.length; i++) {
722 var opToken = arguments[i];
723 var match = this.matchOpToken(opToken);
724 if (match) {
725 return match;
726 }
727 }
728 }
729
730 /**
731 * @param {string} op1
732 * @param {string} [op2]
733 * @param {string} [op3]
734 * @returns {Token | void}
735 */
736 matchAnyToken(op1, op2, op3) {
737 for (var i = 0; i < arguments.length; i++) {
738 var opToken = arguments[i];
739 var match = this.matchToken(opToken);
740 if (match) {
741 return match;
742 }
743 }
744 }
745
746 /**
747 * @param {string} value
748 * @returns {Token | void}
749 */
750 matchOpToken(value) {
751 if (this.currentToken() && this.currentToken().op && this.currentToken().value === value) {
752 return this.consumeToken();
753 }
754 }
755
756 /**
757 * @param {string} type1
758 * @param {string} [type2]
759 * @param {string} [type3]
760 * @param {string} [type4]
761 * @returns {Token}
762 */
763 requireTokenType(type1, type2, type3, type4) {
764 var token = this.matchTokenType(type1, type2, type3, type4);
765 if (token) {
766 return token;
767 } else {
768 this.raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3]));
769 }
770 }
771
772 /**
773 * @param {string} type1
774 * @param {string} [type2]
775 * @param {string} [type3]
776 * @param {string} [type4]
777 * @returns {Token | void}
778 */
779 matchTokenType(type1, type2, type3, type4) {
780 if (
781 this.currentToken() &&
782 this.currentToken().type &&
783 [type1, type2, type3, type4].indexOf(this.currentToken().type) >= 0
784 ) {
785 return this.consumeToken();
786 }
787 }
788
789 /**
790 * @param {string} value
791 * @param {string} [type]
792 * @returns {Token}
793 */
794 requireToken(value, type) {
795 var token = this.matchToken(value, type);
796 if (token) {
797 return token;
798 } else {
799 this.raiseError(this, "Expected '" + value + "' but found '" + this.currentToken().value + "'");
800 }
801 }
802
803 peekToken(value, peek, type) {
804 peek = peek || 0;
805 type = type || "IDENTIFIER";
806 if(this.tokens[peek] && this.tokens[peek].value === value && this.tokens[peek].type === type){
807 return this.tokens[peek];
808 }
809 }
810
811 /**
812 * @param {string} value
813 * @param {string} [type]
814 * @returns {Token | void}
815 */
816 matchToken(value, type) {
817 if (this.follows.indexOf(value) !== -1) {
818 return; // disallowed token here
819 }
820 type = type || "IDENTIFIER";
821 if (this.currentToken() && this.currentToken().value === value && this.currentToken().type === type) {
822 return this.consumeToken();
823 }
824 }
825
826 /**
827 * @returns {Token}
828 */
829 consumeToken() {
830 var match = this.tokens.shift();
831 this.consumed.push(match);
832 this._lastConsumed = match;
833 this.consumeWhitespace(); // consume any whitespace
834 return match;
835 }
836
837 /**
838 * @param {string | null} value
839 * @param {string | null} [type]
840 * @returns {Token[]}
841 */
842 consumeUntil(value, type) {
843 /** @type Token[] */
844 var tokenList = [];
845 var currentToken = this.token(0, true);
846
847 while (
848 (type == null || currentToken.type !== type) &&
849 (value == null || currentToken.value !== value) &&
850 currentToken.type !== "EOF"
851 ) {
852 var match = this.tokens.shift();
853 this.consumed.push(match);
854 tokenList.push(currentToken);
855 currentToken = this.token(0, true);
856 }
857 this.consumeWhitespace(); // consume any whitespace
858 return tokenList;
859 }
860
861 /**
862 * @returns {string}
863 */
864 lastWhitespace() {
865 if (this.consumed[this.consumed.length - 1] && this.consumed[this.consumed.length - 1].type === "WHITESPACE") {
866 return this.consumed[this.consumed.length - 1].value;
867 } else {
868 return "";
869 }
870 }
871
872 consumeUntilWhitespace() {
873 return this.consumeUntil(null, "WHITESPACE");
874 }
875
876 /**
877 * @returns {boolean}
878 */
879 hasMore() {
880 return this.tokens.length > 0;
881 }
882
883 /**
884 * @param {number} n
885 * @param {boolean} [dontIgnoreWhitespace]
886 * @returns {Token}
887 */
888 token(n, dontIgnoreWhitespace) {
889 var /**@type {Token}*/ token;
890 var i = 0;
891 do {
892 if (!dontIgnoreWhitespace) {
893 while (this.tokens[i] && this.tokens[i].type === "WHITESPACE") {
894 i++;
895 }
896 }
897 token = this.tokens[i];
898 n--;
899 i++;
900 } while (n > -1);
901 if (token) {
902 return token;
903 } else {
904 return {
905 type: "EOF",
906 value: "<<<EOF>>>",
907 };
908 }
909 }
910
911 /**
912 * @returns {Token}
913 */
914 currentToken() {
915 return this.token(0);
916 }
917
918 /**
919 * @returns {Token | null}
920 */
921 lastMatch() {
922 return this._lastConsumed;
923 }
924
925 /**
926 * @returns {string}
927 */
928 static sourceFor = function () {
929 return this.programSource.substring(this.startToken.start, this.endToken.end);
930 }
931
932 /**
933 * @returns {string}
934 */
935 static lineFor = function () {
936 return this.programSource.split("\n")[this.startToken.line - 1];
937 }
938
939 follows = [];
940
941 pushFollow(str) {
942 this.follows.push(str);
943 }
944
945 popFollow() {
946 this.follows.pop();
947 }
948
949 clearFollows() {
950 var tmp = this.follows;
951 this.follows = [];
952 return tmp;
953 }
954
955 restoreFollows(f) {
956 this.follows = f;
957 }
958 }
959
960 /**
961 * @callback ParseRule
962 * @param {Parser} parser
963 * @param {Runtime} runtime
964 * @param {Tokens} tokens
965 * @param {*} [root]
966 * @returns {ASTNode | undefined}
967 *
968 * @typedef {Object} ASTNode
969 * @member {boolean} isFeature
970 * @member {string} type
971 * @member {any[]} args
972 * @member {(this: ASTNode, ctx:Context, root:any, ...args:any) => any} op
973 * @member {(this: ASTNode, context?:Context) => any} evaluate
974 * @member {ASTNode} parent
975 * @member {Set<ASTNode>} children
976 * @member {ASTNode} root
977 * @member {String} keyword
978 * @member {Token} endToken
979 * @member {ASTNode} next
980 * @member {(context:Context) => ASTNode} resolveNext
981 * @member {EventSource} eventSource
982 * @member {(this: ASTNode) => void} install
983 * @member {(this: ASTNode, context:Context) => void} execute
984 * @member {(this: ASTNode, target: object, source: object, args?: Object) => void} apply
985 *
986 *
987 */
988
989 class Parser {
990 /**
991 *
992 * @param {Runtime} runtime
993 */
994 constructor(runtime) {
995 this.runtime = runtime
996
997 this.possessivesDisabled = false
998
999 /* ============================================================================================ */
1000 /* Core hyperscript Grammar Elements */
1001 /* ============================================================================================ */
1002 this.addGrammarElement("feature", function (parser, runtime, tokens) {
1003 if (tokens.matchOpToken("(")) {
1004 var featureElement = parser.requireElement("feature", tokens);
1005 tokens.requireOpToken(")");
1006 return featureElement;
1007 }
1008
1009 var featureDefinition = parser.FEATURES[tokens.currentToken().value || ""];
1010 if (featureDefinition) {
1011 return featureDefinition(parser, runtime, tokens);
1012 }
1013 });
1014
1015 this.addGrammarElement("command", function (parser, runtime, tokens) {
1016 if (tokens.matchOpToken("(")) {
1017 const commandElement = parser.requireElement("command", tokens);
1018 tokens.requireOpToken(")");
1019 return commandElement;
1020 }
1021
1022 var commandDefinition = parser.COMMANDS[tokens.currentToken().value || ""];
1023 let commandElement;
1024 if (commandDefinition) {
1025 commandElement = commandDefinition(parser, runtime, tokens);
1026 } else if (tokens.currentToken().type === "IDENTIFIER") {
1027 commandElement = parser.parseElement("pseudoCommand", tokens);
1028 }
1029 if (commandElement) {
1030 return parser.parseElement("indirectStatement", tokens, commandElement);
1031 }
1032
1033 return commandElement;
1034 });
1035
1036 this.addGrammarElement("commandList", function (parser, runtime, tokens) {
1037 if (tokens.hasMore()) {
1038 var cmd = parser.parseElement("command", tokens);
1039 if (cmd) {
1040 tokens.matchToken("then");
1041 const next = parser.parseElement("commandList", tokens);
1042 if (next) cmd.next = next;
1043 return cmd;
1044 }
1045 }
1046 return {
1047 type: "emptyCommandListCommand",
1048 op: function(context){
1049 return runtime.findNext(this, context);
1050 },
1051 execute: function (context) {
1052 return runtime.unifiedExec(this, context);
1053 }
1054 }
1055 });
1056
1057 this.addGrammarElement("leaf", function (parser, runtime, tokens) {
1058 var result = parser.parseAnyOf(parser.LEAF_EXPRESSIONS, tokens);
1059 // symbol is last so it doesn't consume any constants
1060 if (result == null) {
1061 return parser.parseElement("symbol", tokens);
1062 }
1063
1064 return result;
1065 });
1066
1067 this.addGrammarElement("indirectExpression", function (parser, runtime, tokens, root) {
1068 for (var i = 0; i < parser.INDIRECT_EXPRESSIONS.length; i++) {
1069 var indirect = parser.INDIRECT_EXPRESSIONS[i];
1070 root.endToken = tokens.lastMatch();
1071 var result = parser.parseElement(indirect, tokens, root);
1072 if (result) {
1073 return result;
1074 }
1075 }
1076 return root;
1077 });
1078
1079 this.addGrammarElement("indirectStatement", function (parser, runtime, tokens, root) {
1080 if (tokens.matchToken("unless")) {
1081 root.endToken = tokens.lastMatch();
1082 var conditional = parser.requireElement("expression", tokens);
1083 var unless = {
1084 type: "unlessStatementModifier",
1085 args: [conditional],
1086 op: function (context, conditional) {
1087 if (conditional) {
1088 return this.next;
1089 } else {
1090 return root;
1091 }
1092 },
1093 execute: function (context) {
1094 return runtime.unifiedExec(this, context);
1095 },
1096 };
1097 root.parent = unless;
1098 return unless;
1099 }
1100 return root;
1101 });
1102
1103 this.addGrammarElement("primaryExpression", function (parser, runtime, tokens) {
1104 var leaf = parser.parseElement("leaf", tokens);
1105 if (leaf) {
1106 return parser.parseElement("indirectExpression", tokens, leaf);
1107 }
1108 parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value);
1109 });
1110 }
1111
1112 use(plugin) {
1113 plugin(this)
1114 return this
1115 }
1116
1117 /** @type {Object<string,ParseRule>} */
1118 GRAMMAR = {};
1119
1120 /** @type {Object<string,ParseRule>} */
1121 COMMANDS = {};
1122
1123 /** @type {Object<string,ParseRule>} */
1124 FEATURES = {};
1125
1126 /** @type {string[]} */
1127 LEAF_EXPRESSIONS = [];
1128 /** @type {string[]} */
1129 INDIRECT_EXPRESSIONS = [];
1130
1131 /**
1132 * @param {*} parseElement
1133 * @param {*} start
1134 * @param {Tokens} tokens
1135 */
1136 initElt(parseElement, start, tokens) {
1137 parseElement.startToken = start;
1138 parseElement.sourceFor = Tokens.sourceFor;
1139 parseElement.lineFor = Tokens.lineFor;
1140 parseElement.programSource = tokens.source;
1141 }
1142
1143 /**
1144 * @param {string} type
1145 * @param {Tokens} tokens
1146 * @param {ASTNode?} root
1147 * @returns {ASTNode}
1148 */
1149 parseElement(type, tokens, root = undefined) {
1150 var elementDefinition = this.GRAMMAR[type];
1151 if (elementDefinition) {
1152 var start = tokens.currentToken();
1153 var parseElement = elementDefinition(this, this.runtime, tokens, root);
1154 if (parseElement) {
1155 this.initElt(parseElement, start, tokens);
1156 parseElement.endToken = parseElement.endToken || tokens.lastMatch();
1157 var root = parseElement.root;
1158 while (root != null) {
1159 this.initElt(root, start, tokens);
1160 root = root.root;
1161 }
1162 }
1163 return parseElement;
1164 }
1165 }
1166
1167 /**
1168 * @param {string} type
1169 * @param {Tokens} tokens
1170 * @param {string} [message]
1171 * @param {*} [root]
1172 * @returns {ASTNode}
1173 */
1174 requireElement(type, tokens, message, root) {
1175 var result = this.parseElement(type, tokens, root);
1176 if (!result) Parser.raiseParseError(tokens, message || "Expected " + type);
1177 // @ts-ignore
1178 return result;
1179 }
1180
1181 /**
1182 * @param {string[]} types
1183 * @param {Tokens} tokens
1184 * @returns {ASTNode}
1185 */
1186 parseAnyOf(types, tokens) {
1187 for (var i = 0; i < types.length; i++) {
1188 var type = types[i];
1189 var expression = this.parseElement(type, tokens);
1190 if (expression) {
1191 return expression;
1192 }
1193 }
1194 }
1195
1196 /**
1197 * @param {string} name
1198 * @param {ParseRule} definition
1199 */
1200 addGrammarElement(name, definition) {
1201 this.GRAMMAR[name] = definition;
1202 }
1203
1204 /**
1205 * @param {string} keyword
1206 * @param {ParseRule} definition
1207 */
1208 addCommand(keyword, definition) {
1209 var commandGrammarType = keyword + "Command";
1210 var commandDefinitionWrapper = function (parser, runtime, tokens) {
1211 const commandElement = definition(parser, runtime, tokens);
1212 if (commandElement) {
1213 commandElement.type = commandGrammarType;
1214 commandElement.execute = function (context) {
1215 context.meta.command = commandElement;
1216 return runtime.unifiedExec(this, context);
1217 };
1218 return commandElement;
1219 }
1220 };
1221 this.GRAMMAR[commandGrammarType] = commandDefinitionWrapper;
1222 this.COMMANDS[keyword] = commandDefinitionWrapper;
1223 }
1224
1225 /**
1226 * @param {string} keyword
1227 * @param {ParseRule} definition
1228 */
1229 addFeature(keyword, definition) {
1230 var featureGrammarType = keyword + "Feature";
1231
1232 /** @type {ParseRule} */
1233 var featureDefinitionWrapper = function (parser, runtime, tokens) {
1234 var featureElement = definition(parser, runtime, tokens);
1235 if (featureElement) {
1236 featureElement.isFeature = true;
1237 featureElement.keyword = keyword;
1238 featureElement.type = featureGrammarType;
1239 return featureElement;
1240 }
1241 };
1242 this.GRAMMAR[featureGrammarType] = featureDefinitionWrapper;
1243 this.FEATURES[keyword] = featureDefinitionWrapper;
1244 }
1245
1246 /**
1247 * @param {string} name
1248 * @param {ParseRule} definition
1249 */
1250 addLeafExpression(name, definition) {
1251 this.LEAF_EXPRESSIONS.push(name);
1252 this.addGrammarElement(name, definition);
1253 }
1254
1255 /**
1256 * @param {string} name
1257 * @param {ParseRule} definition
1258 */
1259 addIndirectExpression(name, definition) {
1260 this.INDIRECT_EXPRESSIONS.push(name);
1261 this.addGrammarElement(name, definition);
1262 }
1263
1264 /**
1265 *
1266 * @param {Tokens} tokens
1267 * @returns string
1268 */
1269 static createParserContext(tokens) {
1270 var currentToken = tokens.currentToken();
1271 var source = tokens.source;
1272 var lines = source.split("\n");
1273 var line = currentToken && currentToken.line ? currentToken.line - 1 : lines.length - 1;
1274 var contextLine = lines[line];
1275 var offset = /** @type {number} */ (
1276 currentToken && currentToken.line ? currentToken.column : contextLine.length - 1);
1277 return contextLine + "\n" + " ".repeat(offset) + "^^\n\n";
1278 }
1279
1280 /**
1281 * @param {Tokens} tokens
1282 * @param {string} [message]
1283 * @returns {never}
1284 */
1285 static raiseParseError(tokens, message) {
1286 message =
1287 (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" + Parser.createParserContext(tokens);
1288 var error = new Error(message);
1289 error["tokens"] = tokens;
1290 throw error;
1291 }
1292
1293 /**
1294 * @param {Tokens} tokens
1295 * @param {string} [message]
1296 */
1297 raiseParseError(tokens, message) {
1298 Parser.raiseParseError(tokens, message)
1299 }
1300
1301 /**
1302 * @param {Tokens} tokens
1303 * @returns {ASTNode}
1304 */
1305 parseHyperScript(tokens) {
1306 var result = this.parseElement("hyperscript", tokens);
1307 if (tokens.hasMore()) this.raiseParseError(tokens);
1308 if (result) return result;
1309 }
1310
1311 /**
1312 * @param {ASTNode | undefined} elt
1313 * @param {ASTNode} parent
1314 */
1315 setParent(elt, parent) {
1316 if (typeof elt === 'object') {
1317 elt.parent = parent;
1318 if (typeof parent === 'object') {
1319 parent.children = (parent.children || new Set());
1320 parent.children.add(elt)
1321 }
1322 this.setParent(elt.next, parent);
1323 }
1324 }
1325
1326 /**
1327 * @param {Token} token
1328 * @returns {ParseRule}
1329 */
1330 commandStart(token) {
1331 return this.COMMANDS[token.value || ""];
1332 }
1333
1334 /**
1335 * @param {Token} token
1336 * @returns {ParseRule}
1337 */
1338 featureStart(token) {
1339 return this.FEATURES[token.value || ""];
1340 }
1341
1342 /**
1343 * @param {Token} token
1344 * @returns {boolean}
1345 */
1346 commandBoundary(token) {
1347 if (
1348 token.value == "end" ||
1349 token.value == "then" ||
1350 token.value == "else" ||
1351 token.value == "otherwise" ||
1352 token.value == ")" ||
1353 this.commandStart(token) ||
1354 this.featureStart(token) ||
1355 token.type == "EOF"
1356 ) {
1357 return true;
1358 }
1359 return false;
1360 }
1361
1362 /**
1363 * @param {Tokens} tokens
1364 * @returns {(string | ASTNode)[]}
1365 */
1366 parseStringTemplate(tokens) {
1367 /** @type {(string | ASTNode)[]} */
1368 var returnArr = [""];
1369 do {
1370 returnArr.push(tokens.lastWhitespace());
1371 if (tokens.currentToken().value === "$") {
1372 tokens.consumeToken();
1373 var startingBrace = tokens.matchOpToken("{");
1374 returnArr.push(this.requireElement("expression", tokens));
1375 if (startingBrace) {
1376 tokens.requireOpToken("}");
1377 }
1378 returnArr.push("");
1379 } else if (tokens.currentToken().value === "\\") {
1380 tokens.consumeToken(); // skip next
1381 tokens.consumeToken();
1382 } else {
1383 var token = tokens.consumeToken();
1384 returnArr[returnArr.length - 1] += token ? token.value : "";
1385 }
1386 } while (tokens.hasMore());
1387 returnArr.push(tokens.lastWhitespace());
1388 return returnArr;
1389 }
1390
1391 /**
1392 * @param {ASTNode} commandList
1393 */
1394 ensureTerminated(commandList) {
1395 const runtime = this.runtime
1396 var implicitReturn = {
1397 type: "implicitReturn",
1398 op: function (context) {
1399 context.meta.returned = true;
1400 if (context.meta.resolve) {
1401 context.meta.resolve();
1402 }
1403 return runtime.HALT;
1404 },
1405 execute: function (ctx) {
1406 // do nothing
1407 },
1408 };
1409
1410 var end = commandList;
1411 while (end.next) {
1412 end = end.next;
1413 }
1414 end.next = implicitReturn;
1415 }
1416 }
1417
1418 class Runtime {
1419 /**
1420 *
1421 * @param {Lexer} [lexer]
1422 * @param {Parser} [parser]
1423 */
1424 constructor(lexer, parser) {
1425 this.lexer = lexer ?? new Lexer;
1426 this.parser = parser ?? new Parser(this)
1427 .use(hyperscriptCoreGrammar)
1428 .use(hyperscriptWebGrammar);
1429 this.parser.runtime = this
1430 }
1431
1432 /**
1433 * @param {HTMLElement} elt
1434 * @param {string} selector
1435 * @returns boolean
1436 */
1437 matchesSelector(elt, selector) {
1438 // noinspection JSUnresolvedVariable
1439 var matchesFunction =
1440 // @ts-ignore
1441 elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector;
1442 return matchesFunction && matchesFunction.call(elt, selector);
1443 }
1444
1445 /**
1446 * @param {string} eventName
1447 * @param {Object} [detail]
1448 * @returns {Event}
1449 */
1450 makeEvent(eventName, detail) {
1451 var evt;
1452 if (globalScope.Event && typeof globalScope.Event === "function") {
1453 evt = new Event(eventName, {
1454 bubbles: true,
1455 cancelable: true,
1456 });
1457 evt['detail'] = detail;
1458 } else {
1459 evt = document.createEvent("CustomEvent");
1460 evt.initCustomEvent(eventName, true, true, detail);
1461 }
1462 return evt;
1463 }
1464
1465 /**
1466 * @param {Element} elt
1467 * @param {string} eventName
1468 * @param {Object} [detail]
1469 * @param {Element} [sender]
1470 * @returns {boolean}
1471 */
1472 triggerEvent(elt, eventName, detail, sender) {
1473 detail = detail || {};
1474 detail["sender"] = sender;
1475 var event = this.makeEvent(eventName, detail);
1476 var eventResult = elt.dispatchEvent(event);
1477 return eventResult;
1478 }
1479
1480 /**
1481 * isArrayLike returns `true` if the provided value is an array or
1482 * a NodeList (which is close enough to being an array for our purposes).
1483 *
1484 * @param {any} value
1485 * @returns {value is Array | NodeList}
1486 */
1487 isArrayLike(value) {
1488 return Array.isArray(value) ||
1489 (typeof NodeList !== 'undefined' && (value instanceof NodeList || value instanceof HTMLCollection));
1490 }
1491
1492 /**
1493 * isIterable returns `true` if the provided value supports the
1494 * iterator protocol.
1495 *
1496 * @param {any} value
1497 * @returns {value is Iterable}
1498 */
1499 isIterable(value) {
1500 return typeof value === 'object'
1501 && Symbol.iterator in value
1502 && typeof value[Symbol.iterator] === 'function';
1503 }
1504
1505 /**
1506 * shouldAutoIterate returns `true` if the provided value
1507 * should be implicitly iterated over when accessing properties,
1508 * and as the target of some commands.
1509 *
1510 * Currently, this is when the value is an {ElementCollection}
1511 * or {isArrayLike} returns true.
1512 *
1513 * @param {any} value
1514 * @returns {value is (any[] | ElementCollection)}
1515 */
1516 shouldAutoIterate(value) {
1517 return value != null && value[shouldAutoIterateSymbol] ||
1518 this.isArrayLike(value);
1519 }
1520
1521 /**
1522 * forEach executes the provided `func` on every item in the `value` array.
1523 * if `value` is a single item (and not an array) then `func` is simply called
1524 * once. If `value` is null, then no further actions are taken.
1525 *
1526 * @template T
1527 * @param {T | Iterable<T>} value
1528 * @param {(item: T) => void} func
1529 */
1530 forEach(value, func) {
1531 if (value == null) {
1532 // do nothing
1533 } else if (this.isIterable(value)) {
1534 for (const nth of value) {
1535 func(nth);
1536 }
1537 } else if (this.isArrayLike(value)) {
1538 for (var i = 0; i < value.length; i++) {
1539 func(value[i]);
1540 }
1541 } else {
1542 func(value);
1543 }
1544 }
1545
1546 /**
1547 * implicitLoop executes the provided `func` on:
1548 * - every item of {value}, if {value} should be auto-iterated
1549 * (see {shouldAutoIterate})
1550 * - {value} otherwise
1551 *
1552 * @template T
1553 * @param {ElementCollection | T | T[]} value
1554 * @param {(item: T) => void} func
1555 */
1556 implicitLoop(value, func) {
1557 if (this.shouldAutoIterate(value)) {
1558 for (const x of value) func(x);
1559 } else {
1560 func(value);
1561 }
1562 }
1563
1564 wrapArrays(args) {
1565 var arr = [];
1566 for (var i = 0; i < args.length; i++) {
1567 var arg = args[i];
1568 if (Array.isArray(arg)) {
1569 arr.push(Promise.all(arg));
1570 } else {
1571 arr.push(arg);
1572 }
1573 }
1574 return arr;
1575 }
1576
1577 unwrapAsyncs(values) {
1578 for (var i = 0; i < values.length; i++) {
1579 var value = values[i];
1580 if (value.asyncWrapper) {
1581 values[i] = value.value;
1582 }
1583 if (Array.isArray(value)) {
1584 for (var j = 0; j < value.length; j++) {
1585 var valueElement = value[j];
1586 if (valueElement.asyncWrapper) {
1587 value[j] = valueElement.value;
1588 }
1589 }
1590 }
1591 }
1592 }
1593
1594 static HALT = {};
1595 HALT = Runtime.HALT;
1596
1597 /**
1598 * @param {ASTNode} command
1599 * @param {Context} ctx
1600 */
1601 unifiedExec(command, ctx) {
1602 while (true) {
1603 try {
1604 var next = this.unifiedEval(command, ctx);
1605 } catch (e) {
1606 if (ctx.meta.handlingFinally) {
1607 console.error(" Exception in finally block: ", e);
1608 next = Runtime.HALT;
1609 } else {
1610 this.registerHyperTrace(ctx, e);
1611 if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
1612 ctx.meta.handlingError = true;
1613 ctx.locals[ctx.meta.errorSymbol] = e;
1614 command = ctx.meta.errorHandler;
1615 continue;
1616 } else {
1617 ctx.meta.currentException = e;
1618 next = Runtime.HALT;
1619 }
1620 }
1621 }
1622 if (next == null) {
1623 console.error(command, " did not return a next element to execute! context: ", ctx);
1624 return;
1625 } else if (next.then) {
1626 next.then(resolvedNext => {
1627 this.unifiedExec(resolvedNext, ctx);
1628 }).catch(reason => {
1629 this.unifiedExec({ // Anonymous command to simply throw the exception
1630 op: function(){
1631 throw reason;
1632 }
1633 }, ctx);
1634 });
1635 return;
1636 } else if (next === Runtime.HALT) {
1637 if (ctx.meta.finallyHandler && !ctx.meta.handlingFinally) {
1638 ctx.meta.handlingFinally = true;
1639 command = ctx.meta.finallyHandler;
1640 } else {
1641 if (ctx.meta.onHalt) {
1642 ctx.meta.onHalt();
1643 }
1644 if (ctx.meta.currentException) {
1645 if (ctx.meta.reject) {
1646 ctx.meta.reject(ctx.meta.currentException);
1647 return;
1648 } else {
1649 throw ctx.meta.currentException;
1650 }
1651 } else {
1652 return;
1653 }
1654 }
1655 } else {
1656 command = next; // move to the next command
1657 }
1658 }
1659 }
1660
1661 /**
1662 * @param {*} parseElement
1663 * @param {Context} ctx
1664 * @returns {*}
1665 */
1666 unifiedEval(parseElement, ctx) {
1667 /** @type any[] */
1668 var args = [ctx];
1669 var async = false;
1670 var wrappedAsyncs = false;
1671
1672 if (parseElement.args) {
1673 for (var i = 0; i < parseElement.args.length; i++) {
1674 var argument = parseElement.args[i];
1675 if (argument == null) {
1676 args.push(null);
1677 } else if (Array.isArray(argument)) {
1678 var arr = [];
1679 for (var j = 0; j < argument.length; j++) {
1680 var element = argument[j];
1681 var value = element ? element.evaluate(ctx) : null; // OK
1682 if (value) {
1683 if (value.then) {
1684 async = true;
1685 } else if (value.asyncWrapper) {
1686 wrappedAsyncs = true;
1687 }
1688 }
1689 arr.push(value);
1690 }
1691 args.push(arr);
1692 } else if (argument.evaluate) {
1693 var value = argument.evaluate(ctx); // OK
1694 if (value) {
1695 if (value.then) {
1696 async = true;
1697 } else if (value.asyncWrapper) {
1698 wrappedAsyncs = true;
1699 }
1700 }
1701 args.push(value);
1702 } else {
1703 args.push(argument);
1704 }
1705 }
1706 }
1707 if (async) {
1708 return new Promise((resolve, reject) => {
1709 args = this.wrapArrays(args);
1710 Promise.all(args)
1711 .then(function (values) {
1712 if (wrappedAsyncs) {
1713 this.unwrapAsyncs(values);
1714 }
1715 try {
1716 var apply = parseElement.op.apply(parseElement, values);
1717 resolve(apply);
1718 } catch (e) {
1719 reject(e);
1720 }
1721 })
1722 .catch(function (reason) {
1723 reject(reason);
1724 });
1725 });
1726 } else {
1727 if (wrappedAsyncs) {
1728 this.unwrapAsyncs(args);
1729 }
1730 return parseElement.op.apply(parseElement, args);
1731 }
1732 }
1733
1734 /**
1735 * @type {string[] | null}
1736 */
1737 _scriptAttrs = null;
1738
1739 /**
1740 * getAttributes returns the attribute name(s) to use when
1741 * locating hyperscript scripts in a DOM element. If no value
1742 * has been configured, it defaults to config.attributes
1743 * @returns string[]
1744 */
1745 getScriptAttributes() {
1746 if (this._scriptAttrs == null) {
1747 this._scriptAttrs = config.attributes.replace(/ /g, "").split(",");
1748 }
1749 return this._scriptAttrs;
1750 }
1751
1752 /**
1753 * @param {Element} elt
1754 * @returns {string | null}
1755 */
1756 getScript(elt) {
1757 for (var i = 0; i < this.getScriptAttributes().length; i++) {
1758 var scriptAttribute = this.getScriptAttributes()[i];
1759 if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
1760 return elt.getAttribute(scriptAttribute);
1761 }
1762 }
1763 if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") {
1764 return elt.innerText;
1765 }
1766 return null;
1767 }
1768
1769 hyperscriptFeaturesMap = new WeakMap
1770
1771 /**
1772 * @param {*} elt
1773 * @returns {Object}
1774 */
1775 getHyperscriptFeatures(elt) {
1776 var hyperscriptFeatures = this.hyperscriptFeaturesMap.get(elt);
1777 if (typeof hyperscriptFeatures === 'undefined') {
1778 if (elt) {
1779 // in some rare cases, elt is null and this line crashes
1780 this.hyperscriptFeaturesMap.set(elt, hyperscriptFeatures = {});
1781 }
1782 }
1783 return hyperscriptFeatures;
1784 }
1785
1786 /**
1787 * @param {Object} owner
1788 * @param {Context} ctx
1789 */
1790 addFeatures(owner, ctx) {
1791 if (owner) {
1792 Object.assign(ctx.locals, this.getHyperscriptFeatures(owner));
1793 this.addFeatures(owner.parentElement, ctx);
1794 }
1795 }
1796
1797 /**
1798 * @param {*} owner
1799 * @param {*} feature
1800 * @param {*} hyperscriptTarget
1801 * @param {*} event
1802 * @returns {Context}
1803 */
1804 makeContext(owner, feature, hyperscriptTarget, event) {
1805 return new Context(owner, feature, hyperscriptTarget, event, this)
1806 }
1807
1808 /**
1809 * @returns string
1810 */
1811 getScriptSelector() {
1812 return this.getScriptAttributes()
1813 .map(function (attribute) {
1814 return "[" + attribute + "]";
1815 })
1816 .join(", ");
1817 }
1818
1819 /**
1820 * @param {any} value
1821 * @param {string} type
1822 * @returns {any}
1823 */
1824 convertValue(value, type) {
1825 var dynamicResolvers = conversions.dynamicResolvers;
1826 for (var i = 0; i < dynamicResolvers.length; i++) {
1827 var dynamicResolver = dynamicResolvers[i];
1828 var converted = dynamicResolver(type, value);
1829 if (converted !== undefined) {
1830 return converted;
1831 }
1832 }
1833
1834 if (value == null) {
1835 return null;
1836 }
1837 var converter = conversions[type];
1838 if (converter) {
1839 return converter(value);
1840 }
1841
1842 throw "Unknown conversion : " + type;
1843 }
1844
1845 /**
1846 * @param {string} src
1847 * @returns {ASTNode}
1848 */
1849 parse(src) {
1850 const lexer = this.lexer, parser = this.parser
1851 var tokens = lexer.tokenize(src);
1852 if (this.parser.commandStart(tokens.currentToken())) {
1853 var commandList = parser.requireElement("commandList", tokens);
1854 if (tokens.hasMore()) parser.raiseParseError(tokens);
1855 parser.ensureTerminated(commandList);
1856 return commandList;
1857 } else if (parser.featureStart(tokens.currentToken())) {
1858 var hyperscript = parser.requireElement("hyperscript", tokens);
1859 if (tokens.hasMore()) parser.raiseParseError(tokens);
1860 return hyperscript;
1861 } else {
1862 var expression = parser.requireElement("expression", tokens);
1863 if (tokens.hasMore()) parser.raiseParseError(tokens);
1864 return expression;
1865 }
1866 }
1867
1868 /**
1869 *
1870 * @param {ASTNode} elt
1871 * @param {Context} ctx
1872 * @returns {any}
1873 */
1874 evaluateNoPromise(elt, ctx) {
1875 let result = elt.evaluate(ctx);
1876 if (result.next) {
1877 throw new Error(Tokens.sourceFor.call(elt) + " returned a Promise in a context that they are not allowed.");
1878 }
1879 return result;
1880 }
1881
1882 /**
1883 * @param {string} src
1884 * @param {Partial<Context>} [ctx]
1885 * @param {Object} [args]
1886 * @returns {any}
1887 */
1888 evaluate(src, ctx, args) {
1889 class HyperscriptModule extends EventTarget {
1890 constructor(mod) {
1891 super();
1892 this.module = mod;
1893 }
1894 toString() {
1895 return this.module.id;
1896 }
1897 }
1898
1899 var body = 'document' in globalScope
1900 ? globalScope.document.body
1901 : new HyperscriptModule(args && args.module);
1902 ctx = Object.assign(this.makeContext(body, null, body, null), ctx || {});
1903 var element = this.parse(src);
1904 if (element.execute) {
1905 element.execute(ctx);
1906 if(typeof ctx.meta.returnValue !== 'undefined'){
1907 return ctx.meta.returnValue;
1908 } else {
1909 return ctx.result;
1910 }
1911 } else if (element.apply) {
1912 element.apply(body, body, args);
1913 return this.getHyperscriptFeatures(body);
1914 } else {
1915 return element.evaluate(ctx);
1916 }
1917
1918 function makeModule() {
1919 return {}
1920 }
1921 }
1922
1923 /**
1924 * @param {HTMLElement} elt
1925 */
1926 processNode(elt) {
1927 var selector = this.getScriptSelector();
1928 if (this.matchesSelector(elt, selector)) {
1929 this.initElement(elt, elt);
1930 }
1931 if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") {
1932 this.initElement(elt, document.body);
1933 }
1934 if (elt.querySelectorAll) {
1935 this.forEach(elt.querySelectorAll(selector + ", [type='text/hyperscript']"), elt => {
1936 this.initElement(elt, elt instanceof HTMLScriptElement && elt.type === "text/hyperscript" ? document.body : elt);
1937 });
1938 }
1939 }
1940
1941 /**
1942 * @param {Element} elt
1943 * @param {Element} [target]
1944 */
1945 initElement(elt, target) {
1946 if (elt.closest && elt.closest(config.disableSelector)) {
1947 return;
1948 }
1949 var internalData = this.getInternalData(elt);
1950 if (!internalData.initialized) {
1951 var src = this.getScript(elt);
1952 if (src) {
1953 try {
1954 internalData.initialized = true;
1955 internalData.script = src;
1956 const lexer = this.lexer, parser = this.parser
1957 var tokens = lexer.tokenize(src);
1958 var hyperScript = parser.parseHyperScript(tokens);
1959 if (!hyperScript) return;
1960 hyperScript.apply(target || elt, elt);
1961 setTimeout(() => {
1962 this.triggerEvent(target || elt, "load", {
1963 hyperscript: true,
1964 });
1965 }, 1);
1966 } catch (e) {
1967 this.triggerEvent(elt, "exception", {
1968 error: e,
1969 });
1970 console.error(
1971 "hyperscript errors were found on the following element:",
1972 elt,
1973 "\n\n",
1974 e.message,
1975 e.stack
1976 );
1977 }
1978 }
1979 }
1980 }
1981
1982 internalDataMap = new WeakMap
1983
1984 /**
1985 * @param {Element} elt
1986 * @returns {Object}
1987 */
1988 getInternalData(elt) {
1989 var internalData = this.internalDataMap.get(elt);
1990 if (typeof internalData === 'undefined') {
1991 this.internalDataMap.set(elt, internalData = {});
1992 }
1993 return internalData;
1994 }
1995
1996 /**
1997 * @param {any} value
1998 * @param {string} typeString
1999 * @param {boolean} [nullOk]
2000 * @returns {boolean}
2001 */
2002 typeCheck(value, typeString, nullOk) {
2003 if (value == null && nullOk) {
2004 return true;
2005 }
2006 var typeName = Object.prototype.toString.call(value).slice(8, -1);
2007 return typeName === typeString;
2008 }
2009
2010 getElementScope(context) {
2011 var elt = context.meta && context.meta.owner;
2012 if (elt) {
2013 var internalData = this.getInternalData(elt);
2014 var scopeName = "elementScope";
2015 if (context.meta.feature && context.meta.feature.behavior) {
2016 scopeName = context.meta.feature.behavior + "Scope";
2017 }
2018 var elementScope = getOrInitObject(internalData, scopeName);
2019 return elementScope;
2020 } else {
2021 return {}; // no element, return empty scope
2022 }
2023 }
2024
2025 /**
2026 * @param {string} str
2027 * @returns {boolean}
2028 */
2029 isReservedWord(str) {
2030 return ["meta", "it", "result", "locals", "event", "target", "detail", "sender", "body"].includes(str)
2031 }
2032
2033 /**
2034 * @param {any} context
2035 * @returns {boolean}
2036 */
2037 isHyperscriptContext(context) {
2038 return context instanceof Context;
2039 }
2040
2041 /**
2042 * @param {string} str
2043 * @param {Context} context
2044 * @returns {any}
2045 */
2046 resolveSymbol(str, context, type) {
2047 if (str === "me" || str === "my" || str === "I") {
2048 return context.me;
2049 }
2050 if (str === "it" || str === "its" || str === "result") {
2051 return context.result;
2052 }
2053 if (str === "you" || str === "your" || str === "yourself") {
2054 return context.you;
2055 } else {
2056 if (type === "global") {
2057 return globalScope[str];
2058 } else if (type === "element") {
2059 var elementScope = this.getElementScope(context);
2060 return elementScope[str];
2061 } else if (type === "local") {
2062 return context.locals[str];
2063 } else {
2064 // meta scope (used for event conditionals)
2065 if (context.meta && context.meta.context) {
2066 var fromMetaContext = context.meta.context[str];
2067 if (typeof fromMetaContext !== "undefined") {
2068 return fromMetaContext;
2069 }
2070 // resolve against the `detail` object in the meta context as well
2071 if (context.meta.context.detail) {
2072 fromMetaContext = context.meta.context.detail[str];
2073 if (typeof fromMetaContext !== "undefined") {
2074 return fromMetaContext;
2075 }
2076 }
2077 }
2078 if (this.isHyperscriptContext(context) && !this.isReservedWord(str)) {
2079 // local scope
2080 var fromContext = context.locals[str];
2081 } else {
2082 // direct get from normal JS object or top-level of context
2083 var fromContext = context[str];
2084 }
2085 if (typeof fromContext !== "undefined") {
2086 return fromContext;
2087 } else {
2088 // element scope
2089 var elementScope = this.getElementScope(context);
2090 fromContext = elementScope[str];
2091 if (typeof fromContext !== "undefined") {
2092 return fromContext;
2093 } else {
2094 // global scope
2095 return globalScope[str];
2096 }
2097 }
2098 }
2099 }
2100 }
2101
2102 setSymbol(str, context, type, value) {
2103 if (type === "global") {
2104 globalScope[str] = value;
2105 } else if (type === "element") {
2106 var elementScope = this.getElementScope(context);
2107 elementScope[str] = value;
2108 } else if (type === "local") {
2109 context.locals[str] = value;
2110 } else {
2111 if (this.isHyperscriptContext(context) && !this.isReservedWord(str) && typeof context.locals[str] !== "undefined") {
2112 // local scope
2113 context.locals[str] = value;
2114 } else {
2115 // element scope
2116 var elementScope = this.getElementScope(context);
2117 var fromContext = elementScope[str];
2118 if (typeof fromContext !== "undefined") {
2119 elementScope[str] = value;
2120 } else {
2121 if (this.isHyperscriptContext(context) && !this.isReservedWord(str)) {
2122 // local scope
2123 context.locals[str] = value;
2124 } else {
2125 // direct set on normal JS object or top-level of context
2126 context[str] = value;
2127 }
2128 }
2129 }
2130 }
2131 }
2132
2133 /**
2134 * @param {ASTNode} command
2135 * @param {Context} context
2136 * @returns {undefined | ASTNode}
2137 */
2138 findNext(command, context) {
2139 if (command) {
2140 if (command.resolveNext) {
2141 return command.resolveNext(context);
2142 } else if (command.next) {
2143 return command.next;
2144 } else {
2145 return this.findNext(command.parent, context);
2146 }
2147 }
2148 }
2149
2150 /**
2151 * @param {Object<string,any>} root
2152 * @param {string} property
2153 * @param {Getter} getter
2154 * @returns {any}
2155 *
2156 * @callback Getter
2157 * @param {Object<string,any>} root
2158 * @param {string} property
2159 */
2160 flatGet(root, property, getter) {
2161 if (root != null) {
2162 var val = getter(root, property);
2163 if (typeof val !== "undefined") {
2164 return val;
2165 }
2166
2167 if (this.shouldAutoIterate(root)) {
2168 // flat map
2169 var result = [];
2170 for (var component of root) {
2171 var componentValue = getter(component, property);
2172 result.push(componentValue);
2173 }
2174 return result;
2175 }
2176 }
2177 }
2178
2179 resolveProperty(root, property) {
2180 return this.flatGet(root, property, (root, property) => root[property] )
2181 }
2182
2183 resolveAttribute(root, property) {
2184 return this.flatGet(root, property, (root, property) => root.getAttribute && root.getAttribute(property) )
2185 }
2186
2187 /**
2188 *
2189 * @param {Object<string, any>} root
2190 * @param {string} property
2191 * @returns {string}
2192 */
2193 resolveStyle(root, property) {
2194 return this.flatGet(root, property, (root, property) => root.style && root.style[property] )
2195 }
2196
2197 /**
2198 *
2199 * @param {Object<string, any>} root
2200 * @param {string} property
2201 * @returns {string}
2202 */
2203 resolveComputedStyle(root, property) {
2204 return this.flatGet(root, property, (root, property) => getComputedStyle(
2205 /** @type {Element} */ (root)).getPropertyValue(property) )
2206 }
2207
2208 /**
2209 * @param {Element} elt
2210 * @param {string[]} nameSpace
2211 * @param {string} name
2212 * @param {any} value
2213 */
2214 assignToNamespace(elt, nameSpace, name, value) {
2215 let root
2216 if (typeof document !== "undefined" && elt === document.body) {
2217 root = globalScope;
2218 } else {
2219 root = this.getHyperscriptFeatures(elt);
2220 }
2221 var propertyName;
2222 while ((propertyName = nameSpace.shift()) !== undefined) {
2223 var newRoot = root[propertyName];
2224 if (newRoot == null) {
2225 newRoot = {};
2226 root[propertyName] = newRoot;
2227 }
2228 root = newRoot;
2229 }
2230
2231 root[name] = value;
2232 }
2233
2234 getHyperTrace(ctx, thrown) {
2235 var trace = [];
2236 var root = ctx;
2237 while (root.meta.caller) {
2238 root = root.meta.caller;
2239 }
2240 if (root.meta.traceMap) {
2241 return root.meta.traceMap.get(thrown, trace);
2242 }
2243 }
2244
2245 registerHyperTrace(ctx, thrown) {
2246 var trace = [];
2247 var root = null;
2248 while (ctx != null) {
2249 trace.push(ctx);
2250 root = ctx;
2251 ctx = ctx.meta.caller;
2252 }
2253 if (root.meta.traceMap == null) {
2254 root.meta.traceMap = new Map(); // TODO - WeakMap?
2255 }
2256 if (!root.meta.traceMap.get(thrown)) {
2257 var traceEntry = {
2258 trace: trace,
2259 print: function (logger) {
2260 logger = logger || console.error;
2261 logger("hypertrace /// ");
2262 var maxLen = 0;
2263 for (var i = 0; i < trace.length; i++) {
2264 maxLen = Math.max(maxLen, trace[i].meta.feature.displayName.length);
2265 }
2266 for (var i = 0; i < trace.length; i++) {
2267 var traceElt = trace[i];
2268 logger(
2269 " ->",
2270 traceElt.meta.feature.displayName.padEnd(maxLen + 2),
2271 "-",
2272 traceElt.meta.owner
2273 );
2274 }
2275 },
2276 };
2277 root.meta.traceMap.set(thrown, traceEntry);
2278 }
2279 }
2280
2281 /**
2282 * @param {string} str
2283 * @returns {string}
2284 */
2285 escapeSelector(str) {
2286 return str.replace(/:/g, function (str) {
2287 return "\\" + str;
2288 });
2289 }
2290
2291 /**
2292 * @param {any} value
2293 * @param {*} elt
2294 */
2295 nullCheck(value, elt) {
2296 if (value == null) {
2297 throw new Error("'" + elt.sourceFor() + "' is null");
2298 }
2299 }
2300
2301 /**
2302 * @param {any} value
2303 * @returns {boolean}
2304 */
2305 isEmpty(value) {
2306 return value == undefined || value.length === 0;
2307 }
2308
2309 /**
2310 * @param {any} value
2311 * @returns {boolean}
2312 */
2313 doesExist(value) {
2314 if(value == null){
2315 return false;
2316 }
2317 if (this.shouldAutoIterate(value)) {
2318 for (const elt of value) {
2319 return true;
2320 }
2321 return false;
2322 }
2323 return true;
2324 }
2325
2326 /**
2327 * @param {Node} node
2328 * @returns {Document|ShadowRoot}
2329 */
2330 getRootNode(node) {
2331 if (node && node instanceof Node) {
2332 var rv = node.getRootNode();
2333 if (rv instanceof Document || rv instanceof ShadowRoot) return rv;
2334 }
2335 return document;
2336 }
2337
2338 /**
2339 *
2340 * @param {Element} elt
2341 * @param {ASTNode} onFeature
2342 * @returns {EventQueue}
2343 *
2344 * @typedef {{queue:Array, executing:boolean}} EventQueue
2345 */
2346 getEventQueueFor(elt, onFeature) {
2347 let internalData = this.getInternalData(elt);
2348 var eventQueuesForElt = internalData.eventQueues;
2349 if (eventQueuesForElt == null) {
2350 eventQueuesForElt = new Map();
2351 internalData.eventQueues = eventQueuesForElt;
2352 }
2353 var eventQueueForFeature = eventQueuesForElt.get(onFeature);
2354 if (eventQueueForFeature == null) {
2355 eventQueueForFeature = {queue:[], executing:false};
2356 eventQueuesForElt.set(onFeature, eventQueueForFeature);
2357 }
2358 return eventQueueForFeature;
2359 }
2360
2361 beepValueToConsole(element, expression, value) {
2362 if (this.triggerEvent(element, "hyperscript:beep", {element, expression, value})) {
2363 var typeName;
2364 if (value) {
2365 if (value instanceof ElementCollection) {
2366 typeName = "ElementCollection";
2367 } else if (value.constructor) {
2368 typeName = value.constructor.name;
2369 } else {
2370 typeName = "unknown";
2371 }
2372 } else {
2373 typeName = "object (null)"
2374 }
2375 var logValue = value;
2376 if (typeName === "String") {
2377 logValue = '"' + logValue + '"';
2378 } else if (value instanceof ElementCollection) {
2379 logValue = Array.from(value);
2380 }
2381 console.log("///_ BEEP! The expression (" + Tokens.sourceFor.call(expression).replace("beep! ", "") + ") evaluates to:", logValue, "of type " + typeName);
2382 }
2383 }
2384
2385
2386 /** @type string | null */
2387 // @ts-ignore
2388 hyperscriptUrl = "document" in globalScope && document.currentScript ? document.currentScript.src : null;
2389 }
2390
2391
2392 function getCookiesAsArray() {
2393 let cookiesAsArray = document.cookie
2394 .split("; ")
2395 .map(cookieEntry => {
2396 let strings = cookieEntry.split("=");
2397 return {name: strings[0], value: decodeURIComponent(strings[1])}
2398 });
2399 return cookiesAsArray;
2400 }
2401
2402 function clearCookie(name) {
2403 document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
2404 }
2405
2406 function clearAllCookies() {
2407 for (const cookie of getCookiesAsArray()) {
2408 clearCookie(cookie.name);
2409 }
2410 }
2411
2412 const CookieJar = new Proxy({}, {
2413 get(target, prop) {
2414 if (prop === 'then' || prop === 'asyncWrapper') { // ignore special symbols
2415 return null;
2416 } else if (prop === 'length') {
2417 return getCookiesAsArray().length
2418 } else if (prop === 'clear') {
2419 return clearCookie;
2420 } else if (prop === 'clearAll') {
2421 return clearAllCookies;
2422 } else if (typeof prop === "string") {
2423 if (!isNaN(prop)) {
2424 return getCookiesAsArray()[parseInt(prop)];
2425
2426 } else {
2427 let value = document.cookie
2428 .split("; ")
2429 .find((row) => row.startsWith(prop + "="))
2430 ?.split("=")[1];
2431 if(value) {
2432 return decodeURIComponent(value);
2433 }
2434 }
2435 } else if (prop === Symbol.iterator) {
2436 return getCookiesAsArray()[prop];
2437 }
2438 },
2439 set(target, prop, value) {
2440 var finalValue = null;
2441 if ('string' === typeof value) {
2442 finalValue = encodeURIComponent(value)
2443 finalValue += ";samesite=lax"
2444 } else {
2445 finalValue = encodeURIComponent(value.value);
2446 if (value.expires) {
2447 finalValue+=";expires=" + value.maxAge;
2448 }
2449 if (value.maxAge) {
2450 finalValue+=";max-age=" + value.maxAge;
2451 }
2452 if (value.partitioned) {
2453 finalValue+=";partitioned=" + value.partitioned;
2454 }
2455 if (value.path) {
2456 finalValue+=";path=" + value.path;
2457 }
2458 if (value.samesite) {
2459 finalValue+=";samesite=" + value.path;
2460 }
2461 if (value.secure) {
2462 finalValue+=";secure=" + value.path;
2463 }
2464 }
2465 document.cookie= prop + "=" + finalValue;
2466 return true;
2467 }
2468 })
2469
2470 class Context {
2471 /**
2472 * @param {*} owner
2473 * @param {*} feature
2474 * @param {*} hyperscriptTarget
2475 * @param {*} event
2476 */
2477 constructor(owner, feature, hyperscriptTarget, event, runtime) {
2478 this.meta = {
2479 parser: runtime.parser,
2480 lexer: runtime.lexer,
2481 runtime,
2482 owner: owner,
2483 feature: feature,
2484 iterators: {},
2485 ctx: this
2486 }
2487 this.locals = {
2488 cookies:CookieJar
2489 };
2490 this.me = hyperscriptTarget,
2491 this.you = undefined
2492 this.result = undefined
2493 this.event = event;
2494 this.target = event ? event.target : null;
2495 this.detail = event ? event.detail : null;
2496 this.sender = event ? event.detail ? event.detail.sender : null : null;
2497 this.body = "document" in globalScope ? document.body : null;
2498 runtime.addFeatures(owner, this);
2499 }
2500 }
2501
2502 class ElementCollection {
2503 constructor(css, relativeToElement, escape) {
2504 this._css = css;
2505 this.relativeToElement = relativeToElement;
2506 this.escape = escape;
2507 this[shouldAutoIterateSymbol] = true;
2508 }
2509
2510 get css() {
2511 if (this.escape) {
2512 return Runtime.prototype.escapeSelector(this._css);
2513 } else {
2514 return this._css;
2515 }
2516 }
2517
2518 get className() {
2519 return this._css.substr(1);
2520 }
2521
2522 get id() {
2523 return this.className();
2524 }
2525
2526 contains(elt) {
2527 for (let element of this) {
2528 if (element.contains(elt)) {
2529 return true;
2530 }
2531 }
2532 return false;
2533 }
2534
2535 get length() {
2536 return this.selectMatches().length;
2537 }
2538
2539 [Symbol.iterator]() {
2540 let query = this.selectMatches();
2541 return query [Symbol.iterator]();
2542 }
2543
2544 selectMatches() {
2545 let query = Runtime.prototype.getRootNode(this.relativeToElement).querySelectorAll(this.css);
2546 return query;
2547 }
2548 }
2549
2550 /**
2551 * @type {symbol}
2552 */
2553 const shouldAutoIterateSymbol = Symbol()
2554
2555 function getOrInitObject(root, prop) {
2556 var value = root[prop];
2557 if (value) {
2558 return value;
2559 } else {
2560 var newObj = {};
2561 root[prop] = newObj;
2562 return newObj;
2563 }
2564 }
2565
2566 /**
2567 * parseJSON parses a JSON string into a corresponding value. If the
2568 * value passed in is not valid JSON, then it logs an error and returns `null`.
2569 *
2570 * @param {string} jString
2571 * @returns any
2572 */
2573 function parseJSON(jString) {
2574 try {
2575 return JSON.parse(jString);
2576 } catch (error) {
2577 logError(error);
2578 return null;
2579 }
2580 }
2581
2582 /**
2583 * logError writes an error message to the Javascript console. It can take any
2584 * value, but msg should commonly be a simple string.
2585 * @param {*} msg
2586 */
2587 function logError(msg) {
2588 if (console.error) {
2589 console.error(msg);
2590 } else if (console.log) {
2591 console.log("ERROR: ", msg);
2592 }
2593 }
2594
2595 // TODO: JSDoc description of what's happening here
2596 function varargConstructor(Cls, args) {
2597 return new (Cls.bind.apply(Cls, [Cls].concat(args)))();
2598 }
2599
2600 // Grammar
2601
2602 /**
2603 * @param {Parser} parser
2604 */
2605 function hyperscriptCoreGrammar(parser) {
2606 parser.addLeafExpression("parenthesized", function (parser, _runtime, tokens) {
2607 if (tokens.matchOpToken("(")) {
2608 var follows = tokens.clearFollows();
2609 try {
2610 var expr = parser.requireElement("expression", tokens);
2611 } finally {
2612 tokens.restoreFollows(follows);
2613 }
2614 tokens.requireOpToken(")");
2615 return expr;
2616 }
2617 });
2618
2619 parser.addLeafExpression("string", function (parser, runtime, tokens) {
2620 var stringToken = tokens.matchTokenType("STRING");
2621 if (!stringToken) return;
2622 var rawValue = /** @type {string} */ (stringToken.value);
2623 /** @type {any[]} */
2624 var args;
2625 if (stringToken.template) {
2626 var innerTokens = Lexer.tokenize(rawValue, true);
2627 args = parser.parseStringTemplate(innerTokens);
2628 } else {
2629 args = [];
2630 }
2631 return {
2632 type: "string",
2633 token: stringToken,
2634 args: args,
2635 op: function (context) {
2636 var returnStr = "";
2637 for (var i = 1; i < arguments.length; i++) {
2638 var val = arguments[i];
2639 if (val !== undefined) {
2640 returnStr += val;
2641 }
2642 }
2643 return returnStr;
2644 },
2645 evaluate: function (context) {
2646 if (args.length === 0) {
2647 return rawValue;
2648 } else {
2649 return runtime.unifiedEval(this, context);
2650 }
2651 },
2652 };
2653 });
2654
2655 parser.addGrammarElement("nakedString", function (parser, runtime, tokens) {
2656 if (tokens.hasMore()) {
2657 var tokenArr = tokens.consumeUntilWhitespace();
2658 tokens.matchTokenType("WHITESPACE");
2659 return {
2660 type: "nakedString",
2661 tokens: tokenArr,
2662 evaluate: function (context) {
2663 return tokenArr
2664 .map(function (t) {
2665 return t.value;
2666 })
2667 .join("");
2668 },
2669 };
2670 }
2671 });
2672
2673 parser.addLeafExpression("number", function (parser, runtime, tokens) {
2674 var number = tokens.matchTokenType("NUMBER");
2675 if (!number) return;
2676 var numberToken = number;
2677 var value = parseFloat(/** @type {string} */ (number.value));
2678 return {
2679 type: "number",
2680 value: value,
2681 numberToken: numberToken,
2682 evaluate: function () {
2683 return value;
2684 },
2685 };
2686 });
2687
2688 parser.addLeafExpression("idRef", function (parser, runtime, tokens) {
2689 var elementId = tokens.matchTokenType("ID_REF");
2690 if (!elementId) return;
2691 if (!elementId.value) return;
2692 // TODO - unify these two expression types
2693 if (elementId.template) {
2694 var templateValue = elementId.value.substring(2);
2695 var innerTokens = Lexer.tokenize(templateValue);
2696 var innerExpression = parser.requireElement("expression", innerTokens);
2697 return {
2698 type: "idRefTemplate",
2699 args: [innerExpression],
2700 op: function (context, arg) {
2701 return runtime.getRootNode(context.me).getElementById(arg);
2702 },
2703 evaluate: function (context) {
2704 return runtime.unifiedEval(this, context);
2705 },
2706 };
2707 } else {
2708 const value = elementId.value.substring(1);
2709 return {
2710 type: "idRef",
2711 css: elementId.value,
2712 value: value,
2713 evaluate: function (context) {
2714 return (
2715 runtime.getRootNode(context.me).getElementById(value)
2716 );
2717 },
2718 };
2719 }
2720 });
2721
2722 parser.addLeafExpression("classRef", function (parser, runtime, tokens) {
2723 var classRef = tokens.matchTokenType("CLASS_REF");
2724
2725 if (!classRef) return;
2726 if (!classRef.value) return;
2727
2728 // TODO - unify these two expression types
2729 if (classRef.template) {
2730 var templateValue = classRef.value.substring(2);
2731 var innerTokens = Lexer.tokenize(templateValue);
2732 var innerExpression = parser.requireElement("expression", innerTokens);
2733 return {
2734 type: "classRefTemplate",
2735 args: [innerExpression],
2736 op: function (context, arg) {
2737 return new ElementCollection("." + arg, context.me, true)
2738 },
2739 evaluate: function (context) {
2740 return runtime.unifiedEval(this, context);
2741 },
2742 };
2743 } else {
2744 const css = classRef.value;
2745 return {
2746 type: "classRef",
2747 css: css,
2748 evaluate: function (context) {
2749 return new ElementCollection(css, context.me, true)
2750 },
2751 };
2752 }
2753 });
2754
2755 class TemplatedQueryElementCollection extends ElementCollection {
2756 constructor(css, relativeToElement, templateParts) {
2757 super(css, relativeToElement);
2758 this.templateParts = templateParts;
2759 this.elements = templateParts.filter(elt => elt instanceof Element);
2760 }
2761
2762 get css() {
2763 let rv = "", i = 0
2764 for (const val of this.templateParts) {
2765 if (val instanceof Element) {
2766 rv += "[data-hs-query-id='" + i++ + "']";
2767 } else rv += val;
2768 }
2769 return rv;
2770 }
2771
2772 [Symbol.iterator]() {
2773 this.elements.forEach((el, i) => el.dataset.hsQueryId = i);
2774 const rv = super[Symbol.iterator]();
2775 this.elements.forEach(el => el.removeAttribute('data-hs-query-id'));
2776 return rv;
2777 }
2778 }
2779
2780 parser.addLeafExpression("queryRef", function (parser, runtime, tokens) {
2781 var queryStart = tokens.matchOpToken("<");
2782 if (!queryStart) return;
2783 var queryTokens = tokens.consumeUntil("/");
2784 tokens.requireOpToken("/");
2785 tokens.requireOpToken(">");
2786 var queryValue = queryTokens
2787 .map(function (t) {
2788 if (t.type === "STRING") {
2789 return '"' + t.value + '"';
2790 } else {
2791 return t.value;
2792 }
2793 })
2794 .join("");
2795
2796 var template, innerTokens, args;
2797 if (queryValue.indexOf("$") >= 0) {
2798 template = true;
2799 innerTokens = Lexer.tokenize(queryValue, true);
2800 args = parser.parseStringTemplate(innerTokens);
2801 }
2802
2803 return {
2804 type: "queryRef",
2805 css: queryValue,
2806 args: args,
2807 op: function (context, ...args) {
2808 if (template) {
2809 return new TemplatedQueryElementCollection(queryValue, context.me, args)
2810 } else {
2811 return new ElementCollection(queryValue, context.me)
2812 }
2813 },
2814 evaluate: function (context) {
2815 return runtime.unifiedEval(this, context);
2816 },
2817 };
2818 });
2819
2820 parser.addLeafExpression("attributeRef", function (parser, runtime, tokens) {
2821 var attributeRef = tokens.matchTokenType("ATTRIBUTE_REF");
2822 if (!attributeRef) return;
2823 if (!attributeRef.value) return;
2824 var outerVal = attributeRef.value;
2825 if (outerVal.indexOf("[") === 0) {
2826 var innerValue = outerVal.substring(2, outerVal.length - 1);
2827 } else {
2828 var innerValue = outerVal.substring(1);
2829 }
2830 var css = "[" + innerValue + "]";
2831 var split = innerValue.split("=");
2832 var name = split[0];
2833 var value = split[1];
2834 if (value) {
2835 // strip quotes
2836 if (value.indexOf('"') === 0) {
2837 value = value.substring(1, value.length - 1);
2838 }
2839 }
2840 return {
2841 type: "attributeRef",
2842 name: name,
2843 css: css,
2844 value: value,
2845 op: function (context) {
2846 var target = context.you || context.me;
2847 if (target) {
2848 return target.getAttribute(name);
2849 }
2850 },
2851 evaluate: function (context) {
2852 return runtime.unifiedEval(this, context);
2853 },
2854 };
2855 });
2856
2857 parser.addLeafExpression("styleRef", function (parser, runtime, tokens) {
2858 var styleRef = tokens.matchTokenType("STYLE_REF");
2859 if (!styleRef) return;
2860 if (!styleRef.value) return;
2861 var styleProp = styleRef.value.substr(1);
2862 if (styleProp.startsWith("computed-")) {
2863 styleProp = styleProp.substr("computed-".length);
2864 return {
2865 type: "computedStyleRef",
2866 name: styleProp,
2867 op: function (context) {
2868 var target = context.you || context.me;
2869 if (target) {
2870 return runtime.resolveComputedStyle(target, styleProp);
2871 }
2872 },
2873 evaluate: function (context) {
2874 return runtime.unifiedEval(this, context);
2875 },
2876 };
2877 } else {
2878 return {
2879 type: "styleRef",
2880 name: styleProp,
2881 op: function (context) {
2882 var target = context.you || context.me;
2883 if (target) {
2884 return runtime.resolveStyle(target, styleProp);
2885 }
2886 },
2887 evaluate: function (context) {
2888 return runtime.unifiedEval(this, context);
2889 },
2890 };
2891 }
2892 });
2893
2894 parser.addGrammarElement("objectKey", function (parser, runtime, tokens) {
2895 var token;
2896 if ((token = tokens.matchTokenType("STRING"))) {
2897 return {
2898 type: "objectKey",
2899 key: token.value,
2900 evaluate: function () {
2901 return token.value;
2902 },
2903 };
2904 } else if (tokens.matchOpToken("[")) {
2905 var expr = parser.parseElement("expression", tokens);
2906 tokens.requireOpToken("]");
2907 return {
2908 type: "objectKey",
2909 expr: expr,
2910 args: [expr],
2911 op: function (ctx, expr) {
2912 return expr;
2913 },
2914 evaluate: function (context) {
2915 return runtime.unifiedEval(this, context);
2916 },
2917 };
2918 } else {
2919 var key = "";
2920 do {
2921 token = tokens.matchTokenType("IDENTIFIER") || tokens.matchOpToken("-");
2922 if (token) key += token.value;
2923 } while (token);
2924 return {
2925 type: "objectKey",
2926 key: key,
2927 evaluate: function () {
2928 return key;
2929 },
2930 };
2931 }
2932 });
2933
2934 parser.addLeafExpression("objectLiteral", function (parser, runtime, tokens) {
2935 if (!tokens.matchOpToken("{")) return;
2936 var keyExpressions = [];
2937 var valueExpressions = [];
2938 if (!tokens.matchOpToken("}")) {
2939 do {
2940 var name = parser.requireElement("objectKey", tokens);
2941 tokens.requireOpToken(":");
2942 var value = parser.requireElement("expression", tokens);
2943 valueExpressions.push(value);
2944 keyExpressions.push(name);
2945 } while (tokens.matchOpToken(","));
2946 tokens.requireOpToken("}");
2947 }
2948 return {
2949 type: "objectLiteral",
2950 args: [keyExpressions, valueExpressions],
2951 op: function (context, keys, values) {
2952 var returnVal = {};
2953 for (var i = 0; i < keys.length; i++) {
2954 returnVal[keys[i]] = values[i];
2955 }
2956 return returnVal;
2957 },
2958 evaluate: function (context) {
2959 return runtime.unifiedEval(this, context);
2960 },
2961 };
2962 });
2963
2964 parser.addGrammarElement("nakedNamedArgumentList", function (parser, runtime, tokens) {
2965 var fields = [];
2966 var valueExpressions = [];
2967 if (tokens.currentToken().type === "IDENTIFIER") {
2968 do {
2969 var name = tokens.requireTokenType("IDENTIFIER");
2970 tokens.requireOpToken(":");
2971 var value = parser.requireElement("expression", tokens);
2972 valueExpressions.push(value);
2973 fields.push({ name: name, value: value });
2974 } while (tokens.matchOpToken(","));
2975 }
2976 return {
2977 type: "namedArgumentList",
2978 fields: fields,
2979 args: [valueExpressions],
2980 op: function (context, values) {
2981 var returnVal = { _namedArgList_: true };
2982 for (var i = 0; i < values.length; i++) {
2983 var field = fields[i];
2984 returnVal[field.name.value] = values[i];
2985 }
2986 return returnVal;
2987 },
2988 evaluate: function (context) {
2989 return runtime.unifiedEval(this, context);
2990 },
2991 };
2992 });
2993
2994 parser.addGrammarElement("namedArgumentList", function (parser, runtime, tokens) {
2995 if (!tokens.matchOpToken("(")) return;
2996 var elt = parser.requireElement("nakedNamedArgumentList", tokens);
2997 tokens.requireOpToken(")");
2998 return elt;
2999 });
3000
3001 parser.addGrammarElement("symbol", function (parser, runtime, tokens) {
3002 /** @scope {SymbolScope} */
3003 var scope = "default";
3004 if (tokens.matchToken("global")) {
3005 scope = "global";
3006 } else if (tokens.matchToken("element") || tokens.matchToken("module")) {
3007 scope = "element";
3008 // optional possessive
3009 if (tokens.matchOpToken("'")) {
3010 tokens.requireToken("s");
3011 }
3012 } else if (tokens.matchToken("local")) {
3013 scope = "local";
3014 }
3015
3016 // TODO better look ahead here
3017 let eltPrefix = tokens.matchOpToken(":");
3018 let identifier = tokens.matchTokenType("IDENTIFIER");
3019 if (identifier && identifier.value) {
3020 var name = identifier.value;
3021 if (eltPrefix) {
3022 name = ":" + name;
3023 }
3024 if (scope === "default") {
3025 if (name.indexOf("$") === 0) {
3026 scope = "global";
3027 }
3028 if (name.indexOf(":") === 0) {
3029 scope = "element";
3030 }
3031 }
3032 return {
3033 type: "symbol",
3034 token: identifier,
3035 scope: scope,
3036 name: name,
3037 evaluate: function (context) {
3038 return runtime.resolveSymbol(name, context, scope);
3039 },
3040 };
3041 }
3042 });
3043
3044 parser.addGrammarElement("implicitMeTarget", function (parser, runtime, tokens) {
3045 return {
3046 type: "implicitMeTarget",
3047 evaluate: function (context) {
3048 return context.you || context.me;
3049 },
3050 };
3051 });
3052
3053 parser.addLeafExpression("boolean", function (parser, runtime, tokens) {
3054 var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false");
3055 if (!booleanLiteral) return;
3056 const value = booleanLiteral.value === "true";
3057 return {
3058 type: "boolean",
3059 evaluate: function (context) {
3060 return value;
3061 },
3062 };
3063 });
3064
3065 parser.addLeafExpression("null", function (parser, runtime, tokens) {
3066 if (tokens.matchToken("null")) {
3067 return {
3068 type: "null",
3069 evaluate: function (context) {
3070 return null;
3071 },
3072 };
3073 }
3074 });
3075
3076 parser.addLeafExpression("arrayLiteral", function (parser, runtime, tokens) {
3077 if (!tokens.matchOpToken("[")) return;
3078 var values = [];
3079 if (!tokens.matchOpToken("]")) {
3080 do {
3081 var expr = parser.requireElement("expression", tokens);
3082 values.push(expr);
3083 } while (tokens.matchOpToken(","));
3084 tokens.requireOpToken("]");
3085 }
3086 return {
3087 type: "arrayLiteral",
3088 values: values,
3089 args: [values],
3090 op: function (context, values) {
3091 return values;
3092 },
3093 evaluate: function (context) {
3094 return runtime.unifiedEval(this, context);
3095 },
3096 };
3097 });
3098
3099 parser.addLeafExpression("blockLiteral", function (parser, runtime, tokens) {
3100 if (!tokens.matchOpToken("\\")) return;
3101 var args = [];
3102 var arg1 = tokens.matchTokenType("IDENTIFIER");
3103 if (arg1) {
3104 args.push(arg1);
3105 while (tokens.matchOpToken(",")) {
3106 args.push(tokens.requireTokenType("IDENTIFIER"));
3107 }
3108 }
3109 // TODO compound op token
3110 tokens.requireOpToken("-");
3111 tokens.requireOpToken(">");
3112 var expr = parser.requireElement("expression", tokens);
3113 return {
3114 type: "blockLiteral",
3115 args: args,
3116 expr: expr,
3117 evaluate: function (ctx) {
3118 var returnFunc = function () {
3119 //TODO - push scope
3120 for (var i = 0; i < args.length; i++) {
3121 ctx.locals[args[i].value] = arguments[i];
3122 }
3123 return expr.evaluate(ctx); //OK
3124 };
3125 return returnFunc;
3126 },
3127 };
3128 });
3129
3130 parser.addIndirectExpression("propertyAccess", function (parser, runtime, tokens, root) {
3131 if (!tokens.matchOpToken(".")) return;
3132 var prop = tokens.requireTokenType("IDENTIFIER");
3133 var propertyAccess = {
3134 type: "propertyAccess",
3135 root: root,
3136 prop: prop,
3137 args: [root],
3138 op: function (_context, rootVal) {
3139 var value = runtime.resolveProperty(rootVal, prop.value);
3140 return value;
3141 },
3142 evaluate: function (context) {
3143 return runtime.unifiedEval(this, context);
3144 },
3145 };
3146 return parser.parseElement("indirectExpression", tokens, propertyAccess);
3147 });
3148
3149 parser.addIndirectExpression("of", function (parser, runtime, tokens, root) {
3150 if (!tokens.matchToken("of")) return;
3151 var newRoot = parser.requireElement("unaryExpression", tokens);
3152 // find the urroot
3153 var childOfUrRoot = null;
3154 var urRoot = root;
3155 while (urRoot.root) {
3156 childOfUrRoot = urRoot;
3157 urRoot = urRoot.root;
3158 }
3159 if (urRoot.type !== "symbol" && urRoot.type !== "attributeRef" && urRoot.type !== "styleRef" && urRoot.type !== "computedStyleRef") {
3160 parser.raiseParseError(tokens, "Cannot take a property of a non-symbol: " + urRoot.type);
3161 }
3162 var attribute = urRoot.type === "attributeRef";
3163 var style = urRoot.type === "styleRef" || urRoot.type === "computedStyleRef";
3164 if (attribute || style) {
3165 var attributeElt = urRoot
3166 }
3167 var prop = urRoot.name;
3168
3169 var propertyAccess = {
3170 type: "ofExpression",
3171 prop: urRoot.token,
3172 root: newRoot,
3173 attribute: attributeElt,
3174 expression: root,
3175 args: [newRoot],
3176 op: function (context, rootVal) {
3177 if (attribute) {
3178 return runtime.resolveAttribute(rootVal, prop);
3179 } else if (style) {
3180 if (urRoot.type === "computedStyleRef") {
3181 return runtime.resolveComputedStyle(rootVal, prop);
3182 } else {
3183 return runtime.resolveStyle(rootVal, prop);
3184 }
3185 } else {
3186 return runtime.resolveProperty(rootVal, prop);
3187 }
3188 },
3189 evaluate: function (context) {
3190 return runtime.unifiedEval(this, context);
3191 },
3192 };
3193
3194 if (urRoot.type === "attributeRef") {
3195 propertyAccess.attribute = urRoot;
3196 }
3197 if (childOfUrRoot) {
3198 childOfUrRoot.root = propertyAccess;
3199 childOfUrRoot.args = [propertyAccess];
3200 } else {
3201 root = propertyAccess;
3202 }
3203
3204 return parser.parseElement("indirectExpression", tokens, root);
3205 });
3206
3207 parser.addIndirectExpression("possessive", function (parser, runtime, tokens, root) {
3208 if (parser.possessivesDisabled) {
3209 return;
3210 }
3211 var apostrophe = tokens.matchOpToken("'");
3212 if (
3213 apostrophe ||
3214 (root.type === "symbol" &&
3215 (root.name === "my" || root.name === "its" || root.name === "your") &&
3216 (tokens.currentToken().type === "IDENTIFIER" || tokens.currentToken().type === "ATTRIBUTE_REF" || tokens.currentToken().type === "STYLE_REF"))
3217 ) {
3218 if (apostrophe) {
3219 tokens.requireToken("s");
3220 }
3221
3222 var attribute, style, prop;
3223 attribute = parser.parseElement("attributeRef", tokens);
3224 if (attribute == null) {
3225 style = parser.parseElement("styleRef", tokens);
3226 if (style == null) {
3227 prop = tokens.requireTokenType("IDENTIFIER");
3228 }
3229 }
3230 var propertyAccess = {
3231 type: "possessive",
3232 root: root,
3233 attribute: attribute || style,
3234 prop: prop,
3235 args: [root],
3236 op: function (context, rootVal) {
3237 if (attribute) {
3238 // @ts-ignore
3239 var value = runtime.resolveAttribute(rootVal, attribute.name);
3240 } else if (style) {
3241 var value
3242 if (style.type === 'computedStyleRef') {
3243 value = runtime.resolveComputedStyle(rootVal, style['name']);
3244 } else {
3245 value = runtime.resolveStyle(rootVal, style['name']);
3246 }
3247 } else {
3248 var value = runtime.resolveProperty(rootVal, prop.value);
3249 }
3250 return value;
3251 },
3252 evaluate: function (context) {
3253 return runtime.unifiedEval(this, context);
3254 },
3255 };
3256 return parser.parseElement("indirectExpression", tokens, propertyAccess);
3257 }
3258 });
3259
3260 parser.addIndirectExpression("inExpression", function (parser, runtime, tokens, root) {
3261 if (!tokens.matchToken("in")) return;
3262 var target = parser.requireElement("unaryExpression", tokens);
3263 var propertyAccess = {
3264 type: "inExpression",
3265 root: root,
3266 args: [root, target],
3267 op: function (context, rootVal, target) {
3268 var returnArr = [];
3269 if (rootVal.css) {
3270 runtime.implicitLoop(target, function (targetElt) {
3271 var results = targetElt.querySelectorAll(rootVal.css);
3272 for (var i = 0; i < results.length; i++) {
3273 returnArr.push(results[i]);
3274 }
3275 });
3276 } else if (rootVal instanceof Element) {
3277 var within = false;
3278 runtime.implicitLoop(target, function (targetElt) {
3279 if (targetElt.contains(rootVal)) {
3280 within = true;
3281 }
3282 });
3283 if(within) {
3284 return rootVal;
3285 }
3286 } else {
3287 runtime.implicitLoop(rootVal, function (rootElt) {
3288 runtime.implicitLoop(target, function (targetElt) {
3289 if (rootElt === targetElt) {
3290 returnArr.push(rootElt);
3291 }
3292 });
3293 });
3294 }
3295 return returnArr;
3296 },
3297 evaluate: function (context) {
3298 return runtime.unifiedEval(this, context);
3299 },
3300 };
3301 return parser.parseElement("indirectExpression", tokens, propertyAccess);
3302 });
3303
3304 parser.addIndirectExpression("asExpression", function (parser, runtime, tokens, root) {
3305 if (!tokens.matchToken("as")) return;
3306 tokens.matchToken("a") || tokens.matchToken("an");
3307 var conversion = parser.requireElement("dotOrColonPath", tokens).evaluate(); // OK No promise
3308 var propertyAccess = {
3309 type: "asExpression",
3310 root: root,
3311 args: [root],
3312 op: function (context, rootVal) {
3313 return runtime.convertValue(rootVal, conversion);
3314 },
3315 evaluate: function (context) {
3316 return runtime.unifiedEval(this, context);
3317 },
3318 };
3319 return parser.parseElement("indirectExpression", tokens, propertyAccess);
3320 });
3321
3322 parser.addIndirectExpression("functionCall", function (parser, runtime, tokens, root) {
3323 if (!tokens.matchOpToken("(")) return;
3324 var args = [];
3325 if (!tokens.matchOpToken(")")) {
3326 do {
3327 args.push(parser.requireElement("expression", tokens));
3328 } while (tokens.matchOpToken(","));
3329 tokens.requireOpToken(")");
3330 }
3331
3332 if (root.root) {
3333 var functionCall = {
3334 type: "functionCall",
3335 root: root,
3336 argExressions: args,
3337 args: [root.root, args],
3338 op: function (context, rootRoot, args) {
3339 runtime.nullCheck(rootRoot, root.root);
3340 var func = rootRoot[root.prop.value];
3341 runtime.nullCheck(func, root);
3342 if (func.hyperfunc) {
3343 args.push(context);
3344 }
3345 return func.apply(rootRoot, args);
3346 },
3347 evaluate: function (context) {
3348 return runtime.unifiedEval(this, context);
3349 },
3350 };
3351 } else {
3352 var functionCall = {
3353 type: "functionCall",
3354 root: root,
3355 argExressions: args,
3356 args: [root, args],
3357 op: function (context, func, argVals) {
3358 runtime.nullCheck(func, root);
3359 if (func.hyperfunc) {
3360 argVals.push(context);
3361 }
3362 var apply = func.apply(null, argVals);
3363 return apply;
3364 },
3365 evaluate: function (context) {
3366 return runtime.unifiedEval(this, context);
3367 },
3368 };
3369 }
3370 return parser.parseElement("indirectExpression", tokens, functionCall);
3371 });
3372
3373 parser.addIndirectExpression("attributeRefAccess", function (parser, runtime, tokens, root) {
3374 var attribute = parser.parseElement("attributeRef", tokens);
3375 if (!attribute) return;
3376 var attributeAccess = {
3377 type: "attributeRefAccess",
3378 root: root,
3379 attribute: attribute,
3380 args: [root],
3381 op: function (_ctx, rootVal) {
3382 // @ts-ignore
3383 var value = runtime.resolveAttribute(rootVal, attribute.name);
3384 return value;
3385 },
3386 evaluate: function (context) {
3387 return runtime.unifiedEval(this, context);
3388 },
3389 };
3390 return attributeAccess;
3391 });
3392
3393 parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) {
3394 if (!tokens.matchOpToken("[")) return;
3395 var andBefore = false;
3396 var andAfter = false;
3397 var firstIndex = null;
3398 var secondIndex = null;
3399
3400 if (tokens.matchOpToken("..")) {
3401 andBefore = true;
3402 firstIndex = parser.requireElement("expression", tokens);
3403 } else {
3404 firstIndex = parser.requireElement("expression", tokens);
3405
3406 if (tokens.matchOpToken("..")) {
3407 andAfter = true;
3408 var current = tokens.currentToken();
3409 if (current.type !== "R_BRACKET") {
3410 secondIndex = parser.parseElement("expression", tokens);
3411 }
3412 }
3413 }
3414 tokens.requireOpToken("]");
3415
3416 var arrayIndex = {
3417 type: "arrayIndex",
3418 root: root,
3419 prop: firstIndex,
3420 firstIndex: firstIndex,
3421 secondIndex: secondIndex,
3422 args: [root, firstIndex, secondIndex],
3423 op: function (_ctx, root, firstIndex, secondIndex) {
3424 if (root == null) {
3425 return null;
3426 }
3427 if (andBefore) {
3428 if (firstIndex < 0) {
3429 firstIndex = root.length + firstIndex;
3430 }
3431 return root.slice(0, firstIndex + 1); // returns all items from beginning to firstIndex (inclusive)
3432 } else if (andAfter) {
3433 if (secondIndex != null) {
3434 if (secondIndex < 0) {
3435 secondIndex = root.length + secondIndex;
3436 }
3437 return root.slice(firstIndex, secondIndex + 1); // returns all items from firstIndex to secondIndex (inclusive)
3438 } else {
3439 return root.slice(firstIndex); // returns from firstIndex to end of array
3440 }
3441 } else {
3442 return root[firstIndex];
3443 }
3444 },
3445 evaluate: function (context) {
3446 return runtime.unifiedEval(this, context);
3447 },
3448 };
3449
3450 return parser.parseElement("indirectExpression", tokens, arrayIndex);
3451 });
3452
3453 // taken from https://drafts.csswg.org/css-values-4/#relative-length
3454 // and https://drafts.csswg.org/css-values-4/#absolute-length
3455 // (NB: we do not support `in` dues to conflicts w/ the hyperscript grammar)
3456 var STRING_POSTFIXES = [
3457 'em', 'ex', 'cap', 'ch', 'ic', 'rem', 'lh', 'rlh', 'vw', 'vh', 'vi', 'vb', 'vmin', 'vmax',
3458 'cm', 'mm', 'Q', 'pc', 'pt', 'px'
3459 ];
3460 parser.addGrammarElement("postfixExpression", function (parser, runtime, tokens) {
3461 var root = parser.parseElement("primaryExpression", tokens);
3462
3463 let stringPosfix = tokens.matchAnyToken.apply(tokens, STRING_POSTFIXES) || tokens.matchOpToken("%");
3464 if (stringPosfix) {
3465 return {
3466 type: "stringPostfix",
3467 postfix: stringPosfix.value,
3468 args: [root],
3469 op: function (context, val) {
3470 return "" + val + stringPosfix.value;
3471 },
3472 evaluate: function (context) {
3473 return runtime.unifiedEval(this, context);
3474 },
3475 };
3476 }
3477
3478 var timeFactor = null;
3479 if (tokens.matchToken("s") || tokens.matchToken("seconds")) {
3480 timeFactor = 1000;
3481 } else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) {
3482 timeFactor = 1;
3483 }
3484 if (timeFactor) {
3485 return {
3486 type: "timeExpression",
3487 time: root,
3488 factor: timeFactor,
3489 args: [root],
3490 op: function (_context, val) {
3491 return val * timeFactor;
3492 },
3493 evaluate: function (context) {
3494 return runtime.unifiedEval(this, context);
3495 },
3496 };
3497 }
3498
3499 if (tokens.matchOpToken(":")) {
3500 var typeName = tokens.requireTokenType("IDENTIFIER");
3501 if (!typeName.value) return;
3502 var nullOk = !tokens.matchOpToken("!");
3503 return {
3504 type: "typeCheck",
3505 typeName: typeName,
3506 nullOk: nullOk,
3507 args: [root],
3508 op: function (context, val) {
3509 var passed = runtime.typeCheck(val, this.typeName.value, nullOk);
3510 if (passed) {
3511 return val;
3512 } else {
3513 throw new Error("Typecheck failed! Expected: " + typeName.value);
3514 }
3515 },
3516 evaluate: function (context) {
3517 return runtime.unifiedEval(this, context);
3518 },
3519 };
3520 } else {
3521 return root;
3522 }
3523 });
3524
3525 parser.addGrammarElement("logicalNot", function (parser, runtime, tokens) {
3526 if (!tokens.matchToken("not")) return;
3527 var root = parser.requireElement("unaryExpression", tokens);
3528 return {
3529 type: "logicalNot",
3530 root: root,
3531 args: [root],
3532 op: function (context, val) {
3533 return !val;
3534 },
3535 evaluate: function (context) {
3536 return runtime.unifiedEval(this, context);
3537 },
3538 };
3539 });
3540
3541 parser.addGrammarElement("noExpression", function (parser, runtime, tokens) {
3542 if (!tokens.matchToken("no")) return;
3543 var root = parser.requireElement("unaryExpression", tokens);
3544 return {
3545 type: "noExpression",
3546 root: root,
3547 args: [root],
3548 op: function (_context, val) {
3549 return runtime.isEmpty(val);
3550 },
3551 evaluate: function (context) {
3552 return runtime.unifiedEval(this, context);
3553 },
3554 };
3555 });
3556
3557 parser.addLeafExpression("some", function (parser, runtime, tokens) {
3558 if (!tokens.matchToken("some")) return;
3559 var root = parser.requireElement("expression", tokens);
3560 return {
3561 type: "noExpression",
3562 root: root,
3563 args: [root],
3564 op: function (_context, val) {
3565 return !runtime.isEmpty(val);
3566 },
3567 evaluate(context) {
3568 return runtime.unifiedEval(this, context);
3569 },
3570 };
3571 });
3572
3573 parser.addGrammarElement("negativeNumber", function (parser, runtime, tokens) {
3574 if (!tokens.matchOpToken("-")) return;
3575 var root = parser.requireElement("unaryExpression", tokens);
3576 return {
3577 type: "negativeNumber",
3578 root: root,
3579 args: [root],
3580 op: function (context, value) {
3581 return -1 * value;
3582 },
3583 evaluate: function (context) {
3584 return runtime.unifiedEval(this, context);
3585 },
3586 };
3587 });
3588
3589 parser.addGrammarElement("unaryExpression", function (parser, runtime, tokens) {
3590 tokens.matchToken("the"); // optional "the"
3591 return parser.parseAnyOf(
3592 ["beepExpression", "logicalNot", "relativePositionalExpression", "positionalExpression", "noExpression", "negativeNumber", "postfixExpression"],
3593 tokens
3594 );
3595 });
3596
3597 parser.addGrammarElement("beepExpression", function (parser, runtime, tokens) {
3598 if (!tokens.matchToken("beep!")) return;
3599 var expression = parser.parseElement("unaryExpression", tokens);
3600 if (expression) {
3601 expression['booped'] = true;
3602 var originalEvaluate = expression.evaluate;
3603 expression.evaluate = function(ctx){
3604 let value = originalEvaluate.apply(expression, arguments);
3605 let element = ctx.me;
3606 runtime.beepValueToConsole(element, expression, value);
3607 return value;
3608 }
3609 return expression;
3610 }
3611 });
3612
3613 var scanForwardQuery = function(start, root, match, wrap) {
3614 var results = root.querySelectorAll(match);
3615 for (var i = 0; i < results.length; i++) {
3616 var elt = results[i];
3617 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
3618 return elt;
3619 }
3620 }
3621 if (wrap) {
3622 return results[0];
3623 }
3624 }
3625
3626 var scanBackwardsQuery = function(start, root, match, wrap) {
3627 var results = root.querySelectorAll(match);
3628 for (var i = results.length - 1; i >= 0; i--) {
3629 var elt = results[i];
3630 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
3631 return elt;
3632 }
3633 }
3634 if (wrap) {
3635 return results[results.length - 1];
3636 }
3637 }
3638
3639 var scanForwardArray = function(start, array, match, wrap) {
3640 var matches = [];
3641 Runtime.prototype.forEach(array, function(elt){
3642 if (elt.matches(match) || elt === start) {
3643 matches.push(elt);
3644 }
3645 })
3646 for (var i = 0; i < matches.length - 1; i++) {
3647 var elt = matches[i];
3648 if (elt === start) {
3649 return matches[i + 1];
3650 }
3651 }
3652 if (wrap) {
3653 var first = matches[0];
3654 if (first && first.matches(match)) {
3655 return first;
3656 }
3657 }
3658 }
3659
3660 var scanBackwardsArray = function(start, array, match, wrap) {
3661 return scanForwardArray(start, Array.from(array).reverse(), match, wrap);
3662 }
3663
3664 parser.addGrammarElement("relativePositionalExpression", function (parser, runtime, tokens) {
3665 var op = tokens.matchAnyToken("next", "previous");
3666 if (!op) return;
3667 var forwardSearch = op.value === "next";
3668
3669 var thingElt = parser.parseElement("expression", tokens);
3670
3671 if (tokens.matchToken("from")) {
3672 tokens.pushFollow("in");
3673 try {
3674 var from = parser.requireElement("unaryExpression", tokens);
3675 } finally {
3676 tokens.popFollow();
3677 }
3678 } else {
3679 var from = parser.requireElement("implicitMeTarget", tokens);
3680 }
3681
3682 var inSearch = false;
3683 var withinElt;
3684 if (tokens.matchToken("in")) {
3685 inSearch = true;
3686 var inElt = parser.requireElement("unaryExpression", tokens);
3687 } else if (tokens.matchToken("within")) {
3688 withinElt = parser.requireElement("unaryExpression", tokens);
3689 } else {
3690 withinElt = document.body;
3691 }
3692
3693 var wrapping = false;
3694 if (tokens.matchToken("with")) {
3695 tokens.requireToken("wrapping")
3696 wrapping = true;
3697 }
3698
3699 return {
3700 type: "relativePositionalExpression",
3701 from: from,
3702 forwardSearch: forwardSearch,
3703 inSearch: inSearch,
3704 wrapping: wrapping,
3705 inElt: inElt,
3706 withinElt: withinElt,
3707 operator: op.value,
3708 args: [thingElt, from, inElt, withinElt],
3709 op: function (context, thing, from, inElt, withinElt) {
3710
3711 var css = thing.css;
3712 if (css == null) {
3713 throw "Expected a CSS value to be returned by " + Tokens.sourceFor.apply(thingElt);
3714 }
3715
3716 if(inSearch) {
3717 if (inElt) {
3718 if (forwardSearch) {
3719 return scanForwardArray(from, inElt, css, wrapping);
3720 } else {
3721 return scanBackwardsArray(from, inElt, css, wrapping);
3722 }
3723 }
3724 } else {
3725 if (withinElt) {
3726 if (forwardSearch) {
3727 return scanForwardQuery(from, withinElt, css, wrapping);
3728 } else {
3729 return scanBackwardsQuery(from, withinElt, css, wrapping);
3730 }
3731 }
3732 }
3733 },
3734 evaluate: function (context) {
3735 return runtime.unifiedEval(this, context);
3736 },
3737 }
3738
3739 });
3740
3741 parser.addGrammarElement("positionalExpression", function (parser, runtime, tokens) {
3742 var op = tokens.matchAnyToken("first", "last", "random");
3743 if (!op) return;
3744 tokens.matchAnyToken("in", "from", "of");
3745 var rhs = parser.requireElement("unaryExpression", tokens);
3746 const operator = op.value;
3747 return {
3748 type: "positionalExpression",
3749 rhs: rhs,
3750 operator: op.value,
3751 args: [rhs],
3752 op: function (context, rhsVal) {
3753 if (rhsVal && !Array.isArray(rhsVal)) {
3754 if (rhsVal.children) {
3755 rhsVal = rhsVal.children;
3756 } else {
3757 rhsVal = Array.from(rhsVal);
3758 }
3759 }
3760 if (rhsVal) {
3761 if (operator === "first") {
3762 return rhsVal[0];
3763 } else if (operator === "last") {
3764 return rhsVal[rhsVal.length - 1];
3765 } else if (operator === "random") {
3766 return rhsVal[Math.floor(Math.random() * rhsVal.length)];
3767 }
3768 }
3769 },
3770 evaluate: function (context) {
3771 return runtime.unifiedEval(this, context);
3772 },
3773 };
3774 });
3775
3776 parser.addGrammarElement("mathOperator", function (parser, runtime, tokens) {
3777 var expr = parser.parseElement("unaryExpression", tokens);
3778 var mathOp,
3779 initialMathOp = null;
3780 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/") || tokens.matchToken('mod');
3781 while (mathOp) {
3782 initialMathOp = initialMathOp || mathOp;
3783 var operator = mathOp.value;
3784 if (initialMathOp.value !== operator) {
3785 parser.raiseParseError(tokens, "You must parenthesize math operations with different operators");
3786 }
3787 var rhs = parser.parseElement("unaryExpression", tokens);
3788 expr = {
3789 type: "mathOperator",
3790 lhs: expr,
3791 rhs: rhs,
3792 operator: operator,
3793 args: [expr, rhs],
3794 op: function (context, lhsVal, rhsVal) {
3795 if (operator === "+") {
3796 return lhsVal + rhsVal;
3797 } else if (operator === "-") {
3798 return lhsVal - rhsVal;
3799 } else if (operator === "*") {
3800 return lhsVal * rhsVal;
3801 } else if (operator === "/") {
3802 return lhsVal / rhsVal;
3803 } else if (operator === "mod") {
3804 return lhsVal % rhsVal;
3805 }
3806 },
3807 evaluate: function (context) {
3808 return runtime.unifiedEval(this, context);
3809 },
3810 };
3811 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/") || tokens.matchToken('mod');
3812 }
3813 return expr;
3814 });
3815
3816 parser.addGrammarElement("mathExpression", function (parser, runtime, tokens) {
3817 return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens);
3818 });
3819
3820 function sloppyContains(src, container, value){
3821 if (container['contains']) {
3822 return container.contains(value);
3823 } else if (container['includes']) {
3824 return container.includes(value);
3825 } else {
3826 throw Error("The value of " + src.sourceFor() + " does not have a contains or includes method on it");
3827 }
3828 }
3829 function sloppyMatches(src, target, toMatch){
3830 if (target['match']) {
3831 return !!target.match(toMatch);
3832 } else if (target['matches']) {
3833 return target.matches(toMatch);
3834 } else {
3835 throw Error("The value of " + src.sourceFor() + " does not have a match or matches method on it");
3836 }
3837 }
3838
3839 parser.addGrammarElement("comparisonOperator", function (parser, runtime, tokens) {
3840 var expr = parser.parseElement("mathExpression", tokens);
3841 var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==");
3842 var operator = comparisonToken ? comparisonToken.value : null;
3843 var hasRightValue = true; // By default, most comparisons require two values, but there are some exceptions.
3844 var typeCheck = false;
3845
3846 if (operator == null) {
3847 if (tokens.matchToken("is") || tokens.matchToken("am")) {
3848 if (tokens.matchToken("not")) {
3849 if (tokens.matchToken("in")) {
3850 operator = "not in";
3851 } else if (tokens.matchToken("a")) {
3852 operator = "not a";
3853 typeCheck = true;
3854 } else if (tokens.matchToken("empty")) {
3855 operator = "not empty";
3856 hasRightValue = false;
3857 } else {
3858 if (tokens.matchToken("really")) {
3859 operator = "!==";
3860 } else {
3861 operator = "!=";
3862 }
3863 // consume additional optional syntax
3864 if (tokens.matchToken("equal")) {
3865 tokens.matchToken("to");
3866 }
3867 }
3868 } else if (tokens.matchToken("in")) {
3869 operator = "in";
3870 } else if (tokens.matchToken("a")) {
3871 operator = "a";
3872 typeCheck = true;
3873 } else if (tokens.matchToken("empty")) {
3874 operator = "empty";
3875 hasRightValue = false;
3876 } else if (tokens.matchToken("less")) {
3877 tokens.requireToken("than");
3878 if (tokens.matchToken("or")) {
3879 tokens.requireToken("equal");
3880 tokens.requireToken("to");
3881 operator = "<=";
3882 } else {
3883 operator = "<";
3884 }
3885 } else if (tokens.matchToken("greater")) {
3886 tokens.requireToken("than");
3887 if (tokens.matchToken("or")) {
3888 tokens.requireToken("equal");
3889 tokens.requireToken("to");
3890 operator = ">=";
3891 } else {
3892 operator = ">";
3893 }
3894 } else {
3895 if (tokens.matchToken("really")) {
3896 operator = "===";
3897 } else {
3898 operator = "==";
3899 }
3900 if (tokens.matchToken("equal")) {
3901 tokens.matchToken("to");
3902 }
3903 }
3904 } else if (tokens.matchToken("equals")) {
3905 operator = "==";
3906 } else if (tokens.matchToken("really")) {
3907 tokens.requireToken("equals")
3908 operator = "===";
3909 } else if (tokens.matchToken("exist") || tokens.matchToken("exists")) {
3910 operator = "exist";
3911 hasRightValue = false;
3912 } else if (tokens.matchToken("matches") || tokens.matchToken("match")) {
3913 operator = "match";
3914 } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
3915 operator = "contain";
3916 } else if (tokens.matchToken("includes") || tokens.matchToken("include")) {
3917 operator = "include";
3918 } else if (tokens.matchToken("do") || tokens.matchToken("does")) {
3919 tokens.requireToken("not");
3920 if (tokens.matchToken("matches") || tokens.matchToken("match")) {
3921 operator = "not match";
3922 } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
3923 operator = "not contain";
3924 } else if (tokens.matchToken("exist") || tokens.matchToken("exist")) {
3925 operator = "not exist";
3926 hasRightValue = false;
3927 } else if (tokens.matchToken("include")) {
3928 operator = "not include";
3929 } else {
3930 parser.raiseParseError(tokens, "Expected matches or contains");
3931 }
3932 }
3933 }
3934
3935 if (operator) {
3936 // Do not allow chained comparisons, which is dumb
3937 var typeName, nullOk, rhs
3938 if (typeCheck) {
3939 typeName = tokens.requireTokenType("IDENTIFIER");
3940 nullOk = !tokens.matchOpToken("!");
3941 } else if (hasRightValue) {
3942 rhs = parser.requireElement("mathExpression", tokens);
3943 if (operator === "match" || operator === "not match") {
3944 rhs = rhs.css ? rhs.css : rhs;
3945 }
3946 }
3947 var lhs = expr;
3948 expr = {
3949 type: "comparisonOperator",
3950 operator: operator,
3951 typeName: typeName,
3952 nullOk: nullOk,
3953 lhs: expr,
3954 rhs: rhs,
3955 args: [expr, rhs],
3956 op: function (context, lhsVal, rhsVal) {
3957 if (operator === "==") {
3958 return lhsVal == rhsVal;
3959 } else if (operator === "!=") {
3960 return lhsVal != rhsVal;
3961 }
3962 if (operator === "===") {
3963 return lhsVal === rhsVal;
3964 } else if (operator === "!==") {
3965 return lhsVal !== rhsVal;
3966 }
3967 if (operator === "match") {
3968 return lhsVal != null && sloppyMatches(lhs, lhsVal, rhsVal);
3969 }
3970 if (operator === "not match") {
3971 return lhsVal == null || !sloppyMatches(lhs, lhsVal, rhsVal);
3972 }
3973 if (operator === "in") {
3974 return rhsVal != null && sloppyContains(rhs, rhsVal, lhsVal);
3975 }
3976 if (operator === "not in") {
3977 return rhsVal == null || !sloppyContains(rhs, rhsVal, lhsVal);
3978 }
3979 if (operator === "contain") {
3980 return lhsVal != null && sloppyContains(lhs, lhsVal, rhsVal);
3981 }
3982 if (operator === "not contain") {
3983 return lhsVal == null || !sloppyContains(lhs, lhsVal, rhsVal);
3984 }
3985 if (operator === "include") {
3986 return lhsVal != null && sloppyContains(lhs, lhsVal, rhsVal);
3987 }
3988 if (operator === "not include") {
3989 return lhsVal == null || !sloppyContains(lhs, lhsVal, rhsVal);
3990 }
3991 if (operator === "===") {
3992 return lhsVal === rhsVal;
3993 } else if (operator === "!==") {
3994 return lhsVal !== rhsVal;
3995 } else if (operator === "<") {
3996 return lhsVal < rhsVal;
3997 } else if (operator === ">") {
3998 return lhsVal > rhsVal;
3999 } else if (operator === "<=") {
4000 return lhsVal <= rhsVal;
4001 } else if (operator === ">=") {
4002 return lhsVal >= rhsVal;
4003 } else if (operator === "empty") {
4004 return runtime.isEmpty(lhsVal);
4005 } else if (operator === "not empty") {
4006 return !runtime.isEmpty(lhsVal);
4007 } else if (operator === "exist") {
4008 return runtime.doesExist(lhsVal);
4009 } else if (operator === "not exist") {
4010 return !runtime.doesExist(lhsVal);
4011 } else if (operator === "a") {
4012 return runtime.typeCheck(lhsVal, typeName.value, nullOk);
4013 } else if (operator === "not a") {
4014 return !runtime.typeCheck(lhsVal, typeName.value, nullOk);
4015 } else {
4016 throw "Unknown comparison : " + operator;
4017 }
4018 },
4019 evaluate: function (context) {
4020 return runtime.unifiedEval(this, context);
4021 },
4022 };
4023 }
4024 return expr;
4025 });
4026
4027 parser.addGrammarElement("comparisonExpression", function (parser, runtime, tokens) {
4028 return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens);
4029 });
4030
4031 parser.addGrammarElement("logicalOperator", function (parser, runtime, tokens) {
4032 var expr = parser.parseElement("comparisonExpression", tokens);
4033 var logicalOp,
4034 initialLogicalOp = null;
4035 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
4036 while (logicalOp) {
4037 initialLogicalOp = initialLogicalOp || logicalOp;
4038 if (initialLogicalOp.value !== logicalOp.value) {
4039 parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators");
4040 }
4041 var rhs = parser.requireElement("comparisonExpression", tokens);
4042 const operator = logicalOp.value;
4043 expr = {
4044 type: "logicalOperator",
4045 operator: operator,
4046 lhs: expr,
4047 rhs: rhs,
4048 args: [expr, rhs],
4049 op: function (context, lhsVal, rhsVal) {
4050 if (operator === "and") {
4051 return lhsVal && rhsVal;
4052 } else {
4053 return lhsVal || rhsVal;
4054 }
4055 },
4056 evaluate: function (context) {
4057 return runtime.unifiedEval(this, context);
4058 },
4059 };
4060 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
4061 }
4062 return expr;
4063 });
4064
4065 parser.addGrammarElement("logicalExpression", function (parser, runtime, tokens) {
4066 return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens);
4067 });
4068
4069 parser.addGrammarElement("asyncExpression", function (parser, runtime, tokens) {
4070 if (tokens.matchToken("async")) {
4071 var value = parser.requireElement("logicalExpression", tokens);
4072 var expr = {
4073 type: "asyncExpression",
4074 value: value,
4075 evaluate: function (context) {
4076 return {
4077 asyncWrapper: true,
4078 value: this.value.evaluate(context), //OK
4079 };
4080 },
4081 };
4082 return expr;
4083 } else {
4084 return parser.parseElement("logicalExpression", tokens);
4085 }
4086 });
4087
4088 parser.addGrammarElement("expression", function (parser, runtime, tokens) {
4089 tokens.matchToken("the"); // optional the
4090 return parser.parseElement("asyncExpression", tokens);
4091 });
4092
4093 parser.addGrammarElement("assignableExpression", function (parser, runtime, tokens) {
4094 tokens.matchToken("the"); // optional the
4095
4096 // TODO obviously we need to generalize this as a left hand side / targetable concept
4097 var expr = parser.parseElement("primaryExpression", tokens);
4098 if (expr && (
4099 expr.type === "symbol" ||
4100 expr.type === "ofExpression" ||
4101 expr.type === "propertyAccess" ||
4102 expr.type === "attributeRefAccess" ||
4103 expr.type === "attributeRef" ||
4104 expr.type === "styleRef" ||
4105 expr.type === "arrayIndex" ||
4106 expr.type === "possessive")
4107 ) {
4108 return expr;
4109 } else {
4110 parser.raiseParseError(
4111 tokens,
4112 "A target expression must be writable. The expression type '" + (expr && expr.type) + "' is not."
4113 );
4114 }
4115 return expr;
4116 });
4117
4118 parser.addGrammarElement("hyperscript", function (parser, runtime, tokens) {
4119 var features = [];
4120
4121 if (tokens.hasMore()) {
4122 while (parser.featureStart(tokens.currentToken()) || tokens.currentToken().value === "(") {
4123 var feature = parser.requireElement("feature", tokens);
4124 features.push(feature);
4125 tokens.matchToken("end"); // optional end
4126 }
4127 }
4128 return {
4129 type: "hyperscript",
4130 features: features,
4131 apply: function (target, source, args) {
4132 // no op
4133 for (const feature of features) {
4134 feature.install(target, source, args);
4135 }
4136 },
4137 };
4138 });
4139
4140 var parseEventArgs = function (tokens) {
4141 var args = [];
4142 // handle argument list (look ahead 3)
4143 if (
4144 tokens.token(0).value === "(" &&
4145 (tokens.token(1).value === ")" || tokens.token(2).value === "," || tokens.token(2).value === ")")
4146 ) {
4147 tokens.matchOpToken("(");
4148 do {
4149 args.push(tokens.requireTokenType("IDENTIFIER"));
4150 } while (tokens.matchOpToken(","));
4151 tokens.requireOpToken(")");
4152 }
4153 return args;
4154 };
4155
4156 parser.addFeature("on", function (parser, runtime, tokens) {
4157 if (!tokens.matchToken("on")) return;
4158 var every = false;
4159 if (tokens.matchToken("every")) {
4160 every = true;
4161 }
4162 var events = [];
4163 var displayName = null;
4164 do {
4165 var on = parser.requireElement("eventName", tokens, "Expected event name");
4166
4167 var eventName = on.evaluate(); // OK No Promise
4168
4169 if (displayName) {
4170 displayName = displayName + " or " + eventName;
4171 } else {
4172 displayName = "on " + eventName;
4173 }
4174 var args = parseEventArgs(tokens);
4175
4176 var filter = null;
4177 if (tokens.matchOpToken("[")) {
4178 filter = parser.requireElement("expression", tokens);
4179 tokens.requireOpToken("]");
4180 }
4181
4182 var startCount, endCount ,unbounded;
4183 if (tokens.currentToken().type === "NUMBER") {
4184 var startCountToken = tokens.consumeToken();
4185 if (!startCountToken.value) return;
4186 startCount = parseInt(startCountToken.value);
4187 if (tokens.matchToken("to")) {
4188 var endCountToken = tokens.consumeToken();
4189 if (!endCountToken.value) return;
4190 endCount = parseInt(endCountToken.value);
4191 } else if (tokens.matchToken("and")) {
4192 unbounded = true;
4193 tokens.requireToken("on");
4194 }
4195 }
4196
4197 var intersectionSpec, mutationSpec;
4198 if (eventName === "intersection") {
4199 intersectionSpec = {};
4200 if (tokens.matchToken("with")) {
4201 intersectionSpec["with"] = parser.requireElement("expression", tokens).evaluate();
4202 }
4203 if (tokens.matchToken("having")) {
4204 do {
4205 if (tokens.matchToken("margin")) {
4206 intersectionSpec["rootMargin"] = parser.requireElement("stringLike", tokens).evaluate();
4207 } else if (tokens.matchToken("threshold")) {
4208 intersectionSpec["threshold"] = parser.requireElement("expression", tokens).evaluate();
4209 } else {
4210 parser.raiseParseError(tokens, "Unknown intersection config specification");
4211 }
4212 } while (tokens.matchToken("and"));
4213 }
4214 } else if (eventName === "mutation") {
4215 mutationSpec = {};
4216 if (tokens.matchToken("of")) {
4217 do {
4218 if (tokens.matchToken("anything")) {
4219 mutationSpec["attributes"] = true;
4220 mutationSpec["subtree"] = true;
4221 mutationSpec["characterData"] = true;
4222 mutationSpec["childList"] = true;
4223 } else if (tokens.matchToken("childList")) {
4224 mutationSpec["childList"] = true;
4225 } else if (tokens.matchToken("attributes")) {
4226 mutationSpec["attributes"] = true;
4227 mutationSpec["attributeOldValue"] = true;
4228 } else if (tokens.matchToken("subtree")) {
4229 mutationSpec["subtree"] = true;
4230 } else if (tokens.matchToken("characterData")) {
4231 mutationSpec["characterData"] = true;
4232 mutationSpec["characterDataOldValue"] = true;
4233 } else if (tokens.currentToken().type === "ATTRIBUTE_REF") {
4234 var attribute = tokens.consumeToken();
4235 if (mutationSpec["attributeFilter"] == null) {
4236 mutationSpec["attributeFilter"] = [];
4237 }
4238 if (attribute.value.indexOf("@") == 0) {
4239 mutationSpec["attributeFilter"].push(attribute.value.substring(1));
4240 } else {
4241 parser.raiseParseError(
4242 tokens,
4243 "Only shorthand attribute references are allowed here"
4244 );
4245 }
4246 } else {
4247 parser.raiseParseError(tokens, "Unknown mutation config specification");
4248 }
4249 } while (tokens.matchToken("or"));
4250 } else {
4251 mutationSpec["attributes"] = true;
4252 mutationSpec["characterData"] = true;
4253 mutationSpec["childList"] = true;
4254 }
4255 }
4256
4257 var from = null;
4258 var elsewhere = false;
4259 if (tokens.matchToken("from")) {
4260 if (tokens.matchToken("elsewhere")) {
4261 elsewhere = true;
4262 } else {
4263 tokens.pushFollow("or");
4264 try {
4265 from = parser.requireElement("expression", tokens)
4266 } finally {
4267 tokens.popFollow();
4268 }
4269 if (!from) {
4270 parser.raiseParseError(tokens, 'Expected either target value or "elsewhere".');
4271 }
4272 }
4273 }
4274 // support both "elsewhere" and "from elsewhere"
4275 if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) {
4276 elsewhere = true;
4277 }
4278
4279 if (tokens.matchToken("in")) {
4280 var inExpr = parser.parseElement('unaryExpression', tokens);
4281 }
4282
4283 if (tokens.matchToken("debounced")) {
4284 tokens.requireToken("at");
4285 var timeExpr = parser.requireElement("unaryExpression", tokens);
4286 // @ts-ignore
4287 var debounceTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
4288 } else if (tokens.matchToken("throttled")) {
4289 tokens.requireToken("at");
4290 var timeExpr = parser.requireElement("unaryExpression", tokens);
4291 // @ts-ignore
4292 var throttleTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
4293 }
4294
4295 events.push({
4296 execCount: 0,
4297 every: every,
4298 on: eventName,
4299 args: args,
4300 filter: filter,
4301 from: from,
4302 inExpr: inExpr,
4303 elsewhere: elsewhere,
4304 startCount: startCount,
4305 endCount: endCount,
4306 unbounded: unbounded,
4307 debounceTime: debounceTime,
4308 throttleTime: throttleTime,
4309 mutationSpec: mutationSpec,
4310 intersectionSpec: intersectionSpec,
4311 debounced: undefined,
4312 lastExec: undefined,
4313 });
4314 } while (tokens.matchToken("or"));
4315
4316 var queueLast = true;
4317 if (!every) {
4318 if (tokens.matchToken("queue")) {
4319 if (tokens.matchToken("all")) {
4320 var queueAll = true;
4321 var queueLast = false;
4322 } else if (tokens.matchToken("first")) {
4323 var queueFirst = true;
4324 } else if (tokens.matchToken("none")) {
4325 var queueNone = true;
4326 } else {
4327 tokens.requireToken("last");
4328 }
4329 }
4330 }
4331
4332 var start = parser.requireElement("commandList", tokens);
4333 parser.ensureTerminated(start);
4334
4335 var errorSymbol, errorHandler;
4336 if (tokens.matchToken("catch")) {
4337 errorSymbol = tokens.requireTokenType("IDENTIFIER").value;
4338 errorHandler = parser.requireElement("commandList", tokens);
4339 parser.ensureTerminated(errorHandler);
4340 }
4341
4342 if (tokens.matchToken("finally")) {
4343 var finallyHandler = parser.requireElement("commandList", tokens);
4344 parser.ensureTerminated(finallyHandler);
4345 }
4346
4347 var onFeature = {
4348 displayName: displayName,
4349 events: events,
4350 start: start,
4351 every: every,
4352 execCount: 0,
4353 errorHandler: errorHandler,
4354 errorSymbol: errorSymbol,
4355 execute: function (/** @type {Context} */ ctx) {
4356 let eventQueueInfo = runtime.getEventQueueFor(ctx.me, onFeature);
4357 if (eventQueueInfo.executing && every === false) {
4358 if (queueNone || (queueFirst && eventQueueInfo.queue.length > 0)) {
4359 return;
4360 }
4361 if (queueLast) {
4362 eventQueueInfo.queue.length = 0;
4363 }
4364 eventQueueInfo.queue.push(ctx);
4365 return;
4366 }
4367 onFeature.execCount++;
4368 eventQueueInfo.executing = true;
4369 ctx.meta.onHalt = function () {
4370 eventQueueInfo.executing = false;
4371 var queued = eventQueueInfo.queue.shift();
4372 if (queued) {
4373 setTimeout(function () {
4374 onFeature.execute(queued);
4375 }, 1);
4376 }
4377 };
4378 ctx.meta.reject = function (err) {
4379 console.error(err.message ? err.message : err);
4380 var hypertrace = runtime.getHyperTrace(ctx, err);
4381 if (hypertrace) {
4382 hypertrace.print();
4383 }
4384 runtime.triggerEvent(ctx.me, "exception", {
4385 error: err,
4386 });
4387 };
4388 start.execute(ctx);
4389 },
4390 install: function (elt, source) {
4391 for (const eventSpec of onFeature.events) {
4392 var targets;
4393 if (eventSpec.elsewhere) {
4394 targets = [document];
4395 } else if (eventSpec.from) {
4396 targets = eventSpec.from.evaluate(runtime.makeContext(elt, onFeature, elt, null));
4397 } else {
4398 targets = [elt];
4399 }
4400 runtime.implicitLoop(targets, function (target) {
4401 // OK NO PROMISE
4402
4403 var eventName = eventSpec.on;
4404 if (target == null) {
4405 console.warn("'%s' feature ignored because target does not exists:", displayName, elt);
4406 return;
4407 }
4408
4409 if (eventSpec.mutationSpec) {
4410 eventName = "hyperscript:mutation";
4411 const observer = new MutationObserver(function (mutationList, observer) {
4412 if (!onFeature.executing) {
4413 runtime.triggerEvent(target, eventName, {
4414 mutationList: mutationList,
4415 observer: observer,
4416 });
4417 }
4418 });
4419 observer.observe(target, eventSpec.mutationSpec);
4420 }
4421
4422 if (eventSpec.intersectionSpec) {
4423 eventName = "hyperscript:intersection";
4424 const observer = new IntersectionObserver(function (entries) {
4425 for (const entry of entries) {
4426 var detail = {
4427 observer: observer,
4428 };
4429 detail = Object.assign(detail, entry);
4430 detail["intersecting"] = entry.isIntersecting;
4431 runtime.triggerEvent(target, eventName, detail);
4432 }
4433 }, eventSpec.intersectionSpec);
4434 observer.observe(target);
4435 }
4436
4437 var addEventListener = target.addEventListener || target.on;
4438 addEventListener.call(target, eventName, function listener(evt) {
4439 // OK NO PROMISE
4440 if (typeof Node !== 'undefined' && elt instanceof Node && target !== elt && !elt.isConnected) {
4441 target.removeEventListener(eventName, listener);
4442 return;
4443 }
4444
4445 var ctx = runtime.makeContext(elt, onFeature, elt, evt);
4446 if (eventSpec.elsewhere && elt.contains(evt.target)) {
4447 return;
4448 }
4449 if (eventSpec.from) {
4450 ctx.result = target;
4451 }
4452
4453 // establish context
4454 for (const arg of eventSpec.args) {
4455 let eventValue = ctx.event[arg.value];
4456 if (eventValue !== undefined) {
4457 ctx.locals[arg.value] = eventValue;
4458 } else if ('detail' in ctx.event) {
4459 ctx.locals[arg.value] = ctx.event['detail'][arg.value];
4460 }
4461 }
4462
4463 // install error handler if any
4464 ctx.meta.errorHandler = errorHandler;
4465 ctx.meta.errorSymbol = errorSymbol;
4466 ctx.meta.finallyHandler = finallyHandler;
4467
4468 // apply filter
4469 if (eventSpec.filter) {
4470 var initialCtx = ctx.meta.context;
4471 ctx.meta.context = ctx.event;
4472 try {
4473 var value = eventSpec.filter.evaluate(ctx); //OK NO PROMISE
4474 if (value) {
4475 // match the javascript semantics for if statements
4476 } else {
4477 return;
4478 }
4479 } finally {
4480 ctx.meta.context = initialCtx;
4481 }
4482 }
4483
4484 if (eventSpec.inExpr) {
4485 var inElement = evt.target;
4486 while (true) {
4487 if (inElement.matches && inElement.matches(eventSpec.inExpr.css)) {
4488 ctx.result = inElement;
4489 break;
4490 } else {
4491 inElement = inElement.parentElement;
4492 if (inElement == null) {
4493 return; // no match found
4494 }
4495 }
4496 }
4497 }
4498
4499 // verify counts
4500 eventSpec.execCount++;
4501 if (eventSpec.startCount) {
4502 if (eventSpec.endCount) {
4503 if (
4504 eventSpec.execCount < eventSpec.startCount ||
4505 eventSpec.execCount > eventSpec.endCount
4506 ) {
4507 return;
4508 }
4509 } else if (eventSpec.unbounded) {
4510 if (eventSpec.execCount < eventSpec.startCount) {
4511 return;
4512 }
4513 } else if (eventSpec.execCount !== eventSpec.startCount) {
4514 return;
4515 }
4516 }
4517
4518 //debounce
4519 if (eventSpec.debounceTime) {
4520 if (eventSpec.debounced) {
4521 clearTimeout(eventSpec.debounced);
4522 }
4523 eventSpec.debounced = setTimeout(function () {
4524 onFeature.execute(ctx);
4525 }, eventSpec.debounceTime);
4526 return;
4527 }
4528
4529 // throttle
4530 if (eventSpec.throttleTime) {
4531 if (
4532 eventSpec.lastExec &&
4533 Date.now() < (eventSpec.lastExec + eventSpec.throttleTime)
4534 ) {
4535 return;
4536 } else {
4537 eventSpec.lastExec = Date.now();
4538 }
4539 }
4540
4541 // apply execute
4542 onFeature.execute(ctx);
4543 });
4544 });
4545 }
4546 },
4547 };
4548 parser.setParent(start, onFeature);
4549 return onFeature;
4550 });
4551
4552 parser.addFeature("def", function (parser, runtime, tokens) {
4553 if (!tokens.matchToken("def")) return;
4554 var functionName = parser.requireElement("dotOrColonPath", tokens);
4555 var nameVal = functionName.evaluate(); // OK
4556 var nameSpace = nameVal.split(".");
4557 var funcName = nameSpace.pop();
4558
4559 var args = [];
4560 if (tokens.matchOpToken("(")) {
4561 if (tokens.matchOpToken(")")) {
4562 // empty args list
4563 } else {
4564 do {
4565 args.push(tokens.requireTokenType("IDENTIFIER"));
4566 } while (tokens.matchOpToken(","));
4567 tokens.requireOpToken(")");
4568 }
4569 }
4570
4571 var start = parser.requireElement("commandList", tokens);
4572
4573 var errorSymbol, errorHandler;
4574 if (tokens.matchToken("catch")) {
4575 errorSymbol = tokens.requireTokenType("IDENTIFIER").value;
4576 errorHandler = parser.parseElement("commandList", tokens);
4577 }
4578
4579 if (tokens.matchToken("finally")) {
4580 var finallyHandler = parser.requireElement("commandList", tokens);
4581 parser.ensureTerminated(finallyHandler);
4582 }
4583
4584 var functionFeature = {
4585 displayName:
4586 funcName +
4587 "(" +
4588 args
4589 .map(function (arg) {
4590 return arg.value;
4591 })
4592 .join(", ") +
4593 ")",
4594 name: funcName,
4595 args: args,
4596 start: start,
4597 errorHandler: errorHandler,
4598 errorSymbol: errorSymbol,
4599 finallyHandler: finallyHandler,
4600 install: function (target, source) {
4601 var func = function () {
4602 // null, worker
4603 var ctx = runtime.makeContext(source, functionFeature, target, null);
4604
4605 // install error handler if any
4606 ctx.meta.errorHandler = errorHandler;
4607 ctx.meta.errorSymbol = errorSymbol;
4608 ctx.meta.finallyHandler = finallyHandler;
4609
4610 for (var i = 0; i < args.length; i++) {
4611 var name = args[i];
4612 var argumentVal = arguments[i];
4613 if (name) {
4614 ctx.locals[name.value] = argumentVal;
4615 }
4616 }
4617 ctx.meta.caller = arguments[args.length];
4618 if (ctx.meta.caller) {
4619 ctx.meta.callingCommand = ctx.meta.caller.meta.command;
4620 }
4621 var resolve,
4622 reject = null;
4623 var promise = new Promise(function (theResolve, theReject) {
4624 resolve = theResolve;
4625 reject = theReject;
4626 });
4627 start.execute(ctx);
4628 if (ctx.meta.returned) {
4629 return ctx.meta.returnValue;
4630 } else {
4631 ctx.meta.resolve = resolve;
4632 ctx.meta.reject = reject;
4633 return promise;
4634 }
4635 };
4636 func.hyperfunc = true;
4637 func.hypername = nameVal;
4638 runtime.assignToNamespace(target, nameSpace, funcName, func);
4639 },
4640 };
4641
4642 parser.ensureTerminated(start);
4643
4644 // terminate error handler if any
4645 if (errorHandler) {
4646 parser.ensureTerminated(errorHandler);
4647 }
4648
4649 parser.setParent(start, functionFeature);
4650 return functionFeature;
4651 });
4652
4653 parser.addFeature("set", function (parser, runtime, tokens) {
4654 let setCmd = parser.parseElement("setCommand", tokens);
4655 if (setCmd) {
4656 if (setCmd.target.scope !== "element") {
4657 parser.raiseParseError(tokens, "variables declared at the feature level must be element scoped.");
4658 }
4659 let setFeature = {
4660 start: setCmd,
4661 install: function (target, source) {
4662 setCmd && setCmd.execute(runtime.makeContext(target, setFeature, target, null));
4663 },
4664 };
4665 parser.ensureTerminated(setCmd);
4666 return setFeature;
4667 }
4668 });
4669
4670 parser.addFeature("init", function (parser, runtime, tokens) {
4671 if (!tokens.matchToken("init")) return;
4672
4673 var immediately = tokens.matchToken("immediately");
4674
4675 var start = parser.requireElement("commandList", tokens);
4676 var initFeature = {
4677 start: start,
4678 install: function (target, source) {
4679 let handler = function () {
4680 start && start.execute(runtime.makeContext(target, initFeature, target, null));
4681 };
4682 if (immediately) {
4683 handler();
4684 } else {
4685 setTimeout(handler, 0);
4686 }
4687 },
4688 };
4689
4690 // terminate body
4691 parser.ensureTerminated(start);
4692 parser.setParent(start, initFeature);
4693 return initFeature;
4694 });
4695
4696 parser.addFeature("worker", function (parser, runtime, tokens) {
4697 if (tokens.matchToken("worker")) {
4698 parser.raiseParseError(
4699 tokens,
4700 "In order to use the 'worker' feature, include " +
4701 "the _hyperscript worker plugin. See " +
4702 "https://hyperscript.org/features/worker/ for " +
4703 "more info."
4704 );
4705 return undefined
4706 }
4707 });
4708
4709 parser.addFeature("behavior", function (parser, runtime, tokens) {
4710 if (!tokens.matchToken("behavior")) return;
4711 var path = parser.requireElement("dotOrColonPath", tokens).evaluate();
4712 var nameSpace = path.split(".");
4713 var name = nameSpace.pop();
4714
4715 var formalParams = [];
4716 if (tokens.matchOpToken("(") && !tokens.matchOpToken(")")) {
4717 do {
4718 formalParams.push(tokens.requireTokenType("IDENTIFIER").value);
4719 } while (tokens.matchOpToken(","));
4720 tokens.requireOpToken(")");
4721 }
4722 var hs = parser.requireElement("hyperscript", tokens);
4723 for (var i = 0; i < hs.features.length; i++) {
4724 var feature = hs.features[i];
4725 feature.behavior = path;
4726 }
4727
4728 return {
4729 install: function (target, source) {
4730 runtime.assignToNamespace(
4731 globalScope.document && globalScope.document.body,
4732 nameSpace,
4733 name,
4734 function (target, source, innerArgs) {
4735 var internalData = runtime.getInternalData(target);
4736 var elementScope = getOrInitObject(internalData, path + "Scope");
4737 for (var i = 0; i < formalParams.length; i++) {
4738 elementScope[formalParams[i]] = innerArgs[formalParams[i]];
4739 }
4740 hs.apply(target, source);
4741 }
4742 );
4743 },
4744 };
4745 });
4746
4747 parser.addFeature("install", function (parser, runtime, tokens) {
4748 if (!tokens.matchToken("install")) return;
4749 var behaviorPath = parser.requireElement("dotOrColonPath", tokens).evaluate();
4750 var behaviorNamespace = behaviorPath.split(".");
4751 var args = parser.parseElement("namedArgumentList", tokens);
4752
4753 var installFeature;
4754 return (installFeature = {
4755 install: function (target, source) {
4756 runtime.unifiedEval(
4757 {
4758 args: [args],
4759 op: function (ctx, args) {
4760 var behavior = globalScope;
4761 for (var i = 0; i < behaviorNamespace.length; i++) {
4762 behavior = behavior[behaviorNamespace[i]];
4763 if (typeof behavior !== "object" && typeof behavior !== "function")
4764 throw new Error("No such behavior defined as " + behaviorPath);
4765 }
4766
4767 if (!(behavior instanceof Function))
4768 throw new Error(behaviorPath + " is not a behavior");
4769
4770 behavior(target, source, args);
4771 },
4772 },
4773 runtime.makeContext(target, installFeature, target, null)
4774 );
4775 },
4776 });
4777 });
4778
4779 parser.addGrammarElement("jsBody", function (parser, runtime, tokens) {
4780 var jsSourceStart = tokens.currentToken().start;
4781 var jsLastToken = tokens.currentToken();
4782
4783 var funcNames = [];
4784 var funcName = "";
4785 var expectFunctionDeclaration = false;
4786 while (tokens.hasMore()) {
4787 jsLastToken = tokens.consumeToken();
4788 var peek = tokens.token(0, true);
4789 if (peek.type === "IDENTIFIER" && peek.value === "end") {
4790 break;
4791 }
4792 if (expectFunctionDeclaration) {
4793 if (jsLastToken.type === "IDENTIFIER" || jsLastToken.type === "NUMBER") {
4794 funcName += jsLastToken.value;
4795 } else {
4796 if (funcName !== "") funcNames.push(funcName);
4797 funcName = "";
4798 expectFunctionDeclaration = false;
4799 }
4800 } else if (jsLastToken.type === "IDENTIFIER" && jsLastToken.value === "function") {
4801 expectFunctionDeclaration = true;
4802 }
4803 }
4804 var jsSourceEnd = jsLastToken.end + 1;
4805
4806 return {
4807 type: "jsBody",
4808 exposedFunctionNames: funcNames,
4809 jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd),
4810 };
4811 });
4812
4813 parser.addFeature("js", function (parser, runtime, tokens) {
4814 if (!tokens.matchToken("js")) return;
4815 var jsBody = parser.requireElement("jsBody", tokens);
4816
4817 var jsSource =
4818 jsBody.jsSource +
4819 "\nreturn { " +
4820 jsBody.exposedFunctionNames
4821 .map(function (name) {
4822 return name + ":" + name;
4823 })
4824 .join(",") +
4825 " } ";
4826 var func = new Function(jsSource);
4827
4828 return {
4829 jsSource: jsSource,
4830 function: func,
4831 exposedFunctionNames: jsBody.exposedFunctionNames,
4832 install: function () {
4833 Object.assign(globalScope, func());
4834 },
4835 };
4836 });
4837
4838 parser.addCommand("js", function (parser, runtime, tokens) {
4839 if (!tokens.matchToken("js")) return;
4840 // Parse inputs
4841 var inputs = [];
4842 if (tokens.matchOpToken("(")) {
4843 if (tokens.matchOpToken(")")) {
4844 // empty input list
4845 } else {
4846 do {
4847 var inp = tokens.requireTokenType("IDENTIFIER");
4848 inputs.push(inp.value);
4849 } while (tokens.matchOpToken(","));
4850 tokens.requireOpToken(")");
4851 }
4852 }
4853
4854 var jsBody = parser.requireElement("jsBody", tokens);
4855 tokens.matchToken("end");
4856
4857 var func = varargConstructor(Function, inputs.concat([jsBody.jsSource]));
4858
4859 var command = {
4860 jsSource: jsBody.jsSource,
4861 function: func,
4862 inputs: inputs,
4863 op: function (context) {
4864 var args = [];
4865 inputs.forEach(function (input) {
4866 args.push(runtime.resolveSymbol(input, context, 'default'));
4867 });
4868 var result = func.apply(globalScope, args);
4869 if (result && typeof result.then === "function") {
4870 return new Promise(function (resolve) {
4871 result.then(function (actualResult) {
4872 context.result = actualResult;
4873 resolve(runtime.findNext(this, context));
4874 });
4875 });
4876 } else {
4877 context.result = result;
4878 return runtime.findNext(this, context);
4879 }
4880 },
4881 };
4882 return command;
4883 });
4884
4885 parser.addCommand("async", function (parser, runtime, tokens) {
4886 if (!tokens.matchToken("async")) return;
4887 if (tokens.matchToken("do")) {
4888 var body = parser.requireElement("commandList", tokens);
4889
4890 // Append halt
4891 var end = body;
4892 while (end.next) end = end.next;
4893 end.next = runtime.HALT;
4894
4895 tokens.requireToken("end");
4896 } else {
4897 var body = parser.requireElement("command", tokens);
4898 }
4899 var command = {
4900 body: body,
4901 op: function (context) {
4902 setTimeout(function () {
4903 body.execute(context);
4904 });
4905 return runtime.findNext(this, context);
4906 },
4907 };
4908 parser.setParent(body, command);
4909 return command;
4910 });
4911
4912 parser.addCommand("tell", function (parser, runtime, tokens) {
4913 var startToken = tokens.currentToken();
4914 if (!tokens.matchToken("tell")) return;
4915 var value = parser.requireElement("expression", tokens);
4916 var body = parser.requireElement("commandList", tokens);
4917 if (tokens.hasMore() && !parser.featureStart(tokens.currentToken())) {
4918 tokens.requireToken("end");
4919 }
4920 var slot = "tell_" + startToken.start;
4921 var tellCmd = {
4922 value: value,
4923 body: body,
4924 args: [value],
4925 resolveNext: function (context) {
4926 var iterator = context.meta.iterators[slot];
4927 if (iterator.index < iterator.value.length) {
4928 context.you = iterator.value[iterator.index++];
4929 return body;
4930 } else {
4931 // restore original me
4932 context.you = iterator.originalYou;
4933 if (this.next) {
4934 return this.next;
4935 } else {
4936 return runtime.findNext(this.parent, context);
4937 }
4938 }
4939 },
4940 op: function (context, value) {
4941 if (value == null) {
4942 value = [];
4943 } else if (!(Array.isArray(value) || value instanceof NodeList)) {
4944 value = [value];
4945 }
4946 context.meta.iterators[slot] = {
4947 originalYou: context.you,
4948 index: 0,
4949 value: value,
4950 };
4951 return this.resolveNext(context);
4952 },
4953 };
4954 parser.setParent(body, tellCmd);
4955 return tellCmd;
4956 });
4957
4958 parser.addCommand("wait", function (parser, runtime, tokens) {
4959 if (!tokens.matchToken("wait")) return;
4960 var command;
4961
4962 // wait on event
4963 if (tokens.matchToken("for")) {
4964 tokens.matchToken("a"); // optional "a"
4965 var events = [];
4966 do {
4967 var lookahead = tokens.token(0);
4968 if (lookahead.type === 'NUMBER' || lookahead.type === 'L_PAREN') {
4969 events.push({
4970 time: parser.requireElement('expression', tokens).evaluate() // TODO: do we want to allow async here?
4971 })
4972 } else {
4973 events.push({
4974 name: parser.requireElement("dotOrColonPath", tokens, "Expected event name").evaluate(),
4975 args: parseEventArgs(tokens),
4976 });
4977 }
4978 } while (tokens.matchToken("or"));
4979
4980 if (tokens.matchToken("from")) {
4981 var on = parser.requireElement("expression", tokens);
4982 }
4983
4984 // wait on event
4985 command = {
4986 event: events,
4987 on: on,
4988 args: [on],
4989 op: function (context, on) {
4990 var target = on ? on : context.me;
4991 if (!(target instanceof EventTarget))
4992 throw new Error("Not a valid event target: " + this.on.sourceFor());
4993 return new Promise((resolve) => {
4994 var resolved = false;
4995 for (const eventInfo of events) {
4996 var listener = (event) => {
4997 context.result = event;
4998 if (eventInfo.args) {
4999 for (const arg of eventInfo.args) {
5000 context.locals[arg.value] =
5001 event[arg.value] || (event.detail ? event.detail[arg.value] : null);
5002 }
5003 }
5004 if (!resolved) {
5005 resolved = true;
5006 resolve(runtime.findNext(this, context));
5007 }
5008 };
5009 if (eventInfo.name){
5010 target.addEventListener(eventInfo.name, listener, {once: true});
5011 } else if (eventInfo.time != null) {
5012 setTimeout(listener, eventInfo.time, eventInfo.time)
5013 }
5014 }
5015 });
5016 },
5017 };
5018 return command;
5019 } else {
5020 var time;
5021 if (tokens.matchToken("a")) {
5022 tokens.requireToken("tick");
5023 time = 0;
5024 } else {
5025 time = parser.requireElement("expression", tokens);
5026 }
5027
5028 command = {
5029 type: "waitCmd",
5030 time: time,
5031 args: [time],
5032 op: function (context, timeValue) {
5033 return new Promise((resolve) => {
5034 setTimeout(() => {
5035 resolve(runtime.findNext(this, context));
5036 }, timeValue);
5037 });
5038 },
5039 execute: function (context) {
5040 return runtime.unifiedExec(this, context);
5041 },
5042 };
5043 return command;
5044 }
5045 });
5046
5047 // TODO - colon path needs to eventually become part of ruby-style symbols
5048 parser.addGrammarElement("dotOrColonPath", function (parser, runtime, tokens) {
5049 var root = tokens.matchTokenType("IDENTIFIER");
5050 if (root) {
5051 var path = [root.value];
5052
5053 var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":");
5054 if (separator) {
5055 do {
5056 path.push(tokens.requireTokenType("IDENTIFIER", "NUMBER").value);
5057 } while (tokens.matchOpToken(separator.value));
5058 }
5059
5060 return {
5061 type: "dotOrColonPath",
5062 path: path,
5063 evaluate: function () {
5064 return path.join(separator ? separator.value : "");
5065 },
5066 };
5067 }
5068 });
5069
5070
5071 parser.addGrammarElement("eventName", function (parser, runtime, tokens) {
5072 var token;
5073 if ((token = tokens.matchTokenType("STRING"))) {
5074 return {
5075 evaluate: function() {
5076 return token.value;
5077 },
5078 };
5079 }
5080
5081 return parser.parseElement("dotOrColonPath", tokens);
5082 });
5083
5084 function parseSendCmd(cmdType, parser, runtime, tokens) {
5085 var eventName = parser.requireElement("eventName", tokens);
5086
5087 var details = parser.parseElement("namedArgumentList", tokens);
5088 if ((cmdType === "send" && tokens.matchToken("to")) ||
5089 (cmdType === "trigger" && tokens.matchToken("on"))) {
5090 var toExpr = parser.requireElement("expression", tokens);
5091 } else {
5092 var toExpr = parser.requireElement("implicitMeTarget", tokens);
5093 }
5094
5095 var sendCmd = {
5096 eventName: eventName,
5097 details: details,
5098 to: toExpr,
5099 args: [toExpr, eventName, details],
5100 op: function (context, to, eventName, details) {
5101 runtime.nullCheck(to, toExpr);
5102 runtime.implicitLoop(to, function (target) {
5103 runtime.triggerEvent(target, eventName, details, context.me);
5104 });
5105 return runtime.findNext(sendCmd, context);
5106 },
5107 };
5108 return sendCmd;
5109 }
5110
5111 parser.addCommand("trigger", function (parser, runtime, tokens) {
5112 if (tokens.matchToken("trigger")) {
5113 return parseSendCmd("trigger", parser, runtime, tokens);
5114 }
5115 });
5116
5117 parser.addCommand("send", function (parser, runtime, tokens) {
5118 if (tokens.matchToken("send")) {
5119 return parseSendCmd("send", parser, runtime, tokens);
5120 }
5121 });
5122
5123 var parseReturnFunction = function (parser, runtime, tokens, returnAValue) {
5124 if (returnAValue) {
5125 if (parser.commandBoundary(tokens.currentToken())) {
5126 parser.raiseParseError(tokens, "'return' commands must return a value. If you do not wish to return a value, use 'exit' instead.");
5127 } else {
5128 var value = parser.requireElement("expression", tokens);
5129 }
5130 }
5131
5132 var returnCmd = {
5133 value: value,
5134 args: [value],
5135 op: function (context, value) {
5136 var resolve = context.meta.resolve;
5137 context.meta.returned = true;
5138 context.meta.returnValue = value;
5139 if (resolve) {
5140 if (value) {
5141 resolve(value);
5142 } else {
5143 resolve();
5144 }
5145 }
5146 return runtime.HALT;
5147 },
5148 };
5149 return returnCmd;
5150 };
5151
5152 parser.addCommand("return", function (parser, runtime, tokens) {
5153 if (tokens.matchToken("return")) {
5154 return parseReturnFunction(parser, runtime, tokens, true);
5155 }
5156 });
5157
5158 parser.addCommand("exit", function (parser, runtime, tokens) {
5159 if (tokens.matchToken("exit")) {
5160 return parseReturnFunction(parser, runtime, tokens, false);
5161 }
5162 });
5163
5164 parser.addCommand("halt", function (parser, runtime, tokens) {
5165 if (tokens.matchToken("halt")) {
5166 if (tokens.matchToken("the")) {
5167 tokens.requireToken("event");
5168 // optional possessive
5169 if (tokens.matchOpToken("'")) {
5170 tokens.requireToken("s");
5171 }
5172 var keepExecuting = true;
5173 }
5174 if (tokens.matchToken("bubbling")) {
5175 var bubbling = true;
5176 } else if (tokens.matchToken("default")) {
5177 var haltDefault = true;
5178 }
5179 var exit = parseReturnFunction(parser, runtime, tokens, false);
5180
5181 var haltCmd = {
5182 keepExecuting: true,
5183 bubbling: bubbling,
5184 haltDefault: haltDefault,
5185 exit: exit,
5186 op: function (ctx) {
5187 if (ctx.event) {
5188 if (bubbling) {
5189 ctx.event.stopPropagation();
5190 } else if (haltDefault) {
5191 ctx.event.preventDefault();
5192 } else {
5193 ctx.event.stopPropagation();
5194 ctx.event.preventDefault();
5195 }
5196 if (keepExecuting) {
5197 return runtime.findNext(this, ctx);
5198 } else {
5199 return exit;
5200 }
5201 }
5202 },
5203 };
5204 return haltCmd;
5205 }
5206 });
5207
5208 parser.addCommand("log", function (parser, runtime, tokens) {
5209 if (!tokens.matchToken("log")) return;
5210 var exprs = [parser.parseElement("expression", tokens)];
5211 while (tokens.matchOpToken(",")) {
5212 exprs.push(parser.requireElement("expression", tokens));
5213 }
5214 if (tokens.matchToken("with")) {
5215 var withExpr = parser.requireElement("expression", tokens);
5216 }
5217 var logCmd = {
5218 exprs: exprs,
5219 withExpr: withExpr,
5220 args: [withExpr, exprs],
5221 op: function (ctx, withExpr, values) {
5222 if (withExpr) {
5223 withExpr.apply(null, values);
5224 } else {
5225 console.log.apply(null, values);
5226 }
5227 return runtime.findNext(this, ctx);
5228 },
5229 };
5230 return logCmd;
5231 });
5232
5233 parser.addCommand("beep!", function (parser, runtime, tokens) {
5234 if (!tokens.matchToken("beep!")) return;
5235 var exprs = [parser.parseElement("expression", tokens)];
5236 while (tokens.matchOpToken(",")) {
5237 exprs.push(parser.requireElement("expression", tokens));
5238 }
5239 var beepCmd = {
5240 exprs: exprs,
5241 args: [exprs],
5242 op: function (ctx, values) {
5243 for (let i = 0; i < exprs.length; i++) {
5244 const expr = exprs[i];
5245 const val = values[i];
5246 runtime.beepValueToConsole(ctx.me, expr, val);
5247 }
5248 return runtime.findNext(this, ctx);
5249 },
5250 };
5251 return beepCmd;
5252 });
5253
5254 parser.addCommand("throw", function (parser, runtime, tokens) {
5255 if (!tokens.matchToken("throw")) return;
5256 var expr = parser.requireElement("expression", tokens);
5257 var throwCmd = {
5258 expr: expr,
5259 args: [expr],
5260 op: function (ctx, expr) {
5261 runtime.registerHyperTrace(ctx, expr);
5262 throw expr;
5263 },
5264 };
5265 return throwCmd;
5266 });
5267
5268 var parseCallOrGet = function (parser, runtime, tokens) {
5269 var expr = parser.requireElement("expression", tokens);
5270 var callCmd = {
5271 expr: expr,
5272 args: [expr],
5273 op: function (context, result) {
5274 context.result = result;
5275 return runtime.findNext(callCmd, context);
5276 },
5277 };
5278 return callCmd;
5279 };
5280 parser.addCommand("call", function (parser, runtime, tokens) {
5281 if (!tokens.matchToken("call")) return;
5282 var call = parseCallOrGet(parser, runtime, tokens);
5283 if (call.expr && call.expr.type !== "functionCall") {
5284 parser.raiseParseError(tokens, "Must be a function invocation");
5285 }
5286 return call;
5287 });
5288 parser.addCommand("get", function (parser, runtime, tokens) {
5289 if (tokens.matchToken("get")) {
5290 return parseCallOrGet(parser, runtime, tokens);
5291 }
5292 });
5293
5294 parser.addCommand("make", function (parser, runtime, tokens) {
5295 if (!tokens.matchToken("make")) return;
5296 tokens.matchToken("a") || tokens.matchToken("an");
5297
5298 var expr = parser.requireElement("expression", tokens);
5299
5300 var args = [];
5301 if (expr.type !== "queryRef" && tokens.matchToken("from")) {
5302 do {
5303 args.push(parser.requireElement("expression", tokens));
5304 } while (tokens.matchOpToken(","));
5305 }
5306
5307 if (tokens.matchToken("called")) {
5308 var target = parser.requireElement("symbol", tokens);
5309 }
5310
5311 var command;
5312 if (expr.type === "queryRef") {
5313 command = {
5314 op: function (ctx) {
5315 var match,
5316 tagname = "div",
5317 id,
5318 classes = [];
5319 var re = /(?:(^|#|\.)([^#\. ]+))/g;
5320 while ((match = re.exec(expr.css))) {
5321 if (match[1] === "") tagname = match[2].trim();
5322 else if (match[1] === "#") id = match[2].trim();
5323 else classes.push(match[2].trim());
5324 }
5325
5326 var result = document.createElement(tagname);
5327 if (id !== undefined) result.id = id;
5328 for (var i = 0; i < classes.length; i++) {
5329 var cls = classes[i];
5330 result.classList.add(cls)
5331 }
5332
5333 ctx.result = result;
5334 if (target){
5335 runtime.setSymbol(target.name, ctx, target.scope, result);
5336 }
5337
5338 return runtime.findNext(this, ctx);
5339 },
5340 };
5341 return command;
5342 } else {
5343 command = {
5344 args: [expr, args],
5345 op: function (ctx, expr, args) {
5346 ctx.result = varargConstructor(expr, args);
5347 if (target){
5348 runtime.setSymbol(target.name, ctx, target.scope, ctx.result);
5349 }
5350
5351 return runtime.findNext(this, ctx);
5352 },
5353 };
5354 return command;
5355 }
5356 });
5357
5358 parser.addGrammarElement("pseudoCommand", function (parser, runtime, tokens) {
5359
5360 let lookAhead = tokens.token(1);
5361 if (!(lookAhead && lookAhead.op && (lookAhead.value === '.' || lookAhead.value === "("))) {
5362 return null;
5363 }
5364
5365 var expr = parser.requireElement("primaryExpression", tokens);
5366
5367 var rootRoot = expr.root;
5368 var root = expr;
5369 while (rootRoot.root != null) {
5370 root = root.root;
5371 rootRoot = rootRoot.root;
5372 }
5373
5374 if (expr.type !== "functionCall") {
5375 parser.raiseParseError(tokens, "Pseudo-commands must be function calls");
5376 }
5377
5378 if (root.type === "functionCall" && root.root.root == null) {
5379 if (tokens.matchAnyToken("the", "to", "on", "with", "into", "from", "at")) {
5380 var realRoot = parser.requireElement("expression", tokens);
5381 } else if (tokens.matchToken("me")) {
5382 var realRoot = parser.requireElement("implicitMeTarget", tokens);
5383 }
5384 }
5385
5386 /** @type {ASTNode} */
5387
5388 var pseudoCommand
5389 if(realRoot){
5390 pseudoCommand = {
5391 type: "pseudoCommand",
5392 root: realRoot,
5393 argExressions: root.argExressions,
5394 args: [realRoot, root.argExressions],
5395 op: function (context, rootRoot, args) {
5396 runtime.nullCheck(rootRoot, realRoot);
5397 var func = rootRoot[root.root.name];
5398 runtime.nullCheck(func, root);
5399 if (func.hyperfunc) {
5400 args.push(context);
5401 }
5402 context.result = func.apply(rootRoot, args);
5403 return runtime.findNext(pseudoCommand, context);
5404 },
5405 execute: function (context) {
5406 return runtime.unifiedExec(this, context);
5407 },
5408 }
5409 } else {
5410 pseudoCommand = {
5411 type: "pseudoCommand",
5412 expr: expr,
5413 args: [expr],
5414 op: function (context, result) {
5415 context.result = result;
5416 return runtime.findNext(pseudoCommand, context);
5417 },
5418 execute: function (context) {
5419 return runtime.unifiedExec(this, context);
5420 },
5421 };
5422 }
5423
5424 return pseudoCommand;
5425 });
5426
5427 /**
5428 * @param {Parser} parser
5429 * @param {Runtime} runtime
5430 * @param {Tokens} tokens
5431 * @param {*} target
5432 * @param {*} value
5433 * @returns
5434 */
5435 var makeSetter = function (parser, runtime, tokens, target, value) {
5436
5437 var symbolWrite = target.type === "symbol";
5438 var attributeWrite = target.type === "attributeRef";
5439 var styleWrite = target.type === "styleRef";
5440 var arrayWrite = target.type === "arrayIndex";
5441
5442 if (!(attributeWrite || styleWrite || symbolWrite) && target.root == null) {
5443 parser.raiseParseError(tokens, "Can only put directly into symbols, not references");
5444 }
5445
5446 var rootElt = null;
5447 var prop = null;
5448 if (symbolWrite) {
5449 // rootElt is null
5450 } else if (attributeWrite || styleWrite) {
5451 rootElt = parser.requireElement("implicitMeTarget", tokens);
5452 var attribute = target;
5453 } else if(arrayWrite) {
5454 prop = target.firstIndex;
5455 rootElt = target.root;
5456 } else {
5457 prop = target.prop ? target.prop.value : null;
5458 var attribute = target.attribute;
5459 rootElt = target.root;
5460 }
5461
5462 /** @type {ASTNode} */
5463 var setCmd = {
5464 target: target,
5465 symbolWrite: symbolWrite,
5466 value: value,
5467 args: [rootElt, prop, value],
5468 op: function (context, root, prop, valueToSet) {
5469 if (symbolWrite) {
5470 runtime.setSymbol(target.name, context, target.scope, valueToSet);
5471 } else {
5472 runtime.nullCheck(root, rootElt);
5473 if (arrayWrite) {
5474 root[prop] = valueToSet;
5475 } else {
5476 runtime.implicitLoop(root, function (elt) {
5477 if (attribute) {
5478 if (attribute.type === "attributeRef") {
5479 if (valueToSet == null) {
5480 elt.removeAttribute(attribute.name);
5481 } else {
5482 elt.setAttribute(attribute.name, valueToSet);
5483 }
5484 } else {
5485 elt.style[attribute.name] = valueToSet;
5486 }
5487 } else {
5488 elt[prop] = valueToSet;
5489 }
5490 });
5491 }
5492 }
5493 return runtime.findNext(this, context);
5494 },
5495 };
5496 return setCmd;
5497 };
5498
5499 parser.addCommand("default", function (parser, runtime, tokens) {
5500 if (!tokens.matchToken("default")) return;
5501 var target = parser.requireElement("assignableExpression", tokens);
5502 tokens.requireToken("to");
5503
5504 var value = parser.requireElement("expression", tokens);
5505
5506 /** @type {ASTNode} */
5507 var setter = makeSetter(parser, runtime, tokens, target, value);
5508 var defaultCmd = {
5509 target: target,
5510 value: value,
5511 setter: setter,
5512 args: [target],
5513 op: function (context, target) {
5514 if (target) {
5515 return runtime.findNext(this, context);
5516 } else {
5517 return setter;
5518 }
5519 },
5520 };
5521 setter.parent = defaultCmd;
5522 return defaultCmd;
5523 });
5524
5525 parser.addCommand("set", function (parser, runtime, tokens) {
5526 if (!tokens.matchToken("set")) return;
5527 if (tokens.currentToken().type === "L_BRACE") {
5528 var obj = parser.requireElement("objectLiteral", tokens);
5529 tokens.requireToken("on");
5530 var target = parser.requireElement("expression", tokens);
5531
5532 var command = {
5533 objectLiteral: obj,
5534 target: target,
5535 args: [obj, target],
5536 op: function (ctx, obj, target) {
5537 Object.assign(target, obj);
5538 return runtime.findNext(this, ctx);
5539 },
5540 };
5541 return command;
5542 }
5543
5544 try {
5545 tokens.pushFollow("to");
5546 var target = parser.requireElement("assignableExpression", tokens);
5547 } finally {
5548 tokens.popFollow();
5549 }
5550 tokens.requireToken("to");
5551 var value = parser.requireElement("expression", tokens);
5552 return makeSetter(parser, runtime, tokens, target, value);
5553 });
5554
5555 parser.addCommand("if", function (parser, runtime, tokens) {
5556 if (!tokens.matchToken("if")) return;
5557 var expr = parser.requireElement("expression", tokens);
5558 tokens.matchToken("then"); // optional 'then'
5559 var trueBranch = parser.parseElement("commandList", tokens);
5560 var nestedIfStmt = false;
5561 let elseToken = tokens.matchToken("else") || tokens.matchToken("otherwise");
5562 if (elseToken) {
5563 let elseIfIfToken = tokens.peekToken("if");
5564 nestedIfStmt = elseIfIfToken != null && elseIfIfToken.line === elseToken.line;
5565 if (nestedIfStmt) {
5566 var falseBranch = parser.parseElement("command", tokens);
5567 } else {
5568 var falseBranch = parser.parseElement("commandList", tokens);
5569 }
5570 }
5571 if (tokens.hasMore() && !nestedIfStmt) {
5572 tokens.requireToken("end");
5573 }
5574
5575 /** @type {ASTNode} */
5576 var ifCmd = {
5577 expr: expr,
5578 trueBranch: trueBranch,
5579 falseBranch: falseBranch,
5580 args: [expr],
5581 op: function (context, exprValue) {
5582 if (exprValue) {
5583 return trueBranch;
5584 } else if (falseBranch) {
5585 return falseBranch;
5586 } else {
5587 return runtime.findNext(this, context);
5588 }
5589 },
5590 };
5591 parser.setParent(trueBranch, ifCmd);
5592 parser.setParent(falseBranch, ifCmd);
5593 return ifCmd;
5594 });
5595
5596 var parseRepeatExpression = function (parser, tokens, runtime, startedWithForToken) {
5597 var innerStartToken = tokens.currentToken();
5598 var identifier;
5599 if (tokens.matchToken("for") || startedWithForToken) {
5600 var identifierToken = tokens.requireTokenType("IDENTIFIER");
5601 identifier = identifierToken.value;
5602 tokens.requireToken("in");
5603 var expression = parser.requireElement("expression", tokens);
5604 } else if (tokens.matchToken("in")) {
5605 identifier = "it";
5606 var expression = parser.requireElement("expression", tokens);
5607 } else if (tokens.matchToken("while")) {
5608 var whileExpr = parser.requireElement("expression", tokens);
5609 } else if (tokens.matchToken("until")) {
5610 var isUntil = true;
5611 if (tokens.matchToken("event")) {
5612 var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
5613 if (tokens.matchToken("from")) {
5614 var on = parser.requireElement("expression", tokens);
5615 }
5616 } else {
5617 var whileExpr = parser.requireElement("expression", tokens);
5618 }
5619 } else {
5620 if (!parser.commandBoundary(tokens.currentToken()) &&
5621 tokens.currentToken().value !== 'forever') {
5622 var times = parser.requireElement("expression", tokens);
5623 tokens.requireToken("times");
5624 } else {
5625 tokens.matchToken("forever"); // consume optional forever
5626 var forever = true;
5627 }
5628 }
5629
5630 if (tokens.matchToken("index")) {
5631 var identifierToken = tokens.requireTokenType("IDENTIFIER");
5632 var indexIdentifier = identifierToken.value;
5633 }
5634
5635 var loop = parser.parseElement("commandList", tokens);
5636 if (loop && evt) {
5637 // if this is an event based loop, wait a tick at the end of the loop so that
5638 // events have a chance to trigger in the loop condition o_O)))
5639 var last = loop;
5640 while (last.next) {
5641 last = last.next;
5642 }
5643 var waitATick = {
5644 type: "waitATick",
5645 op: function () {
5646 return new Promise(function (resolve) {
5647 setTimeout(function () {
5648 resolve(runtime.findNext(waitATick));
5649 }, 0);
5650 });
5651 },
5652 };
5653 last.next = waitATick;
5654 }
5655 if (tokens.hasMore()) {
5656 tokens.requireToken("end");
5657 }
5658
5659 if (identifier == null) {
5660 identifier = "_implicit_repeat_" + innerStartToken.start;
5661 var slot = identifier;
5662 } else {
5663 var slot = identifier + "_" + innerStartToken.start;
5664 }
5665
5666 var repeatCmd = {
5667 identifier: identifier,
5668 indexIdentifier: indexIdentifier,
5669 slot: slot,
5670 expression: expression,
5671 forever: forever,
5672 times: times,
5673 until: isUntil,
5674 event: evt,
5675 on: on,
5676 whileExpr: whileExpr,
5677 resolveNext: function () {
5678 return this;
5679 },
5680 loop: loop,
5681 args: [whileExpr, times],
5682 op: function (context, whileValue, times) {
5683 var iteratorInfo = context.meta.iterators[slot];
5684 var keepLooping = false;
5685 var loopVal = null;
5686 if (this.forever) {
5687 keepLooping = true;
5688 } else if (this.until) {
5689 if (evt) {
5690 keepLooping = context.meta.iterators[slot].eventFired === false;
5691 } else {
5692 keepLooping = whileValue !== true;
5693 }
5694 } else if (whileExpr) {
5695 keepLooping = whileValue;
5696 } else if (times) {
5697 keepLooping = iteratorInfo.index < times;
5698 } else {
5699 var nextValFromIterator = iteratorInfo.iterator.next();
5700 keepLooping = !nextValFromIterator.done;
5701 loopVal = nextValFromIterator.value;
5702 }
5703
5704 if (keepLooping) {
5705 if (iteratorInfo.value) {
5706 context.result = context.locals[identifier] = loopVal;
5707 } else {
5708 context.result = iteratorInfo.index;
5709 }
5710 if (indexIdentifier) {
5711 context.locals[indexIdentifier] = iteratorInfo.index;
5712 }
5713 iteratorInfo.index++;
5714 return loop;
5715 } else {
5716 context.meta.iterators[slot] = null;
5717 return runtime.findNext(this.parent, context);
5718 }
5719 },
5720 };
5721 parser.setParent(loop, repeatCmd);
5722 var repeatInit = {
5723 name: "repeatInit",
5724 args: [expression, evt, on],
5725 op: function (context, value, event, on) {
5726 var iteratorInfo = {
5727 index: 0,
5728 value: value,
5729 eventFired: false,
5730 };
5731 context.meta.iterators[slot] = iteratorInfo;
5732 if (value && value[Symbol.iterator]) {
5733 iteratorInfo.iterator = value[Symbol.iterator]();
5734 }
5735 if (evt) {
5736 var target = on || context.me;
5737 target.addEventListener(
5738 event,
5739 function (e) {
5740 context.meta.iterators[slot].eventFired = true;
5741 },
5742 { once: true }
5743 );
5744 }
5745 return repeatCmd; // continue to loop
5746 },
5747 execute: function (context) {
5748 return runtime.unifiedExec(this, context);
5749 },
5750 };
5751 parser.setParent(repeatCmd, repeatInit);
5752 return repeatInit;
5753 };
5754
5755 parser.addCommand("repeat", function (parser, runtime, tokens) {
5756 if (tokens.matchToken("repeat")) {
5757 return parseRepeatExpression(parser, tokens, runtime, false);
5758 }
5759 });
5760
5761 parser.addCommand("for", function (parser, runtime, tokens) {
5762 if (tokens.matchToken("for")) {
5763 return parseRepeatExpression(parser, tokens, runtime, true);
5764 }
5765 });
5766
5767 parser.addCommand("continue", function (parser, runtime, tokens) {
5768
5769 if (!tokens.matchToken("continue")) return;
5770
5771 var command = {
5772 op: function (context) {
5773
5774 // scan for the closest repeat statement
5775 for (var parent = this.parent ; true ; parent = parent.parent) {
5776
5777 if (parent == undefined) {
5778 parser.raiseParseError(tokens, "Command `continue` cannot be used outside of a `repeat` loop.")
5779 }
5780 if (parent.loop != undefined) {
5781 return parent.resolveNext(context)
5782 }
5783 }
5784 }
5785 };
5786 return command;
5787 });
5788
5789 parser.addCommand("break", function (parser, runtime, tokens) {
5790
5791 if (!tokens.matchToken("break")) return;
5792
5793 var command = {
5794 op: function (context) {
5795
5796 // scan for the closest repeat statement
5797 for (var parent = this.parent ; true ; parent = parent.parent) {
5798
5799 if (parent == undefined) {
5800 parser.raiseParseError(tokens, "Command `continue` cannot be used outside of a `repeat` loop.")
5801 }
5802 if (parent.loop != undefined) {
5803 return runtime.findNext(parent.parent, context);
5804 }
5805 }
5806 }
5807 };
5808 return command;
5809 });
5810
5811 parser.addGrammarElement("stringLike", function (parser, runtime, tokens) {
5812 return parser.parseAnyOf(["string", "nakedString"], tokens);
5813 });
5814
5815 parser.addCommand("append", function (parser, runtime, tokens) {
5816 if (!tokens.matchToken("append")) return;
5817 var targetExpr = null;
5818
5819 var value = parser.requireElement("expression", tokens);
5820
5821 /** @type {ASTNode} */
5822 var implicitResultSymbol = {
5823 type: "symbol",
5824 evaluate: function (context) {
5825 return runtime.resolveSymbol("result", context);
5826 },
5827 };
5828
5829 if (tokens.matchToken("to")) {
5830 targetExpr = parser.requireElement("expression", tokens);
5831 } else {
5832 targetExpr = implicitResultSymbol;
5833 }
5834
5835 var setter = null;
5836 if (targetExpr.type === "symbol" || targetExpr.type === "attributeRef" || targetExpr.root != null) {
5837 setter = makeSetter(parser, runtime, tokens, targetExpr, implicitResultSymbol);
5838 }
5839
5840 var command = {
5841 value: value,
5842 target: targetExpr,
5843 args: [targetExpr, value],
5844 op: function (context, target, value) {
5845 if (Array.isArray(target)) {
5846 target.push(value);
5847 return runtime.findNext(this, context);
5848 } else if (target instanceof Element) {
5849 target.innerHTML += value;
5850 return runtime.findNext(this, context);
5851 } else if(setter) {
5852 context.result = (target || "") + value;
5853 return setter;
5854 } else {
5855 throw Error("Unable to append a value!")
5856 }
5857 },
5858 execute: function (context) {
5859 return runtime.unifiedExec(this, context/*, value, target*/);
5860 },
5861 };
5862
5863 if (setter != null) {
5864 setter.parent = command;
5865 }
5866
5867 return command;
5868 });
5869
5870 function parsePickRange(parser, runtime, tokens) {
5871 tokens.matchToken("at") || tokens.matchToken("from");
5872 const rv = { includeStart: true, includeEnd: false }
5873
5874 rv.from = tokens.matchToken("start") ? 0 : parser.requireElement("expression", tokens)
5875
5876 if (tokens.matchToken("to") || tokens.matchOpToken("..")) {
5877 if (tokens.matchToken("end")) {
5878 rv.toEnd = true;
5879 } else {
5880 rv.to = parser.requireElement("expression", tokens);
5881 }
5882 }
5883
5884 if (tokens.matchToken("inclusive")) rv.includeEnd = true;
5885 else if (tokens.matchToken("exclusive")) rv.includeStart = false;
5886
5887 return rv;
5888 }
5889
5890 class RegExpIterator {
5891 constructor(re, str) {
5892 this.re = re;
5893 this.str = str;
5894 }
5895
5896 next() {
5897 const match = this.re.exec(this.str);
5898 if (match === null) return { done: true };
5899 else return { value: match };
5900 }
5901 }
5902
5903 class RegExpIterable {
5904 constructor(re, flags, str) {
5905 this.re = re;
5906 this.flags = flags;
5907 this.str = str;
5908 }
5909
5910 [Symbol.iterator]() {
5911 return new RegExpIterator(new RegExp(this.re, this.flags), this.str);
5912 }
5913 }
5914
5915 parser.addCommand("pick", (parser, runtime, tokens) => {
5916 if (!tokens.matchToken("pick")) return;
5917
5918 tokens.matchToken("the");
5919
5920 if (tokens.matchToken("item") || tokens.matchToken("items")
5921 || tokens.matchToken("character") || tokens.matchToken("characters")) {
5922 const range = parsePickRange(parser, runtime, tokens);
5923
5924 tokens.requireToken("from");
5925 const root = parser.requireElement("expression", tokens);
5926
5927 return {
5928 args: [root, range.from, range.to],
5929 op(ctx, root, from, to) {
5930 if (range.toEnd) to = root.length;
5931 if (!range.includeStart) from++;
5932 if (range.includeEnd) to++;
5933 if (to == null || to == undefined) to = from + 1;
5934 ctx.result = root.slice(from, to);
5935 return runtime.findNext(this, ctx);
5936 }
5937 }
5938 }
5939
5940 if (tokens.matchToken("match")) {
5941 tokens.matchToken("of");
5942 const re = parser.parseElement("expression", tokens);
5943 let flags = ""
5944 if (tokens.matchOpToken("|")) {
5945 flags = tokens.requireToken("identifier").value;
5946 }
5947
5948 tokens.requireToken("from");
5949 const root = parser.parseElement("expression", tokens);
5950
5951 return {
5952 args: [root, re],
5953 op(ctx, root, re) {
5954 ctx.result = new RegExp(re, flags).exec(root);
5955 return runtime.findNext(this, ctx);
5956 }
5957 }
5958 }
5959
5960 if (tokens.matchToken("matches")) {
5961 tokens.matchToken("of");
5962 const re = parser.parseElement("expression", tokens);
5963 let flags = "gu"
5964 if (tokens.matchOpToken("|")) {
5965 flags = 'g' + tokens.requireToken("identifier").value.replace('g', '');
5966 }
5967 console.log('flags', flags)
5968
5969 tokens.requireToken("from");
5970 const root = parser.parseElement("expression", tokens);
5971
5972 return {
5973 args: [root, re],
5974 op(ctx, root, re) {
5975 ctx.result = new RegExpIterable(re, flags, root);
5976 return runtime.findNext(this, ctx);
5977 }
5978 }
5979 }
5980 });
5981
5982 parser.addCommand("increment", function (parser, runtime, tokens) {
5983 if (!tokens.matchToken("increment")) return;
5984 var amountExpr;
5985
5986 // This is optional. Defaults to "result"
5987 var target = parser.parseElement("assignableExpression", tokens);
5988
5989 // This is optional. Defaults to 1.
5990 if (tokens.matchToken("by")) {
5991 amountExpr = parser.requireElement("expression", tokens);
5992 }
5993
5994 var implicitIncrementOp = {
5995 type: "implicitIncrementOp",
5996 target: target,
5997 args: [target, amountExpr],
5998 op: function (context, targetValue, amount) {
5999 targetValue = targetValue ? parseFloat(targetValue) : 0;
6000 amount = amountExpr ? parseFloat(amount) : 1;
6001 var newValue = targetValue + amount;
6002 context.result = newValue;
6003 return newValue;
6004 },
6005 evaluate: function (context) {
6006 return runtime.unifiedEval(this, context);
6007 }
6008 };
6009
6010 return makeSetter(parser, runtime, tokens, target, implicitIncrementOp);
6011 });
6012
6013 parser.addCommand("decrement", function (parser, runtime, tokens) {
6014 if (!tokens.matchToken("decrement")) return;
6015 var amountExpr;
6016
6017 // This is optional. Defaults to "result"
6018 var target = parser.parseElement("assignableExpression", tokens);
6019
6020 // This is optional. Defaults to 1.
6021 if (tokens.matchToken("by")) {
6022 amountExpr = parser.requireElement("expression", tokens);
6023 }
6024
6025 var implicitDecrementOp = {
6026 type: "implicitDecrementOp",
6027 target: target,
6028 args: [target, amountExpr],
6029 op: function (context, targetValue, amount) {
6030 targetValue = targetValue ? parseFloat(targetValue) : 0;
6031 amount = amountExpr ? parseFloat(amount) : 1;
6032 var newValue = targetValue - amount;
6033 context.result = newValue;
6034 return newValue;
6035 },
6036 evaluate: function (context) {
6037 return runtime.unifiedEval(this, context);
6038 }
6039 };
6040
6041 return makeSetter(parser, runtime, tokens, target, implicitDecrementOp);
6042 });
6043
6044 function parseConversionInfo(tokens, parser) {
6045 var type = "text";
6046 var conversion;
6047 tokens.matchToken("a") || tokens.matchToken("an");
6048 if (tokens.matchToken("json") || tokens.matchToken("Object")) {
6049 type = "json";
6050 } else if (tokens.matchToken("response")) {
6051 type = "response";
6052 } else if (tokens.matchToken("html")) {
6053 type = "html";
6054 } else if (tokens.matchToken("text")) {
6055 // default, ignore
6056 } else {
6057 conversion = parser.requireElement("dotOrColonPath", tokens).evaluate();
6058 }
6059 return {type, conversion};
6060 }
6061
6062 parser.addCommand("fetch", function (parser, runtime, tokens) {
6063 if (!tokens.matchToken("fetch")) return;
6064 var url = parser.requireElement("stringLike", tokens);
6065
6066 if (tokens.matchToken("as")) {
6067 var conversionInfo = parseConversionInfo(tokens, parser);
6068 }
6069
6070 if (tokens.matchToken("with") && tokens.currentToken().value !== "{") {
6071 var args = parser.parseElement("nakedNamedArgumentList", tokens);
6072 } else {
6073 var args = parser.parseElement("objectLiteral", tokens);
6074 }
6075
6076 if (conversionInfo == null && tokens.matchToken("as")) {
6077 conversionInfo = parseConversionInfo(tokens, parser);
6078 }
6079
6080 var type = conversionInfo ? conversionInfo.type : "text";
6081 var conversion = conversionInfo ? conversionInfo.conversion : null
6082
6083 /** @type {ASTNode} */
6084 var fetchCmd = {
6085 url: url,
6086 argExpressions: args,
6087 args: [url, args],
6088 op: function (context, url, args) {
6089 var detail = args || {};
6090 detail["sender"] = context.me;
6091 detail["headers"] = detail["headers"] || {}
6092 var abortController = new AbortController();
6093 let abortListener = context.me.addEventListener('fetch:abort', function(){
6094 abortController.abort();
6095 }, {once: true});
6096 detail['signal'] = abortController.signal;
6097 runtime.triggerEvent(context.me, "hyperscript:beforeFetch", detail);
6098 runtime.triggerEvent(context.me, "fetch:beforeRequest", detail);
6099 args = detail;
6100 var finished = false;
6101 if (args.timeout) {
6102 setTimeout(function () {
6103 if (!finished) {
6104 abortController.abort();
6105 }
6106 }, args.timeout);
6107 }
6108 return fetch(url, args)
6109 .then(function (resp) {
6110 let resultDetails = {response:resp};
6111 runtime.triggerEvent(context.me, "fetch:afterResponse", resultDetails);
6112 resp = resultDetails.response;
6113
6114 if (type === "response") {
6115 context.result = resp;
6116 runtime.triggerEvent(context.me, "fetch:afterRequest", {result:resp});
6117 finished = true;
6118 return runtime.findNext(fetchCmd, context);
6119 }
6120 if (type === "json") {
6121 return resp.json().then(function (result) {
6122 context.result = result;
6123 runtime.triggerEvent(context.me, "fetch:afterRequest", {result});
6124 finished = true;
6125 return runtime.findNext(fetchCmd, context);
6126 });
6127 }
6128 return resp.text().then(function (result) {
6129 if (conversion) result = runtime.convertValue(result, conversion);
6130
6131 if (type === "html") result = runtime.convertValue(result, "Fragment");
6132
6133 context.result = result;
6134 runtime.triggerEvent(context.me, "fetch:afterRequest", {result});
6135 finished = true;
6136 return runtime.findNext(fetchCmd, context);
6137 });
6138 })
6139 .catch(function (reason) {
6140 runtime.triggerEvent(context.me, "fetch:error", {
6141 reason: reason,
6142 });
6143 throw reason;
6144 }).finally(function(){
6145 context.me.removeEventListener('fetch:abort', abortListener);
6146 });
6147 },
6148 };
6149 return fetchCmd;
6150 });
6151 }
6152
6153 function hyperscriptWebGrammar(parser) {
6154 parser.addCommand("settle", function (parser, runtime, tokens) {
6155 if (tokens.matchToken("settle")) {
6156 if (!parser.commandBoundary(tokens.currentToken())) {
6157 var onExpr = parser.requireElement("expression", tokens);
6158 } else {
6159 var onExpr = parser.requireElement("implicitMeTarget", tokens);
6160 }
6161
6162 var settleCommand = {
6163 type: "settleCmd",
6164 args: [onExpr],
6165 op: function (context, on) {
6166 runtime.nullCheck(on, onExpr);
6167 var resolve = null;
6168 var resolved = false;
6169 var transitionStarted = false;
6170
6171 var promise = new Promise(function (r) {
6172 resolve = r;
6173 });
6174
6175 // listen for a transition begin
6176 on.addEventListener(
6177 "transitionstart",
6178 function () {
6179 transitionStarted = true;
6180 },
6181 { once: true }
6182 );
6183
6184 // if no transition begins in 500ms, cancel
6185 setTimeout(function () {
6186 if (!transitionStarted && !resolved) {
6187 resolve(runtime.findNext(settleCommand, context));
6188 }
6189 }, 500);
6190
6191 // continue on a transition emd
6192 on.addEventListener(
6193 "transitionend",
6194 function () {
6195 if (!resolved) {
6196 resolve(runtime.findNext(settleCommand, context));
6197 }
6198 },
6199 { once: true }
6200 );
6201 return promise;
6202 },
6203 execute: function (context) {
6204 return runtime.unifiedExec(this, context);
6205 },
6206 };
6207 return settleCommand;
6208 }
6209 });
6210
6211 parser.addCommand("add", function (parser, runtime, tokens) {
6212 if (tokens.matchToken("add")) {
6213 var classRef = parser.parseElement("classRef", tokens);
6214 var attributeRef = null;
6215 var cssDeclaration = null;
6216 if (classRef == null) {
6217 attributeRef = parser.parseElement("attributeRef", tokens);
6218 if (attributeRef == null) {
6219 cssDeclaration = parser.parseElement("styleLiteral", tokens);
6220 if (cssDeclaration == null) {
6221 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression");
6222 }
6223 }
6224 } else {
6225 var classRefs = [classRef];
6226 while ((classRef = parser.parseElement("classRef", tokens))) {
6227 classRefs.push(classRef);
6228 }
6229 }
6230
6231 if (tokens.matchToken("to")) {
6232 var toExpr = parser.requireElement("expression", tokens);
6233 } else {
6234 var toExpr = parser.requireElement("implicitMeTarget", tokens);
6235 }
6236
6237 if (tokens.matchToken("when")) {
6238 if (cssDeclaration) {
6239 parser.raiseParseError(tokens, "Only class and properties are supported with a when clause")
6240 }
6241 var when = parser.requireElement("expression", tokens);
6242 }
6243
6244 if (classRefs) {
6245 return {
6246 classRefs: classRefs,
6247 to: toExpr,
6248 args: [toExpr, classRefs],
6249 op: function (context, to, classRefs) {
6250 runtime.nullCheck(to, toExpr);
6251 runtime.forEach(classRefs, function (classRef) {
6252 runtime.implicitLoop(to, function (target) {
6253 if (when) {
6254 context.result = target;
6255 let whenResult = runtime.evaluateNoPromise(when, context);
6256 if (whenResult) {
6257 if (target instanceof Element) target.classList.add(classRef.className);
6258 } else {
6259 if (target instanceof Element) target.classList.remove(classRef.className);
6260 }
6261 context.result = null;
6262 } else {
6263 if (target instanceof Element) target.classList.add(classRef.className);
6264 }
6265 });
6266 });
6267 return runtime.findNext(this, context);
6268 },
6269 };
6270 } else if (attributeRef) {
6271 return {
6272 type: "addCmd",
6273 attributeRef: attributeRef,
6274 to: toExpr,
6275 args: [toExpr],
6276 op: function (context, to, attrRef) {
6277 runtime.nullCheck(to, toExpr);
6278 runtime.implicitLoop(to, function (target) {
6279 if (when) {
6280 context.result = target;
6281 let whenResult = runtime.evaluateNoPromise(when, context);
6282 if (whenResult) {
6283 target.setAttribute(attributeRef.name, attributeRef.value);
6284 } else {
6285 target.removeAttribute(attributeRef.name);
6286 }
6287 context.result = null;
6288 } else {
6289 target.setAttribute(attributeRef.name, attributeRef.value);
6290 }
6291 });
6292 return runtime.findNext(this, context);
6293 },
6294 execute: function (ctx) {
6295 return runtime.unifiedExec(this, ctx);
6296 },
6297 };
6298 } else {
6299 return {
6300 type: "addCmd",
6301 cssDeclaration: cssDeclaration,
6302 to: toExpr,
6303 args: [toExpr, cssDeclaration],
6304 op: function (context, to, css) {
6305 runtime.nullCheck(to, toExpr);
6306 runtime.implicitLoop(to, function (target) {
6307 target.style.cssText += css;
6308 });
6309 return runtime.findNext(this, context);
6310 },
6311 execute: function (ctx) {
6312 return runtime.unifiedExec(this, ctx);
6313 },
6314 };
6315 }
6316 }
6317 });
6318
6319 parser.addGrammarElement("styleLiteral", function (parser, runtime, tokens) {
6320 if (!tokens.matchOpToken("{")) return;
6321
6322 var stringParts = [""]
6323 var exprs = []
6324
6325 while (tokens.hasMore()) {
6326 if (tokens.matchOpToken("\\")) {
6327 tokens.consumeToken();
6328 } else if (tokens.matchOpToken("}")) {
6329 break;
6330 } else if (tokens.matchToken("$")) {
6331 var opencurly = tokens.matchOpToken("{");
6332 var expr = parser.parseElement("expression", tokens);
6333 if (opencurly) tokens.requireOpToken("}");
6334
6335 exprs.push(expr)
6336 stringParts.push("")
6337 } else {
6338 var tok = tokens.consumeToken();
6339 stringParts[stringParts.length-1] += tokens.source.substring(tok.start, tok.end);
6340 }
6341
6342 stringParts[stringParts.length-1] += tokens.lastWhitespace();
6343 }
6344
6345 return {
6346 type: "styleLiteral",
6347 args: [exprs],
6348 op: function (ctx, exprs) {
6349 var rv = "";
6350
6351 stringParts.forEach(function (part, idx) {
6352 rv += part;
6353 if (idx in exprs) rv += exprs[idx];
6354 });
6355
6356 return rv;
6357 },
6358 evaluate: function(ctx) {
6359 return runtime.unifiedEval(this, ctx);
6360 }
6361 }
6362 })
6363
6364 parser.addCommand("remove", function (parser, runtime, tokens) {
6365 if (tokens.matchToken("remove")) {
6366 var classRef = parser.parseElement("classRef", tokens);
6367 var attributeRef = null;
6368 var elementExpr = null;
6369 if (classRef == null) {
6370 attributeRef = parser.parseElement("attributeRef", tokens);
6371 if (attributeRef == null) {
6372 elementExpr = parser.parseElement("expression", tokens);
6373 if (elementExpr == null) {
6374 parser.raiseParseError(
6375 tokens,
6376 "Expected either a class reference, attribute expression or value expression"
6377 );
6378 }
6379 }
6380 } else {
6381 var classRefs = [classRef];
6382 while ((classRef = parser.parseElement("classRef", tokens))) {
6383 classRefs.push(classRef);
6384 }
6385 }
6386
6387 if (tokens.matchToken("from")) {
6388 var fromExpr = parser.requireElement("expression", tokens);
6389 } else {
6390 if (elementExpr == null) {
6391 var fromExpr = parser.requireElement("implicitMeTarget", tokens);
6392 }
6393 }
6394
6395 if (elementExpr) {
6396 return {
6397 elementExpr: elementExpr,
6398 from: fromExpr,
6399 args: [elementExpr, fromExpr],
6400 op: function (context, element, from) {
6401 runtime.nullCheck(element, elementExpr);
6402 runtime.implicitLoop(element, function (target) {
6403 if (target.parentElement && (from == null || from.contains(target))) {
6404 target.parentElement.removeChild(target);
6405 }
6406 });
6407 return runtime.findNext(this, context);
6408 },
6409 };
6410 } else {
6411 return {
6412 classRefs: classRefs,
6413 attributeRef: attributeRef,
6414 elementExpr: elementExpr,
6415 from: fromExpr,
6416 args: [classRefs, fromExpr],
6417 op: function (context, classRefs, from) {
6418 runtime.nullCheck(from, fromExpr);
6419 if (classRefs) {
6420 runtime.forEach(classRefs, function (classRef) {
6421 runtime.implicitLoop(from, function (target) {
6422 target.classList.remove(classRef.className);
6423 });
6424 });
6425 } else {
6426 runtime.implicitLoop(from, function (target) {
6427 target.removeAttribute(attributeRef.name);
6428 });
6429 }
6430 return runtime.findNext(this, context);
6431 },
6432 };
6433 }
6434 }
6435 });
6436
6437 parser.addCommand("toggle", function (parser, runtime, tokens) {
6438 if (tokens.matchToken("toggle")) {
6439 tokens.matchAnyToken("the", "my");
6440 if (tokens.currentToken().type === "STYLE_REF") {
6441 let styleRef = tokens.consumeToken();
6442 var name = styleRef.value.substr(1);
6443 var visibility = true;
6444 var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name);
6445 if (tokens.matchToken("of")) {
6446 tokens.pushFollow("with");
6447 try {
6448 var onExpr = parser.requireElement("expression", tokens);
6449 } finally {
6450 tokens.popFollow();
6451 }
6452 } else {
6453 var onExpr = parser.requireElement("implicitMeTarget", tokens);
6454 }
6455 } else if (tokens.matchToken("between")) {
6456 var between = true;
6457 var classRef = parser.parseElement("classRef", tokens);
6458 tokens.requireToken("and");
6459 var classRef2 = parser.requireElement("classRef", tokens);
6460 } else {
6461 var classRef = parser.parseElement("classRef", tokens);
6462 var attributeRef = null;
6463 if (classRef == null) {
6464 attributeRef = parser.parseElement("attributeRef", tokens);
6465 if (attributeRef == null) {
6466 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression");
6467 }
6468 } else {
6469 var classRefs = [classRef];
6470 while ((classRef = parser.parseElement("classRef", tokens))) {
6471 classRefs.push(classRef);
6472 }
6473 }
6474 }
6475
6476 if (visibility !== true) {
6477 if (tokens.matchToken("on")) {
6478 var onExpr = parser.requireElement("expression", tokens);
6479 } else {
6480 var onExpr = parser.requireElement("implicitMeTarget", tokens);
6481 }
6482 }
6483
6484 if (tokens.matchToken("for")) {
6485 var time = parser.requireElement("expression", tokens);
6486 } else if (tokens.matchToken("until")) {
6487 var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
6488 if (tokens.matchToken("from")) {
6489 var from = parser.requireElement("expression", tokens);
6490 }
6491 }
6492
6493 var toggleCmd = {
6494 classRef: classRef,
6495 classRef2: classRef2,
6496 classRefs: classRefs,
6497 attributeRef: attributeRef,
6498 on: onExpr,
6499 time: time,
6500 evt: evt,
6501 from: from,
6502 toggle: function (on, classRef, classRef2, classRefs) {
6503 runtime.nullCheck(on, onExpr);
6504 if (visibility) {
6505 runtime.implicitLoop(on, function (target) {
6506 hideShowStrategy("toggle", target);
6507 });
6508 } else if (between) {
6509 runtime.implicitLoop(on, function (target) {
6510 if (target.classList.contains(classRef.className)) {
6511 target.classList.remove(classRef.className);
6512 target.classList.add(classRef2.className);
6513 } else {
6514 target.classList.add(classRef.className);
6515 target.classList.remove(classRef2.className);
6516 }
6517 });
6518 } else if (classRefs) {
6519 runtime.forEach(classRefs, function (classRef) {
6520 runtime.implicitLoop(on, function (target) {
6521 target.classList.toggle(classRef.className);
6522 });
6523 });
6524 } else {
6525 runtime.forEach(on, function (target) {
6526 if (target.hasAttribute(attributeRef.name)) {
6527 target.removeAttribute(attributeRef.name);
6528 } else {
6529 target.setAttribute(attributeRef.name, attributeRef.value);
6530 }
6531 });
6532 }
6533 },
6534 args: [onExpr, time, evt, from, classRef, classRef2, classRefs],
6535 op: function (context, on, time, evt, from, classRef, classRef2, classRefs) {
6536 if (time) {
6537 return new Promise(function (resolve) {
6538 toggleCmd.toggle(on, classRef, classRef2, classRefs);
6539 setTimeout(function () {
6540 toggleCmd.toggle(on, classRef, classRef2, classRefs);
6541 resolve(runtime.findNext(toggleCmd, context));
6542 }, time);
6543 });
6544 } else if (evt) {
6545 return new Promise(function (resolve) {
6546 var target = from || context.me;
6547 target.addEventListener(
6548 evt,
6549 function () {
6550 toggleCmd.toggle(on, classRef, classRef2, classRefs);
6551 resolve(runtime.findNext(toggleCmd, context));
6552 },
6553 { once: true }
6554 );
6555 toggleCmd.toggle(on, classRef, classRef2, classRefs);
6556 });
6557 } else {
6558 this.toggle(on, classRef, classRef2, classRefs);
6559 return runtime.findNext(toggleCmd, context);
6560 }
6561 },
6562 };
6563 return toggleCmd;
6564 }
6565 });
6566
6567 var HIDE_SHOW_STRATEGIES = {
6568 display: function (op, element, arg) {
6569 if (arg) {
6570 element.style.display = arg;
6571 } else if (op === "toggle") {
6572 if (getComputedStyle(element).display === "none") {
6573 HIDE_SHOW_STRATEGIES.display("show", element, arg);
6574 } else {
6575 HIDE_SHOW_STRATEGIES.display("hide", element, arg);
6576 }
6577 } else if (op === "hide") {
6578 const internalData = parser.runtime.getInternalData(element);
6579 if (internalData.originalDisplay == null) {
6580 internalData.originalDisplay = element.style.display;
6581 }
6582 element.style.display = "none";
6583 } else {
6584 const internalData = parser.runtime.getInternalData(element);
6585 if (internalData.originalDisplay && internalData.originalDisplay !== 'none') {
6586 element.style.display = internalData.originalDisplay;
6587 } else {
6588 element.style.removeProperty('display');
6589 }
6590 }
6591 },
6592 visibility: function (op, element, arg) {
6593 if (arg) {
6594 element.style.visibility = arg;
6595 } else if (op === "toggle") {
6596 if (getComputedStyle(element).visibility === "hidden") {
6597 HIDE_SHOW_STRATEGIES.visibility("show", element, arg);
6598 } else {
6599 HIDE_SHOW_STRATEGIES.visibility("hide", element, arg);
6600 }
6601 } else if (op === "hide") {
6602 element.style.visibility = "hidden";
6603 } else {
6604 element.style.visibility = "visible";
6605 }
6606 },
6607 opacity: function (op, element, arg) {
6608 if (arg) {
6609 element.style.opacity = arg;
6610 } else if (op === "toggle") {
6611 if (getComputedStyle(element).opacity === "0") {
6612 HIDE_SHOW_STRATEGIES.opacity("show", element, arg);
6613 } else {
6614 HIDE_SHOW_STRATEGIES.opacity("hide", element, arg);
6615 }
6616 } else if (op === "hide") {
6617 element.style.opacity = "0";
6618 } else {
6619 element.style.opacity = "1";
6620 }
6621 },
6622 };
6623
6624 var parseShowHideTarget = function (parser, runtime, tokens) {
6625 var target;
6626 var currentTokenValue = tokens.currentToken();
6627 if (currentTokenValue.value === "when" || currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) {
6628 target = parser.parseElement("implicitMeTarget", tokens);
6629 } else {
6630 target = parser.parseElement("expression", tokens);
6631 }
6632 return target;
6633 };
6634
6635 var resolveHideShowStrategy = function (parser, tokens, name) {
6636 var configDefault = config.defaultHideShowStrategy;
6637 var strategies = HIDE_SHOW_STRATEGIES;
6638 if (config.hideShowStrategies) {
6639 strategies = Object.assign(strategies, config.hideShowStrategies); // merge in user provided strategies
6640 }
6641 name = name || configDefault || "display";
6642 var value = strategies[name];
6643 if (value == null) {
6644 parser.raiseParseError(tokens, "Unknown show/hide strategy : " + name);
6645 }
6646 return value;
6647 };
6648
6649 parser.addCommand("hide", function (parser, runtime, tokens) {
6650 if (tokens.matchToken("hide")) {
6651 var targetExpr = parseShowHideTarget(parser, runtime, tokens);
6652
6653 var name = null;
6654 if (tokens.matchToken("with")) {
6655 name = tokens.requireTokenType("IDENTIFIER", "STYLE_REF").value;
6656 if (name.indexOf("*") === 0) {
6657 name = name.substr(1);
6658 }
6659 }
6660 var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name);
6661
6662 return {
6663 target: targetExpr,
6664 args: [targetExpr],
6665 op: function (ctx, target) {
6666 runtime.nullCheck(target, targetExpr);
6667 runtime.implicitLoop(target, function (elt) {
6668 hideShowStrategy("hide", elt);
6669 });
6670 return runtime.findNext(this, ctx);
6671 },
6672 };
6673 }
6674 });
6675
6676 parser.addCommand("show", function (parser, runtime, tokens) {
6677 if (tokens.matchToken("show")) {
6678 var targetExpr = parseShowHideTarget(parser, runtime, tokens);
6679
6680 var name = null;
6681 if (tokens.matchToken("with")) {
6682 name = tokens.requireTokenType("IDENTIFIER", "STYLE_REF").value;
6683 if (name.indexOf("*") === 0) {
6684 name = name.substr(1);
6685 }
6686 }
6687 var arg = null;
6688 if (tokens.matchOpToken(":")) {
6689 var tokenArr = tokens.consumeUntilWhitespace();
6690 tokens.matchTokenType("WHITESPACE");
6691 arg = tokenArr
6692 .map(function (t) {
6693 return t.value;
6694 })
6695 .join("");
6696 }
6697
6698 if (tokens.matchToken("when")) {
6699 var when = parser.requireElement("expression", tokens);
6700 }
6701
6702 var hideShowStrategy = resolveHideShowStrategy(parser, tokens, name);
6703
6704 return {
6705 target: targetExpr,
6706 when: when,
6707 args: [targetExpr],
6708 op: function (ctx, target) {
6709 runtime.nullCheck(target, targetExpr);
6710 runtime.implicitLoop(target, function (elt) {
6711 if (when) {
6712 ctx.result = elt;
6713 let whenResult = runtime.evaluateNoPromise(when, ctx);
6714 if (whenResult) {
6715 hideShowStrategy("show", elt, arg);
6716 } else {
6717 hideShowStrategy("hide", elt);
6718 }
6719 ctx.result = null;
6720 } else {
6721 hideShowStrategy("show", elt, arg);
6722 }
6723 });
6724 return runtime.findNext(this, ctx);
6725 },
6726 };
6727 }
6728 });
6729
6730 parser.addCommand("take", function (parser, runtime, tokens) {
6731 if (tokens.matchToken("take")) {
6732 let classRef = null;
6733 let classRefs = [];
6734 while ((classRef = parser.parseElement("classRef", tokens))) {
6735 classRefs.push(classRef);
6736 }
6737
6738 var attributeRef = null;
6739 var replacementValue = null;
6740
6741 let weAreTakingClasses = classRefs.length > 0;
6742 if (!weAreTakingClasses) {
6743 attributeRef = parser.parseElement("attributeRef", tokens);
6744 if (attributeRef == null) {
6745 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression");
6746 }
6747
6748 if (tokens.matchToken("with")) {
6749 replacementValue = parser.requireElement("expression", tokens);
6750 }
6751 }
6752
6753 if (tokens.matchToken("from")) {
6754 var fromExpr = parser.requireElement("expression", tokens);
6755 }
6756
6757 if (tokens.matchToken("for")) {
6758 var forExpr = parser.requireElement("expression", tokens);
6759 } else {
6760 var forExpr = parser.requireElement("implicitMeTarget", tokens);
6761 }
6762
6763 if (weAreTakingClasses) {
6764 var takeCmd = {
6765 classRefs: classRefs,
6766 from: fromExpr,
6767 forElt: forExpr,
6768 args: [classRefs, fromExpr, forExpr],
6769 op: function (context, classRefs, from, forElt) {
6770 runtime.nullCheck(forElt, forExpr);
6771 runtime.implicitLoop(classRefs, function(classRef){
6772 var clazz = classRef.className;
6773 if (from) {
6774 runtime.implicitLoop(from, function (target) {
6775 target.classList.remove(clazz);
6776 });
6777 } else {
6778 runtime.implicitLoop(classRef, function (target) {
6779 target.classList.remove(clazz);
6780 });
6781 }
6782 runtime.implicitLoop(forElt, function (target) {
6783 target.classList.add(clazz);
6784 });
6785 })
6786 return runtime.findNext(this, context);
6787 },
6788 };
6789 return takeCmd;
6790 } else {
6791 var takeCmd = {
6792 attributeRef: attributeRef,
6793 from: fromExpr,
6794 forElt: forExpr,
6795 args: [fromExpr, forExpr, replacementValue],
6796 op: function (context, from, forElt, replacementValue) {
6797 runtime.nullCheck(from, fromExpr);
6798 runtime.nullCheck(forElt, forExpr);
6799 runtime.implicitLoop(from, function (target) {
6800 if (!replacementValue) {
6801 target.removeAttribute(attributeRef.name);
6802 } else {
6803 target.setAttribute(attributeRef.name, replacementValue)
6804 }
6805 });
6806 runtime.implicitLoop(forElt, function (target) {
6807 target.setAttribute(attributeRef.name, attributeRef.value || "")
6808 });
6809 return runtime.findNext(this, context);
6810 },
6811 };
6812 return takeCmd;
6813 }
6814 }
6815 });
6816
6817 function putInto(runtime, context, prop, valueToPut) {
6818 if (prop != null) {
6819 var value = runtime.resolveSymbol(prop, context);
6820 } else {
6821 var value = context;
6822 }
6823 if (value instanceof Element || value instanceof HTMLDocument) {
6824 while (value.firstChild) value.removeChild(value.firstChild);
6825 value.append(parser.runtime.convertValue(valueToPut, "Fragment"));
6826 runtime.processNode(value);
6827 } else {
6828 if (prop != null) {
6829 runtime.setSymbol(prop, context, null, valueToPut);
6830 } else {
6831 throw "Don't know how to put a value into " + typeof context;
6832 }
6833 }
6834 }
6835
6836 parser.addCommand("put", function (parser, runtime, tokens) {
6837 if (tokens.matchToken("put")) {
6838 var value = parser.requireElement("expression", tokens);
6839
6840 var operationToken = tokens.matchAnyToken("into", "before", "after");
6841
6842 if (operationToken == null && tokens.matchToken("at")) {
6843 tokens.matchToken("the"); // optional "the"
6844 operationToken = tokens.matchAnyToken("start", "end");
6845 tokens.requireToken("of");
6846 }
6847
6848 if (operationToken == null) {
6849 parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'");
6850 }
6851 var target = parser.requireElement("expression", tokens);
6852
6853 var operation = operationToken.value;
6854
6855 var arrayIndex = false;
6856 var symbolWrite = false;
6857 var rootExpr = null;
6858 var prop = null;
6859
6860 if (target.type === "arrayIndex" && operation === "into") {
6861 arrayIndex = true;
6862 prop = target.prop;
6863 rootExpr = target.root;
6864 } else if (target.prop && target.root && operation === "into") {
6865 prop = target.prop.value;
6866 rootExpr = target.root;
6867 } else if (target.type === "symbol" && operation === "into") {
6868 symbolWrite = true;
6869 prop = target.name;
6870 } else if (target.type === "attributeRef" && operation === "into") {
6871 var attributeWrite = true;
6872 prop = target.name;
6873 rootExpr = parser.requireElement("implicitMeTarget", tokens);
6874 } else if (target.type === "styleRef" && operation === "into") {
6875 var styleWrite = true;
6876 prop = target.name;
6877 rootExpr = parser.requireElement("implicitMeTarget", tokens);
6878 } else if (target.attribute && operation === "into") {
6879 var attributeWrite = target.attribute.type === "attributeRef";
6880 var styleWrite = target.attribute.type === "styleRef";
6881 prop = target.attribute.name;
6882 rootExpr = target.root;
6883 } else {
6884 rootExpr = target;
6885 }
6886
6887 var putCmd = {
6888 target: target,
6889 operation: operation,
6890 symbolWrite: symbolWrite,
6891 value: value,
6892 args: [rootExpr, prop, value],
6893 op: function (context, root, prop, valueToPut) {
6894 if (symbolWrite) {
6895 putInto(runtime, context, prop, valueToPut);
6896 } else {
6897 runtime.nullCheck(root, rootExpr);
6898 if (operation === "into") {
6899 if (attributeWrite) {
6900 runtime.implicitLoop(root, function (elt) {
6901 elt.setAttribute(prop, valueToPut);
6902 });
6903 } else if (styleWrite) {
6904 runtime.implicitLoop(root, function (elt) {
6905 elt.style[prop] = valueToPut;
6906 });
6907 } else if (arrayIndex) {
6908 root[prop] = valueToPut;
6909 } else {
6910 runtime.implicitLoop(root, function (elt) {
6911 putInto(runtime, elt, prop, valueToPut);
6912 });
6913 }
6914 } else {
6915 var op =
6916 operation === "before"
6917 ? Element.prototype.before
6918 : operation === "after"
6919 ? Element.prototype.after
6920 : operation === "start"
6921 ? Element.prototype.prepend
6922 : operation === "end"
6923 ? Element.prototype.append
6924 : Element.prototype.append; // unreachable
6925
6926 runtime.implicitLoop(root, function (elt) {
6927 op.call(
6928 elt,
6929 valueToPut instanceof Node
6930 ? valueToPut
6931 : runtime.convertValue(valueToPut, "Fragment")
6932 );
6933 // process any new content
6934 if (elt.parentElement) {
6935 runtime.processNode(elt.parentElement);
6936 } else {
6937 runtime.processNode(elt);
6938 }
6939 });
6940 }
6941 }
6942 return runtime.findNext(this, context);
6943 },
6944 };
6945 return putCmd;
6946 }
6947 });
6948
6949 function parsePseudopossessiveTarget(parser, runtime, tokens) {
6950 var targets;
6951 if (
6952 tokens.matchToken("the") ||
6953 tokens.matchToken("element") ||
6954 tokens.matchToken("elements") ||
6955 tokens.currentToken().type === "CLASS_REF" ||
6956 tokens.currentToken().type === "ID_REF" ||
6957 (tokens.currentToken().op && tokens.currentToken().value === "<")
6958 ) {
6959 parser.possessivesDisabled = true;
6960 try {
6961 targets = parser.parseElement("expression", tokens);
6962 } finally {
6963 delete parser.possessivesDisabled;
6964 }
6965 // optional possessive
6966 if (tokens.matchOpToken("'")) {
6967 tokens.requireToken("s");
6968 }
6969 } else if (tokens.currentToken().type === "IDENTIFIER" && tokens.currentToken().value === "its") {
6970 var identifier = tokens.matchToken("its");
6971 targets = {
6972 type: "pseudopossessiveIts",
6973 token: identifier,
6974 name: identifier.value,
6975 evaluate: function (context) {
6976 return runtime.resolveSymbol("it", context);
6977 },
6978 };
6979 } else {
6980 tokens.matchToken("my") || tokens.matchToken("me"); // consume optional 'my'
6981 targets = parser.parseElement("implicitMeTarget", tokens);
6982 }
6983 return targets;
6984 }
6985
6986 parser.addCommand("transition", function (parser, runtime, tokens) {
6987 if (tokens.matchToken("transition")) {
6988 var targetsExpr = parsePseudopossessiveTarget(parser, runtime, tokens);
6989
6990 var properties = [];
6991 var from = [];
6992 var to = [];
6993 var currentToken = tokens.currentToken();
6994 while (
6995 !parser.commandBoundary(currentToken) &&
6996 currentToken.value !== "over" &&
6997 currentToken.value !== "using"
6998 ) {
6999 if (tokens.currentToken().type === "STYLE_REF") {
7000 let styleRef = tokens.consumeToken();
7001 let styleProp = styleRef.value.substr(1);
7002 properties.push({
7003 type: "styleRefValue",
7004 evaluate: function () {
7005 return styleProp;
7006 },
7007 });
7008 } else {
7009 properties.push(parser.requireElement("stringLike", tokens));
7010 }
7011
7012 if (tokens.matchToken("from")) {
7013 from.push(parser.requireElement("expression", tokens));
7014 } else {
7015 from.push(null);
7016 }
7017 tokens.requireToken("to");
7018 if (tokens.matchToken("initial")) {
7019 to.push({
7020 type: "initial_literal",
7021 evaluate : function(){
7022 return "initial";
7023 }
7024 });
7025 } else {
7026 to.push(parser.requireElement("expression", tokens));
7027 }
7028 currentToken = tokens.currentToken();
7029 }
7030 if (tokens.matchToken("over")) {
7031 var over = parser.requireElement("expression", tokens);
7032 } else if (tokens.matchToken("using")) {
7033 var using = parser.requireElement("expression", tokens);
7034 }
7035
7036 var transition = {
7037 to: to,
7038 args: [targetsExpr, properties, from, to, using, over],
7039 op: function (context, targets, properties, from, to, using, over) {
7040 runtime.nullCheck(targets, targetsExpr);
7041 var promises = [];
7042 runtime.implicitLoop(targets, function (target) {
7043 var promise = new Promise(function (resolve, reject) {
7044 var initialTransition = target.style.transition;
7045 if (over) {
7046 target.style.transition = "all " + over + "ms ease-in";
7047 } else if (using) {
7048 target.style.transition = using;
7049 } else {
7050 target.style.transition = config.defaultTransition;
7051 }
7052 var internalData = runtime.getInternalData(target);
7053 var computedStyles = getComputedStyle(target);
7054
7055 var initialStyles = {};
7056 for (var i = 0; i < computedStyles.length; i++) {
7057 var name = computedStyles[i];
7058 var initialValue = computedStyles[name];
7059 initialStyles[name] = initialValue;
7060 }
7061
7062 // store initial values
7063 if (!internalData.initialStyles) {
7064 internalData.initialStyles = initialStyles;
7065 }
7066
7067 for (var i = 0; i < properties.length; i++) {
7068 var property = properties[i];
7069 var fromVal = from[i];
7070 if (fromVal === "computed" || fromVal == null) {
7071 target.style[property] = initialStyles[property];
7072 } else {
7073 target.style[property] = fromVal;
7074 }
7075 }
7076 //console.log("transition started", transition);
7077
7078 var transitionStarted = false;
7079 var resolved = false;
7080
7081 target.addEventListener(
7082 "transitionend",
7083 function () {
7084 if (!resolved) {
7085 //console.log("transition ended", transition);
7086 target.style.transition = initialTransition;
7087 resolved = true;
7088 resolve();
7089 }
7090 },
7091 { once: true }
7092 );
7093
7094 target.addEventListener(
7095 "transitionstart",
7096 function () {
7097 transitionStarted = true;
7098 },
7099 { once: true }
7100 );
7101
7102 // it no transition has started in 100ms, continue
7103 setTimeout(function () {
7104 if (!resolved && !transitionStarted) {
7105 //console.log("transition ended", transition);
7106 target.style.transition = initialTransition;
7107 resolved = true;
7108 resolve();
7109 }
7110 }, 100);
7111
7112 setTimeout(function () {
7113 var autoProps = [];
7114 for (var i = 0; i < properties.length; i++) {
7115 var property = properties[i];
7116 var toVal = to[i];
7117 if (toVal === "initial") {
7118 var propertyValue = internalData.initialStyles[property];
7119 target.style[property] = propertyValue;
7120 } else {
7121 target.style[property] = toVal;
7122 }
7123 //console.log("set", property, "to", target.style[property], "on", target, "value passed in : ", toVal);
7124 }
7125 }, 0);
7126 });
7127 promises.push(promise);
7128 });
7129 return Promise.all(promises).then(function () {
7130 return runtime.findNext(transition, context);
7131 });
7132 },
7133 };
7134 return transition;
7135 }
7136 });
7137
7138 parser.addCommand("measure", function (parser, runtime, tokens) {
7139 if (!tokens.matchToken("measure")) return;
7140
7141 var targetExpr = parsePseudopossessiveTarget(parser, runtime, tokens);
7142
7143 var propsToMeasure = [];
7144 if (!parser.commandBoundary(tokens.currentToken()))
7145 do {
7146 propsToMeasure.push(tokens.matchTokenType("IDENTIFIER").value);
7147 } while (tokens.matchOpToken(","));
7148
7149 return {
7150 properties: propsToMeasure,
7151 args: [targetExpr],
7152 op: function (ctx, target) {
7153 runtime.nullCheck(target, targetExpr);
7154 if (0 in target) target = target[0]; // not measuring multiple elts
7155 var rect = target.getBoundingClientRect();
7156 var scroll = {
7157 top: target.scrollTop,
7158 left: target.scrollLeft,
7159 topMax: target.scrollTopMax,
7160 leftMax: target.scrollLeftMax,
7161 height: target.scrollHeight,
7162 width: target.scrollWidth,
7163 };
7164
7165 ctx.result = {
7166 x: rect.x,
7167 y: rect.y,
7168 left: rect.left,
7169 top: rect.top,
7170 right: rect.right,
7171 bottom: rect.bottom,
7172 width: rect.width,
7173 height: rect.height,
7174 bounds: rect,
7175
7176 scrollLeft: scroll.left,
7177 scrollTop: scroll.top,
7178 scrollLeftMax: scroll.leftMax,
7179 scrollTopMax: scroll.topMax,
7180 scrollWidth: scroll.width,
7181 scrollHeight: scroll.height,
7182 scroll: scroll,
7183 };
7184
7185 runtime.forEach(propsToMeasure, function (prop) {
7186 if (prop in ctx.result) ctx.locals[prop] = ctx.result[prop];
7187 else throw "No such measurement as " + prop;
7188 });
7189
7190 return runtime.findNext(this, ctx);
7191 },
7192 };
7193 });
7194
7195 parser.addLeafExpression("closestExpr", function (parser, runtime, tokens) {
7196 if (tokens.matchToken("closest")) {
7197 if (tokens.matchToken("parent")) {
7198 var parentSearch = true;
7199 }
7200
7201 var css = null;
7202 if (tokens.currentToken().type === "ATTRIBUTE_REF") {
7203 var attributeRef = parser.requireElement("attributeRefAccess", tokens, null);
7204 css = "[" + attributeRef.attribute.name + "]";
7205 }
7206
7207 if (css == null) {
7208 var expr = parser.requireElement("expression", tokens);
7209 if (expr.css == null) {
7210 parser.raiseParseError(tokens, "Expected a CSS expression");
7211 } else {
7212 css = expr.css;
7213 }
7214 }
7215
7216 if (tokens.matchToken("to")) {
7217 var to = parser.parseElement("expression", tokens);
7218 } else {
7219 var to = parser.parseElement("implicitMeTarget", tokens);
7220 }
7221
7222 var closestExpr = {
7223 type: "closestExpr",
7224 parentSearch: parentSearch,
7225 expr: expr,
7226 css: css,
7227 to: to,
7228 args: [to],
7229 op: function (ctx, to) {
7230 if (to == null) {
7231 return null;
7232 } else {
7233 let result = [];
7234 runtime.implicitLoop(to, function(to){
7235 if (parentSearch) {
7236 result.push(to.parentElement ? to.parentElement.closest(css) : null);
7237 } else {
7238 result.push(to.closest(css));
7239 }
7240 })
7241 if (runtime.shouldAutoIterate(to)) {
7242 return result;
7243 } else {
7244 return result[0];
7245 }
7246 }
7247 },
7248 evaluate: function (context) {
7249 return runtime.unifiedEval(this, context);
7250 },
7251 };
7252
7253 if (attributeRef) {
7254 attributeRef.root = closestExpr;
7255 attributeRef.args = [closestExpr];
7256 return attributeRef;
7257 } else {
7258 return closestExpr;
7259 }
7260 }
7261 });
7262
7263 parser.addCommand("go", function (parser, runtime, tokens) {
7264 if (tokens.matchToken("go")) {
7265 if (tokens.matchToken("back")) {
7266 var back = true;
7267 } else {
7268 tokens.matchToken("to");
7269 if (tokens.matchToken("url")) {
7270 var target = parser.requireElement("stringLike", tokens);
7271 var url = true;
7272 if (tokens.matchToken("in")) {
7273 tokens.requireToken("new");
7274 tokens.requireToken("window");
7275 var newWindow = true;
7276 }
7277 } else {
7278 tokens.matchToken("the"); // optional the
7279 var verticalPosition = tokens.matchAnyToken("top", "middle", "bottom");
7280 var horizontalPosition = tokens.matchAnyToken("left", "center", "right");
7281 if (verticalPosition || horizontalPosition) {
7282 tokens.requireToken("of");
7283 }
7284 var target = parser.requireElement("unaryExpression", tokens);
7285
7286 var plusOrMinus = tokens.matchAnyOpToken("+", "-");
7287 if (plusOrMinus) {
7288 tokens.pushFollow("px");
7289 try {
7290 var offset = parser.requireElement("expression", tokens);
7291 } finally {
7292 tokens.popFollow();
7293 }
7294 }
7295 tokens.matchToken("px"); // optional px
7296
7297 var smoothness = tokens.matchAnyToken("smoothly", "instantly");
7298
7299 var scrollOptions = {
7300 block: "start",
7301 inline: "nearest"
7302 };
7303
7304 if (verticalPosition) {
7305 if (verticalPosition.value === "top") {
7306 scrollOptions.block = "start";
7307 } else if (verticalPosition.value === "bottom") {
7308 scrollOptions.block = "end";
7309 } else if (verticalPosition.value === "middle") {
7310 scrollOptions.block = "center";
7311 }
7312 }
7313
7314 if (horizontalPosition) {
7315 if (horizontalPosition.value === "left") {
7316 scrollOptions.inline = "start";
7317 } else if (horizontalPosition.value === "center") {
7318 scrollOptions.inline = "center";
7319 } else if (horizontalPosition.value === "right") {
7320 scrollOptions.inline = "end";
7321 }
7322 }
7323
7324 if (smoothness) {
7325 if (smoothness.value === "smoothly") {
7326 scrollOptions.behavior = "smooth";
7327 } else if (smoothness.value === "instantly") {
7328 scrollOptions.behavior = "instant";
7329 }
7330 }
7331 }
7332 }
7333
7334 var goCmd = {
7335 target: target,
7336 args: [target, offset],
7337 op: function (ctx, to, offset) {
7338 if (back) {
7339 window.history.back();
7340 } else if (url) {
7341 if (to) {
7342 if (newWindow) {
7343 window.open(to);
7344 } else {
7345 window.location.href = to;
7346 }
7347 }
7348 } else {
7349 runtime.implicitLoop(to, function (target) {
7350
7351 if (target === window) {
7352 target = document.body;
7353 }
7354
7355 if(plusOrMinus) {
7356 // a scroll w/ an offset of some sort
7357 let boundingRect = target.getBoundingClientRect();
7358
7359 let scrollShim = document.createElement("div");
7360
7361 let actualOffset = plusOrMinus.value === "+" ? offset : offset * -1;
7362
7363 let offsetX = scrollOptions.inline == "start" || scrollOptions.inline == "end" ? actualOffset : 0;
7364
7365 let offsetY = scrollOptions.block == "start" || scrollOptions.block == "end" ? actualOffset : 0;
7366
7367 scrollShim.style.position = "absolute";
7368 scrollShim.style.top = (boundingRect.top + window.scrollY + offsetY) + "px";
7369 scrollShim.style.left = (boundingRect.left + window.scrollX + offsetX) + "px";
7370 scrollShim.style.height = boundingRect.height + "px";
7371 scrollShim.style.width = boundingRect.width + "px";
7372 scrollShim.style.zIndex = "" + Number.MIN_SAFE_INTEGER;
7373 scrollShim.style.opacity = "0";
7374
7375 document.body.appendChild(scrollShim);
7376 setTimeout(function () {
7377 document.body.removeChild(scrollShim);
7378 }, 100);
7379
7380 target = scrollShim;
7381 }
7382
7383 target.scrollIntoView(scrollOptions);
7384 });
7385 }
7386 return runtime.findNext(goCmd, ctx);
7387 },
7388 };
7389 return goCmd;
7390 }
7391 });
7392
7393 config.conversions.dynamicResolvers.push(function (str, node) {
7394 if (!(str === "Values" || str.indexOf("Values:") === 0)) {
7395 return;
7396 }
7397 var conversion = str.split(":")[1];
7398 /** @type Object<string,string | string[]> */
7399 var result = {};
7400
7401 var implicitLoop = parser.runtime.implicitLoop.bind(parser.runtime);
7402
7403 implicitLoop(node, function (/** @type HTMLInputElement */ node) {
7404 // Try to get a value directly from this node
7405 var input = getInputInfo(node);
7406
7407 if (input !== undefined) {
7408 result[input.name] = input.value;
7409 return;
7410 }
7411
7412 // Otherwise, try to query all child elements of this node that *should* contain values.
7413 if (node.querySelectorAll != undefined) {
7414 /** @type {NodeListOf<HTMLInputElement>} */
7415 var children = node.querySelectorAll("input,select,textarea");
7416 children.forEach(appendValue);
7417 }
7418 });
7419
7420 if (conversion) {
7421 if (conversion === "JSON") {
7422 return JSON.stringify(result);
7423 } else if (conversion === "Form") {
7424 /** @ts-ignore */
7425 // TODO: does this work with multiple inputs of the same name?
7426 return new URLSearchParams(result).toString();
7427 } else {
7428 throw "Unknown conversion: " + conversion;
7429 }
7430 } else {
7431 return result;
7432 }
7433
7434 /**
7435 * @param {HTMLInputElement} node
7436 */
7437 function appendValue(node) {
7438 var info = getInputInfo(node);
7439
7440 if (info == undefined) {
7441 return;
7442 }
7443
7444 // If there is no value already stored in this space.
7445 if (result[info.name] == undefined) {
7446 result[info.name] = info.value;
7447 return;
7448 }
7449
7450 if (Array.isArray(result[info.name]) && Array.isArray(info.value)) {
7451 result[info.name] = [].concat(result[info.name], info.value);
7452 return;
7453 }
7454 }
7455
7456 /**
7457 * @param {HTMLInputElement} node
7458 * @returns {{name:string, value:string | string[]} | undefined}
7459 */
7460 function getInputInfo(node) {
7461 try {
7462 /** @type {{name: string, value: string | string[]}}*/
7463 var result = {
7464 name: node.name,
7465 value: node.value,
7466 };
7467
7468 if (result.name == undefined || result.value == undefined) {
7469 return undefined;
7470 }
7471
7472 if (node.type == "radio" && node.checked == false) {
7473 return undefined;
7474 }
7475
7476 if (node.type == "checkbox") {
7477 if (node.checked == false) {
7478 result.value = undefined;
7479 } else if (typeof result.value === "string") {
7480 result.value = [result.value];
7481 }
7482 }
7483
7484 if (node.type == "select-multiple") {
7485 /** @type {NodeListOf<HTMLSelectElement>} */
7486 var selected = node.querySelectorAll("option[selected]");
7487
7488 result.value = [];
7489 for (var index = 0; index < selected.length; index++) {
7490 result.value.push(selected[index].value);
7491 }
7492 }
7493 return result;
7494 } catch (e) {
7495 return undefined;
7496 }
7497 }
7498 });
7499
7500 config.conversions["HTML"] = function (value) {
7501 var toHTML = /** @returns {string}*/ function (/** @type any*/ value) {
7502 if (value instanceof Array) {
7503 return value
7504 .map(function (item) {
7505 return toHTML(item);
7506 })
7507 .join("");
7508 }
7509
7510 if (value instanceof HTMLElement) {
7511 return value.outerHTML;
7512 }
7513
7514 if (value instanceof NodeList) {
7515 var result = "";
7516 for (var i = 0; i < value.length; i++) {
7517 var node = value[i];
7518 if (node instanceof HTMLElement) {
7519 result += node.outerHTML;
7520 }
7521 }
7522 return result;
7523 }
7524
7525 if (value.toString) {
7526 return value.toString();
7527 }
7528
7529 return "";
7530 };
7531
7532 return toHTML(value);
7533 };
7534
7535 config.conversions["Fragment"] = function (val) {
7536 var frag = document.createDocumentFragment();
7537 parser.runtime.implicitLoop(val, function (val) {
7538 if (val instanceof Node) frag.append(val);
7539 else {
7540 var temp = document.createElement("template");
7541 temp.innerHTML = val;
7542 frag.append(temp.content);
7543 }
7544 });
7545 return frag;
7546 };
7547 }
7548
7549
7550 // Public API
7551
7552 const runtime_ = new Runtime(), lexer_ = runtime_.lexer, parser_ = runtime_.parser
7553
7554 /**
7555 *
7556 * @param {string} src
7557 * @param {Partial<Context>} [ctx]
7558 */
7559 function run(src, ctx) {
7560 return runtime_.evaluate(src, ctx)
7561 }
7562
7563 function browserInit() {
7564 /** @type {HTMLScriptElement[]} */
7565 var scripts = Array.from(globalScope.document.querySelectorAll("script[type='text/hyperscript'][src]"))
7566 Promise.all(
7567 scripts.map(function (script) {
7568 return fetch(script.src)
7569 .then(function (res) {
7570 return res.text();
7571 });
7572 })
7573 )
7574 .then(script_values => script_values.forEach(sc => _hyperscript(sc)))
7575 .then(() => ready(function () {
7576 mergeMetaConfig();
7577 runtime_.processNode(document.documentElement);
7578 globalScope.document.addEventListener("htmx:load", function (/** @type {CustomEvent} */ evt) {
7579 runtime_.processNode(evt.detail.elt);
7580 });
7581 }));
7582
7583 function ready(fn) {
7584 if (document.readyState !== "loading") {
7585 setTimeout(fn);
7586 } else {
7587 document.addEventListener("DOMContentLoaded", fn);
7588 }
7589 }
7590
7591 function getMetaConfig() {
7592 /** @type {HTMLMetaElement} */
7593 var element = document.querySelector('meta[name="htmx-config"]');
7594 if (element) {
7595 return parseJSON(element.content);
7596 } else {
7597 return null;
7598 }
7599 }
7600
7601 function mergeMetaConfig() {
7602 var metaConfig = getMetaConfig();
7603 if (metaConfig) {
7604 Object.assign(config, metaConfig);
7605 }
7606 }
7607 }
7608
7609 /**
7610 * @typedef {Object} HyperscriptAPI
7611 *
7612 * @property {Object} config
7613 * @property {string} config.attributes
7614 * @property {string} config.defaultTransition
7615 * @property {string} config.disableSelector
7616 * @property {typeof conversions} config.conversions
7617 *
7618 * @property {Object} internals
7619 * @property {Lexer} internals.lexer
7620 * @property {typeof Lexer} internals.Lexer
7621 * @property {Parser} internals.parser
7622 * @property {typeof Parser} internals.Parser
7623 * @property {Runtime} internals.runtime
7624 * @property {typeof Runtime} internals.Runtime
7625 *
7626 * @property {typeof ElementCollection} ElementCollection
7627 *
7628 * @property {(keyword: string, definition: ParseRule) => void} addFeature
7629 * @property {(keyword: string, definition: ParseRule) => void} addCommand
7630 * @property {(keyword: string, definition: ParseRule) => void} addLeafExpression
7631 * @property {(keyword: string, definition: ParseRule) => void} addIndirectExpression
7632 *
7633 * @property {(src: string, ctx?: Partial<Context>) => any} evaluate
7634 * @property {(src: string) => ASTNode} parse
7635 * @property {(node: Element) => void} processNode
7636 *
7637 * @property {() => void} browserInit
7638 *
7639 *
7640 * @typedef {HyperscriptAPI & ((src: string, ctx?: Partial<Context>) => any)} Hyperscript
7641 */
7642
7643 /**
7644 * @type {Hyperscript}
7645 */
7646 const _hyperscript = Object.assign(
7647 run,
7648 {
7649 config,
7650
7651 use(plugin) { plugin(_hyperscript) },
7652
7653 internals: {
7654 lexer: lexer_, parser: parser_, runtime: runtime_,
7655 Lexer, Tokens, Parser, Runtime,
7656 },
7657 ElementCollection,
7658
7659 addFeature: parser_.addFeature.bind(parser_),
7660 addCommand: parser_.addCommand.bind(parser_),
7661 addLeafExpression: parser_.addLeafExpression.bind(parser_),
7662 addIndirectExpression: parser_.addIndirectExpression.bind(parser_),
7663
7664 evaluate: runtime_.evaluate.bind(runtime_),
7665 parse: runtime_.parse.bind(runtime_),
7666 processNode: runtime_.processNode.bind(runtime_),
7667 version: "0.9.12",
7668 browserInit,
7669 }
7670 )
7671
7672 return _hyperscript
7673})