Skip to main content

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}