panache_parser/parser/utils/
chunk_options.rs1#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ChunkOptionValue {
10 Simple(String),
13
14 Expression(String),
17}
18
19pub fn hashpipe_comment_prefix(language: &str) -> Option<&'static str> {
21 match language.to_ascii_lowercase().as_str() {
22 "r" | "python" | "julia" | "bash" | "shell" | "sh" | "ruby" | "perl" => Some("#|"),
23 "c" | "cpp" | "c++" | "rcpp" | "java" | "javascript" | "js" | "typescript" | "ts"
24 | "rust" | "go" | "swift" | "kotlin" | "scala" | "csharp" | "c#" | "php" | "ojs"
25 | "dot" => Some("//|"),
26 "sql" | "mysql" | "postgres" | "postgresql" | "sqlite" => Some("--|"),
27 "mermaid" => Some("%%|"),
28 _ => None,
29 }
30}
31
32pub fn classify_value(value: &Option<String>) -> ChunkOptionValue {
40 match value {
41 None => ChunkOptionValue::Simple(String::new()), Some(v) => {
43 if is_boolean_literal(v) || is_numeric_literal(v) || is_simple_string(v) {
46 ChunkOptionValue::Simple(v.clone())
47 } else {
48 ChunkOptionValue::Expression(v.clone())
49 }
50 }
51 }
52}
53
54fn is_simple_string(s: &str) -> bool {
58 if s.is_empty() {
60 return true;
61 }
62
63 if s.contains('(')
65 || s.contains(')')
66 || s.contains('{')
67 || s.contains('}')
68 || s.contains('$')
69 || s.contains('[')
70 || s.contains(']')
71 || s.contains('+')
72 || s.contains('-')
73 || s.contains('*')
74 || s.contains('/')
75 || s.contains('<')
76 || s.contains('>')
77 || s.contains('!')
78 || s.contains(':')
79 {
80 return false;
81 }
82
83 if !s.contains(' ')
86 && !s.contains('.')
87 && !s.contains('/')
88 && !s.contains('\\')
89 && !s.contains(',')
90 && s.chars().all(|c| c.is_alphanumeric() || c == '_')
91 {
92 return false;
94 }
95
96 true
98}
99
100pub fn is_boolean_literal(s: &str) -> bool {
104 matches!(s, "TRUE" | "FALSE" | "T" | "F")
105}
106
107pub fn is_numeric_literal(s: &str) -> bool {
111 s.parse::<f64>().is_ok()
113}
114
115pub fn is_quoted_string(s: &str) -> bool {
120 (s.starts_with('"') && s.ends_with('"') && s.len() >= 2)
121 || (s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_is_boolean_literal() {
130 assert!(is_boolean_literal("TRUE"));
131 assert!(is_boolean_literal("FALSE"));
132 assert!(is_boolean_literal("T"));
133 assert!(is_boolean_literal("F"));
134
135 assert!(!is_boolean_literal("true"));
136 assert!(!is_boolean_literal("false"));
137 assert!(!is_boolean_literal("True"));
138 assert!(!is_boolean_literal("MAYBE"));
139 }
140
141 #[test]
142 fn test_is_numeric_literal() {
143 assert!(is_numeric_literal("7"));
145 assert!(is_numeric_literal("0"));
146 assert!(is_numeric_literal("-3"));
147 assert!(is_numeric_literal("100"));
148
149 assert!(is_numeric_literal("3.14"));
151 assert!(is_numeric_literal("-2.5"));
152 assert!(is_numeric_literal("0.1"));
153
154 assert!(is_numeric_literal("1e5"));
156 assert!(is_numeric_literal("1.5e-3"));
157
158 assert!(!is_numeric_literal("abc"));
160 assert!(!is_numeric_literal("7x"));
161 assert!(!is_numeric_literal(""));
162 }
163
164 #[test]
165 fn test_is_quoted_string() {
166 assert!(is_quoted_string("\"hello\""));
168 assert!(is_quoted_string("\"with spaces\""));
169 assert!(is_quoted_string("\"\""));
170
171 assert!(is_quoted_string("'hello'"));
173 assert!(is_quoted_string("'with spaces'"));
174 assert!(is_quoted_string("''"));
175
176 assert!(!is_quoted_string("hello"));
178 assert!(!is_quoted_string("\""));
179 assert!(!is_quoted_string("'"));
180 assert!(!is_quoted_string("\"hello'"));
181 assert!(!is_quoted_string("'hello\""));
182 assert!(!is_quoted_string(""));
183 }
184
185 #[test]
186 fn test_classify_boolean() {
187 let result = classify_value(&Some("TRUE".to_string()));
188 assert_eq!(result, ChunkOptionValue::Simple("TRUE".to_string()));
189
190 let result = classify_value(&Some("FALSE".to_string()));
191 assert_eq!(result, ChunkOptionValue::Simple("FALSE".to_string()));
192 }
193
194 #[test]
195 fn test_classify_number() {
196 let result = classify_value(&Some("7".to_string()));
197 assert_eq!(result, ChunkOptionValue::Simple("7".to_string()));
198
199 let result = classify_value(&Some("3.14".to_string()));
200 assert_eq!(result, ChunkOptionValue::Simple("3.14".to_string()));
201 }
202
203 #[test]
204 fn test_classify_quoted_string() {
205 let result = classify_value(&Some("\"hello\"".to_string()));
206 assert_eq!(result, ChunkOptionValue::Simple("\"hello\"".to_string()));
207
208 let result = classify_value(&Some("'world'".to_string()));
209 assert_eq!(result, ChunkOptionValue::Simple("'world'".to_string()));
210 }
211
212 #[test]
213 fn test_classify_function_call() {
214 let result = classify_value(&Some("paste(\"a\", \"b\")".to_string()));
215 assert_eq!(
216 result,
217 ChunkOptionValue::Expression("paste(\"a\", \"b\")".to_string())
218 );
219 }
220
221 #[test]
222 fn test_classify_variable() {
223 let result = classify_value(&Some("my_var".to_string()));
224 assert_eq!(result, ChunkOptionValue::Expression("my_var".to_string()));
225 }
226
227 #[test]
228 fn test_classify_none() {
229 let result = classify_value(&None);
230 assert_eq!(result, ChunkOptionValue::Simple(String::new()));
231 }
232
233 #[test]
234 fn test_classify_expression_with_operators() {
235 let result = classify_value(&Some("x + y".to_string()));
236 assert_eq!(result, ChunkOptionValue::Expression("x + y".to_string()));
237
238 let result = classify_value(&Some("data$col".to_string()));
239 assert_eq!(result, ChunkOptionValue::Expression("data$col".to_string()));
240
241 let result = classify_value(&Some("vec[1]".to_string()));
242 assert_eq!(result, ChunkOptionValue::Expression("vec[1]".to_string()));
243 }
244
245 #[test]
246 fn test_hashpipe_comment_prefix() {
247 assert_eq!(hashpipe_comment_prefix("r"), Some("#|"));
248 assert_eq!(hashpipe_comment_prefix("cpp"), Some("//|"));
249 assert_eq!(hashpipe_comment_prefix("Rcpp"), Some("//|"));
250 assert_eq!(hashpipe_comment_prefix("sql"), Some("--|"));
251 assert_eq!(hashpipe_comment_prefix("mermaid"), Some("%%|"));
252 assert_eq!(hashpipe_comment_prefix("fortran"), None);
253 }
254}