interpreter: print statements; calling conventions

This commit is contained in:
Guillem L. Jara
2026-05-29 02:06:25 +02:00
parent 1df4886ac5
commit b23d1e317c
5 changed files with 104 additions and 13 deletions
+12 -2
View File
@@ -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<Redirection>);
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",
+40 -6
View File
@@ -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<T>(
&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<T>(self, code: &mut Code, f: impl FnOnce(&mut Code) -> T) -> Self {
f(code);
fn scope<T>(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<T>(self, code: &mut Code, f: impl FnOnce(&mut Code) -> T) {
f(code);
+2 -4
View File
@@ -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(()),
}
}
}
+47 -1
View File
@@ -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<Identifier<'a>, Value<'a>, RandomState, &'a Bump>,
// separate table for cheap invalidation. It's an arena _visibly shrugs_.
records: HashMap<usize, Value<'a>, 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<Redirection>) {
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<Reg>) -> &[Value<'a>] {
&self.0[regs.start.0 as usize..regs.end.0 as _]
}
}
impl Display for Interpreter<'_> {
+3
View File
@@ -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,