refactor(source): name private analysis expression kind explicitly

This commit is contained in:
2026-09-12 21:31:40 -07:00
parent 2c515cf3bd
commit 9ff6e7cf7a
3 changed files with 64 additions and 54 deletions
@@ -33,7 +33,7 @@ pub struct Argument {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Expression { pub struct Expression {
pub kind: Kind, pub kind: ExpressionKind,
pub span: Span, pub span: Span,
pub depth: usize, pub depth: usize,
pub grouped: bool, pub grouped: bool,
@@ -47,7 +47,7 @@ pub struct LiteralSyntax {
pub tokens: Vec<Span>, pub tokens: Vec<Span>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Kind { pub enum ExpressionKind {
Boolean(bool), Boolean(bool),
Number(Exact), Number(Exact),
Quantity { Quantity {
@@ -64,7 +64,7 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
let value = p.text() == "true"; let value = p.text() == "true";
let span = p.span(); let span = p.span();
p.cursor += 1; p.cursor += 1;
values.push(p.node(Kind::Boolean(value), span)?); values.push(p.node(ExpressionKind::Boolean(value), span)?);
} }
"-" => { "-" => {
p.open()?; p.open()?;
@@ -95,14 +95,18 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
))); )));
} else { } else {
let end = reference.span.end; let end = reference.span.end;
values.push(p.node(Kind::Property(reference), Span { start, end })?); values.push(
p.node(ExpressionKind::Property(reference), Span { start, end })?,
);
} }
} }
"$" => { "$" => {
p.cursor += 1; p.cursor += 1;
let reference = p.reference()?; let reference = p.reference()?;
let end = reference.span.end; let end = reference.span.end;
values.push(p.node(Kind::Parameter(reference), Span { start, end })?); values.push(
p.node(ExpressionKind::Parameter(reference), Span { start, end })?,
);
} }
"ratio" => { "ratio" => {
let (value, literal) = p.ratio()?; let (value, literal) = p.ratio()?;
@@ -184,7 +188,7 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
end: right.span.end, end: right.span.end,
}; };
values.push(p.node( values.push(p.node(
Kind::Binary { ExpressionKind::Binary {
op, op,
left: Box::new(left), left: Box::new(left),
right: Box::new(right), right: Box::new(right),
@@ -197,7 +201,7 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
let end = operand.span.end; let end = operand.span.end;
p.close(); p.close();
values.push(p.node( values.push(p.node(
Kind::Unary { ExpressionKind::Unary {
op, op,
operand: Box::new(operand), operand: Box::new(operand),
}, },
@@ -292,7 +296,7 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
let end = p.take(")")?.end; let end = p.take(")")?.end;
p.close(); p.close();
values.push(p.node( values.push(p.node(
Kind::Power { ExpressionKind::Power {
base: Box::new(base), base: Box::new(base),
exponent, exponent,
}, },
@@ -304,7 +308,7 @@ pub(super) fn parse(source: &str, tokens: &[Token], budget: &mut Budget) -> Resu
let end = p.take(")")?.end; let end = p.take(")")?.end;
p.close(); p.close();
values.push(p.node( values.push(p.node(
Kind::AsKind { ExpressionKind::AsKind {
kind, kind,
value: Box::new(value), value: Box::new(value),
}, },
@@ -441,29 +445,32 @@ impl Parser<'_, '_> {
let unit = self.reference()?; let unit = self.reference()?;
let end = self.take("]")?.end; let end = self.take("]")?.end;
self.close(); self.close();
self.node(Kind::Quantity { magnitude, unit }, Span { start, end }) self.node(
ExpressionKind::Quantity { magnitude, unit },
Span { start, end },
)
} else { } else {
self.node(Kind::Number(magnitude), Span { start, end }) self.node(ExpressionKind::Number(magnitude), Span { start, end })
}?; }?;
expression.literal = Some(literal); expression.literal = Some(literal);
Ok(expression) Ok(expression)
} }
fn node(&mut self, kind: Kind, span: Span) -> Result<Expression, Error> { fn node(&mut self, kind: ExpressionKind, span: Span) -> Result<Expression, Error> {
let child_depth = match &kind { let child_depth = match &kind {
Kind::Unary { operand, .. } => operand.depth, ExpressionKind::Unary { operand, .. } => operand.depth,
Kind::Binary { left, right, .. } => left.depth.max(right.depth), ExpressionKind::Binary { left, right, .. } => left.depth.max(right.depth),
Kind::If { ExpressionKind::If {
condition, condition,
when_true, when_true,
when_false, when_false,
} => condition.depth.max(when_true.depth).max(when_false.depth), } => condition.depth.max(when_true.depth).max(when_false.depth),
Kind::List(values) => values.iter().map(|v| v.depth).max().unwrap_or(0), ExpressionKind::List(values) => values.iter().map(|v| v.depth).max().unwrap_or(0),
Kind::Call { arguments, .. } => { ExpressionKind::Call { arguments, .. } => {
arguments.iter().map(|a| a.value.depth).max().unwrap_or(0) arguments.iter().map(|a| a.value.depth).max().unwrap_or(0)
} }
Kind::Sum { values, initial } => values.depth.max(initial.depth), ExpressionKind::Sum { values, initial } => values.depth.max(initial.depth),
Kind::Power { base, .. } => base.depth, ExpressionKind::Power { base, .. } => base.depth,
Kind::AsKind { value, .. } => value.depth, ExpressionKind::AsKind { value, .. } => value.depth,
_ => 0, _ => 0,
}; };
if child_depth >= crate::analysis::MAX_EXPRESSION_DEPTH { if child_depth >= crate::analysis::MAX_EXPRESSION_DEPTH {
@@ -490,21 +497,21 @@ impl Parser<'_, '_> {
.ok_or_else(|| error(span, "Missing required constructor argument.")) .ok_or_else(|| error(span, "Missing required constructor argument."))
}; };
let kind = match sequence.kind { let kind = match sequence.kind {
SequenceKind::If => Kind::If { SequenceKind::If => ExpressionKind::If {
condition: next()?, condition: next()?,
when_true: next()?, when_true: next()?,
when_false: next()?, when_false: next()?,
}, },
SequenceKind::Sum => Kind::Sum { SequenceKind::Sum => ExpressionKind::Sum {
values: next()?, values: next()?,
initial: next()?, initial: next()?,
}, },
SequenceKind::Unary(op) => Kind::Unary { SequenceKind::Unary(op) => ExpressionKind::Unary {
op, op,
operand: next()?, operand: next()?,
}, },
SequenceKind::List => Kind::List(values.map(|(_, value)| value).collect()), SequenceKind::List => ExpressionKind::List(values.map(|(_, value)| value).collect()),
SequenceKind::Call(definition) => Kind::Call { SequenceKind::Call(definition) => ExpressionKind::Call {
definition, definition,
arguments: values arguments: values
.map(|(parameter, value)| Argument { .map(|(parameter, value)| Argument {
@@ -529,7 +536,7 @@ fn comparison(op: BinaryOp) -> bool {
) )
} }
fn ungrouped_comparison(value: &Expression) -> bool { fn ungrouped_comparison(value: &Expression) -> bool {
!value.grouped && matches!(&value.kind, Kind::Binary { op, .. } if comparison(*op)) !value.grouped && matches!(&value.kind, ExpressionKind::Binary { op, .. } if comparison(*op))
} }
fn operator(text: &str) -> Option<(BinaryOp, u8)> { fn operator(text: &str) -> Option<(BinaryOp, u8)> {
Some(match text { Some(match text {
@@ -15,14 +15,14 @@ fn expression(source: &str) -> Expression {
} }
} }
fn number(value: &Expression) -> String { fn number(value: &Expression) -> String {
let Kind::Number(exact) = &value.kind else { let ExpressionKind::Number(exact) = &value.kind else {
panic!("expected Number"); panic!("expected Number");
}; };
exact.canonical_literal() exact.canonical_literal()
} }
fn binary_children(value: Expression, expected: BinaryOp) -> (Expression, Expression) { fn binary_children(value: Expression, expected: BinaryOp) -> (Expression, Expression) {
let Kind::Binary { op, left, right } = value.kind else { let ExpressionKind::Binary { op, left, right } = value.kind else {
panic!("expected Binary {expected:?}"); panic!("expected Binary {expected:?}");
}; };
assert_eq!(op, expected); assert_eq!(op, expected);
@@ -41,18 +41,18 @@ fn mixed_precedence_and_grouping_have_independent_expected_child_topology() {
let (conjunction, last) = let (conjunction, last) =
binary_children(expression("not(false) and true or false"), BinaryOp::Or); binary_children(expression("not(false) and true or false"), BinaryOp::Or);
assert_eq!(last.kind, Kind::Boolean(false)); assert_eq!(last.kind, ExpressionKind::Boolean(false));
let (negated, middle) = binary_children(conjunction, BinaryOp::And); let (negated, middle) = binary_children(conjunction, BinaryOp::And);
assert_eq!(middle.kind, Kind::Boolean(true)); assert_eq!(middle.kind, ExpressionKind::Boolean(true));
let Kind::Unary { op, operand } = negated.kind else { let ExpressionKind::Unary { op, operand } = negated.kind else {
panic!("expected Not"); panic!("expected Not");
}; };
assert_eq!(op, UnaryOp::Not); assert_eq!(op, UnaryOp::Not);
assert_eq!(operand.kind, Kind::Boolean(false)); assert_eq!(operand.kind, ExpressionKind::Boolean(false));
let (negative, two) = binary_children(expression("-1 * 2"), BinaryOp::Multiply); let (negative, two) = binary_children(expression("-1 * 2"), BinaryOp::Multiply);
assert_eq!(number(&two), "2"); assert_eq!(number(&two), "2");
let Kind::Unary { op, operand } = negative.kind else { let ExpressionKind::Unary { op, operand } = negative.kind else {
panic!("expected Negate"); panic!("expected Negate");
}; };
assert_eq!(op, UnaryOp::Negate); assert_eq!(op, UnaryOp::Negate);
@@ -85,10 +85,10 @@ fn flat_binary_depth_and_malformed_constructor_retain_bounded_completed_children
// Independently verify the complete left-associated shape, not just depth. // Independently verify the complete left-associated shape, not just depth.
for _ in 1..128 { for _ in 1..128 {
let (left, right) = binary_children(tree, BinaryOp::And); let (left, right) = binary_children(tree, BinaryOp::And);
assert_eq!(right.kind, Kind::Boolean(true)); assert_eq!(right.kind, ExpressionKind::Boolean(true));
tree = left; tree = left;
} }
assert_eq!(tree.kind, Kind::Boolean(true)); assert_eq!(tree.kind, ExpressionKind::Boolean(true));
assert!( assert!(
parse(&flat(129), &mut budget()) parse(&flat(129), &mut budget())
.unwrap_err() .unwrap_err()
@@ -129,7 +129,7 @@ fn significant_ratio_and_quantity_trivia_preserve_original_spans() {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
["ratio", "(", "-", "3", ",", "4", ")"] ["ratio", "(", "-", "3", ",", "4", ")"]
); );
let Kind::Quantity { magnitude, unit } = parsed.kind else { let ExpressionKind::Quantity { magnitude, unit } = parsed.kind else {
panic!("expected Quantity"); panic!("expected Quantity");
}; };
assert_eq!(magnitude.canonical_literal(), "ratio(-3, 4)"); assert_eq!(magnitude.canonical_literal(), "ratio(-3, 4)");
@@ -192,14 +192,14 @@ fn unary_literals_precedence_and_comparison_children_keep_distinct_trees() {
let negative = expression("-(1)"); let negative = expression("-(1)");
assert!(matches!( assert!(matches!(
negative.kind, negative.kind,
Kind::Unary { ExpressionKind::Unary {
op: UnaryOp::Negate, op: UnaryOp::Negate,
.. ..
} }
)); ));
let left = expression("(1 - 2) - 3"); let left = expression("(1 - 2) - 3");
let right = expression("1 - (2 - 3)"); let right = expression("1 - (2 - 3)");
let Kind::Binary { let ExpressionKind::Binary {
left, right: last, .. left, right: last, ..
} = left.kind } = left.kind
else { else {
@@ -207,13 +207,13 @@ fn unary_literals_precedence_and_comparison_children_keep_distinct_trees() {
}; };
assert!(matches!( assert!(matches!(
left.kind, left.kind,
Kind::Binary { ExpressionKind::Binary {
op: BinaryOp::Subtract, op: BinaryOp::Subtract,
.. ..
} }
)); ));
assert_eq!(number(&last), "3"); assert_eq!(number(&last), "3");
let Kind::Binary { let ExpressionKind::Binary {
left: first, right, .. left: first, right, ..
} = right.kind } = right.kind
else { else {
@@ -222,7 +222,7 @@ fn unary_literals_precedence_and_comparison_children_keep_distinct_trees() {
assert_eq!(number(&first), "1"); assert_eq!(number(&first), "1");
assert!(matches!( assert!(matches!(
right.kind, right.kind,
Kind::Binary { ExpressionKind::Binary {
op: BinaryOp::Subtract, op: BinaryOp::Subtract,
.. ..
} }
@@ -249,7 +249,7 @@ fn unary_literals_precedence_and_comparison_children_keep_distinct_trees() {
#[test] #[test]
fn every_expression_shape_and_exact_constructor_arity_is_represented() { fn every_expression_shape_and_exact_constructor_arity_is_represented() {
let tree = expression("if(true, sum([@p, $x], 0), as_kind(@duration, power(abs(-(2)), -2)))"); let tree = expression("if(true, sum([@p, $x], 0), as_kind(@duration, power(abs(-(2)), -2)))");
let Kind::If { let ExpressionKind::If {
condition, condition,
when_true, when_true,
when_false, when_false,
@@ -257,24 +257,27 @@ fn every_expression_shape_and_exact_constructor_arity_is_represented() {
else { else {
panic!(); panic!();
}; };
assert_eq!(condition.kind, Kind::Boolean(true)); assert_eq!(condition.kind, ExpressionKind::Boolean(true));
let Kind::Sum { values, initial } = when_true.kind else { let ExpressionKind::Sum { values, initial } = when_true.kind else {
panic!(); panic!();
}; };
assert_eq!(number(&initial), "0"); assert_eq!(number(&initial), "0");
let Kind::List(values) = values.kind else { let ExpressionKind::List(values) = values.kind else {
panic!(); panic!();
}; };
assert!(matches!(&values[0].kind, Kind::Property(r) if r.name == "p")); assert!(matches!(&values[0].kind, ExpressionKind::Property(r) if r.name == "p"));
assert!(matches!(&values[1].kind, Kind::Parameter(r) if r.name == "x")); assert!(matches!(&values[1].kind, ExpressionKind::Parameter(r) if r.name == "x"));
let Kind::AsKind { kind, value } = when_false.kind else { let ExpressionKind::AsKind { kind, value } = when_false.kind else {
panic!(); panic!();
}; };
assert_eq!(kind.name, "duration"); assert_eq!(kind.name, "duration");
assert!(matches!(value.kind, Kind::Power { exponent: -2, .. })); assert!(matches!(
assert!(matches!(expression("[]").kind, Kind::List(v) if v.is_empty())); value.kind,
ExpressionKind::Power { exponent: -2, .. }
));
assert!(matches!(expression("[]").kind, ExpressionKind::List(v) if v.is_empty()));
assert!( assert!(
matches!(expression("@zero()").kind, Kind::Call { arguments, .. } if arguments.is_empty()) matches!(expression("@zero()").kind, ExpressionKind::Call { arguments, .. } if arguments.is_empty())
); );
assert!(matches!( assert!(matches!(
parse(" unbound // pending", &mut budget()).unwrap(), parse(" unbound // pending", &mut budget()).unwrap(),
@@ -301,17 +304,17 @@ fn every_expression_shape_and_exact_constructor_arity_is_represented() {
fn repeated_call_argument_occurrences_have_distinct_original_spans() { fn repeated_call_argument_occurrences_have_distinct_original_spans() {
let source = "@combine(a: @p, b: 2) + @combine(a: @q, b: 4)"; let source = "@combine(a: @p, b: 2) + @combine(a: @q, b: 4)";
let tree = expression(source); let tree = expression(source);
let Kind::Binary { left, right, .. } = tree.kind else { let ExpressionKind::Binary { left, right, .. } = tree.kind else {
panic!(); panic!();
}; };
let Kind::Call { let ExpressionKind::Call {
definition: first, definition: first,
arguments: a, arguments: a,
} = left.kind } = left.kind
else { else {
panic!(); panic!();
}; };
let Kind::Call { let ExpressionKind::Call {
definition: second, definition: second,
arguments: b, arguments: b,
} = right.kind } = right.kind