]> git.sesse.net Git - casparcg/blob - core/producer/scene/expression_parser.cpp
ab40286c3fb62123d96a0f518764429e25df2000
[casparcg] / core / producer / scene / expression_parser.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Helge Norberg, helge.norberg@svt.se
20 */
21
22 #include "../../StdAfx.h"
23
24 #include "expression_parser.h"
25
26 #include <string>
27 #include <memory>
28 #include <vector>
29 #include <functional>
30 #include <typeinfo>
31 #include <cstdint>
32
33 #include <boost/any.hpp>
34
35 #include <common/log.h>
36 #include <common/except.h>
37 #include <common/utf.h>
38 #include <common/tweener.h>
39
40 namespace caspar { namespace core { namespace scene {
41
42 wchar_t next_non_whitespace(
43                 std::wstring::const_iterator& cursor,
44                 const std::wstring& str,
45                 const std::wstring& error_if_eof)
46 {
47         while (cursor != str.end())
48         {
49                 switch (*cursor)
50                 {
51                 case L' ':
52                 case L'\t':
53                         ++cursor;
54                         continue;
55                 default:
56                         return *cursor;
57                 }
58         }
59
60         CASPAR_THROW_EXCEPTION(user_error() << msg_info(
61                         L"Unexpected end of input (" + error_if_eof + L") in " + str));
62 }
63
64 std::wstring at_position(
65                 const std::wstring::const_iterator& cursor, const std::wstring& str)
66 {
67         int index = static_cast<int>(cursor - str.begin());
68
69         return L" at index " + boost::lexical_cast<std::wstring>(index)
70                         + L" in " + str;
71 }
72
73 template<typename T>
74 binding<T> require(const boost::any& value)
75 {
76         auto b = as_binding(value);
77
78         if (is<binding<T>>(b))
79                 return as<binding<T>>(b);
80         else
81                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
82                         L"Required binding of type " + u16(typeid(T).name())
83                         + L" but got " + u16(value.type().name())));
84 }
85
86 boost::any parse_expression(
87                 std::wstring::const_iterator& cursor,
88                 const std::wstring& str,
89                 const variable_repository& var_repo);
90
91 boost::any parse_parenthesis(
92                 std::wstring::const_iterator& cursor,
93                 const std::wstring& str,
94                 const variable_repository& var_repo)
95 {
96         if (*cursor++ != L'(')
97                 CASPAR_THROW_EXCEPTION(user_error()
98                                 << msg_info(L"Expected (" + at_position(cursor, str)));
99
100         auto expr = parse_expression(cursor, str, var_repo);
101
102         if (next_non_whitespace(cursor, str, L"Expected )") != L')')
103                 CASPAR_THROW_EXCEPTION(user_error()
104                                 << msg_info(L"Expected )" + at_position(cursor, str)));
105
106         ++cursor;
107
108         return expr;
109 }
110
111 boost::any create_animate_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
112 {
113         if (params.size() != 3)
114                 CASPAR_THROW_EXCEPTION(user_error()
115                         << msg_info(L"animate() function requires three parameters: to_animate, duration, tweener"));
116
117         auto to_animate         = require<double>(params.at(0));
118         auto frame_counter      = var_repo(L"frame").as<int64_t>().as<double>();
119         auto duration           = require<double>(params.at(1));
120         auto tw                         = require<std::wstring>(params.at(2)).transformed([](const std::wstring& s) { return tweener(s); });
121
122         return to_animate.animated(frame_counter, duration, tw);
123 }
124
125 boost::any parse_function(
126                 const std::wstring& function_name,
127                 std::wstring::const_iterator& cursor,
128                 const std::wstring& str,
129                 const variable_repository& var_repo)
130 {
131         static std::map<std::wstring, std::function<boost::any (const std::vector<boost::any>& params, const variable_repository& var_repo)>> FUNCTIONS
132         {
133                 {L"animate", create_animate_function }
134         };
135
136         auto function = FUNCTIONS.find(function_name);
137
138         if (function == FUNCTIONS.end())
139                 CASPAR_THROW_EXCEPTION(user_error()
140                                 << msg_info(function_name + L"() is an unknown function" + at_position(cursor, str)));
141
142         if (*cursor++ != L'(')
143                 CASPAR_THROW_EXCEPTION(user_error()
144                         << msg_info(L"Expected (" + at_position(cursor, str)));
145
146         std::vector<boost::any> params;
147
148         while (cursor != str.end())
149         {
150                 params.push_back(parse_expression(cursor, str, var_repo));
151
152                 auto next = next_non_whitespace(cursor, str, L"Expected , or )");
153
154                 if (next == L')')
155                         break;
156                 else if (next != L',')
157                         CASPAR_THROW_EXCEPTION(user_error()
158                                 << msg_info(L"Expected ) or ," + at_position(cursor, str)));
159
160                 ++cursor;
161         }
162
163         if (next_non_whitespace(cursor, str, L"Expected , or )") != L')')
164                 CASPAR_THROW_EXCEPTION(user_error()
165                         << msg_info(L"Expected ) " + at_position(cursor, str)));
166
167         ++cursor;
168
169         return function->second(params, var_repo);
170 }
171
172 double parse_constant(
173                 std::wstring::const_iterator& cursor, const std::wstring& str)
174 {
175         std::wstring constant;
176
177         while (cursor != str.end())
178         {
179                 wchar_t ch = *cursor;
180
181                 if ((ch >= L'0' && ch <= L'9') || ch == L'.')
182                         constant += ch;
183                 else
184                         break;
185
186                 ++cursor;
187         }
188
189         return boost::lexical_cast<double>(constant);
190 }
191
192 std::wstring parse_string_literal(
193                 std::wstring::const_iterator& cursor, const std::wstring& str)
194 {
195         std::wstring literal;
196
197         if (*cursor++ != L'"')
198                 CASPAR_THROW_EXCEPTION(user_error()
199                                 << msg_info(L"Expected (" + at_position(cursor, str)));
200
201         bool escaping = false;
202
203         while (cursor != str.end())
204         {
205                 wchar_t ch = *cursor;
206
207                 switch (ch)
208                 {
209                 case L'\\':
210                         if (escaping)
211                         {
212                                 literal += ch;
213                                 escaping = false;
214                         }
215                         else
216                                 escaping = true;
217                         break;
218                 case L'"':
219                         if (escaping)
220                         {
221                                 literal += ch;
222                                 escaping = false;
223                                 break;
224                         }
225                         else
226                         {
227                                 ++cursor;
228                                 return std::move(literal);
229                         }
230                 case L'n':
231                         if (escaping)
232                         {
233                                 literal += L'\n';
234                                 escaping = false;
235                         }
236                         else
237                                 literal += ch;
238                         break;
239                 default:
240                         literal += ch;
241                 }
242
243                 ++cursor;
244         }
245
246         CASPAR_THROW_EXCEPTION(user_error() << msg_info(
247                         L"Unexpected end of input (Expected closing \") in " + str));
248 }
249
250 boost::any parse_variable(
251                 std::wstring::const_iterator& cursor,
252                 const std::wstring& str,
253                 const variable_repository& var_repo)
254 {
255         std::wstring variable_name;
256
257         while (cursor != str.end())
258         {
259                 wchar_t ch = *cursor;
260
261                 if (ch == L'.'
262                                 || ch == L'_'
263                                 || (ch >= L'a' && ch <= L'z')
264                                 || (ch >= L'A' && ch <= L'Z')
265                                 || (variable_name.length() > 0 && ch >= L'0' && ch <= L'9'))
266                         variable_name += ch;
267                 else
268                         break;
269
270                 ++cursor;
271         }
272
273         if (cursor != str.end() && *cursor == L'(')
274                 return variable_name;
275
276         if (variable_name == L"true")
277                 return true;
278         else if (variable_name == L"false")
279                 return false;
280
281         variable& var = var_repo(variable_name);
282
283         if (var.is<double>())
284                 return var.as<double>();
285         else if (var.is<int64_t>())
286                 return var.as<int64_t>().as<double>();
287         else if (var.is<std::wstring>())
288                 return var.as<std::wstring>();
289         else if (var.is<bool>())
290                 return var.as<bool>();
291
292         CASPAR_THROW_EXCEPTION(user_error() << msg_info(
293                                 L"Unhandled variable type of " + variable_name
294                                 + at_position(cursor, str)));
295 }
296
297 struct op
298 {
299         enum class op_type
300         {
301                 UNARY,
302                 BINARY,
303                 TERNARY
304         };
305
306         std::wstring characters;
307         int precedence;
308         op_type type;
309
310         op(wchar_t ch, int precedence, op_type type)
311                 : characters(1, ch), precedence(precedence), type(type)
312         {
313         }
314
315         op(const std::wstring& chs, int precedence, op_type type)
316                 : characters(chs), precedence(precedence), type(type)
317         {
318         }
319 };
320
321 op parse_operator(std::wstring::const_iterator& cursor, const std::wstring& str)
322 {
323         static const wchar_t NONE = L' ';
324         wchar_t first = NONE;
325
326         while (cursor != str.end())
327         {
328                 wchar_t ch = *cursor;
329
330                 switch (ch)
331                 {
332                 case L'+':
333                         ++cursor;
334                         return op(ch, 6, op::op_type::BINARY);
335                 case L'*':
336                 case L'/':
337                 case L'%':
338                         ++cursor;
339                         return op(ch, 5, op::op_type::BINARY);
340                 case L'?':
341                 case L':':
342                         ++cursor;
343                         return op(ch, 15, op::op_type::TERNARY);
344                 case L'-':
345                         if (first == L'-')
346                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
347                                                 L"Did not expect -" + at_position(cursor, str)));
348                         else
349                                 first = ch;
350
351                         ++cursor;
352                         break;
353                 case L'!':
354                         if (first == L'!')
355                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
356                                                 L"Did not expect !" + at_position(cursor, str)));
357                         else
358                                 first = ch;
359
360                         ++cursor;
361                         break;
362                 case L'<':
363                         if (first == L'<')
364                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
365                                                 L"Did not expect <" + at_position(cursor, str)));
366                         else
367                                 first = ch;
368
369                         ++cursor;
370                         break;
371                 case L'>':
372                         if (first == L'>')
373                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
374                                                 L"Did not expect >" + at_position(cursor, str)));
375                         else
376                                 first = ch;
377
378                         ++cursor;
379                         break;
380                 case L'=':
381                         if (first == L'=')
382                         {
383                                 ++cursor;
384                                 return op(L"==", 9, op::op_type::BINARY);
385                         }
386                         else if (first == L'!')
387                         {
388                                 ++cursor;
389                                 return op(L"!=", 9, op::op_type::BINARY);
390                         }
391                         else if (first == L'>')
392                         {
393                                 ++cursor;
394                                 return op(L">=", 8, op::op_type::BINARY);
395                         }
396                         else if (first == L'<')
397                         {
398                                 ++cursor;
399                                 return op(L"<=", 8, op::op_type::BINARY);
400                         }
401                         else if (first == NONE)
402                         {
403                                 ++cursor;
404                                 first = L'=';
405                         }
406                         else
407                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
408                                                 L"Did not expect =" + at_position(cursor, str)));
409
410                         break;
411                 case L'|':
412                         if (first == L'|')
413                         {
414                                 ++cursor;
415                                 return op(L"||", 14, op::op_type::BINARY);
416                         }
417                         else if (first == NONE)
418                         {
419                                 ++cursor;
420                                 first = L'|';
421                         }
422                         else
423                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
424                                                 L"Did not expect =" + at_position(cursor, str)));
425
426                         break;
427                 case L'&':
428                         if (first == L'&')
429                         {
430                                 ++cursor;
431                                 return op(L"&&", 13, op::op_type::BINARY);
432                         }
433                         else if (first == NONE)
434                         {
435                                 ++cursor;
436                                 first = L'&';
437                         }
438                         else
439                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
440                                                 L"Did not expect =" + at_position(cursor, str)));
441
442                         break;
443                 case L' ':
444                 case L'\t':
445                         if (first == L'-')
446                                 return op(L'-', 6, op::op_type::BINARY);
447                         else if (first == L'!')
448                                 return op(L'!', 3, op::op_type::UNARY);
449                 default:
450                         if (first == L'<')
451                                 return op(L'<', 8, op::op_type::BINARY);
452                         else if (first == L'>')
453                                 return op(L'>', 8, op::op_type::BINARY);
454                         else if (first == L'-')
455                                 return op(L"unary-", 3, op::op_type::UNARY);
456                         else if (first == L'!')
457                                 return op(L'!', 3, op::op_type::UNARY);
458                         else
459                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
460                                                 L"Expected second character of operator"
461                                                 + at_position(cursor, str)));
462                 }
463         }
464
465         CASPAR_THROW_EXCEPTION(user_error() << msg_info(
466                         L"Unexpected end of input (Expected operator) in " + str));
467 }
468
469 boost::any as_binding(const boost::any& value)
470 {
471         // Wrap supported constants as bindings
472         if (is<double>(value))
473                 return binding<double>(as<double>(value));
474         else if (is<bool>(value))
475                 return binding<bool>(as<bool>(value));
476         else if (is<std::wstring>(value))
477                 return binding<std::wstring>(as<std::wstring>(value));
478         // Already one of the supported binding types
479         else if (is<binding<double>>(value))
480                 return as<binding<double>>(value);
481         else if (is<binding<bool>>(value))
482                 return as<binding<bool>>(value);
483         else if (is<binding<std::wstring>>(value))
484                 return as<binding<std::wstring>>(value);
485         else
486                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(
487                                 L"Couldn't detect type of " + u16(value.type().name())));
488 }
489
490 boost::any negative(const boost::any& to_create_negative_of)
491 {
492         return -require<double>(to_create_negative_of);
493 }
494
495 boost::any not_(const boost::any& to_create_not_of)
496 {
497         return !require<bool>(to_create_not_of);
498 }
499
500 boost::any multiply(const boost::any& lhs, boost::any& rhs)
501 {
502         return require<double>(lhs) * require<double>(rhs);
503 }
504
505 boost::any divide(const boost::any& lhs, boost::any& rhs)
506 {
507         return require<double>(lhs) / require<double>(rhs);
508 }
509
510 boost::any modulus(const boost::any& lhs, boost::any& rhs)
511 {
512         return
513                         (
514                                         require<double>(lhs).as<int64_t>()
515                                         % require<double>(rhs).as<int64_t>()
516                         ).as<double>();
517 }
518
519 binding<std::wstring> stringify(const boost::any& value)
520 {
521         auto b = as_binding(value);
522
523         if (is<binding<std::wstring>>(b))
524                 return as<binding<std::wstring>>(b);
525         else if (is<binding<double>>(b))
526                 return as<binding<double>>(b).as<std::wstring>();
527         else if (is<binding<bool>>(b))
528                 return as<binding<bool>>(b).as<std::wstring>();
529         else
530                 CASPAR_THROW_EXCEPTION(user_error()
531                                 << msg_info(L"Couldn't stringify " + u16(value.type().name())));
532 }
533
534 boost::any add(const boost::any& lhs, boost::any& rhs)
535 {
536         auto l = as_binding(lhs);
537         auto r = as_binding(rhs);
538
539         // number
540         if (is<binding<double>>(l) && is<binding<double>>(r))
541                 return as<binding<double>>(l) + as<binding<double>>(r);
542         // string
543         else if (is<binding<std::wstring>>(l) && is<binding<std::wstring>>(r))
544                 return as<binding<std::wstring>>(l) + as<binding<std::wstring>>(r);
545         // mixed types to string and concatenated
546         else
547                 return stringify(lhs) + stringify(rhs);
548 }
549
550 boost::any subtract(const boost::any& lhs, boost::any& rhs)
551 {
552         return require<double>(lhs) - require<double>(rhs);
553 }
554
555 boost::any less(const boost::any& lhs, boost::any& rhs)
556 {
557         return require<double>(lhs) < require<double>(rhs);
558 }
559
560 boost::any less_or_equal(const boost::any& lhs, boost::any& rhs)
561 {
562         return require<double>(lhs) <= require<double>(rhs);
563 }
564
565 boost::any greater(const boost::any& lhs, boost::any& rhs)
566 {
567         return require<double>(lhs) > require<double>(rhs);
568 }
569
570 boost::any greater_or_equal(const boost::any& lhs, boost::any& rhs)
571 {
572         return require<double>(lhs) >= require<double>(rhs);
573 }
574
575 boost::any equal(const boost::any& lhs, boost::any& rhs)
576 {
577         auto l = as_binding(lhs);
578         auto r = as_binding(rhs);
579
580         // number
581         if (is<binding<double>>(l) && is<binding<double>>(r))
582                 return as<binding<double>>(l) == as<binding<double>>(r);
583         // string
584         else if (is<binding<std::wstring>>(l) && is<binding<std::wstring>>(r))
585                 return as<binding<std::wstring>>(l) == as<binding<std::wstring>>(r);
586         // boolean
587         else
588                 return require<bool>(l) == require<bool>(r);
589 }
590
591 boost::any and_(const boost::any& lhs, boost::any& rhs)
592 {
593         return require<bool>(lhs) && require<bool>(rhs);
594 }
595
596 boost::any or_(const boost::any& lhs, boost::any& rhs)
597 {
598         return require<bool>(lhs) || require<bool>(rhs);
599 }
600
601 template<typename T>
602 binding<T> ternary(
603                 const binding<bool>& condition,
604                 const binding<T>& true_value,
605                 const binding<T>& false_value)
606 {
607         return when(condition).then(true_value).otherwise(false_value);
608 }
609
610 boost::any ternary(
611                 const boost::any& condition,
612                 const boost::any& true_value,
613                 const boost::any& false_value)
614 {
615         auto cond = require<bool>(condition);
616         auto t = as_binding(true_value);
617         auto f = as_binding(false_value);
618         
619         // double
620         if (is<binding<double>>(t) && is<binding<double>>(f))
621                 return ternary(cond, as<binding<double>>(t), as<binding<double>>(f));
622         // string
623         else if (is<binding<std::wstring>>(t) && is<binding<std::wstring>>(f))
624                 return ternary(
625                                 cond,
626                                 as<binding<std::wstring>>(t),
627                                 as<binding<std::wstring>>(f));
628         // bool
629         else
630                 return ternary(cond, require<bool>(t), require<bool>(f));
631 }
632
633 void resolve_operators(int precedence, std::vector<boost::any>& tokens)
634 {
635         for (int i = 0; i < tokens.size(); ++i)
636         {
637                 auto& token = tokens.at(i);
638
639                 if (!is<op>(token))
640                         continue;
641
642                 auto op_token = as<op>(token);
643
644                 if (op_token.precedence != precedence)
645                         continue;
646
647                 int index_after = i + 1;
648                 auto& token_after = tokens.at(index_after);
649
650                 switch (op_token.type)
651                 {
652                 case op::op_type::UNARY:
653                         if (op_token.characters == L"unary-")
654                         {
655                                 tokens.at(i) = negative(token_after);
656                         }
657                         else if (op_token.characters == L"!")
658                         {
659                                 tokens.at(i) = not_(token_after);
660                         }
661
662                         tokens.erase(tokens.begin() + index_after);
663
664                         break;
665                 case op::op_type::BINARY:
666                         {
667                                 auto& token_before = tokens.at(i - 1);
668
669                                 if (op_token.characters == L"*")
670                                         token_before = multiply(token_before, token_after);
671                                 else if (op_token.characters == L"/")
672                                         token_before = divide(token_before, token_after);
673                                 else if (op_token.characters == L"%")
674                                         token_before = modulus(token_before, token_after);
675                                 else if (op_token.characters == L"+")
676                                         token_before = add(token_before, token_after);
677                                 else if (op_token.characters == L"-")
678                                         token_before = subtract(token_before, token_after);
679                                 else if (op_token.characters == L"<")
680                                         token_before = less(token_before, token_after);
681                                 else if (op_token.characters == L"<=")
682                                         token_before = less_or_equal(token_before, token_after);
683                                 else if (op_token.characters == L">")
684                                         token_before = greater(token_before, token_after);
685                                 else if (op_token.characters == L">=")
686                                         token_before = greater_or_equal(token_before, token_after);
687                                 else if (op_token.characters == L"==")
688                                         token_before = equal(token_before, token_after);
689                                 else if (op_token.characters == L"!=")
690                                         token_before = not_(equal(token_before, token_after));
691                                 else if (op_token.characters == L"&&")
692                                         token_before = and_(token_before, token_after);
693                                 else if (op_token.characters == L"||")
694                                         token_before = or_(token_before, token_after);
695                         }
696
697                         tokens.erase(tokens.begin() + i, tokens.begin() + i + 2);
698                         --i;
699
700                         break;
701                 case op::op_type::TERNARY:
702                         if (op_token.characters == L"?")
703                         {
704                                 auto& token_before = tokens.at(i - 1);
705                                 auto& token_colon_operator = tokens.at(i + 2);
706
707                                 if (as<op>(token_colon_operator).characters != L":")
708                                         CASPAR_THROW_EXCEPTION(user_error() << msg_info(
709                                                         L"Expected : as part of ternary expression"));
710
711                                 auto& token_false_value = tokens.at(i + 3);
712                                 token_before = ternary(
713                                                 token_before, token_after, token_false_value);
714                                 tokens.erase(tokens.begin() + i, tokens.begin() + i + 4);
715                                 --i;
716                         }
717
718                         break;
719                 }
720         }
721 }
722
723 boost::any parse_expression(
724                 std::wstring::const_iterator& cursor,
725                 const std::wstring& str,
726                 const variable_repository& var_repo)
727 {
728         std::vector<boost::any> tokens;
729         bool stop = false;
730
731         while (cursor != str.end())
732         {
733                 wchar_t ch = next_non_whitespace(cursor, str, L"Expected expression");
734
735                 switch (ch)
736                 {
737                 case L'0':
738                 case L'1':
739                 case L'2':
740                 case L'3':
741                 case L'4':
742                 case L'5':
743                 case L'6':
744                 case L'7':
745                 case L'8':
746                 case L'9':
747                         tokens.push_back(parse_constant(cursor, str));
748                         break;
749                 case L'+':
750                 case L'-':
751                 case L'*':
752                 case L'/':
753                 case L'%':
754                 case L'<':
755                 case L'>':
756                 case L'!':
757                 case L'=':
758                 case L'|':
759                 case L'&':
760                 case L'?':
761                 case L':':
762                         tokens.push_back(parse_operator(cursor, str));
763                         break;
764                 case L'"':
765                         tokens.push_back(parse_string_literal(cursor, str));
766                         break;
767                 case L'(':
768                         if (!tokens.empty() && is<std::wstring>(tokens.back()))
769                         {
770                                 auto function_name = as<std::wstring>(tokens.back());
771                                 tokens.pop_back();
772                                 tokens.push_back(parse_function(function_name, cursor, str, var_repo));
773                         }
774                         else
775                                 tokens.push_back(parse_parenthesis(cursor, str, var_repo));
776                         break;
777                 case L')':
778                 case L',':
779                         stop = true;
780                         break;
781                 default:
782                         tokens.push_back(parse_variable(cursor, str, var_repo));
783                         break;
784                 }
785
786                 if (stop)
787                         break;
788         }
789
790         if (tokens.empty())
791                 CASPAR_THROW_EXCEPTION(user_error()
792                                 << msg_info(L"Expected expression" + at_position(cursor, str)));
793
794         int precedence = 1;
795
796         while (tokens.size() > 1)
797         {
798                 resolve_operators(precedence++, tokens);
799         }
800
801         return as_binding(tokens.at(0));
802 }
803
804 }}}