Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic databinding variable expressions #550

Merged
merged 12 commits into from
Jan 24, 2024
137 changes: 72 additions & 65 deletions Source/Core/DataExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ struct InstructionData {
Variant data;
};

struct ProgramState {
size_t program_length;
int stack_size;
};

namespace Parse {
static void Assignment(DataParser& parser);
static void Expression(DataParser& parser);
Expand Down Expand Up @@ -196,12 +201,6 @@ class DataParser {
parse_error = true;
Error(CreateString(120, "Internal parser error, inconsistent stack operations. Stack size is %d at parse end.", program_stack_size));
}
if (!parse_error && !program_states.empty())
{
parse_error = true;
Error(CreateString(140, "Internal parser error, inconsistent state stack operations. State stack size is %d at parse end.",
(int)program_states.size()));
}

return !parse_error;
}
Expand Down Expand Up @@ -255,28 +254,19 @@ class DataParser {
program.push_back(InstructionData{Instruction::NumArguments, Variant(int(num_arguments))});
program.push_back(InstructionData{instruction, Variant(std::move(name))});
}
void Variable(const String& expression) { VariableGetSet(expression, false); }
void Assign(const String& expression) { VariableGetSet(expression, true); }
void StaticVariable(const String& expression) { VariableGetSet(expression, false); }
void StaticAssign(const String& expression) { VariableGetSet(expression, true); }
Dakror marked this conversation as resolved.
Show resolved Hide resolved

void PushState() { program_states.push_back(ProgramState{program.size(), program_stack_size, index}); }
ProgramState GetProgramState() { return ProgramState{program.size(), program_stack_size}; }

void PopState()
void SetProgramState(const ProgramState& state)
{
RMLUI_ASSERT(!program_states.empty());
auto const& state = program_states.back();
RMLUI_ASSERT(state.program_length <= program.size());
program.resize(state.program_length);
program_stack_size = state.stack_size;
program_states.pop_back();
}

void DiscardState()
{
RMLUI_ASSERT(!program_states.empty());
program_states.pop_back();
}

bool Address(const String& name)
bool AddVariableAddress(const String& name)
{
DataAddress address = expression_interface.ParseAddress(name);
if (address.empty())
Expand Down Expand Up @@ -313,14 +303,6 @@ class DataParser {
Program program;

AddressList variable_addresses;

struct ProgramState {
size_t program_length;
int stack_size;
size_t index;
};

Vector<ProgramState> program_states;
};

namespace Parse {
Expand All @@ -335,8 +317,9 @@ namespace Parse {
static void Term(DataParser& parser);
static void Factor(DataParser& parser);

static String NumberLiteral(DataParser& parser, bool silent = false);
static void NumberLiteral(DataParser& parser);
static void StringLiteral(DataParser& parser);
static String VariableExpression(DataParser& parser, const String& address_prefix);
static void VariableOrFunction(DataParser& parser);

static void Add(DataParser& parser);
Expand Down Expand Up @@ -366,7 +349,7 @@ namespace Parse {
if (is_alpha || (c >= '0' && c <= '9'))
return true;

for (char valid_char : "_. ")
for (char valid_char : "_.")
{
if (c == valid_char && valid_char != '\0')
return true;
Expand All @@ -387,24 +370,42 @@ namespace Parse {
is_first_character = false;
}

// Right trim spaces in name
size_t new_size = String::npos;
for (int i = int(name.size()) - 1; i >= 1; i--)
{
if (name[i] == ' ')
new_size = size_t(i);
else
break;
}
if (new_size != String::npos)
name.resize(new_size);

if (out_valid_function_name)
*out_valid_function_name = (name.find_first_of(". ") == String::npos);

return name;
}

static String FindNumberLiteral(DataParser& parser)
{
String str;

bool first_match = false;
bool has_dot = false;
char c = parser.Look();
if (c == '-')
{
str += c;
c = parser.Next();
}

while ((c >= '0' && c <= '9') || (c == '.' && !has_dot))
{
first_match = true;
str += c;
if (c == '.')
has_dot = true;
c = parser.Next();
}

if (!first_match)
{
return String();
}

return str;
}

// Parser functions
static void Assignment(DataParser& parser)
{
Expand All @@ -420,12 +421,14 @@ namespace Parse {
return;
}

parser.SkipWhitespace();

const char c = parser.Look();
if (c == '=')
{
parser.Match('=');
Expression(parser);
parser.Assign(variable_name);
parser.StaticAssign(variable_name);
}
else if (c == '(' || c == ';' || c == '\0')
{
Expand Down Expand Up @@ -564,9 +567,7 @@ namespace Parse {
}
else if (c == '-' || (c >= '0' && c <= '9'))
{
const double number = FromString(NumberLiteral(parser), 0.0);

parser.Emit(Instruction::Literal, Variant(number));
NumberLiteral(parser);
parser.SkipWhitespace();
}
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
Expand All @@ -578,7 +579,7 @@ namespace Parse {
parser.Expected("literal, variable name, function name, parenthesis, or '!'");
}

static String NumberLiteral(DataParser& parser, bool silent)
static void NumberLiteral(DataParser& parser)
Dakror marked this conversation as resolved.
Show resolved Hide resolved
{
String str;

Expand All @@ -602,12 +603,13 @@ namespace Parse {

if (!first_match)
{
if (!silent)
parser.Error(CreateString(100, "Invalid number literal. Expected '0-9' or '.' but found '%c'.", c));
return String();
parser.Error(CreateString(100, "Invalid number literal. Expected '0-9' or '.' but found '%c'.", c));
return;
}

return str;
const double number = FromString(str, 0.0);

parser.Emit(Instruction::Literal, Variant(number));
}

static void StringLiteral(DataParser& parser)
Expand Down Expand Up @@ -636,7 +638,7 @@ namespace Parse {
parser.Emit(Instruction::Literal, Variant(str));
}

static String VariableExpression(DataParser& parser, String const& address_prefix)
static String VariableExpression(DataParser& parser, const String& address_prefix)
{
if (parser.Look() == '[')
{
Expand All @@ -645,19 +647,21 @@ namespace Parse {

prefix.push_back('[');

parser.PushState();
String index = NumberLiteral(parser, true);
// Backup program state before trying to parse number inside brackets.
// Could turn out to be expression and needs reparsing
auto backup_state = parser.GetProgramState();

String index = FindNumberLiteral(parser);
if (!index.empty() && parser.Look() == ']')
{
parser.DiscardState();
parser.Next();
prefix.append(index);
prefix.push_back(']');
return VariableExpression(parser, prefix);
}
else
{
parser.PopState();
parser.SetProgramState(backup_state);

parser.Emit(Instruction::Literal, Variant(prefix));
parser.Push();
Expand Down Expand Up @@ -693,8 +697,6 @@ namespace Parse {
}
return VariableExpression(parser, address_prefix + next);
}

return address_prefix;
}

static void VariableOrFunction(DataParser& parser)
Expand Down Expand Up @@ -723,26 +725,27 @@ namespace Parse {
}
else if (parser.Look() == '[')
{
parser.PushState();
// Backup program state before trying to parse part inside brackets.
// Could turn out to be expression and needs reparsing
auto backup_state = parser.GetProgramState();

String full_address = VariableExpression(parser, name);

if (!full_address.empty())
{
parser.PopState();
parser.Variable(full_address);
parser.SetProgramState(backup_state);
parser.StaticVariable(full_address);
}
else
{
// add the root of a variable expression as dependency into the address list
parser.Address(name);
parser.AddVariableAddress(name);

parser.DiscardState();
parser.Emit(Instruction::DynamicVariable, Variant());
}
}
else
parser.Variable(name);
parser.StaticVariable(name);
}

static void Add(DataParser& parser)
Expand Down Expand Up @@ -1115,7 +1118,11 @@ class DataInterpreter {
break;
case Instruction::CastToInt:
{
R = R.Get<int>();
int tmp;
if (!R.GetInto(tmp))
return Error("Could not cast value to int.");
else
R = tmp;
}
break;
default: RMLUI_ERRORMSG("Instruction not implemented."); break;
Expand Down
2 changes: 1 addition & 1 deletion Tests/Source/UnitTests/DataBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include <cmath>
#include <doctest.h>


using namespace Rml;

namespace {
Expand Down Expand Up @@ -559,6 +558,7 @@ TEST_CASE("databinding.dynamic_variables")
CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "c2");

document->Close();
*globals.i1 = 1;

TestsShell::ShutdownShell();
}
Expand Down