1use std::path::Path;
4
5use crate::{
6 check::check,
7 context::Context,
8 error::Error,
9 eval::eval,
10 expr::Expr,
11 lexer::{token::Token, Lexer},
12 macro_expand::macro_expand,
13 optimize::optimize,
14 parser::Parser,
15 prune::prune,
16 range::Position,
17 util::fs::get_full_extension,
18};
19
20pub const TAN_FILE_EXTENSION: &str = "tan";
21
22pub const TAN_FILE_EMOJI_EXTENSION: &str = "👅";
23
24pub fn has_tan_extension(path: impl AsRef<Path>) -> bool {
27 let path = path.as_ref();
28 if let Some(extension) = path.extension() {
29 extension == TAN_FILE_EXTENSION || extension == TAN_FILE_EMOJI_EXTENSION
30 } else {
31 false
32 }
33}
34
35pub fn has_tan_extension_strict(path: impl AsRef<Path>) -> bool {
39 let path = path.as_ref();
40 if let Some(extension) = get_full_extension(path) {
41 extension == TAN_FILE_EXTENSION || extension == TAN_FILE_EMOJI_EXTENSION
42 } else {
43 false
44 }
45}
46
47pub fn strip_tan_extension(path: impl Into<String>) -> String {
49 let path = path.into();
50
51 if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EXTENSION}")) {
52 path.to_owned()
53 } else if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EMOJI_EXTENSION}")) {
54 path.to_owned()
55 } else {
56 path
57 }
58}
59
60pub fn lex_string(input: impl AsRef<str>) -> Result<Vec<Token>, Vec<Error>> {
62 let input = input.as_ref();
63 let mut lexer = Lexer::new(input);
64 lexer.lex()
65}
66
67pub fn parse_string(input: impl AsRef<str>) -> Result<Expr, Vec<Error>> {
71 let input = input.as_ref();
72
73 let mut lexer = Lexer::new(input);
74 let tokens = lexer.lex()?;
75
76 let mut parser = Parser::new(&tokens);
77 let mut expr = parser.parse()?;
78
79 let expr = expr.swap_remove(0);
81
82 Ok(expr)
83}
84
85pub fn parse_string_with_position(
87 input: impl AsRef<str>,
88 start_position: Position,
89) -> Result<Expr, Vec<Error>> {
90 let input = input.as_ref();
91
92 let mut lexer = Lexer::new(input).with_position(start_position);
93 let tokens = lexer.lex()?;
94
95 let mut parser = Parser::new(&tokens).with_position(start_position);
96 let mut expr = parser.parse()?;
97
98 let expr = expr.swap_remove(0);
100
101 Ok(expr)
102}
103
104pub fn parse_string_all(input: impl AsRef<str>) -> Result<Vec<Expr>, Vec<Error>> {
107 let input = input.as_ref();
108
109 let mut lexer = Lexer::new(input);
110 let tokens = lexer.lex()?;
111
112 let mut parser = Parser::new(&tokens);
113 let exprs = parser.parse()?;
114
115 Ok(exprs)
116}
117
118pub fn compile(expr: Expr, context: &mut Context) -> Result<Expr, Vec<Error>> {
120 let Some(expr) = prune(expr) else {
132 return Ok(Expr::None);
135 };
136
137 let expr = macro_expand(expr, context);
141
142 let Ok(expr) = expr else {
146 return Err(vec![expr.unwrap_err()]);
147 };
148
149 let Some(expr) = expr else {
152 return Ok(Expr::None);
155 };
156
157 let expr = check(expr);
162 let Ok(expr) = expr else {
163 return Err(vec![expr.unwrap_err()]);
164 };
165
166 let expr = optimize(expr);
170
171 Ok(expr)
178}
179
180pub fn compile_string(
185 input: impl AsRef<str>,
186 context: &mut Context,
187) -> Result<Vec<Expr>, Vec<Error>> {
188 let exprs = parse_string_all(input)?;
189
190 let mut compiled_exprs = Vec::new();
208
209 for expr in exprs {
210 let expr = compile(expr, context)?;
212 if !expr.is_none() {
213 compiled_exprs.push(expr);
214 }
215 }
216
217 Ok(compiled_exprs)
218}
219
220pub fn eval_string(input: impl AsRef<str>, context: &mut Context) -> Result<Expr, Vec<Error>> {
224 let exprs = compile_string(input, context)?;
225
226 let mut last_value = Expr::None;
227
228 for expr in exprs {
229 let value = eval(&expr, context);
230
231 let Ok(value) = value else {
232 return Err(vec![value.unwrap_err()]);
233 };
234
235 last_value = value;
236 }
237
238 Ok(last_value)
239}
240
241#[cfg(test)]
244mod tests {
245 use crate::api::{has_tan_extension, has_tan_extension_strict, strip_tan_extension};
246
247 #[test]
248 fn strip_tan_extension_usage() {
249 assert_eq!(strip_tan_extension("hello.tan"), "hello");
250 assert_eq!(
251 strip_tan_extension("deep/nesting/hello.tan"),
252 "deep/nesting/hello"
253 );
254 assert_eq!(strip_tan_extension("emoji/works.👅"), "emoji/works");
255 }
256
257 #[test]
258 fn has_tan_extension_usage() {
259 assert!(has_tan_extension("hello.tan"));
260 assert!(has_tan_extension("hello.data.tan"));
261 assert!(has_tan_extension("nested/dir/hello.data.tan"));
262 assert!(has_tan_extension("nested/.hidden/dir/hello.data.tan"));
263 assert!(has_tan_extension(
264 "nested/.local-tan-root/@std/zonk/main.tan"
265 ));
266 }
267
268 #[test]
269 fn has_tan_extension_strict_usage() {
270 assert!(has_tan_extension_strict("hello.tan"));
271 assert!(!has_tan_extension_strict("hello.data.tan"));
272 assert!(has_tan_extension_strict("nested/.hidden/dir/hello.tan"));
273 }
274}