string_interpolation/
string-interpolation.rs1use std::collections::HashMap;
3
4use logos::{Lexer, Logos};
5
6type 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 , String )),
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 ),
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 ),
40 #[token("'")]
41 Quote,
42 #[token("}")]
43 InterpolationEnd,
44}
45fn 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}
74fn 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}
97fn 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}
106fn 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