UNPKG

142 kBJavaScriptView Raw
1//AMD insanity
2(function (root, factory) {
3 if (typeof define === 'function' && define.amd) {
4 // AMD. Register as an anonymous module.
5 define([], factory);
6 } else {
7 // Browser globals
8 root._hyperscript = factory();
9 }
10}(typeof self !== 'undefined' ? self : this, function () {
11 return (function () {
12 'use strict';
13
14
15 //====================================================================
16 // Utilities
17 //====================================================================
18
19 function mergeObjects(obj1, obj2) {
20 for (var key in obj2) {
21 if (obj2.hasOwnProperty(key)) {
22 obj1[key] = obj2[key];
23 }
24 }
25 return obj1;
26 }
27
28 function parseJSON(jString) {
29 try {
30 return JSON.parse(jString);
31 } catch(error) {
32 logError(error);
33 return null;
34 }
35 }
36
37 function logError(msg) {
38 if(console.error) {
39 console.error(msg);
40 } else if (console.log) {
41 console.log("ERROR: ", msg);
42 }
43 }
44
45 // https://stackoverflow.com/a/8843181
46 function varargConstructor(Cls, args) {
47 return new (Cls.bind.apply(Cls, [Cls].concat(args)));
48 }
49
50 var globalScope = typeof self !== 'undefined' ? self : typeof global !== 'undefined' ? global : this;
51
52 //====================================================================
53 // Lexer
54 //====================================================================
55 var _lexer = function () {
56 var OP_TABLE = {
57 '+': 'PLUS',
58 '-': 'MINUS',
59 '*': 'MULTIPLY',
60 '/': 'DIVIDE',
61 '.': 'PERIOD',
62 '\\': 'BACKSLASH',
63 ':': 'COLON',
64 '%': 'PERCENT',
65 '|': 'PIPE',
66 '!': 'EXCLAMATION',
67 '?': 'QUESTION',
68 '#': 'POUND',
69 '&': 'AMPERSAND',
70 ';': 'SEMI',
71 ',': 'COMMA',
72 '(': 'L_PAREN',
73 ')': 'R_PAREN',
74 '<': 'L_ANG',
75 '>': 'R_ANG',
76 '<=': 'LTE_ANG',
77 '>=': 'GTE_ANG',
78 '==': 'EQ',
79 '===': 'EQQ',
80 '!=': 'NEQ',
81 '!==': 'NEQQ',
82 '{': 'L_BRACE',
83 '}': 'R_BRACE',
84 '[': 'L_BRACKET',
85 ']': 'R_BRACKET',
86 '=': 'EQUALS'
87 };
88
89 function isValidCSSClassChar(c) {
90 return isAlpha(c) || isNumeric(c) || c === "-" || c === "_";
91 }
92
93 function isValidCSSIDChar(c) {
94 return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
95 }
96
97 function isWhitespace(c) {
98 return c === " " || c === "\t" || isNewline(c);
99 }
100
101 function positionString(token) {
102 return "[Line: " + token.line + ", Column: " + token.col + "]"
103 }
104
105 function isNewline(c) {
106 return c === '\r' || c === '\n';
107 }
108
109 function isNumeric(c) {
110 return c >= '0' && c <= '9';
111 }
112
113 function isAlpha(c) {
114 return (c >= 'a' && c <= 'z') ||
115 (c >= 'A' && c <= 'Z');
116 }
117
118 function isIdentifierChar(c) {
119 return (c === "_" || c === "$");
120 }
121
122 function isReservedChar(c) {
123 return (c === "`" || c === "^");
124 }
125
126
127 function makeTokensObject(tokens, consumed, source) {
128
129 var ignoreWhiteSpace = true;
130 matchTokenType("WHITESPACE"); // consume any initial whitespace
131
132 function raiseError(tokens, error) {
133 _parser.raiseParseError(tokens, error);
134 }
135
136 function requireOpToken(value) {
137 var token = matchOpToken(value);
138 if (token) {
139 return token;
140 } else {
141 raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
142 }
143 }
144
145 function matchAnyOpToken(op1, op2, op3) {
146 for (var i = 0; i < arguments.length; i++) {
147 var opToken = arguments[i];
148 var match = matchOpToken(opToken);
149 if (match) {
150 return match;
151 }
152 }
153 }
154
155 function matchOpToken(value) {
156 if (currentToken() && currentToken().op && currentToken().value === value) {
157 return consumeToken();
158 }
159 }
160
161 function requireTokenType(type1, type2, type3, type4) {
162 var token = matchTokenType(type1, type2, type3, type4);
163 if (token) {
164 return token;
165 } else {
166 raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3]));
167 }
168 }
169
170 function matchTokenType(type1, type2, type3, type4) {
171 if (currentToken() && currentToken().type && [type1, type2, type3, type4].indexOf(currentToken().type) >= 0) {
172 return consumeToken();
173 }
174 }
175
176 function requireToken(value, type) {
177 var token = matchToken(value, type);
178 if (token) {
179 return token;
180 } else {
181 raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
182 }
183 }
184
185 function matchToken(value, type) {
186 var type = type || "IDENTIFIER";
187 if (currentToken() && currentToken().value === value && currentToken().type === type) {
188 return consumeToken();
189 }
190 }
191
192 function consumeToken() {
193 var match = tokens.shift();
194 consumed.push(match);
195 if(ignoreWhiteSpace) {
196 matchTokenType("WHITESPACE"); // consume any whitespace until the next token
197 }
198 return match;
199 }
200
201 function consumeUntilWhitespace() {
202 var tokenList = [];
203 ignoreWhiteSpace = false;
204 while (currentToken() &&
205 currentToken().type !== "WHITESPACE" &&
206 currentToken().type !== "EOF") {
207 tokenList.push(consumeToken());
208 }
209 ignoreWhiteSpace = true;
210 return tokenList;
211 }
212
213 function hasMore() {
214 return tokens.length > 0;
215 }
216
217 function currentToken(ignoreWhiteSpace) {
218 var token;
219 if (ignoreWhiteSpace) {
220 var i = 0;
221 do {
222 token = tokens[i++]
223 } while (token && token.type === "WHITESPACE");
224 } else {
225 token = tokens[0];
226 }
227 if (token) {
228 return token;
229 } else {
230 return {
231 type:"EOF"
232 }
233 }
234 }
235
236 return {
237 matchAnyOpToken: matchAnyOpToken,
238 matchOpToken: matchOpToken,
239 requireOpToken: requireOpToken,
240 matchTokenType: matchTokenType,
241 requireTokenType: requireTokenType,
242 consumeToken: consumeToken,
243 matchToken: matchToken,
244 requireToken: requireToken,
245 list: tokens,
246 consumed: consumed,
247 source: source,
248 hasMore: hasMore,
249 currentToken: currentToken,
250 consumeUntilWhitespace: consumeUntilWhitespace
251 }
252 }
253
254 function tokenize(string) {
255 var source = string;
256 var tokens = [];
257 var position = 0;
258 var column = 0;
259 var line = 1;
260 var lastToken = "<START>";
261
262 while (position < source.length) {
263 if (currentChar() === "-" && nextChar() === "-") {
264 consumeComment();
265 } else {
266 if (isWhitespace(currentChar())) {
267 tokens.push(consumeWhitespace());
268 } else if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
269 tokens.push(consumeClassReference());
270 } else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
271 tokens.push(consumeIdReference());
272 } else if (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
273 tokens.push(consumeIdentifier());
274 } else if (isNumeric(currentChar())) {
275 tokens.push(consumeNumber());
276 } else if (currentChar() === '"' || currentChar() === "'") {
277 tokens.push(consumeString());
278 } else if (OP_TABLE[currentChar()]) {
279 tokens.push(consumeOp());
280 } else if (isReservedChar(currentChar())) {
281 tokens.push(makeToken('RESERVED', currentChar))
282 } else {
283 if (position < source.length) {
284 throw Error("Unknown token: " + currentChar() + " ");
285 }
286 }
287 }
288 }
289
290 return makeTokensObject(tokens, [], source);
291
292 function makeOpToken(type, value) {
293 var token = makeToken(type, value);
294 token.op = true;
295 return token;
296 }
297
298 function makeToken(type, value) {
299 return {
300 type: type,
301 value: value,
302 start: position,
303 end: position + 1,
304 column: column,
305 line: line
306 };
307 }
308
309 function consumeComment() {
310 while (currentChar() && !isNewline(currentChar())) {
311 consumeChar();
312 }
313 consumeChar();
314 }
315
316 function consumeClassReference() {
317 var classRef = makeToken("CLASS_REF");
318 var value = consumeChar();
319 while (isValidCSSClassChar(currentChar())) {
320 value += consumeChar();
321 }
322 classRef.value = value;
323 classRef.end = position;
324 return classRef;
325 }
326
327
328 function consumeIdReference() {
329 var idRef = makeToken("ID_REF");
330 var value = consumeChar();
331 while (isValidCSSIDChar(currentChar())) {
332 value += consumeChar();
333 }
334 idRef.value = value;
335 idRef.end = position;
336 return idRef;
337 }
338
339 function consumeIdentifier() {
340 var identifier = makeToken("IDENTIFIER");
341 var value = consumeChar();
342 while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
343 value += consumeChar();
344 }
345 identifier.value = value;
346 identifier.end = position;
347 return identifier;
348 }
349
350 function consumeNumber() {
351 var number = makeToken("NUMBER");
352 var value = consumeChar();
353 while (isNumeric(currentChar())) {
354 value += consumeChar();
355 }
356 if (currentChar() === ".") {
357 value += consumeChar();
358 }
359 while (isNumeric(currentChar())) {
360 value += consumeChar();
361 }
362 number.value = value;
363 number.end = position;
364 return number;
365 }
366
367 function consumeOp() {
368 var value = consumeChar(); // consume leading char
369 while (currentChar() && OP_TABLE[value + currentChar()]) {
370 value += consumeChar();
371 }
372 var op = makeOpToken(OP_TABLE[value], value);
373 op.value = value;
374 op.end = position;
375 return op;
376 }
377
378 function consumeString() {
379 var string = makeToken("STRING");
380 var startChar = consumeChar(); // consume leading quote
381 var value = "";
382 while (currentChar() && currentChar() !== startChar) {
383 if (currentChar() === "\\") {
384 consumeChar(); // consume escape char and move on
385 }
386 value += consumeChar();
387 }
388 if (currentChar() !== startChar) {
389 throw Error("Unterminated string at " + positionString(string));
390 } else {
391 consumeChar(); // consume final quote
392 }
393 string.value = value;
394 string.end = position;
395 return string;
396 }
397
398 function currentChar() {
399 return source.charAt(position);
400 }
401
402 function nextChar() {
403 return source.charAt(position + 1);
404 }
405
406 function consumeChar() {
407 lastToken = currentChar();
408 position++;
409 column++;
410 return lastToken;
411 }
412
413 function possiblePrecedingSymbol() {
414 return isAlpha(lastToken) || isNumeric(lastToken) || lastToken === ")" || lastToken === "}" || lastToken === "]"
415 }
416
417 function consumeWhitespace() {
418 var whitespace = makeToken("WHITESPACE");
419 var value = "";
420 while (currentChar() && isWhitespace(currentChar())) {
421 if (isNewline(currentChar())) {
422 column = 0;
423 line++;
424 }
425 value += consumeChar();
426 }
427 whitespace.value = value;
428 whitespace.end = position;
429 return whitespace;
430 }
431 }
432
433 return {
434 tokenize: tokenize,
435 makeTokensObject: makeTokensObject
436 }
437 }();
438
439 //====================================================================
440 // Parser
441 //====================================================================
442 var _parser = function () {
443
444 var GRAMMAR = {}
445 var COMMANDS = {}
446 var FEATURES = {}
447 var LEAF_EXPRESSIONS = [];
448 var INDIRECT_EXPRESSIONS = [];
449
450 function parseElement(type, tokens, root) {
451 var elementDefinition = GRAMMAR[type];
452 if (elementDefinition) return elementDefinition(_parser, _runtime, tokens, root);
453 }
454
455 function requireElement(type, tokens, message, root) {
456 var result = parseElement(type, tokens, root);
457 return result || raiseParseError(tokens, message || "Expected " + type.autocapitalize);
458 }
459
460 function parseAnyOf(types, tokens) {
461 for (var i = 0; i < types.length; i++) {
462 var type = types[i];
463 var expression = parseElement(type, tokens);
464 if (expression) {
465 return expression;
466 }
467 }
468 }
469
470 function addGrammarElement(name, definition) {
471 GRAMMAR[name] = definition;
472 }
473
474 function addCommand(keyword, definition) {
475 var commandGrammarType = keyword + "Command";
476 var commandDefinitionWrapper = function (parser, runtime, tokens) {
477 var commandElement = definition(parser, runtime, tokens);
478 if (commandElement) {
479 commandElement.type = commandGrammarType;
480 commandElement.execute = function (context) {
481 return runtime.unifiedExec(this, context);
482 }
483 return commandElement;
484 }
485 };
486 GRAMMAR[commandGrammarType] = commandDefinitionWrapper;
487 COMMANDS[keyword] = commandDefinitionWrapper;
488 }
489
490 function addFeature(keyword, definition) {
491 var featureGrammarType = keyword + "Feature";
492 var featureDefinitionWrapper = function (parser, runtime, tokens) {
493 var featureElement = definition(parser, runtime, tokens);
494 if (featureElement) {
495 featureElement.keyword = keyword;
496 featureElement.type = featureGrammarType;
497 return featureElement;
498 }
499 };
500 GRAMMAR[featureGrammarType] = featureDefinitionWrapper;
501 FEATURES[keyword] = featureDefinitionWrapper;
502 }
503
504 function addLeafExpression(name, definition) {
505 LEAF_EXPRESSIONS.push(name);
506 addGrammarElement(name, definition);
507 }
508
509 function addIndirectExpression(name, definition) {
510 INDIRECT_EXPRESSIONS.push(name);
511 addGrammarElement(name, definition);
512 }
513
514 /* ============================================================================================ */
515 /* Core hyperscript Grammar Elements */
516 /* ============================================================================================ */
517 addGrammarElement("feature", function(parser, runtime, tokens) {
518 var featureDefinition = FEATURES[tokens.currentToken().value];
519 if (featureDefinition) {
520 return featureDefinition(parser, runtime, tokens);
521 }
522 })
523
524 addGrammarElement("command", function(parser, runtime, tokens) {
525 var commandDefinition = COMMANDS[tokens.currentToken().value];
526 if (commandDefinition) {
527 return commandDefinition(parser, runtime, tokens);
528 }
529 })
530
531 addGrammarElement("commandList", function(parser, runtime, tokens) {
532 var cmd = parser.parseElement("command", tokens);
533 if (cmd) {
534 tokens.matchToken("then");
535 cmd.next = parser.parseElement("commandList", tokens);
536 return cmd;
537 }
538 })
539
540 addGrammarElement("leaf", function(parser, runtime, tokens) {
541 var result = parseAnyOf(LEAF_EXPRESSIONS, tokens);
542 // symbol is last so it doesn't consume any constants
543 if (result == null) {
544 return parseElement('symbol', tokens);
545 } else {
546 return result;
547 }
548 })
549
550 addGrammarElement("indirectExpression", function(parser, runtime, tokens, root) {
551 for (var i = 0; i < INDIRECT_EXPRESSIONS.length; i++) {
552 var indirect = INDIRECT_EXPRESSIONS[i];
553 var result = parser.parseElement(indirect, tokens, root);
554 if(result){
555 return result;
556 }
557 }
558 return root;
559 });
560
561 addGrammarElement("primaryExpression", function(parser, runtime, tokens) {
562 var leaf = parser.parseElement("leaf", tokens);
563 if (leaf) {
564 return parser.parseElement("indirectExpression", tokens, leaf);
565 }
566 parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value);
567 });
568 /* ============================================================================================ */
569 /* END Core hyperscript Grammar Elements */
570 /* ============================================================================================ */
571
572
573 function createParserContext(tokens) {
574 var currentToken = tokens.currentToken();
575 var source = tokens.source;
576 var lines = source.split("\n");
577 var line = currentToken ? currentToken.line - 1 : lines.length - 1;
578 var contextLine = lines[line];
579 var offset = currentToken ? currentToken.column : contextLine.length - 1;
580 return contextLine + "\n" + " ".repeat(offset) + "^^\n\n";
581 }
582
583 function raiseParseError(tokens, message) {
584 message = (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" +
585 createParserContext(tokens);
586 var error = new Error(message);
587 error.tokens = tokens;
588 throw error
589 }
590
591 function parseHyperScript(tokens) {
592 return parseElement("hyperscript", tokens)
593 }
594
595 function setParent(elt, parent) {
596 if (elt) {
597 elt.parent = parent;
598 setParent(elt.next, parent);
599 }
600 }
601
602 function commandBoundary(token) {
603 if (token.value == "end" || token.value == "then" || COMMANDS[token.value] || FEATURES[token.value] || token.type == "EOF") {
604 return true;
605 }
606 }
607
608 return {
609 // parser API
610 setParent: setParent,
611 requireElement: requireElement,
612 parseElement: parseElement,
613 commandBoundary: commandBoundary,
614 parseAnyOf: parseAnyOf,
615 parseHyperScript: parseHyperScript,
616 raiseParseError: raiseParseError,
617 addGrammarElement: addGrammarElement,
618 addCommand: addCommand,
619 addFeature: addFeature,
620 addLeafExpression: addLeafExpression,
621 addIndirectExpression: addIndirectExpression,
622 }
623 }();
624
625 //====================================================================
626 // Runtime
627 //====================================================================
628 var _runtime = function () {
629
630 function matchesSelector(elt, selector) {
631 // noinspection JSUnresolvedVariable
632 var matchesFunction = elt.matches ||
633 elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
634 || elt.webkitMatchesSelector || elt.oMatchesSelector;
635 return matchesFunction && matchesFunction.call(elt, selector);
636 }
637
638 function makeEvent(eventName, detail) {
639 var evt;
640 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
641 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
642 } else {
643 evt = document.createEvent('CustomEvent');
644 evt.initCustomEvent(eventName, true, true, detail);
645 }
646 return evt;
647 }
648
649 function triggerEvent(elt, eventName, detail) {
650 var detail = detail || {};
651 detail["sentBy"] = elt;
652 var event = makeEvent(eventName, detail);
653 var eventResult = elt.dispatchEvent(event);
654 return eventResult;
655 }
656
657 function isArrayLike(value) {
658 return Array.isArray(value) || value instanceof NodeList;
659 }
660
661 function forEach(value, func) {
662 if (value == null) {
663 // do nothing
664 } else if (isArrayLike(value)) {
665 for (var i = 0; i < value.length; i++) {
666 func(value[i]);
667 }
668 } else {
669 func(value);
670 }
671 }
672
673 var ARRAY_SENTINEL = {array_sentinel:true}
674 function linearize(args) {
675 var arr = [];
676 for (var i = 0; i < args.length; i++) {
677 var arg = args[i];
678 if (Array.isArray(arg)) {
679 arr.push(ARRAY_SENTINEL);
680 for (var j = 0; j < arg.length; j++) {
681 arr.push(arg[j]);
682 }
683 arr.push(ARRAY_SENTINEL);
684 } else {
685 arr.push(arg);
686 }
687 }
688 return arr;
689 }
690
691 function delinearize(values){
692 var arr = [];
693 for (var i = 0; i < values.length; i++) {
694 var value = values[i];
695 if (value === ARRAY_SENTINEL) {
696 value = values[++i];
697 var valueArray = [];
698 arr.push(valueArray);
699 while (value !== ARRAY_SENTINEL) {
700 valueArray.push(value);
701 value = values[++i];
702 }
703 } else {
704 arr.push(value);
705 }
706 }
707 return arr;
708
709 }
710
711 function unwrapAsyncs(values) {
712 for (var i = 0; i < values.length; i++) {
713 var value = values[i];
714 if (value.asyncWrapper) {
715 values[i] = value.value;
716 }
717 if (Array.isArray(value)) {
718 for (var j = 0; j < value.length; j++) {
719 var valueElement = value[j];
720 if (valueElement.asyncWrapper) {
721 value[j] = valueElement.value;
722 }
723 }
724 }
725 }
726 }
727
728 var HALT = {halt_flag:true};
729 function unifiedExec(command, ctx) {
730 while(true) {
731 var next = unifiedEval(command, ctx);
732 if (next == null) {
733 console.error(command, " did not return a next element to execute! context: " , ctx)
734 return;
735 } else if (next.then) {
736 next.then(function (resolvedNext) {
737 unifiedExec(resolvedNext, ctx);
738 }).catch(function(reason){
739 if (ctx.meta && ctx.meta.reject) {
740 ctx.meta.reject(reason);
741 } else {
742 // TODO: no meta context to reject with, trigger event?
743 }
744 });
745 return;
746 } else if (next === HALT) {
747 // done
748 return;
749 } else {
750 command = next; // move to the next command
751 }
752 }
753 }
754
755 function unifiedEval(parseElement, ctx) {
756 var async = false;
757 var wrappedAsyncs = false;
758 var args = [ctx];
759 if (parseElement.args) {
760 for (var i = 0; i < parseElement.args.length; i++) {
761 var argument = parseElement.args[i];
762 if (argument == null) {
763 args.push(null);
764 } else if (Array.isArray(argument)) {
765 var arr = [];
766 for (var j = 0; j < argument.length; j++) {
767 var element = argument[j];
768 var value = element.evaluate(ctx); // OK
769 if (value) {
770 if (value.then) {
771 async = true;
772 } else if (value.asyncWrapper) {
773 wrappedAsyncs = true;
774 }
775 }
776 arr.push(value);
777 }
778 args.push(arr);
779 } else if (argument.evaluate) {
780 var value = argument.evaluate(ctx); // OK
781 if (value) {
782 if (value.then) {
783 async = true;
784 } else if (value.asyncWrapper) {
785 wrappedAsyncs = true;
786 }
787 }
788 args.push(value);
789 } else {
790 args.push(argument);
791 }
792 }
793 }
794 if (async) {
795 return new Promise(function(resolve, reject){
796 var linearized = linearize(args);
797 Promise.all(linearized).then(function(values){
798 values = delinearize(values);
799 if (wrappedAsyncs) {
800 unwrapAsyncs(values);
801 }
802 try{
803 var apply = parseElement.op.apply(parseElement, values);
804 resolve(apply);
805 } catch(e) {
806 reject(e);
807 }
808 }).catch(function(reason){
809 if (ctx.meta && ctx.meta.reject) {
810 ctx.meta.reject(reason);
811 } else {
812 // TODO: no meta context to reject with, trigger event?
813 }
814 })
815 })
816 } else {
817 if (wrappedAsyncs) {
818 unwrapAsyncs(args);
819 }
820 try {
821 return parseElement.op.apply(parseElement, args);
822 } catch (e) {
823 if (ctx.meta && ctx.meta.reject) {
824 ctx.meta.reject(e);
825 } else {
826 throw e;
827 }
828 }
829 }
830 }
831
832 var _scriptAttrs = null;
833 function getScriptAttributes() {
834 if (_scriptAttrs == null) {
835 _scriptAttrs = _hyperscript.config.attributes.replace(/ /g,'').split(",")
836 }
837 return _scriptAttrs;
838 }
839
840 function getScript(elt) {
841 for (var i = 0; i < getScriptAttributes().length; i++) {
842 var scriptAttribute = getScriptAttributes()[i];
843 if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
844 return elt.getAttribute(scriptAttribute)
845 }
846 }
847 if (elt.type === "text/hyperscript") {
848 return elt.innerText;
849 }
850 return null;
851 }
852
853 function makeContext(root, elt, event) {
854 var ctx = {
855 meta: {
856 parser: _parser,
857 lexer: _lexer,
858 runtime: _runtime,
859 root: root,
860 iterators: root
861 },
862 me: elt,
863 event: event,
864 detail: event ? event.detail : null,
865 body: 'document' in globalScope ? document.body : null
866 }
867 ctx.meta.ctx = ctx;
868 return ctx;
869 }
870
871 function applyEventListeners(hypeScript, elt) {
872 forEach(hypeScript.onFeatures, function (onFeature) {
873 forEach(
874 onFeature.elsewhere ? [document]
875 : onFeature.from ? onFeature.from.evaluate({})
876 : [elt], function(target){ // OK NO PROMISE
877 target.addEventListener(onFeature.on.evaluate(), function(evt){ // OK NO PROMISE
878 if (onFeature.elsewhere && elt.contains(evt.target)) return
879 var ctx = makeContext(onFeature, elt, evt);
880 onFeature.execute(ctx)
881 });
882 })
883 });
884 }
885
886 function getScriptSelector() {
887 return getScriptAttributes().map(function (attribute) {
888 return "[" + attribute + "]";
889 }).join(", ");
890 }
891
892 function isType(o, type) {
893 return Object.prototype.toString.call(o) === "[object " + type + "]";
894 }
895
896 function evaluate(typeOrSrc, srcOrCtx, ctxArg) {
897 if (isType(srcOrCtx, "Object")) {
898 var src = typeOrSrc;
899 var ctx = srcOrCtx;
900 var type = "expression"
901 } else if (isType(srcOrCtx, "String")) {
902 var src = srcOrCtx;
903 var type = typeOrSrc
904 var ctx = ctxArg;
905 } else {
906 var src = typeOrSrc;
907 var ctx = {};
908 var type = "expression";
909 }
910 ctx = ctx || {};
911 var compiled = _parser.parseElement(type, _lexer.tokenize(src) );
912 return compiled.evaluate ? compiled.evaluate(ctx) : compiled.execute(ctx); // OK
913 }
914
915 function processNode(elt) {
916 var selector = _runtime.getScriptSelector();
917 if (matchesSelector(elt, selector)) {
918 initElement(elt);
919 }
920 if (elt.querySelectorAll) {
921 forEach(elt.querySelectorAll(selector), function (elt) {
922 initElement(elt);
923 });
924 }
925 if (elt.type === "text/hyperscript") {
926 initElement(elt, document.body);
927 }
928 if (elt.querySelectorAll) {
929 forEach(elt.querySelectorAll("[type=\'text/hyperscript\']"), function (elt) {
930 initElement(elt, document.body);
931 });
932 }
933 }
934
935 function initElement(elt, target) {
936 var internalData = getInternalData(elt);
937 if (!internalData.initialized) {
938 var src = getScript(elt);
939 if (src) {
940 try {
941 internalData.initialized = true;
942 internalData.script = src;
943 var tokens = _lexer.tokenize(src);
944 var hyperScript = _parser.parseHyperScript(tokens);
945 _runtime.applyEventListeners(hyperScript, target || elt);
946 setTimeout(function () {
947 triggerEvent(target || elt, 'load');
948 }, 1);
949 } catch(e) {
950 console.error("hyperscript errors were found on the following element:", elt, "\n\n", e.message, e.stack);
951 }
952 }
953 }
954 }
955
956 function getInternalData(elt) {
957 var dataProp = 'hyperscript-internal-data';
958 var data = elt[dataProp];
959 if (!data) {
960 data = elt[dataProp] = {};
961 }
962 return data;
963 }
964
965 function typeCheck(value, typeString, nullOk) {
966 if (value == null && nullOk) {
967 return value;
968 }
969 var typeName = Object.prototype.toString.call(value).slice(8, -1);
970 var typeCheckValue = value && typeName === typeString;
971 if (typeCheckValue) {
972 return value;
973 } else {
974 throw new Error("Typecheck failed! Expected: " + typeString + ", Found: " + typeName);
975 }
976 }
977
978 function resolveSymbol(str, context) {
979 if (str === "me" || str === "my") {
980 return context["me"];
981 } if (str === "it" || str === "its") {
982 return context["it"];
983 } else {
984 if (context.meta && context.meta.context) {
985 var fromMetaContext = context.meta.context[str];
986 if (typeof fromMetaContext !== "undefined") {
987 return fromMetaContext;
988 }
989 }
990 var fromContext = context[str];
991 if (typeof fromContext !== "undefined") {
992 return fromContext;
993 } else {
994 return globalScope[str];
995 }
996 }
997 }
998
999 function findNext(command) {
1000 if (command) {
1001 if (command.resolveNext) {
1002 return command.resolveNext();
1003 } else if (command.next) {
1004 return command.next;
1005 } else {
1006 return findNext(command.parent)
1007 }
1008 }
1009 }
1010
1011 function resolveProperty(root, property) {
1012 if (root != null) {
1013 var val = root[property];
1014 if (typeof val !== 'undefined') {
1015 return val;
1016 } else {
1017 if (isArrayLike(root)) {
1018 if (property === "first") {
1019 return root[0];
1020 } else if (property === "last") {
1021 return root[root.length - 1];
1022 } else if (property === "random") {
1023 return root[Math.floor(root.length * Math.random())]
1024 } else {
1025 // flat map
1026 var result = [];
1027 for (var i = 0; i < root.length; i++) {
1028 var component = root[i];
1029 var componentValue = component[property];
1030 if (componentValue) {
1031 result.push(componentValue);
1032 }
1033 }
1034 return result;
1035 }
1036 }
1037 }
1038 }
1039 }
1040
1041 function assignToNamespace(nameSpace, name, value) {
1042 var root = globalScope;
1043 while (nameSpace.length > 0) {
1044 var propertyName = nameSpace.shift();
1045 var newRoot = root[propertyName];
1046 if (newRoot == null) {
1047 newRoot = {};
1048 root[propertyName] = newRoot;
1049 }
1050 root = newRoot;
1051 }
1052
1053 root[name] = value;
1054 }
1055
1056 var hyperscriptUrl = 'document' in globalScope ? document.currentScript.src : null
1057
1058 return {
1059 typeCheck: typeCheck,
1060 forEach: forEach,
1061 triggerEvent: triggerEvent,
1062 matchesSelector: matchesSelector,
1063 getScript: getScript,
1064 applyEventListeners: applyEventListeners,
1065 processNode: processNode,
1066 evaluate: evaluate,
1067 getScriptSelector: getScriptSelector,
1068 resolveSymbol: resolveSymbol,
1069 makeContext: makeContext,
1070 findNext: findNext,
1071 unifiedEval: unifiedEval,
1072 unifiedExec: unifiedExec,
1073 resolveProperty: resolveProperty,
1074 assignToNamespace: assignToNamespace,
1075 hyperscriptUrl: hyperscriptUrl,
1076 HALT: HALT
1077 }
1078 }();
1079
1080 //====================================================================
1081 // Grammar
1082 //====================================================================
1083 {
1084 _parser.addLeafExpression("parenthesized", function(parser, runtime, tokens) {
1085 if (tokens.matchOpToken('(')) {
1086 var expr = parser.requireElement("expression", tokens);
1087 tokens.requireOpToken(")");
1088 return {
1089 type: "parenthesized",
1090 expr: expr,
1091 evaluate: function (context) {
1092 return expr.evaluate(context); //OK
1093 }
1094 }
1095 }
1096 })
1097
1098 _parser.addLeafExpression("string", function(parser, runtime, tokens) {
1099 var stringToken = tokens.matchTokenType('STRING');
1100 if (stringToken) {
1101 return {
1102 type: "string",
1103 token: stringToken,
1104 evaluate: function (context) {
1105 return stringToken.value;
1106 }
1107 }
1108 }
1109 })
1110
1111 _parser.addGrammarElement("nakedString", function(parser, runtime, tokens) {
1112 if (tokens.hasMore()) {
1113 var tokenArr = tokens.consumeUntilWhitespace();
1114 tokens.matchTokenType("WHITESPACE");
1115 return {
1116 type: "nakedString",
1117 tokens: tokenArr,
1118 evaluate: function (context) {
1119 return tokenArr.map(function (t) {return t.value}).join("");
1120 }
1121 }
1122 }
1123 })
1124
1125 _parser.addLeafExpression("number", function(parser, runtime, tokens) {
1126 var number = tokens.matchTokenType('NUMBER');
1127 if (number) {
1128 var numberToken = number;
1129 var value = parseFloat(number.value)
1130 return {
1131 type: "number",
1132 value: value,
1133 numberToken: numberToken,
1134 evaluate: function () {
1135 return value;
1136 }
1137 }
1138 }
1139 })
1140
1141 _parser.addLeafExpression("idRef", function(parser, runtime, tokens) {
1142 var elementId = tokens.matchTokenType('ID_REF');
1143 if (elementId) {
1144 return {
1145 type: "idRef",
1146 value: elementId.value.substr(1),
1147 evaluate: function (context) {
1148 return document.getElementById(this.value);
1149 }
1150 };
1151 }
1152 })
1153
1154 _parser.addLeafExpression("classRef", function(parser, runtime, tokens) {
1155 var classRef = tokens.matchTokenType('CLASS_REF');
1156 if (classRef) {
1157 return {
1158 type: "classRef",
1159 value: classRef.value,
1160 className: function () {
1161 return this.value.substr(1);
1162 },
1163 evaluate: function () {
1164 return document.querySelectorAll(this.value);
1165 }
1166 };
1167 }
1168 })
1169
1170 _parser.addGrammarElement("attributeRef", function(parser, runtime, tokens) {
1171 if (tokens.matchOpToken("[")) {
1172 var name = tokens.matchTokenType("IDENTIFIER");
1173 var value = null;
1174 if (tokens.matchOpToken("=")) {
1175 value = parser.requireElement("expression", tokens);
1176 }
1177 tokens.requireOpToken("]");
1178 return {
1179 type: "attribute_expression",
1180 name: name.value,
1181 value: value,
1182 args: [value],
1183 op:function(context, value){
1184 if (this.value) {
1185 return {name:this.name, value:value}
1186 } else {
1187 return {name:this.name};
1188 }
1189 },
1190 evaluate: function (context) {
1191 return runtime.unifiedEval(this, context);
1192 }
1193 }
1194 }
1195 })
1196
1197 _parser.addLeafExpression("objectLiteral", function(parser, runtime, tokens) {
1198 if (tokens.matchOpToken("{")) {
1199 var fields = []
1200 var valueExpressions = []
1201 if (!tokens.matchOpToken("}")) {
1202 do {
1203 var name = tokens.requireTokenType("IDENTIFIER");
1204 tokens.requireOpToken(":");
1205 var value = parser.requireElement("expression", tokens);
1206 valueExpressions.push(value);
1207 fields.push({name: name, value: value});
1208 } while (tokens.matchOpToken(","))
1209 tokens.requireOpToken("}");
1210 }
1211 return {
1212 type: "objectLiteral",
1213 fields: fields,
1214 args: [valueExpressions],
1215 op:function(context, values){
1216 var returnVal = {};
1217 for (var i = 0; i < values.length; i++) {
1218 var field = fields[i];
1219 returnVal[field.name.value] = values[i];
1220 }
1221 return returnVal;
1222 },
1223 evaluate: function (context) {
1224 return runtime.unifiedEval(this, context);
1225 }
1226 }
1227 }
1228 })
1229
1230 _parser.addGrammarElement("namedArgumentList", function(parser, runtime, tokens) {
1231 if (tokens.matchOpToken("(")) {
1232 var fields = []
1233 var valueExpressions = []
1234 if (!tokens.matchOpToken(")")) {
1235 do {
1236 var name = tokens.requireTokenType("IDENTIFIER");
1237 tokens.requireOpToken(":");
1238 var value = parser.requireElement("expression", tokens);
1239 valueExpressions.push(value);
1240 fields.push({name: name, value: value});
1241 } while (tokens.matchOpToken(","))
1242 tokens.requireOpToken(")");
1243 }
1244 return {
1245 type: "namedArgumentList",
1246 fields: fields,
1247 args:[valueExpressions],
1248 op:function(context, values){
1249 var returnVal = {_namedArgList_:true};
1250 for (var i = 0; i < values.length; i++) {
1251 var field = fields[i];
1252 returnVal[field.name.value] = values[i];
1253 }
1254 return returnVal;
1255 },
1256 evaluate: function (context) {
1257 return runtime.unifiedEval(this, context);
1258 }
1259 }
1260 }
1261
1262
1263 })
1264
1265 _parser.addGrammarElement("symbol", function(parser, runtime, tokens) {
1266 var identifier = tokens.matchTokenType('IDENTIFIER');
1267 if (identifier) {
1268 return {
1269 type: "symbol",
1270 name: identifier.value,
1271 evaluate: function (context) {
1272 return runtime.resolveSymbol(identifier.value, context);
1273 }
1274 };
1275 }
1276 });
1277
1278 _parser.addGrammarElement("implicitMeTarget", function(parser, runtime, tokens) {
1279 return {
1280 type: "implicitMeTarget",
1281 evaluate: function (context) {
1282 return context.me
1283 }
1284 };
1285 });
1286
1287 _parser.addGrammarElement("implicitAllTarget", function(parser, runtime, tokens) {
1288 return {
1289 type: "implicitAllTarget",
1290 evaluate: function (context) {
1291 return document.querySelectorAll("*");
1292 }
1293 };
1294 });
1295
1296 _parser.addLeafExpression("boolean", function(parser, runtime, tokens) {
1297 var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false");
1298 if (booleanLiteral) {
1299 return {
1300 type: "boolean",
1301 evaluate: function (context) {
1302 return booleanLiteral.value === "true";
1303 }
1304 }
1305 }
1306 });
1307
1308 _parser.addLeafExpression("null", function(parser, runtime, tokens) {
1309 if (tokens.matchToken('null')) {
1310 return {
1311 type: "null",
1312 evaluate: function (context) {
1313 return null;
1314 }
1315 }
1316 }
1317 });
1318
1319 _parser.addLeafExpression("arrayLiteral", function(parser, runtime, tokens) {
1320 if (tokens.matchOpToken('[')) {
1321 var values = [];
1322 if (!tokens.matchOpToken(']')) {
1323 do {
1324 var expr = parser.requireElement("expression", tokens);
1325 values.push(expr);
1326 } while (tokens.matchOpToken(","))
1327 tokens.requireOpToken("]");
1328 }
1329 return {
1330 type: "arrayLiteral",
1331 values: values,
1332 args: [values],
1333 op:function(context, values){
1334 return values;
1335 },
1336 evaluate: function (context) {
1337 return runtime.unifiedEval(this, context);
1338 }
1339 }
1340 }
1341 });
1342
1343 _parser.addLeafExpression("blockLiteral", function(parser, runtime, tokens) {
1344 if (tokens.matchOpToken('\\')) {
1345 var args = []
1346 var arg1 = tokens.matchTokenType("IDENTIFIER");
1347 if (arg1) {
1348 args.push(arg1);
1349 while (tokens.matchOpToken(",")) {
1350 args.push(tokens.requireTokenType("IDENTIFIER"));
1351 }
1352 }
1353 // TODO compound op token
1354 tokens.requireOpToken("-");
1355 tokens.requireOpToken(">");
1356 var expr = parser.requireElement("expression", tokens);
1357 return {
1358 type: "blockLiteral",
1359 args: args,
1360 expr: expr,
1361 evaluate: function (ctx) {
1362 var returnFunc = function(){
1363 //TODO - push scope
1364 for (var i = 0; i < args.length; i++) {
1365 ctx[args[i].value] = arguments[i];
1366 }
1367 return expr.evaluate(ctx) //OK
1368 }
1369 return returnFunc;
1370 }
1371 }
1372 }
1373 });
1374
1375 _parser.addGrammarElement("timeExpression", function(parser, runtime, tokens){
1376 var time = parser.requireElement("expression", tokens);
1377 var factor = 1;
1378 if (tokens.matchToken("s") || tokens.matchToken("seconds")) {
1379 factor = 1000;
1380 } else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) {
1381 // do nothing
1382 }
1383 return {
1384 type:"timeExpression",
1385 time: time,
1386 factor: factor,
1387 args: [time],
1388 op: function (context, val) {
1389 return val * this.factor
1390 },
1391 evaluate: function (context) {
1392 return runtime.unifiedEval(this, context);
1393 }
1394 }
1395 })
1396
1397 _parser.addIndirectExpression("propertyAccess", function(parser, runtime, tokens, root) {
1398 if (tokens.matchOpToken(".")) {
1399 var prop = tokens.requireTokenType("IDENTIFIER");
1400 var propertyAccess = {
1401 type: "propertyAccess",
1402 root: root,
1403 prop: prop,
1404 args: [root],
1405 op:function(context, rootVal){
1406 return runtime.resolveProperty(rootVal, prop.value);
1407 },
1408 evaluate: function (context) {
1409 return runtime.unifiedEval(this, context);
1410 }
1411 };
1412 return parser.parseElement("indirectExpression", tokens, propertyAccess);
1413 }
1414 });
1415
1416 _parser.addIndirectExpression("functionCall", function(parser, runtime, tokens, root) {
1417 if (tokens.matchOpToken("(")) {
1418 var args = [];
1419 if (!tokens.matchOpToken(')')) {
1420 do {
1421 args.push(parser.requireElement("expression", tokens));
1422 } while (tokens.matchOpToken(","))
1423 tokens.requireOpToken(")");
1424 }
1425
1426 if (root.root) {
1427 var functionCall = {
1428 type: "functionCall",
1429 root: root,
1430 argExressions: args,
1431 args: [root.root, args],
1432 op: function (context, rootRoot, args) {
1433 var func = rootRoot[root.prop.value];
1434 return func.apply(rootRoot, args);
1435 },
1436 evaluate: function (context) {
1437 return runtime.unifiedEval(this, context);
1438 }
1439 }
1440 } else {
1441 var functionCall = {
1442 type: "functionCall",
1443 root: root,
1444 argExressions: args,
1445 args: [root, args],
1446 op: function(context, func, argVals){
1447 var apply = func.apply(null, argVals);
1448 return apply;
1449 },
1450 evaluate: function (context) {
1451 return runtime.unifiedEval(this, context);
1452 }
1453 }
1454 }
1455 return parser.parseElement("indirectExpression", tokens, functionCall);
1456 }
1457 });
1458
1459 _parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) {
1460 if (tokens.matchOpToken("[")) {
1461 var index = parser.requireElement("expression", tokens);
1462 tokens.requireOpToken("]")
1463
1464 var arrayIndex = {
1465 type: "arrayIndex",
1466 root: root,
1467 index: index,
1468 args: [root, index],
1469 op: function(ctx, root, index) {
1470 return root[index]
1471 },
1472 evaluate: function(context){
1473 return _runtime.unifiedEval(this, context);
1474 }
1475 };
1476
1477 return _parser.parseElement("indirectExpression", tokens, arrayIndex);
1478 }
1479 });
1480
1481 _parser.addGrammarElement("postfixExpression", function(parser, runtime, tokens) {
1482 var root = parser.parseElement("primaryExpression", tokens);
1483 if (tokens.matchOpToken(":")) {
1484 var typeName = tokens.requireTokenType("IDENTIFIER");
1485 var nullOk = !tokens.matchOpToken("!");
1486 return {
1487 type: "typeCheck",
1488 typeName: typeName,
1489 root: root,
1490 nullOk: nullOk,
1491 args: [root],
1492 op: function (context, val) {
1493 return runtime.typeCheck(val, this.typeName.value, this.nullOk);
1494 },
1495 evaluate: function (context) {
1496 return runtime.unifiedEval(this, context);
1497 }
1498 }
1499 } else {
1500 return root;
1501 }
1502 });
1503
1504 _parser.addGrammarElement("logicalNot", function(parser, runtime, tokens) {
1505 if (tokens.matchToken("not")) {
1506 var root = parser.requireElement("unaryExpression", tokens);
1507 return {
1508 type: "logicalNot",
1509 root: root,
1510 args: [root],
1511 op: function (context, val) {
1512 return !val;
1513 },
1514 evaluate: function (context) {
1515 return runtime.unifiedEval(this, context);
1516 }
1517 };
1518 }
1519 });
1520
1521 _parser.addGrammarElement("noExpression", function(parser, runtime, tokens) {
1522 if (tokens.matchToken("no")) {
1523 var root = parser.requireElement("unaryExpression", tokens);
1524 return {
1525 type: "noExpression",
1526 root: root,
1527 args: [root],
1528 op: function (context, val) {
1529 return val == null;
1530 },
1531 evaluate: function (context) {
1532 return runtime.unifiedEval(this, context);
1533 }
1534 };
1535 }
1536 });
1537
1538 _parser.addGrammarElement("negativeNumber", function(parser, runtime, tokens) {
1539 if (tokens.matchOpToken("-")) {
1540 var root = parser.requireElement("unaryExpression", tokens);
1541 return {
1542 type: "negativeNumber",
1543 root: root,
1544 args: [root],
1545 op:function(context, value){
1546 return -1 * value;
1547 },
1548 evaluate: function (context) {
1549 return runtime.unifiedEval(this, context);
1550 }
1551 };
1552 }
1553 });
1554
1555 _parser.addGrammarElement("unaryExpression", function(parser, runtime, tokens) {
1556 return parser.parseAnyOf(["logicalNot", "noExpression", "negativeNumber", "postfixExpression"], tokens);
1557 });
1558
1559 _parser.addGrammarElement("mathOperator", function(parser, runtime, tokens) {
1560 var expr = parser.parseElement("unaryExpression", tokens);
1561 var mathOp, initialMathOp = null;
1562 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
1563 while (mathOp) {
1564 initialMathOp = initialMathOp || mathOp;
1565 var operator = mathOp.value;
1566 if (initialMathOp.value !== operator) {
1567 parser.raiseParseError(tokens, "You must parenthesize math operations with different operators")
1568 }
1569 var rhs = parser.parseElement("unaryExpression", tokens);
1570 expr = {
1571 type: "mathOperator",
1572 lhs: expr,
1573 rhs: rhs,
1574 operator: operator,
1575 args: [expr, rhs],
1576 op:function (context, lhsVal, rhsVal) {
1577 if (this.operator === "+") {
1578 return lhsVal + rhsVal;
1579 } else if (this.operator === "-") {
1580 return lhsVal - rhsVal;
1581 } else if (this.operator === "*") {
1582 return lhsVal * rhsVal;
1583 } else if (this.operator === "/") {
1584 return lhsVal / rhsVal;
1585 } else if (this.operator === "%") {
1586 return lhsVal % rhsVal;
1587 }
1588 },
1589 evaluate: function (context) {
1590 return runtime.unifiedEval(this, context);
1591 }
1592 }
1593 mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
1594 }
1595 return expr;
1596 });
1597
1598 _parser.addGrammarElement("mathExpression", function(parser, runtime, tokens) {
1599 return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens);
1600 });
1601
1602 _parser.addGrammarElement("comparisonOperator", function(parser, runtime, tokens) {
1603 var expr = parser.parseElement("mathExpression", tokens);
1604 var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==")
1605 var comparisonStr = comparisonToken ? comparisonToken.value : null;
1606 if (comparisonStr == null && tokens.matchToken("is")) {
1607 if (tokens.matchToken("not")) {
1608 comparisonStr = "!=";
1609 } else {
1610 comparisonStr = "==";
1611 }
1612 }
1613 if (comparisonStr) { // Do not allow chained comparisons, which is dumb
1614 var rhs = parser.requireElement("mathExpression", tokens);
1615 expr = {
1616 type: "comparisonOperator",
1617 operator: comparisonStr,
1618 lhs: expr,
1619 rhs: rhs,
1620 args: [expr, rhs],
1621 op:function (context, lhsVal, rhsVal) {
1622 if (this.operator === "<") {
1623 return lhsVal < rhsVal;
1624 } else if (this.operator === ">") {
1625 return lhsVal > rhsVal;
1626 } else if (this.operator === "<=") {
1627 return lhsVal <= rhsVal;
1628 } else if (this.operator === ">=") {
1629 return lhsVal >= rhsVal;
1630 } else if (this.operator === "==") {
1631 return lhsVal == rhsVal;
1632 } else if (this.operator === "===") {
1633 return lhsVal === rhsVal;
1634 } else if (this.operator === "!=") {
1635 return lhsVal != rhsVal;
1636 } else if (this.operator === "!==") {
1637 return lhsVal !== rhsVal;
1638 }
1639 },
1640 evaluate: function (context) {
1641 return runtime.unifiedEval(this, context);
1642 }
1643 };
1644 }
1645 return expr;
1646 });
1647
1648 _parser.addGrammarElement("comparisonExpression", function(parser, runtime, tokens) {
1649 return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens);
1650 });
1651
1652 _parser.addGrammarElement("logicalOperator", function(parser, runtime, tokens) {
1653 var expr = parser.parseElement("comparisonExpression", tokens);
1654 var logicalOp, initialLogicalOp = null;
1655 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
1656 while (logicalOp) {
1657 initialLogicalOp = initialLogicalOp || logicalOp;
1658 if (initialLogicalOp.value !== logicalOp.value) {
1659 parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators")
1660 }
1661 var rhs = parser.requireElement("comparisonExpression", tokens);
1662 expr = {
1663 type: "logicalOperator",
1664 operator: logicalOp.value,
1665 lhs: expr,
1666 rhs: rhs,
1667 args: [expr, rhs],
1668 op: function (context, lhsVal, rhsVal) {
1669 if (this.operator === "and") {
1670 return lhsVal && rhsVal;
1671 } else {
1672 return lhsVal || rhsVal;
1673 }
1674 },
1675 evaluate: function (context) {
1676 return runtime.unifiedEval(this, context);
1677 }
1678 }
1679 logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
1680 }
1681 return expr;
1682 });
1683
1684 _parser.addGrammarElement("logicalExpression", function(parser, runtime, tokens) {
1685 return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens);
1686 });
1687
1688 _parser.addGrammarElement("asyncExpression", function(parser, runtime, tokens) {
1689 if (tokens.matchToken('async')) {
1690 var value = parser.requireElement("logicalExpression", tokens);
1691 var expr = {
1692 type: "asyncExpression",
1693 value: value,
1694 evaluate: function (context) {
1695 return {
1696 asyncWrapper: true,
1697 value: this.value.evaluate(context) //OK
1698 }
1699 }
1700 }
1701 return expr;
1702 } else {
1703 return parser.parseElement("logicalExpression", tokens);
1704 }
1705 });
1706
1707 _parser.addGrammarElement("expression", function(parser, runtime, tokens) {
1708 return parser.parseElement("asyncExpression", tokens);
1709 });
1710
1711 _parser.addGrammarElement("target", function(parser, runtime, tokens) {
1712 var expr = _parser.parseElement("expression", tokens);
1713 if (expr.type === "symbol" || expr.type === "idRef" ||
1714 expr.type === "classRef" || expr.type === "propertyAccess") {
1715 return expr;
1716 } else {
1717 _parser.raiseParseError(tokens, "A target expression must be writable");
1718 }
1719 return expr;
1720 });
1721
1722 _parser.addGrammarElement("hyperscript", function(parser, runtime, tokens) {
1723 var onFeatures = []
1724 var functionFeatures = []
1725
1726 if (tokens.hasMore()) {
1727 do {
1728 var feature = parser.requireElement("feature", tokens);
1729 if (feature.type === "onFeature") {
1730 onFeatures.push(feature);
1731 } else if(feature.type === "defFeature") {
1732 feature.execute();
1733 functionFeatures.push(feature);
1734 } else if (feature.type === "jsFeature") {
1735 feature.execute();
1736 } else { // this is a plugin feature
1737 if ('execute' in feature) {
1738 feature.execute();
1739 }
1740 }
1741 var chainedOn = feature.type === "onFeature" && tokens.currentToken() && tokens.currentToken().value === "on";
1742 } while ((chainedOn || tokens.matchToken("end")) && tokens.hasMore())
1743 if (tokens.hasMore()) {
1744 parser.raiseParseError(tokens);
1745 }
1746 }
1747 return {
1748 type: "hyperscript",
1749 onFeatures: onFeatures,
1750 functions: functionFeatures,
1751 execute: function () {
1752 // no op
1753 }
1754 };
1755 })
1756
1757 _parser.addFeature("on", function(parser, runtime, tokens) {
1758 if (tokens.matchToken('on')) {
1759 var every = false;
1760 if (tokens.matchToken("every")) {
1761 every = true;
1762 }
1763 var on = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
1764
1765 var args = [];
1766 if (tokens.matchOpToken("(")) {
1767 do {
1768 args.push(tokens.requireTokenType('IDENTIFIER'));
1769 } while (tokens.matchOpToken(","))
1770 tokens.requireOpToken(')')
1771 }
1772
1773 var filter = null;
1774 if (tokens.matchOpToken('[')) {
1775 filter = parser.requireElement("expression", tokens);
1776 tokens.requireOpToken(']');
1777 }
1778
1779 var from = null;
1780 var elsewhere = false;
1781 if (tokens.matchToken("from")) {
1782 if (tokens.matchToken('elsewhere')) {
1783 elsewhere = true;
1784 } else {
1785 from = parser.parseElement("target", tokens)
1786 if (!from) {
1787 parser.raiseParseError('Expected either target value or "elsewhere".', tokens);
1788 }
1789 }
1790 }
1791
1792 // support both "elsewhere" and "from elsewhere"
1793 if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) {
1794 elsewhere = true;
1795 }
1796
1797 var start = parser.requireElement("commandList", tokens);
1798
1799 var implicitReturn = {
1800 type: "implicitReturn",
1801 op: function (context) {
1802 // automatically resolve at the end of an event handler if nothing else does
1803 context.meta.resolve();
1804 return runtime.HALT;
1805 },
1806 execute: function (ctx) {
1807 // do nothing
1808 }
1809 };
1810 if (start) {
1811 var end = start;
1812 while (end.next) {
1813 end = end.next;
1814 }
1815 end.next = implicitReturn
1816 } else {
1817 start = implicitReturn
1818 }
1819
1820 var onFeature = {
1821 args: args,
1822 on: on,
1823 every: every,
1824 from: from,
1825 elsewhere: elsewhere,
1826 filter: filter,
1827 start: start,
1828 executing: false,
1829 execCount: 0,
1830 execute: function (ctx) {
1831 if (this.executing && this.every === false) {
1832 return;
1833 }
1834 this.execCount++;
1835 this.executing = true;
1836 runtime.forEach(args, function (arg) {
1837 ctx[arg.value] = ctx.event[arg.value] || (ctx.event.detail ? ctx.event.detail[arg.value] : null);
1838 });
1839 if (filter) {
1840 var initialCtx = ctx.meta.context;
1841 ctx.meta.context = ctx.event;
1842 try {
1843 var value = filter.evaluate(ctx); //OK NO PROMISE
1844 if (value) {
1845 // match the javascript semantics for if statements
1846 } else {
1847 this.executing = false;
1848 return;
1849 }
1850 } finally {
1851 ctx.meta.context = initialCtx;
1852 }
1853 }
1854
1855 ctx.meta.resolve = function () {
1856 onFeature.executing = false;
1857 }
1858 ctx.meta.reject = function (err) {
1859 console.error(err);
1860 runtime.triggerEvent(ctx.me, 'exception', {error: err})
1861 onFeature.executing = false;
1862 }
1863 start.execute(ctx);
1864 }
1865 };
1866 parser.setParent(start, onFeature);
1867 return onFeature;
1868 }
1869 });
1870
1871 _parser.addFeature("def", function(parser, runtime, tokens) {
1872 if (tokens.matchToken('def')) {
1873 var functionName = parser.requireElement("dotOrColonPath", tokens);
1874 var nameVal = functionName.evaluate(); // OK
1875 var nameSpace = nameVal.split(".");
1876 var funcName = nameSpace.pop();
1877
1878 var args = [];
1879 if (tokens.matchOpToken("(")) {
1880 if (tokens.matchOpToken(")")) {
1881 // emtpy args list
1882 } else {
1883 do {
1884 args.push(tokens.requireTokenType('IDENTIFIER'));
1885 } while (tokens.matchOpToken(","))
1886 tokens.requireOpToken(')')
1887 }
1888 }
1889
1890 var start = parser.parseElement("commandList", tokens);
1891 var functionFeature = {
1892 name: funcName,
1893 args: args,
1894 start: start,
1895 execute: function (ctx) {
1896 runtime.assignToNamespace(nameSpace, funcName, function () {
1897 // null, worker
1898 var root = 'document' in globalScope ? document.body : null
1899 var elt = 'document' in globalScope ? document.body : globalScope
1900 var ctx = runtime.makeContext(root, elt, null);
1901 for (var i = 0; i < arguments.length; i++) {
1902 var argumentVal = arguments[i];
1903 var name = args[i];
1904 if (name) {
1905 ctx[name.value] = argumentVal;
1906 }
1907 }
1908 var resolve, reject = null;
1909 var promise = new Promise(function (theResolve, theReject) {
1910 resolve = theResolve;
1911 reject = theReject;
1912 });
1913 start.execute(ctx);
1914 if (ctx.meta.returned) {
1915 return ctx.meta.returnValue;
1916 } else {
1917 ctx.meta.resolve = resolve;
1918 ctx.meta.reject = reject;
1919 return promise
1920 }
1921 });
1922 }
1923 };
1924
1925 var implicitReturn = {
1926 type: "implicitReturn",
1927 op: function (context) {
1928 // automatically return at the end of the function if nothing else does
1929 context.meta.returned = true;
1930 if (context.meta.resolve) {
1931 context.meta.resolve();
1932 }
1933 return runtime.HALT;
1934 },
1935 execute: function (context) {
1936 // do nothing
1937 }
1938 }
1939 if (start) {
1940 var end = start;
1941 while (end.next) {
1942 end = end.next;
1943 }
1944 end.next = implicitReturn
1945 } else {
1946 functionFeature.start = implicitReturn
1947 }
1948
1949 parser.setParent(start, functionFeature);
1950 return functionFeature;
1951 }
1952 });
1953
1954 _parser.addFeature("worker", function (parser, runtime, tokens) {
1955 if (tokens.matchToken("worker")) {
1956 parser.raiseParseError(tokens,
1957 "In order to use the 'worker' feature, include " +
1958 "the _hyperscript worker plugin. See " +
1959 "https://hyperscript.org/features/worker/ for " +
1960 "more info.")
1961 }
1962 })
1963
1964 _parser.addGrammarElement("jsBody", function(parser, runtime, tokens) {
1965 var jsSourceStart = tokens.currentToken().start;
1966 var jsLastToken = tokens.currentToken();
1967
1968 var funcNames = [];
1969 var funcName = "";
1970 var expectFunctionDeclaration = false;
1971 while (tokens.hasMore()) {
1972 jsLastToken = tokens.consumeToken();
1973 var peek = tokens.currentToken(true);
1974 if (peek.type === "IDENTIFIER"
1975 && peek.value === "end") {
1976 break;
1977 }
1978 if (expectFunctionDeclaration) {
1979 if (jsLastToken.type === "IDENTIFIER"
1980 || jsLastToken.type === "NUMBER") {
1981 funcName += jsLastToken.value;
1982 } else {
1983 if (funcName !== "") funcNames.push(funcName);
1984 funcName = "";
1985 expectFunctionDeclaration = false;
1986 }
1987 } else if (jsLastToken.type === "IDENTIFIER"
1988 && jsLastToken.value === "function") {
1989 expectFunctionDeclaration = true;
1990 }
1991 }
1992 var jsSourceEnd = jsLastToken.end + 1;
1993
1994 return {
1995 type: 'jsBody',
1996 exposedFunctionNames: funcNames,
1997 jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd),
1998 }
1999 })
2000
2001 _parser.addFeature("js", function(parser, runtime, tokens) {
2002 if (tokens.matchToken('js')) {
2003
2004 var jsBody = parser.parseElement('jsBody', tokens);
2005
2006 var jsSource = jsBody.jsSource +
2007 "\nreturn { " +
2008 jsBody.exposedFunctionNames.map(function (name) {
2009 return name+":"+name;
2010 }).join(",") +
2011 " } ";
2012 var func = new Function(jsSource);
2013
2014 return {
2015 jsSource: jsSource,
2016 function: func,
2017 exposedFunctionNames: jsBody.exposedFunctionNames,
2018 execute: function() {
2019 mergeObjects(globalScope, func())
2020 }
2021 }
2022 }
2023 })
2024
2025 _parser.addCommand("js", function (parser, runtime, tokens) {
2026 if (tokens.matchToken("js")) {
2027 // Parse inputs
2028 var inputs = [];
2029 if (tokens.matchOpToken("(")) {
2030 if (tokens.matchOpToken(")")) {
2031 // empty input list
2032 } else {
2033 do {
2034 var inp = tokens.requireTokenType('IDENTIFIER');
2035 inputs.push(inp.value);
2036 } while (tokens.matchOpToken(","));
2037 tokens.requireOpToken(')');
2038 }
2039 }
2040
2041 var jsBody = parser.parseElement('jsBody', tokens);
2042 tokens.matchToken('end');
2043
2044 var func = varargConstructor(Function, inputs.concat([jsBody.jsSource]));
2045
2046 return {
2047 jsSource: jsBody.jsSource,
2048 function: func,
2049 inputs: inputs,
2050 op: function (context) {
2051 var args = [];
2052 inputs.forEach(function (input) {
2053 args.push(runtime.resolveSymbol(input, context))
2054 });
2055 var result = func.apply(globalScope, args)
2056 if (result && typeof result.then === 'function') {
2057 return Promise(function (resolve) {
2058 result.then(function (actualResult) {
2059 context.it = actualResult
2060 resolve(runtime.findNext(this));
2061 })
2062 })
2063 } else {
2064 context.it = result
2065 return runtime.findNext(this);
2066 }
2067 }
2068 };
2069 }
2070 })
2071
2072 _parser.addCommand("add", function(parser, runtime, tokens) {
2073 if (tokens.matchToken("add")) {
2074 var classRef = parser.parseElement("classRef", tokens);
2075 var attributeRef = null;
2076 if (classRef == null) {
2077 attributeRef = parser.parseElement("attributeRef", tokens);
2078 if (attributeRef == null) {
2079 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
2080 }
2081 }
2082
2083 if (tokens.matchToken("to")) {
2084 var to = parser.requireElement("target", tokens);
2085 } else {
2086 var to = parser.parseElement("implicitMeTarget");
2087 }
2088
2089 if (classRef) {
2090 var addCmd = {
2091 classRef: classRef,
2092 attributeRef: attributeRef,
2093 to: to,
2094 args: [to],
2095 op: function (context, to) {
2096 runtime.forEach(to, function (target) {
2097 target.classList.add(classRef.className());
2098 })
2099 return runtime.findNext(this);
2100 }
2101 }
2102 } else {
2103 var addCmd = {
2104 type: "addCmd",
2105 classRef: classRef,
2106 attributeRef: attributeRef,
2107 to: to,
2108 args: [to, attributeRef],
2109 op: function (context, to, attrRef) {
2110 runtime.forEach(to, function (target) {
2111 target.setAttribute(attrRef.name, attrRef.value);
2112 })
2113 return runtime.findNext(addCmd, context);
2114 },
2115 execute: function (ctx) {
2116 return runtime.unifiedExec(this, ctx);
2117 }
2118 };
2119 }
2120 return addCmd
2121 }
2122 });
2123
2124 _parser.addCommand("remove", function(parser, runtime, tokens) {
2125 if (tokens.matchToken('remove')) {
2126 var classRef = parser.parseElement("classRef", tokens);
2127 var attributeRef = null;
2128 var elementExpr = null;
2129 if (classRef == null) {
2130 attributeRef = parser.parseElement("attributeRef", tokens);
2131 if (attributeRef == null) {
2132 elementExpr = parser.parseElement("expression", tokens)
2133 if (elementExpr == null) {
2134 parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression");
2135 }
2136 }
2137 }
2138 if (tokens.matchToken("from")) {
2139 var from = parser.requireElement("target", tokens);
2140 } else {
2141 var from = parser.requireElement("implicitMeTarget");
2142 }
2143
2144 if (elementExpr) {
2145 var removeCmd = {
2146 classRef: classRef,
2147 attributeRef: attributeRef,
2148 elementExpr: elementExpr,
2149 from: from,
2150 args: [elementExpr],
2151 op: function (context, element) {
2152 runtime.forEach(element, function (target) {
2153 target.parentElement.removeChild(target);
2154 })
2155 return runtime.findNext(this);
2156 }
2157 };
2158 } else {
2159 var removeCmd = {
2160 classRef: classRef,
2161 attributeRef: attributeRef,
2162 elementExpr: elementExpr,
2163 from: from,
2164 args: [from],
2165 op: function (context, from) {
2166 if (this.classRef) {
2167 runtime.forEach(from, function (target) {
2168 target.classList.remove(classRef.className());
2169 })
2170 } else {
2171 runtime.forEach(from, function (target) {
2172 target.removeAttribute(attributeRef.name);
2173 })
2174 }
2175 return runtime.findNext(this);
2176 }
2177 };
2178
2179 }
2180 return removeCmd
2181 }
2182 });
2183
2184 _parser.addCommand("toggle", function(parser, runtime, tokens) {
2185 if (tokens.matchToken('toggle')) {
2186 var classRef = parser.parseElement("classRef", tokens);
2187 var attributeRef = null;
2188 if (classRef == null) {
2189 attributeRef = parser.parseElement("attributeRef", tokens);
2190 if (attributeRef == null) {
2191 parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
2192 }
2193 }
2194
2195 if (tokens.matchToken("on")) {
2196 var on = parser.requireElement("target", tokens);
2197 } else {
2198 var on = parser.requireElement("implicitMeTarget");
2199 }
2200
2201 if (tokens.matchToken("for")) {
2202 var time = parser.requireElement("timeExpression", tokens);
2203 } else if (tokens.matchToken("until")) {
2204 var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
2205 if (tokens.matchToken("from")) {
2206 var from = parser.requireElement("expression", tokens);
2207 }
2208 }
2209
2210 var toggleCmd = {
2211 classRef: classRef,
2212 attributeRef: attributeRef,
2213 on: on,
2214 time: time,
2215 evt: evt,
2216 from: from,
2217 toggle: function (on, value) {
2218 if (this.classRef) {
2219 runtime.forEach(on, function (target) {
2220 target.classList.toggle(classRef.className())
2221 });
2222 } else {
2223 runtime.forEach(on, function (target) {
2224 if (target.hasAttribute(attributeRef.name)) {
2225 target.removeAttribute(attributeRef.name);
2226 } else {
2227 target.setAttribute(attributeRef.name, value)
2228 }
2229 });
2230 }
2231 },
2232 args: [on, attributeRef ? attributeRef.value : null, time, evt, from],
2233 op: function (context, on, value, time, evt, from) {
2234 if (time) {
2235 return new Promise(function (resolve) {
2236 toggleCmd.toggle(on, value);
2237 setTimeout(function () {
2238 toggleCmd.toggle(on, value);
2239 resolve(runtime.findNext(toggleCmd));
2240 }, time);
2241 });
2242 } else if (evt) {
2243 return new Promise(function (resolve) {
2244 var target = from || context.me;
2245 target.addEventListener(evt, function () {
2246 toggleCmd.toggle(on, value);
2247 resolve(runtime.findNext(toggleCmd));
2248 }, {once: true})
2249 toggleCmd.toggle(on, value);
2250 });
2251 } else {
2252 this.toggle(on, value);
2253 return runtime.findNext(toggleCmd);
2254 }
2255 }
2256 };
2257 return toggleCmd
2258 }
2259 })
2260
2261 var HIDE_SHOW_STRATEGIES = {
2262 "display": function (op, element, arg) {
2263 if(arg){
2264 element.style.display = arg;
2265 } else if (op === 'hide') {
2266 element.style.display = 'none';
2267 } else {
2268 element.style.display = 'block';
2269 }
2270 },
2271 "visibility": function (op, element, arg) {
2272 if(arg){
2273 element.style.visibility = arg;
2274 } else if (op === 'hide') {
2275 element.style.visibility = 'hidden';
2276 } else {
2277 element.style.visibility = 'visible';
2278 }
2279 },
2280 "opacity": function (op, element, arg) {
2281 if(arg){
2282 element.style.opacity = arg;
2283 } else if (op === 'hide') {
2284 element.style.opacity = '0';
2285 } else {
2286 element.style.opacity = '1';
2287 }
2288 }
2289 }
2290
2291 var parseShowHideTarget = function (parser, runtime, tokens) {
2292 var target;
2293 var currentTokenValue = tokens.currentToken();
2294 if (currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) {
2295 target = parser.parseElement("implicitMeTarget", tokens);
2296 } else {
2297 target = parser.parseElement("target", tokens);
2298 }
2299 return target;
2300 }
2301
2302 var resolveStrategy = function (parser, tokens, name) {
2303 var configDefault = _hyperscript.config.defaultHideShowStrategy;
2304 var strategies = HIDE_SHOW_STRATEGIES;
2305 if (_hyperscript.config.hideShowStrategies) {
2306 strategies = mergeObjects(strategies, _hyperscript.config.hideShowStrategies); // merge in user provided strategies
2307 }
2308 name = name || configDefault || "display";
2309 var value = strategies[name];
2310 if (value == null) {
2311 parser.raiseParseError(tokens, 'Unknown show/hide strategy : ' + name);
2312 }
2313 return value;
2314 }
2315
2316 _parser.addCommand("hide", function (parser, runtime, tokens) {
2317 if (tokens.matchToken("hide")) {
2318 var target = parseShowHideTarget(parser, runtime, tokens);
2319
2320 var name = null;
2321 if (tokens.matchToken("with")) {
2322 name = tokens.requireTokenType("IDENTIFIER").value;
2323 }
2324 var hideShowStrategy = resolveStrategy(parser, tokens, name);
2325
2326 return {
2327 target: target,
2328 args: [target],
2329 op: function (ctx, target) {
2330 runtime.forEach(target, function (elt) {
2331 hideShowStrategy('hide', elt);
2332 });
2333 return runtime.findNext(this);
2334 }
2335 }
2336 }
2337 });
2338
2339 _parser.addCommand("show", function (parser, runtime, tokens) {
2340 if (tokens.matchToken("show")) {
2341 var target = parseShowHideTarget(parser, runtime, tokens);
2342
2343 var name = null;
2344 if (tokens.matchToken("with")) {
2345 name = tokens.requireTokenType("IDENTIFIER").value;
2346 }
2347 var arg = null;
2348 if (tokens.matchOpToken(":")) {
2349 var tokenArr = tokens.consumeUntilWhitespace();
2350 tokens.matchTokenType("WHITESPACE");
2351 arg = tokenArr.map(function (t) {
2352 return t.value
2353 }).join("");
2354 }
2355 var hideShowStrategy = resolveStrategy(parser, tokens, name);
2356
2357 return {
2358 target: target,
2359 args: [target],
2360 op: function (ctx, target) {
2361 runtime.forEach(target, function (elt) {
2362 hideShowStrategy('show', elt, arg);
2363 });
2364 return runtime.findNext(this);
2365 }
2366 }
2367 }
2368 });
2369
2370 _parser.addCommand("wait", function(parser, runtime, tokens) {
2371 if (tokens.matchToken("wait")) {
2372 // wait on event
2373 if (tokens.matchToken("for")) {
2374 tokens.matchToken("a"); // optional "a"
2375 var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
2376 if (tokens.matchToken("from")) {
2377 var on = parser.requireElement("expression", tokens);
2378 }
2379 // wait on event
2380 var waitCmd = {
2381 event: evt,
2382 on: on,
2383 args: [evt, on],
2384 op: function (context, eventName, on) {
2385 var target = on ? on : context.me;
2386 return new Promise(function (resolve) {
2387 var listener = function () {
2388 resolve(runtime.findNext(waitCmd));
2389 };
2390 target.addEventListener(eventName, listener, {once: true});
2391 });
2392 }
2393 };
2394 } else {
2395 var time = _parser.requireElement("timeExpression", tokens);
2396 var waitCmd = {
2397 type: "waitCmd",
2398 time: time,
2399 args: [time],
2400 op: function (context, timeValue) {
2401 return new Promise(function (resolve) {
2402 setTimeout(function () {
2403 resolve(runtime.findNext(waitCmd));
2404 }, timeValue);
2405 });
2406 },
2407 execute: function (context) {
2408 return runtime.unifiedExec(this, context);
2409 }
2410 };
2411 }
2412 return waitCmd
2413 }
2414 })
2415
2416 // TODO - colon path needs to eventually become part of ruby-style symbols
2417 _parser.addGrammarElement("dotOrColonPath", function(parser, runtime, tokens) {
2418 var root = tokens.matchTokenType("IDENTIFIER");
2419 if (root) {
2420 var path = [root.value];
2421
2422 var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":");
2423 if (separator) {
2424 do {
2425 path.push(tokens.requireTokenType("IDENTIFIER").value);
2426 } while (tokens.matchOpToken(separator.value))
2427 }
2428
2429 return {
2430 type: "dotOrColonPath",
2431 path: path,
2432 evaluate: function () {
2433 return path.join(separator ? separator.value : "");
2434 }
2435 }
2436 }
2437 });
2438
2439 _parser.addCommand("send", function(parser, runtime, tokens) {
2440 if (tokens.matchToken('send')) {
2441 var eventName = parser.requireElement("dotOrColonPath", tokens);
2442
2443 var details = parser.parseElement("namedArgumentList", tokens);
2444 if (tokens.matchToken("to")) {
2445 var to = parser.requireElement("target", tokens);
2446 } else {
2447 var to = parser.requireElement("implicitMeTarget");
2448 }
2449
2450
2451 var sendCmd = {
2452 eventName: eventName,
2453 details: details,
2454 to: to,
2455 args: [to, eventName, details],
2456 op: function (context, to, eventName, details) {
2457 runtime.forEach(to, function (target) {
2458 runtime.triggerEvent(target, eventName, details ? details : {});
2459 });
2460 return runtime.findNext(sendCmd);
2461 }
2462 };
2463 return sendCmd
2464 }
2465 })
2466
2467 _parser.addCommand("return", function(parser, runtime, tokens) {
2468 if (tokens.matchToken('return')) {
2469 var value = parser.requireElement("expression", tokens);
2470
2471 var returnCmd = {
2472 value: value,
2473 args: [value],
2474 op: function (context, value) {
2475 var resolve = context.meta.resolve;
2476 context.meta.returned = true;
2477 if (resolve) {
2478 if (value) {
2479 resolve(value);
2480 } else {
2481 resolve()
2482 }
2483 } else {
2484 context.meta.returned = true;
2485 context.meta.returnValue = value;
2486 }
2487 return runtime.HALT;
2488 }
2489 };
2490 return returnCmd
2491 }
2492 })
2493
2494 _parser.addCommand("trigger", function(parser, runtime, tokens) {
2495 if (tokens.matchToken('trigger')) {
2496 var eventName = parser.requireElement("dotOrColonPath", tokens);
2497 var details = parser.parseElement("namedArgumentList", tokens);
2498
2499 var triggerCmd = {
2500 eventName: eventName,
2501 details: details,
2502 args: [eventName, details],
2503 op: function (context, eventNameStr, details) {
2504 runtime.triggerEvent(context.me, eventNameStr, details ? details : {});
2505 return runtime.findNext(triggerCmd);
2506 }
2507 };
2508 return triggerCmd
2509 }
2510 })
2511
2512 _parser.addCommand("take", function(parser, runtime, tokens) {
2513 if (tokens.matchToken('take')) {
2514 var classRef = tokens.requireTokenType(tokens, "CLASS_REF");
2515
2516 if (tokens.matchToken("from")) {
2517 var from = parser.requireElement("target", tokens);
2518 } else {
2519 var from = parser.requireElement("implicitAllTarget")
2520 }
2521
2522 if (tokens.matchToken("for")) {
2523 var forElt = parser.requireElement("target", tokens);
2524 } else {
2525 var forElt = parser.requireElement("implicitMeTarget")
2526 }
2527
2528 var takeCmd = {
2529 classRef: classRef,
2530 from: from,
2531 forElt: forElt,
2532 args: [from, forElt],
2533 op: function (context, from, forElt) {
2534 var clazz = this.classRef.value.substr(1)
2535 runtime.forEach(from, function (target) {
2536 target.classList.remove(clazz);
2537 })
2538 runtime.forEach(forElt, function (target) {
2539 target.classList.add(clazz);
2540 });
2541 return runtime.findNext(this);
2542 }
2543 };
2544 return takeCmd
2545 }
2546 })
2547
2548 _parser.addCommand("log", function(parser, runtime, tokens) {
2549 if (tokens.matchToken('log')) {
2550 var exprs = [parser.parseElement("expression", tokens)];
2551 while (tokens.matchOpToken(",")) {
2552 exprs.push(parser.requireElement("expression", tokens));
2553 }
2554 if (tokens.matchToken("with")) {
2555 var withExpr = parser.requireElement("expression", tokens);
2556 }
2557 var logCmd = {
2558 exprs: exprs,
2559 withExpr: withExpr,
2560 args: [withExpr, exprs],
2561 op: function (ctx, withExpr, values) {
2562 if (withExpr) {
2563 withExpr.apply(null, values);
2564 } else {
2565 console.log.apply(null, values);
2566 }
2567 return runtime.findNext(this);
2568 }
2569 };
2570 return logCmd;
2571 }
2572 })
2573
2574 _parser.addCommand("throw", function(parser, runtime, tokens) {
2575 if (tokens.matchToken('throw')) {
2576 var expr = parser.requireElement("expression", tokens);
2577 var throwCmd = {
2578 expr: expr,
2579 args: [expr],
2580 op: function (ctx, expr) {
2581 var reject = ctx.meta && ctx.meta.reject;
2582 if (reject) {
2583 reject(expr);
2584 return runtime.HALT;
2585 } else {
2586 throw expr;
2587 }
2588 }
2589 };
2590 return throwCmd;
2591 }
2592 })
2593
2594 var parseCallOrGet = function(parser, runtime, tokens) {
2595 var expr = parser.requireElement("expression", tokens);
2596 var callCmd = {
2597 expr: expr,
2598 args: [expr],
2599 op: function (context, it) {
2600 context.it = it;
2601 return runtime.findNext(callCmd);
2602 }
2603 };
2604 return callCmd
2605 }
2606 _parser.addCommand("call", function(parser, runtime, tokens) {
2607 if (tokens.matchToken('call')) {
2608 return parseCallOrGet(parser, runtime, tokens);
2609 }
2610 })
2611 _parser.addCommand("get", function(parser, runtime, tokens) {
2612 if (tokens.matchToken('get')) {
2613 return parseCallOrGet(parser, runtime, tokens);
2614 }
2615 })
2616
2617 _parser.addCommand("put", function(parser, runtime, tokens) {
2618 if (tokens.matchToken('put')) {
2619 var value = parser.requireElement("expression", tokens);
2620
2621 var operationToken = tokens.matchToken("into") ||
2622 tokens.matchToken("before") ||
2623 tokens.matchToken("after");
2624
2625 if (operationToken == null && tokens.matchToken("at")) {
2626 operationToken = tokens.matchToken("start") ||
2627 tokens.matchToken("end");
2628 tokens.requireToken("of");
2629 }
2630
2631 if (operationToken == null) {
2632 parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'");
2633 }
2634 var target = parser.requireElement("target", tokens);
2635
2636 var operation = operationToken.value;
2637 var symbolWrite = target.type === "symbol" && operation === "into";
2638 if (target.type !== "symbol" && operation === "into" && target.root == null) {
2639 parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
2640 }
2641
2642 var root = null;
2643 var prop = null;
2644 if (symbolWrite) {
2645 // root is null
2646 } else if (operation === "into") {
2647 prop = target.prop.value;
2648 root = target.root;
2649 } else {
2650 root = target;
2651 }
2652
2653 var putCmd = {
2654 target: target,
2655 operation: operation,
2656 symbolWrite: symbolWrite,
2657 value: value,
2658 args: [root, value],
2659 op: function (context, root, valueToPut) {
2660 if (symbolWrite) {
2661 context[target.name] = valueToPut;
2662 } else {
2663 if (operation === "into") {
2664 runtime.forEach(root, function (elt) {
2665 elt[prop] = valueToPut;
2666 })
2667 } else if (operation === "before") {
2668 runtime.forEach(root, function (elt) {
2669 elt.insertAdjacentHTML('beforebegin', valueToPut);
2670 })
2671 } else if (operation === "start") {
2672 runtime.forEach(root, function (elt) {
2673 elt.insertAdjacentHTML('afterbegin', valueToPut);
2674 })
2675 } else if (operation === "end") {
2676 runtime.forEach(root, function (elt) {
2677 elt.insertAdjacentHTML('beforeend', valueToPut);
2678 })
2679 } else if (operation === "after") {
2680 runtime.forEach(root, function (elt) {
2681 elt.insertAdjacentHTML('afterend', valueToPut);
2682 })
2683 }
2684 }
2685 return runtime.findNext(this);
2686 }
2687 };
2688 return putCmd
2689 }
2690 })
2691
2692 _parser.addCommand("set", function(parser, runtime, tokens) {
2693 if (tokens.matchToken('set')) {
2694 var target = parser.requireElement("target", tokens);
2695
2696 tokens.requireToken("to");
2697
2698 var value = parser.requireElement("expression", tokens);
2699
2700 var symbolWrite = target.type === "symbol";
2701 if (target.type !== "symbol" && target.root == null) {
2702 parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
2703 }
2704
2705 var root = null;
2706 var prop = null;
2707 if (symbolWrite) {
2708 // root is null
2709 } else {
2710 prop = target.prop.value;
2711 root = target.root;
2712 }
2713
2714 var setCmd = {
2715 target: target,
2716 symbolWrite: symbolWrite,
2717 value: value,
2718 args: [root, value],
2719 op: function (context, root, valueToSet) {
2720 if (symbolWrite) {
2721 context[target.name] = valueToSet;
2722 } else {
2723 runtime.forEach(root, function (elt) {
2724 elt[prop] = valueToSet;
2725 })
2726 }
2727 return runtime.findNext(this);
2728 }
2729 };
2730 return setCmd
2731 }
2732 })
2733
2734 _parser.addCommand("if", function(parser, runtime, tokens) {
2735 if (tokens.matchToken('if')) {
2736 var expr = parser.requireElement("expression", tokens);
2737 tokens.matchToken("then"); // optional 'then'
2738 var trueBranch = parser.parseElement("commandList", tokens);
2739 if (tokens.matchToken("else")) {
2740 var falseBranch = parser.parseElement("commandList", tokens);
2741 }
2742 if (tokens.hasMore()) {
2743 tokens.requireToken("end");
2744 }
2745 var ifCmd = {
2746 expr: expr,
2747 trueBranch: trueBranch,
2748 falseBranch: falseBranch,
2749 args: [expr],
2750 op: function (context, expr) {
2751 if (expr) {
2752 return trueBranch;
2753 } else if (falseBranch) {
2754 return falseBranch;
2755 } else {
2756 return runtime.findNext(this);
2757 }
2758 }
2759 };
2760 parser.setParent(trueBranch, ifCmd);
2761 parser.setParent(falseBranch, ifCmd);
2762 return ifCmd
2763 }
2764 })
2765
2766 var parseRepeatExpression = function(parser, tokens, runtime, startedWithForToken) {
2767 var innerStartToken = tokens.currentToken();
2768 if (tokens.matchToken("for") || startedWithForToken) {
2769 var identifierToken = tokens.requireTokenType('IDENTIFIER');
2770 var identifier = identifierToken.value;
2771 tokens.requireToken("in");
2772 var expression = parser.requireElement("expression", tokens);
2773 } else if (tokens.matchToken("in")) {
2774 var identifier = "it";
2775 var expression = parser.requireElement("expression", tokens);
2776 } else if (tokens.matchToken("while")) {
2777 var whileExpr = parser.requireElement("expression", tokens);
2778 } else if (tokens.matchToken("until")) {
2779 var isUntil = true;
2780 if (tokens.matchToken("event")) {
2781 var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
2782 if (tokens.matchToken("from")) {
2783 var on = parser.requireElement("expression", tokens);
2784 }
2785 } else {
2786 var whileExpr = parser.requireElement("expression", tokens);
2787 }
2788 } else if (tokens.matchTokenType('NUMBER')) {
2789 var times = parseFloat(innerStartToken.value);
2790 tokens.requireToken('times');
2791 } else {
2792 tokens.matchToken("forever"); // consume optional forever
2793 var forever = true;
2794 }
2795
2796 if (tokens.matchToken("index")) {
2797 var identifierToken = tokens.requireTokenType('IDENTIFIER');
2798 var indexIdentifier = identifierToken.value
2799 }
2800
2801 var loop = parser.parseElement("commandList", tokens);
2802 if (tokens.hasMore()) {
2803 tokens.requireToken("end");
2804 }
2805
2806 if (identifier == null) {
2807 identifier = "_implicit_repeat_" + innerStartToken.start;
2808 var slot = identifier;
2809 } else {
2810 var slot = identifier + "_" + innerStartToken.start;
2811 }
2812
2813 var repeatCmd = {
2814 identifier: identifier,
2815 indexIdentifier: indexIdentifier,
2816 slot: slot,
2817 expression: expression,
2818 forever: forever,
2819 times: times,
2820 until: isUntil,
2821 event: evt,
2822 on: on,
2823 whileExpr: whileExpr,
2824 resolveNext: function () {
2825 return this;
2826 },
2827 loop: loop,
2828 args: [whileExpr],
2829 op: function (context, whileValue) {
2830 var iterator = context.meta.iterators[slot];
2831 var keepLooping = false;
2832 if (this.forever) {
2833 keepLooping = true;
2834 } else if (this.until) {
2835 if (evt) {
2836 keepLooping = context.meta.iterators[slot].eventFired == false;
2837 } else {
2838 keepLooping = whileValue != true;
2839 }
2840 } else if (whileValue) {
2841 keepLooping = true;
2842 } else if (times) {
2843 keepLooping = iterator.index < this.times;
2844 } else {
2845 keepLooping = iterator.value !== null && iterator.index < iterator.value.length
2846 }
2847
2848 if (keepLooping) {
2849 if (iterator.value) {
2850 context[identifier] = iterator.value[iterator.index];
2851 context.it = iterator.value[iterator.index];
2852 } else {
2853 context.it = iterator.index;
2854 }
2855 if (indexIdentifier) {
2856 context[indexIdentifier] = iterator.index;
2857 }
2858 iterator.index++;
2859 return loop;
2860 } else {
2861 context.meta.iterators[slot] = null;
2862 return runtime.findNext(this.parent);
2863 }
2864 }
2865 };
2866 parser.setParent(loop, repeatCmd);
2867 var repeatInit = {
2868 name: "repeatInit",
2869 args: [expression, evt, on],
2870 op: function (context, value, event, on) {
2871 context.meta.iterators[slot] = {
2872 index: 0,
2873 value: value,
2874 eventFired: false
2875 };
2876 if (evt) {
2877 var target = on || context.me;
2878 target.addEventListener(event, function (e) {
2879 context.meta.iterators[slot].eventFired = true;
2880 }, {once: true});
2881 }
2882 return repeatCmd; // continue to loop
2883 },
2884 execute: function (context) {
2885 return runtime.unifiedExec(this, context);
2886 }
2887 }
2888 parser.setParent(repeatCmd, repeatInit);
2889 return repeatInit
2890 }
2891
2892 _parser.addCommand("repeat", function(parser, runtime, tokens) {
2893 if (tokens.matchToken('repeat')) {
2894 return parseRepeatExpression(parser, tokens, runtime,false);
2895 }
2896 })
2897
2898 _parser.addCommand("for", function(parser, runtime, tokens) {
2899 if (tokens.matchToken('for')) {
2900 return parseRepeatExpression(parser, tokens, runtime, true);
2901 }
2902 })
2903
2904 _parser.addCommand("fetch", function(parser, runtime, tokens) {
2905 if (tokens.matchToken('fetch')) {
2906
2907 var url = parser.parseElement("string", tokens);
2908 if (url == null) {
2909 var url = parser.parseElement("nakedString", tokens);
2910 }
2911 if (url == null) {
2912 parser.raiseParseError(tokens, "Expected a URL");
2913 }
2914
2915 var args = parser.parseElement("objectLiteral", tokens);
2916
2917 var type = "text";
2918 if (tokens.matchToken("as")) {
2919 if (tokens.matchToken("json")) {
2920 type = "json";
2921 } else if (tokens.matchToken("response")) {
2922 type = "response";
2923 } else if (tokens.matchToken("text")) {
2924 } else {
2925 parser.raiseParseError(tokens, "Unknown response type: " + tokens.currentToken());
2926 }
2927 }
2928
2929 var fetchCmd = {
2930 url:url,
2931 argExrepssions:args,
2932 args: [url, args],
2933 op: function (context, url, args) {
2934 return new Promise(function (resolve, reject) {
2935 fetch(url, args)
2936 .then(function (value) {
2937 if (type === "response") {
2938 context.it = value;
2939 resolve(runtime.findNext(fetchCmd));
2940 } else if (type === "json") {
2941 value.json().then(function (result) {
2942 context.it = result;
2943 resolve(runtime.findNext(fetchCmd));
2944 })
2945 } else {
2946 value.text().then(function (result) {
2947 context.it = result;
2948 resolve(runtime.findNext(fetchCmd));
2949 })
2950 }
2951 })
2952 .catch(function (reason) {
2953 runtime.triggerEvent(context.me, "fetch:error", {
2954 reason: reason
2955 })
2956 reject(reason);
2957 })
2958 })
2959 }
2960 };
2961 return fetchCmd;
2962 }
2963 })
2964 }
2965
2966 //====================================================================
2967 // API
2968 //====================================================================
2969
2970 //====================================================================
2971 // Initialization
2972 //====================================================================
2973 function ready(fn) {
2974 if (document.readyState !== 'loading') {
2975 fn();
2976 } else {
2977 document.addEventListener('DOMContentLoaded', fn);
2978 }
2979 }
2980
2981 function getMetaConfig() {
2982 var element = document.querySelector('meta[name="htmx-config"]');
2983 if (element) {
2984 return parseJSON(element.content);
2985 } else {
2986 return null;
2987 }
2988 }
2989
2990 function mergeMetaConfig() {
2991 var metaConfig = getMetaConfig();
2992 if (metaConfig) {
2993 _hyperscript.config = mergeObjects(_hyperscript.config , metaConfig)
2994 }
2995 }
2996
2997 if ('document' in globalScope) {
2998 ready(function () {
2999 mergeMetaConfig();
3000 _runtime.processNode(document.body);
3001 document.addEventListener("htmx:load", function(evt){
3002 _runtime.processNode(evt.detail.elt);
3003 })
3004 })
3005 }
3006
3007 /* Public API */
3008 return {
3009 internals:{
3010 lexer: _lexer,
3011 parser: _parser,
3012 runtime: _runtime,
3013 },
3014 addFeature: function(keyword, definition) {
3015 _parser.addFeature(keyword, definition)
3016 },
3017 addCommand: function(keyword, definition) {
3018 _parser.addCommand(keyword, definition)
3019 },
3020 addLeafExpression: function(keyword, definition) {
3021 _parser.addLeafExpression(definition)
3022 },
3023 addIndirectExpression: function(keyword, definition) {
3024 _parser.addIndirectExpression(definition)
3025 },
3026 evaluate: function (str) { //OK
3027 return _runtime.evaluate(str); //OK
3028 },
3029 processNode: function (elt) {
3030 _runtime.processNode(elt);
3031 },
3032 config: {
3033 attributes : "_, script, data-script"
3034 },
3035 }
3036 }
3037 )()
3038}));
\No newline at end of file