Skip to content

Commit 010765e

Browse files
rminderhoudexjam
andauthored
DataExpression: Implement ternary with jumps (#740)
Avoids evaluating both the true and false branch of the ternary. This can help if you are writing an expression where one side of the ternary might result in an invalid array access, e.g. ``` read_mail_index < inbox.size ? inbox[read_mail_index].from : '' ``` --------- Co-authored-by: James Benton <[email protected]>
1 parent 6407b2b commit 010765e

File tree

2 files changed

+45
-22
lines changed

2 files changed

+45
-22
lines changed

Source/Core/DataExpression.cpp

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ class DataParser;
5050
The abstract machine has three registers:
5151
R Typically results and right-hand side arguments.
5252
L Typically left-hand side arguments.
53-
C Typically center arguments (eg. in ternary operator).
5453
5554
And a stack:
5655
S The program stack.
@@ -64,9 +63,9 @@ class DataParser;
6463
*/
6564
enum class Instruction {
6665
// clang-format off
67-
// Assignment (register/stack) = Read (register R/L/C, instruction data D, or stack)
66+
// Assignment (register/stack) = Read (register R/L, instruction data D, or stack)
6867
Push = 'P', // S+ = R
69-
Pop = 'o', // <R/L/C> = S- (D determines R/L/C)
68+
Pop = 'o', // <R/L> = S- (D determines R/L)
7069
Literal = 'D', // R = D
7170
Variable = 'V', // R = DataModel.GetVariable(D) (D is an index into the variable address list)
7271
Add = '+', // R = L + R
@@ -82,20 +81,20 @@ enum class Instruction {
8281
GreaterEq = 'G', // R = L >= R
8382
Equal = '=', // R = L == R
8483
NotEqual = 'N', // R = L != R
85-
Ternary = '?', // R = L ? C : R
8684
NumArguments = '#', // R = D (Contains the num. arguments currently on the stack, immediately followed by a 'T' or 'E' instruction)
8785
TransformFnc = 'T', // R = DataModel.Execute(D, A) where A = S[TOP - R, TOP]; S -= R; (D determines function name, input R the num. arguments, A the arguments)
8886
EventFnc = 'E', // DataModel.EventCallback(D, A); S -= R;
8987
Assign = 'A', // DataModel.SetVariable(D, R)
9088
DynamicVariable = 'Y', // DataModel.GetVariable(DataModel.ParseAddress(R)) (Looks up a variable by path in R)
91-
CastToInt = 'I' // R = (int)R
89+
CastToInt = 'I', // R = (int)R
90+
Jump = 'J', // Jumps to instruction index D
91+
JumpIfZero = 'Z', // If R is false, jumps to instruction index D
9292
// clang-format on
9393
};
9494

9595
enum class Register {
9696
R,
9797
L,
98-
C,
9998
};
10099

101100
struct InstructionData {
@@ -256,6 +255,8 @@ class DataParser {
256255
}
257256
void Variable(const String& data_address) { VariableGetSet(data_address, false); }
258257
void Assign(const String& data_address) { VariableGetSet(data_address, true); }
258+
size_t InstructionIndex() const { return program.size(); }
259+
void PatchInstruction(size_t index, InstructionData data) { program[index] = data; }
259260

260261
ProgramState GetProgramState() { return ProgramState{program.size(), program_stack_size}; }
261262

@@ -844,16 +845,23 @@ namespace Parse {
844845

845846
static void Ternary(DataParser& parser)
846847
{
848+
size_t jump_false_branch = parser.InstructionIndex();
849+
parser.Emit(Instruction::JumpIfZero);
850+
847851
parser.Match('?');
848-
parser.Push();
849852
Expression(parser);
850-
parser.Push();
853+
size_t jump_end = parser.InstructionIndex();
854+
parser.Emit(Instruction::Jump);
855+
851856
parser.Match(':');
857+
size_t false_branch = parser.InstructionIndex();
852858
Expression(parser);
853-
parser.Pop(Register::C);
854-
parser.Pop(Register::L);
855-
parser.Emit(Instruction::Ternary);
859+
860+
size_t end = parser.InstructionIndex();
861+
parser.PatchInstruction(jump_false_branch, InstructionData{Instruction::JumpIfZero, Variant((uint64_t)false_branch)});
862+
parser.PatchInstruction(jump_end, InstructionData{Instruction::Jump, Variant((uint64_t)end)});
856863
}
864+
857865
static void Function(DataParser& parser, Instruction function_type, String&& func_name, bool first_argument_piped)
858866
{
859867
RMLUI_ASSERT(function_type == Instruction::TransformFnc || function_type == Instruction::EventFnc);
@@ -928,13 +936,16 @@ class DataInterpreter {
928936
bool Run()
929937
{
930938
bool success = true;
931-
for (size_t i = 0; i < program.size(); i++)
939+
size_t i = 0;
940+
while (i < program.size())
932941
{
933-
if (!Execute(program[i].instruction, program[i].data))
942+
size_t next_instruction = i + 1;
943+
if (!Execute(program[i].instruction, program[i].data, next_instruction))
934944
{
935945
success = false;
936946
break;
937947
}
948+
i = next_instruction;
938949
}
939950

940951
if (success && !stack.empty())
@@ -954,14 +965,14 @@ class DataInterpreter {
954965
Variant Result() const { return R; }
955966

956967
private:
957-
Variant R, L, C;
968+
Variant R, L;
958969
Vector<Variant> stack;
959970

960971
const Program& program;
961972
const AddressList& addresses;
962973
DataExpressionInterface expression_interface;
963974

964-
bool Execute(const Instruction instruction, const Variant& data)
975+
bool Execute(const Instruction instruction, const Variant& data, size_t& next_instruction)
965976
{
966977
auto AnyString = [](const Variant& v1, const Variant& v2) { return v1.GetType() == Variant::STRING || v2.GetType() == Variant::STRING; };
967978

@@ -984,7 +995,6 @@ class DataInterpreter {
984995
// clang-format off
985996
case Register::R: R = stack.back(); stack.pop_back(); break;
986997
case Register::L: L = stack.back(); stack.pop_back(); break;
987-
case Register::C: C = stack.back(); stack.pop_back(); break;
988998
// clang-format on
989999
default: return Error(CreateString("Invalid register %d.", int(reg)));
9901000
}
@@ -1049,12 +1059,6 @@ class DataInterpreter {
10491059
R = Variant(L.Get<double>() != R.Get<double>());
10501060
}
10511061
break;
1052-
case Instruction::Ternary:
1053-
{
1054-
if (L.Get<bool>())
1055-
R = C;
1056-
}
1057-
break;
10581062
case Instruction::NumArguments:
10591063
{
10601064
const int num_arguments = data.Get<int>(-1);
@@ -1107,6 +1111,17 @@ class DataInterpreter {
11071111
R = tmp;
11081112
}
11091113
break;
1114+
case Instruction::JumpIfZero:
1115+
{
1116+
if (!R.Get<bool>())
1117+
next_instruction = data.Get<size_t>(0);
1118+
}
1119+
break;
1120+
case Instruction::Jump:
1121+
{
1122+
next_instruction = data.Get<size_t>(0);
1123+
}
1124+
break;
11101125
default: RMLUI_ERRORMSG("Instruction not implemented."); break;
11111126
}
11121127
return true;

Tests/Source/UnitTests/DataExpression.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,15 @@ TEST_CASE("Data expressions")
9393
int num_trolls = 1;
9494
String color_name = "color";
9595
Colourb color_value = Colourb(180, 100, 255);
96+
std::vector<String> num_multi = {"left", "right"};
9697

9798
DataModelConstructor constructor(&model);
99+
constructor.RegisterArray<std::vector<String>>();
100+
98101
constructor.Bind("radius", &radius);
99102
constructor.Bind("color_name", &color_name);
100103
constructor.Bind("num_trolls", &num_trolls);
104+
constructor.Bind("num_multi", &num_multi);
101105
constructor.BindFunc("color_value", [&](Variant& variant) { variant = ToString(color_value); });
102106

103107
constructor.RegisterTransformFunc("concatenate", [](const VariantList& arguments) -> Variant {
@@ -192,4 +196,8 @@ TEST_CASE("Data expressions")
192196
handle.DirtyVariable("num_trolls");
193197
CHECK(TestExpression("concatenate('It takes', num_trolls*3 + ' goats', 'to outsmart', num_trolls | number_suffix('troll','trolls'))") ==
194198
"It takes,9 goats,to outsmart,3 trolls");
199+
200+
// Test that only one side of ternary is evaluated
201+
CHECK(TestExpression("true ? num_multi[0] : num_multi[999]") == "left");
202+
CHECK(TestExpression("false ? num_multi[999] : num_multi[1]") == "right");
195203
}

0 commit comments

Comments
 (0)