1mod builtins;
2mod expr;
3pub mod stmt;
4
5use std::cell::Cell;
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::{BufRead, BufReader, Write};
9use std::process::{Child, ChildStdin, ChildStdout};
10
11use crate::ast::*;
12use crate::error::{Error, Result};
13use crate::value::Value;
14
15use regex::Regex;
16
17pub struct PipeInput {
19 #[allow(dead_code)]
20 child: Child,
21 reader: BufReader<ChildStdout>,
22}
23
24pub enum OutputFile {
26 File(File),
27 Pipe(ChildStdin),
28}
29
30impl Write for OutputFile {
31 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
32 match self {
33 OutputFile::File(f) => f.write(buf),
34 OutputFile::Pipe(p) => p.write(buf),
35 }
36 }
37
38 fn flush(&mut self) -> std::io::Result<()> {
39 match self {
40 OutputFile::File(f) => f.flush(),
41 OutputFile::Pipe(p) => p.flush(),
42 }
43 }
44}
45
46thread_local! {
47 static MAIN_INPUT: Cell<Option<*mut (dyn BufRead + 'static)>> = const { Cell::new(None) };
64}
65
66struct MainInputGuard {
68 previous: Option<*mut (dyn BufRead + 'static)>,
69}
70
71impl MainInputGuard {
72 unsafe fn new(reader: &mut dyn BufRead) -> Self {
76 let ptr: *mut (dyn BufRead + '_) = reader;
77 let ptr: *mut (dyn BufRead + 'static) = unsafe { std::mem::transmute(ptr) };
79 let previous = MAIN_INPUT.with(|slot| slot.replace(Some(ptr)));
80 MainInputGuard { previous }
81 }
82}
83
84impl Drop for MainInputGuard {
85 fn drop(&mut self) {
86 MAIN_INPUT.with(|slot| slot.set(self.previous));
87 }
88}
89
90fn read_record_from(reader: &mut dyn BufRead, paragraph: bool) -> Result<Option<String>> {
96 let mut line = String::new();
97
98 if !paragraph {
99 let bytes_read = reader.read_line(&mut line).map_err(Error::Io)?;
100 if bytes_read == 0 {
101 return Ok(None);
102 }
103 if line.ends_with('\n') {
104 line.pop();
105 if line.ends_with('\r') {
106 line.pop();
107 }
108 }
109 return Ok(Some(line));
110 }
111
112 let mut record = String::new();
114 let mut in_record = false;
115
116 loop {
117 line.clear();
118 let bytes_read = reader.read_line(&mut line).map_err(Error::Io)?;
119
120 if bytes_read == 0 {
121 if in_record && !record.is_empty() {
123 return Ok(Some(record));
124 }
125 return Ok(None);
126 }
127
128 if line.trim().is_empty() {
129 if in_record && !record.is_empty() {
130 return Ok(Some(record));
131 }
132 continue;
134 }
135
136 if line.ends_with('\n') {
137 line.pop();
138 if line.ends_with('\r') {
139 line.pop();
140 }
141 }
142 if in_record {
143 record.push('\n');
144 }
145 record.push_str(&line);
146 in_record = true;
147 }
148}
149
150pub struct Interpreter<'a> {
152 program: &'a Program,
154
155 pub(crate) variables: HashMap<String, Value>,
157
158 pub(crate) arrays: HashMap<String, HashMap<String, Value>>,
160
161 pub(crate) functions: HashMap<String, &'a FunctionDef>,
163
164 pub(crate) fs: String,
167 pub(crate) ofs: String,
169 pub(crate) rs: String,
171 pub(crate) ors: String,
173 pub(crate) ofmt: String,
175 pub(crate) convfmt: String,
177 pub(crate) subsep: String,
179 pub(crate) fpat: String,
181 pub(crate) fieldwidths: String,
183
184 pub(crate) posix_mode: bool,
186 pub(crate) traditional_mode: bool,
187
188 pub(crate) record: String,
190 pub(crate) fields: Vec<String>,
192 pub(crate) nf: usize,
194 pub(crate) nr: usize,
196 pub(crate) fnr: usize,
198 pub(crate) filename: String,
200
201 pub(crate) filenames: Vec<String>,
204
205 pub(crate) rstart: usize,
207 pub(crate) rlength: i32,
208
209 should_exit: bool,
211 exit_code: i32,
212 should_next: bool,
213 should_nextfile: bool,
214
215 pub(crate) output_files: HashMap<String, OutputFile>,
217
218 pub(crate) input_files: HashMap<String, BufReader<File>>,
220
221 pub(crate) pipes: HashMap<String, PipeInput>,
223
224 pub(crate) regex_cache: HashMap<String, Regex>,
226
227 range_states: HashMap<usize, bool>,
229
230 pub(crate) rand_seed: u64,
232 pub(crate) rand_state: u64,
233
234 pub(crate) argc: usize,
236 pub(crate) argv: Vec<String>,
237
238 pub(crate) environ: HashMap<String, String>,
240
241 pub(crate) array_aliases: HashMap<String, String>,
244}
245
246impl<'a> Interpreter<'a> {
247 pub fn new(program: &'a Program) -> Self {
248 let mut functions = HashMap::new();
249 for func in &program.functions {
250 functions.insert(func.name.clone(), func);
251 }
252
253 let environ: HashMap<String, String> = std::env::vars().collect();
255
256 use std::time::{SystemTime, UNIX_EPOCH};
258 let rand_seed = SystemTime::now()
259 .duration_since(UNIX_EPOCH)
260 .map(|d| d.as_nanos() as u64)
261 .unwrap_or(12345);
262
263 Self {
264 program,
265 variables: HashMap::new(),
266 arrays: HashMap::new(),
267 functions,
268 fs: " ".to_string(),
269 ofs: " ".to_string(),
270 rs: "\n".to_string(),
271 ors: "\n".to_string(),
272 ofmt: "%.6g".to_string(),
273 convfmt: "%.6g".to_string(),
274 subsep: "\x1c".to_string(),
275 fpat: String::new(),
276 fieldwidths: String::new(),
277 posix_mode: false,
278 traditional_mode: false,
279 record: String::new(),
280 fields: Vec::new(),
281 nf: 0,
282 nr: 0,
283 fnr: 0,
284 filename: String::new(),
285 filenames: Vec::new(),
286 rstart: 0,
287 rlength: -1,
288 should_exit: false,
289 exit_code: 0,
290 should_next: false,
291 should_nextfile: false,
292 output_files: HashMap::new(),
293 input_files: HashMap::new(),
294 pipes: HashMap::new(),
295 regex_cache: HashMap::new(),
296 range_states: HashMap::new(),
297 rand_seed,
298 rand_state: rand_seed,
299 argc: 0,
300 argv: Vec::new(),
301 environ,
302 array_aliases: HashMap::new(),
303 }
304 }
305
306 pub fn set_args(&mut self, args: Vec<String>) {
308 self.argc = args.len();
309 self.argv = args;
310 }
311
312 pub fn set_fs(&mut self, fs: &str) {
314 self.fs = fs.to_string();
315 self.fpat.clear();
317 self.fieldwidths.clear();
318 }
319
320 pub fn set_posix_mode(&mut self, enabled: bool) {
322 self.posix_mode = enabled;
323 if enabled {
324 self.traditional_mode = false;
325 }
326 }
327
328 pub fn set_traditional_mode(&mut self, enabled: bool) {
330 self.traditional_mode = enabled;
331 if enabled {
332 self.posix_mode = false;
333 }
334 }
335
336 pub fn set_variable(&mut self, name: &str, value: &str) {
338 self.variables
339 .insert(name.to_string(), Value::from_string(value.to_string()));
340 }
341
342 pub fn set_filename(&mut self, filename: &str) {
344 self.filename = filename.to_string();
345 }
346
347 pub fn set_filenames(&mut self, filenames: Vec<String>) {
353 if let Some(first) = filenames.first() {
354 self.filename = first.clone();
355 }
356 self.filenames = filenames;
357 }
358
359 pub fn run<R: BufRead, W: Write>(&mut self, inputs: Vec<R>, output: &mut W) -> Result<i32> {
361 for rule in &self.program.rules {
363 if matches!(&rule.pattern, Some(Pattern::Begin)) {
364 if let Some(action) = &rule.action {
365 self.execute_block(action, output)?;
366 }
367 if self.should_exit {
368 return Ok(self.exit_code);
369 }
370 }
371 }
372
373 for (idx, input) in inputs.into_iter().enumerate() {
375 self.fnr = 0;
376
377 if let Some(name) = self.filenames.get(idx) {
379 self.filename = name.clone();
380 }
381
382 for rule in &self.program.rules {
384 if matches!(&rule.pattern, Some(Pattern::BeginFile)) {
385 if let Some(action) = &rule.action {
386 self.execute_block(action, output)?;
387 }
388 if self.should_exit {
389 return Ok(self.exit_code);
390 }
391 }
392 }
393
394 self.process_input(input, output)?;
395
396 for rule in &self.program.rules {
398 if matches!(&rule.pattern, Some(Pattern::EndFile)) {
399 if let Some(action) = &rule.action {
400 self.execute_block(action, output)?;
401 }
402 if self.should_exit {
403 return Ok(self.exit_code);
404 }
405 }
406 }
407
408 if self.should_exit {
409 return Ok(self.exit_code);
410 }
411 }
412
413 for rule in &self.program.rules {
415 if let (Some(Pattern::End), Some(action)) = (&rule.pattern, &rule.action) {
416 self.execute_block(action, output)?;
417 }
418 }
419
420 Ok(self.exit_code)
421 }
422
423 fn process_input<R: BufRead, W: Write>(&mut self, mut input: R, output: &mut W) -> Result<()> {
424 let _guard = unsafe { MainInputGuard::new(&mut input) };
427 self.process_input_loop(output)
428 }
429
430 fn process_input_loop<W: Write>(&mut self, output: &mut W) -> Result<()> {
433 while let Some(record) = self.next_main_record()? {
434 self.nr += 1;
435 self.fnr += 1;
436 self.set_record(&record);
437
438 self.process_current_record(output)?;
439
440 if self.should_nextfile {
441 self.should_nextfile = false;
442 break;
443 }
444
445 if self.should_exit {
446 break;
447 }
448 }
449
450 Ok(())
451 }
452
453 pub(crate) fn next_main_record(&mut self) -> Result<Option<String>> {
459 let paragraph = self.rs.is_empty();
460 let ptr = MAIN_INPUT.with(|slot| slot.get());
461 match ptr {
462 Some(ptr) => read_record_from(unsafe { &mut *ptr }, paragraph),
466 None => Ok(None),
467 }
468 }
469
470 fn process_current_record<W: Write>(&mut self, output: &mut W) -> Result<()> {
472 for (idx, rule) in self.program.rules.iter().enumerate() {
473 if matches!(
475 &rule.pattern,
476 Some(Pattern::Begin)
477 | Some(Pattern::End)
478 | Some(Pattern::BeginFile)
479 | Some(Pattern::EndFile)
480 ) {
481 continue;
482 }
483
484 let matches = self.pattern_matches(&rule.pattern, idx)?;
485 if matches {
486 if let Some(action) = &rule.action {
487 self.execute_block(action, output)?;
488 } else {
489 writeln!(output, "{}", self.record).map_err(Error::Io)?;
491 }
492 }
493
494 if self.should_next {
495 self.should_next = false;
496 break;
497 }
498
499 if self.should_nextfile || self.should_exit {
500 break;
501 }
502 }
503 Ok(())
504 }
505
506 pub(crate) fn set_record(&mut self, record: &str) {
507 self.record = record.to_string();
508 self.split_fields();
509 }
510
511 fn split_fields(&mut self) {
512 self.fields.clear();
513
514 if self.record.is_empty() {
515 self.nf = 0;
516 return;
517 }
518
519 let estimated_fields = self.record.len() / 8 + 1;
521 self.fields.reserve(estimated_fields.min(64));
522
523 if !self.fpat.is_empty() && !self.posix_mode && !self.traditional_mode {
525 self.split_fields_fpat();
526 return;
527 }
528
529 if !self.fieldwidths.is_empty() && !self.posix_mode && !self.traditional_mode {
531 self.split_fields_widths();
532 return;
533 }
534
535 if self.fs == " " {
537 self.fields
540 .extend(self.record.split_whitespace().map(String::from));
541 } else if self.fs.len() == 1 {
542 let sep = self.fs.as_bytes()[0];
544 let bytes = self.record.as_bytes();
545 let mut start = 0;
546
547 for (i, &b) in bytes.iter().enumerate() {
548 if b == sep {
549 self.fields.push(self.record[start..i].to_string());
550 start = i + 1;
551 }
552 }
553 self.fields.push(self.record[start..].to_string());
555 } else {
556 let fs = self.fs.clone();
558 let record = self.record.clone();
559 if let Some(regex) = self.regex_cache.get(&fs) {
560 self.fields.extend(regex.split(&record).map(String::from));
561 } else if let Ok(regex) = Regex::new(&fs) {
562 self.fields.extend(regex.split(&record).map(String::from));
563 self.regex_cache.insert(fs, regex);
564 } else {
565 self.fields.extend(record.split(&fs).map(String::from));
567 }
568 }
569
570 self.nf = self.fields.len();
571 }
572
573 fn split_fields_fpat(&mut self) {
575 let fpat = self.fpat.clone();
576 let record = self.record.clone();
577
578 if let Some(regex) = self.regex_cache.get(&fpat) {
579 for mat in regex.find_iter(&record) {
580 self.fields.push(mat.as_str().to_string());
581 }
582 } else if let Ok(regex) = Regex::new(&fpat) {
583 for mat in regex.find_iter(&record) {
584 self.fields.push(mat.as_str().to_string());
585 }
586 self.regex_cache.insert(fpat, regex);
587 }
588
589 self.nf = self.fields.len();
590 }
591
592 fn split_fields_widths(&mut self) {
594 let widths: Vec<usize> = self
595 .fieldwidths
596 .split_whitespace()
597 .filter_map(|s| s.parse().ok())
598 .collect();
599
600 let mut pos = 0;
601 let chars: Vec<char> = self.record.chars().collect();
602
603 for width in widths {
604 if pos >= chars.len() {
605 break;
606 }
607 let end = (pos + width).min(chars.len());
608 let field: String = chars[pos..end].iter().collect();
609 self.fields.push(field);
610 pos = end;
611 }
612
613 self.nf = self.fields.len();
614 }
615
616 #[inline]
617 pub(crate) fn get_field(&self, index: usize) -> String {
618 if index == 0 {
619 self.record.clone()
620 } else if index <= self.fields.len() {
621 self.fields[index - 1].clone()
622 } else {
623 String::new()
624 }
625 }
626
627 #[inline]
629 #[allow(dead_code)]
630 pub(crate) fn get_field_ref(&self, index: usize) -> &str {
631 if index == 0 {
632 &self.record
633 } else if index <= self.fields.len() {
634 &self.fields[index - 1]
635 } else {
636 ""
637 }
638 }
639
640 pub(crate) fn set_field(&mut self, index: usize, value: String) {
641 if index == 0 {
642 self.record = value;
643 self.split_fields();
644 } else {
645 while self.fields.len() < index {
647 self.fields.push(String::new());
648 }
649 self.fields[index - 1] = value;
650 self.nf = self.fields.len();
651 self.record = self.fields.join(&self.ofs);
653 }
654 }
655
656 fn pattern_matches(&mut self, pattern: &Option<Pattern>, rule_idx: usize) -> Result<bool> {
657 match pattern {
658 None => Ok(true), Some(Pattern::Begin)
660 | Some(Pattern::End)
661 | Some(Pattern::BeginFile)
662 | Some(Pattern::EndFile) => Ok(false),
663 Some(Pattern::Expr(expr)) => {
664 let val = self.eval_expr(expr)?;
665 Ok(val.is_truthy())
666 }
667 Some(Pattern::Regex(regex)) => {
668 let record = self.record.clone();
669 let re = self.get_regex(regex)?;
670 Ok(re.is_match(&record))
671 }
672 Some(Pattern::Range { start, end }) => {
673 let active = self.range_states.get(&rule_idx).copied().unwrap_or(false);
674 if !active {
675 if self.pattern_matches(&Some(start.as_ref().clone()), rule_idx)? {
677 self.range_states.insert(rule_idx, true);
678 return Ok(true);
679 }
680 Ok(false)
681 } else {
682 if self.pattern_matches(&Some(end.as_ref().clone()), rule_idx)? {
684 self.range_states.insert(rule_idx, false);
685 }
686 Ok(true)
687 }
688 }
689 Some(Pattern::And(left, right)) => Ok(self
690 .pattern_matches(&Some(left.as_ref().clone()), rule_idx)?
691 && self.pattern_matches(&Some(right.as_ref().clone()), rule_idx)?),
692 Some(Pattern::Or(left, right)) => Ok(self
693 .pattern_matches(&Some(left.as_ref().clone()), rule_idx)?
694 || self.pattern_matches(&Some(right.as_ref().clone()), rule_idx)?),
695 Some(Pattern::Not(inner)) => {
696 Ok(!self.pattern_matches(&Some(inner.as_ref().clone()), rule_idx)?)
697 }
698 }
699 }
700
701 pub(crate) fn get_regex(&mut self, pattern: &str) -> Result<&Regex> {
702 if !self.regex_cache.contains_key(pattern) {
703 let regex = Regex::new(pattern).map_err(Error::Regex)?;
704 self.regex_cache.insert(pattern.to_string(), regex);
705 }
706 Ok(self.regex_cache.get(pattern).unwrap())
707 }
708
709 pub(crate) fn get_variable(&self, name: &str) -> Value {
710 match name {
712 "NF" => Value::Number(self.nf as f64),
713 "NR" => Value::Number(self.nr as f64),
714 "FNR" => Value::Number(self.fnr as f64),
715 "FS" => Value::from_string(self.fs.clone()),
716 "OFS" => Value::from_string(self.ofs.clone()),
717 "RS" => Value::from_string(self.rs.clone()),
718 "ORS" => Value::from_string(self.ors.clone()),
719 "OFMT" => Value::from_string(self.ofmt.clone()),
720 "CONVFMT" => Value::from_string(self.convfmt.clone()),
721 "SUBSEP" => Value::from_string(self.subsep.clone()),
722 "FILENAME" => Value::from_string(self.filename.clone()),
723 "RSTART" => Value::Number(self.rstart as f64),
724 "RLENGTH" => Value::Number(self.rlength as f64),
725 "ARGC" => Value::Number(self.argc as f64),
726 "FPAT" => Value::from_string(self.fpat.clone()),
728 "FIELDWIDTHS" => Value::from_string(self.fieldwidths.clone()),
729 _ => self
730 .variables
731 .get(name)
732 .cloned()
733 .unwrap_or(Value::Uninitialized),
734 }
735 }
736
737 pub(crate) fn get_special_array(&self, array: &str, key: &str) -> Option<Value> {
739 match array {
740 "ARGV" => key
741 .parse::<usize>()
742 .ok()
743 .and_then(|i| self.argv.get(i))
744 .map(|s| Value::from_string(s.clone())),
745 "ENVIRON" => self.environ.get(key).map(|s| Value::from_string(s.clone())),
746 "PROCINFO" => {
747 match key {
749 "version" => Some(Value::from_string(env!("CARGO_PKG_VERSION").to_string())),
750 "strftime" => Some(Value::from_string("%a %b %e %H:%M:%S %Z %Y".to_string())),
751 "FS" => {
752 if !self.fpat.is_empty() {
753 Some(Value::from_string("FPAT".to_string()))
754 } else if !self.fieldwidths.is_empty() {
755 Some(Value::from_string("FIELDWIDTHS".to_string()))
756 } else {
757 Some(Value::from_string("FS".to_string()))
758 }
759 }
760 "identifiers" => Some(Value::Number(0.0)), "pid" => Some(Value::Number(std::process::id() as f64)),
762 "ppid" => Some(Value::Number(0.0)), "uid" => Some(Value::Number(0.0)), "gid" => Some(Value::Number(0.0)), "euid" => Some(Value::Number(0.0)), "egid" => Some(Value::Number(0.0)), "pgrpid" => Some(Value::Number(0.0)), _ => Some(Value::Uninitialized),
769 }
770 }
771 _ => None,
772 }
773 }
774
775 pub(crate) fn set_variable_value(&mut self, name: &str, value: Value) {
776 match name {
778 "NF" => {
779 let new_nf = value.to_number() as usize;
780 if new_nf < self.nf {
781 self.fields.truncate(new_nf);
782 } else {
783 while self.fields.len() < new_nf {
784 self.fields.push(String::new());
785 }
786 }
787 self.nf = new_nf;
788 self.record = self.fields.join(&self.ofs);
789 }
790 "FS" => {
791 self.fs = value.to_string_val();
792 self.fpat.clear();
794 self.fieldwidths.clear();
795 }
796 "OFS" => self.ofs = value.to_string_val(),
797 "RS" => self.rs = value.to_string_val(),
798 "ORS" => self.ors = value.to_string_val(),
799 "OFMT" => self.ofmt = value.to_string_val(),
800 "CONVFMT" => self.convfmt = value.to_string_val(),
801 "SUBSEP" => self.subsep = value.to_string_val(),
802 "FPAT" => {
804 self.fpat = value.to_string_val();
805 self.fieldwidths.clear();
807 }
808 "FIELDWIDTHS" => {
809 self.fieldwidths = value.to_string_val();
810 self.fpat.clear();
812 }
813 _ => {
814 self.variables.insert(name.to_string(), value);
815 }
816 }
817 }
818
819 fn resolve_array_name<'b>(&'b self, array: &'b str) -> &'b str {
821 self.array_aliases
822 .get(array)
823 .map(|s| s.as_str())
824 .unwrap_or(array)
825 }
826
827 pub(crate) fn get_array_element(&self, array: &str, key: &str) -> Value {
828 let array = self.resolve_array_name(array);
829
830 if let Some(val) = self.get_special_array(array, key) {
832 return val;
833 }
834
835 self.arrays
836 .get(array)
837 .and_then(|arr| arr.get(key))
838 .cloned()
839 .unwrap_or(Value::Uninitialized)
840 }
841
842 pub(crate) fn set_array_element(&mut self, array: &str, key: &str, value: Value) {
843 let array = self.resolve_array_name(array).to_string();
844 self.arrays
845 .entry(array)
846 .or_default()
847 .insert(key.to_string(), value);
848 }
849
850 pub(crate) fn array_key_exists(&self, array: &str, key: &str) -> bool {
851 let array = self.resolve_array_name(array);
852
853 match array {
855 "ARGV" => key
856 .parse::<usize>()
857 .ok()
858 .map(|i| i < self.argv.len())
859 .unwrap_or(false),
860 "ENVIRON" => self.environ.contains_key(key),
861 _ => self
862 .arrays
863 .get(array)
864 .map(|arr| arr.contains_key(key))
865 .unwrap_or(false),
866 }
867 }
868
869 pub(crate) fn delete_array_element(&mut self, array: &str, key: &str) {
870 let array = self.resolve_array_name(array).to_string();
871 if let Some(arr) = self.arrays.get_mut(&array) {
872 arr.remove(key);
873 }
874 }
875
876 pub(crate) fn make_array_key(&self, indices: &[Value]) -> String {
877 indices
878 .iter()
879 .map(|v| self.to_conv_str(v))
880 .collect::<Vec<_>>()
881 .join(&self.subsep)
882 }
883
884 #[inline]
887 pub(crate) fn to_conv_str(&self, value: &Value) -> String {
888 value.to_string_with_format(&self.convfmt)
889 }
890
891 #[inline]
893 pub(crate) fn to_output_str(&self, value: &Value) -> String {
894 value.to_string_with_format(&self.ofmt)
895 }
896}
897
898#[cfg(test)]
899mod tests {
900 use super::*;
901 use crate::lexer::Lexer;
902 use crate::parser::Parser;
903 use std::io::Cursor;
904
905 fn run_awk(program: &str, input: &str) -> String {
906 let mut lexer = Lexer::new(program);
907 let tokens = lexer.tokenize().unwrap();
908 let mut parser = Parser::new(tokens);
909 let ast = parser.parse().unwrap();
910
911 let mut interpreter = Interpreter::new(&ast);
912 let mut output = Vec::new();
913 let inputs: Vec<std::io::BufReader<Cursor<&str>>> = if input.is_empty() {
914 vec![]
915 } else {
916 vec![std::io::BufReader::new(Cursor::new(input))]
917 };
918
919 interpreter.run(inputs, &mut output).unwrap();
920 String::from_utf8(output).unwrap()
921 }
922
923 #[test]
924 fn test_begin_print() {
925 let output = run_awk(r#"BEGIN { print "hello" }"#, "");
926 assert_eq!(output, "hello\n");
927 }
928
929 #[test]
930 fn test_print_field() {
931 let output = run_awk("{ print $1 }", "one two three");
932 assert_eq!(output, "one\n");
933 }
934
935 #[test]
936 fn test_print_multiple_fields() {
937 let output = run_awk("{ print $1, $3 }", "one two three");
938 assert_eq!(output, "one three\n");
939 }
940
941 #[test]
942 fn test_arithmetic() {
943 let output = run_awk("BEGIN { print 2 + 3 * 4 }", "");
944 assert_eq!(output, "14\n");
945 }
946
947 #[test]
948 fn test_variable() {
949 let output = run_awk("BEGIN { x = 5; print x }", "");
950 assert_eq!(output, "5\n");
951 }
952
953 #[test]
954 fn test_if_statement() {
955 let output = run_awk("BEGIN { x = 10; if (x > 5) print \"big\" }", "");
956 assert_eq!(output, "big\n");
957 }
958
959 #[test]
960 fn test_while_loop() {
961 let output = run_awk("BEGIN { i = 1; while (i <= 3) { print i; i++ } }", "");
962 assert_eq!(output, "1\n2\n3\n");
963 }
964
965 #[test]
966 fn test_pattern_match() {
967 let output = run_awk("/two/ { print $0 }", "one\ntwo\nthree");
968 assert_eq!(output, "two\n");
969 }
970
971 #[test]
972 fn test_for_loop() {
973 let output = run_awk("BEGIN { for (i = 1; i <= 3; i++) print i }", "");
974 assert_eq!(output, "1\n2\n3\n");
975 }
976
977 #[test]
978 fn test_for_in_loop() {
979 let output = run_awk(
980 "BEGIN { a[1]=1; a[2]=2; for (k in a) count++; print count }",
981 "",
982 );
983 assert_eq!(output, "2\n");
984 }
985
986 #[test]
987 fn test_do_while() {
988 let output = run_awk("BEGIN { i = 0; do { i++ } while (i < 3); print i }", "");
989 assert_eq!(output, "3\n");
990 }
991
992 #[test]
993 fn test_break() {
994 let output = run_awk(
995 "BEGIN { for (i=1; i<=10; i++) { if (i==3) break; print i } }",
996 "",
997 );
998 assert_eq!(output, "1\n2\n");
999 }
1000
1001 #[test]
1002 fn test_continue() {
1003 let output = run_awk(
1004 "BEGIN { for (i=1; i<=3; i++) { if (i==2) continue; print i } }",
1005 "",
1006 );
1007 assert_eq!(output, "1\n3\n");
1008 }
1009
1010 #[test]
1011 fn test_next() {
1012 let output = run_awk("/skip/ { next } { print }", "one\nskip\ntwo");
1013 assert_eq!(output, "one\ntwo\n");
1014 }
1015
1016 #[test]
1017 fn test_exit() {
1018 let output = run_awk("NR == 2 { exit } { print }", "one\ntwo\nthree");
1020 assert_eq!(output, "one\n");
1021 }
1022
1023 #[test]
1024 fn test_exit_in_end() {
1025 let output = run_awk("{ print } END { print \"done\" }", "one\ntwo");
1026 assert!(output.contains("done"));
1027 }
1028
1029 #[test]
1030 fn test_array_access() {
1031 let output = run_awk("BEGIN { a[\"x\"] = 1; print a[\"x\"] }", "");
1032 assert_eq!(output, "1\n");
1033 }
1034
1035 #[test]
1036 fn test_array_in() {
1037 let output = run_awk("BEGIN { a[1]=1; print (1 in a), (2 in a) }", "");
1038 assert_eq!(output, "1 0\n");
1039 }
1040
1041 #[test]
1042 fn test_delete() {
1043 let output = run_awk(
1044 "BEGIN { a[1]=1; a[2]=2; delete a[1]; for(k in a) print k }",
1045 "",
1046 );
1047 assert_eq!(output, "2\n");
1048 }
1049
1050 #[test]
1051 fn test_special_variables() {
1052 let output = run_awk("{ print NR, NF, $0 }", "a b c");
1053 assert_eq!(output, "1 3 a b c\n");
1054 }
1055
1056 #[test]
1057 fn test_fs_change() {
1058 let output = run_awk("BEGIN { FS = \":\" } { print $1 }", "a:b:c");
1059 assert_eq!(output, "a\n");
1060 }
1061
1062 #[test]
1063 fn test_ofs() {
1064 let output = run_awk("BEGIN { OFS = \"-\" } { print $1, $2 }", "a b c");
1065 assert_eq!(output, "a-b\n");
1066 }
1067
1068 #[test]
1069 fn test_nf_access() {
1070 let output = run_awk("{ print $NF }", "a b c");
1071 assert_eq!(output, "c\n");
1072 }
1073
1074 #[test]
1075 fn test_field_modify() {
1076 let output = run_awk("{ $2 = \"X\"; print $0 }", "a b c");
1077 assert_eq!(output, "a X c\n");
1078 }
1079
1080 #[test]
1081 fn test_user_function() {
1082 let output = run_awk(
1083 "function double(x) { return x*2 } BEGIN { print double(5) }",
1084 "",
1085 );
1086 assert_eq!(output, "10\n");
1087 }
1088
1089 #[test]
1090 fn test_recursion() {
1091 let output = run_awk(
1092 "function fact(n) { return n<=1 ? 1 : n*fact(n-1) } BEGIN { print fact(5) }",
1093 "",
1094 );
1095 assert_eq!(output, "120\n");
1096 }
1097
1098 #[test]
1099 fn test_printf() {
1100 let output = run_awk("BEGIN { printf \"%d %s\\n\", 42, \"hello\" }", "");
1101 assert_eq!(output, "42 hello\n");
1102 }
1103
1104 #[test]
1105 fn test_range_pattern() {
1106 let output = run_awk(
1107 "/start/,/end/ { print }",
1108 "before\nstart\nmiddle\nend\nafter",
1109 );
1110 assert_eq!(output, "start\nmiddle\nend\n");
1111 }
1112
1113 #[test]
1114 fn test_compound_pattern_and() {
1115 let output = run_awk("NR > 1 && NR < 4 { print }", "one\ntwo\nthree\nfour");
1116 assert_eq!(output, "two\nthree\n");
1117 }
1118
1119 #[test]
1120 fn test_logical_or_in_expr() {
1121 let output = run_awk("{ if (/a/ || /c/) print }", "a\nb\nc");
1122 assert_eq!(output, "a\nc\n");
1123 }
1124
1125 #[test]
1126 fn test_negated_pattern() {
1127 let output = run_awk("!/skip/ { print }", "keep\nskip\nkeep");
1128 assert_eq!(output, "keep\nkeep\n");
1129 }
1130
1131 #[test]
1132 fn test_builtin_length() {
1133 let output = run_awk("BEGIN { print length(\"hello\") }", "");
1134 assert_eq!(output, "5\n");
1135 }
1136
1137 #[test]
1138 fn test_builtin_substr() {
1139 let output = run_awk("BEGIN { print substr(\"hello\", 2, 3) }", "");
1140 assert_eq!(output, "ell\n");
1141 }
1142
1143 #[test]
1144 fn test_builtin_index() {
1145 let output = run_awk("BEGIN { print index(\"hello\", \"ll\") }", "");
1146 assert_eq!(output, "3\n");
1147 }
1148
1149 #[test]
1150 fn test_builtin_split() {
1151 let output = run_awk(
1152 "BEGIN { n = split(\"a:b:c\", arr, \":\"); print n, arr[1], arr[2] }",
1153 "",
1154 );
1155 assert_eq!(output, "3 a b\n");
1156 }
1157
1158 #[test]
1159 fn test_builtin_sub() {
1160 let output = run_awk("BEGIN { x = \"hello\"; sub(\"l\", \"L\", x); print x }", "");
1161 assert_eq!(output, "heLlo\n");
1162 }
1163
1164 #[test]
1165 fn test_builtin_gsub() {
1166 let output = run_awk(
1167 "BEGIN { x = \"hello\"; gsub(\"l\", \"L\", x); print x }",
1168 "",
1169 );
1170 assert_eq!(output, "heLLo\n");
1171 }
1172
1173 #[test]
1174 fn test_builtin_match() {
1175 let output = run_awk("BEGIN { print match(\"hello\", \"ll\") }", "");
1176 assert_eq!(output, "3\n");
1177 }
1178
1179 #[test]
1180 fn test_builtin_sprintf() {
1181 let output = run_awk("BEGIN { print sprintf(\"%05d\", 42) }", "");
1182 assert_eq!(output, "00042\n");
1183 }
1184
1185 #[test]
1186 fn test_builtin_tolower() {
1187 let output = run_awk("BEGIN { print tolower(\"HELLO\") }", "");
1188 assert_eq!(output, "hello\n");
1189 }
1190
1191 #[test]
1192 fn test_builtin_toupper() {
1193 let output = run_awk("BEGIN { print toupper(\"hello\") }", "");
1194 assert_eq!(output, "HELLO\n");
1195 }
1196
1197 #[test]
1198 fn test_builtin_math() {
1199 let output = run_awk("BEGIN { print int(3.7), sqrt(4), sin(0) }", "");
1200 assert_eq!(output, "3 2 0\n");
1201 }
1202
1203 #[test]
1204 fn test_ternary() {
1205 let output = run_awk("BEGIN { print 1 ? \"yes\" : \"no\" }", "");
1206 assert_eq!(output, "yes\n");
1207 }
1208
1209 #[test]
1210 fn test_concatenation() {
1211 let output = run_awk("BEGIN { print \"a\" \"b\" \"c\" }", "");
1212 assert_eq!(output, "abc\n");
1213 }
1214
1215 #[test]
1216 fn test_unary_ops() {
1217 let output = run_awk("BEGIN { x = 5; print -x, +x, !0 }", "");
1218 assert_eq!(output, "-5 5 1\n");
1219 }
1220
1221 #[test]
1222 fn test_post_increment() {
1223 let output = run_awk("BEGIN { x = 5; print x++ \" \" x }", "");
1224 assert_eq!(output, "5 6\n");
1225 }
1226
1227 #[test]
1228 fn test_pre_increment() {
1229 let output = run_awk("BEGIN { x = 5; print ++x }", "");
1230 assert_eq!(output, "6\n");
1231 }
1232
1233 #[test]
1234 fn test_compound_assign() {
1235 let output = run_awk("BEGIN { x = 10; x += 5; x -= 3; x *= 2; print x }", "");
1236 assert_eq!(output, "24\n");
1237 }
1238
1239 #[test]
1240 fn test_getline_var() {
1241 let output = run_awk("{ getline next_line; print $0, next_line }", "a\nb");
1243 assert_eq!(output, "a b\n");
1244 }
1245
1246 #[test]
1247 fn test_getline_var_updates_nr_but_not_record() {
1248 let output = run_awk(
1250 "{ getline v; print $0 \"|\" v \"|\" NR \"|\" NF }",
1251 "a b\nc d",
1252 );
1253 assert_eq!(output, "a b|c d|2|2\n");
1254 }
1255
1256 #[test]
1257 fn test_bare_getline_sets_record() {
1258 let output = run_awk("{ getline; print $0, NF, NR }", "a\nx y z");
1260 assert_eq!(output, "x y z 3 2\n");
1261 }
1262
1263 #[test]
1264 fn test_bare_getline_returns_zero_at_eof() {
1265 let output = run_awk("{ r = getline; print r, $0 }", "a\nb\nc");
1266 assert_eq!(output, "1 b\n0 c\n");
1267 }
1268
1269 #[test]
1270 fn test_getline_in_begin_without_input_is_eof() {
1271 let output = run_awk("BEGIN { print getline }", "");
1273 assert_eq!(output, "0\n");
1274 }
1275
1276 #[test]
1277 fn test_begin_end_run_once_over_multiple_inputs() {
1278 let mut lexer = Lexer::new(r#"BEGIN { print "hi" } END { print NR }"#);
1279 let tokens = lexer.tokenize().unwrap();
1280 let mut parser = Parser::new(tokens);
1281 let ast = parser.parse().unwrap();
1282
1283 let mut interpreter = Interpreter::new(&ast);
1284 interpreter.set_filenames(vec!["one".to_string(), "two".to_string()]);
1285 let mut output = Vec::new();
1286 let inputs = vec![
1287 std::io::BufReader::new(Cursor::new("a\nb\n")),
1288 std::io::BufReader::new(Cursor::new("c\n")),
1289 ];
1290 interpreter.run(inputs, &mut output).unwrap();
1291
1292 assert_eq!(String::from_utf8(output).unwrap(), "hi\n3\n");
1293 }
1294
1295 #[test]
1296 fn test_filename_and_fnr_track_each_input() {
1297 let mut lexer = Lexer::new("{ print FILENAME, NR, FNR }");
1298 let tokens = lexer.tokenize().unwrap();
1299 let mut parser = Parser::new(tokens);
1300 let ast = parser.parse().unwrap();
1301
1302 let mut interpreter = Interpreter::new(&ast);
1303 interpreter.set_filenames(vec!["one".to_string(), "two".to_string()]);
1304 let mut output = Vec::new();
1305 let inputs = vec![
1306 std::io::BufReader::new(Cursor::new("a\nb\n")),
1307 std::io::BufReader::new(Cursor::new("c\n")),
1308 ];
1309 interpreter.run(inputs, &mut output).unwrap();
1310
1311 assert_eq!(
1312 String::from_utf8(output).unwrap(),
1313 "one 1 1\none 2 2\ntwo 3 1\n"
1314 );
1315 }
1316
1317 #[test]
1318 fn test_fpat() {
1319 let output = run_awk("BEGIN { FPAT = \"[^,]+\" } { print $1, $2 }", "a,b,c");
1320 assert_eq!(output, "a b\n");
1321 }
1322
1323 #[test]
1324 fn test_fieldwidths() {
1325 let output = run_awk(
1326 "BEGIN { FIELDWIDTHS = \"2 3 2\" } { print $1, $2 }",
1327 "abcdefg",
1328 );
1329 assert_eq!(output, "ab cde\n");
1330 }
1331
1332 #[test]
1333 fn test_paragraph_mode() {
1334 let output = run_awk("BEGIN { RS = \"\" } { print NR, NF }", "a b\nc d\n\ne f");
1335 assert!(output.contains("1"));
1337 }
1338}