From 6b9979da9a07e76d6dc176631fe99a9c7bfef964 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 7 Nov 2022 23:59:47 -0500 Subject: [PATCH] tamer: nir::desugar::interp: Valid parses This completes the valid parses, though some more refactoring will be done. Next up is error handling and recovery. DEV-13156 --- tamer/src/nir/desugar/interp.rs | 39 ++++- tamer/src/nir/desugar/interp/test.rs | 222 ++++++++++++++++++++++++++- 2 files changed, 249 insertions(+), 12 deletions(-) diff --git a/tamer/src/nir/desugar/interp.rs b/tamer/src/nir/desugar/interp.rs index 6722df8b..7da7a76a 100644 --- a/tamer/src/nir/desugar/interp.rs +++ b/tamer/src/nir/desugar/interp.rs @@ -306,25 +306,49 @@ impl ParseState for InterpState { // Note that this is the position _relative to the offset_, // not the beginning of the string. match s[offset..].chars().position(|ch| ch == '{') { - Some(0) => todo!("no literal prefix"), + // The literal is the empty string, + // which is useless to output, + // so ignore it and proceed with parsing. + Some(0) => { + Transition(ParseInterpAt(s, gen_param, offset + 1)) + .incomplete() + .with_lookahead(SugaredNirSymbol(sym, span)) + } // Everything from the offset until the curly brace is a // literal. - Some(pos) => { - let literal = s[offset..pos].intern(); + Some(rel_pos) => { + let end = offset + rel_pos; + + let literal = s[offset..end].intern(); let span_text = - span.context().span_or_zz(offset, pos - offset); + span.context().span_or_zz(offset, rel_pos); let text = PlainNir::TplParamText( PlainNirSymbol::Todo(literal, span_text), ); - Transition(ParseInterpAt(s, gen_param, pos + 1)) + Transition(ParseInterpAt(s, gen_param, end + 1)) .ok(Expanded(text)) .with_lookahead(SugaredNirSymbol(sym, span)) } - None => todo!("remaining literal"), + // The remainder of the specification is a literal. + None => { + let literal = s[offset..].intern(); + let span_text = + span.context().span_or_zz(offset, s.len() - offset); + + let text = PlainNir::TplParamText( + PlainNirSymbol::Todo(literal, span_text), + ); + + // Keep in the current state but update the offset; + // we'll complete parsing next pass. + Transition(ParseLiteralAt(s, gen_param, s.len())) + .ok(Expanded(text)) + .with_lookahead(SugaredNirSymbol(sym, span)) + } } } @@ -431,6 +455,9 @@ impl InterpState { PlainNirSymbol::Todo(gen_desc, span), ); + // Begin parsing in a _literal_ context, + // since interpolation is most commonly utilized with literal + // prefixes. Transition(ParseLiteralAt(sym.lookup_str(), gen_param, 0)) .ok(Expanded(open)) .with_lookahead(SugaredNirSymbol(sym, span)) diff --git a/tamer/src/nir/desugar/interp/test.rs b/tamer/src/nir/desugar/interp/test.rs index 492d6be6..a14abebd 100644 --- a/tamer/src/nir/desugar/interp/test.rs +++ b/tamer/src/nir/desugar/interp/test.rs @@ -37,13 +37,18 @@ type Sut = InterpState; // Best to just leave it be. #[test] fn does_not_desugar_literal_only() { - let sym = "foo".into(); - let toks = vec![SugaredNirSymbol::<{ StringLiteral }>(sym, S1)]; + // `@bar@` is a metavariable, + // but it's also a literal because it's not enclosed in braces. + for literal in ["foo", "@bar@"] { + let sym = literal.into(); + let toks = vec![SugaredNirSymbol::<{ StringLiteral }>(sym, S1)]; - assert_eq!( - Ok(vec![Object(ReplaceSym(PlainNirSymbol::Todo(sym, S1)))]), - Sut::parse(toks.into_iter()).collect(), - ); + assert_eq!( + Ok(vec![Object(ReplaceSym(PlainNirSymbol::Todo(sym, S1)))]), + Sut::parse(toks.into_iter()).collect(), + "literal `{literal}` must not desugar", + ); + } } // When ending with an interpolated variable, @@ -123,3 +128,208 @@ fn desugars_literal_with_ending_var() { assert_eq!(sut.next(), None); } + +// This is largely the same as the above test, +// with the literal and interpolation var reversed. +// +// Explanations above are omitted here. +#[test] +fn desugars_var_with_ending_literal() { + let given_val = "{@foo@}bar"; + // |[---] [-] + // |1 5 7 9 + // | B C| + // [--------] + // 0 9 + // A + + let a = DC.span(0, 10); + let b = DC.span(1, 5); + let c = DC.span(7, 3); + + let given_sym = SugaredNirSymbol::<{ StringLiteral }>(given_val.into(), a); + let toks = vec![given_sym]; + + let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(a); + let expect_dfn = PlainNirSymbol::Todo(expect_name.into(), a); + let expect_param = PlainNirSymbol::Todo("@foo@".into(), b); + let expect_text = PlainNirSymbol::Todo("bar".into(), c); + + let mut sut = Sut::parse(toks.into_iter()); + + // + // See above test for explanations that are not repeated here. + // + + assert_matches!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamOpen( + dfn, + PlainNirSymbol::Todo(desc_str, desc_span) + ))))) if dfn == expect_dfn + && desc_str.lookup_str().contains(given_val) + && desc_span == a + ); + + assert_eq!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamValue(expect_param))))), + ); + + assert_eq!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamText(expect_text))))) + ); + + assert_eq!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamClose(a))))) + ); + + assert_matches!( + sut.next(), + Some(Ok(Object(ReplaceSym(PlainNirSymbol::Todo(given_replace, given_span))))) + if given_replace == expect_name && given_span == a + ); + + assert_eq!(sut.next(), None); +} + +// Combination of the above two tests. +// +// Explanations above are omitted here. +#[test] +fn desugars_many_vars_and_literals() { + let given_val = "foo{@bar@}baz{@quux@}"; + // [-] [---] [-] [----]| + // 0 2 4 8 10 14 19| + // |B C D E | + // [-------------------] + // 0 20 + // A + + let a = DC.span(0, 21); + let b = DC.span(0, 3); + let c = DC.span(4, 5); + let d = DC.span(10, 3); + let e = DC.span(14, 6); + + let given_sym = SugaredNirSymbol::<{ StringLiteral }>(given_val.into(), a); + let toks = vec![given_sym]; + + let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(a); + let expect_dfn = PlainNirSymbol::Todo(expect_name.into(), a); + let expect_text1 = PlainNirSymbol::Todo("foo".into(), b); + let expect_param1 = PlainNirSymbol::Todo("@bar@".into(), c); + let expect_text2 = PlainNirSymbol::Todo("baz".into(), d); + let expect_param2 = PlainNirSymbol::Todo("@quux@".into(), e); + + let mut sut = Sut::parse(toks.into_iter()); + + // + // See above tests for explanations that are not repeated here. + // + + assert_matches!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamOpen( + dfn, + PlainNirSymbol::Todo(desc_str, desc_span) + ))))) if dfn == expect_dfn + && desc_str.lookup_str().contains(given_val) + && desc_span == a + ); + + assert_eq!( + Ok(vec![ + // These two are the as previous tests. + Object(Expanded(PlainNir::TplParamText(expect_text1))), + Object(Expanded(PlainNir::TplParamValue(expect_param1))), + // This pair repeats literals and vars further into the pattern + // to ensure that the parser is able to handle returning to + // previous states and is able to handle inputs at different + // offsets. + Object(Expanded(PlainNir::TplParamText(expect_text2))), + Object(Expanded(PlainNir::TplParamValue(expect_param2))), + ]), + sut.by_ref().take(4).collect(), + ); + + assert_eq!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamClose(a))))) + ); + + assert_matches!( + sut.next(), + Some(Ok(Object(ReplaceSym(PlainNirSymbol::Todo(given_replace, given_span))))) + if given_replace == expect_name && given_span == a + ); + + assert_eq!(sut.next(), None); +} + +// Adjacent vars with empty literal between them. +#[test] +fn desugars_adjacent_interpolated_vars() { + let given_val = "{@foo@}{@bar@}{@baz@}"; + // |[---] [---] [---]| + // |1 5 8 12 15 19| + // | B C D | + // [-------------------] + // 0 20 + // A + + let a = DC.span(0, 21); + let b = DC.span(1, 5); + let c = DC.span(8, 5); + let d = DC.span(15, 5); + + let given_sym = SugaredNirSymbol::<{ StringLiteral }>(given_val.into(), a); + let toks = vec![given_sym]; + + let GenIdentSymbolId(expect_name) = gen_tpl_param_ident_at_offset(a); + let expect_dfn = PlainNirSymbol::Todo(expect_name.into(), a); + let expect_param1 = PlainNirSymbol::Todo("@foo@".into(), b); + let expect_param2 = PlainNirSymbol::Todo("@bar@".into(), c); + let expect_param3 = PlainNirSymbol::Todo("@baz@".into(), d); + + let mut sut = Sut::parse(toks.into_iter()); + + // + // See above tests for explanations that are not repeated here. + // + + assert_matches!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamOpen( + dfn, + PlainNirSymbol::Todo(desc_str, desc_span) + ))))) if dfn == expect_dfn + && desc_str.lookup_str().contains(given_val) + && desc_span == a + ); + + // These are the three adjacent vars. + assert_eq!( + Ok(vec![ + Object(Expanded(PlainNir::TplParamValue(expect_param1))), + Object(Expanded(PlainNir::TplParamValue(expect_param2))), + Object(Expanded(PlainNir::TplParamValue(expect_param3))), + ]), + sut.by_ref().take(3).collect(), + ); + + assert_eq!( + sut.next(), + Some(Ok(Object(Expanded(PlainNir::TplParamClose(a))))) + ); + + assert_matches!( + sut.next(), + Some(Ok(Object(ReplaceSym(PlainNirSymbol::Todo(given_replace, given_span))))) + if given_replace == expect_name && given_span == a + ); + + assert_eq!(sut.next(), None); +}