Skip to content

Commit

Permalink
Add support for interpolated strings (f-strings) in the parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Mingun committed Sep 6, 2024
1 parent eb30135 commit 14ceed0
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/model/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ pub enum OwningNode {
/// Boolean constant
Bool(bool),

/// String with embedded expressions (interpolated string, f-string).
///
/// Literal parts represented by [`OwningNode::Str`] node, interpolated parts
/// represented by any other nodes.
InterpolatedStr(Vec<OwningNode>),

/// Name of field of the type in which attribute expression is defined
Attr(FieldName),
/// Built-in variable
Expand Down Expand Up @@ -127,6 +133,7 @@ impl OwningNode {
Node::Int(val) => Int(val),
Node::Float(val)=> Float(val),
Node::Bool(val) => Bool(val),
Node::InterpolatedStr(val) => InterpolatedStr(Self::validate_all(val)),

//TODO: Name already contains only valid symbols, but need to check that it is really exists
Node::Attr(val) => Attr(FieldName::valid(val)),
Expand Down
122 changes: 122 additions & 0 deletions src/parser/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub enum Node<'input> {
/// Boolean constant
Bool(bool),

/// String with embedded expressions (interpolated string, f-string).
///
/// Literal parts represented by [`Node::Str`] node, interpolated parts
/// represented by any other nodes.
InterpolatedStr(Vec<Node<'input>>),

/// Name of field of the type in which attribute expression is defined
Attr(&'input str),
/// Built-in variable
Expand Down Expand Up @@ -486,6 +492,18 @@ peg::parser! {
rule quoted_oct() -> char = s:$(oct()+) {? to_char(s, 8) };
rule quoted_hex() -> char = ['u'] s:$(hex()*<4>) {? to_char(s, 16) };

/// String which interpolates expressions inside `{}`.
/// Defined as string literal prefixed with `f` character: `f"..."`
rule fstring() -> Vec<Node<'input>> = "f\"" e:fstring_element()* "\"" {e};
rule fstring_element() -> Node<'input>
// Interpolated expression inside of f-string (inside `{}`)
= "{" _ e:expr() _ "}" {e}
// Literal part of interpolated string
/ v:(fstring_ch() / escaped())+ { Node::Str(String::from_iter(v.into_iter())) }
;
/// Single non-escaped character in f-string
rule fstring_ch() -> char = [^ '"' | '\\' | '{'];

rule integer() -> BigInt
= n:$(['1'..='9'] ['0'..='9' | '_']*) {? to_int(n, 10) }
/ "0" ['b' | 'B'] n:$(bin()+) {? to_int(n, 2) }
Expand Down Expand Up @@ -608,6 +626,7 @@ peg::parser! {
/ "[" _ l:list()? _ "]" { Node::List(l.unwrap_or_default()) }
/ "sizeof" _ "<" _ t:type_ref() _ ">" { Node::SizeOf { type_: t, bit: false } }
/ "bitsizeof" _ "<" _ t:type_ref() _ ">" { Node::SizeOf { type_: t, bit: true } }
/ e:fstring() { Node::InterpolatedStr(e) }
/ v:(s:string() _ {s})+ { Node::Str(String::from_iter(v.into_iter())) }
/ n:special_name() !name_part() { n }
/ e:enum_name() { e }
Expand Down Expand Up @@ -1009,6 +1028,109 @@ mod parse {
}
}

mod f_string {
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn empty() {
assert_eq!(parse_single(r#" f"" "#), Ok(InterpolatedStr(vec![])));
}

#[test]
fn literal() {
assert_eq!(parse_single(r#" f"\n\r\t 1\n\r\t 2\n\r\t " "#), Ok(InterpolatedStr(vec![
Str("\n\r\t 1\n\r\t 2\n\r\t ".into()),
])));
}

#[test]
fn literal_then_expr() {
assert_eq!(parse_single(r#" f"foo={123}" "#), Ok(InterpolatedStr(vec![
Str("foo=".into()),
Int(123.into()),
])));
}

#[test]
fn expr_then_literal() {
assert_eq!(parse_single(r#" f"{123}=abc" "#), Ok(InterpolatedStr(vec![
Int(123.into()),
Str("=abc".into()),
])));
}

#[test]
fn expr_then_expr() {
assert_eq!(parse_single(r#" f"{123}{abc}" "#), Ok(InterpolatedStr(vec![
Int(123.into()),
Attr("abc".into()),
])));
}

mod interpolated {
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn int_expr() {
assert_eq!(parse_single(r#" f"{123}" "#), Ok(InterpolatedStr(vec![
Int(123.into()),
])));
}

#[test]
fn str_expr() {
assert_eq!(parse_single(r#" f"abc{"def"}ghi" "#), Ok(InterpolatedStr(vec![
Str("abc".into()),
Str("def".into()),
Str("ghi".into()),
])));
}

#[test]
fn f_str_literal_expr() {
assert_eq!(parse_single(r#" f" { f"def" } " "#), Ok(InterpolatedStr(vec![
Str(" ".into()),
// f"def"
InterpolatedStr(vec![Str("def".into())]),
Str(" ".into()),
])));
}

#[test]
fn f_str_expr_expr() {
assert_eq!(parse_single(r#" f" { f"{def}" } " "#), Ok(InterpolatedStr(vec![
Str(" ".into()),
// f"abc{def}"
InterpolatedStr(vec![Attr("def".into())]),
Str(" ".into()),
])));
}
}

#[test]
fn double_quote_in_literal() {
assert_eq!(parse_single(r#" f"this \" is a quote" "#), Ok(InterpolatedStr(vec![
Str("this \" is a quote".into())
])));
}

#[test]
fn starts_with_quote() {
assert_eq!(parse_single(r#" f"\" is a quote" "#), Ok(InterpolatedStr(vec![
Str("\" is a quote".into())
])));
}

#[test]
fn starts_with_space_quote() {
assert_eq!(parse_single(r#" f" \" is a quote" "#), Ok(InterpolatedStr(vec![
Str(" \" is a quote".into())
])));
}
}

mod expr {
use super::*;
use pretty_assertions::assert_eq;
Expand Down

0 comments on commit 14ceed0

Please sign in to comment.