<ClInclude Include="producer\binding.h" />\r
<ClInclude Include="producer\draw\freehand_producer.h" />\r
<ClInclude Include="producer\scene\const_producer.h" />\r
+ <ClInclude Include="producer\scene\expression_parser.h" />\r
<ClInclude Include="producer\scene\hotswap_producer.h" />\r
<ClInclude Include="producer\scene\scene_producer.h" />\r
<ClInclude Include="producer\scene\xml_scene_producer.h" />\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
</ClCompile>\r
+ <ClCompile Include="producer\scene\expression_parser.cpp">\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ </ClCompile>\r
<ClCompile Include="producer\scene\hotswap_producer.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../StdAfx.h</PrecompiledHeaderFile>\r
<ClInclude Include="producer\variable.h">\r
<Filter>source\producer</Filter>\r
</ClInclude>\r
+ <ClInclude Include="producer\scene\expression_parser.h">\r
+ <Filter>source\producer\scene</Filter>\r
<ClInclude Include="producer\text\utils\text_info.h">\r
<Filter>source\producer\text\utils</Filter>\r
</ClInclude>\r
<ClCompile Include="producer\scene\xml_scene_producer.cpp">\r
<Filter>source\producer\scene</Filter>\r
</ClCompile>\r
+ <ClCompile Include="producer\scene\expression_parser.cpp">\r
+ <Filter>source\producer\scene</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
#include <functional>
#include <memory>
#include <vector>
+#include <string>
#include <map>
#include <algorithm>
+#include <type_traits>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
namespace caspar { namespace core {
void depend_on(const std::shared_ptr<impl_base>& dependency)
{
- dependency->on_change(shared_from_this(), [=] { evaluate(); });
+ auto self = shared_from_this();
+
+ if (dependency->depends_on(self))
+ throw std::exception("Can't have circular dependencies between bindings");
+
+ dependency->on_change(self, [=] { evaluate(); });
dependencies_.push_back(dependency);
}
+ bool depends_on(const std::shared_ptr<impl_base>& other) const
+ {
+ BOOST_FOREACH(auto& dependency, dependencies_)
+ {
+ if (dependency == other)
+ return true;
+
+ if (dependency->depends_on(other))
+ return true;
+ }
+
+ return false;
+ }
+
void on_change(
const std::weak_ptr<void>& dependant,
const std::function<void ()>& listener) const
}
template<typename T2>
- binding<T2> as() const
+ typename std::enable_if<
+ std::is_same<T, T2>::value,
+ binding<T2>
+ >::type as() const
+ {
+ return *this;
+ }
+
+ template<typename T2>
+ typename std::enable_if<
+ (std::is_arithmetic<T>::value || std::is_same<bool, T>::value) && (std::is_arithmetic<T2>::value || std::is_same<bool, T2>::value) && !std::is_same<T, T2>::value,
+ binding<T2>
+ >::type as() const
{
return transformed([](T val) { return static_cast<T2>(val); });
}
+ template<typename T2>
+ typename std::enable_if<
+ (std::is_same<std::wstring, T>::value || std::is_same<std::wstring, T2>::value) && !std::is_same<T, T2>::value,
+ binding<T2>
+ >::type as() const
+ {
+ return transformed([](T val) { return boost::lexical_cast<T2>(val); });
+ }
+
// Func -> R (T self_val)
// Returns binding<R>
template<typename Func>
return lhs.composed(rhs, [](bool lhs, bool rhs) { return lhs || rhs; });
}
+static binding<bool> operator||(const binding<bool>& lhs, bool rhs)
+{
+ return lhs.transformed([rhs](bool lhs) { return lhs || rhs; });
+}
+
static binding<bool> operator&&(const binding<bool>& lhs, const binding<bool>& rhs)
{
return lhs.composed(rhs, [](bool lhs, bool rhs) { return lhs && rhs; });
}
+static binding<bool> operator&&(const binding<bool>& lhs, bool rhs)
+{
+ return lhs.transformed([rhs](bool lhs) { return lhs && rhs; });
+}
+
static binding<bool> operator!(const binding<bool>& self)
{
return self.transformed([](bool self) { return !self; });
#include "color/color_producer.h"
#include "draw/freehand_producer.h"
#include "separated/separated_producer.h"
+#include "variable.h"
#include <common/assert.h>
#include <common/except.h>
return impl_->frame_number_;
}
+variable& frame_producer_base::get_variable(const std::wstring& name)
+{
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"No variable called " + name + L" found in " + print()));
+}
+
+const std::vector<std::wstring>& frame_producer_base::get_variables() const
+{
+ static std::vector<std::wstring> empty;
+
+ return empty;
+}
+
const spl::shared_ptr<frame_producer>& frame_producer::empty()
{
class empty_frame_producer : public frame_producer
std::wstring name() const override {return L"empty";}
uint32_t frame_number() const override {return 0;}
boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params) override{CASPAR_THROW_EXCEPTION(not_supported());}
+ variable& get_variable(const std::wstring& name) override { CASPAR_THROW_EXCEPTION(not_supported()); }
+ const std::vector<std::wstring>& get_variables() const override { static std::vector<std::wstring> empty; return empty; }
draw_frame last_frame() {return draw_frame::empty();}
constraints& pixel_constraints() override { static constraints c; return c; }
uint32_t frame_number() const override {return producer_->frame_number();}
boost::property_tree::wptree info() const override {return producer_->info();}
boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params) override {return producer_->call(params);}
+ variable& get_variable(const std::wstring& name) override {return producer_->get_variable(name);}
+ const std::vector<std::wstring>& get_variables() const override {return producer_->get_variables();}
void leading_producer(const spl::shared_ptr<frame_producer>& producer) override {return producer_->leading_producer(producer);}
uint32_t nb_frames() const override {return producer_->nb_frames();}
class draw_frame last_frame() {return producer_->last_frame();}
namespace caspar { namespace core {
+class variable;
+
struct constraints
{
binding<double> width;
virtual class draw_frame receive() = 0;
virtual boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params) = 0;
+ virtual variable& get_variable(const std::wstring& name) = 0;
+ virtual const std::vector<std::wstring>& get_variables() const = 0;
// monitor::observable
// Methods
- virtual boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params);
+ virtual boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params) override;
+ virtual variable& get_variable(const std::wstring& name) override;
+ virtual const std::vector<std::wstring>& get_variables() const override;
// monitor::observable
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "../../stdafx.h"
+
+#include "expression_parser.h"
+
+#include <string>
+#include <memory>
+#include <vector>
+#include <functional>
+#include <typeinfo>
+
+#include <boost/any.hpp>
+
+#include <common/log.h>
+#include <common/except.h>
+#include <common/utf.h>
+
+namespace caspar { namespace core { namespace scene {
+
+wchar_t next_non_whitespace(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const std::wstring& error_if_eof)
+{
+ while (cursor != str.end())
+ {
+ switch (*cursor)
+ {
+ case L' ':
+ case L'\t':
+ ++cursor;
+ continue;
+ default:
+ return *cursor;
+ }
+ }
+
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Unexpected end of input (" + error_if_eof + L") in " + str));
+}
+
+std::wstring at_position(
+ const std::wstring::const_iterator& cursor, const std::wstring& str)
+{
+ int index = static_cast<int>(cursor - str.begin());
+
+ return L" at index " + boost::lexical_cast<std::wstring>(index)
+ + L" in " + str;
+}
+
+boost::any parse_expression(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo);
+
+boost::any parse_parenthesis(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo)
+{
+ if (*cursor++ != L'(')
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"Expected (" + at_position(cursor, str)));
+
+ auto expr = parse_expression(cursor, str, var_repo);
+
+ if (*cursor++ != L')')
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"Expected )" + at_position(cursor, str)));
+
+ return expr;
+}
+
+double parse_constant(
+ std::wstring::const_iterator& cursor, const std::wstring& str)
+{
+ std::wstring constant;
+
+ while (cursor != str.end())
+ {
+ wchar_t ch = *cursor;
+
+ if ((ch >= L'0' && ch <= L'9') || ch == L'.')
+ constant += ch;
+ else
+ break;
+
+ ++cursor;
+ }
+
+ return boost::lexical_cast<double>(constant);
+}
+
+std::wstring parse_string_literal(
+ std::wstring::const_iterator& cursor, const std::wstring& str)
+{
+ std::wstring literal;
+
+ if (*cursor++ != L'"')
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"Expected (" + at_position(cursor, str)));
+
+ bool escaping = false;
+
+ while (cursor != str.end())
+ {
+ wchar_t ch = *cursor;
+
+ switch (ch)
+ {
+ case L'\\':
+ if (escaping)
+ {
+ literal += ch;
+ escaping = false;
+ }
+ else
+ escaping = true;
+ break;
+ case L'"':
+ if (escaping)
+ {
+ literal += ch;
+ escaping = false;
+ break;
+ }
+ else
+ {
+ ++cursor;
+ return std::move(literal);
+ }
+ case L'n':
+ if (escaping)
+ {
+ literal += L'\n';
+ escaping = false;
+ }
+ else
+ literal += ch;
+ break;
+ default:
+ literal += ch;
+ }
+
+ ++cursor;
+ }
+
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Unexpected end of input (Expected closing \") in " + str));
+}
+
+boost::any parse_variable(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo)
+{
+ std::wstring variable_name;
+
+ while (cursor != str.end())
+ {
+ wchar_t ch = *cursor;
+
+ if (ch == L'.'
+ || ch == L'_'
+ || (ch >= L'a' && ch <= L'z')
+ || (ch >= L'A' && ch <= L'Z'))
+ variable_name += ch;
+ else
+ break;
+
+ ++cursor;
+ }
+
+ if (variable_name == L"true")
+ return true;
+ else if (variable_name == L"false")
+ return false;
+
+ variable& var = var_repo(variable_name);
+
+ if (var.is<double>())
+ return var.as<double>();
+ else if (var.is<int64_t>())
+ return var.as<int64_t>().as<double>();
+ else if (var.is<std::wstring>())
+ return var.as<std::wstring>();
+ else if (var.is<bool>())
+ return var.as<bool>();
+
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Unhandled variable type of " + variable_name
+ + at_position(cursor, str)));
+}
+
+struct op
+{
+ enum op_type
+ {
+ UNARY,
+ BINARY,
+ TERNARY
+ };
+
+ std::wstring characters;
+ int precedence;
+ op_type type;
+
+ op(wchar_t ch, int precedence, op_type type)
+ : characters(1, ch), precedence(precedence), type(type)
+ {
+ }
+
+ op(const std::wstring& chs, int precedence, op_type type)
+ : characters(chs), precedence(precedence), type(type)
+ {
+ }
+};
+
+op parse_operator(std::wstring::const_iterator& cursor, const std::wstring& str)
+{
+ std::wstring characters;
+ static const wchar_t NONE = L' ';
+ wchar_t first = NONE;
+
+ while (cursor != str.end())
+ {
+ wchar_t ch = *cursor;
+
+ switch (ch)
+ {
+ case L'+':
+ ++cursor;
+ return op(ch, 6, op::BINARY);
+ case L'*':
+ case L'/':
+ case L'%':
+ ++cursor;
+ return op(ch, 5, op::BINARY);
+ case L'?':
+ case L':':
+ ++cursor;
+ return op(ch, 15, op::TERNARY);
+ case L'-':
+ if (first == L'-')
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect -" + at_position(cursor, str)));
+ else
+ first = ch;
+
+ ++cursor;
+ break;
+ case L'!':
+ if (first == L'!')
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect !" + at_position(cursor, str)));
+ else
+ first = ch;
+
+ ++cursor;
+ break;
+ case L'<':
+ if (first == L'<')
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect <" + at_position(cursor, str)));
+ else
+ first = ch;
+
+ ++cursor;
+ break;
+ case L'>':
+ if (first == L'>')
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect >" + at_position(cursor, str)));
+ else
+ first = ch;
+
+ ++cursor;
+ break;
+ case L'=':
+ if (first == L'=')
+ {
+ ++cursor;
+ return op(L"==", 9, op::BINARY);
+ }
+ else if (first == L'!')
+ {
+ ++cursor;
+ return op(L"!=", 9, op::BINARY);
+ }
+ else if (first == L'>')
+ {
+ ++cursor;
+ return op(L">=", 8, op::BINARY);
+ }
+ else if (first == L'<')
+ {
+ ++cursor;
+ return op(L"<=", 8, op::BINARY);
+ }
+ else if (first == NONE)
+ {
+ ++cursor;
+ first = L'=';
+ }
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect =" + at_position(cursor, str)));
+
+ break;
+ case L'|':
+ if (first == L'|')
+ {
+ ++cursor;
+ return op(L"||", 14, op::BINARY);
+ }
+ else if (first == NONE)
+ {
+ ++cursor;
+ first = L'|';
+ }
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Did not expect =" + at_position(cursor, str)));
+
+ break;
+ case L'&':
+ if (first == L'&')
+ {
+ ++cursor;
+ return op(L"&&", 13, op::BINARY);
+ }
+ else if (first == NONE)
+ {
+ ++cursor;
+ first = L'&';
+ }
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << 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);
+ else if (first == L'!')
+ return op(L'!', 3, op::UNARY);
+ default:
+ if (first == L'<')
+ return op(L'<', 8, op::BINARY);
+ else if (first == L'>')
+ return op(L'>', 8, op::BINARY);
+ else if (first == L'-')
+ return op(L"unary-", 3, op::UNARY);
+ else if (first == L'!')
+ return op(L'!', 3, op::UNARY);
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"Expected second character of operator"
+ + at_position(cursor, str)));
+ }
+ }
+
+ CASPAR_THROW_EXCEPTION(caspar_exception() << 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
+ if (is<double>(value))
+ return binding<double>(as<double>(value));
+ else if (is<bool>(value))
+ return binding<bool>(as<bool>(value));
+ else if (is<std::wstring>(value))
+ return binding<std::wstring>(as<std::wstring>(value));
+ // Already one of the supported binding types
+ else if (is<binding<double>>(value))
+ return as<binding<double>>(value);
+ else if (is<binding<bool>>(value))
+ return as<binding<bool>>(value);
+ else if (is<binding<std::wstring>>(value))
+ return as<binding<std::wstring>>(value);
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << 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)
+{
+ return !require<bool>(to_create_not_of);
+}
+
+boost::any multiply(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) * require<double>(rhs);
+}
+
+boost::any divide(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) / require<double>(rhs);
+}
+
+boost::any modulus(const boost::any& lhs, boost::any& rhs)
+{
+ return
+ (
+ require<double>(lhs).as<int64_t>()
+ % require<double>(rhs).as<int64_t>()
+ ).as<double>();
+}
+
+binding<std::wstring> stringify(const boost::any& value)
+{
+ auto b = as_binding(value);
+
+ if (is<binding<std::wstring>>(b))
+ return as<binding<std::wstring>>(b);
+ else if (is<binding<double>>(b))
+ return as<binding<double>>(b).as<std::wstring>();
+ else if (is<binding<bool>>(b))
+ return as<binding<bool>>(b).as<std::wstring>();
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"Couldn't stringify " + u16(value.type().name())));
+}
+
+boost::any add(const boost::any& lhs, boost::any& rhs)
+{
+ auto l = as_binding(lhs);
+ auto r = as_binding(rhs);
+
+ // number
+ if (is<binding<double>>(l) && is<binding<double>>(r))
+ return as<binding<double>>(l) + as<binding<double>>(r);
+ // string
+ else if (is<binding<std::wstring>>(l) && is<binding<std::wstring>>(r))
+ return as<binding<std::wstring>>(l) + as<binding<std::wstring>>(r);
+ // mixed types to string and concatenated
+ else
+ return stringify(lhs) + stringify(rhs);
+}
+
+boost::any subtract(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) - require<double>(rhs);
+}
+
+boost::any less(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) < require<double>(rhs);
+}
+
+boost::any less_or_equal(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) <= require<double>(rhs);
+}
+
+boost::any greater(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) > require<double>(rhs);
+}
+
+boost::any greater_or_equal(const boost::any& lhs, boost::any& rhs)
+{
+ return require<double>(lhs) >= require<double>(rhs);
+}
+
+boost::any equal(const boost::any& lhs, boost::any& rhs)
+{
+ auto l = as_binding(lhs);
+ auto r = as_binding(rhs);
+
+ // number
+ if (is<binding<double>>(l) && is<binding<double>>(r))
+ return as<binding<double>>(l) == as<binding<double>>(r);
+ // string
+ else if (is<binding<std::wstring>>(l) && is<binding<std::wstring>>(r))
+ return as<binding<std::wstring>>(l) == as<binding<std::wstring>>(r);
+ // boolean
+ else
+ return require<bool>(l) == require<bool>(r);
+}
+
+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)
+{
+ return require<bool>(lhs) || require<bool>(rhs);
+}
+
+template<typename T>
+binding<T> ternary(
+ const binding<bool>& condition,
+ const binding<T>& true_value,
+ const binding<T>& false_value)
+{
+ return when(condition).then(true_value).otherwise(false_value);
+}
+
+boost::any ternary(
+ const boost::any& condition,
+ const boost::any& true_value,
+ const boost::any& false_value)
+{
+ 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));
+ // string
+ else if (is<binding<std::wstring>>(t) && is<binding<std::wstring>>(f))
+ return ternary(
+ cond,
+ as<binding<std::wstring>>(t),
+ as<binding<std::wstring>>(f));
+ // bool
+ else
+ return ternary(cond, require<bool>(t), require<bool>(f));
+}
+
+void resolve_operators(int precedence, std::vector<boost::any>& tokens)
+{
+ for (int i = 0; i < tokens.size(); ++i)
+ {
+ auto& token = tokens.at(i);
+
+ if (!is<op>(token))
+ continue;
+
+ auto op_token = as<op>(token);
+
+ if (op_token.precedence != precedence)
+ continue;
+
+ int index_after = i + 1;
+ auto& token_after = tokens.at(index_after);
+
+ switch (op_token.type)
+ {
+ case op::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.erase(tokens.begin() + index_after);
+
+ break;
+ case op::BINARY:
+ {
+ auto& token_before = tokens.at(i - 1);
+
+ if (op_token.characters == L"*")
+ token_before = multiply(token_before, token_after);
+ else if (op_token.characters == L"/")
+ token_before = divide(token_before, token_after);
+ else if (op_token.characters == L"%")
+ token_before = modulus(token_before, token_after);
+ else if (op_token.characters == L"+")
+ token_before = add(token_before, token_after);
+ else if (op_token.characters == L"-")
+ token_before = subtract(token_before, token_after);
+ else if (op_token.characters == L"<")
+ token_before = less(token_before, token_after);
+ else if (op_token.characters == L"<=")
+ token_before = less_or_equal(token_before, token_after);
+ else if (op_token.characters == L">")
+ token_before = greater(token_before, token_after);
+ else if (op_token.characters == L">=")
+ token_before = greater_or_equal(token_before, token_after);
+ 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));
+ else if (op_token.characters == L"&&")
+ token_before = and(token_before, token_after);
+ else if (op_token.characters == L"||")
+ token_before = or(token_before, token_after);
+ }
+
+ tokens.erase(tokens.begin() + i, tokens.begin() + i + 2);
+ --i;
+
+ break;
+ case op::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(
+ L"Expected : as part of ternary expression"));
+
+ auto& token_false_value = tokens.at(i + 3);
+ token_before = ternary(
+ token_before, token_after, token_false_value);
+ tokens.erase(tokens.begin() + i, tokens.begin() + i + 4);
+ --i;
+ }
+
+ break;
+ }
+ }
+}
+
+boost::any parse_expression(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo)
+{
+ std::vector<boost::any> tokens;
+ bool stop = false;
+
+ while (cursor != str.end())
+ {
+ wchar_t ch = next_non_whitespace(cursor, str, L"Expected expression");
+
+ switch (ch)
+ {
+ case L'0':
+ case L'1':
+ case L'2':
+ case L'3':
+ case L'4':
+ case L'5':
+ case L'6':
+ case L'7':
+ case L'8':
+ case L'9':
+ tokens.push_back(parse_constant(cursor, str));
+ break;
+ case L'+':
+ case L'-':
+ case L'*':
+ case L'/':
+ case L'%':
+ case L'<':
+ case L'>':
+ case L'!':
+ case L'=':
+ case L'|':
+ case L'&':
+ case L'?':
+ case L':':
+ tokens.push_back(parse_operator(cursor, str));
+ break;
+ case L'"':
+ tokens.push_back(parse_string_literal(cursor, str));
+ break;
+ case L'(':
+ tokens.push_back(parse_parenthesis(cursor, str, var_repo));
+ break;
+ case L')':
+ stop = true;
+ break;
+ default:
+ tokens.push_back(parse_variable(cursor, str, var_repo));
+ break;
+ }
+
+ if (stop)
+ break;
+ }
+
+ if (tokens.empty())
+ CASPAR_THROW_EXCEPTION(caspar_exception()
+ << msg_info(L"Expected expression"));
+
+ int precedence = 1;
+
+ while (tokens.size() > 1)
+ {
+ resolve_operators(precedence++, tokens);
+ }
+
+ 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())));
+}
+
+}}}
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include "../variable.h"
+
+namespace caspar { namespace core { namespace scene {
+
+typedef std::function<variable& (const std::wstring& name)> variable_repository;
+
+boost::any parse_expression(
+ std::wstring::const_iterator& cursor,
+ const std::wstring& str,
+ const variable_repository& var_repo);
+
+template<typename T>
+bool is(const boost::any& value)
+{
+ return value.type() == typeid(T);
+}
+
+template<typename T>
+static binding<T> 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<T>>(expr))
+ return boost::any_cast<binding<T>>(expr);
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(
+ L"parse_expression() Unsupported type "
+ + u16(expr.type().name())));
+}
+
+template<>
+binding<std::wstring> parse_expression(
+ const std::wstring& str, const variable_repository& var_repo);
+
+}}}
double frame_fraction_;
std::map<void*, timeline> timelines_;
std::map<std::wstring, std::shared_ptr<core::variable>> variables_;
+ std::vector<std::wstring> variable_names_;
impl(int width, int height)
: pixel_constraints_(width, height)
, aggregator_([=] (double x, double y) { return collission_detect(x, y); })
, frame_fraction_(0)
{
- auto speed_variable = std::make_shared<core::variable_impl<double>>(L"", true, 1.0);
+ auto speed_variable = std::make_shared<core::variable_impl<double>>(L"1.0", true, 1.0);
store_variable(L"scene_speed", speed_variable);
speed_ = speed_variable->value();
+ auto frame_variable = std::make_shared<core::variable_impl<int64_t>>(L"0", true, 0);
+ store_variable(L"frame", frame_variable);
+ frame_number_ = frame_variable->value();
}
layer& create_layer(
const std::wstring& name, const std::shared_ptr<core::variable>& var)
{
variables_.insert(std::make_pair(name, var));
+ variable_names_.push_back(name);
}
core::variable& get_variable(const std::wstring& name)
return *found->second;
}
+ const std::vector<std::wstring>& get_variables() const
+ {
+ return variable_names_;
+ }
+
binding<int64_t> frame()
{
return frame_number_;
auto& pos = transform.image_transform.fill_translation;
auto& scale = transform.image_transform.fill_scale;
+ //auto& clip_pos = transform.image_transform.clip_translation;
+ //auto& clip_scale = transform.image_transform.clip_scale;
pos[0] = static_cast<double>(layer.position.x.get()) / static_cast<double>(pixel_constraints_.width.get());
pos[1] = static_cast<double>(layer.position.y.get()) / static_cast<double>(pixel_constraints_.height.get());
scale[1] = static_cast<double>(layer.producer.get()->pixel_constraints().height.get())
/ static_cast<double>(pixel_constraints_.height.get());
+ /*clip_pos[0] = static_cast<double>(layer.clipping.upper_left.x.get()) / static_cast<double>(pixel_constraints_.width.get());
+ clip_pos[1] = static_cast<double>(layer.clipping.upper_left.y.get()) / static_cast<double>(pixel_constraints_.height.get());
+ clip_scale[0] = static_cast<double>(layer.clipping.width.get()) / static_cast<double>(pixel_constraints_.width.get());
+ clip_scale[1] = static_cast<double>(layer.clipping.height.get()) / static_cast<double>(pixel_constraints_.height.get());*/
+
transform.image_transform.opacity = layer.adjustments.opacity.get();
transform.image_transform.is_key = layer.is_key.get();
return impl_->get_variable(name);
}
+const std::vector<std::wstring>& scene_producer::get_variables() const
+{
+ return impl_->get_variables();
+}
+
spl::shared_ptr<core::frame_producer> create_dummy_scene_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params)
{
if (params.size() < 1 || !boost::iequals(params.at(0), L"[SCENE]"))
return result;
};
- //auto& car_layer = scene->create_layer(create_producer(frame_factory, format_desc, create_param(L"car")));
+ auto& car_layer = scene->create_layer(create_producer(frame_factory, format_desc, create_param(L"car")), L"car");
+ car_layer.clipping.upper_left.x.set(80);
+ car_layer.clipping.upper_left.y.set(45);
+ car_layer.clipping.width.unbind();
+ car_layer.clipping.width.set(640);
+ car_layer.clipping.height.unbind();
+ car_layer.clipping.height.set(360);
+ car_layer.adjustments.opacity.set(0.5);
//car_layer.hidden = scene->frame() % 50 > 25 || !(scene->frame() < 1000);
std::vector<std::wstring> sub_params;
sub_params.push_back(L"[FREEHAND]");
to_affect.set(tweened);
- CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened;
+ //CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened;
};
store_keyframe(to_affect.identity(), k);
store_keyframe(to_affect.identity(), k);
}
- core::variable& get_variable(const std::wstring& name);
+ core::variable& get_variable(const std::wstring& name) override;
+ const std::vector<std::wstring>& get_variables() const override;
private:
void store_keyframe(void* timeline_identity, const keyframe& k);
void store_variable(
#include "../../stdafx.h"
#include "xml_scene_producer.h"
+#include "expression_parser.h"
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
namespace caspar { namespace core { namespace scene {
+void deduce_expression(variable& var, const variable_repository& repo)
+{
+ auto expr_str = var.original_expr();
+ auto trimmed = boost::trim_copy(expr_str);
+
+ if (boost::starts_with(trimmed, L"${") && boost::ends_with(trimmed, L"}"))
+ {
+ expr_str = trimmed.substr(2, expr_str.length() - 3);
+
+ if (var.is<double>())
+ var.as<double>().bind(parse_expression<double>(expr_str, repo));
+ else if (var.is<bool>())
+ var.as<bool>().bind(parse_expression<bool>(expr_str, repo));
+ else if (var.is<std::wstring>())
+ var.as<std::wstring>().bind(parse_expression<std::wstring>(expr_str, repo));
+ }
+ else if (!expr_str.empty())
+ {
+ var.from_string(expr_str);
+ }
+}
+
spl::shared_ptr<core::frame_producer> create_xml_scene_producer(
const spl::shared_ptr<core::frame_factory>& frame_factory,
const core::video_format_desc& format_desc,
BOOST_FOREACH(auto elem, root.get_child(L"scene.variables"))
{
auto type = elem.second.get<std::wstring>(L"<xmlattr>.type");
-
- if (type == L"double")
- scene->create_variable<double>(
- L"variable." + elem.second.get<std::wstring>(L"<xmlattr>.id"),
- false,
- elem.second.get_value<std::wstring>());
+ auto id = elem.second.get<std::wstring>(L"<xmlattr>.id");
+ auto is_public = elem.second.get(L"<xmlattr>.public", false);
+ auto expr = elem.second.get_value<std::wstring>();
+
+ if (!is_public)
+ id = L"variable." + id;
+
+ if (type == L"number")
+ scene->create_variable<double>(id, is_public, expr);
+ else if (type == L"string")
+ scene->create_variable<std::wstring>(id, is_public, expr);
+ else if (type == L"bool")
+ scene->create_variable<bool>(id, is_public, expr);
}
BOOST_FOREACH(auto elem, root.get_child(L"scene.layers"))
layer.position.x = scene->create_variable<double>(variable_prefix + L"x", false, elem.second.get<std::wstring>(L"x"));
layer.position.y = scene->create_variable<double>(variable_prefix + L"y", false, elem.second.get<std::wstring>(L"y"));
+ layer.adjustments.opacity = scene->create_variable<double>(variable_prefix + L"adjustment.opacity", false, elem.second.get(L"adjustments.opacity", L"1.0"));
+
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
+
+ BOOST_FOREACH(auto& var_name, producer->get_variables())
+ {
+ auto& var = producer->get_variable(var_name);
+ auto expr = elem.second.get<std::wstring>(L"parameters." + var_name, L"");
+
+ if (var.is<double>())
+ scene->create_variable<double>(variable_prefix + L"parameter." + var_name, false, expr) = var.as<double>();
+ else if (var.is<std::wstring>())
+ scene->create_variable<std::wstring>(variable_prefix + L"parameter." + var_name, false, expr) = var.as<std::wstring>();
+ else if (var.is<bool>())
+ scene->create_variable<bool>(variable_prefix + L"parameter." + var_name, false, expr) = var.as<bool>();
+ else if (var.is<int>())
+ scene->create_variable<double>(variable_prefix + L"parameter." + var_name, false, expr) = var.as<int>().as<double>();
+ }
}
BOOST_FOREACH(auto& elem, root.get_child(L"scene.timelines"))
}
}
- BOOST_FOREACH(auto& elem, root.get_child(L"scene.parameters"))
+ auto repo = [&scene](const std::wstring& name) -> variable&
{
- auto& variable = scene->get_variable(elem.second.get<std::wstring>(L"<xmlattr>.variable"));
- auto id = elem.second.get<std::wstring>(L"<xmlattr>.id");
+ return scene->get_variable(name);
+ };
- if (variable.is<double>())
- scene->create_variable<double>(id, true) = variable.as<double>();
- else if (variable.is<std::wstring>())
- scene->create_variable<std::wstring>(id, true) = variable.as<std::wstring>();
+ BOOST_FOREACH(auto& var_name, scene->get_variables())
+ {
+ deduce_expression(scene->get_variable(var_name), repo);
}
+ auto params2 = params;
+ params2.erase(params2.begin());
+
+ scene->call(params2);
+
return scene;
}
#include <core/producer/frame_producer.h>
#include <core/producer/color/color_producer.h>
+#include <core/producer/variable.h>
#include <core/frame/geometry.h>
#include <core/frame/frame.h>
#include <core/frame/draw_frame.h>
constraints constraints_;
int x_, y_, parent_width_, parent_height_;
bool standalone_;
- binding<std::wstring> text_;
+ variable_impl<std::wstring> text_;
std::shared_ptr<void> text_subscription_;
- binding<int> current_bearing_y_;
- binding<int> current_protrude_under_y_;
+ variable_impl<int> current_bearing_y_;
+ variable_impl<int> current_protrude_under_y_;
draw_frame frame_;
text::texture_atlas atlas_;
text::texture_font font_;
font_.load_glyphs(text::Latin_1_Supplement, text_info.color);
font_.load_glyphs(text::Latin_Extended_A, text_info.color);
- text_subscription_ = text_.on_change([this]()
+ text_subscription_ = text_.value().on_change([this]()
{
- generate_frame(text_.get());
+ generate_frame(text_.value().get());
});
+ constraints_.height.depend_on(text());
+ constraints_.width.depend_on(text());
+ current_bearing_y_.as<int>().depend_on(text());
+ current_protrude_under_y_.as<int>().depend_on(text());
+
//generate frame
- text_.set(str);
+ text_.value().set(str);
CASPAR_LOG(info) << print() << L" Initialized";
}
this->constraints_.width.set(metrics.width);
this->constraints_.height.set(metrics.height);
- current_bearing_y_.set(metrics.bearingY);
- current_protrude_under_y_.set(metrics.protrudeUnderY);
+ current_bearing_y_.value().set(metrics.bearingY);
+ current_protrude_under_y_.value().set(metrics.protrudeUnderY);
frame_ = core::draw_frame(std::move(frame));
+ frame_.transform().image_transform.fill_translation[1] = static_cast<double>(metrics.bearingY) / static_cast<double>(metrics.height);
}
text::string_metrics measure_string(const std::wstring& str)
boost::unique_future<std::wstring> call(const std::vector<std::wstring>& param)
{
std::wstring result;
- text_.set(param.empty() ? L"" : param[0]);
+ text_.value().set(param.empty() ? L"" : param[0]);
return async(launch::deferred, [=]{return result;});
}
+ variable& get_variable(const std::wstring& name)
+ {
+ if (name == L"text")
+ return text_;
+ else if (name == L"current_bearing_y")
+ return current_bearing_y_;
+ else if (name == L"current_protrude_under_y")
+ return current_protrude_under_y_;
+
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"text_producer does not have a variable called " + name));
+ }
+
+ const std::vector<std::wstring>& get_variables() const
+ {
+ static std::vector<std::wstring> vars =
+ boost::assign::list_of<std::wstring>
+ (L"text")
+ (L"current_bearing_y")
+ (L"current_protrude_under_y");
+
+ return vars;
+ }
+
constraints& pixel_constraints()
{
return constraints_;
binding<std::wstring>& text()
{
- return text_;
+ return text_.value();
}
const binding<int>& current_bearing_y() const
{
- return current_bearing_y_;
+ return current_bearing_y_.value();
}
const binding<int>& current_protrude_under_y() const
{
- return current_protrude_under_y_;
+ return current_protrude_under_y_.value();
}
std::wstring print() const
{
- return L"text[" + text_.get() + L"]";
+ return L"text[" + text_.value().get() + L"]";
}
std::wstring name() const
{
boost::property_tree::wptree info;
info.add(L"type", L"text");
- info.add(L"text", text_.get());
+ info.add(L"text", text_.value().get());
return info;
}
};
draw_frame text_producer::receive_impl() { return impl_->receive_impl(); }
boost::unique_future<std::wstring> text_producer::call(const std::vector<std::wstring>& param) { return impl_->call(param); }
+variable& text_producer::get_variable(const std::wstring& name) { return impl_->get_variable(name); }
+const std::vector<std::wstring>& text_producer::get_variables() const { return impl_->get_variables(); }
text::string_metrics text_producer::measure_string(const std::wstring& str) { return impl_->measure_string(str); }
constraints& text_producer::pixel_constraints() { return impl_->pixel_constraints(); }
try_get_color(col_str, col_val);
text_info.color = core::text::color<float>(col_val);
- return text_producer::create(frame_factory, x, y, params.at(1), text_info, format_desc.width, format_desc.height, true);
+ bool standalone = get_param(L"STANDALONE", params, false);
+
+ return text_producer::create(frame_factory, x, y, params.at(1), text_info, format_desc.width, format_desc.height, standalone);
}
}}
draw_frame receive_impl() override;
boost::unique_future<std::wstring> call(const std::vector<std::wstring>& param) override;
+ variable& get_variable(const std::wstring& name) override;
+ const std::vector<std::wstring>& get_variables() const override;
text::string_metrics measure_string(const std::wstring& str);
#include <boost/lexical_cast.hpp>
+#include <common/log.h>
+#include <common/except.h>
+
#include "binding.h"
namespace caspar { namespace core {
return dynamic_cast<variable_impl<T>&>(*this).value();
}
+ template<typename T>
+ const binding<T>& as() const
+ {
+ return dynamic_cast<const variable_impl<T>&>(*this).value();
+ }
+
virtual void from_string(const std::wstring& raw_value) = 0;
virtual std::wstring to_string() const = 0;
private:
private:
binding<T> value_;
public:
+ variable_impl()
+ : variable(L"", true)
+ {
+ }
+
variable_impl(const std::wstring& expr, bool is_public, T initial_value = T())
: variable(expr, is_public)
, value_(initial_value)
return value_;
}
+ const binding<T>& value() const
+ {
+ return value_;
+ }
+
virtual void from_string(const std::wstring& raw_value)
{
- value_.set(boost::lexical_cast<T>(raw_value));
+ if (std::is_same<bool, T>::value)
+ {
+ binding<bool>& bool_binding = *reinterpret_cast<binding<bool>*>(&value_);
+
+ if (raw_value == L"true")
+ bool_binding.set(true);
+ else if (raw_value == L"false")
+ bool_binding.set(false);
+ else
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"bool constants should be true or false"));
+ }
+ else
+ value_.set(boost::lexical_cast<T>(raw_value));
}
virtual std::wstring to_string() const
{
return false;
}
+
+ int delay_millis() const
+ {
+ return 60;
+ }
int buffer_depth() const override
{
- return 3;
+ int delay_in_frames = static_cast<int>(delay_millis() / (1000.0 / format_desc_.fps));
+
+ return delay_in_frames;
}
int index() const override