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