Add match blocks capable of matching against enum variants
This commit is contained in:
parent
062bd8abd7
commit
2918260c5a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -783,6 +783,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"pollster",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
@ -13,6 +13,7 @@ pub enum Block {
|
||||
ComponentElement(ComponentElement),
|
||||
IfBlock(IfBlock),
|
||||
ForBlock(ForBlock),
|
||||
MatchBlock(MatchBlock),
|
||||
Text(StringExpr),
|
||||
DefineExpandSlot(DefineExpandSlot),
|
||||
DefineFragment(DefineFragment),
|
||||
@ -53,6 +54,25 @@ pub struct ForBlock {
|
||||
pub loc: Locator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct MatchBlock {
|
||||
pub matchable: Expression,
|
||||
pub arms: Vec<(MatchBinding, Vec<Block>)>,
|
||||
pub loc: Locator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub enum MatchBinding {
|
||||
/// `Some(x) =>`
|
||||
TupleVariant { name: IStr, pieces: Vec<Binding> },
|
||||
|
||||
/// `None =>`
|
||||
UnitVariant { name: IStr },
|
||||
|
||||
/// `_ =>`
|
||||
Ignore,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub enum Binding {
|
||||
Variable(IStr),
|
||||
|
@ -12,6 +12,7 @@ BlockContent = _{
|
||||
Text |
|
||||
DefineExpandSlot |
|
||||
ForBlock |
|
||||
MatchBlock |
|
||||
DefineFragment
|
||||
}
|
||||
|
||||
@ -55,6 +56,25 @@ EmptyForBlock = {
|
||||
PEEK_ALL ~ "empty" ~ lineEnd ~ NewBlock
|
||||
}
|
||||
|
||||
MatchBlock = {
|
||||
"match" ~ ws+ ~ Expr ~ lineEnd ~
|
||||
PEEK_ALL ~ PUSH(" "+ | "\t"+) ~ MatchArm ~
|
||||
(PEEK_ALL ~ MatchArm)* ~ DROP
|
||||
}
|
||||
MatchArm = {
|
||||
MatchBinding ~ ws* ~ "=>" ~ lineEnd ~ NewBlock
|
||||
}
|
||||
MatchBinding = {
|
||||
MBTupleVariant | IgnoreBinding | MBUnitVariant
|
||||
}
|
||||
|
||||
MBTupleVariant = {
|
||||
Identifier ~ ws* ~ "(" ~ ws* ~ (Binding ~ ws* ~ ("," ~ ws* ~ Binding ~ ws*)*)? ~ ")"
|
||||
}
|
||||
MBUnitVariant = {
|
||||
Identifier
|
||||
}
|
||||
|
||||
DefineExpandSlot = {
|
||||
SlotOptional? ~ "slot" ~ ws+ ~ ":" ~ Identifier ~ lineEnd
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::ast::{
|
||||
Binding, Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, ForBlock,
|
||||
HtmlElement, IfBlock, StringExpr, StringPiece, Template,
|
||||
HtmlElement, IfBlock, MatchBinding, MatchBlock, StringExpr, StringPiece, Template,
|
||||
};
|
||||
use crate::{intern, IStr, Locator};
|
||||
use lazy_static::lazy_static;
|
||||
@ -374,6 +374,50 @@ impl HornbeamParser {
|
||||
}))
|
||||
}
|
||||
|
||||
fn MatchBlock(input: Node) -> PCResult<Block> {
|
||||
let loc = nodeloc(&input);
|
||||
|
||||
let (expr, arms) = match_nodes!(input.into_children();
|
||||
[Expr(expr), MatchArm(arms)..] => (expr, arms.collect())
|
||||
);
|
||||
|
||||
Ok(Block::MatchBlock(MatchBlock {
|
||||
matchable: expr,
|
||||
arms,
|
||||
loc,
|
||||
}))
|
||||
}
|
||||
|
||||
fn MatchArm(input: Node) -> PCResult<(MatchBinding, Vec<Block>)> {
|
||||
Ok(match_nodes!(input.into_children();
|
||||
[MatchBinding(binding), blocks..] => (
|
||||
binding,
|
||||
HornbeamParser::helper_blocks(blocks)?,
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn MatchBinding(input: Node) -> PCResult<MatchBinding> {
|
||||
Ok(match_nodes!(input.into_children();
|
||||
[MBTupleVariant(mb)] => mb,
|
||||
[MBUnitVariant(mb)] => mb,
|
||||
[IgnoreBinding(_)] => MatchBinding::Ignore,
|
||||
))
|
||||
}
|
||||
|
||||
fn MBTupleVariant(input: Node) -> PCResult<MatchBinding> {
|
||||
Ok(match_nodes!(input.into_children();
|
||||
[Identifier(name), Binding(bindings)..] => {
|
||||
MatchBinding::TupleVariant { name, pieces: bindings.collect() }
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
fn MBUnitVariant(input: Node) -> PCResult<MatchBinding> {
|
||||
let name = Self::Identifier(input.into_children().single()?)?;
|
||||
Ok(MatchBinding::UnitVariant { name })
|
||||
}
|
||||
|
||||
fn Binding(input: Node) -> PCResult<Binding> {
|
||||
Ok(match_nodes!(input.into_children();
|
||||
[VarBinding(b)] => b,
|
||||
@ -421,6 +465,7 @@ impl HornbeamParser {
|
||||
Rule::Text => Some(HornbeamParser::Text(input)?),
|
||||
Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?),
|
||||
Rule::ForBlock => Some(HornbeamParser::ForBlock(input)?),
|
||||
Rule::MatchBlock => Some(HornbeamParser::MatchBlock(input)?),
|
||||
Rule::DefineFragment => Some(HornbeamParser::DefineFragment(input)?),
|
||||
Rule::DefineExpandSlot => Some(HornbeamParser::DefineExpandSlot(input)?),
|
||||
Rule::EOI => None,
|
||||
@ -540,6 +585,22 @@ empty
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_blocks() {
|
||||
assert_yaml_snapshot!(parse_template(
|
||||
r#"
|
||||
if $x
|
||||
match $y
|
||||
None =>
|
||||
"None"
|
||||
Some($z) =>
|
||||
"Some(${$z})"
|
||||
"#,
|
||||
"inp"
|
||||
)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragments_and_slots() {
|
||||
assert_yaml_snapshot!(parse_template(
|
||||
|
@ -0,0 +1,53 @@
|
||||
---
|
||||
source: hornbeam_grammar/src/parser.rs
|
||||
expression: "parse_template(r#\"\nif $x\n match $y\n None =>\n \"None\"\n Some($z) =>\n \"Some(${$z})\"\n \"#,\n \"inp\").unwrap()"
|
||||
---
|
||||
blocks:
|
||||
- IfBlock:
|
||||
condition:
|
||||
Variable:
|
||||
name: x
|
||||
loc:
|
||||
filename: inp
|
||||
line: 2
|
||||
column: 4
|
||||
blocks:
|
||||
- MatchBlock:
|
||||
matchable:
|
||||
Variable:
|
||||
name: y
|
||||
loc:
|
||||
filename: inp
|
||||
line: 3
|
||||
column: 11
|
||||
arms:
|
||||
- - UnitVariant:
|
||||
name: None
|
||||
- - Text:
|
||||
pieces:
|
||||
- Literal: None
|
||||
- - TupleVariant:
|
||||
name: Some
|
||||
pieces:
|
||||
- Variable: z
|
||||
- - Text:
|
||||
pieces:
|
||||
- Literal: Some(
|
||||
- Interpolation:
|
||||
Variable:
|
||||
name: z
|
||||
loc:
|
||||
filename: inp
|
||||
line: 7
|
||||
column: 21
|
||||
- Literal: )
|
||||
loc:
|
||||
filename: inp
|
||||
line: 3
|
||||
column: 5
|
||||
else_blocks: []
|
||||
loc:
|
||||
filename: inp
|
||||
line: 2
|
||||
column: 1
|
||||
|
@ -22,6 +22,8 @@ itertools = "0.10.5"
|
||||
|
||||
pollster = "0.3.0"
|
||||
|
||||
tracing = "0.1.37"
|
||||
|
||||
[features]
|
||||
default = ["fluent"]
|
||||
fluent = ["fluent-templates"]
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::interface::{LocalisationSystem, OutputSystem};
|
||||
use crate::InterpreterError;
|
||||
use async_recursion::async_recursion;
|
||||
use bevy_reflect::{FromReflect, Reflect, ReflectRef};
|
||||
use bevy_reflect::{FromReflect, Reflect, ReflectRef, VariantType};
|
||||
use fluent_templates::lazy_static::lazy_static;
|
||||
use hornbeam_grammar::ast::Binding;
|
||||
use hornbeam_grammar::ast::{Binding, MatchBinding};
|
||||
use hornbeam_grammar::Locator;
|
||||
use hornbeam_ir::ir::{Expression, Step, StepDef, StringPiece};
|
||||
use itertools::Itertools;
|
||||
@ -13,6 +13,7 @@ use std::collections::BTreeMap;
|
||||
use std::fmt::Write;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) struct FilledSlot<'a> {
|
||||
pub scope_idx: usize,
|
||||
@ -249,7 +250,7 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
_ => {
|
||||
return Err(InterpreterError::TypeError {
|
||||
context: "For".to_string(),
|
||||
conflict: format!("not iterable reflective: {reflective:?}."),
|
||||
@ -268,6 +269,131 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
|
||||
}
|
||||
}
|
||||
}
|
||||
StepDef::Match { matchable, arms } => {
|
||||
let matchable_evaled =
|
||||
self.evaluate_expression(scope_idx, matchable, &step.locator)?;
|
||||
|
||||
for (arm_binding, arm_steps) in arms {
|
||||
// if this arm's binding matches, then bind the variable...
|
||||
match arm_binding {
|
||||
MatchBinding::UnitVariant { name } => {
|
||||
match &matchable_evaled {
|
||||
Value::Reflective(reflective) => match reflective.reflect_ref() {
|
||||
ReflectRef::Enum(reflenum) => {
|
||||
// Check if the variant name matches!
|
||||
if reflenum.variant_name() != name as &str {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("trying to `match` weird reflective: {reflective:?} vs {name} at {}", step.locator);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
warn!(
|
||||
"trying to `match` non-reflective vs {name} at {}",
|
||||
step.locator
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
MatchBinding::TupleVariant { name, pieces } => {
|
||||
match &matchable_evaled {
|
||||
Value::Reflective(reflective) => match reflective.reflect_ref() {
|
||||
ReflectRef::Enum(reflenum) => {
|
||||
// Check if the variant name matches!
|
||||
if reflenum.variant_name() != name as &str {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bind the pieces!
|
||||
if pieces.len() != reflenum.field_len() {
|
||||
return Err(InterpreterError::TypeError {
|
||||
context: "Match".to_string(),
|
||||
conflict: format!("wrong number of tuple pieces on {name}: wanted {}, found {}", pieces.len(), reflenum.field_len()),
|
||||
location: step.locator.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if reflenum.variant_type() != VariantType::Tuple {
|
||||
return Err(InterpreterError::TypeError {
|
||||
context: "Match".to_string(),
|
||||
conflict: format!(
|
||||
"not a tuple variant: {name} is a {:?}",
|
||||
reflenum.variant_type()
|
||||
),
|
||||
location: step.locator.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
for (piece, field) in
|
||||
pieces.iter().zip(reflenum.iter_fields())
|
||||
{
|
||||
// TODO duplicated code. Should probably make some 'Binder' tool that also makes it easy to unbind afterwards!
|
||||
match piece {
|
||||
Binding::Variable(var) => {
|
||||
self.scopes[scope_idx].variables.insert(
|
||||
String::from(var as &str),
|
||||
// TODO would be nice to avoid this clone!
|
||||
Value::from_reflect(
|
||||
field.value().clone_value(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Binding::Ignore => {
|
||||
// nop.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("trying to `match` weird reflective: {reflective:?} vs {name}(...) at {}", step.locator);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
warn!(
|
||||
"trying to `match` non-reflective vs {name}(...) at {}",
|
||||
step.locator
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
MatchBinding::Ignore => {
|
||||
// always matches: no variable to bind, no conditions to check!
|
||||
}
|
||||
};
|
||||
|
||||
// and then execute the instructions
|
||||
self.run_steps(scope_idx, arm_steps).await?;
|
||||
|
||||
// and then unbind the variables.
|
||||
match arm_binding {
|
||||
MatchBinding::TupleVariant { pieces, .. } => {
|
||||
for piece in pieces {
|
||||
// TODO duplicated code. Should probably make some 'Binder' tool that also makes it easy to unbind afterwards!
|
||||
match piece {
|
||||
Binding::Variable(var) => {
|
||||
self.scopes[scope_idx].variables.remove(var as &str);
|
||||
}
|
||||
Binding::Ignore => {
|
||||
// nop.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MatchBinding::UnitVariant { .. } | MatchBinding::Ignore => {
|
||||
// no variables to unbind
|
||||
}
|
||||
};
|
||||
|
||||
// (don't fall through to other arms since we matched this one)
|
||||
break;
|
||||
}
|
||||
}
|
||||
StepDef::Call { name, args, slots } => {
|
||||
let mut evaled_args = BTreeMap::new();
|
||||
for (key, expr) in args {
|
||||
|
@ -96,6 +96,13 @@ fn pull_out_entrypoints_from_block<'a>(
|
||||
pull_out_entrypoints_from_block(child, template_name, target)?;
|
||||
}
|
||||
}
|
||||
Block::MatchBlock(matchb) => {
|
||||
for (_arm_binding, arm_blocks) in &mut matchb.arms {
|
||||
for child in arm_blocks {
|
||||
pull_out_entrypoints_from_block(child, template_name, target)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Block::DefineFragment(frag) => {
|
||||
// First process children
|
||||
for child in &mut frag.blocks {
|
||||
@ -349,6 +356,26 @@ fn compile_ast_block_to_steps<'a>(
|
||||
locator: forb.loc.clone(),
|
||||
});
|
||||
}
|
||||
Block::MatchBlock(matchb) => {
|
||||
let mut arms_instrs = Vec::new();
|
||||
|
||||
for (arm_binding, arm_blocks) in &matchb.arms {
|
||||
let mut this_arm_instrs = Vec::new();
|
||||
for b in arm_blocks {
|
||||
compile_ast_block_to_steps(b, &mut this_arm_instrs)?;
|
||||
}
|
||||
|
||||
arms_instrs.push((arm_binding.clone(), this_arm_instrs));
|
||||
}
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::Match {
|
||||
matchable: matchb.matchable.clone(),
|
||||
arms: arms_instrs,
|
||||
},
|
||||
locator: matchb.loc.clone(),
|
||||
})
|
||||
}
|
||||
Block::Text(text) => {
|
||||
for piece in &text.pieces {
|
||||
match piece {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use hornbeam_grammar::ast::Binding;
|
||||
use hornbeam_grammar::ast::{Binding, MatchBinding};
|
||||
pub use hornbeam_grammar::ast::{Expression, StringPiece};
|
||||
use hornbeam_grammar::{IStr, Locator};
|
||||
use serde::Serialize;
|
||||
@ -39,6 +39,10 @@ pub enum StepDef {
|
||||
body_steps: Vec<Step>,
|
||||
empty_steps: Vec<Step>,
|
||||
},
|
||||
Match {
|
||||
matchable: Expression,
|
||||
arms: Vec<(MatchBinding, Vec<Step>)>,
|
||||
},
|
||||
Call {
|
||||
name: IStr,
|
||||
args: BTreeMap<IStr, Expression>,
|
||||
|
@ -70,6 +70,11 @@ fn apply_peephole_pass<F: Fn(&mut Vec<Step>)>(steps: &mut Vec<Step>, pass: &F) {
|
||||
apply_peephole_pass(body_steps, pass);
|
||||
apply_peephole_pass(empty_steps, pass);
|
||||
}
|
||||
StepDef::Match { arms, .. } => {
|
||||
for (_, arm_steps) in arms {
|
||||
apply_peephole_pass(arm_steps, pass);
|
||||
}
|
||||
}
|
||||
StepDef::Call { slots, .. } => {
|
||||
for slot_steps in slots.values_mut() {
|
||||
apply_peephole_pass(slot_steps, pass);
|
||||
|
Loading…
Reference in New Issue
Block a user