* Author: Helge Norberg, helge.norberg@svt.se
*/
-#include "../../stdafx.h"
+#include "../../StdAfx.h"
#include "expression_parser.h"
#include <functional>
#include <typeinfo>
#include <cstdint>
+#include <cmath>
#include <boost/any.hpp>
+#include <boost/locale.hpp>
#include <common/log.h>
#include <common/except.h>
#include <common/utf.h>
+#include <common/tweener.h>
namespace caspar { namespace core { namespace scene {
}
}
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Unexpected end of input (" + error_if_eof + L") in " + str));
}
+ L" in " + str;
}
+boost::any as_binding(const boost::any& value);
+
+template<typename T>
+binding<T> require(const boost::any& value)
+{
+ auto b = as_binding(value);
+
+ if (is<binding<T>>(b))
+ return as<binding<T>>(b);
+ else
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
+ L"Required binding of type " + u16(typeid(T).name())
+ + L" but got " + u16(value.type().name())));
+}
+
boost::any parse_expression(
std::wstring::const_iterator& cursor,
const std::wstring& str,
const variable_repository& var_repo)
{
if (*cursor++ != L'(')
- CASPAR_THROW_EXCEPTION(caspar_exception()
+ CASPAR_THROW_EXCEPTION(user_error()
<< msg_info(L"Expected (" + at_position(cursor, str)));
auto expr = parse_expression(cursor, str, var_repo);
- if (*cursor++ != L')')
- CASPAR_THROW_EXCEPTION(caspar_exception()
+ if (next_non_whitespace(cursor, str, L"Expected )") != L')')
+ CASPAR_THROW_EXCEPTION(user_error()
<< msg_info(L"Expected )" + at_position(cursor, str)));
+ ++cursor;
+
return expr;
}
+boost::any create_animate_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 3)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"animate() function requires three parameters: to_animate, duration, tweener"));
+
+ auto to_animate = require<double>(params.at(0));
+ auto frame_counter = var_repo(L"frame").as<double>();
+ auto duration = require<double>(params.at(1));
+ auto tw = require<std::wstring>(params.at(2)).transformed([](const std::wstring& s) { return tweener(s); });
+
+ return to_animate.animated(frame_counter, duration, tw);
+}
+
+boost::any create_sin_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"sin() function requires one parameters: angle"));
+
+ auto angle = require<double>(params.at(0));
+
+ return angle.transformed([](double a) { return std::sin(a); });
+}
+
+boost::any create_cos_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"cos() function requires one parameters: angle"));
+
+ auto angle = require<double>(params.at(0));
+
+ return angle.transformed([](double a) { return std::cos(a); });
+}
+
+boost::any create_abs_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"abs() function requires one parameters: value"));
+
+ auto val = require<double>(params.at(0));
+
+ return val.transformed([](double v) { return std::abs(v); });
+}
+
+boost::any create_floor_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"floor() function requires one parameters: value"));
+
+ auto val = require<double>(params.at(0));
+
+ return val.transformed([](double v) { return std::floor(v); });
+}
+
+std::locale create_utf_locale()
+{
+ boost::locale::generator gen;
+ gen.categories(boost::locale::codepage_facet);
+ gen.categories(boost::locale::convert_facet);
+
+ return gen("");
+}
+
+boost::any create_to_lower_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"to_lower() function requires one parameters: str"));
+
+ auto str = require<std::wstring>(params.at(0));
+ auto locale = create_utf_locale();
+
+ return str.transformed([=](std::wstring v) { return boost::locale::to_lower(v, locale); });
+}
+
+boost::any create_to_upper_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"to_upper() function requires one parameters: str"));
+
+ auto str = require<std::wstring>(params.at(0));
+ auto locale = create_utf_locale();
+
+ return str.transformed([=](std::wstring v) { return boost::locale::to_upper(v, locale); });
+}
+
+boost::any create_length_function(const std::vector<boost::any>& params, const variable_repository& var_repo)
+{
+ if (params.size() != 1)
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"length() function requires one parameters: str"));
+
+ auto str = require<std::wstring>(params.at(0));
+
+ return str.transformed([](std::wstring v) { return static_cast<double>(v.length()); });
+}
+
+boost::any parse_function(
+ const std::wstring& function_name,
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo)
+{
+ static std::map<std::wstring, std::function<boost::any (const std::vector<boost::any>& params, const variable_repository& var_repo)>> FUNCTIONS
+ {
+ { L"animate", create_animate_function },
+ { L"sin", create_sin_function },
+ { L"cos", create_cos_function },
+ { L"abs", create_abs_function },
+ { L"floor", create_floor_function },
+ { L"to_lower", create_to_lower_function },
+ { L"to_upper", create_to_upper_function },
+ { L"length", create_length_function }
+ };
+
+ auto function = FUNCTIONS.find(function_name);
+
+ if (function == FUNCTIONS.end())
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(function_name + L"() is an unknown function" + at_position(cursor, str)));
+
+ if (*cursor++ != L'(')
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"Expected (" + at_position(cursor, str)));
+
+ std::vector<boost::any> params;
+
+ while (cursor != str.end())
+ {
+ params.push_back(parse_expression(cursor, str, var_repo));
+
+ auto next = next_non_whitespace(cursor, str, L"Expected , or )");
+
+ if (next == L')')
+ break;
+ else if (next != L',')
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"Expected ) or ," + at_position(cursor, str)));
+
+ ++cursor;
+ }
+
+ if (next_non_whitespace(cursor, str, L"Expected , or )") != L')')
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"Expected ) " + at_position(cursor, str)));
+
+ ++cursor;
+
+ return function->second(params, var_repo);
+}
+
double parse_constant(
std::wstring::const_iterator& cursor, const std::wstring& str)
{
std::wstring literal;
if (*cursor++ != L'"')
- CASPAR_THROW_EXCEPTION(caspar_exception()
+ CASPAR_THROW_EXCEPTION(user_error()
<< msg_info(L"Expected (" + at_position(cursor, str)));
bool escaping = false;
++cursor;
}
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Unexpected end of input (Expected closing \") in " + str));
}
if (ch == L'.'
|| ch == L'_'
|| (ch >= L'a' && ch <= L'z')
- || (ch >= L'A' && ch <= L'Z'))
+ || (ch >= L'A' && ch <= L'Z')
+ || (variable_name.length() > 0 && ch >= L'0' && ch <= L'9'))
variable_name += ch;
else
break;
++cursor;
}
+ if (cursor != str.end() && *cursor == L'(')
+ return variable_name;
+
if (variable_name == L"true")
return true;
else if (variable_name == L"false")
else if (var.is<bool>())
return var.as<bool>();
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Unhandled variable type of " + variable_name
+ at_position(cursor, str)));
}
struct op
{
- enum op_type
+ enum class op_type
{
UNARY,
BINARY,
{
case L'+':
++cursor;
- return op(ch, 6, op::BINARY);
+ return op(ch, 6, op::op_type::BINARY);
case L'*':
case L'/':
case L'%':
++cursor;
- return op(ch, 5, op::BINARY);
+ return op(ch, 5, op::op_type::BINARY);
case L'?':
case L':':
++cursor;
- return op(ch, 15, op::TERNARY);
+ return op(ch, 15, op::op_type::TERNARY);
case L'-':
if (first == L'-')
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect -" + at_position(cursor, str)));
else
first = ch;
break;
case L'!':
if (first == L'!')
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect !" + at_position(cursor, str)));
else
first = ch;
break;
case L'<':
if (first == L'<')
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect <" + at_position(cursor, str)));
else
first = ch;
break;
case L'>':
if (first == L'>')
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect >" + at_position(cursor, str)));
else
first = ch;
if (first == L'=')
{
++cursor;
- return op(L"==", 9, op::BINARY);
+ return op(L"==", 9, op::op_type::BINARY);
}
else if (first == L'!')
{
++cursor;
- return op(L"!=", 9, op::BINARY);
+ return op(L"!=", 9, op::op_type::BINARY);
}
else if (first == L'>')
{
++cursor;
- return op(L">=", 8, op::BINARY);
+ return op(L">=", 8, op::op_type::BINARY);
}
else if (first == L'<')
{
++cursor;
- return op(L"<=", 8, op::BINARY);
+ return op(L"<=", 8, op::op_type::BINARY);
}
else if (first == NONE)
{
first = L'=';
}
else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect =" + at_position(cursor, str)));
break;
if (first == L'|')
{
++cursor;
- return op(L"||", 14, op::BINARY);
+ return op(L"||", 14, op::op_type::BINARY);
}
else if (first == NONE)
{
first = L'|';
}
else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect =" + at_position(cursor, str)));
break;
if (first == L'&')
{
++cursor;
- return op(L"&&", 13, op::BINARY);
+ return op(L"&&", 13, op::op_type::BINARY);
}
else if (first == NONE)
{
first = L'&';
}
else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Did not expect =" + at_position(cursor, str)));
break;
case L' ':
case L'\t':
if (first == L'-')
- return op(L'-', 6, op::BINARY);
+ return op(L'-', 6, op::op_type::BINARY);
else if (first == L'!')
- return op(L'!', 3, op::UNARY);
+ return op(L'!', 3, op::op_type::UNARY);
default:
if (first == L'<')
- return op(L'<', 8, op::BINARY);
+ return op(L'<', 8, op::op_type::BINARY);
else if (first == L'>')
- return op(L'>', 8, op::BINARY);
+ return op(L'>', 8, op::op_type::BINARY);
else if (first == L'-')
- return op(L"unary-", 3, op::UNARY);
+ return op(L"unary-", 3, op::op_type::UNARY);
else if (first == L'!')
- return op(L'!', 3, op::UNARY);
+ return op(L'!', 3, op::op_type::UNARY);
else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Expected second character of operator"
+ at_position(cursor, str)));
}
}
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Unexpected end of input (Expected operator) in " + str));
}
-template<typename T>
-T as(const boost::any& value)
-{
- return boost::any_cast<T>(value);
-}
-
boost::any as_binding(const boost::any& value)
{
// Wrap supported constants as bindings
else if (is<binding<std::wstring>>(value))
return as<binding<std::wstring>>(value);
else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Couldn't detect type of " + u16(value.type().name())));
}
-template<typename T>
-binding<T> require(const boost::any& value)
-{
- auto b = as_binding(value);
-
- if (is<binding<T>>(b))
- return as<binding<T>>(b);
- else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
- L"Required binding of type " + u16(typeid(T).name())
- + L" but got " + u16(value.type().name())));
-}
-
boost::any negative(const boost::any& to_create_negative_of)
{
return -require<double>(to_create_negative_of);
}
-boost::any not(const boost::any& to_create_not_of)
+boost::any not_(const boost::any& to_create_not_of)
{
return !require<bool>(to_create_not_of);
}
else if (is<binding<bool>>(b))
return as<binding<bool>>(b).as<std::wstring>();
else
- CASPAR_THROW_EXCEPTION(caspar_exception()
+ CASPAR_THROW_EXCEPTION(user_error()
<< msg_info(L"Couldn't stringify " + u16(value.type().name())));
}
return require<bool>(l) == require<bool>(r);
}
-boost::any and(const boost::any& lhs, boost::any& rhs)
+boost::any and_(const boost::any& lhs, boost::any& rhs)
{
return require<bool>(lhs) && require<bool>(rhs);
}
-boost::any or(const boost::any& lhs, boost::any& rhs)
+boost::any or_(const boost::any& lhs, boost::any& rhs)
{
return require<bool>(lhs) || require<bool>(rhs);
}
auto cond = require<bool>(condition);
auto t = as_binding(true_value);
auto f = as_binding(false_value);
-
+
// double
if (is<binding<double>>(t) && is<binding<double>>(f))
return ternary(cond, as<binding<double>>(t), as<binding<double>>(f));
switch (op_token.type)
{
- case op::UNARY:
+ case op::op_type::UNARY:
if (op_token.characters == L"unary-")
{
tokens.at(i) = negative(token_after);
}
else if (op_token.characters == L"!")
{
- tokens.at(i) = not(token_after);
+ tokens.at(i) = not_(token_after);
}
tokens.erase(tokens.begin() + index_after);
break;
- case op::BINARY:
+ case op::op_type::BINARY:
{
auto& token_before = tokens.at(i - 1);
else if (op_token.characters == L"==")
token_before = equal(token_before, token_after);
else if (op_token.characters == L"!=")
- token_before = not(equal(token_before, token_after));
+ token_before = not_(equal(token_before, token_after));
else if (op_token.characters == L"&&")
- token_before = and(token_before, token_after);
+ token_before = and_(token_before, token_after);
else if (op_token.characters == L"||")
- token_before = or(token_before, token_after);
+ token_before = or_(token_before, token_after);
}
tokens.erase(tokens.begin() + i, tokens.begin() + i + 2);
--i;
break;
- case op::TERNARY:
+ case op::op_type::TERNARY:
if (op_token.characters == L"?")
{
auto& token_before = tokens.at(i - 1);
auto& token_colon_operator = tokens.at(i + 2);
if (as<op>(token_colon_operator).characters != L":")
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(
L"Expected : as part of ternary expression"));
auto& token_false_value = tokens.at(i + 3);
tokens.push_back(parse_string_literal(cursor, str));
break;
case L'(':
- tokens.push_back(parse_parenthesis(cursor, str, var_repo));
+ if (!tokens.empty() && is<std::wstring>(tokens.back()))
+ {
+ auto function_name = as<std::wstring>(tokens.back());
+ tokens.pop_back();
+ tokens.push_back(parse_function(function_name, cursor, str, var_repo));
+ }
+ else
+ tokens.push_back(parse_parenthesis(cursor, str, var_repo));
break;
case L')':
+ case L',':
stop = true;
break;
default:
}
if (tokens.empty())
- CASPAR_THROW_EXCEPTION(caspar_exception()
- << msg_info(L"Expected expression"));
+ CASPAR_THROW_EXCEPTION(user_error()
+ << msg_info(L"Expected expression" + at_position(cursor, str)));
int precedence = 1;
return as_binding(tokens.at(0));
}
-template<>
-binding<std::wstring> parse_expression(
- const std::wstring& str, const variable_repository& var_repo)
-{
- auto cursor = str.cbegin();
- auto expr = parse_expression(cursor, str, var_repo);
-
- if (is<binding<std::wstring>>(expr))
- return as<binding<std::wstring>>(expr);
- else if (is<binding<double>>(expr))
- return as<binding<double>>(expr).as<std::wstring>();
- else
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
- L"parse_expression() Unsupported type "
- + u16(expr.type().name())));
-}
-
}}}