Skip to content

Commit be76d76

Browse files
committed
fix: Complex chained comparison compile into broken code
Squashed commit of the following: commit 12c5908629226cefc2bfca43d3a6c78ccbdae02c Author: Jan Max Meyer <[email protected]> Date: Sat Dec 28 16:39:11 2024 +0100 Adding testcases commit 5909fff Author: Jan Max Meyer <[email protected]> Date: Fri Dec 27 23:01:46 2024 +0100 Fixing chained comparison using ImlOp::If commit 38b5a95 Author: Jan Max Meyer <[email protected]> Date: Fri Dec 27 20:11:06 2024 +0100 Improve comparison-ast-traversal
1 parent a0e249d commit be76d76

File tree

3 files changed

+76
-33
lines changed

3 files changed

+76
-33
lines changed

src/compiler/ast.rs

+55-33
Original file line numberDiff line numberDiff line change
@@ -1022,10 +1022,14 @@ fn traverse_node(scope: &Scope, node: &Dict) -> ImlOp {
10221022
// comparison -----------------------------------------------------
10231023
"comparison" => {
10241024
// comparison can be a chain of comparisons, allowing to compare e.g. `1 < 2 < 3`
1025+
let mut branches = Vec::new();
1026+
10251027
let children = node["children"].borrow();
1026-
let mut children = children.object::<List>().unwrap().clone();
1028+
let children = children.object::<List>().unwrap();
1029+
1030+
let mut child_iter = children.iter().peekable();
10271031

1028-
let first = children.remove(0);
1032+
let first = child_iter.next().unwrap();
10291033
let first = first.borrow();
10301034

10311035
let mut ops = Vec::new();
@@ -1036,28 +1040,37 @@ fn traverse_node(scope: &Scope, node: &Dict) -> ImlOp {
10361040
Rvalue::CallOrLoad,
10371041
));
10381042

1039-
let mut backpatch = Vec::new();
1043+
while let Some(op) = child_iter.next() {
1044+
let op = op.borrow();
1045+
let op = op.object::<Dict>().unwrap();
10401046

1041-
while !children.is_empty() {
1042-
let child = children.remove(0);
1043-
let child = child.borrow();
1044-
let child = child.object::<Dict>().unwrap();
1045-
1046-
let emit = child["emit"].borrow();
1047+
let emit = op["emit"].borrow();
10471048
let emit = emit.object::<Str>().unwrap().as_str();
10481049

1049-
let next = child["children"].borrow();
1050+
let next = op["children"].borrow();
10501051

1051-
ops.push(traverse_node_rvalue(
1052-
scope,
1053-
&next.object::<Dict>().unwrap(),
1054-
Rvalue::CallOrLoad,
1055-
));
1052+
// Old (current) path
1053+
if let Some(next) = next.object::<Dict>() {
1054+
ops.push(traverse_node_rvalue(scope, &next, Rvalue::CallOrLoad));
1055+
}
1056+
// New (desired) path
1057+
else {
1058+
let next = child_iter.next().unwrap();
1059+
let next = next.borrow();
10561060

1057-
// Chained comparison requires forperand duplication
1058-
if !children.is_empty() {
1061+
ops.push(traverse_node_rvalue(
1062+
scope,
1063+
&next.object::<Dict>().unwrap(),
1064+
Rvalue::CallOrLoad,
1065+
));
1066+
}
1067+
1068+
let is_chained = child_iter.peek().is_some();
1069+
1070+
// Chained comparison requires for operand duplication
1071+
if is_chained {
10591072
ops.push(ImlOp::from(Op::Swap(2))); // Swap operands
1060-
ops.push(ImlOp::from(Op::Copy(2))); // Copy second operand
1073+
ops.push(ImlOp::from(Op::Copy(2))); // Copy second operand, to keep a copy
10611074
}
10621075

10631076
ops.push(ImlOp::from(match emit {
@@ -1070,25 +1083,34 @@ fn traverse_node(scope: &Scope, node: &Dict) -> ImlOp {
10701083
_ => unimplemented!("{}", emit),
10711084
}));
10721085

1073-
// Push and remember placeholder for later clean-up jump
1074-
if !children.is_empty() {
1075-
backpatch.push(ops.len());
1076-
ops.push(ImlOp::Nop); // Placeholder for condition
1077-
}
1086+
// Push this branch
1087+
branches.push(ops);
1088+
ops = Vec::new();
10781089
}
10791090

1080-
if backpatch.len() > 0 {
1081-
// Jump over clean-up part with last result
1082-
ops.push(ImlOp::from(Op::Forward(3)));
1091+
let mut branches = branches.into_iter().rev().peekable();
1092+
1093+
while let Some(mut branch) = branches.next() {
1094+
// println!("{:?} {:?}", branch, branches.peek().is_none());
1095+
branch.extend(ops);
1096+
ops = branch;
10831097

1084-
// Otherwise, remember clean-up start
1085-
let clean_up = ops.len();
1086-
ops.push(ImlOp::from(Op::Drop));
1087-
ops.push(ImlOp::from(Op::PushFalse));
1098+
if branches.peek().is_some() {
1099+
let then: Vec<_> = ops.drain(..).collect();
10881100

1089-
// Backpatch all placeholders to relative jump to the clean-up part
1090-
for index in backpatch {
1091-
ops[index] = ImlOp::from(Op::ForwardIfFalse(clean_up - index + 1));
1101+
ops.push(ImlOp::If {
1102+
peek: false,
1103+
test: true,
1104+
then: Box::new(if then.len() == 0 {
1105+
ImlOp::from(Op::PushTrue)
1106+
} else {
1107+
ImlOp::from(then)
1108+
}),
1109+
else_: Box::new(ImlOp::from(vec![
1110+
ImlOp::from(Op::Drop),
1111+
ImlOp::from(Op::PushFalse),
1112+
])),
1113+
})
10921114
}
10931115
}
10941116

tests/comparison.tok

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
1 >= 1 > 2
1111
2 >= 1 > 0
1212

13+
# complex chained comparison
14+
l = 1, 2, 3
15+
l[0] > l[1] > l[2]
16+
l[0] < l[1] < l[2]
17+
1318
#---
1419

1520
#false
@@ -22,3 +27,6 @@
2227
#true
2328
#false
2429
#true
30+
31+
#false
32+
#true

tests/comparison_captures.tok

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Testcase for chained comparions from capture.
2+
3+
_ : ' '
4+
5+
(Int _)+ print($1[0] < $1[1] < $1[2])
6+
7+
#---
8+
#6 5 4
9+
#4 5 6
10+
11+
#---
12+
#false
13+
#true

0 commit comments

Comments
 (0)