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
main
Mike Gerwitz 2022-11-07 23:59:47 -05:00
parent 4a7fe887d5
commit 6b9979da9a
2 changed files with 249 additions and 12 deletions

View File

@ -306,25 +306,49 @@ impl<const TY: NirSymbolTy> ParseState for InterpState<TY> {
// 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<const TY: NirSymbolTy> InterpState<TY> {
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))

View File

@ -37,13 +37,18 @@ type Sut<const TY: NirSymbolTy> = InterpState<TY>;
// 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);
}