From b23d1e317c2688752aee6b51ac2b08e16e707284 Mon Sep 17 00:00:00 2001 From: "Guillem L. Jara" <4lon3ly0@tutanota.com> Date: Fri, 29 May 2026 02:06:12 +0200 Subject: [PATCH] interpreter: print statements; calling conventions --- interpreter/src/ir.rs | 14 +++++++++-- interpreter/src/ir/lower.rs | 46 ++++++++++++++++++++++++++++++----- interpreter/src/types.rs | 6 ++--- interpreter/src/vm.rs | 48 ++++++++++++++++++++++++++++++++++++- parser/src/ast.rs | 3 +++ 5 files changed, 104 insertions(+), 13 deletions(-) diff --git a/interpreter/src/ir.rs b/interpreter/src/ir.rs index 00927ac..4c688c1 100644 --- a/interpreter/src/ir.rs +++ b/interpreter/src/ir.rs @@ -15,6 +15,7 @@ pub mod lower; use std::fmt::{Debug, Display}; pub use lower::test_interpreter; +use parser::{Command, Redirection}; #[derive(Clone, Copy, Debug)] #[repr(transparent)] @@ -72,6 +73,7 @@ pub enum Instruction { StoreUserArray(MemArrayArg), StoreBuiltinArray(MemArrayArg), IntrinsicCall(CallArgs), + OutputCall(OutputCallArgs), UserCall(IndCallArgs), IndirectCall(CallArgs), Jump(JumpArg), @@ -88,8 +90,9 @@ pub type MemArrayArg = (Reg, Reg, NonLocal); pub type JumpArg = Label; pub type RetArg = Reg; pub type BranchArg = (Reg, Label, Label); -pub type CallArgs = (Reg, NonLocal, ArgCount); -pub type IndCallArgs = (Reg, Reg, ArgCount); +pub type CallArgs = (Reg, Reg, NonLocal); +pub type OutputCallArgs = (Reg, Reg, Command, Option); +pub type IndCallArgs = (Reg, Reg, Reg); impl Instruction { fn set_label(&mut self, label: Label) { @@ -166,6 +169,12 @@ impl Display for Instruction { Self::IntrinsicCall((dest, code, args)) | Self::IndirectCall((dest, code, args)) => { write!(f, "{dest} <- {op} {code}, {args}") } + Self::OutputCall((start, end, call, Some(redir))) => { + write!(f, "{call}{redir:?} {start}, {end}") + } + Self::OutputCall((start, end, call, None)) => { + write!(f, "{call} {start}, {end}") + } Self::UserCall((dest, src, args)) => { write!(f, "{dest} <- {op} {src}, {args}") } @@ -210,6 +219,7 @@ impl Instruction { Self::IntrinsicCall(_) => "icall", Self::UserCall(_) => "ucall", Self::IndirectCall(_) => "vcall", + Self::OutputCall(_) => "out", Self::Jump(_) => "jmp", Self::Return(_) => "ret", Self::Branch(_) => "brif", diff --git a/interpreter/src/ir/lower.rs b/interpreter/src/ir/lower.rs index eedf85c..7178a19 100644 --- a/interpreter/src/ir/lower.rs +++ b/interpreter/src/ir/lower.rs @@ -121,6 +121,19 @@ impl<'a> Code<'a> { let reg = self.lower_expr(expr); self.free_reg(reg); } + Statement::Simple(SimpleStatement::Command { name, args, redirection }) => { + let (call_start, call_end, redir) = self.gen_call_convention(args, |this| { + redirection.as_ref().map(|(r, expr)| { + let redir_reg = this.alloc_reg(); + this.lower_expr_into(expr, *redir_reg); + this.free_reg(redir_reg); + *r + }) + }); + self.bc.emit(Instruction::OutputCall(( + call_start, call_end, *name, redir, + ))); + } _ => todo!(), } } @@ -187,9 +200,11 @@ impl<'a> Code<'a> { Label(0), ))); - state = state.scope(self, |c| { - c.lower_expr_into(true_then, dest); - }); + state = state + .scope(self, |c| { + c.lower_expr_into(true_then, dest); + }) + .0; let jump = self.bc.emit(Instruction::Jump(Label(0))); let label = self.following_instr(0); @@ -340,6 +355,25 @@ impl<'a> Code<'a> { }) } + fn gen_call_convention( + &mut self, + args: &[Expr<'_>], + extra: impl FnOnce(&mut Code) -> T, + ) -> (Reg, Reg, T) { + RegsState::new(self) + .scope(self, |this| { + let call_start = this.reg_pointer; + let call_end = call_start + args.len() as u16; + + this.reg_pointer = call_end; + for (i, arg) in args.iter().enumerate() { + this.lower_expr_into(arg, Reg(call_start + i as u16)); + } + (Reg(call_start), Reg(call_end), extra(this)) + }) + .1 + } + fn free_reg(&mut self, reg: LinearReg) { self.free_regs.push(reg.into_inner()); } @@ -391,12 +425,12 @@ impl RegsState { n_free_regs: code.free_regs.len(), } } - fn scope(self, code: &mut Code, f: impl FnOnce(&mut Code) -> T) -> Self { - f(code); + fn scope(self, code: &mut Code, f: impl FnOnce(&mut Code) -> T) -> (Self, T) { + let ret = f(code); let old = code.reg_pointer; code.reg_pointer = self.reg_pointer; code.free_regs.truncate(self.n_free_regs); - Self { reg_pointer: old, n_free_regs: self.n_free_regs } + (Self { reg_pointer: old, ..self }, ret) } fn scope_hwm(self, code: &mut Code, f: impl FnOnce(&mut Code) -> T) { f(code); diff --git a/interpreter/src/types.rs b/interpreter/src/types.rs index 379f46d..ad698a9 100644 --- a/interpreter/src/types.rs +++ b/interpreter/src/types.rs @@ -246,12 +246,10 @@ impl Display for Value<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Value::Float(n) => <_ as Display>::fmt(n, f), - Value::String(s) => write!(f, "{:?}", String::from_utf8_lossy(s)), + Value::String(s) => write!(f, "{}", String::from_utf8_lossy(s)), Value::Regex(s) => write!(f, "/{}/", String::from_utf8_lossy(s)), &Value::Bool(b) => write!(f, "{}", b as usize), - Value::Array(_) => write!(f, "array"), - Value::Untyped => write!(f, "untyped"), - Value::Unassigned => write!(f, "unassigned"), + _ => Ok(()), } } } diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 6434aa0..06776ad 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -5,7 +5,9 @@ use std::{ fmt::{self, Display}, + io::{self, Write}, mem::replace, + ops::Range, vec::Vec as StdVec, }; @@ -13,7 +15,7 @@ use ahash::RandomState; use bumpalo::{Bump, collections::Vec}; use hashbrown::HashMap; use indexmap_allocator_api::{IndexMap, IndexSet}; -use parser::Identifier; +use parser::{Command, Identifier, Redirection}; use crate::{ ir::{ @@ -49,6 +51,8 @@ pub struct SymbolTable<'a> { user: IndexMap, Value<'a>, RandomState, &'a Bump>, // separate table for cheap invalidation. It's an arena _visibly shrugs_. records: HashMap, RandomState, &'a Bump>, + ofs: Value<'a>, + rfs: Value<'a>, // etc } @@ -74,6 +78,8 @@ impl<'a> SymbolTable<'a> { Self { user: IndexMap::new_in(arena), records: HashMap::with_hasher_in(RandomState::new(), arena), + ofs: Value::String(b" ".into()), + rfs: Value::String(b"\n".into()), } } @@ -97,6 +103,12 @@ impl<'a> SymbolTable<'a> { NonLocal(self.user.insert_full(ident, Value::Untyped).0 as _) } } + + pub fn record(&self, value: Value<'a>) -> &Value<'a> { + self.records + .get(&(value.to_num() as usize)) + .unwrap_or(&Value::Unassigned) + } } impl<'a> Consts<'a> { @@ -188,6 +200,9 @@ impl Interpreter<'_> { Instruction::StoreBuiltinScalar((_dest, _src)) => todo!(), Instruction::StoreBuiltinArray((_dest, _src, _place)) => todo!(), Instruction::IntrinsicCall((_dest, _code, _args)) => todo!(), + Instruction::OutputCall((start, end, fun, redir)) => { + self.intrinsic_print(start, end, fun, redir); + } Instruction::UserCall((_dest, _code, _args)) => todo!(), Instruction::IndirectCall((_dest, _code, _args)) => todo!(), Instruction::Jump(Label(label)) => { @@ -207,6 +222,34 @@ impl Interpreter<'_> { self.program_counter += 1; } } + + fn intrinsic_print(&mut self, start: Reg, end: Reg, fun: Command, redir: Option) { + let Command::Print = fun else { todo!() }; + let None = redir else { todo!() }; + let out = &mut io::stdout().lock(); + let range = self.registers.get_range(start..end); + + if range.is_empty() { + let record = self.symbols.record(Value::Float(0.)); + self.write_fmt(out, format_args!("{record}")); + } else { + for reg in range { + self.write_fmt(out, format_args!("{ofs}{reg}", ofs = self.symbols.ofs)); + } + } + self.write_fmt(out, format_args!("{rfs}", rfs = self.symbols.rfs)); + } + + fn write_fmt(&self, out: &mut impl Write, args: fmt::Arguments<'_>) { + if let Err(e) = out.write_fmt(args) + && e.kind() != io::ErrorKind::BrokenPipe + { + let _ = write!( + io::stderr().lock(), + "awk: warning: error writing to standard output: {e}" + ); + } + } } impl<'a> Registers<'a> { @@ -223,6 +266,9 @@ impl<'a> Registers<'a> { fn write(&mut self, dest: Reg, src: Value<'a>) { self.0[dest.0 as usize] = src; } + fn get_range(&self, regs: Range) -> &[Value<'a>] { + &self.0[regs.start.0 as usize..regs.end.0 as _] + } } impl Display for Interpreter<'_> { diff --git a/parser/src/ast.rs b/parser/src/ast.rs index af3e864..7de8f1d 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -168,6 +168,8 @@ pub enum Place<'a> { } /// GNU docs: https://www.gnu.org/software/gawk/manual/html_node/Redirection.html +#[derive(Clone, Copy)] +#[repr(u8)] pub enum Redirection { WriteFile, AppendFile, @@ -250,6 +252,7 @@ pub struct Function<'a> { } #[derive(Debug, Clone, Copy)] +#[repr(u8)] pub enum Command { Print, Printf,