UNPKG

208 kBJavaScriptView Raw
1///=========================================================================
2/// This module provides the core runtime and grammar for hyperscript
3///=========================================================================
4//AMD insanity
5(function (root, factory) {
6 if (typeof define === 'function' && define.amd) {
7 // AMD. Register as an anonymous module.
8 define([], factory);
9 } else {
10 // Browser globals
11 root._hyperscript = factory();
12 }
13}(typeof self !== 'undefined' ? self : this, function () {
14 return (function () {
15 'use strict';
16
17 //====================================================================
18 // Utilities
19 //====================================================================
20
21 /**
22 * mergeObjects combines the keys from obj2 into obj2, then returns obj1
23 *
24 * @param {object} obj1
25 * @param {object} obj2
26 * @returns object
27 */
28 function mergeObjects(obj1, obj2) {
29 for (var key in obj2) {
30 if (obj2.hasOwnProperty(key)) {
31 obj1[key] = obj2[key];
32 }
33 }
34 return obj1;
35 }
36
37 /**
38 * parseJSON parses a JSON string into a corresponding value. If the
39 * value passed in is not valid JSON, then it logs an error and returns `null`.
40 *
41 * @param {string} jString
42 * @returns any
43 */
44 function parseJSON(jString) {
45 try {
46 return JSON.parse(jString);
47 } catch(error) {
48 logError(error);
49 return null;
50 }
51 }
52
53 /**
54 * logError writes an error message to the Javascript console. It can take any
55 * value, but msg should commonly be a simple string.
56 * @param {*} msg
57 */
58 function logError(msg) {
59 if(console.error) {
60 console.error(msg);
61 } else if (console.log) {
62 console.log("ERROR: ", msg);
63 }
64 }
65
66 // TODO: JSDoc description of what's happening here
67 function varargConstructor(Cls, args) {
68 return new (Cls.bind.apply(Cls, [Cls].concat(args)));
69 }
70
71 var globalScope = (typeof self !== 'undefined') ? self : ((typeof global !== 'undefined') ? global : this);
72
73 //====================================================================
74 // Lexer
75 //====================================================================
76 var _lexer = function () {
77 var OP_TABLE = {
78 '+': 'PLUS',
79 '-': 'MINUS',
80 '*': 'MULTIPLY',
81 '/': 'DIVIDE',
82 '.': 'PERIOD',
83 '\\': 'BACKSLASH',
84 ':': 'COLON',
85 '%': 'PERCENT',
86 '|': 'PIPE',
87 '!': 'EXCLAMATION',
88 '?': 'QUESTION',
89 '#': 'POUND',
90 '&': 'AMPERSAND',
91 '$': 'DOLLAR',
92 ';': 'SEMI',
93 ',': 'COMMA',
94 '(': 'L_PAREN',
95 ')': 'R_PAREN',
96 '<': 'L_ANG',
97 '>': 'R_ANG',
98 '<=': 'LTE_ANG',
99 '>=': 'GTE_ANG',
100 '==': 'EQ',
101 '===': 'EQQ',
102 '!=': 'NEQ',
103 '!==': 'NEQQ',
104 '{': 'L_BRACE',
105 '}': 'R_BRACE',
106 '[': 'L_BRACKET',
107 ']': 'R_BRACKET',
108 '=': 'EQUALS'
109 };
110
111 /**
112 * isValidCSSClassChar returns `true` if the provided character is valid in a CSS class.
113 * @param {string} c
114 * @returns boolean
115 */
116 function isValidCSSClassChar(c) {
117 return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
118 }
119
120 /**
121 * isValidCSSIDChar returns `true` if the provided character is valid in a CSS ID
122 * @param {string} c
123 * @returns boolean
124 */
125 function isValidCSSIDChar(c) {
126 return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
127 }
128
129 /**
130 * isWhitespace returns `true` if the provided character is whitespace.
131 * @param {string} c
132 * @returns boolean
133 */
134 function isWhitespace(c) {
135 return c === " " || c === "\t" || isNewline(c);
136 }
137
138 /**
139 * positionString returns a string representation of a Token's line and column details.
140 * @param {Token} token
141 * @returns string
142 */
143 function positionString(token) {
144 return "[Line: " + token.line + ", Column: " + token.col + "]"
145 }
146
147 /**
148 * isNewline returns `true` if the provided character is a carrage return or newline
149 * @param {string} c
150 * @returns boolean
151 */
152 function isNewline(c) {
153 return c === '\r' || c === '\n';
154 }
155
156 /**
157 * isNumeric returns `true` if the provided character is a number (0-9)
158 * @param {string} c
159 * @returns boolean
160 */
161 function isNumeric(c) {
162 return c >= '0' && c <= '9';
163 }
164
165 /**
166 * isAlpha returns `true` if the provided character is a letter in the alphabet
167 * @param {string} c
168 * @returns boolean
169 */
170 function isAlpha(c) {
171 return (c >= 'a' && c <= 'z') ||
172 (c >= 'A' && c <= 'Z');
173 }
174
175 /**
176 * @param {string} c
177 * @param {boolean} [dollarIsOp]
178 * @returns boolean
179 */
180 function isIdentifierChar(c, dollarIsOp) {
181 return (c === "_" || (!dollarIsOp && c === "$"));
182 }
183
184 /**
185 * @param {string} c
186 * @returns boolean
187 */
188 function isReservedChar(c) {
189 return (c === "`" || c === "^");
190 }
191
192 /**
193 * @param {Token[]} tokens
194 * @param {Token[]} consumed
195 * @param {*} source
196 * @returns
197 */
198 function makeTokensObject(tokens, consumed, source) {
199
200 consumeWhitespace(); // consume initial whitespace
201 var _lastConsumed = null;
202
203 function consumeWhitespace(){
204 while(token(0, true).type === "WHITESPACE") {
205 consumed.push(tokens.shift());
206 }
207 }
208
209 /**
210 * @param {Token[]} tokens
211 * @param {*} error
212 */
213 function raiseError(tokens, error) {
214 _parser.raiseParseError(tokens, error);
215 }
216
217 /**
218 * @param {string} value
219 * @returns [Token]
220 */
221 function requireOpToken(value) {
222 var token = matchOpToken(value);
223 if (token) {
224 return token;
225 } else {
226 raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
227 }
228 }
229
230 /**
231 * @param {string} op1
232 * @param {string} op2
233 * @param {string} op3
234 * @returns [Token]
235 */
236 function matchAnyOpToken(op1, op2, op3) {
237 for (var i = 0; i < arguments.length; i++) {
238 var opToken = arguments[i];
239 var match = matchOpToken(opToken);
240 if (match) {
241 return match;
242 }
243 }
244 }
245
246 /**
247 * @param {string} value
248 * @returns [Token]
249 */
250 function matchAnyToken(op1, op2, op3) {
251 for (var i = 0; i < arguments.length; i++) {
252 var opToken = arguments[i];
253 var match = matchToken(opToken);
254 if (match) {
255 return match;
256 }
257 }
258 }
259
260 /**
261 * @param {string} value
262 * @returns [Token]
263 */
264 function matchOpToken(value) {
265 if (currentToken() && currentToken().op && currentToken().value === value) {
266 return consumeToken();
267 }
268 }
269
270 /**
271 * @param {string} type1
272 * @param {string} type2
273 * @param {string} type3
274 * @param {string} type4
275 * @returns Token
276 */
277 function requireTokenType(type1, type2, type3, type4) {
278 var token = matchTokenType(type1, type2, type3, type4);
279 if (token) {
280 return token;
281 } else {
282 raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3]));
283 }
284 }
285
286 /**
287 * @param {string} type1
288 * @param {string} type2
289 * @param {string} type3
290 * @param {string} type4
291 * @returns [Token]
292 */
293 function matchTokenType(type1, type2, type3, type4) {
294 if (currentToken() && currentToken().type && [type1, type2, type3, type4].indexOf(currentToken().type) >= 0) {
295 return consumeToken();
296 }
297 }
298
299 /**
300 * @param {string} value
301 * @param {string} type
302 * @returns Token
303 */
304 function requireToken(value, type) {
305 var token = matchToken(value, type);
306 if (token) {
307 return token;
308 } else {
309 raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
310 }
311 }
312
313 /**
314 * @param {string} value
315 * @param {string} type
316 * @returns [Token]
317 */
318 function matchToken(value, type) {
319 var type = type || "IDENTIFIER";
320 if (currentToken() && currentToken().value === value && currentToken().type === type) {
321 return consumeToken();
322 }
323 }
324
325 /**
326 * @returns Token
327 */
328 function consumeToken() {
329 var match = tokens.shift();
330 consumed.push(match);
331 _lastConsumed = match;
332 consumeWhitespace(); // consume any whitespace
333 return match;
334 }
335
336 /**
337 * @param {string} value
338 * @param {string} type
339 * @returns Token[]
340 */
341 function consumeUntil(value, type) {
342
343 /** @type Token[] */
344 var tokenList = [];
345 var currentToken = token(0, true);
346
347 while ((type == null || currentToken.type !== type) &&
348 (value == null || currentToken.value !== value) &&
349 currentToken.type !== "EOF") {
350 var match = tokens.shift();
351 consumed.push(match);
352 tokenList.push(currentToken);
353 currentToken = token(0, true);
354 }
355 consumeWhitespace(); // consume any whitespace
356 return tokenList;
357 }
358
359 /**
360 * @returns string
361 */
362 function lastWhitespace() {
363 if (consumed[consumed.length - 1] && consumed[consumed.length - 1].type === "WHITESPACE") {
364 return consumed[consumed.length - 1].value;
365 } else {
366 return "";
367 }
368 }
369
370 function consumeUntilWhitespace() {
371 return consumeUntil(null, "WHITESPACE");
372 }
373
374 /**
375 * @returns boolean
376 */
377 function hasMore() {
378 return tokens.length > 0;
379 }
380
381 /**
382 * @param {number} n
383 * @param {boolean} [dontIgnoreWhitespace]
384 * @returns Token
385 */
386 function token(n, dontIgnoreWhitespace) {
387
388 /** @type {Token} */
389 var token;
390 var i = 0;
391 do {
392 if (!dontIgnoreWhitespace) {
393 while (tokens[i] && tokens[i].type === "WHITESPACE") {
394 i++;
395 }
396 }
397 token = tokens[i];
398 n--;
399 i++;
400 } while (n > -1)
401 if (token) {
402 return token;
403 } else {
404 return {
405 type:"EOF",
406 value:"<<<EOF>>>"
407 }
408 }
409 }
410
411 /**
412 * @returns Token
413 */
414 function currentToken() {
415 return token(0);
416 }
417
418 function lastMatch() {
419 return _lastConsumed;
420 }
421
422 function sourceFor() {
423 return source.substring(this.startToken.start, this.endToken.end);
424 }
425
426 function lineFor() {
427 return source
428 .split("\n")[this.startToken.line - 1];
429 }
430
431 return {
432 matchAnyToken: matchAnyToken,
433 matchAnyOpToken: matchAnyOpToken,
434 matchOpToken: matchOpToken,
435 requireOpToken: requireOpToken,
436 matchTokenType: matchTokenType,
437 requireTokenType: requireTokenType,
438 consumeToken: consumeToken,
439 matchToken: matchToken,
440 requireToken: requireToken,
441 list: tokens,
442 consumed: consumed,
443 source: source,
444 hasMore: hasMore,
445 currentToken: currentToken,
446 lastMatch: lastMatch,
447 token: token,
448 consumeUntil: consumeUntil,
449 consumeUntilWhitespace: consumeUntilWhitespace,
450 lastWhitespace: lastWhitespace,
451 sourceFor: sourceFor,
452 lineFor: lineFor
453 }
454 }
455
456 /**
457 * @param {string} string
458 * @param {boolean} [noDollarStart]
459 * @returns TokensObject
460 */
461 function tokenize(string, noDollarStart) {
462 /** @type Token[]*/
463 var tokens = [];
464 var source = string;
465 var position = 0;
466 var column = 0;
467 var line = 1;
468 var lastToken = "<START>";
469
470 while (position < source.length) {
471 if (currentChar() === "-" && nextChar() === "-") {
472 consumeComment();
473 } else {
474 if (isWhitespace(currentChar())) {
475 tokens.push(consumeWhitespace());
476 } else if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
477 tokens.push(consumeClassReference());
478 } else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
479 tokens.push(consumeIdReference());
480 } else if (isAlpha(currentChar()) || isIdentifierChar(currentChar(), noDollarStart)) {
481 tokens.push(consumeIdentifier());
482 } else if (isNumeric(currentChar())) {
483 tokens.push(consumeNumber());
484 } else if (currentChar() === '"' || currentChar() === "'" || currentChar() === "`") {
485 tokens.push(consumeString());
486 } else if (OP_TABLE[currentChar()]) {
487 tokens.push(consumeOp());
488 } else if (isReservedChar(currentChar())) {
489 tokens.push(makeToken('RESERVED', currentChar()))
490 } else {
491 if (position < source.length) {
492 throw Error("Unknown token: " + currentChar() + " ");
493 }
494 }
495 }
496 }
497
498 return makeTokensObject(tokens, [], source);
499
500 /**
501 * @param {string} type
502 * @param {string} [value]
503 * @returns Token
504 */
505 function makeOpToken(type, value) {
506 var token = makeToken(type, value);
507 token.op = true;
508 return token;
509 }
510
511 /**
512 * @param {string} type
513 * @param {string} [value]
514 * @returns Token
515 */
516 function makeToken(type, value) {
517 return {
518 type: type,
519 value: value,
520 start: position,
521 end: position + 1,
522 column: column,
523 line: line
524 };
525 }
526
527 function consumeComment() {
528 while (currentChar() && !isNewline(currentChar())) {
529 consumeChar();
530 }
531 consumeChar();
532 }
533
534 /**
535 * @returns Token
536 */
537 function consumeClassReference() {
538 var classRef = makeToken("CLASS_REF");
539 var value = consumeChar();
540 while (isValidCSSClassChar(currentChar())) {
541 value += consumeChar();
542 }
543 classRef.value = value;
544 classRef.end = position;
545 return classRef;
546 }
547
548 /**
549 * @returns Token
550 */
551 function consumeIdReference() {
552 var idRef = makeToken("ID_REF");
553 var value = consumeChar();
554 while (isValidCSSIDChar(currentChar())) {
555 value += consumeChar();
556 }
557 idRef.value = value;
558 idRef.end = position;
559 return idRef;
560 }
561
562 /**
563 * @returns Token
564 */
565 function consumeIdentifier() {
566 var identifier = makeToken("IDENTIFIER");
567 var value = consumeChar();
568 while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
569 value += consumeChar();
570 }
571 identifier.value = value;
572 identifier.end = position;
573 return identifier;
574 }
575
576 /**
577 * @returns Token
578 */
579 function consumeNumber() {
580 var number = makeToken("NUMBER");
581 var value = consumeChar();
582 while (isNumeric(currentChar())) {
583 value += consumeChar();
584 }
585 if (currentChar() === ".") {
586 value += consumeChar();
587 }
588 while (isNumeric(currentChar())) {
589 value += consumeChar();
590 }
591 number.value = value;
592 number.end = position;
593 return number;
594 }
595
596 /**
597 * @returns Token
598 */
599 function consumeOp() {
600 var op = makeOpToken();
601 var value = consumeChar(); // consume leading char
602 while (currentChar() && OP_TABLE[value + currentChar()]) {
603 value += consumeChar();
604 }
605 op.type = OP_TABLE[value];
606 op.value = value;
607 op.end = position;
608 return op;
609 }
610
611 /**
612 * @returns Token
613 */
614 function consumeString() {
615 var string = makeToken("STRING");
616 var startChar = consumeChar(); // consume leading quote
617 var value = "";
618 while (currentChar() && currentChar() !== startChar) {
619 if (currentChar() === "\\") {
620 consumeChar(); // consume escape char and move on
621 }
622 value += consumeChar();
623 }
624 if (currentChar() !== startChar) {
625 throw Error("Unterminated string at " + positionString(string));
626 } else {
627 consumeChar(); // consume final quote
628 }
629 string.value = value;
630 string.end = position;
631 string.template = startChar === "`";
632 return string;
633 }
634
635 /**
636 * @returns string
637 */
638 function currentChar() {
639 return source.charAt(position);
640 }
641
642 /**
643 * @returns string
644 */
645 function nextChar() {
646 return source.charAt(position + 1);
647 }
648
649 /**
650 * @returns string
651 */
652 function consumeChar() {
653 lastToken = currentChar();
654 position++;
655 column++;
656 return lastToken;
657 }
658
659 /**
660 * @returns boolean
661 */
662 function possiblePrecedingSymbol() {
663 return isAlpha(lastToken) || isNumeric(lastToken) || lastToken === ")" || lastToken === "}" || lastToken === "]"
664 }
665
666 /**
667 * @returns Token
668 */
669 function consumeWhitespace() {
670 var whitespace = makeToken("WHITESPACE");
671 var value = "";
672 while (currentChar() && isWhitespace(currentChar())) {
673 if (isNewline(currentChar())) {
674 column = 0;
675 line++;
676 }
677 value += consumeChar();
678 }
679 whitespace.value = value;
680 whitespace.end = position;
681 return whitespace;
682 }
683 }
684
685 return {
686 tokenize: tokenize,
687 makeTokensObject: makeTokensObject
688 }
689 }();
690
691 //====================================================================
692 // Parser
693 //====================================================================
694 var _parser = function () {
695
696 var GRAMMAR = {}
697 var COMMANDS = {}
698 var FEATURES = {}
699 var LEAF_EXPRESSIONS = [];
700 var INDIRECT_EXPRESSIONS = [];
701
702 /**
703 * @param {string} type
704 * @param {TokensObject} tokens
705 * @param {*} root
706 * @returns
707 */
708 function initElt(parseElement, start, tokens) {
709 parseElement.startToken = start;
710 parseElement.sourceFor = tokens.sourceFor;
711 parseElement.lineFor = tokens.lineFor;
712 parseElement.programSource = tokens.source;
713 }
714
715
716 function parseElement(type, tokens, root) {
717 var elementDefinition = GRAMMAR[type];
718 if (elementDefinition) {
719 var start = tokens.currentToken();
720 var parseElement = elementDefinition(_parser, _runtime, tokens, root);
721 if (parseElement) {
722 initElt(parseElement, start, tokens);
723 parseElement.endToken = parseElement.endToken || tokens.lastMatch()
724 var root = parseElement.root;
725 while (root != null) {
726 initElt(root, start, tokens);
727 root = root.root;
728 }
729 }
730 return parseElement;
731 }
732 }
733
734 /**
735 * @param {string} type
736 * @param {TokensObject} tokens
737 * @param {string} [message]
738 * @param {*} [root]
739 * @returns
740 */
741 function requireElement(type, tokens, message, root) {
742 var result = parseElement(type, tokens, root);
743 return result || raiseParseError(tokens, message || "Expected " + type);
744 }
745
746 /**
747 * @param {string[]} types
748 * @param {TokensObject} tokens
749 * @returns
750 */
751 function parseAnyOf(types, tokens) {
752 for (var i = 0; i < types.length; i++) {
753 var type = types[i];
754 var expression = parseElement(type, tokens);
755 if (expression) {
756 return expression;
757 }
758 }
759 }
760
761 /**
762 * @param {string} name
763 * @param {GrammarDefinition} definition
764 */
765 function addGrammarElement(name, definition) {
766 GRAMMAR[name] = definition;
767 }
768
769 /**
770 * @param {string} keyword
771 * @param {CommandDefinition} definition
772 */
773 function addCommand(keyword, definition) {
774 var commandGrammarType = keyword + "Command";
775 var commandDefinitionWrapper = function (parser, runtime, tokens) {
776 var commandElement = definition(parser, runtime, tokens);
777 if (commandElement) {
778 commandElement.type = commandGrammarType;
779 commandElement.execute = function (context) {
780 context.meta.command = commandElement;
781 return runtime.unifiedExec(this, context);
782 }
783 return commandElement;
784 }
785 };
786 GRAMMAR[commandGrammarType] = commandDefinitionWrapper;
787 COMMANDS[keyword] = commandDefinitionWrapper;
788 }
789
790 /**
791 * @param {string} keyword
792 * @param {FeatureDefinition} definition
793 */
794 function addFeature(keyword, definition) {
795 var featureGrammarType = keyword + "Feature";
796
797 /** @type FeatureDefinition*/
798 var featureDefinitionWrapper = function (parser, runtime, tokens) {
799 var featureElement = definition(parser, runtime, tokens);
800 if (featureElement) {
801 featureElement.keyword = keyword;
802 featureElement.type = featureGrammarType;
803 return featureElement;
804 }
805 };
806 GRAMMAR[featureGrammarType] = featureDefinitionWrapper;
807 FEATURES[keyword] = featureDefinitionWrapper;
808 }
809
810 /**
811 * @param {string} name
812 * @param {ExpressionDefinition} definition
813 */
814 function addLeafExpression(name, definition) {
815 LEAF_EXPRESSIONS.push(name);
816 addGrammarElement(name, definition);
817 }
818
819 /**
820 * @param {string} name
821 * @param {ExpressionDefinition} definition
822 */
823 function addIndirectExpression(name, definition) {
824 INDIRECT_EXPRESSIONS.push(name);
825 addGrammarElement(name, definition);
826 }
827
828 /* ============================================================================================ */
829 /* Core hyperscript Grammar Elements */
830 /* ============================================================================================ */
831 addGrammarElement("feature", function(parser, runtime, tokens) {
832 if (tokens.matchOpToken("(")) {
833 var featureDefinition = parser.requireElement("feature", tokens);
834 tokens.requireOpToken(")");
835 return featureDefinition;
836 } else {
837 var featureDefinition = FEATURES[tokens.currentToken().value];
838 if (featureDefinition) {
839 return featureDefinition(parser, runtime, tokens);
840 }
841 }
842 })
843
844 addGrammarElement("command", function(parser, runtime, tokens) {
845 if (tokens.matchOpToken("(")) {
846 var commandDefinition = parser.requireElement("command", tokens);
847 tokens.requireOpToken(")");
848 return commandDefinition;
849 } else {
850 var commandDefinition = COMMANDS[tokens.currentToken().value];
851 if (commandDefinition) {
852 return commandDefinition(parser, runtime, tokens);
853 } else if (tokens.currentToken().type === "IDENTIFIER" && tokens.token(1).value === "(") {
854 return parser.requireElement("pseudoCommand", tokens);
855 }
856 }
857 })
858
859 addGrammarElement("commandList", function(parser, runtime, tokens) {
860 var cmd = parser.parseElement("command", tokens);
861 if (cmd) {
862 tokens.matchToken("then");
863 cmd.next = parser.parseElement("commandList", tokens);
864 return cmd;
865 }
866 })
867
868 addGrammarElement("leaf", function(parser, runtime, tokens) {
869 var result = parseAnyOf(LEAF_EXPRESSIONS, tokens);
870 // symbol is last so it doesn't consume any constants
871 if (result == null) {
872 return parseElement('symbol', tokens);
873 } else {
874 return result;
875 }
876 })
877
878 addGrammarElement("indirectExpression", function(parser, runtime, tokens, root) {
879 for (var i = 0; i < INDIRECT_EXPRESSIONS.length; i++) {
880 var indirect = INDIRECT_EXPRESSIONS[i];
881 root.endToken = tokens.lastMatch();
882 var result = parser.parseElement(indirect, tokens, root);
883 if(result){
884 return result;
885 }
886 }
887 return root;
888 });
889
890 addGrammarElement("primaryExpression", function(parser, runtime, tokens) {
891 var leaf = parser.parseElement("leaf", tokens);
892 if (leaf) {
893 return parser.parseElement("indirectExpression", tokens, leaf);
894 }
895 parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value);
896 });
897
898 /* ============================================================================================ */
899 /* END Core hyperscript Grammar Elements */
900 /* ============================================================================================ */
901
902 /**
903 *
904 * @param {TokensObject} tokens
905 * @returns string
906 */
907 function createParserContext(tokens) {
908 var currentToken = tokens.currentToken();
909 var source = tokens.source;
910 var lines = source.split("\n");
911 var line = currentToken ? currentToken.line - 1 : lines.length - 1;
912 var contextLine = lines[line];
913 var offset = currentToken ? currentToken.column : contextLine.length - 1;
914 return contextLine + "\n" + " ".repeat(offset) + "^^\n\n";
915 }
916
917 /**
918 * @param {*} tokens
919 * @param {string} message
920 */
921 function raiseParseError(tokens, message) {
922 message = (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" +
923 createParserContext(tokens);
924 var error = new Error(message);
925 error['tokens'] = tokens;
926 throw error
927 }
928
929 /**
930 * @param {TokensObject} tokens
931 * @returns
932 */
933 function parseHyperScript(tokens) {
934 return parseElement("hyperscript", tokens)
935 }
936
937 /**
938 * @param {*} elt
939 * @param {*} parent
940 */
941 function setParent(elt, parent) {
942 if (elt) {
943 elt.parent = parent;
944 setParent(elt.next, parent);
945 }
946 }
947
948 /**
949 *
950 * @param {Token} token
951 * @returns
952 */
953 function commandStart(token){
954 return COMMANDS[token.value];
955 }
956
957 /**
958 *
959 * @param {Token} token
960 * @returns
961 */
962 function featureStart(token){
963 return FEATURES[token.value];
964 }
965
966 /**
967 *
968 * @param {Token} token
969 * @returns
970 */
971 function commandBoundary(token) {
972 if (token.value == "end" ||
973 token.value == "then" ||
974 token.value == "else" ||
975 token.value == ")" ||
976 commandStart(token) ||
977 featureStart(token) ||
978 token.type == "EOF") {
979 return true;
980 }
981 }
982
983 /**
984 *
985 * @param {TokensObject} tokens
986 * @returns
987 */
988 function parseStringTemplate(tokens) {
989 var returnArr = [""];
990 do {
991 returnArr.push(tokens.lastWhitespace());
992 if (tokens.currentToken().value === "$") {
993 tokens.consumeToken();
994 var startingBrace = tokens.matchOpToken('{');
995 returnArr.push(requireElement("expression", tokens));
996 if(startingBrace){
997 tokens.requireOpToken("}");
998 }
999 returnArr.push("");
1000 } else if (tokens.currentToken().value === "\\") {
1001 tokens.consumeToken(); // skip next
1002 tokens.consumeToken()
1003 } else {
1004 var token = tokens.consumeToken();
1005 returnArr[returnArr.length - 1] += token.value;
1006 }
1007 } while (tokens.hasMore())
1008 returnArr.push(tokens.lastWhitespace());
1009 return returnArr;
1010 }
1011
1012 return {
1013 // parser API
1014 setParent: setParent,
1015 requireElement: requireElement,
1016 parseElement: parseElement,
1017 featureStart: featureStart,
1018 commandStart: commandStart,
1019 commandBoundary: commandBoundary,
1020 parseAnyOf: parseAnyOf,
1021 parseHyperScript: parseHyperScript,
1022 raiseParseError: raiseParseError,
1023 addGrammarElement: addGrammarElement,
1024 addCommand: addCommand,
1025 addFeature: addFeature,
1026 addLeafExpression: addLeafExpression,
1027 addIndirectExpression: addIndirectExpression,
1028 parseStringTemplate: parseStringTemplate,
1029 }
1030 }();
1031
1032 //====================================================================
1033 // Runtime
1034 //====================================================================
1035 var CONVERSIONS = {
1036 dynamicResolvers : [],
1037 "String" : function(val){
1038 if(val.toString){
1039 return val.toString();
1040 } else {
1041 return "" + val;
1042 }
1043 },
1044 "Int" : function(val){
1045 return parseInt(val);
1046 },
1047 "Float" : function(val){
1048 return parseFloat(val);
1049 },
1050 "Number" : function(val){
1051 return Number(val);
1052 },
1053 "Date" : function(val){
1054 return Date(val);
1055 },
1056 "Array" : function(val){
1057 return Array.from(val);
1058 },
1059 "JSON" : function(val){
1060 return JSON.stringify(val);
1061 },
1062 "Object" : function(val){
1063 if (val instanceof String) {
1064 val = val.toString()
1065 }
1066 if (typeof val === 'string') {
1067 return JSON.parse(val);
1068 } else {
1069 return mergeObjects({}, val);
1070 }
1071 }
1072 }
1073 var _runtime = function () {
1074
1075 /**
1076 * @param {HTMLElement} elt
1077 * @param {string} selector
1078 * @returns boolean
1079 */
1080 function matchesSelector(elt, selector) {
1081 // noinspection JSUnresolvedVariable
1082 var matchesFunction = elt.matches ||
1083 elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
1084 || elt.webkitMatchesSelector || elt.oMatchesSelector;
1085 return matchesFunction && matchesFunction.call(elt, selector);
1086 }
1087
1088 /**
1089 * @param {string} eventName
1090 * @param {{}} detail
1091 * @returns
1092 */
1093 function makeEvent(eventName, detail) {
1094 var evt;
1095 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
1096 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
1097 } else {
1098 evt = document.createEvent('CustomEvent');
1099 evt.initCustomEvent(eventName, true, true, detail);
1100 }
1101 return evt;
1102 }
1103
1104 /**
1105 * @param {HTMLElement} elt
1106 * @param {string} eventName
1107 * @param {{}} detail
1108 * @returns
1109 */
1110 function triggerEvent(elt, eventName, detail) {
1111 var detail = detail || {};
1112 detail["sentBy"] = elt;
1113 var event = makeEvent(eventName, detail);
1114 var eventResult = elt.dispatchEvent(event);
1115 return eventResult;
1116 }
1117
1118 /**
1119 * isArrayLike returns `true` if the provided value is an array or
1120 * a NodeList (which is close enough to being an array for our purposes).
1121 *
1122 * @param {any} value
1123 * @returns {value is Array | NodeList}
1124 */
1125 function isArrayLike(value) {
1126 return Array.isArray(value) || value instanceof NodeList;
1127 }
1128
1129 /**
1130 * forEach executes the provided `func` on every item in the `value` array.
1131 * if `value` is a single item (and not an array) then `func` is simply called
1132 * once. If `value` is null, then no further actions are taken.
1133 *
1134 * @function
1135 * @template T
1136 * @param {T | T[]} value
1137 * @param {(item:T) => void} func
1138 */
1139 function forEach(value, func) {
1140 if (value == null) {
1141 // do nothing
1142 } else if (isArrayLike(value)) {
1143 for (var i = 0; i < value.length; i++) {
1144 func(value[i]);
1145 }
1146 } else {
1147 func(value);
1148 }
1149 }
1150
1151 var ARRAY_SENTINEL = {array_sentinel:true}
1152 function linearize(args) {
1153 var arr = [];
1154 for (var i = 0; i < args.length; i++) {
1155 var arg = args[i];
1156 if (Array.isArray(arg)) {
1157 arr.push(ARRAY_SENTINEL);
1158 for (var j = 0; j < arg.length; j++) {
1159 arr.push(arg[j]);
1160 }
1161 arr.push(ARRAY_SENTINEL);
1162 } else {
1163 arr.push(arg);
1164 }
1165 }
1166 return arr;
1167 }
1168
1169 function delinearize(values){
1170 var arr = [];
1171 for (var i = 0; i < values.length; i++) {
1172 var value = values[i];
1173 if (value === ARRAY_SENTINEL) {
1174 value = values[++i];
1175 var valueArray = [];
1176 arr.push(valueArray);
1177 while (value !== ARRAY_SENTINEL) {
1178 valueArray.push(value);
1179 value = values[++i];
1180 }
1181 } else {
1182 arr.push(value);
1183 }
1184 }
1185 return arr;
1186 }
1187
1188 function unwrapAsyncs(values) {
1189 for (var i = 0; i < values.length; i++) {
1190 var value = values[i];
1191 if (value.asyncWrapper) {
1192 values[i] = value.value;
1193 }
1194 if (Array.isArray(value)) {
1195 for (var j = 0; j < value.length; j++) {
1196 var valueElement = value[j];
1197 if (valueElement.asyncWrapper) {
1198 value[j] = valueElement.value;
1199 }
1200 }
1201 }
1202 }
1203 }
1204
1205 var HALT = {halt_flag:true};
1206 function unifiedExec(command, ctx) {
1207 while(true) {
1208 try {
1209 var next = unifiedEval(command, ctx);
1210 } catch(e) {
1211 _runtime.registerHyperTrace(ctx, e);
1212 if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
1213 ctx.meta.handlingError = true;
1214 ctx[ctx.meta.errorSymmbol] = e;
1215 command = ctx.meta.errorHandler;
1216 continue;
1217 } else if (ctx.meta.reject) {
1218 ctx.meta.reject(e);
1219 next = HALT;
1220 } else {
1221 throw e;
1222 }
1223 }
1224 if (next == null) {
1225 console.error(command, " did not return a next element to execute! context: " , ctx)
1226 return;
1227 } else if (next.then) {
1228 next.then(function (resolvedNext) {
1229 unifiedExec(resolvedNext, ctx);
1230 }).catch(function(reason){
1231 _runtime.registerHyperTrace(ctx, reason);
1232 if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
1233 ctx.meta.handlingError = true;
1234 ctx[ctx.meta.errorSymmbol] = reason;
1235 unifiedExec(ctx.meta.errorHandler, ctx);
1236 } else if(ctx.meta.reject) {
1237 ctx.meta.reject(reason);
1238 } else {
1239 throw reason;
1240 }
1241 });
1242 return;
1243 } else if (next === HALT) {
1244 // done
1245 return;
1246 } else {
1247 command = next; // move to the next command
1248 }
1249 }
1250 }
1251
1252 function unifiedEval(parseElement, ctx) {
1253 var async = false;
1254 var wrappedAsyncs = false;
1255 var args = [ctx];
1256 if (parseElement.args) {
1257 for (var i = 0; i < parseElement.args.length; i++) {
1258 var argument = parseElement.args[i];
1259 if (argument == null) {
1260 args.push(null);
1261 } else if (Array.isArray(argument)) {
1262 var arr = [];
1263 for (var j = 0; j < argument.length; j++) {
1264 var element = argument[j];
1265 var value = element ? element.evaluate(ctx) : null; // OK
1266 if (value) {
1267 if (value.then) {
1268 async = true;
1269 } else if (value.asyncWrapper) {
1270 wrappedAsyncs = true;
1271 }
1272 }
1273 arr.push(value);
1274 }
1275 args.push(arr);
1276 } else if (argument.evaluate) {
1277 var value = argument.evaluate(ctx); // OK
1278 if (value) {
1279 if (value.then) {
1280 async = true;
1281 } else if (value.asyncWrapper) {
1282 wrappedAsyncs = true;
1283 }
1284 }
1285 args.push(value);
1286 } else {
1287 args.push(argument);
1288 }
1289 }
1290 }
1291 if (async) {
1292 return new Promise(function(resolve, reject){
1293 var linearized = linearize(args);
1294 Promise.all(linearized).then(function(values){
1295 values = delinearize(values);
1296 if (wrappedAsyncs) {
1297 unwrapAsyncs(values);
1298 }
1299 try{
1300 var apply = parseElement.op.apply(parseElement, values);
1301 resolve(apply);
1302 } catch(e) {
1303 reject(e);
1304 }
1305 }).catch(function(reason){
1306 if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
1307 ctx.meta.handlingError = true;
1308 ctx[ctx.meta.errorSymmbol] = reason;
1309 unifiedExec(ctx.meta.errorHandler, ctx);
1310 } else if(ctx.meta.reject) {
1311 ctx.meta.reject(reason);
1312 } else {
1313 // TODO: no meta context to reject with, trigger event?
1314 }
1315 })
1316 })
1317 } else {
1318 if (wrappedAsyncs) {
1319 unwrapAsyncs(args);
1320 }
1321 return parseElement.op.apply(parseElement, args);
1322 }
1323 }
1324
1325 var _scriptAttrs = null;
1326 function getScriptAttributes() {
1327 if (_scriptAttrs == null) {
1328 _scriptAttrs = _hyperscript.config.attributes.replace(/ /g,'').split(",")
1329 }
1330 return _scriptAttrs;
1331 }
1332
1333 function getScript(elt) {
1334 for (var i = 0; i < getScriptAttributes().length; i++) {
1335 var scriptAttribute = getScriptAttributes()[i];
1336 if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
1337 return elt.getAttribute(scriptAttribute)
1338 }
1339 }
1340 if (elt.type === "text/hyperscript") {
1341 return elt.innerText;
1342 }
1343 return null;
1344 }
1345
1346 function makeContext(owner, feature, hyperscriptTarget, event) {
1347 var ctx = {
1348 meta: {
1349 parser: _parser,
1350 lexer: _lexer,
1351 runtime: _runtime,
1352 owner: owner,
1353 feature: feature,
1354 iterators: {}
1355 },
1356 me: hyperscriptTarget,
1357 event: event,
1358 target: event ? event.target : null,
1359 detail: event ? event.detail : null,
1360 body: 'document' in globalScope ? document.body : null
1361 }
1362 ctx.meta.ctx = ctx;
1363 return ctx;
1364 }
1365
1366 function getScriptSelector() {
1367 return getScriptAttributes().map(function (attribute) {
1368 return "[" + attribute + "]";
1369 }).join(", ");
1370 }
1371
1372 function convertValue(value, type) {
1373
1374 var dynamicResolvers = CONVERSIONS.dynamicResolvers;
1375 for (var i = 0; i < dynamicResolvers.length; i++) {
1376 var dynamicResolver = dynamicResolvers[i];
1377 var converted = dynamicResolver(type, value);
1378 if (converted !== undefined) {
1379 return converted;
1380 }
1381 }
1382
1383 if (value == null) {
1384 return null;
1385 }
1386 var converter = CONVERSIONS[type];
1387 if (converter) {
1388 return converter(value);
1389 }
1390
1391 throw "Unknown conversion : " + type;
1392 }
1393
1394
1395 function isType(o, type) {
1396 return Object.prototype.toString.call(o) === "[object " + type + "]";
1397 }
1398
1399 function parse(src) {
1400 var tokens = _lexer.tokenize(src);
1401 if (_parser.commandStart(tokens.currentToken())) {
1402 var commandList = _parser.parseElement("commandList", tokens);
1403 var last = commandList;
1404 while (last.next) {
1405 last = last.next;
1406 }
1407 last.next = {
1408 op : function() {
1409 return HALT;
1410 }
1411 }
1412 return commandList
1413 } else if (_parser.featureStart(tokens.currentToken())) {
1414 var hyperscript = _parser.parseElement("hyperscript", tokens);
1415 return hyperscript;
1416 } else {
1417 var expression = _parser.parseElement("expression", tokens);
1418 return expression;
1419 }
1420 }
1421
1422 function evaluate(src, ctx) {
1423 ctx = ctx || {};
1424 var element = parse(src);
1425 if (element.execute) {
1426 element.execute(ctx);
1427 } else if (element.apply) {
1428 element.apply(document.body, null);
1429 } else {
1430 return element.evaluate(ctx);
1431 }
1432 }
1433
1434 function processNode(elt) {
1435 var selector = _runtime.getScriptSelector();
1436 if (matchesSelector(elt, selector)) {
1437 initElement(elt);
1438 }
1439 if (elt.querySelectorAll) {
1440 forEach(elt.querySelectorAll(selector), function (elt) {
1441 initElement(elt);
1442 });
1443 }
1444 if (elt.type === "text/hyperscript") {
1445 initElement(elt, document.body);
1446 }
1447 if (elt.querySelectorAll) {
1448 forEach(elt.querySelectorAll("[type=\'text/hyperscript\']"), function (elt) {
1449 initElement(elt, document.body);
1450 });
1451 }
1452 }
1453
1454 function initElement(elt, target) {
1455 var internalData = getInternalData(elt);
1456 if (!internalData.initialized) {
1457 var src = getScript(elt);
1458 if (src) {
1459 try {
1460 internalData.initialized = true;
1461 internalData.script = src;
1462 var tokens = _lexer.tokenize(src);
1463 var hyperScript = _parser.parseHyperScript(tokens);
1464 hyperScript.apply(target || elt, elt);
1465 setTimeout(function () {
1466 triggerEvent(target || elt, 'load', {'hyperscript':true});
1467 }, 1);
1468 } catch(e) {
1469 console.error("hyperscript errors were found on the following element:", elt, "\n\n", e.message, e.stack);
1470 }
1471 }
1472 }
1473 }
1474
1475 function getInternalData(elt) {
1476 var dataProp = 'hyperscript-internal-data';
1477 var data = elt[dataProp];
1478 if (!data) {
1479 data = elt[dataProp] = {};
1480 }
1481 return data;
1482 }
1483
1484 function typeCheck(value, typeString, nullOk) {
1485 if (value == null && nullOk) {
1486 return value;
1487 }
1488 var typeName = Object.prototype.toString.call(value).slice(8, -1);
1489 var typeCheckValue = value && typeName === typeString;
1490 if (typeCheckValue) {
1491 return value;
1492 } else {
1493 throw new Error("Typecheck failed! Expected: " + typeString + ", Found: " + typeName);
1494 }
1495 }
1496
1497 function resolveSymbol(str, context) {
1498 if (str === "me" || str === "my" || str === "I") {
1499 return context["me"];
1500 } if (str === "it" || str === "its") {
1501 return context["result"];
1502 } else {
1503 if (context.meta && context.meta.context) {
1504 var fromMetaContext = context.meta.context[str];
1505 if (typeof fromMetaContext !== "undefined") {
1506 return fromMetaContext;
1507 }
1508 }
1509 var fromContext = context[str];
1510 if (typeof fromContext !== "undefined") {
1511 return fromContext;
1512 } else {
1513 return globalScope[str];
1514 }
1515 }
1516 }
1517
1518 function findNext(command, context) {
1519 if (command) {
1520 if (command.resolveNext) {
1521 return command.resolveNext(context);
1522 } else if (command.next) {
1523 return command.next;
1524 } else {
1525 return findNext(command.parent, context)
1526 }
1527 }
1528 }
1529
1530 function resolveProperty(root, property) {
1531 if (root != null) {
1532 var val = root[property];
1533 if (typeof val !== 'undefined') {
1534 return val;
1535 } else {
1536 if (isArrayLike(root)) {
1537 if (property === "first") {
1538 return root[0];
1539 } else if (property === "last") {
1540 return root[root.length - 1];
1541 } else if (property === "random") {
1542 return root[Math.floor(root.length * Math.random())]
1543 } else {
1544 // flat map
1545 var result = [];
1546 for (var i = 0; i < root.length; i++) {
1547 var component = root[i];
1548 var componentValue = component[property];
1549 if (componentValue) {
1550 result.push(componentValue);
1551 }
1552 }
1553 return result;
1554 }
1555 }
1556 }
1557 }
1558 }
1559
1560 function assignToNamespace(nameSpace, name, value) {
1561 var root = globalScope;
1562 while (nameSpace.length > 0) {
1563 var propertyName = nameSpace.shift();
1564 var newRoot = root[propertyName];
1565 if (newRoot == null) {
1566 newRoot = {};
1567 root[propertyName] = newRoot;
1568 }
1569 root = newRoot;
1570 }
1571
1572 root[name] = value;
1573 }
1574
1575 function getHyperTrace(ctx, thrown) {
1576 var trace = [];
1577 var root = ctx;
1578 while(root.meta.caller) {
1579 root = root.meta.caller;
1580 }
1581 if (root.meta.traceMap) {
1582 return root.meta.traceMap.get(thrown, trace);
1583 }
1584 }
1585
1586 function registerHyperTrace(ctx, thrown) {
1587 var trace = [];
1588 var root = null;
1589 while(ctx != null) {
1590 trace.push(ctx);
1591 root = ctx;
1592 ctx = ctx.meta.caller;
1593 }
1594 if (root.meta.traceMap == null) {
1595 root.meta.traceMap = new Map(); // TODO - WeakMap?
1596 }
1597 if (!root.meta.traceMap.get(thrown)) {
1598 var traceEntry = {
1599 trace: trace,
1600 print : function(logger) {
1601 logger = logger || console.error;
1602 logger("hypertrace /// ")
1603 var maxLen = 0;
1604 for (var i = 0; i < trace.length; i++) {
1605 maxLen = Math.max(maxLen, trace[i].meta.feature.displayName.length);
1606 }
1607 for (var i = 0; i < trace.length; i++) {
1608 var traceElt = trace[i];
1609 logger(" ->", traceElt.meta.feature.displayName.padEnd(maxLen + 2), "-", traceElt.meta.owner)
1610 }
1611 }
1612 };
1613 root.meta.traceMap.set(thrown, traceEntry);
1614 }
1615 }
1616
1617 function escapeSelector(str) {
1618 return str.replace(/:/g, function(str){
1619 return "\\" + str;
1620 });
1621 }
1622
1623 function nullCheck(value, elt) {
1624 if (value == null) {
1625 throw new Error(elt.sourceFor() + " is null");
1626 }
1627 }
1628
1629 var hyperscriptUrl = 'document' in globalScope ? document.currentScript.src : null
1630
1631 return {
1632 typeCheck: typeCheck,
1633 forEach: forEach,
1634 triggerEvent: triggerEvent,
1635 matchesSelector: matchesSelector,
1636 getScript: getScript,
1637 processNode: processNode,
1638 evaluate: evaluate,
1639 parse: parse,
1640 getScriptSelector: getScriptSelector,
1641 resolveSymbol: resolveSymbol,
1642 makeContext: makeContext,
1643 findNext: findNext,
1644 unifiedEval: unifiedEval,
1645 convertValue: convertValue,
1646 unifiedExec: unifiedExec,
1647 resolveProperty: resolveProperty,
1648 assignToNamespace: assignToNamespace,
1649 registerHyperTrace: registerHyperTrace,
1650 getHyperTrace: getHyperTrace,
1651 getInternalData: getInternalData,
1652 escapeSelector: escapeSelector,
1653 nullCheck: nullCheck,
1654 hyperscriptUrl: hyperscriptUrl,
1655 HALT: HALT
1656 }
1657 }();
1658
1659 //====================================================================
1660 // Grammar
1661 //====================================================================
1662 {
1663 _parser.addLeafExpression("parenthesized", function(parser, runtime, tokens) {
1664 if (tokens.matchOpToken('(')) {
1665 var expr = parser.requireElement("expression", tokens);
1666 tokens.requireOpToken(")");
1667 return {
1668 type: "parenthesized",
1669 expr: expr,
1670 evaluate: function (context) {
1671 return expr.evaluate(context); //OK
1672 }
1673 }
1674 }
1675 })
1676
1677 _parser.addLeafExpression("string", function(parser, runtime, tokens) {
1678 var stringToken = tokens.matchTokenType('STRING');
1679 if (stringToken) {
1680 var rawValue = stringToken.value;
1681 if (stringToken.template) {
1682 var innerTokens = _lexer.tokenize(rawValue, true);
1683 var args = parser.parseStringTemplate(innerTokens);
1684 } else {
1685 var args = [];
1686 }
1687 return {
1688 type: "string",
1689 token: stringToken,
1690 args: args,
1691 op: function (context) {
1692 var returnStr = "";
1693 for (var i = 1; i < arguments.length; i++) {
1694 var val = arguments[i];
1695 if (val) {
1696 returnStr += val;
1697 }
1698 }
1699 return returnStr;
1700 },
1701 evaluate: function (context) {
1702 if (args.length === 0) {
1703 return rawValue;
1704 } else {
1705 return runtime.unifiedEval(this, context);
1706 }
1707 }
1708 };
1709 }
1710 })
1711
1712 _parser.addGrammarElement("nakedString", function(parser, runtime, tokens) {
1713 if (tokens.hasMore()) {
1714 var tokenArr = tokens.consumeUntilWhitespace();
1715 tokens.matchTokenType("WHITESPACE");
1716 return {
1717 type: "nakedString",
1718 tokens: tokenArr,
1719 evaluate: function (context) {
1720 return tokenArr.map(function (t) {return t.value}).join("");
1721 }
1722 }
1723 }
1724 })
1725
1726 _parser.addLeafExpression("number", function(parser, runtime, tokens) {
1727 var number = tokens.matchTokenType('NUMBER');
1728 if (number) {
1729 var numberToken = number;
1730 var value = parseFloat(number.value)
1731 return {
1732 type: "number",
1733 value: value,
1734 numberToken: numberToken,
1735 evaluate: function () {
1736 return value;
1737 }
1738 }
1739 }
1740 })
1741
1742 _parser.addLeafExpression("idRef", function(parser, runtime, tokens) {
1743 var elementId = tokens.matchTokenType('ID_REF');
1744 if (elementId) {
1745 return {
1746 type: "idRef",
1747 css: elementId.value,
1748 value: elementId.value.substr(1),
1749 evaluate: function (context) {
1750 return document.getElementById(this.value);
1751 }
1752 };
1753 }
1754 })
1755
1756 _parser.addLeafExpression("classRef", function(parser, runtime, tokens) {
1757 var classRef = tokens.matchTokenType('CLASS_REF');
1758
1759 if (classRef) {
1760 return {
1761 type: "classRef",
1762 css: classRef.value,
1763 className: function () {
1764 return this.css.substr(1);
1765 },
1766 evaluate: function () {
1767 return document.querySelectorAll(runtime.escapeSelector(this.css));
1768 }
1769 };
1770 }
1771 })
1772
1773 _parser.addLeafExpression("queryRef", function(parser, runtime, tokens) {
1774 var queryStart = tokens.matchOpToken('<');
1775 if (queryStart) {
1776 var queryTokens = tokens.consumeUntil("/");
1777 tokens.requireOpToken("/");
1778 tokens.requireOpToken(">");
1779 var queryValue = queryTokens.map(function(t){
1780 if (t.type === "STRING") {
1781 return '"' + t.value + '"';
1782 } else {
1783 return t.value;
1784 }
1785 }).join("");
1786 return {
1787 type: "queryRef",
1788 css: queryValue,
1789 evaluate: function () {
1790 return document.querySelectorAll(this.css);
1791 }
1792 };
1793 }
1794 })
1795
1796 _parser.addGrammarElement("attributeRef", function(parser, runtime, tokens) {
1797 if (tokens.matchOpToken("[")) {
1798 var content = tokens.consumeUntil("]");
1799 var contentStr = content.map(function (t) {
1800 return t.value
1801 }).join("");
1802 var values = contentStr.split("=");
1803 var name = values[0];
1804 var value = values[1];
1805 tokens.requireOpToken("]");
1806
1807 return {
1808 type: "attribute_expression",
1809 name: name,
1810 value: value,
1811 args: [value],
1812 op:function(context, value){
1813 if (this.value) {
1814 return {name:this.name, value:value}
1815 } else {
1816 return {name:this.name};
1817 }
1818 },
1819 evaluate: function (context) {
1820 return runtime.unifiedEval(this, context);
1821 }
1822 }
1823 }
1824 })
1825
1826 _parser.addGrammarElement("objectKey", function(parser, runtime, tokens) {
1827 var token;
1828 if (token = tokens.matchTokenType("STRING")) {
1829 return {
1830 type: "objectKey",
1831 key: token.value,
1832 evaluate: function() { return this.key }
1833 };
1834 } else if (tokens.matchOpToken("[")) {
1835 var expr = parser.parseElement("expression", tokens);
1836 tokens.requireOpToken("]");
1837 return {
1838 type: "objectKey",
1839 expr: expr,
1840 args: [expr],
1841 op: function (ctx, expr) { return expr },
1842 evaluate: function (context) {
1843 return runtime.unifiedEval(this, context);
1844 }
1845 }
1846 } else {
1847 var key = "";
1848 do {
1849 token = tokens.matchTokenType("IDENTIFIER") || tokens.matchOpToken("-");
1850 if (token) key += token.value;
1851 } while (token)
1852 return {
1853 type: "objectKey",
1854 key: key,
1855 evaluate: function() { return this.key }
1856 };
1857 }
1858 })
1859
1860 _parser.addLeafExpression("objectLiteral", function(parser, runtime, tokens) {
1861 if (tokens.matchOpToken("{")) {
1862 var keyExpressions = []
1863 var valueExpressions = []
1864 if (!tokens.matchOpToken("}")) {
1865 do {
1866 var name = parser.requireElement("objectKey", tokens);
1867 tokens.requireOpToken(":");
1868 var value = parser.requireElement("expression", tokens);
1869 valueExpressions.push(value);
1870 keyExpressions.push(name);
1871 } while (tokens.matchOpToken(","))
1872 tokens.requireOpToken("}");
1873 }
1874 return {
1875 type: "objectLiteral",
1876 args: [keyExpressions, valueExpressions],
1877 op:function(context, keys, values){
1878 var returnVal = {};
1879 for (var i = 0; i < keys.length; i++) {
1880 returnVal[keys[i]] = values[i];
1881 }
1882 return returnVal;
1883 },
1884 evaluate: function (context) {
1885 return runtime.unifiedEval(this, context);
1886 }
1887 }
1888 }
1889 })
1890
1891 _parser.addGrammarElement("namedArgumentList", function(parser, runtime, tokens) {
1892 if (tokens.matchOpToken("(")) {
1893 var fields = []
1894 var valueExpressions = []
1895 if (!tokens.matchOpToken(")")) {
1896 do {
1897 var name = tokens.requireTokenType("IDENTIFIER");
1898 tokens.requireOpToken(":");
1899 var value = parser.requireElement("expression", tokens);
1900 valueExpressions.push(value);
1901 fields.push({name: name, value: value});
1902 } while (tokens.matchOpToken(","))
1903 tokens.requireOpToken(")");
1904 }
1905 return {
1906 type: "namedArgumentList",
1907 fields: fields,
1908 args:[valueExpressions],
1909 op:function(context, values){
1910 var returnVal = {_namedArgList_:true};
1911 for (var i = 0; i < values.length; i++) {
1912 var field = fields[i];
1913 returnVal[field.name.value] = values[i];
1914 }
1915 return returnVal;
1916 },
1917 evaluate: function (context) {
1918 return runtime.unifiedEval(this, context);
1919 }
1920 }
1921 }
1922
1923
1924 })
1925
1926 _parser.addGrammarElement("symbol", function(parser, runtime, tokens) {
1927 var identifier = tokens.matchTokenType('IDENTIFIER');
1928 if (identifier) {
1929 return {
1930 type: "symbol",
1931 token: identifier,
1932 name: identifier.value,
1933 evaluate: function (context) {
1934 return runtime.resolveSymbol(identifier.value, context);
1935 }
1936 };
1937 }
1938 });
1939
1940 _parser.addGrammarElement("implicitMeTarget", function(parser, runtime, tokens) {
1941 return {
1942 type: "implicitMeTarget",
1943 evaluate: function (context) {
1944 return context.me
1945 }
1946 };
1947 });
1948
1949 _parser.addLeafExpression("boolean", function(parser, runtime, tokens) {
1950 var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false");
1951 if (booleanLiteral) {
1952 return {
1953 type: "boolean",
1954 evaluate: function (context) {
1955 return booleanLiteral.value === "true";
1956 }
1957 }
1958 }
1959 });
1960
1961 _parser.addLeafExpression("null", function(parser, runtime, tokens) {
1962 if (tokens.matchToken('null')) {
1963 return {
1964 type: "null",
1965 evaluate: function (context) {
1966 return null;
1967 }
1968 }
1969 }
1970 });
1971
1972 _parser.addLeafExpression("arrayLiteral", function(parser, runtime, tokens) {
1973 if (tokens.matchOpToken('[')) {
1974 var values = [];
1975 if (!tokens.matchOpToken(']')) {
1976 do {
1977 var expr = parser.requireElement("expression", tokens);
1978 values.push(expr);
1979 } while (tokens.matchOpToken(","))
1980 tokens.requireOpToken("]");
1981 }
1982 return {
1983 type: "arrayLiteral",
1984 values: values,
1985 args: [values],
1986 op:function(context, values){
1987 return values;
1988 },
1989 evaluate: function (context) {
1990 return runtime.unifiedEval(this, context);
1991 }
1992 }
1993 }
1994 });
1995
1996 _parser.addLeafExpression("blockLiteral", function(parser, runtime, tokens) {
1997 if (tokens.matchOpToken('\\')) {
1998 var args = []
1999 var arg1 = tokens.matchTokenType("IDENTIFIER");
2000 if (arg1) {
2001 args.push(arg1);
2002 while (tokens.matchOpToken(",")) {
2003 args.push(tokens.requireTokenType("IDENTIFIER"));
2004 }
2005 }
2006 // TODO compound op token
2007 tokens.requireOpToken("-");
2008 tokens.requireOpToken(">");
2009 var expr = parser.requireElement("expression", tokens);
2010 return {
2011 type: "blockLiteral",
2012 args: args,
2013 expr: expr,
2014 evaluate: function (ctx) {
2015 var returnFunc = function(){
2016 //TODO - push scope
2017 for (var i = 0; i < args.length; i++) {
2018 ctx[args[i].value] = arguments[i];
2019 }
2020 return expr.evaluate(ctx) //OK
2021 }
2022 return returnFunc;
2023 }
2024 }
2025 }
2026 });
2027
2028 _parser.addGrammarElement("timeExpression", function(parser, runtime, tokens){
2029 var time = parser.requireElement("expression", tokens);
2030 var factor = 1;
2031 if (tokens.matchToken("s") || tokens.matchToken("seconds")) {
2032 factor = 1000;
2033 } else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) {
2034 // do nothing
2035 }
2036 return {
2037 type:"timeExpression",
2038 time: time,
2039 factor: factor,
2040 args: [time],
2041 op: function (context, val) {
2042 return val * this.factor
2043 },
2044 evaluate: function (context) {
2045 return runtime.unifiedEval(this, context);
2046 }
2047 }
2048 })
2049
2050 _parser.addIndirectExpression("propertyAccess", function(parser, runtime, tokens, root) {
2051 if (tokens.matchOpToken(".")) {
2052 var prop = tokens.requireTokenType("IDENTIFIER");
2053 var propertyAccess = {
2054 type: "propertyAccess",
2055 root: root,
2056 prop: prop,
2057 args: [root],
2058 op:function(context, rootVal){
2059 var value = runtime.resolveProperty(rootVal, prop.value);
2060 return value;
2061 },
2062 evaluate: function (context) {
2063 return runtime.unifiedEval(this, context);
2064 }
2065 };
2066 return parser.parseElement("indirectExpression", tokens, propertyAccess);
2067 }
2068 });
2069
2070 _parser.addIndirectExpression("of", function(parser, runtime, tokens, root) {
2071 if (tokens.matchToken("of")) {
2072 var newRoot = parser.requireElement('expression', tokens);
2073 // find the urroot
2074 var childOfUrRoot = null;
2075 var urRoot = root;
2076 while (urRoot.root) {
2077 childOfUrRoot = urRoot;
2078 urRoot = urRoot.root;
2079 }
2080 if (urRoot.type !== 'symbol') {
2081 parser.raiseParseError(tokens, "Cannot take a property of a non-symbol");
2082 }
2083 var prop = urRoot.name;
2084 var propertyAccess = {
2085 type: "ofExpression",
2086 prop: urRoot.token,
2087 root: newRoot,
2088 expression: root,
2089 args: [newRoot],
2090 op:function(context, rootVal){
2091 return runtime.resolveProperty(rootVal, prop);
2092 },
2093 evaluate: function (context) {
2094 return runtime.unifiedEval(this, context);
2095 }
2096 };
2097
2098 if (childOfUrRoot) {
2099 childOfUrRoot.root = propertyAccess;
2100 childOfUrRoot.args = [propertyAccess];
2101 } else {
2102 root = propertyAccess;
2103 }
2104
2105 return parser.parseElement("indirectExpression", tokens, root);
2106 }
2107 });
2108
2109 _parser.addIndirectExpression("inExpression", function(parser, runtime, tokens, root) {
2110 if (tokens.matchToken("in")) {
2111 if (root.type !== "idRef" && root.type === "queryRef" || root.type === "classRef") {
2112 var query = true;
2113 }
2114 var target = parser.requireElement("expression", tokens);
2115 var propertyAccess = {
2116 type: "inExpression",
2117 root: root,
2118 args: [query ? null : root, target],
2119 op:function(context, rootVal, target){
2120 var returnArr = [];
2121 if(query){
2122 runtime.forEach(target, function (targetElt) {
2123 var results = targetElt.querySelectorAll(root.css);
2124 for (var i = 0; i < results.length; i++) {
2125 returnArr.push(results[i]);
2126 }
2127 })
2128 } else {
2129 runtime.forEach(rootVal, function(rootElt){
2130 runtime.forEach(target, function(targetElt){
2131 if (rootElt === targetElt) {
2132 returnArr.push(rootElt);
2133 }
2134 })
2135 })
2136 }
2137 if (returnArr.length > 0) {
2138 return returnArr;
2139 } else {
2140 return null;
2141 }
2142 },
2143 evaluate: function (context) {
2144 return runtime.unifiedEval(this, context);
2145 }
2146 };
2147 return parser.parseElement("indirectExpression", tokens, propertyAccess);
2148 }
2149 });
2150
2151 _parser.addIndirectExpression("asExpression", function(parser, runtime, tokens, root) {
2152 if (tokens.matchToken("as")) {
2153 var conversion = parser.requireElement('dotOrColonPath', tokens).evaluate(); // OK No promise
2154 var propertyAccess = {
2155 type: "asExpression",
2156 root: root,
2157 args: [root],
2158 op:function(context, rootVal){
2159 return runtime.convertValue(rootVal, conversion);
2160 },
2161 evaluate: function (context) {
2162 return runtime.unifiedEval(this, context);
2163 }
2164 };
2165 return parser.parseElement("indirectExpression", tokens, propertyAccess);
2166 }
2167 });
2168
2169 _parser.addIndirectExpression("functionCall", function(parser, runtime, tokens, root) {
2170 if (tokens.matchOpToken("(")) {
2171 var args = [];
2172 if (!tokens.matchOpToken(')')) {
2173 do {
2174 args.push(parser.requireElement("expression", tokens));
2175 } while (tokens.matchOpToken(","))
2176 tokens.requireOpToken(")");
2177 }
2178
2179 if (root.root) {
2180 var functionCall = {
2181 type: "functionCall",
2182 root: root,
2183 argExressions: args,
2184 args: [root.root, args],
2185 op: function (context, rootRoot, args) {
2186 runtime.nullCheck(rootRoot, root.root);
2187 var func = rootRoot[root.prop.value];
2188 runtime.nullCheck(func, root);
2189 if (func.hyperfunc) {
2190 args.push(context);
2191 }
2192 return func.apply(rootRoot, args);
2193 },
2194 evaluate: function (context) {
2195 return runtime.unifiedEval(this, context);
2196 }
2197 }
2198 } else {
2199 var functionCall = {
2200 type: "functionCall",
2201 root: root,
2202 argExressions: args,
2203 args: [root, args],
2204 op: function(context, func, argVals){
2205 runtime.nullCheck(func, root);
2206 if (func.hyperfunc) {
2207 argVals.push(context);
2208 }
2209 var apply = func.apply(null, argVals);
2210 return apply;
2211 },
2212 evaluate: function (context) {
2213 return runtime.unifiedEval(this, context);
2214 }
2215 }
2216 }
2217 return parser.parseElement("indirectExpression", tokens, functionCall);
2218 }
2219 });
2220
2221 _parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) {
2222 if (tokens.matchOpToken("[")) {
2223 var index = parser.requireElement("expression", tokens);
2224 tokens.requireOpToken("]")
2225
2226 var arrayIndex = {
2227 type: "arrayIndex",
2228 root: root,
2229 index: index,
2230 args: [root, index],
2231 op: function(ctx, root, index) {
2232 return root[index]
2233 },
2234 evaluate: function(context){
2235 return _runtime.unifiedEval(this, context);
2236 }
2237 };
2238
2239 return _parser.parseElement("indirectExpression", tokens, arrayIndex);
2240 }
2241 });
2242
2243 _parser.addGrammarElement("postfixExpression", function(parser, runtime, tokens) {
2244 var root = parser.parseElement("primaryExpression", tokens);
2245 if (tokens.matchOpToken(":")) {
2246 var typeName = tokens.requireTokenType("IDENTIFIER");
2247 var nullOk = !tokens.matchOpToken("!");
2248 return {
2249 type: "typeCheck",
2250 typeName: typeName,
2251 root: root,
2252 nullOk: nullOk,
2253 args: [root],
2254 op: function (context, val) {
2255 return runtime.typeCheck(val, this.typeName.value, this.nullOk);
2256 },
2257 evaluate: function (context) {
2258 return runtime.unifiedEval(this, context);
2259 }
2260 }
2261 } else {
2262 return root;
2263 }
2264 });
2265
2266 _parser.addGrammarElement("logicalNot", function(parser, runtime, tokens) {
2267 if (tokens.matchToken("not")) {
2268 var root = parser.requireElement("unaryExpression", tokens);
2269 return {
2270 type: "logicalNot",
2271 root: root,
2272 args: [root],
2273 op: function (context, val) {
2274 return !val;
2275 },
2276 evaluate: function (context) {
2277 return runtime.unifiedEval(this, context);
2278 }
2279 };
2280 }
2281 });
2282
2283 _parser.addGrammarElement("noExpression", function(parser, runtime, tokens) {
2284 if (tokens.matchToken("no")) {
2285 var root = parser.requireElement("unaryExpression", tokens);
2286 return {
2287 type: "noExpression",
2288 root: root,
2289 args: [root],
2290 op: function (context, val) {
2291 return val == null || val.length === 0;
2292 },
2293 evaluate: function (context) {
2294 return runtime.unifiedEval(this, context);
2295 }
2296 };
2297 }
2298 });
2299
2300 _parser.addGrammarElement("negativeNumber", function(parser, runtime, tokens) {
2301 if (tokens.matchOpToken("-")) {
2302 var root = parser.requireElement("unaryExpression", tokens);
2303 return {
2304 type: "negativeNumber",
2305 root: root,
2306 args: [root],
2307 op:function(context, value){
2308 return -1 * value;
2309 },
2310 evaluate: function (context) {
2311 return runtime.unifiedEval(this, context);
2312 }
2313 };
2314 }
2315 });
2316
2317 _parser.addGrammarElement("unaryExpression", function(parser, runtime, tokens) {
2318 return parser.parseAnyOf(["logicalNot", "positionalExpression", "noExpression", "negativeNumber", "postfixExpression"], tokens);
2319 });
2320
2321 _parser.addGrammarElement("positionalExpression", function(parser, runtime, tokens) {
2322 var op = tokens.matchAnyToken('first', 'last', 'random')
2323 if (op) {
2324 tokens.matchAnyToken("in", "from", "of");
2325 var rhs = parser.requireElement('unaryExpression', tokens);
2326 return {
2327 type: "positionalExpression",
2328 rhs: rhs,
2329 operator: op.value,
2330 args: [rhs],
2331 op:function (context, rhsVal) {
2332 if (!Array.isArray(rhsVal)) {
2333 if (rhsVal.children) {
2334 rhsVal = rhsVal.children
2335 } else {
2336 rhsVal = Array.from(rhsVal);
2337 }
2338 }
2339 if (this.operator === "first") {
2340 return rhsVal[0];
2341 } else if (this.operator === "last") {
2342 return rhsVal[rhsVal.length - 1];
2343 } else if (this.operator === "random") {
2344 return rhsVal[Math.floor(Math.random() * rhsVal.length)];
2345 }
2346 },
2347 evaluate: function (context) {
2348 return runtime.unifiedEval(this, context);
2349 }
2350 }
2351 }
2352 });
2353
2354 _parser.addGrammarElement("mathOperator", function(parser, runtime, tokens) {
2355 var expr = parser.parseElement("unaryExpression", tokens);
2356 var mathOp, initialMathOp = null;
2357 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
2358 while (mathOp) {
2359 initialMathOp = initialMathOp || mathOp;
2360 var operator = mathOp.value;
2361 if (initialMathOp.value !== operator) {
2362 parser.raiseParseError(tokens, "You must parenthesize math operations with different operators")
2363 }
2364 var rhs = parser.parseElement("unaryExpression", tokens);
2365 expr = {
2366 type: "mathOperator",
2367 lhs: expr,
2368 rhs: rhs,
2369 operator: operator,
2370 args: [expr, rhs],
2371 op:function (context, lhsVal, rhsVal) {
2372 if (this.operator === "+") {
2373 return lhsVal + rhsVal;
2374 } else if (this.operator === "-") {
2375 return lhsVal - rhsVal;
2376 } else if (this.operator === "*") {
2377 return lhsVal * rhsVal;
2378 } else if (this.operator === "/") {
2379 return lhsVal / rhsVal;
2380 } else if (this.operator === "%") {
2381 return lhsVal % rhsVal;
2382 }
2383 },
2384 evaluate: function (context) {
2385 return runtime.unifiedEval(this, context);
2386 }
2387 }
2388 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
2389 }
2390 return expr;
2391 });
2392
2393 _parser.addGrammarElement("mathExpression", function(parser, runtime, tokens) {
2394 return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens);
2395 });
2396
2397 _parser.addGrammarElement("comparisonOperator", function(parser, runtime, tokens) {
2398 var expr = parser.parseElement("mathExpression", tokens);
2399 var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==")
2400 var comparisonStr = comparisonToken ? comparisonToken.value : null;
2401 if (comparisonStr == null) {
2402 if (tokens.matchToken("is") || tokens.matchToken("am")) {
2403 if (tokens.matchToken("not")) {
2404 if (tokens.matchToken("in")) {
2405 comparisonStr = "not in";
2406 } else {
2407 comparisonStr = "!=";
2408 }
2409 } else {
2410 if (tokens.matchToken("in")) {
2411 comparisonStr = "in";
2412 } else {
2413 comparisonStr = "==";
2414 }
2415 }
2416 } else if (tokens.matchToken("matches") || tokens.matchToken("match")) {
2417 comparisonStr = "match";
2418 } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
2419 comparisonStr = "contain";
2420 } else if (tokens.matchToken("do") || tokens.matchToken("does")) {
2421 tokens.requireToken('not');
2422 if (tokens.matchToken("matches") || tokens.matchToken("match")) {
2423 comparisonStr = "not match";
2424 } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
2425 comparisonStr = "not contain";
2426 } else {
2427 parser.raiseParseError(tokens, "Expected matches or contains");
2428 }
2429 }
2430 }
2431
2432 if (comparisonStr) { // Do not allow chained comparisons, which is dumb
2433 var rhs = parser.requireElement("mathExpression", tokens);
2434 if (comparisonStr === "match" || comparisonStr === "not match") {
2435 rhs = rhs.css ? rhs.css : rhs;
2436 }
2437 expr = {
2438 type: "comparisonOperator",
2439 operator: comparisonStr,
2440 lhs: expr,
2441 rhs: rhs,
2442 args: [expr, rhs],
2443 op:function (context, lhsVal, rhsVal) {
2444 if (this.operator === "==") {
2445 return lhsVal == rhsVal;
2446 } else if (this.operator === "!=") {
2447 return lhsVal != rhsVal;
2448 } if (this.operator === "in") {
2449 return (rhsVal != null) && Array.from(rhsVal).indexOf(lhsVal) >= 0;
2450 } if (this.operator === "not in") {
2451 return (rhsVal == null) || Array.from(rhsVal).indexOf(lhsVal) < 0;
2452 } if (this.operator === "match") {
2453 return (lhsVal != null) && lhsVal.matches(rhsVal);
2454 } if (this.operator === "not match") {
2455 return (lhsVal == null) || !lhsVal.matches(rhsVal);
2456 } if (this.operator === "contain") {
2457 return (lhsVal != null) && lhsVal.contains(rhsVal);
2458 } if (this.operator === "not contain") {
2459 return (lhsVal == null) || !lhsVal.contains(rhsVal);
2460 } if (this.operator === "===") {
2461 return lhsVal === rhsVal;
2462 } else if (this.operator === "!==") {
2463 return lhsVal !== rhsVal;
2464 } else if (this.operator === "<") {
2465 return lhsVal < rhsVal;
2466 } else if (this.operator === ">") {
2467 return lhsVal > rhsVal;
2468 } else if (this.operator === "<=") {
2469 return lhsVal <= rhsVal;
2470 } else if (this.operator === ">=") {
2471 return lhsVal >= rhsVal;
2472 }
2473 },
2474 evaluate: function (context) {
2475 return runtime.unifiedEval(this, context);
2476 }
2477 };
2478 }
2479 return expr;
2480 });
2481
2482 _parser.addGrammarElement("comparisonExpression", function(parser, runtime, tokens) {
2483 return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens);
2484 });
2485
2486 _parser.addGrammarElement("logicalOperator", function(parser, runtime, tokens) {
2487 var expr = parser.parseElement("comparisonExpression", tokens);
2488 var logicalOp, initialLogicalOp = null;
2489 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
2490 while (logicalOp) {
2491 initialLogicalOp = initialLogicalOp || logicalOp;
2492 if (initialLogicalOp.value !== logicalOp.value) {
2493 parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators")
2494 }
2495 var rhs = parser.requireElement("comparisonExpression", tokens);
2496 expr = {
2497 type: "logicalOperator",
2498 operator: logicalOp.value,
2499 lhs: expr,
2500 rhs: rhs,
2501 args: [expr, rhs],
2502 op: function (context, lhsVal, rhsVal) {
2503 if (this.operator === "and") {
2504 return lhsVal && rhsVal;
2505 } else {
2506 return lhsVal || rhsVal;
2507 }
2508 },
2509 evaluate: function (context) {
2510 return runtime.unifiedEval(this, context);
2511 }
2512 }
2513 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
2514 }
2515 return expr;
2516 });
2517
2518 _parser.addGrammarElement("logicalExpression", function(parser, runtime, tokens) {
2519 return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens);
2520 });
2521
2522 _parser.addGrammarElement("asyncExpression", function(parser, runtime, tokens) {
2523 if (tokens.matchToken('async')) {
2524 var value = parser.requireElement("logicalExpression", tokens);
2525 var expr = {
2526 type: "asyncExpression",
2527 value: value,
2528 evaluate: function (context) {
2529 return {
2530 asyncWrapper: true,
2531 value: this.value.evaluate(context) //OK
2532 }
2533 }
2534 }
2535 return expr;
2536 } else {
2537 return parser.parseElement("logicalExpression", tokens);
2538 }
2539 });
2540
2541 _parser.addGrammarElement("expression", function(parser, runtime, tokens) {
2542 tokens.matchToken("the"); // optional the
2543 return parser.parseElement("asyncExpression", tokens);
2544 });
2545
2546 _parser.addGrammarElement("targetExpression", function(parser, runtime, tokens) {
2547 tokens.matchToken("the"); // optional the
2548 // TODO put parser into mode where [ is interpreted as a property ref
2549 var expr = parser.parseElement("primaryExpression", tokens);
2550 if (expr.type === "symbol" || expr.type === "idRef" || expr.type === "inExpression" ||
2551 expr.type === "queryRef" || expr.type === "classRef" || expr.type === "ofExpression" ||
2552 expr.type === "propertyAccess") {
2553 return expr;
2554 } else {
2555 _parser.raiseParseError(tokens, "A target expression must be writable");
2556 }
2557 return expr;
2558 });
2559
2560 _parser.addGrammarElement("hyperscript", function(parser, runtime, tokens) {
2561
2562 var features = [];
2563
2564 if (tokens.hasMore()) {
2565 do {
2566 var feature = parser.requireElement("feature", tokens);
2567 features.push(feature);
2568 tokens.matchToken("end"); // optional end
2569 } while (parser.featureStart(tokens.currentToken()) || tokens.currentToken().value === "(")
2570 if (tokens.hasMore()) {
2571 parser.raiseParseError(tokens);
2572 }
2573 }
2574 return {
2575 type: "hyperscript",
2576 features: features,
2577 apply: function (target, source) {
2578 // no op
2579 _runtime.forEach(features, function(feature){
2580 feature.install(target, source);
2581 })
2582 }
2583 };
2584 })
2585
2586 _parser.addFeature("on", function(parser, runtime, tokens) {
2587 if (tokens.matchToken('on')) {
2588 var every = false;
2589 if (tokens.matchToken("every")) {
2590 every = true;
2591 }
2592 var events = [];
2593 var displayName = null;
2594 do {
2595
2596 var on = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
2597
2598 var eventName = on.evaluate(); // OK No Promise
2599 if (displayName) {
2600 displayName = displayName + " or " + eventName;
2601 } else {
2602 displayName = "on " + eventName;
2603 }
2604 var args = [];
2605 // handle argument list (look ahead 3)
2606 if (tokens.token(0).value === "(" &&
2607 (tokens.token(1).value === ")" ||
2608 tokens.token(2).value === "," ||
2609 tokens.token(2).value === ")")) {
2610 tokens.matchOpToken("(");
2611 do {
2612 args.push(tokens.requireTokenType('IDENTIFIER'));
2613 } while (tokens.matchOpToken(","))
2614 tokens.requireOpToken(')')
2615 }
2616
2617 var filter = null;
2618 if (tokens.matchOpToken('[')) {
2619 filter = parser.requireElement("expression", tokens);
2620 tokens.requireOpToken(']');
2621 }
2622
2623 if (tokens.currentToken().type === "NUMBER") {
2624 var startCountToken = tokens.consumeToken();
2625 var startCount = parseInt(startCountToken.value);
2626 if (tokens.matchToken("to")) {
2627 var endCountToken = tokens.consumeToken();
2628 var endCount = parseInt(endCountToken.value);
2629 } else if (tokens.matchToken("and")) {
2630 var unbounded = true;
2631 tokens.requireToken("on");
2632 }
2633 }
2634
2635 var from = null;
2636 var elsewhere = false;
2637 if (tokens.matchToken("from")) {
2638 if (tokens.matchToken('elsewhere')) {
2639 elsewhere = true;
2640 } else {
2641 from = parser.parseElement("targetExpression", tokens)
2642 if (!from) {
2643 parser.raiseParseError('Expected either target value or "elsewhere".', tokens);
2644 }
2645 }
2646 }
2647 // support both "elsewhere" and "from elsewhere"
2648 if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) {
2649 elsewhere = true;
2650 }
2651
2652 if (tokens.matchToken('in')) {
2653 var inExpr = parser.parseAnyOf(["idRef", "queryRef", "classRef"], tokens);
2654 }
2655
2656 if (tokens.matchToken('debounced')) {
2657 tokens.requireToken("at");
2658 var timeExpr = parser.requireElement("timeExpression", tokens);
2659 var debounceTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
2660 } else if (tokens.matchToken('throttled')) {
2661 tokens.requireToken("at");
2662 var timeExpr = parser.requireElement("timeExpression", tokens);
2663 var throttleTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
2664 }
2665
2666 events.push({
2667 execCount: 0,
2668 every: every,
2669 on: eventName,
2670 args: args,
2671 filter: filter,
2672 from:from,
2673 inExpr:inExpr,
2674 elsewhere:elsewhere,
2675 startCount : startCount,
2676 endCount : endCount,
2677 unbounded : unbounded,
2678 debounceTime : debounceTime,
2679 throttleTime : throttleTime,
2680 })
2681 } while (tokens.matchToken("or"))
2682
2683
2684 var queue = [];
2685 var queueLast = true;
2686 if (!every) {
2687 if (tokens.matchToken("queue")) {
2688 if (tokens.matchToken("all")) {
2689 var queueAll = true;
2690 var queueLast = false;
2691 } else if(tokens.matchToken("first")) {
2692 var queueFirst = true;
2693 } else if(tokens.matchToken("none")) {
2694 var queueNone = true;
2695 } else {
2696 tokens.requireToken("last");
2697 }
2698 }
2699 }
2700
2701 var start = parser.requireElement("commandList", tokens);
2702
2703 var implicitReturn = {
2704 type: "implicitReturn",
2705 op: function (context) {
2706 // automatically resolve at the end of an event handler if nothing else does
2707 context.meta.resolve();
2708 return runtime.HALT;
2709 },
2710 execute: function (ctx) {
2711 // do nothing
2712 }
2713 };
2714 if (start) {
2715 var end = start;
2716 while (end.next) {
2717 end = end.next;
2718 }
2719 end.next = implicitReturn
2720 } else {
2721 start = implicitReturn
2722 }
2723
2724 var onFeature = {
2725 displayName: displayName,
2726 events:events,
2727 start: start,
2728 every: every,
2729 executing: false,
2730 execCount: 0,
2731 queue: queue,
2732 execute: function (/** @type {Context} */ctx) {
2733 if (this.executing && this.every === false) {
2734 if (queueNone || (queueFirst && queue.length > 0)) {
2735 return;
2736 }
2737 if (queueLast) {
2738 onFeature.queue.length = 0;
2739 }
2740 onFeature.queue.push(ctx);
2741 return;
2742 }
2743 this.execCount++;
2744 this.executing = true;
2745 ctx.meta.resolve = function () {
2746 onFeature.executing = false;
2747 var queued = onFeature.queue.shift();
2748 if (queued) {
2749 setTimeout(function () {
2750 onFeature.execute(queued);
2751 }, 1);
2752 }
2753 }
2754 ctx.meta.reject = function (err) {
2755 console.error(err.message ? err.message : err);
2756 var hypertrace = runtime.getHyperTrace(ctx, err);
2757 if (hypertrace) {
2758 hypertrace.print();
2759 }
2760 runtime.triggerEvent(ctx.me, 'exception', {error: err})
2761 onFeature.executing = false;
2762 var queued = onFeature.queue.shift();
2763 if (queued) {
2764 setTimeout(function () {
2765 onFeature.execute(queued);
2766 }, 1);
2767 }
2768 }
2769 start.execute(ctx);
2770 },
2771 install: function (elt, source) {
2772 runtime.forEach(onFeature.events, function(eventSpec) {
2773 var targets;
2774 if (eventSpec.elsewhere) {
2775 targets = [document];
2776 } else if (eventSpec.from) {
2777 targets = eventSpec.from.evaluate({});
2778 } else {
2779 targets = [elt];
2780 }
2781 runtime.forEach(targets, function (target) { // OK NO PROMISE
2782 target.addEventListener(eventSpec.on, function (evt) { // OK NO PROMISE
2783 var ctx = runtime.makeContext(elt, onFeature, elt, evt);
2784 if (eventSpec.elsewhere && elt.contains(evt.target)) {
2785 return
2786 }
2787
2788 // establish context
2789 runtime.forEach(eventSpec.args, function (arg) {
2790 ctx[arg.value] = ctx.event[arg.value] || (ctx.event.detail ? ctx.event.detail[arg.value] : null);
2791 });
2792
2793 // apply filter
2794 if (eventSpec.filter) {
2795 var initialCtx = ctx.meta.context;
2796 ctx.meta.context = ctx.event;
2797 try {
2798 var value = eventSpec.filter.evaluate(ctx); //OK NO PROMISE
2799 if (value) {
2800 // match the javascript semantics for if statements
2801 } else {
2802 return;
2803 }
2804 } finally {
2805 ctx.meta.context = initialCtx;
2806 }
2807 }
2808
2809 if (eventSpec.inExpr) {
2810 var inElement = evt.target;
2811 while(true) {
2812 if (inElement.matches && inElement.matches(eventSpec.inExpr.css)) {
2813 ctx.result = inElement;
2814 break;
2815 } else {
2816 inElement = inElement.parentElement;
2817 if (inElement == null) {
2818 return; // no match found
2819 }
2820 }
2821 }
2822 }
2823
2824 // verify counts
2825 eventSpec.execCount++;
2826 if (eventSpec.startCount) {
2827 if (eventSpec.endCount) {
2828 if (eventSpec.execCount < eventSpec.startCount ||
2829 eventSpec.execCount > eventSpec.endCount) {
2830 return;
2831 }
2832 } else if (eventSpec.unbounded) {
2833 if (eventSpec.execCount < eventSpec.startCount) {
2834 return;
2835 }
2836 } else if (eventSpec.execCount !== eventSpec.startCount) {
2837 return;
2838 }
2839 }
2840
2841 //debounce
2842 if (eventSpec.debounceTime) {
2843 if (eventSpec.debounced) {
2844 clearTimeout(eventSpec.debounced);
2845 }
2846 eventSpec.debounced = setTimeout(function () {
2847 onFeature.execute(ctx);
2848 }, eventSpec.debounceTime);
2849 return;
2850 }
2851
2852 // throttle
2853 if (eventSpec.throttleTime) {
2854 if (eventSpec.lastExec && Date.now() < eventSpec.lastExec + eventSpec.throttleTime) {
2855 return;
2856 } else {
2857 eventSpec.lastExec = Date.now();
2858 }
2859 }
2860
2861 // apply execute
2862 onFeature.execute(ctx);
2863 });
2864 })
2865 });
2866 }
2867 };
2868 parser.setParent(start, onFeature);
2869 return onFeature;
2870 }
2871 });
2872
2873 _parser.addFeature("def", function(parser, runtime, tokens) {
2874 if (tokens.matchToken('def')) {
2875 var functionName = parser.requireElement("dotOrColonPath", tokens);
2876 var nameVal = functionName.evaluate(); // OK
2877 var nameSpace = nameVal.split(".");
2878 var funcName = nameSpace.pop();
2879
2880 var args = [];
2881 if (tokens.matchOpToken("(")) {
2882 if (tokens.matchOpToken(")")) {
2883 // emtpy args list
2884 } else {
2885 do {
2886 args.push(tokens.requireTokenType('IDENTIFIER'));
2887 } while (tokens.matchOpToken(","))
2888 tokens.requireOpToken(')')
2889 }
2890 }
2891
2892 var start = parser.parseElement("commandList", tokens);
2893 if (tokens.matchToken('catch')) {
2894 var errorSymbol = tokens.requireTokenType('IDENTIFIER').value;
2895 var errorHandler = parser.parseElement("commandList", tokens);
2896 }
2897 var functionFeature = {
2898 displayName: funcName + "(" + args.map(function(arg){ return arg.value }).join(", ") + ")",
2899 name: funcName,
2900 args: args,
2901 start: start,
2902 errorHandler: errorHandler,
2903 errorSymbol: errorSymbol,
2904 install: function (target, source) {
2905 var func = function () {
2906 // null, worker
2907 var elt = 'document' in globalScope ? document.body : globalScope
2908 var ctx = runtime.makeContext(source, functionFeature, elt, null);
2909
2910 // install error handler if any
2911 ctx.meta.errorHandler = errorHandler;
2912 ctx.meta.errorSymmbol = errorSymbol;
2913
2914 for (var i = 0; i < args.length; i++) {
2915 var name = args[i];
2916 var argumentVal = arguments[i];
2917 if (name) {
2918 ctx[name.value] = argumentVal;
2919 }
2920 }
2921 ctx.meta.caller = arguments[args.length];
2922 if (ctx.meta.caller) {
2923 ctx.meta.callingCommand = ctx.meta.caller.meta.command;
2924 }
2925 var resolve, reject = null;
2926 var promise = new Promise(function (theResolve, theReject) {
2927 resolve = theResolve;
2928 reject = theReject;
2929 });
2930 start.execute(ctx);
2931 if (ctx.meta.returned) {
2932 return ctx.meta.returnValue;
2933 } else {
2934 ctx.meta.resolve = resolve;
2935 ctx.meta.reject = reject;
2936 return promise
2937 }
2938 };
2939 func.hyperfunc = true;
2940 func.hypername = nameVal;
2941 runtime.assignToNamespace(nameSpace, funcName, func);
2942 }
2943 };
2944
2945 var implicitReturn = {
2946 type: "implicitReturn",
2947 op: function (context) {
2948 // automatically return at the end of the function if nothing else does
2949 context.meta.returned = true;
2950 if (context.meta.resolve) {
2951 context.meta.resolve();
2952 }
2953 return runtime.HALT;
2954 },
2955 execute: function (context) {
2956 // do nothing
2957 }
2958 }
2959 // terminate body
2960 if (start) {
2961 var end = start;
2962 while (end.next) {
2963 end = end.next;
2964 }
2965 end.next = implicitReturn
2966 } else {
2967 functionFeature.start = implicitReturn
2968 }
2969
2970 // terminate error handler
2971 if (errorHandler) {
2972 var end = errorHandler;
2973 while (end.next) {
2974 end = end.next;
2975 }
2976 end.next = implicitReturn
2977 }
2978
2979 parser.setParent(start, functionFeature);
2980 return functionFeature;
2981 }
2982 });
2983
2984 _parser.addFeature("init", function(parser, runtime, tokens) {
2985 if (tokens.matchToken('init')) {
2986 var start = parser.parseElement("commandList", tokens);
2987 var initFeature = {
2988 start: start,
2989 install: function (target, source) {
2990 setTimeout(function () {
2991 start.execute(runtime.makeContext(target, this, target));
2992 }, 0);
2993 }
2994 };
2995
2996 var implicitReturn = {
2997 type: "implicitReturn",
2998 op: function (context) {
2999 return runtime.HALT;
3000 },
3001 execute: function (context) {
3002 // do nothing
3003 }
3004 }
3005 // terminate body
3006 if (start) {
3007 var end = start;
3008 while (end.next) {
3009 end = end.next;
3010 }
3011 end.next = implicitReturn
3012 } else {
3013 initFeature.start = implicitReturn
3014 }
3015 parser.setParent(start, initFeature);
3016 return initFeature;
3017 }
3018 });
3019
3020 _parser.addFeature("worker", function (parser, runtime, tokens) {
3021 if (tokens.matchToken("worker")) {
3022 parser.raiseParseError(tokens,
3023 "In order to use the 'worker' feature, include " +
3024 "the _hyperscript worker plugin. See " +
3025 "https://hyperscript.org/features/worker/ for " +
3026 "more info.")
3027 }
3028 })
3029
3030 _parser.addGrammarElement("jsBody", function(parser, runtime, tokens) {
3031 var jsSourceStart = tokens.currentToken().start;
3032 var jsLastToken = tokens.currentToken();
3033
3034 var funcNames = [];
3035 var funcName = "";
3036 var expectFunctionDeclaration = false;
3037 while (tokens.hasMore()) {
3038 jsLastToken = tokens.consumeToken();
3039 var peek = tokens.currentToken(true);
3040 if (peek.type === "IDENTIFIER"
3041 && peek.value === "end") {
3042 break;
3043 }
3044 if (expectFunctionDeclaration) {
3045 if (jsLastToken.type === "IDENTIFIER"
3046 || jsLastToken.type === "NUMBER") {
3047 funcName += jsLastToken.value;
3048 } else {
3049 if (funcName !== "") funcNames.push(funcName);
3050 funcName = "";
3051 expectFunctionDeclaration = false;
3052 }
3053 } else if (jsLastToken.type === "IDENTIFIER"
3054 && jsLastToken.value === "function") {
3055 expectFunctionDeclaration = true;
3056 }
3057 }
3058 var jsSourceEnd = jsLastToken.end + 1;
3059
3060 return {
3061 type: 'jsBody',
3062 exposedFunctionNames: funcNames,
3063 jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd),
3064 }
3065 })
3066
3067 _parser.addFeature("js", function(parser, runtime, tokens) {
3068 if (tokens.matchToken('js')) {
3069
3070 var jsBody = parser.parseElement('jsBody', tokens);
3071
3072 var jsSource = jsBody.jsSource +
3073 "\nreturn { " +
3074 jsBody.exposedFunctionNames.map(function (name) {
3075 return name+":"+name;
3076 }).join(",") +
3077 " } ";
3078 var func = new Function(jsSource);
3079
3080 return {
3081 jsSource: jsSource,
3082 function: func,
3083 exposedFunctionNames: jsBody.exposedFunctionNames,
3084 install: function() {
3085 mergeObjects(globalScope, func())
3086 }
3087 }
3088 }
3089 })
3090
3091 _parser.addCommand("js", function (parser, runtime, tokens) {
3092 if (tokens.matchToken("js")) {
3093 // Parse inputs
3094 var inputs = [];
3095 if (tokens.matchOpToken("(")) {
3096 if (tokens.matchOpToken(")")) {
3097 // empty input list
3098 } else {
3099 do {
3100 var inp = tokens.requireTokenType('IDENTIFIER');
3101 inputs.push(inp.value);
3102 } while (tokens.matchOpToken(","));
3103 tokens.requireOpToken(')');
3104 }
3105 }
3106
3107 var jsBody = parser.parseElement('jsBody', tokens);
3108 tokens.matchToken('end');
3109
3110 var func = varargConstructor(Function, inputs.concat([jsBody.jsSource]));
3111
3112 return {
3113 jsSource: jsBody.jsSource,
3114 function: func,
3115 inputs: inputs,
3116 op: function (context) {
3117 var args = [];
3118 inputs.forEach(function (input) {
3119 args.push(runtime.resolveSymbol(input, context))
3120 });
3121 var result = func.apply(globalScope, args)
3122 if (result && typeof result.then === 'function') {
3123 return Promise(function (resolve) {
3124 result.then(function (actualResult) {
3125 context.result = actualResult
3126 resolve(runtime.findNext(this, context));
3127 })
3128 })
3129 } else {
3130 context.result = result
3131 return runtime.findNext(this, context);
3132 }
3133 }
3134 };
3135 }
3136 })
3137
3138 _parser.addCommand("async", function (parser, runtime, tokens) {
3139 if (tokens.matchToken("async")) {
3140 if (tokens.matchToken("do")) {
3141 var body = parser.requireElement('commandList', tokens)
3142 tokens.requireToken("end")
3143 } else {
3144 var body = parser.requireElement('command', tokens)
3145 }
3146 return {
3147 body: body,
3148 op: function (context) {
3149 setTimeout(function(){
3150 body.execute(context);
3151 })
3152 return runtime.findNext(this, context);
3153 }
3154 };
3155 }
3156 })
3157
3158 _parser.addCommand("with", function (parser, runtime, tokens) {
3159 var startToken = tokens.currentToken();
3160 if (tokens.matchToken("with")) {
3161 var value = parser.requireElement("expression", tokens);
3162 var body = parser.requireElement('commandList', tokens)
3163 if (tokens.hasMore()) {
3164 tokens.requireToken("end");
3165 }
3166 var slot = "with_" + startToken.start;
3167 var withCmd = {
3168 value: value,
3169 body: body,
3170 args: [value],
3171 resolveNext: function (context) {
3172 var iterator = context.meta.iterators[slot];
3173 if (iterator.index < iterator.value.length) {
3174 context.me = iterator.value[iterator.index++];
3175 return body;
3176 } else {
3177 // restore original me
3178 context.me = iterator.originalMe;
3179 if (this.next) {
3180 return this.next;
3181 } else {
3182 return runtime.findNext(this.parent, context);
3183 }
3184 }
3185 },
3186 op: function (context, value) {
3187 if (value == null) {
3188 value = [];
3189 } else if (!(Array.isArray(value) || value instanceof NodeList)) {
3190 value = [value];
3191 }
3192 context.meta.iterators[slot] = {
3193 originalMe: context.me,
3194 index: 0,
3195 value: value
3196 };
3197 return this.resolveNext(context);
3198 }
3199 };
3200 parser.setParent(body, withCmd);
3201 return withCmd;
3202 }
3203 })
3204
3205 _parser.addCommand("wait", function(parser, runtime, tokens) {
3206 if (tokens.matchToken("wait")) {
3207 // wait on event
3208 if (tokens.matchToken("for")) {
3209 tokens.matchToken("a"); // optional "a"
3210 var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
3211 if (tokens.matchToken("from")) {
3212 var on = parser.requireElement("expression", tokens);
3213 }
3214 // wait on event
3215 var waitCmd = {
3216 event: evt,
3217 on: on,
3218 args: [evt, on],
3219 op: function (context, eventName, on) {
3220 var target = on ? on : context.me;
3221 return new Promise(function (resolve) {
3222 var listener = function () {
3223 resolve(runtime.findNext(waitCmd, context));
3224 };
3225 target.addEventListener(eventName, listener, {once: true});
3226 });
3227 }
3228 };
3229 } else {
3230 if(tokens.matchToken("a")){
3231 tokens.requireToken('tick');
3232 time = 0;
3233 } else {
3234 var time = _parser.requireElement("timeExpression", tokens);
3235 }
3236
3237 var waitCmd = {
3238 type: "waitCmd",
3239 time: time,
3240 args: [time],
3241 op: function (context, timeValue) {
3242 return new Promise(function (resolve) {
3243 setTimeout(function () {
3244 resolve(runtime.findNext(waitCmd, context));
3245 }, timeValue);
3246 });
3247 },
3248 execute: function (context) {
3249 return runtime.unifiedExec(this, context);
3250 }
3251 };
3252 }
3253 return waitCmd
3254 }
3255 })
3256
3257 // TODO - colon path needs to eventually become part of ruby-style symbols
3258 _parser.addGrammarElement("dotOrColonPath", function(parser, runtime, tokens) {
3259 var root = tokens.matchTokenType("IDENTIFIER");
3260 if (root) {
3261 var path = [root.value];
3262
3263 var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":");
3264 if (separator) {
3265 do {
3266 path.push(tokens.requireTokenType("IDENTIFIER").value);
3267 } while (tokens.matchOpToken(separator.value))
3268 }
3269
3270 return {
3271 type: "dotOrColonPath",
3272 path: path,
3273 evaluate: function () {
3274 return path.join(separator ? separator.value : "");
3275 }
3276 }
3277 }
3278 });
3279
3280 _parser.addCommand("send", function(parser, runtime, tokens) {
3281 if (tokens.matchToken('send')) {
3282 var eventName = parser.requireElement("dotOrColonPath", tokens);
3283
3284 var details = parser.parseElement("namedArgumentList", tokens);
3285 if (tokens.matchToken("to")) {
3286 var to = parser.requireElement("targetExpression", tokens);
3287 } else {
3288 var to = parser.requireElement("implicitMeTarget", tokens);
3289 }
3290
3291
3292 var sendCmd = {
3293 eventName: eventName,
3294 details: details,
3295 to: to,
3296 args: [to, eventName, details],
3297 op: function (context, to, eventName, details) {
3298 runtime.forEach(to, function (target) {
3299 runtime.triggerEvent(target, eventName, details ? details : {});
3300 });
3301 return runtime.findNext(sendCmd, context);
3302 }
3303 };
3304 return sendCmd
3305 }
3306 })
3307
3308 _parser.addCommand("return", function(parser, runtime, tokens) {
3309 if (tokens.matchToken('return')) {
3310 var value = parser.requireElement("expression", tokens);
3311
3312 var returnCmd = {
3313 value: value,
3314 args: [value],
3315 op: function (context, value) {
3316 var resolve = context.meta.resolve;
3317 context.meta.returned = true;
3318 if (resolve) {
3319 if (value) {
3320 resolve(value);
3321 } else {
3322 resolve()
3323 }
3324 } else {
3325 context.meta.returned = true;
3326 context.meta.returnValue = value;
3327 }
3328 return runtime.HALT;
3329 }
3330 };
3331 return returnCmd
3332 }
3333 })
3334
3335 _parser.addCommand("log", function(parser, runtime, tokens) {
3336 if (tokens.matchToken('log')) {
3337 var exprs = [parser.parseElement("expression", tokens)];
3338 while (tokens.matchOpToken(",")) {
3339 exprs.push(parser.requireElement("expression", tokens));
3340 }
3341 if (tokens.matchToken("with")) {
3342 var withExpr = parser.requireElement("expression", tokens);
3343 }
3344 var logCmd = {
3345 exprs: exprs,
3346 withExpr: withExpr,
3347 args: [withExpr, exprs],
3348 op: function (ctx, withExpr, values) {
3349 if (withExpr) {
3350 withExpr.apply(null, values);
3351 } else {
3352 console.log.apply(null, values);
3353 }
3354 return runtime.findNext(this, ctx);
3355 }
3356 };
3357 return logCmd;
3358 }
3359 })
3360
3361 _parser.addCommand("throw", function(parser, runtime, tokens) {
3362 if (tokens.matchToken('throw')) {
3363 var expr = parser.requireElement("expression", tokens);
3364 var throwCmd = {
3365 expr: expr,
3366 args: [expr],
3367 op: function (ctx, expr) {
3368 runtime.registerHyperTrace(ctx, expr);
3369 var reject = ctx.meta && ctx.meta.reject;
3370 if (reject) {
3371 reject(expr);
3372 return runtime.HALT;
3373 } else {
3374 throw expr;
3375 }
3376 }
3377 };
3378 return throwCmd;
3379 }
3380 })
3381
3382 var parseCallOrGet = function(parser, runtime, tokens) {
3383 var expr = parser.requireElement("expression", tokens);
3384 var callCmd = {
3385 expr: expr,
3386 args: [expr],
3387 op: function (context, result) {
3388 context.result = result;
3389 return runtime.findNext(callCmd, context);
3390 }
3391 };
3392 return callCmd
3393 }
3394 _parser.addCommand("call", function(parser, runtime, tokens) {
3395 if (tokens.matchToken('call')) {
3396 var call = parseCallOrGet(parser, runtime, tokens);
3397 if (call.expr && call.expr.type !== "functionCall") {
3398 parser.raiseParseError(tokens, "Must be a function invocation");
3399 }
3400 return call;
3401 }
3402 })
3403 _parser.addCommand("get", function(parser, runtime, tokens) {
3404 if (tokens.matchToken('get')) {
3405 return parseCallOrGet(parser, runtime, tokens);
3406 }
3407 })
3408
3409 _parser.addGrammarElement("pseudoCommand", function(parser, runtime, tokens) {
3410 var expr = parser.requireElement("primaryExpression", tokens);
3411 if (expr.type !== 'functionCall' && expr.root.type !== "symbol") {
3412 parser.raiseParseError("Implicit function calls must start with a simple function", tokens);
3413 }
3414 // optional "on", "with", or "to"
3415 if (!tokens.matchAnyToken("to","on","with") && parser.commandBoundary(tokens.currentToken())) {
3416 var target = parser.requireElement("implicitMeTarget", tokens);
3417 } else {
3418 var target = parser.requireElement("expression", tokens);
3419 }
3420 var functionName = expr.root.name;
3421 var functionArgs = expr.argExressions;
3422
3423 var pseudoCommand = {
3424 type: "pseudoCommand",
3425 expr: expr,
3426 args: [target, functionArgs],
3427 op: function (context, target, args) {
3428 var func = target[functionName];
3429 if (func.hyperfunc) {
3430 args.push(context);
3431 }
3432 var result = func.apply(target, args);
3433 context.result = result;
3434 return runtime.findNext(pseudoCommand, context);
3435 },
3436 execute : function (context) {
3437 return runtime.unifiedExec(this, context);
3438 }
3439 };
3440
3441 return pseudoCommand;
3442 })
3443
3444 _parser.addCommand("set", function(parser, runtime, tokens) {
3445 if (tokens.matchToken('set')) {
3446 if (tokens.currentToken().type === "L_BRACE") {
3447 var obj = parser.requireElement("objectLiteral", tokens);
3448 tokens.requireToken("on");
3449 var target = parser.requireElement("expression", tokens);
3450
3451 return {
3452 objectLiteral: obj,
3453 target: target,
3454 args: [obj, target],
3455 op: function (ctx, obj, target) {
3456 mergeObjects(target, obj);
3457 }
3458 }
3459 }
3460
3461 var target = parser.requireElement("targetExpression", tokens);
3462
3463 tokens.requireToken("to");
3464
3465 var value = parser.requireElement("expression", tokens);
3466
3467 var symbolWrite = target.type === "symbol";
3468 if (target.type !== "symbol" && target.root == null) {
3469 parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
3470 }
3471
3472 var root = null;
3473 var prop = null;
3474 if (symbolWrite) {
3475 // root is null
3476 } else {
3477 prop = target.prop.value;
3478 root = target.root;
3479 }
3480
3481 var setCmd = {
3482 target: target,
3483 symbolWrite: symbolWrite,
3484 value: value,
3485 args: [root, value],
3486 op: function (context, root, valueToSet) {
3487 if (symbolWrite) {
3488 context[target.name] = valueToSet;
3489 } else {
3490 runtime.forEach(root, function (elt) {
3491 elt[prop] = valueToSet;
3492 })
3493 }
3494 return runtime.findNext(this, context);
3495 }
3496 };
3497 return setCmd
3498 }
3499 })
3500
3501 _parser.addCommand("if", function(parser, runtime, tokens) {
3502 if (tokens.matchToken('if')) {
3503 var expr = parser.requireElement("expression", tokens);
3504 tokens.matchToken("then"); // optional 'then'
3505 var trueBranch = parser.parseElement("commandList", tokens);
3506 if (tokens.matchToken("else")) {
3507 var falseBranch = parser.parseElement("commandList", tokens);
3508 }
3509 if (tokens.hasMore()) {
3510 tokens.requireToken("end");
3511 }
3512 var ifCmd = {
3513 expr: expr,
3514 trueBranch: trueBranch,
3515 falseBranch: falseBranch,
3516 args: [expr],
3517 op: function (context, exprValue) {
3518 if (exprValue) {
3519 return trueBranch;
3520 } else if (falseBranch) {
3521 return falseBranch;
3522 } else {
3523 return runtime.findNext(this, context);
3524 }
3525 }
3526 };
3527 parser.setParent(trueBranch, ifCmd);
3528 parser.setParent(falseBranch, ifCmd);
3529 return ifCmd
3530 }
3531 })
3532
3533 var parseRepeatExpression = function(parser, tokens, runtime, startedWithForToken) {
3534 var innerStartToken = tokens.currentToken();
3535 if (tokens.matchToken("for") || startedWithForToken) {
3536 var identifierToken = tokens.requireTokenType('IDENTIFIER');
3537 var identifier = identifierToken.value;
3538 tokens.requireToken("in");
3539 var expression = parser.requireElement("expression", tokens);
3540 } else if (tokens.matchToken("in")) {
3541 var identifier = "it";
3542 var expression = parser.requireElement("expression", tokens);
3543 } else if (tokens.matchToken("while")) {
3544 var whileExpr = parser.requireElement("expression", tokens);
3545 } else if (tokens.matchToken("until")) {
3546 var isUntil = true;
3547 if (tokens.matchToken("event")) {
3548 var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
3549 if (tokens.matchToken("from")) {
3550 var on = parser.requireElement("expression", tokens);
3551 }
3552 } else {
3553 var whileExpr = parser.requireElement("expression", tokens);
3554 }
3555 } else if (tokens.matchTokenType('NUMBER')) {
3556 var times = parseFloat(innerStartToken.value);
3557 tokens.requireToken('times');
3558 } else {
3559 tokens.matchToken("forever"); // consume optional forever
3560 var forever = true;
3561 }
3562
3563 if (tokens.matchToken("index")) {
3564 var identifierToken = tokens.requireTokenType('IDENTIFIER');
3565 var indexIdentifier = identifierToken.value
3566 }
3567
3568 var loop = parser.parseElement("commandList", tokens);
3569 if (tokens.hasMore()) {
3570 tokens.requireToken("end");
3571 }
3572
3573 if (identifier == null) {
3574 identifier = "_implicit_repeat_" + innerStartToken.start;
3575 var slot = identifier;
3576 } else {
3577 var slot = identifier + "_" + innerStartToken.start;
3578 }
3579
3580 var repeatCmd = {
3581 identifier: identifier,
3582 indexIdentifier: indexIdentifier,
3583 slot: slot,
3584 expression: expression,
3585 forever: forever,
3586 times: times,
3587 until: isUntil,
3588 event: evt,
3589 on: on,
3590 whileExpr: whileExpr,
3591 resolveNext: function () {
3592 return this;
3593 },
3594 loop: loop,
3595 args: [whileExpr],
3596 op: function (context, whileValue) {
3597 var iterator = context.meta.iterators[slot];
3598 var keepLooping = false;
3599 if (this.forever) {
3600 keepLooping = true;
3601 } else if (this.until) {
3602 if (evt) {
3603 keepLooping = context.meta.iterators[slot].eventFired === false;
3604 } else {
3605 keepLooping = whileValue !== true;
3606 }
3607 } else if (whileValue) {
3608 keepLooping = true;
3609 } else if (times) {
3610 keepLooping = iterator.index < this.times;
3611 } else {
3612 keepLooping = iterator.value !== null && iterator.index < iterator.value.length
3613 }
3614
3615 if (keepLooping) {
3616 if (iterator.value) {
3617 context[identifier] = iterator.value[iterator.index];
3618 context.result = iterator.value[iterator.index];
3619 } else {
3620 context.result = iterator.index;
3621 }
3622 if (indexIdentifier) {
3623 context[indexIdentifier] = iterator.index;
3624 }
3625 iterator.index++;
3626 return loop;
3627 } else {
3628 context.meta.iterators[slot] = null;
3629 return runtime.findNext(this.parent, context);
3630 }
3631 }
3632 };
3633 parser.setParent(loop, repeatCmd);
3634 var repeatInit = {
3635 name: "repeatInit",
3636 args: [expression, evt, on],
3637 op: function (context, value, event, on) {
3638 context.meta.iterators[slot] = {
3639 index: 0,
3640 value: value,
3641 eventFired: false
3642 };
3643 if (evt) {
3644 var target = on || context.me;
3645 target.addEventListener(event, function (e) {
3646 context.meta.iterators[slot].eventFired = true;
3647 }, {once: true});
3648 }
3649 return repeatCmd; // continue to loop
3650 },
3651 execute: function (context) {
3652 return runtime.unifiedExec(this, context);
3653 }
3654 }
3655 parser.setParent(repeatCmd, repeatInit);
3656 return repeatInit
3657 }
3658
3659 _parser.addCommand("repeat", function(parser, runtime, tokens) {
3660 if (tokens.matchToken('repeat')) {
3661 return parseRepeatExpression(parser, tokens, runtime,false);
3662 }
3663 })
3664
3665 _parser.addCommand("for", function(parser, runtime, tokens) {
3666 if (tokens.matchToken('for')) {
3667 return parseRepeatExpression(parser, tokens, runtime, true);
3668 }
3669 })
3670
3671
3672 _parser.addGrammarElement("stringLike", function(parser, runtime, tokens) {
3673 return _parser.parseAnyOf(["string", "nakedString"], tokens);
3674 });
3675
3676 _parser.addCommand("fetch", function(parser, runtime, tokens) {
3677 if (tokens.matchToken('fetch')) {
3678
3679
3680 var url = parser.requireElement("stringLike", tokens);
3681 var args = parser.parseElement("objectLiteral", tokens);
3682
3683 var type = "text";
3684 if (tokens.matchToken("as")) {
3685 if (tokens.matchToken("json")) {
3686 type = "json";
3687 } else if (tokens.matchToken("response")) {
3688 type = "response";
3689 } else if (tokens.matchToken("text")) {
3690 } else {
3691 parser.raiseParseError(tokens, "Unknown response type: " + tokens.currentToken());
3692 }
3693 }
3694
3695 var fetchCmd = {
3696 url:url,
3697 argExrepssions:args,
3698 args: [url, args],
3699 op: function (context, url, args) {
3700 return new Promise(function (resolve, reject) {
3701 fetch(url, args)
3702 .then(function (value) {
3703 if (type === "response") {
3704 context.result = value;
3705 resolve(runtime.findNext(fetchCmd, context));
3706 } else if (type === "json") {
3707 value.json().then(function (result) {
3708 context.result = result;
3709 resolve(runtime.findNext(fetchCmd, context));
3710 })
3711 } else {
3712 value.text().then(function (result) {
3713 context.result = result;
3714 resolve(runtime.findNext(fetchCmd, context));
3715 })
3716 }
3717 })
3718 .catch(function (reason) {
3719 runtime.triggerEvent(context.me, "fetch:error", {
3720 reason: reason
3721 })
3722 reject(reason);
3723 })
3724 })
3725 }
3726 };
3727 return fetchCmd;
3728 }
3729 })
3730 }
3731
3732 //====================================================================
3733 // Initialization
3734 //====================================================================
3735 function ready(fn) {
3736 if (document.readyState !== 'loading') {
3737 fn();
3738 } else {
3739 document.addEventListener('DOMContentLoaded', fn);
3740 }
3741 }
3742
3743 function getMetaConfig() {
3744 var element = document.querySelector('meta[name="htmx-config"]');
3745 if (element) {
3746 return parseJSON(element.content);
3747 } else {
3748 return null;
3749 }
3750 }
3751
3752 function mergeMetaConfig() {
3753 var metaConfig = getMetaConfig();
3754 if (metaConfig) {
3755 _hyperscript.config = mergeObjects(_hyperscript.config , metaConfig)
3756 }
3757 }
3758
3759 if ('document' in globalScope) {
3760 ready(function () {
3761 mergeMetaConfig();
3762 _runtime.processNode(document.body);
3763 document.addEventListener("htmx:load", function(evt){
3764 _runtime.processNode(evt.detail.elt);
3765 })
3766 })
3767 }
3768
3769 //====================================================================
3770 // API
3771 //====================================================================
3772 return mergeObjects(function (str, ctx) {
3773 return _runtime.evaluate(str, ctx); //OK
3774 }, {
3775 internals: {
3776 lexer: _lexer,
3777 parser: _parser,
3778 runtime: _runtime,
3779 },
3780 addFeature: function (keyword, definition) {
3781 _parser.addFeature(keyword, definition);
3782 },
3783 addCommand: function (keyword, definition) {
3784 _parser.addCommand(keyword, definition);
3785 },
3786 addLeafExpression: function (name, definition) {
3787 _parser.addLeafExpression(name, definition);
3788 },
3789 addIndirectExpression: function (keyword, definition) {
3790 _parser.addIndirectExpression(definition);
3791 },
3792 evaluate: function (str, ctx) { //OK
3793 return _runtime.evaluate(str, ctx); //OK
3794 },
3795 parse: function (str) { //OK
3796 return _runtime.parse(str); //OK
3797 },
3798 processNode: function (elt) {
3799 _runtime.processNode(elt);
3800 },
3801 config: {
3802 attributes: "_, script, data-script",
3803 defaultTransition: "all 500ms ease-in",
3804 conversions: CONVERSIONS
3805 }
3806 }
3807 )
3808 }
3809 )()
3810}));
3811
3812///=========================================================================
3813/// This module provides the core web functionality for hyperscript
3814///=========================================================================
3815(function(){
3816
3817 function mergeObjects(obj1, obj2) {
3818 for (var key in obj2) {
3819 if (obj2.hasOwnProperty(key)) {
3820 obj1[key] = obj2[key];
3821 }
3822 }
3823 return obj1;
3824 }
3825
3826 _hyperscript.addCommand("settle", function(parser, runtime, tokens) {
3827 if (tokens.matchToken("settle")) {
3828
3829 if (!parser.commandBoundary(tokens.currentToken())) {
3830 var on = parser.requireElement("expression", tokens);
3831 } else {
3832 var on = parser.requireElement("implicitMeTarget", tokens);
3833 }
3834
3835 var settleCommand = {
3836 type: "settleCmd",
3837 args: [on],
3838 op: function (context, on) {
3839 var resolve = null;
3840 var resolved = false;
3841 var transitionStarted = false;
3842
3843 var promise = new Promise(function (r) {
3844 resolve = r;
3845 });
3846
3847 // listen for a transition begin
3848 on.addEventListener('transitionstart', function () {
3849 transitionStarted = true;
3850 }, {once: true});
3851
3852 // if no transition begins in 500ms, cancel
3853 setTimeout(function () {
3854 if (!transitionStarted && !resolved) {
3855 resolve(runtime.findNext(settleCommand, context));
3856 }
3857 }, 500);
3858
3859 // continue on a transition emd
3860 on.addEventListener('transitionend', function () {
3861 if (!resolved) {
3862 resolve(runtime.findNext(settleCommand, context));
3863 }
3864 }, {once: true});
3865 return promise;
3866
3867 },
3868 execute: function (context) {
3869 return runtime.unifiedExec(this, context);
3870 }
3871 };
3872 return settleCommand
3873 }
3874 })
3875
3876 _hyperscript.addCommand("add", function(parser, runtime, tokens) {
3877 if (tokens.matchToken("add")) {
3878 var classRef = parser.parseElement("classRef", tokens);
3879 var attributeRef = null;
3880 var cssDeclaration = null;
3881 if (classRef == null) {
3882 attributeRef = parser.parseElement("attributeRef", tokens);
3883 if (attributeRef == null) {
3884 cssDeclaration = parser.parseElement("objectLiteral", tokens);
3885 if (cssDeclaration == null) {
3886 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
3887 }
3888 }
3889 } else {
3890 var classRefs = [classRef];
3891 while (classRef = parser.parseElement("classRef", tokens)) {
3892 classRefs.push(classRef);
3893 }
3894 }
3895
3896 if (tokens.matchToken("to")) {
3897 var to = parser.requireElement("targetExpression", tokens);
3898 } else {
3899 var to = parser.parseElement("implicitMeTarget", tokens);
3900 }
3901
3902 if (classRefs) {
3903 var addCmd = {
3904 classRefs: classRefs,
3905 to: to,
3906 args: [to],
3907 op: function (context, to) {
3908 runtime.forEach(classRefs, function (classRef) {
3909 runtime.forEach(to, function (target) {
3910 target.classList.add(classRef.className());
3911 })
3912 });
3913 return runtime.findNext(this, context);
3914 }
3915 }
3916 } else if (attributeRef) {
3917 var addCmd = {
3918 type: "addCmd",
3919 attributeRef: attributeRef,
3920 to: to,
3921 args: [to, attributeRef],
3922 op: function (context, to, attrRef) {
3923 runtime.forEach(to, function (target) {
3924 target.setAttribute(attrRef.name, attrRef.value);
3925 })
3926 return runtime.findNext(addCmd, context);
3927 },
3928 execute: function (ctx) {
3929 return runtime.unifiedExec(this, ctx);
3930 }
3931 };
3932 } else {
3933 var addCmd = {
3934 type: "addCmd",
3935 cssDeclaration: cssDeclaration,
3936 to: to,
3937 args: [to, cssDeclaration],
3938 op: function (context, to, css) {
3939 runtime.forEach(to, function (target) {
3940 for (var key in css) {
3941 if (css.hasOwnProperty(key)) {
3942 target.style.setProperty(key, css[key]);
3943 }
3944 }
3945 })
3946 return runtime.findNext(addCmd, context);
3947 },
3948 execute: function (ctx) {
3949 return runtime.unifiedExec(this, ctx);
3950 }
3951 };
3952 }
3953 return addCmd
3954 }
3955 });
3956
3957 _hyperscript.addCommand("remove", function(parser, runtime, tokens) {
3958 if (tokens.matchToken('remove')) {
3959 var classRef = parser.parseElement("classRef", tokens);
3960 var attributeRef = null;
3961 var elementExpr = null;
3962 if (classRef == null) {
3963 attributeRef = parser.parseElement("attributeRef", tokens);
3964 if (attributeRef == null) {
3965 elementExpr = parser.parseElement("expression", tokens)
3966 if (elementExpr == null) {
3967 parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression");
3968 }
3969 }
3970 } else {
3971 var classRefs = [classRef];
3972 while (classRef = parser.parseElement("classRef", tokens)) {
3973 classRefs.push(classRef);
3974 }
3975 }
3976
3977 if (tokens.matchToken("from")) {
3978 var from = parser.requireElement("targetExpression", tokens);
3979 } else {
3980 var from = parser.requireElement("implicitMeTarget", tokens);
3981 }
3982
3983 if (elementExpr) {
3984 var removeCmd = {
3985 elementExpr: elementExpr,
3986 from: from,
3987 args: [elementExpr],
3988 op: function (context, element) {
3989 runtime.forEach(element, function (target) {
3990 target.parentElement.removeChild(target);
3991 })
3992 return runtime.findNext(this, context);
3993 }
3994 };
3995 } else {
3996 var removeCmd = {
3997 classRefs: classRefs,
3998 attributeRef: attributeRef,
3999 elementExpr: elementExpr,
4000 from: from,
4001 args: [from],
4002 op: function (context, from) {
4003 if (this.classRefs) {
4004 runtime.forEach(classRefs, function (classRef) {
4005 runtime.forEach(from, function (target) {
4006 target.classList.remove(classRef.className());
4007 })
4008 });
4009 } else {
4010 runtime.forEach(from, function (target) {
4011 target.removeAttribute(attributeRef.name);
4012 })
4013 }
4014 return runtime.findNext(this, context);
4015 }
4016 };
4017
4018 }
4019 return removeCmd
4020 }
4021 });
4022
4023 _hyperscript.addCommand("toggle", function(parser, runtime, tokens) {
4024 if (tokens.matchToken('toggle')) {
4025
4026 if (tokens.matchToken('between')) {
4027 var between = true;
4028 var classRef = parser.parseElement("classRef", tokens);
4029 tokens.requireToken("and");
4030 var classRef2 = parser.requireElement("classRef", tokens);
4031 } else {
4032 var classRef = parser.parseElement("classRef", tokens);
4033 var attributeRef = null;
4034 if (classRef == null) {
4035 attributeRef = parser.parseElement("attributeRef", tokens);
4036 if (attributeRef == null) {
4037 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
4038 }
4039 } else {
4040 var classRefs = [classRef];
4041 while (classRef = parser.parseElement("classRef", tokens)) {
4042 classRefs.push(classRef);
4043 }
4044 }
4045 }
4046
4047 if (tokens.matchToken("on")) {
4048 var on = parser.requireElement("targetExpression", tokens);
4049 } else {
4050 var on = parser.requireElement("implicitMeTarget", tokens);
4051 }
4052
4053 if (tokens.matchToken("for")) {
4054 var time = parser.requireElement("timeExpression", tokens);
4055 } else if (tokens.matchToken("until")) {
4056 var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
4057 if (tokens.matchToken("from")) {
4058 var from = parser.requireElement("expression", tokens);
4059 }
4060 }
4061
4062 var toggleCmd = {
4063 classRef: classRef,
4064 classRef2: classRef2,
4065 classRefs: classRefs,
4066 attributeRef: attributeRef,
4067 on: on,
4068 time: time,
4069 evt: evt,
4070 from: from,
4071 toggle: function (on, value) {
4072 if (between) {
4073 runtime.forEach(on, function (target) {
4074 if (target.classList.contains(classRef.className())) {
4075 target.classList.remove(classRef.className());
4076 target.classList.add(classRef2.className());
4077 } else {
4078 target.classList.add(classRef.className());
4079 target.classList.remove(classRef2.className());
4080 }
4081 })
4082 } else if (this.classRefs) {
4083 runtime.forEach(this.classRefs, function (classRef) {
4084 runtime.forEach(on, function (target) {
4085 target.classList.toggle(classRef.className())
4086 });
4087 })
4088 } else {
4089 runtime.forEach(on, function (target) {
4090 if (target.hasAttribute(attributeRef.name)) {
4091 target.removeAttribute(attributeRef.name);
4092 } else {
4093 target.setAttribute(attributeRef.name, value)
4094 }
4095 });
4096 }
4097 },
4098 args: [on, attributeRef ? attributeRef.value : null, time, evt, from],
4099 op: function (context, on, value, time, evt, from) {
4100 if (time) {
4101 return new Promise(function (resolve) {
4102 toggleCmd.toggle(on, value);
4103 setTimeout(function () {
4104 toggleCmd.toggle(on, value);
4105 resolve(runtime.findNext(toggleCmd, context));
4106 }, time);
4107 });
4108 } else if (evt) {
4109 return new Promise(function (resolve) {
4110 var target = from || context.me;
4111 target.addEventListener(evt, function () {
4112 toggleCmd.toggle(on, value);
4113 resolve(runtime.findNext(toggleCmd, context));
4114 }, {once: true})
4115 toggleCmd.toggle(on, value);
4116 });
4117 } else {
4118 this.toggle(on, value);
4119 return runtime.findNext(toggleCmd, context);
4120 }
4121 }
4122 };
4123 return toggleCmd
4124 }
4125 })
4126
4127 var HIDE_SHOW_STRATEGIES = {
4128 "display": function (op, element, arg) {
4129 if(arg){
4130 element.style.display = arg;
4131 } else if (op === 'hide') {
4132 element.style.display = 'none';
4133 } else {
4134 element.style.display = 'block';
4135 }
4136 },
4137 "visibility": function (op, element, arg) {
4138 if(arg){
4139 element.style.visibility = arg;
4140 } else if (op === 'hide') {
4141 element.style.visibility = 'hidden';
4142 } else {
4143 element.style.visibility = 'visible';
4144 }
4145 },
4146 "opacity": function (op, element, arg) {
4147 if(arg){
4148 element.style.opacity = arg;
4149 } else if (op === 'hide') {
4150 element.style.opacity = '0';
4151 } else {
4152 element.style.opacity = '1';
4153 }
4154 }
4155 }
4156
4157 var parseShowHideTarget = function (parser, runtime, tokens) {
4158 var target;
4159 var currentTokenValue = tokens.currentToken();
4160 if (currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) {
4161 target = parser.parseElement("implicitMeTarget", tokens);
4162 } else {
4163 target = parser.parseElement("targetExpression", tokens);
4164 }
4165 return target;
4166 }
4167
4168 var resolveStrategy = function (parser, tokens, name) {
4169 var configDefault = _hyperscript.config.defaultHideShowStrategy;
4170 var strategies = HIDE_SHOW_STRATEGIES;
4171 if (_hyperscript.config.hideShowStrategies) {
4172 strategies = mergeObjects(strategies, _hyperscript.config.hideShowStrategies); // merge in user provided strategies
4173 }
4174 name = name || configDefault || "display";
4175 var value = strategies[name];
4176 if (value == null) {
4177 parser.raiseParseError(tokens, 'Unknown show/hide strategy : ' + name);
4178 }
4179 return value;
4180 }
4181
4182 _hyperscript.addCommand("hide", function (parser, runtime, tokens) {
4183 if (tokens.matchToken("hide")) {
4184 var target = parseShowHideTarget(parser, runtime, tokens);
4185
4186 var name = null;
4187 if (tokens.matchToken("with")) {
4188 name = tokens.requireTokenType("IDENTIFIER").value;
4189 }
4190 var hideShowStrategy = resolveStrategy(parser, tokens, name);
4191
4192 return {
4193 target: target,
4194 args: [target],
4195 op: function (ctx, target) {
4196 runtime.forEach(target, function (elt) {
4197 hideShowStrategy('hide', elt);
4198 });
4199 return runtime.findNext(this, ctx);
4200 }
4201 }
4202 }
4203 });
4204
4205 _hyperscript.addCommand("show", function (parser, runtime, tokens) {
4206 if (tokens.matchToken("show")) {
4207 var target = parseShowHideTarget(parser, runtime, tokens);
4208
4209 var name = null;
4210 if (tokens.matchToken("with")) {
4211 name = tokens.requireTokenType("IDENTIFIER").value;
4212 }
4213 var arg = null;
4214 if (tokens.matchOpToken(":")) {
4215 var tokenArr = tokens.consumeUntilWhitespace();
4216 tokens.matchTokenType("WHITESPACE");
4217 arg = tokenArr.map(function (t) {
4218 return t.value
4219 }).join("");
4220 }
4221 var hideShowStrategy = resolveStrategy(parser, tokens, name);
4222
4223 return {
4224 target: target,
4225 args: [target],
4226 op: function (ctx, target) {
4227 runtime.forEach(target, function (elt) {
4228 hideShowStrategy('show', elt, arg);
4229 });
4230 return runtime.findNext(this, ctx);
4231 }
4232 }
4233 }
4234 });
4235
4236 _hyperscript.addCommand("trigger", function(parser, runtime, tokens) {
4237 if (tokens.matchToken('trigger')) {
4238 var eventName = parser.requireElement("dotOrColonPath", tokens);
4239 var details = parser.parseElement("namedArgumentList", tokens);
4240
4241 var triggerCmd = {
4242 eventName: eventName,
4243 details: details,
4244 args: [eventName, details],
4245 op: function (context, eventNameStr, details) {
4246 runtime.triggerEvent(context.me, eventNameStr, details ? details : {});
4247 return runtime.findNext(triggerCmd, context);
4248 }
4249 };
4250 return triggerCmd
4251 }
4252 })
4253
4254 _hyperscript.addCommand("take", function(parser, runtime, tokens) {
4255 if (tokens.matchToken('take')) {
4256 var classRef = parser.parseElement("classRef", tokens);
4257
4258 if (tokens.matchToken("from")) {
4259 var from = parser.requireElement("targetExpression", tokens);
4260 } else {
4261 var from = classRef;
4262 }
4263
4264 if (tokens.matchToken("for")) {
4265 var forElt = parser.requireElement("targetExpression", tokens);
4266 } else {
4267 var forElt = parser.requireElement("implicitMeTarget", tokens)
4268 }
4269
4270 var takeCmd = {
4271 classRef: classRef,
4272 from: from,
4273 forElt: forElt,
4274 args: [from, forElt],
4275 op: function (context, from, forElt) {
4276 var clazz = this.classRef.css.substr(1)
4277 runtime.forEach(from, function (target) {
4278 target.classList.remove(clazz);
4279 })
4280 runtime.forEach(forElt, function (target) {
4281 target.classList.add(clazz);
4282 });
4283 return runtime.findNext(this, context);
4284 }
4285 };
4286 return takeCmd
4287 }
4288 })
4289
4290 function putInto(context, prop, valueToPut){
4291 if (prop) {
4292 var value = context[prop];
4293 } else {
4294 var value = context;
4295 }
4296 if (value instanceof Element || value instanceof HTMLDocument) {
4297 value.innerHTML = valueToPut;
4298 } else {
4299 if (prop) {
4300 context[prop] = valueToPut;
4301 } else {
4302 throw "Don't know how to put a value into " + typeof context;
4303 }
4304 }
4305 }
4306
4307 _hyperscript.addCommand("put", function(parser, runtime, tokens) {
4308 if (tokens.matchToken('put')) {
4309 var value = parser.requireElement("expression", tokens);
4310
4311 var operationToken = tokens.matchAnyToken("into", "before", "after");
4312
4313 if (operationToken == null && tokens.matchToken("at")) {
4314 operationToken = tokens.matchAnyToken("start", "end");
4315 tokens.requireToken("of");
4316 }
4317
4318 if (operationToken == null) {
4319 parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'");
4320 }
4321 var target = parser.requireElement("targetExpression", tokens);
4322
4323 var operation = operationToken.value;
4324
4325 var symbolWrite = false;
4326 var root = null;
4327 var prop = null;
4328 if (target.type === "propertyAccess" && operation === "into") {
4329 prop = target.prop.value;
4330 root = target.root;
4331 } else if(target.type === "symbol" && operation === "into") {
4332 symbolWrite = true;
4333 prop = target.name;
4334 } else {
4335 root = target;
4336 }
4337
4338 var putCmd = {
4339 target: target,
4340 operation: operation,
4341 symbolWrite: symbolWrite,
4342 value: value,
4343 args: [root, value],
4344 op: function (context, root, valueToPut) {
4345 if (symbolWrite) {
4346 putInto(context, prop, valueToPut);
4347 context[target.name] = valueToPut;
4348 } else {
4349 if (operation === "into") {
4350 runtime.forEach(root, function (elt) {
4351 putInto(elt, prop, valueToPut);
4352 })
4353 } else if (operation === "before") {
4354 runtime.forEach(root, function (elt) {
4355 elt.insertAdjacentHTML('beforebegin', valueToPut);
4356 })
4357 } else if (operation === "start") {
4358 runtime.forEach(root, function (elt) {
4359 elt.insertAdjacentHTML('afterbegin', valueToPut);
4360 })
4361 } else if (operation === "end") {
4362 runtime.forEach(root, function (elt) {
4363 elt.insertAdjacentHTML('beforeend', valueToPut);
4364 })
4365 } else if (operation === "after") {
4366 runtime.forEach(root, function (elt) {
4367 elt.insertAdjacentHTML('afterend', valueToPut);
4368 })
4369 }
4370 }
4371 return runtime.findNext(this, context);
4372 }
4373 };
4374 return putCmd
4375 }
4376 })
4377
4378 _hyperscript.addCommand("transition", function(parser, runtime, tokens) {
4379 if (tokens.matchToken("transition")) {
4380 if (tokens.matchToken('element') || tokens.matchToken('elements')) {
4381 var targets = parser.parseElement("expression", tokens);
4382 } else {
4383 var targets = parser.parseElement("implicitMeTarget", tokens);
4384 }
4385 var properties = [];
4386 var from = [];
4387 var to = [];
4388 var currentToken = tokens.currentToken();
4389 while (!parser.commandBoundary(currentToken) && currentToken.value !== "using") {
4390
4391 properties.push(parser.requireElement("stringLike", tokens));
4392
4393 if (tokens.matchToken("from")) {
4394 from.push(parser.requireElement("stringLike", tokens));
4395 } else {
4396 from.push(null);
4397 }
4398 tokens.requireToken("to");
4399 to.push(parser.requireElement("stringLike", tokens));
4400 currentToken = tokens.currentToken();
4401 }
4402 if (tokens.matchToken("using")) {
4403 var using = parser.requireElement("expression", tokens);
4404 }
4405
4406 var transition = {
4407 to: to,
4408 args: [targets, properties, from, to, using],
4409 op: function (context, targets, properties, from, to, using) {
4410 var promises = [];
4411 runtime.forEach(targets, function(target){
4412 var promise = new Promise(function (resolve, reject) {
4413 var initialTransition = target.style.transition;
4414 target.style.transition = using || _hyperscript.config.defaultTransition;
4415 var internalData = runtime.getInternalData(target);
4416 var computedStyles = getComputedStyle(target);
4417
4418 var initialStyles = {};
4419 for (var i = 0; i < computedStyles.length; i++) {
4420 var name = computedStyles[i];
4421 var initialValue = computedStyles[name];
4422 initialStyles[name] = initialValue;
4423 }
4424
4425 // store intitial values
4426 if (!internalData.initalStyles) {
4427 internalData.initalStyles = initialStyles;
4428 }
4429
4430 for (var i = 0; i < properties.length; i++) {
4431 var property = properties[i];
4432 var fromVal = from[i];
4433 if (fromVal == 'computed' || fromVal == null) {
4434 target.style[property] = initialStyles[property];
4435 } else {
4436 target.style[property] = fromVal;
4437 }
4438 }
4439 // console.log("transition started", transition);
4440 setTimeout(function () {
4441 var autoProps = [];
4442 for (var i = 0; i < properties.length; i++) {
4443 var property = properties[i];
4444 var toVal = to[i];
4445 if (toVal == 'initial') {
4446 var propertyValue = internalData.initalStyles[property];
4447 target.style[property] = propertyValue;
4448 } else {
4449 target.style[property] = toVal;
4450 }
4451 // console.log("set", property, "to", target.style[property], "on", target, "value passed in : ", toVal);
4452 }
4453 target.addEventListener('transitionend', function () {
4454 // console.log("transition ended", transition);
4455 target.style.transition = initialTransition;
4456 resolve();
4457 }, {once:true})
4458 }, 5);
4459 });
4460 promises.push(promise);
4461 })
4462 return Promise.all(promises).then(function(){
4463 return runtime.findNext(transition, context);
4464 })
4465 }
4466 };
4467 return transition
4468 }
4469 });
4470
4471 _hyperscript.addLeafExpression('closestExpr', function (parser, runtime, tokens) {
4472 if (tokens.matchToken('closest')) {
4473 var expr = parser.parseElement("targetExpression", tokens);
4474 if (expr.css == null) {
4475 parser.raiseParseError(tokens, "Expected a CSS expression");
4476 }
4477 if (tokens.matchToken('to')) {
4478 var to = parser.parseElement("targetExpression", tokens);
4479 } else {
4480 var to = parser.parseElement("implicitMeTarget", tokens);
4481 }
4482 return {
4483 expr: expr,
4484 to: to,
4485 args: [to],
4486 op: function (ctx, to) {
4487 return to == null ? null : to.closest(expr.css);
4488 },
4489 evaluate: function (context) {
4490 return runtime.unifiedEval(this, context);
4491 }
4492 }
4493 }
4494 });
4495
4496 _hyperscript.config.conversions["Values"] = function(/** @type {Node | NodeList} */ node) {
4497
4498 /** @type Object<string,string | string[]> */
4499 var result = {};
4500
4501 var forEach = _hyperscript.internals.runtime.forEach;
4502
4503 forEach(node, function(/** @type HTMLInputElement */ node) {
4504
4505 // Try to get a value directly from this node
4506 var input = getInputInfo(node);
4507
4508 if (input !== undefined) {
4509 result[input.name] = input.value;
4510 return;
4511 }
4512
4513 // Otherwise, try to query all child elements of this node that *should* contain values.
4514 if (node.querySelectorAll != undefined) {
4515 var children = node.querySelectorAll("input,select,textarea");
4516 forEach(children, appendValue);
4517 }
4518 })
4519
4520 return result;
4521
4522 /**
4523 * @param {HTMLInputElement} node
4524 */
4525 function appendValue(node) {
4526
4527 var info = getInputInfo(node);
4528
4529 if (info == undefined) {
4530 return;
4531 }
4532
4533 // If there is no value already stored in this space.
4534 if (result[info.name] == undefined) {
4535 result[info.name] = info.value;
4536 return;
4537 }
4538
4539 if (Array.isArray(result[info.name]) && Array.isArray(info.value)) {
4540 result[info.name] = [].concat(result[info.name], info.value);
4541 return;
4542 }
4543 }
4544
4545 /**
4546 * @param {HTMLInputElement} node
4547 * @returns {{name:string, value:string | string[]} | undefined}
4548 */
4549 function getInputInfo(node) {
4550 try {
4551
4552 /** @type {{name: string, value: string | string[]}}*/
4553 var result = {
4554 name: node.name,
4555 value: node.value
4556 };
4557
4558 if ((result.name == undefined) || (result.value == undefined)) {
4559 return undefined;
4560 }
4561
4562 if ((node.type == "radio") && (node.checked == false)) {
4563 return undefined;
4564 }
4565
4566 if (node.type == "checkbox") {
4567 if (node.checked == false) {
4568 result.value = undefined;
4569 } else if (typeof result.value === "string") {
4570 result.value = [result.value];
4571 }
4572 }
4573
4574 if (node.type == "select-multiple") {
4575
4576 /** @type {NodeListOf<HTMLSelectElement>} */
4577 var selected = node.querySelectorAll("option[selected]");
4578
4579 result.value = []
4580 for (var index = 0 ; index < selected.length ; index++) {
4581 result.value.push(selected[index].value)
4582 }
4583 }
4584 return result;
4585
4586 } catch (e) {
4587 return undefined;
4588 }
4589 }
4590 }
4591
4592})()