Skip to main content

string_interpolation/
string-interpolation.rs

1/* ANCHOR: all */
2use std::collections::HashMap;
3
4use logos::{Lexer, Logos};
5
6/* ANCHOR: lexers */
7type SymbolTable = HashMap<String, String>;
8
9#[derive(Logos, Debug, PartialEq, Clone)]
10#[logos(skip r"\s+")]
11#[logos(extras = SymbolTable)]
12enum VariableDefinitionContext {
13    #[regex(r"[[:alpha:]][[:alnum:]]*", variable_definition)]
14    Id((String /* variable name */, String /* value */)),
15    #[token("=")]
16    Equals,
17    #[token("'")]
18    Quote,
19}
20
21#[derive(Logos, Debug, PartialEq, Clone)]
22#[logos(extras = SymbolTable)]
23enum StringContext {
24    #[token("'")]
25    Quote,
26    #[regex("[^'$]+")]
27    Content,
28    #[token("${", evaluate_interpolation)]
29    InterpolationStart(String /* evaluated value of the interpolation */),
30    #[token("$")]
31    DollarSign,
32}
33
34#[derive(Logos, Debug, PartialEq, Clone)]
35#[logos(skip r"\s+")]
36#[logos(extras = SymbolTable)]
37enum StringInterpolationContext {
38    #[regex(r"[[:alpha:]][[:alnum:]]*", get_variable_value)]
39    Id(String /* value for the given id */),
40    #[token("'")]
41    Quote,
42    #[token("}")]
43    InterpolationEnd,
44}
45/* ANCHOR_END: lexers */
46
47/* ANCHOR: variable_definition */
48fn get_string_content(lex: &mut Lexer<StringContext>) -> String {
49    let mut s = String::new();
50    while let Some(Ok(token)) = lex.next() {
51        match token {
52            StringContext::Content => s.push_str(lex.slice()),
53            StringContext::DollarSign => s.push('$'),
54            StringContext::InterpolationStart(value) => s.push_str(&value),
55            StringContext::Quote => break,
56        }
57    }
58    s
59}
60
61fn variable_definition(lex: &mut Lexer<VariableDefinitionContext>) -> Option<(String, String)> {
62    let id = lex.slice().to_string();
63    if let Some(Ok(VariableDefinitionContext::Equals)) = lex.next() {
64        if let Some(Ok(VariableDefinitionContext::Quote)) = lex.next() {
65            let mut lex2 = lex.clone().morph::<StringContext>();
66            let value = get_string_content(&mut lex2);
67            *lex = lex2.morph();
68            lex.extras.insert(id.clone(), value.clone());
69            return Some((id, value));
70        }
71    }
72    None
73}
74/* ANCHOR_END: variable_definition */
75
76/* ANCHOR: evaluate_interpolation */
77fn evaluate_interpolation(lex: &mut Lexer<StringContext>) -> Option<String> {
78    let mut lex2 = lex.clone().morph::<StringInterpolationContext>();
79    let mut interpolation = String::new();
80    while let Some(result) = lex2.next() {
81        match result {
82            Ok(token) => match token {
83                StringInterpolationContext::Id(value) => interpolation.push_str(&value),
84                StringInterpolationContext::Quote => {
85                    *lex = lex2.morph();
86                    interpolation.push_str(&get_string_content(lex));
87                    lex2 = lex.clone().morph();
88                }
89                StringInterpolationContext::InterpolationEnd => break,
90            },
91            Err(()) => panic!("Interpolation error"),
92        }
93    }
94    *lex = lex2.morph();
95    Some(interpolation)
96}
97/* ANCHOR_END: evaluate_interpolation */
98
99/* ANCHOR: get_variable_value */
100fn get_variable_value(lex: &mut Lexer<StringInterpolationContext>) -> Option<String> {
101    if let Some(value) = lex.extras.get(lex.slice()) {
102        return Some(value.clone());
103    }
104    None
105}
106/* ANCHOR_END: get_variable_value */
107
108/* ANCHOR: main */
109fn test_variable_definition(
110    expected_id: &str,
111    expected_value: &str,
112    token: Option<Result<VariableDefinitionContext, ()>>,
113) {
114    if let Some(Ok(VariableDefinitionContext::Id((id, value)))) = token {
115        assert_eq!(id, expected_id);
116        assert_eq!(value, expected_value);
117    } else {
118        panic!("Expected key: {} not found", expected_id);
119    }
120}
121
122fn main() {
123    let mut lex = VariableDefinitionContext::lexer(
124        "\
125        name = 'Mark'\n\
126        greeting = 'Hi ${name}!'\n\
127        surname = 'Scott'\n\
128        greeting2 = 'Hi ${name ' ' surname}!'\n\
129        greeting3 = 'Hi ${name ' ${surname}!'}!'\n\
130        ",
131    );
132    test_variable_definition("name", "Mark", lex.next());
133    test_variable_definition("greeting", "Hi Mark!", lex.next());
134    test_variable_definition("surname", "Scott", lex.next());
135    test_variable_definition("greeting2", "Hi Mark Scott!", lex.next());
136    test_variable_definition("greeting3", "Hi Mark Scott!!", lex.next());
137}
138/* ANCHOR_END: main */
139/* ANCHOR_END: all */