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