bd85b79199f656334776d83719bc365a67d3aaa3
[Packages/TYPO3.CMS.git] / typo3 / sysext / t3editor / res / jslib / parse_typoscript / parsetyposcript.js
1 /* TypoScript parser
2 *
3 * based on parsejavascript.js by Marijn Haverbeke
4 *
5 * A parser that can be plugged into the CodeMirror system has to
6 * implement the following interface: It is a function that, when
7 * called with a string stream (stringstream.js) as an argument,
8 * returns a MochiKit-style iterator (object with a 'next' method).
9 * This iterator, when called, consumes some input from the string
10 * stream, and returns a token object. Token objects must have a
11 * 'value' property (the text they represent), a 'style' property (the
12 * CSS style that should be used to colour them). Tokens for newline
13 * characters must also have a 'lexicalContext' property, which has an
14 * 'indentation' method that can be used to determine the proper
15 * indentation level for the next line. This method optionally takes
16 * the first character of the next line as an argument, which it can
17 * use to adjust the indentation level.
18 *
19 * So far this should be easy. The hard part is that the iterator
20 * produced by the parse function must also have a 'copy' method. This
21 * method, called without arguments, returns a function representing
22 * the current state of the parser. When this function is later called
23 * with a string stream as its argument, it returns a parser iterator
24 * object that resumes parsing using the old state and the new input
25 * stream. It may assume that only one parser is active at a time, and
26 * clobber the state of the old parser (the implementation below
27 * certianly does).
28 */
29
30 // Parse function for TypoScript. Makes use of the tokenizer from
31 // tokenizetyposcript.js. Note that your parsers do not have to be
32 // this complicated -- if you don't want to recognize local variables,
33 // in many languages it is enough to just look for braces, semicolons,
34 // parentheses, etc, and know when you are inside a string or comment.
35 Editor.Parser = (function() {
36 // Token types that can be considered to be atoms.
37 var atomicTypes = {
38 "atom": true,
39 "number": true,
40 "variable": true,
41 "string": true,
42 "regexp": true
43 };
44
45 // Constructor for the lexical context objects.
46 function TSLexical(indented, column, type, align, prev) {
47 // indentation at start of this line
48 this.indented = indented;
49 // column at which this scope was opened
50 this.column = column;
51 // type of scope ('vardef', 'stat' (statement), '[', '{', or '(')
52 this.type = type;
53 // '[', '{', or '(' blocks that have any text after their opening
54 // character are said to be 'aligned' -- any lines below are
55 // indented all the way to the opening character.
56 if (align != null) {
57 this.align = align;
58 }
59 // Parent scope, if any.
60 this.prev = prev;
61
62 }
63
64
65 // My favourite TypoScript indentation rules.
66 function indentTS(lexical) {
67 return function(firstChars) {
68 var firstChar = firstChars && firstChars.charAt(0);
69 var closing = firstChar == lexical.type;
70
71 if (lexical.type == "{" && firstChar != "}") {
72 return lexical.indented + 2;
73 }
74
75 if (firstChar == "}" && lexical.prev) {
76 lexical = lexical.prev;
77 }
78
79 if (lexical.align) {
80 return lexical.column - (closing ? 1: 0);
81 } else {
82 return lexical.indented + (closing ? 0: 2);
83 }
84
85 };
86 }
87
88 // The parser-iterator-producing function itself.
89 function parseTS(input) {
90 // Wrap the input in a token stream
91 var tokens = tokenizeTypoScript(input);
92 // The parser state. cc is a stack of actions that have to be
93 // performed to finish the current statement. For example we might
94 // know that we still need to find a closing parenthesis and a
95 // semicolon. Actions at the end of the stack go first. It is
96 // initialized with an infinitely looping action that consumes
97 // whole statements.
98 var cc = [statements];
99 // Context contains information about the current local scope, the
100 // variables defined in that, and the scopes above it.
101 var context = null;
102 // The lexical scope, used mostly for indentation.
103 var lexical = new TSLexical( -2, 0, "block", false);
104 // Current column, and the indentation at the start of the current
105 // line. Used to create lexical scope objects.
106 var column = 0;
107 var indented = 0;
108 // Variables which are used by the mark, cont, and pass functions
109 // below to communicate with the driver loop in the 'next'
110 // function.
111 var consume,
112 marked;
113
114 // The iterator object.
115 var parser = {
116 next: next,
117 copy: copy
118 };
119
120 function next() {
121 // Start by performing any 'lexical' actions (adjusting the
122 // lexical variable), or the operations below will be working
123 // with the wrong lexical state.
124 while (cc[cc.length - 1].lex) {
125 cc.pop()();
126 }
127
128 // Fetch a token.
129 var token = tokens.next();
130 // Adjust column and indented.
131 if (token.type == "whitespace" && column == 0) {
132 indented = token.value.length;
133 }
134 column += token.value.length;
135 if (token.type == "newline") {
136 indented = column = 0;
137 // If the lexical scope's align property is still undefined at
138 // the end of the line, it is an un-aligned scope.
139 if (! ("align" in lexical)) {
140 lexical.align = false;
141 }
142 // Newline tokens get a lexical context associated with them,
143 // which is used for indentation.
144 token.indentation = indentTS(lexical);
145 }
146 // No more processing for meaningless tokens.
147 if (token.type == "whitespace" || token.type == "newline" || token.type == "comment") {
148 return token;
149 }
150 // When a meaningful token is found and the lexical scope's
151 // align is undefined, it is an aligned scope.
152 if (! ("align" in lexical)) {
153 lexical.align = true;
154 }
155 // Execute actions until one 'consumes' the token and we can
156 // return it. Marked is used to
157 while (true) {
158 consume = marked = false;
159 // Take and execute the topmost action.
160 cc.pop()(token.type, token.name);
161 if (consume) {
162 // Marked is used to change the style of the current token.
163 if (marked) {
164 token.style = marked;
165 }
166 return token;
167 }
168 }
169 }
170
171 // This makes a copy of the parser state. It stores all the
172 // stateful variables in a closure, and returns a function that
173 // will restore them when called with a new input stream. Note
174 // that the cc array has to be copied, because it is contantly
175 // being modified. Lexical objects are not mutated, and context
176 // objects are not mutated in a harmful way, so they can be shared
177 // between runs of the parser.
178 function copy() {
179 var _context = context,
180 _lexical = lexical,
181 _cc = cc.concat([]),
182 _regexp = tokens.regexp,
183 _comment = tokens.inComment;
184
185 return function(input) {
186 context = _context;
187 lexical = _lexical;
188 cc = _cc.concat([]);
189 // copies the array
190 column = indented = 0;
191 tokens = tokenizeTypoScript(input);
192 tokens.regexp = _regexp;
193 tokens.inComment = _comment;
194 return parser;
195 };
196 }
197
198 // Helper function for pushing a number of actions onto the cc
199 // stack in reverse order.
200 function push(fs) {
201 for (var i = fs.length - 1; i >= 0; i--) {
202 cc.push(fs[i]);
203 }
204 }
205
206 // cont and pass are used by the action functions to add other
207 // actions to the stack. cont will cause the current token to be
208 // consumed, pass will leave it for the next action.
209 function cont() {
210 push(arguments);
211 consume = true;
212 }
213
214 function pass() {
215 push(arguments);
216 consume = false;
217 }
218
219 // Used to change the style of the current token.
220 function mark(style) {
221 marked = style;
222 }
223
224 // Push a new scope. Will automatically link the the current
225 // scope.
226 function pushcontext() {
227 context = {
228 prev: context,
229 vars: {
230 "this": true,
231 "arguments": true
232 }
233 };
234 }
235
236 // Pop off the current scope.
237 function popcontext() {
238 context = context.prev;
239 }
240
241 // Register a variable in the current scope.
242 function register(varname) {
243 if (context) {
244 mark("variabledef");
245 context.vars[varname] = true;
246 }
247 }
248
249 // Push a new lexical context of the given type.
250 function pushlex(type) {
251 var result = function() {
252 lexical = new TSLexical(indented, column, type, null, lexical)
253 };
254 result.lex = true;
255 return result;
256 }
257
258 // Pop off the current lexical context.
259 function poplex() {
260 lexical = lexical.prev;
261 }
262
263 poplex.lex = true;
264 // The 'lex' flag on these actions is used by the 'next' function
265 // to know they can (and have to) be ran before moving on to the
266 // next token.
267
268 // Creates an action that discards tokens until it finds one of
269 // the given type.
270 function expect(wanted) {
271 return function(type) {
272 if (type == wanted) {
273 cont();
274 } else {
275 cont(arguments.callee);
276 }
277 };
278 }
279
280 // Looks for a statement, and then calls itself.
281 function statements(type) {
282 return pass(statement, statements);
283 }
284 // Dispatches various types of statements based on the type of the
285 // current token.
286 function statement(type) {
287 if (type == "{") {
288 cont(pushlex("{"), block, poplex);
289 } else {
290 cont();
291 }
292 }
293
294 // Dispatch expression types.
295 function expression(type) {
296 if (atomicTypes.hasOwnProperty(type)) {
297 cont(maybeoperator);
298
299 } else if (type == "function") {
300 cont(functiondef);
301
302 } else if (type == "keyword c") {
303 cont(expression);
304
305 } else if (type == "(") {
306 cont(pushlex(")"), expression, expect(")"), poplex);
307
308 } else if (type == "operator") {
309 cont(expression);
310
311 } else if (type == "[") {
312 cont(pushlex("]"), commasep(expression), expect("]"), poplex);
313
314 } else if (type == "{") {
315 cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
316 }
317 }
318
319 // Called for places where operators, function calls, or
320 // subscripts are valid. Will skip on to the next action if none
321 // is found.
322 function maybeoperator(type) {
323 if (type == "operator") {
324 cont(expression);
325
326 } else if (type == "(") {
327 cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
328
329 } else if (type == ".") {
330 cont(property, maybeoperator);
331
332 } else if (type == "[") {
333 cont(pushlex("]"), expression, expect("]"), poplex);
334 }
335 }
336
337 // When a statement starts with a variable name, it might be a
338 // label. If no colon follows, it's a regular statement.
339 function maybelabel(type) {
340 if (type == ":") {
341 cont(poplex, statement);
342 } else {
343 pass(maybeoperator, expect(";"), poplex);
344 }
345 }
346
347 // Property names need to have their style adjusted -- the
348 // tokenizer think they are variables.
349 function property(type) {
350 if (type == "variable") {
351 mark("property");
352 cont();
353 }
354 }
355
356 // This parses a property and its value in an object literal.
357 function objprop(type) {
358 if (type == "variable") {
359 mark("property");
360 }
361 if (atomicTypes.hasOwnProperty(type)) {
362 cont(expect(":"), expression);
363 }
364 }
365
366 // Parses a comma-separated list of the things that are recognized
367 // by the 'what' argument.
368 function commasep(what) {
369 function proceed(type) {
370 if (type == ",") {
371 cont(what, proceed);
372 }
373 };
374 return function() {
375 pass(what, proceed);
376 };
377 }
378
379 // Look for statements until a closing brace is found.
380 function block(type) {
381 if (type == "}") {
382 cont();
383 } else {
384 pass(statement, block);
385 }
386 }
387
388 // Look for statements until a closing brace is found.
389 function condition(type) {
390 if (type == "]") {
391 cont();
392 } else {
393 pass(statement, block);
394 }
395 }
396
397 // Variable definitions are split into two actions -- 1 looks for
398 // a name or the end of the definition, 2 looks for an '=' sign or
399 // a comma.
400 function vardef1(type, value) {
401 if (type == "variable") {
402 register(value);
403 cont(vardef2);
404 } else {
405 cont();
406 }
407 }
408
409 function vardef2(type) {
410 if (type == "operator") {
411 cont(expression, vardef2);
412 } else if (type == ",") {
413 cont(vardef1);
414 }
415 }
416
417 // For loops.
418 function forspec1(type, value) {
419 if (type == "var") {
420 cont(vardef1, forspec2);
421 } else {
422 cont(expression, forspec2);
423 }
424 }
425
426 function forspec2(type) {
427 if (type == ",") {
428 cont(forspec1);
429 }
430 if (type == ";") {
431 cont(expression, expect(";"), expression);
432 }
433 }
434
435 // A function definition creates a new context, and the variables
436 // in its argument list have to be added to this context.
437 function functiondef(type, value) {
438 if (type == "variable") {
439 register(value);
440 cont(functiondef);
441 } else if (type == "(") {
442 cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
443 }
444 }
445
446 function funarg(type, value) {
447 if (type == "variable") {
448 register(value);
449 cont();
450 }
451 }
452
453 return parser;
454 }
455
456 return {make: parseTS, electricChars: "{}"};
457 })();
458 /* TypoScript parser
459 *
460 * based on parsejavascript.js by Marijn Haverbeke
461 *
462 * A parser that can be plugged into the CodeMirror system has to
463 * implement the following interface: It is a function that, when
464 * called with a string stream (stringstream.js) as an argument,
465 * returns a MochiKit-style iterator (object with a 'next' method).
466 * This iterator, when called, consumes some input from the string
467 * stream, and returns a token object. Token objects must have a
468 * 'value' property (the text they represent), a 'style' property (the
469 * CSS style that should be used to colour them). Tokens for newline
470 * characters must also have a 'lexicalContext' property, which has an
471 * 'indentation' method that can be used to determine the proper
472 * indentation level for the next line. This method optionally takes
473 * the first character of the next line as an argument, which it can
474 * use to adjust the indentation level.
475 *
476 * So far this should be easy. The hard part is that the iterator
477 * produced by the parse function must also have a 'copy' method. This
478 * method, called without arguments, returns a function representing
479 * the current state of the parser. When this function is later called
480 * with a string stream as its argument, it returns a parser iterator
481 * object that resumes parsing using the old state and the new input
482 * stream. It may assume that only one parser is active at a time, and
483 * clobber the state of the old parser (the implementation below
484 * certianly does).
485 */
486
487 // Parse function for TypoScript. Makes use of the tokenizer from
488 // tokenizetyposcript.js. Note that your parsers do not have to be
489 // this complicated -- if you don't want to recognize local variables,
490 // in many languages it is enough to just look for braces, semicolons,
491 // parentheses, etc, and know when you are inside a string or comment.
492 Editor.Parser = (function() {
493 // Token types that can be considered to be atoms.
494 var atomicTypes = {
495 "atom": true,
496 "number": true,
497 "variable": true,
498 "string": true,
499 "regexp": true
500 };
501
502 // Constructor for the lexical context objects.
503 function TSLexical(indented, column, type, align, prev) {
504 // indentation at start of this line
505 this.indented = indented;
506 // column at which this scope was opened
507 this.column = column;
508 // type of scope ('vardef', 'stat' (statement), '[', '{', or '(')
509 this.type = type;
510 // '[', '{', or '(' blocks that have any text after their opening
511 // character are said to be 'aligned' -- any lines below are
512 // indented all the way to the opening character.
513 if (align != null) {
514 this.align = align;
515 }
516 // Parent scope, if any.
517 this.prev = prev;
518
519 }
520
521
522 // My favourite TypoScript indentation rules.
523 function indentTS(lexical) {
524 return function(firstChars) {
525 var firstChar = firstChars && firstChars.charAt(0);
526 var closing = firstChar == lexical.type;
527
528 if (lexical.type == "{" && firstChar != "}") {
529 return lexical.indented + 2;
530 }
531
532 if (firstChar == "}" && lexical.prev) {
533 lexical = lexical.prev;
534 }
535
536 if (lexical.align) {
537 return lexical.column - (closing ? 1: 0);
538 } else {
539 return lexical.indented + (closing ? 0: 2);
540 }
541
542 };
543 }
544
545 // The parser-iterator-producing function itself.
546 function parseTS(input) {
547 // Wrap the input in a token stream
548 var tokens = tokenizeTypoScript(input);
549 // The parser state. cc is a stack of actions that have to be
550 // performed to finish the current statement. For example we might
551 // know that we still need to find a closing parenthesis and a
552 // semicolon. Actions at the end of the stack go first. It is
553 // initialized with an infinitely looping action that consumes
554 // whole statements.
555 var cc = [statements];
556 // Context contains information about the current local scope, the
557 // variables defined in that, and the scopes above it.
558 var context = null;
559 // The lexical scope, used mostly for indentation.
560 var lexical = new TSLexical( -2, 0, "block", false);
561 // Current column, and the indentation at the start of the current
562 // line. Used to create lexical scope objects.
563 var column = 0;
564 var indented = 0;
565 // Variables which are used by the mark, cont, and pass functions
566 // below to communicate with the driver loop in the 'next'
567 // function.
568 var consume,
569 marked;
570
571 // The iterator object.
572 var parser = {
573 next: next,
574 copy: copy
575 };
576
577 function next() {
578 // Start by performing any 'lexical' actions (adjusting the
579 // lexical variable), or the operations below will be working
580 // with the wrong lexical state.
581 while (cc[cc.length - 1].lex) {
582 cc.pop()();
583 }
584
585 // Fetch a token.
586 var token = tokens.next();
587 // Adjust column and indented.
588 if (token.type == "whitespace" && column == 0) {
589 indented = token.value.length;
590 }
591 column += token.value.length;
592 if (token.type == "newline") {
593 indented = column = 0;
594 // If the lexical scope's align property is still undefined at
595 // the end of the line, it is an un-aligned scope.
596 if (! ("align" in lexical)) {
597 lexical.align = false;
598 }
599 // Newline tokens get a lexical context associated with them,
600 // which is used for indentation.
601 token.indentation = indentTS(lexical);
602 }
603 // No more processing for meaningless tokens.
604 if (token.type == "whitespace" || token.type == "newline" || token.type == "comment") {
605 return token;
606 }
607 // When a meaningful token is found and the lexical scope's
608 // align is undefined, it is an aligned scope.
609 if (! ("align" in lexical)) {
610 lexical.align = true;
611 }
612 // Execute actions until one 'consumes' the token and we can
613 // return it. Marked is used to
614 while (true) {
615 consume = marked = false;
616 // Take and execute the topmost action.
617 cc.pop()(token.type, token.name);
618 if (consume) {
619 // Marked is used to change the style of the current token.
620 if (marked) {
621 token.style = marked;
622 }
623 return token;
624 }
625 }
626 }
627
628 // This makes a copy of the parser state. It stores all the
629 // stateful variables in a closure, and returns a function that
630 // will restore them when called with a new input stream. Note
631 // that the cc array has to be copied, because it is contantly
632 // being modified. Lexical objects are not mutated, and context
633 // objects are not mutated in a harmful way, so they can be shared
634 // between runs of the parser.
635 function copy() {
636 var _context = context,
637 _lexical = lexical,
638 _cc = cc.concat([]),
639 _regexp = tokens.regexp,
640 _comment = tokens.inComment;
641
642 return function(input) {
643 context = _context;
644 lexical = _lexical;
645 cc = _cc.concat([]);
646 // copies the array
647 column = indented = 0;
648 tokens = tokenizeTypoScript(input);
649 tokens.regexp = _regexp;
650 tokens.inComment = _comment;
651 return parser;
652 };
653 }
654
655 // Helper function for pushing a number of actions onto the cc
656 // stack in reverse order.
657 function push(fs) {
658 for (var i = fs.length - 1; i >= 0; i--) {
659 cc.push(fs[i]);
660 }
661 }
662
663 // cont and pass are used by the action functions to add other
664 // actions to the stack. cont will cause the current token to be
665 // consumed, pass will leave it for the next action.
666 function cont() {
667 push(arguments);
668 consume = true;
669 }
670
671 function pass() {
672 push(arguments);
673 consume = false;
674 }
675
676 // Used to change the style of the current token.
677 function mark(style) {
678 marked = style;
679 }
680
681 // Push a new scope. Will automatically link the the current
682 // scope.
683 function pushcontext() {
684 context = {
685 prev: context,
686 vars: {
687 "this": true,
688 "arguments": true
689 }
690 };
691 }
692
693 // Pop off the current scope.
694 function popcontext() {
695 context = context.prev;
696 }
697
698 // Register a variable in the current scope.
699 function register(varname) {
700 if (context) {
701 mark("variabledef");
702 context.vars[varname] = true;
703 }
704 }
705
706 // Push a new lexical context of the given type.
707 function pushlex(type) {
708 var result = function() {
709 lexical = new TSLexical(indented, column, type, null, lexical)
710 };
711 result.lex = true;
712 return result;
713 }
714
715 // Pop off the current lexical context.
716 function poplex() {
717 lexical = lexical.prev;
718 }
719
720 poplex.lex = true;
721 // The 'lex' flag on these actions is used by the 'next' function
722 // to know they can (and have to) be ran before moving on to the
723 // next token.
724
725 // Creates an action that discards tokens until it finds one of
726 // the given type.
727 function expect(wanted) {
728 return function(type) {
729 if (type == wanted) {
730 cont();
731 } else {
732 cont(arguments.callee);
733 }
734 };
735 }
736
737 // Looks for a statement, and then calls itself.
738 function statements(type) {
739 return pass(statement, statements);
740 }
741 // Dispatches various types of statements based on the type of the
742 // current token.
743 function statement(type) {
744 if (type == "{") {
745 cont(pushlex("{"), block, poplex);
746 } else {
747 cont();
748 }
749 }
750
751 // Dispatch expression types.
752 function expression(type) {
753 if (atomicTypes.hasOwnProperty(type)) {
754 cont(maybeoperator);
755
756 } else if (type == "function") {
757 cont(functiondef);
758
759 } else if (type == "keyword c") {
760 cont(expression);
761
762 } else if (type == "(") {
763 cont(pushlex(")"), expression, expect(")"), poplex);
764
765 } else if (type == "operator") {
766 cont(expression);
767
768 } else if (type == "[") {
769 cont(pushlex("]"), commasep(expression), expect("]"), poplex);
770
771 } else if (type == "{") {
772 cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
773 }
774 }
775
776 // Called for places where operators, function calls, or
777 // subscripts are valid. Will skip on to the next action if none
778 // is found.
779 function maybeoperator(type) {
780 if (type == "operator") {
781 cont(expression);
782
783 } else if (type == "(") {
784 cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
785
786 } else if (type == ".") {
787 cont(property, maybeoperator);
788
789 } else if (type == "[") {
790 cont(pushlex("]"), expression, expect("]"), poplex);
791 }
792 }
793
794 // When a statement starts with a variable name, it might be a
795 // label. If no colon follows, it's a regular statement.
796 function maybelabel(type) {
797 if (type == ":") {
798 cont(poplex, statement);
799 } else {
800 pass(maybeoperator, expect(";"), poplex);
801 }
802 }
803
804 // Property names need to have their style adjusted -- the
805 // tokenizer think they are variables.
806 function property(type) {
807 if (type == "variable") {
808 mark("property");
809 cont();
810 }
811 }
812
813 // This parses a property and its value in an object literal.
814 function objprop(type) {
815 if (type == "variable") {
816 mark("property");
817 }
818 if (atomicTypes.hasOwnProperty(type)) {
819 cont(expect(":"), expression);
820 }
821 }
822
823 // Parses a comma-separated list of the things that are recognized
824 // by the 'what' argument.
825 function commasep(what) {
826 function proceed(type) {
827 if (type == ",") {
828 cont(what, proceed);
829 }
830 };
831 return function() {
832 pass(what, proceed);
833 };
834 }
835
836 // Look for statements until a closing brace is found.
837 function block(type) {
838 if (type == "}") {
839 cont();
840 } else {
841 pass(statement, block);
842 }
843 }
844
845 // Look for statements until a closing brace is found.
846 function condition(type) {
847 if (type == "]") {
848 cont();
849 } else {
850 pass(statement, block);
851 }
852 }
853
854 // Variable definitions are split into two actions -- 1 looks for
855 // a name or the end of the definition, 2 looks for an '=' sign or
856 // a comma.
857 function vardef1(type, value) {
858 if (type == "variable") {
859 register(value);
860 cont(vardef2);
861 } else {
862 cont();
863 }
864 }
865
866 function vardef2(type) {
867 if (type == "operator") {
868 cont(expression, vardef2);
869 } else if (type == ",") {
870 cont(vardef1);
871 }
872 }
873
874 // For loops.
875 function forspec1(type, value) {
876 if (type == "var") {
877 cont(vardef1, forspec2);
878 } else {
879 cont(expression, forspec2);
880 }
881 }
882
883 function forspec2(type) {
884 if (type == ",") {
885 cont(forspec1);
886 }
887 if (type == ";") {
888 cont(expression, expect(";"), expression);
889 }
890 }
891
892 // A function definition creates a new context, and the variables
893 // in its argument list have to be added to this context.
894 function functiondef(type, value) {
895 if (type == "variable") {
896 register(value);
897 cont(functiondef);
898 } else if (type == "(") {
899 cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
900 }
901 }
902
903 function funarg(type, value) {
904 if (type == "variable") {
905 register(value);
906 cont();
907 }
908 }
909
910 return parser;
911 }
912
913 return {make: parseTS, electricChars: "{}"};
914 })();