rawk_core/awk.rs
1use crate::{Evaluator, Lexer, ParseError, Parser, Program};
2
3/// High-level wrapper for compiling and running an AWK script.
4///
5/// The script is parsed once on construction and can then be executed against
6/// any number of independent inputs without re-parsing.
7///
8/// # Examples
9///
10/// Print every line of input unchanged:
11///
12/// ```
13/// use rawk_core::awk::Awk;
14///
15/// let awk = Awk::new("{ print }").unwrap();
16/// let (output, error) = awk.run(vec!["hello world".into()], None, None);
17/// assert_eq!(output, vec!["hello world".to_string()]);
18/// assert!(error.is_none());
19/// ```
20///
21/// Supply a filename so that the `FILENAME` built-in variable is populated:
22///
23/// ```
24/// use rawk_core::awk::Awk;
25///
26/// let awk = Awk::new("{ print FILENAME }").unwrap();
27/// let (output, error) = awk.run(vec!["ignored".into()], Some("data/input.txt".into()), None);
28/// assert_eq!(output, vec!["data/input.txt".to_string()]);
29/// assert!(error.is_none());
30/// ```
31///
32/// Use a custom field separator to parse CSV-style input:
33///
34/// ```
35/// use rawk_core::awk::Awk;
36///
37/// let awk = Awk::new("{ print $1 }").unwrap();
38/// let (output, error) = awk.run(vec!["Alice,30,engineer".into()], None, Some(",".into()));
39/// assert_eq!(output, vec!["Alice".to_string()]);
40/// assert!(error.is_none());
41/// ```
42pub struct Awk {
43 program: Program<'static>,
44}
45
46impl Awk {
47 /// Parse an AWK script into an executable program.
48 ///
49 /// The script is stored with a static lifetime to keep the AST valid.
50 /// Returns a parse error if the script is not valid AWK according to this parser.
51 pub fn new(script: impl Into<String>) -> Result<Self, ParseError<'static>> {
52 let script: String = script.into();
53 let script: &'static str = Box::leak(script.into_boxed_str());
54
55 let lexer = Lexer::new(script);
56 let parser: &'static mut Parser<'static> = Box::leak(Box::new(Parser::new(lexer)));
57 let program = parser.try_parse_program()?;
58
59 Ok(Self { program })
60 }
61
62 /// Execute the compiled program against the given input lines.
63 ///
64 /// - `filename` — exposed as the `FILENAME` built-in variable inside the script.
65 /// Pass `None` to use the default value `"-"` (conventional stdin placeholder).
66 /// - `field_separator` — overrides the `FS` built-in variable used to split each
67 /// input record into fields (`$1`, `$2`, …). Pass `None` to use the default `" "`,
68 /// which splits on runs of whitespace.
69 ///
70 /// Returns `(output_lines, runtime_error)`. When a runtime error occurs, output
71 /// collected before the error is still returned alongside the error message.
72 pub fn run(
73 &self,
74 input: Vec<String>,
75 filename: Option<String>,
76 field_separator: Option<String>,
77 ) -> (Vec<String>, Option<String>) {
78 let filename = filename.unwrap_or_else(|| "-".to_string());
79 let mut evaluator = Evaluator::new(self.program.clone(), input, filename);
80 if let Some(fs) = field_separator {
81 evaluator = evaluator.with_field_separator(fs);
82 }
83
84 let output = evaluator.eval();
85 (output, evaluator.runtime_error().map(str::to_string))
86 }
87}