tamer: NIR->xmli: Basic match support
This introduces `<match on="foo" />` and `<match on="foo" value="bar" />`, which are both equality predicates. Other types of predicates are not yet supported. This change is a bit messy and leaves a bit to be desired. `NirToAir` is quite messy and needs some cleanup. There's also the issue of introducing XML-specific errors in NIR so that users know what things like "subject" mean, but not being able to do so yet because NIR is agnostic to the source document type; another layer of abstraction is needed. But, my priority is first to get derivation of a particularly expensive (generated) package in our internal systems working first. DEV-13708main
parent
1f2ead7f9b
commit
82e228009d
|
@ -41,7 +41,7 @@ use super::ObjectKind;
|
||||||
/// all child expressions,
|
/// all child expressions,
|
||||||
/// but also any applicable closing span.
|
/// but also any applicable closing span.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Expr(ExprOp, ExprDim, Span);
|
pub struct Expr(pub ExprOp, ExprDim, Span);
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
pub fn new(op: ExprOp, span: Span) -> Self {
|
pub fn new(op: ExprOp, span: Span) -> Self {
|
||||||
|
@ -102,12 +102,15 @@ pub enum ExprOp {
|
||||||
Conj,
|
Conj,
|
||||||
/// Logical disjunction (∨)
|
/// Logical disjunction (∨)
|
||||||
Disj,
|
Disj,
|
||||||
|
/// Equality predicate (=)
|
||||||
|
Eq,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ExprOp {
|
impl Display for ExprOp {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
use ExprOp::*;
|
use ExprOp::*;
|
||||||
|
|
||||||
|
// "{self} expression"
|
||||||
match self {
|
match self {
|
||||||
Sum => write!(f, "sum (+)"),
|
Sum => write!(f, "sum (+)"),
|
||||||
Product => write!(f, "product (×)"),
|
Product => write!(f, "product (×)"),
|
||||||
|
@ -115,6 +118,7 @@ impl Display for ExprOp {
|
||||||
Floor => write!(f, "floor (⌊)"),
|
Floor => write!(f, "floor (⌊)"),
|
||||||
Conj => write!(f, "conjunctive (∧)"),
|
Conj => write!(f, "conjunctive (∧)"),
|
||||||
Disj => write!(f, "disjunctive (∨)"),
|
Disj => write!(f, "disjunctive (∨)"),
|
||||||
|
Eq => write!(f, "equality (=)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,8 +191,8 @@ impl<'a> TreeContext<'a> {
|
||||||
depth,
|
depth,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Object::Expr((expr, _)) => {
|
Object::Expr((expr, oi_expr)) => {
|
||||||
self.emit_expr(expr, paired_rel.source(), depth)
|
self.emit_expr(expr, *oi_expr, paired_rel.source(), depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::Tpl((tpl, oi_tpl)) => {
|
Object::Tpl((tpl, oi_tpl)) => {
|
||||||
|
@ -243,6 +243,7 @@ impl<'a> TreeContext<'a> {
|
||||||
fn emit_expr(
|
fn emit_expr(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
|
oi_expr: ObjectIndex<Expr>,
|
||||||
src: &Object<OiPairObjectInner>,
|
src: &Object<OiPairObjectInner>,
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
) -> Option<Xirf> {
|
) -> Option<Xirf> {
|
||||||
|
@ -250,7 +251,25 @@ impl<'a> TreeContext<'a> {
|
||||||
Object::Ident((ident, _)) => {
|
Object::Ident((ident, _)) => {
|
||||||
self.emit_expr_ident(expr, ident, depth)
|
self.emit_expr_ident(expr, ident, depth)
|
||||||
}
|
}
|
||||||
_ => Some(expr_ele(expr, depth)),
|
Object::Expr((pexpr, _)) => match (pexpr.op(), expr.op()) {
|
||||||
|
(ExprOp::Conj | ExprOp::Disj, ExprOp::Eq) => {
|
||||||
|
Some(self.emit_match(expr, oi_expr, depth))
|
||||||
|
}
|
||||||
|
_ => Some(expr_ele(expr, oi_expr, depth)),
|
||||||
|
},
|
||||||
|
// TODO: See `:tamer/tests/xmli/template` regarding `match` and
|
||||||
|
// `when`/`c:*`;
|
||||||
|
// this is not an ambiguity that can be resolved without
|
||||||
|
// adding more information to the graph,
|
||||||
|
// but is hopefully one that we can avoid.
|
||||||
|
Object::Tpl(..) => match expr.op() {
|
||||||
|
ExprOp::Eq => Some(self.emit_match(expr, oi_expr, depth)),
|
||||||
|
_ => Some(expr_ele(expr, oi_expr, depth)),
|
||||||
|
},
|
||||||
|
// TODO: Perhaps errors for Root and Meta?
|
||||||
|
Object::Root(_) | Object::Pkg(_) | Object::Meta(_) => {
|
||||||
|
Some(expr_ele(expr, oi_expr, depth))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +287,11 @@ impl<'a> TreeContext<'a> {
|
||||||
ExprOp::Sum => (QN_RATE, QN_YIELDS),
|
ExprOp::Sum => (QN_RATE, QN_YIELDS),
|
||||||
ExprOp::Conj => (QN_CLASSIFY, QN_AS),
|
ExprOp::Conj => (QN_CLASSIFY, QN_AS),
|
||||||
|
|
||||||
ExprOp::Product | ExprOp::Ceil | ExprOp::Floor | ExprOp::Disj => {
|
ExprOp::Product
|
||||||
|
| ExprOp::Ceil
|
||||||
|
| ExprOp::Floor
|
||||||
|
| ExprOp::Disj
|
||||||
|
| ExprOp::Eq => {
|
||||||
todo!("stmt: {expr:?}")
|
todo!("stmt: {expr:?}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -283,6 +306,47 @@ impl<'a> TreeContext<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit a match expression.
|
||||||
|
///
|
||||||
|
/// This is intended as a classify/any/all child.
|
||||||
|
fn emit_match(
|
||||||
|
&mut self,
|
||||||
|
expr: &Expr,
|
||||||
|
oi_expr: ObjectIndex<Expr>,
|
||||||
|
depth: Depth,
|
||||||
|
) -> Xirf {
|
||||||
|
let mut edges = oi_expr.edges_filtered::<Ident>(self.asg);
|
||||||
|
|
||||||
|
// note: the edges are reversed (TODO?)
|
||||||
|
let value = edges
|
||||||
|
.next()
|
||||||
|
.diagnostic_expect(
|
||||||
|
|| vec![oi_expr.note("for this match")],
|
||||||
|
"missing @value ref",
|
||||||
|
)
|
||||||
|
.resolve(self.asg);
|
||||||
|
|
||||||
|
let on = edges
|
||||||
|
.next()
|
||||||
|
.diagnostic_expect(
|
||||||
|
|| vec![oi_expr.note("for this match")],
|
||||||
|
"missing @on ref",
|
||||||
|
)
|
||||||
|
.resolve(self.asg);
|
||||||
|
|
||||||
|
if let Some(unexpected) = edges.next() {
|
||||||
|
diagnostic_panic!(
|
||||||
|
vec![unexpected.error("a third ref was unexpected")],
|
||||||
|
"unexpected third ref during match generation",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.push(attr_value(value.name()));
|
||||||
|
self.push(attr_on(on.name()));
|
||||||
|
|
||||||
|
Xirf::open(QN_MATCH, OpenSpan::without_name_span(expr.span()), depth)
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit a template definition or application.
|
/// Emit a template definition or application.
|
||||||
fn emit_template(
|
fn emit_template(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -450,11 +514,15 @@ fn attr_name(name: SPair) -> Xirf {
|
||||||
Xirf::attr(QN_NAME, name, (name.span(), name.span()))
|
Xirf::attr(QN_NAME, name, (name.span(), name.span()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attr_on(on: SPair) -> Xirf {
|
||||||
|
Xirf::attr(QN_ON, on, (on.span(), on.span()))
|
||||||
|
}
|
||||||
|
|
||||||
fn attr_value(val: SPair) -> Xirf {
|
fn attr_value(val: SPair) -> Xirf {
|
||||||
Xirf::attr(QN_VALUE, val, (val.span(), val.span()))
|
Xirf::attr(QN_VALUE, val, (val.span(), val.span()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_ele(expr: &Expr, depth: Depth) -> Xirf {
|
fn expr_ele(expr: &Expr, oi_expr: ObjectIndex<Expr>, depth: Depth) -> Xirf {
|
||||||
use ExprOp::*;
|
use ExprOp::*;
|
||||||
|
|
||||||
let qname = match expr.op() {
|
let qname = match expr.op() {
|
||||||
|
@ -464,6 +532,11 @@ fn expr_ele(expr: &Expr, depth: Depth) -> Xirf {
|
||||||
Floor => QN_C_FLOOR,
|
Floor => QN_C_FLOOR,
|
||||||
Conj => QN_ALL,
|
Conj => QN_ALL,
|
||||||
Disj => QN_ANY,
|
Disj => QN_ANY,
|
||||||
|
|
||||||
|
Eq => diagnostic_panic!(
|
||||||
|
vec![oi_expr.error("unsupported expression type in this context")],
|
||||||
|
"cannot derive expression of this type in this context",
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
Xirf::open(qname, OpenSpan::without_name_span(expr.span()), depth)
|
Xirf::open(qname, OpenSpan::without_name_span(expr.span()), depth)
|
||||||
|
|
|
@ -228,6 +228,8 @@ pub enum NirEntity {
|
||||||
All,
|
All,
|
||||||
/// Disjunctive (∨) expression.
|
/// Disjunctive (∨) expression.
|
||||||
Any,
|
Any,
|
||||||
|
/// Logical predicate.
|
||||||
|
Match,
|
||||||
|
|
||||||
/// Template.
|
/// Template.
|
||||||
Tpl,
|
Tpl,
|
||||||
|
@ -275,6 +277,7 @@ impl Display for NirEntity {
|
||||||
Classify => write!(f, "classification"),
|
Classify => write!(f, "classification"),
|
||||||
All => write!(f, "conjunctive (∧) expression"),
|
All => write!(f, "conjunctive (∧) expression"),
|
||||||
Any => write!(f, "disjunctive (∨) expression"),
|
Any => write!(f, "disjunctive (∨) expression"),
|
||||||
|
Match => write!(f, "logical predicate (match)"),
|
||||||
|
|
||||||
Tpl => write!(f, "template"),
|
Tpl => write!(f, "template"),
|
||||||
TplParam => write!(f, "template param (metavariable)"),
|
TplParam => write!(f, "template param (metavariable)"),
|
||||||
|
|
|
@ -21,8 +21,14 @@
|
||||||
|
|
||||||
use super::Nir;
|
use super::Nir;
|
||||||
use crate::{
|
use crate::{
|
||||||
asg::air::Air, diagnose::Diagnostic, parse::prelude::*, span::UNKNOWN_SPAN,
|
asg::air::Air,
|
||||||
|
diagnose::{Annotate, Diagnostic},
|
||||||
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
|
parse::prelude::*,
|
||||||
|
span::{Span, UNKNOWN_SPAN},
|
||||||
|
sym::{st::raw::U_TRUE, SymbolId},
|
||||||
};
|
};
|
||||||
|
use arrayvec::ArrayVec;
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
// These are also used by the `test` module which imports `super`.
|
// These are also used by the `test` module which imports `super`.
|
||||||
|
@ -36,6 +42,13 @@ use crate::{
|
||||||
pub enum NirToAir {
|
pub enum NirToAir {
|
||||||
#[default]
|
#[default]
|
||||||
Ready,
|
Ready,
|
||||||
|
|
||||||
|
/// Predicate opened but its subject is not yet known.
|
||||||
|
PredOpen(Span),
|
||||||
|
|
||||||
|
/// A predicate has been partially applied to its subject,
|
||||||
|
/// but we do not yet know its function or comparison value.
|
||||||
|
PredPartial(Span, SPair),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for NirToAir {
|
impl Display for NirToAir {
|
||||||
|
@ -44,17 +57,30 @@ impl Display for NirToAir {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Ready => write!(f, "ready to lower NIR to AIR"),
|
Ready => write!(f, "ready to lower NIR to AIR"),
|
||||||
|
PredOpen(_) => {
|
||||||
|
write!(f, "awaiting information about open predicate")
|
||||||
|
}
|
||||||
|
PredPartial(_, name) => write!(
|
||||||
|
f,
|
||||||
|
"waiting to determine type of predicate for identifier {}",
|
||||||
|
TtQuote::wrap(name),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueuedObj = Option<Air>;
|
/// Stack of [`Air`] objects to yield on subsequent iterations.
|
||||||
|
type ObjStack = ArrayVec<Air, 2>;
|
||||||
|
|
||||||
|
/// The symbol to use when lexically expanding shorthand notations to
|
||||||
|
/// compare against values of `1`.
|
||||||
|
pub const SYM_TRUE: SymbolId = U_TRUE;
|
||||||
|
|
||||||
impl ParseState for NirToAir {
|
impl ParseState for NirToAir {
|
||||||
type Token = Nir;
|
type Token = Nir;
|
||||||
type Object = Air;
|
type Object = Air;
|
||||||
type Error = NirToAirError;
|
type Error = NirToAirError;
|
||||||
type Context = QueuedObj;
|
type Context = ObjStack;
|
||||||
|
|
||||||
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
#[cfg(not(feature = "wip-asg-derived-xmli"))]
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
|
@ -72,14 +98,14 @@ impl ParseState for NirToAir {
|
||||||
fn parse_token(
|
fn parse_token(
|
||||||
self,
|
self,
|
||||||
tok: Self::Token,
|
tok: Self::Token,
|
||||||
queue: &mut Self::Context,
|
stack: &mut Self::Context,
|
||||||
) -> TransitionResult<Self::Super> {
|
) -> TransitionResult<Self::Super> {
|
||||||
use NirToAir::*;
|
use NirToAir::*;
|
||||||
|
use NirToAirError::*;
|
||||||
|
|
||||||
use crate::{diagnose::Annotate, diagnostic_panic};
|
use crate::diagnostic_panic;
|
||||||
|
|
||||||
// Single-item "queue".
|
if let Some(obj) = stack.pop() {
|
||||||
if let Some(obj) = queue.take() {
|
|
||||||
return Transition(Ready).ok(obj).with_lookahead(tok);
|
return Transition(Ready).ok(obj).with_lookahead(tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +137,41 @@ impl ParseState for NirToAir {
|
||||||
Transition(Ready).ok(Air::ExprStart(ExprOp::Disj, span))
|
Transition(Ready).ok(Air::ExprStart(ExprOp::Disj, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match
|
||||||
|
(Ready, Open(Match, span)) => {
|
||||||
|
Transition(PredOpen(span)).incomplete()
|
||||||
|
}
|
||||||
|
(PredOpen(ospan), RefSubject(on)) => {
|
||||||
|
Transition(PredPartial(ospan, on)).incomplete()
|
||||||
|
}
|
||||||
|
(PredPartial(ospan, on), Ref(value)) => {
|
||||||
|
stack.push(Air::RefIdent(value));
|
||||||
|
stack.push(Air::RefIdent(on));
|
||||||
|
Transition(Ready).ok(Air::ExprStart(ExprOp::Eq, ospan))
|
||||||
|
}
|
||||||
|
(PredPartial(ospan, on), Close(Match, cspan)) => {
|
||||||
|
stack.push(Air::RefIdent(SPair(SYM_TRUE, ospan)));
|
||||||
|
stack.push(Air::RefIdent(on));
|
||||||
|
Transition(Ready)
|
||||||
|
.ok(Air::ExprStart(ExprOp::Eq, ospan))
|
||||||
|
.with_lookahead(Close(Match, cspan))
|
||||||
|
}
|
||||||
|
// Special case of the general error below,
|
||||||
|
// since recovery here involves discarding the nonsense match.
|
||||||
|
(PredOpen(ospan), Close(Match, span)) => Transition(Ready)
|
||||||
|
.err(MatchSubjectExpected(ospan, Close(Match, span))),
|
||||||
|
(PredOpen(ospan), tok) => Transition(PredOpen(ospan))
|
||||||
|
.err(MatchSubjectExpected(ospan, tok)),
|
||||||
|
(Ready, Close(Match, cspan)) => {
|
||||||
|
Transition(Ready).ok(Air::ExprEnd(cspan))
|
||||||
|
}
|
||||||
|
(PredPartial(ospan, on), tok) => {
|
||||||
|
// TODO: Until match body is supported,
|
||||||
|
// error and discard tokens.
|
||||||
|
Transition(PredPartial(ospan, on))
|
||||||
|
.err(TodoMatchBody(ospan, tok.span()))
|
||||||
|
}
|
||||||
|
|
||||||
(Ready, Open(Tpl, span)) => {
|
(Ready, Open(Tpl, span)) => {
|
||||||
Transition(Ready).ok(Air::TplStart(span))
|
Transition(Ready).ok(Air::TplStart(span))
|
||||||
}
|
}
|
||||||
|
@ -183,14 +244,23 @@ impl ParseState for NirToAir {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
fn is_accepting(&self, stack: &Self::Context) -> bool {
|
||||||
true
|
matches!(self, Self::Ready) && stack.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum NirToAirError {
|
pub enum NirToAirError {
|
||||||
Todo,
|
/// Expected a match subject,
|
||||||
|
/// but encountered some other token.
|
||||||
|
///
|
||||||
|
/// TODO: "match subject" is not familiar terminology to the user;
|
||||||
|
/// we'll want to introduce a layer permitting XML-specific augmenting
|
||||||
|
/// with `@on` when derived from an XML source.
|
||||||
|
MatchSubjectExpected(Span, Nir),
|
||||||
|
|
||||||
|
/// Match body is not yet supported.
|
||||||
|
TodoMatchBody(Span, Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for NirToAirError {
|
impl Display for NirToAirError {
|
||||||
|
@ -198,17 +268,40 @@ impl Display for NirToAirError {
|
||||||
use NirToAirError::*;
|
use NirToAirError::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Todo => write!(f, "TODO"),
|
MatchSubjectExpected(_, nir) => {
|
||||||
|
write!(f, "expected match subject, found {nir}")
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoMatchBody(_, _) => {
|
||||||
|
write!(f, "match body is not yet supported by TAMER")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for NirToAirError {}
|
impl Error for NirToAirError {}
|
||||||
|
|
||||||
|
// TODO: We need to be able to augment with useful context,
|
||||||
|
// e.g. XML suggestions.
|
||||||
impl Diagnostic for NirToAirError {
|
impl Diagnostic for NirToAirError {
|
||||||
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
|
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
|
||||||
// TODO
|
use NirToAirError::*;
|
||||||
vec![]
|
|
||||||
|
match self {
|
||||||
|
MatchSubjectExpected(ospan, given) => vec![
|
||||||
|
ospan.note("for this match"),
|
||||||
|
given
|
||||||
|
.span()
|
||||||
|
.error("comparison value provided before subject"),
|
||||||
|
],
|
||||||
|
|
||||||
|
TodoMatchBody(ospan, given) => vec![
|
||||||
|
ospan.note("for this match"),
|
||||||
|
given.error(
|
||||||
|
"tokens in match body are not yet supported by TAMER",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::{parse::util::SPair, span::dummy::*};
|
||||||
|
|
||||||
type Sut = NirToAir;
|
type Sut = NirToAir;
|
||||||
|
|
||||||
use Parsed::Object as O;
|
use Parsed::{Incomplete, Object as O};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn package_to_pkg() {
|
fn package_to_pkg() {
|
||||||
|
@ -213,3 +213,133 @@ fn apply_template_long_form_args() {
|
||||||
Sut::parse(toks.into_iter()).collect(),
|
Sut::parse(toks.into_iter()).collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Short-hand matches with no value desugar to an equality check
|
||||||
|
// against `TRUE`.
|
||||||
|
#[test]
|
||||||
|
fn match_short_no_value() {
|
||||||
|
let name = SPair("matchOn".into(), S2);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Open(Match, S1),
|
||||||
|
RefSubject(name), // @on
|
||||||
|
Close(Match, S3),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
#[rustfmt::skip]
|
||||||
|
Ok(vec![
|
||||||
|
// When first encountering a `Match`,
|
||||||
|
// we do not know what predicate needs to be emitted for AIR.
|
||||||
|
Incomplete,
|
||||||
|
// Nor do we know when encountering an identifier,
|
||||||
|
// which serves as the first argument to the yet-to-be-known
|
||||||
|
// predicate.
|
||||||
|
Incomplete,
|
||||||
|
// Once closing,
|
||||||
|
// we default to an equality check against `TRUE`.
|
||||||
|
O(Air::ExprStart(ExprOp::Eq, S1)),
|
||||||
|
O(Air::RefIdent(name)),
|
||||||
|
O(Air::RefIdent(SPair(SYM_TRUE, S1))),
|
||||||
|
O(Air::ExprEnd(S3)),
|
||||||
|
]),
|
||||||
|
Sut::parse(toks.into_iter()).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as above but _not_ defaulted to `TRUE`.
|
||||||
|
#[test]
|
||||||
|
fn match_short_with_value() {
|
||||||
|
let name = SPair("matchOn".into(), S2);
|
||||||
|
let value = SPair("value".into(), S3);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Open(Match, S1),
|
||||||
|
RefSubject(name), // @on
|
||||||
|
Ref(value), // @value
|
||||||
|
Close(Match, S4),
|
||||||
|
];
|
||||||
|
|
||||||
|
// See notes from `match_short_no_value`,
|
||||||
|
// which are not duplicated here.
|
||||||
|
assert_eq!(
|
||||||
|
#[rustfmt::skip]
|
||||||
|
Ok(vec![
|
||||||
|
Incomplete,
|
||||||
|
Incomplete,
|
||||||
|
O(Air::ExprStart(ExprOp::Eq, S1)),
|
||||||
|
O(Air::RefIdent(name)),
|
||||||
|
// Rather than defaulting to `SYM_TRUE` as above,
|
||||||
|
// we use the _user-provided_ value.
|
||||||
|
O(Air::RefIdent(value)),
|
||||||
|
O(Air::ExprEnd(S4)),
|
||||||
|
]),
|
||||||
|
Sut::parse(toks.into_iter()).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equivalently stated in XML: `match/@value` before `match/@on`;
|
||||||
|
// NIR imposes ordering,
|
||||||
|
// such that the `@on` must come first.
|
||||||
|
#[test]
|
||||||
|
fn match_short_value_before_subject_err() {
|
||||||
|
let name = SPair("matchOn".into(), S2);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Open(Match, S1),
|
||||||
|
Ref(name), // @value, not @on
|
||||||
|
|
||||||
|
// RECOVERY: Provide the subject
|
||||||
|
RefSubject(name),
|
||||||
|
Close(Match, S3),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
#[rustfmt::skip]
|
||||||
|
vec![
|
||||||
|
Ok(Incomplete), // Open
|
||||||
|
// We were expecting RefSubject (@on) but got Ref (@value)
|
||||||
|
Err(ParseError::StateError(
|
||||||
|
NirToAirError::MatchSubjectExpected(S1, Ref(name))
|
||||||
|
)),
|
||||||
|
// RECOVERY: Subject is provided.
|
||||||
|
Ok(Incomplete),
|
||||||
|
// And we then close as an eq match on TRUE,
|
||||||
|
// because no value has been provided
|
||||||
|
// (rather the value was consumed in the error).
|
||||||
|
Ok(O(Air::ExprStart(ExprOp::Eq, S1))),
|
||||||
|
Ok(O(Air::RefIdent(name))),
|
||||||
|
Ok(O(Air::RefIdent(SPair(SYM_TRUE, S1)))),
|
||||||
|
Ok(O(Air::ExprEnd(S3))),
|
||||||
|
],
|
||||||
|
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closing a match before providing any arguments.
|
||||||
|
#[test]
|
||||||
|
fn match_no_args_err() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Open(Match, S1),
|
||||||
|
// No refs.
|
||||||
|
Close(Match, S2),
|
||||||
|
// RECOVERY: We have no choice but to discard the above match.
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
#[rustfmt::skip]
|
||||||
|
vec![
|
||||||
|
Ok(Incomplete), // Open
|
||||||
|
// We were expecting RefSubject (@on) but closed instead.
|
||||||
|
Err(ParseError::StateError(
|
||||||
|
NirToAirError::MatchSubjectExpected(S1, Close(Match, S2))
|
||||||
|
)),
|
||||||
|
// RECOVERY: Useless match above discarded.
|
||||||
|
],
|
||||||
|
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -581,13 +581,14 @@ ele_parse! {
|
||||||
///
|
///
|
||||||
/// The dimensionality of the expression will be automatically
|
/// The dimensionality of the expression will be automatically
|
||||||
/// determined by the dimensionality of the matches' [`@on`](QN_ON).
|
/// determined by the dimensionality of the matches' [`@on`](QN_ON).
|
||||||
MatchExpr := QN_MATCH {
|
MatchExpr := QN_MATCH(_, ospan) {
|
||||||
@ {
|
@ {
|
||||||
QN_ON => TodoAttr,
|
QN_ON => RefSubject,
|
||||||
QN_VALUE => TodoAttr,
|
QN_VALUE => Ref,
|
||||||
QN_INDEX => TodoAttr,
|
QN_INDEX => TodoAttr,
|
||||||
QN_ANY_OF => TodoAttr,
|
QN_ANY_OF => TodoAttr,
|
||||||
} => Todo,
|
} => NirEntity::Match.open(ospan),
|
||||||
|
/(cspan) => NirEntity::Match.close(cspan),
|
||||||
|
|
||||||
CalcPredExpr,
|
CalcPredExpr,
|
||||||
};
|
};
|
||||||
|
|
|
@ -725,6 +725,8 @@ pub mod st {
|
||||||
L_RETMAP_UUUHEAD: str ":retmap:___head",
|
L_RETMAP_UUUHEAD: str ":retmap:___head",
|
||||||
L_RETMAP_UUUTAIL: str ":retmap:___tail",
|
L_RETMAP_UUUTAIL: str ":retmap:___tail",
|
||||||
|
|
||||||
|
U_TRUE: cid "TRUE",
|
||||||
|
|
||||||
URI_LV_CALC: uri "http://www.lovullo.com/calc",
|
URI_LV_CALC: uri "http://www.lovullo.com/calc",
|
||||||
URI_LV_LINKER: uri "http://www.lovullo.com/rater/linker",
|
URI_LV_LINKER: uri "http://www.lovullo.com/rater/linker",
|
||||||
URI_LV_PREPROC: uri "http://www.lovullo.com/rater/preproc",
|
URI_LV_PREPROC: uri "http://www.lovullo.com/rater/preproc",
|
||||||
|
|
|
@ -13,5 +13,13 @@
|
||||||
</any>
|
</any>
|
||||||
<any />
|
<any />
|
||||||
</classify>
|
</classify>
|
||||||
|
|
||||||
|
<classify as="short-match-implicit-eq-implicit-true">
|
||||||
|
<match on="foo" value="TRUE" />
|
||||||
|
</classify>
|
||||||
|
|
||||||
|
<classify as="short-match-implicit-eq-explicit-value">
|
||||||
|
<match on="foo" value="bar" />
|
||||||
|
</classify>
|
||||||
</package>
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -13,5 +13,13 @@
|
||||||
</any>
|
</any>
|
||||||
<any />
|
<any />
|
||||||
</classify>
|
</classify>
|
||||||
|
|
||||||
|
<classify as="short-match-implicit-eq-implicit-true">
|
||||||
|
<match on="foo" />
|
||||||
|
</classify>
|
||||||
|
|
||||||
|
<classify as="short-match-implicit-eq-explicit-value">
|
||||||
|
<match on="foo" value="bar" />
|
||||||
|
</classify>
|
||||||
</package>
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -170,5 +170,19 @@
|
||||||
</apply-template>
|
</apply-template>
|
||||||
</c:sum>
|
</c:sum>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<template name="_match-child_">
|
||||||
|
<match on="foo" value="TRUE" />
|
||||||
|
</template>
|
||||||
</package>
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -170,5 +170,19 @@
|
||||||
</c:sum>
|
</c:sum>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
This next one is a bit awkward,
|
||||||
|
because it creates an ambiguity when regenerating XML.
|
||||||
|
Each of `match`, `when`, and `c:*` are represented the same on the graph,
|
||||||
|
so it will not be clear until expansion how the body of the below
|
||||||
|
|
||||||
|
The ambiguity will go away once template application is performed by
|
||||||
|
TAMER in the near future;
|
||||||
|
until that time,
|
||||||
|
we cannot support the generation of each of those things within
|
||||||
|
templates.
|
||||||
|
|
||||||
|
<template name="_match-child_">
|
||||||
|
<match on="foo" />
|
||||||
|
</template>
|
||||||
</package>
|
</package>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue