diff --git a/interpreter/src/ir.rs b/interpreter/src/ir.rs index 85fa458..0efc859 100644 --- a/interpreter/src/ir.rs +++ b/interpreter/src/ir.rs @@ -16,6 +16,8 @@ use std::fmt::{Debug, Display}; pub use lower::test_interpreter; +use crate::ir::lower::ValueContext; + #[derive(Clone, Copy, Debug)] #[repr(transparent)] pub struct NonLocal(pub u16); @@ -140,13 +142,13 @@ impl Instruction { } } - fn load_store(opcode: impl Into, dest: Reg, src: NonLocal) -> Self { + fn load_store(opcode: impl Into, dest: Reg, src: NonLocal, ctx: ValueContext) -> Self { let opcode = opcode.into(); debug_assert!(opcode.is_load_store()); Self { opcode, args: Arguments { load_store: (dest, src) }, - hint: Hint::None, + hint: ctx.into(), } } @@ -252,6 +254,8 @@ pub enum Hint { UnboxedFloat64, UnboxedLhsFloat64, UnboxedRhsFloat64, + ScalarCtx, + ArrayCtx, } trait HintedReg { @@ -259,6 +263,26 @@ trait HintedReg { fn hint(&self) -> Hint; } +impl From for Hint { + fn from(value: ValueContext) -> Self { + match value { + ValueContext::Scalar => Self::ScalarCtx, + ValueContext::Array => Self::ArrayCtx, + ValueContext::Untyped => Self::None, + } + } +} + +impl From for ValueContext { + fn from(value: Hint) -> Self { + match value { + Hint::ScalarCtx => Self::Scalar, + Hint::ArrayCtx => Self::Array, + _ => Self::Untyped, + } + } +} + impl Debug for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Instruction::{:?}", self.opcode)?; @@ -346,6 +370,8 @@ impl Display for Instruction { Hint::UnboxedFloat64 => write!(f, " @ all_unboxedf64"), Hint::UnboxedLhsFloat64 => write!(f, " @ lhs_unboxedf64"), Hint::UnboxedRhsFloat64 => write!(f, " @ rhs_unboxedf64"), + Hint::ScalarCtx => write!(f, "@ scalar_ctx"), + Hint::ArrayCtx => write!(f, "@ array_ctx"), Hint::None => Ok(()), } } diff --git a/interpreter/src/ir/lower.rs b/interpreter/src/ir/lower.rs index 6bc11d7..73a601d 100644 --- a/interpreter/src/ir/lower.rs +++ b/interpreter/src/ir/lower.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // files that was distributed with this source code. -use std::{hash::Hash, mem::forget}; +use std::mem::forget; use bumpalo::{Bump, collections::Vec}; use parser::{ @@ -13,7 +13,8 @@ use parser::{ use crate::{ ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg}, - vm::{Consts, ExecMode, Interpreter, SymbolTable, Value}, + types::Value, + vm::{Consts, ExecMode, Interpreter, SymbolTable}, }; #[derive(Debug)] @@ -30,6 +31,13 @@ pub struct Code<'arena> { #[derive(Debug)] struct LinearReg(Reg, Hint); +#[derive(Clone, Copy)] +pub enum ValueContext { + Untyped, + Scalar, + Array, +} + impl Code<'_> { fn lower_body(&mut self, body: &Body) { for stmnt in &body.0 { @@ -41,7 +49,7 @@ impl Code<'_> { match stmnt { Statement::If { condition, then_body, else_body } => { let mut state = RegsState::new(self); - let condition = self.lower_expr(condition); + let condition = self.lower_expr(condition, ValueContext::Scalar); let label_then = self.following_instr(1); let if_label = self.bc @@ -60,7 +68,7 @@ impl Code<'_> { } Statement::While { condition, then_body } => { let cond_label = self.following_instr(0); - let condition = self.lower_expr(condition); + let condition = self.lower_expr(condition, ValueContext::Scalar); let while_label = self.bc.emit(Instruction::branch( condition.reg(), self.following_instr(1), @@ -74,7 +82,7 @@ impl Code<'_> { Statement::DoWhile { then_body, condition } => { let do_label = self.following_instr(0); self.lower_body(then_body); - let condition = self.lower_expr(condition); + let condition = self.lower_expr(condition, ValueContext::Scalar); self.bc.emit(Instruction::branch( condition.reg(), do_label, @@ -83,75 +91,83 @@ impl Code<'_> { self.free_reg(condition); } Statement::Simple(SimpleStatement::Expression(expr)) => { - let reg = self.lower_expr(expr); + let reg = self.lower_expr(expr, ValueContext::Untyped); self.free_reg(reg); } _ => todo!(), } } - fn lower_expr(&mut self, expr: &Expr) -> LinearReg { + fn lower_expr(&mut self, expr: &Expr, ctx: ValueContext) -> LinearReg { let mut dest = self.alloc_reg(); - let hint = self.lower_expr_into(expr, dest.reg()); + let hint = self.lower_expr_into(expr, dest.reg(), ctx); dest.1 = hint; dest } - fn lower_expr_into(&mut self, expr: &Expr, dest: Reg) -> Hint { + fn lower_expr_into(&mut self, expr: &Expr, dest: Reg, ctx: ValueContext) -> Hint { match expr { Expr::Leaf(atom) => match atom { Atom::Variable(Variable::User(ident)) => { let src = self.symbols.register_user_var(ident, self.arena); self.bc - .emit(Instruction::load_store(OpCode::LoadUser, dest, src)); + .emit(Instruction::load_store(OpCode::LoadUser, dest, src, ctx)); } &Atom::Number(n) => { - let src = self.register_const(Value(n)); - self.bc - .emit(Instruction::load_store(OpCode::LoadConst, dest, src)); + let src = self.register_const(Value::Float(n)); + self.bc.emit(Instruction::load_store( + OpCode::LoadConst, + dest, + src, + ValueContext::Scalar, + )); return Hint::UnboxedFloat64; } _ => todo!(), }, Expr::Node(node) => match node.as_ref() { ExprNode::UnaryOperation(op, expr) => { - let src = self.lower_expr(expr); + let src = self.lower_expr(expr, ValueContext::Scalar); self.bc.emit(Instruction::unary(*op, dest, &src)); self.free_reg(src); } ExprNode::BinaryOperation(op, lhs, rhs) => { - let lhs = self.lower_expr(lhs); - let rhs = self.lower_expr(rhs); + let lhs = self.lower_expr(lhs, ValueContext::Scalar); + let rhs = self.lower_expr(rhs, ValueContext::Scalar); self.bc.emit(Instruction::binary(*op, dest, &lhs, &rhs)); self.free_reg(lhs); self.free_reg(rhs); } ExprNode::Ternary(cond, true_then, false_then) => { - self.lower_expr_into(cond, dest); + self.lower_expr_into(cond, dest, ValueContext::Scalar); let mut state = RegsState::new(self); let branch = self.bc .emit(Instruction::branch(dest, self.following_instr(1), 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, ValueContext::Scalar) + }); let jump = self.bc.emit(Instruction::jump(Label(0))); let label = self.following_instr(0); - state.scope_hwm(self, |c| c.lower_expr_into(false_then, dest)); + state.scope_hwm(self, |c| { + c.lower_expr_into(false_then, dest, ValueContext::Scalar) + }); self.bc.nth(branch).args.branch.2 = label; self.bc.nth(jump).args.jump = self.following_instr(0); } ExprNode::BinaryPlaceOperation(BinaryPlaceOperator::Assignment, place, expr) => { - self.lower_expr_into(expr, dest); + self.lower_expr_into(expr, dest, ctx); let Place::Variable(Variable::User(var)) = place else { todo!() }; let var = self.symbols.register_user_var(var, self.arena); self.bc - .emit(Instruction::load_store(OpCode::StoreUser, dest, var)); + .emit(Instruction::load_store(OpCode::StoreUser, dest, var, ctx)); } _ => todo!(), }, @@ -286,14 +302,6 @@ impl From for OpCode { } } -impl Hash for Value { - fn hash(&self, state: &mut H) { - state.write(&self.0.to_ne_bytes()); - } -} - -impl Eq for Value {} - // trait Fold { // type Args; // fn fold(&self, args: Self::Args) -> T; diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index dbbdb46..0241b9e 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -6,6 +6,7 @@ #![allow(dead_code)] pub(crate) mod ir; +mod types; mod vm; pub use ir::test_interpreter; diff --git a/interpreter/src/types.rs b/interpreter/src/types.rs index 3791a39..d4782ea 100644 --- a/interpreter/src/types.rs +++ b/interpreter/src/types.rs @@ -1,14 +1,109 @@ -use std::ptr::NonNull; +use std::{ + hash::Hash, + mem::discriminant, + ops::{Add, Div, Mul, Sub}, +}; -// / NaN-boxed value -union AValue { - float: f64, - ptr: NonNull<()>, +use ahash::RandomState; +use hashbrown::HashMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + Float(f64), + String(Vec), + Array(HashMap), + Bool(bool), + Untyped, + Unassigned, } -impl AValue { - fn get_float(&self) -> f64 { - let float = unsafe { self.float }; - if !float.is_nan() { float } else { todo!() } +impl Value { + /// Called when loading a variable's value. Forces subsequent uses to be + /// typed as an AWK scalar (anything that's not an array, basically). + pub fn scalar_context(&mut self) -> &mut Self { + // TODO: Exit "nicely" on Self::Array(_). + match self { + Self::Untyped => *self = Self::Unassigned, + Self::Array(_) => panic!("Attempted to use array in scalar context!"), + _ => {} + } + self + } + + pub fn array_context(&mut self) -> &mut Self { + // TODO: Exit "nicely" on Self::Array(_). + match self { + Self::Untyped => *self = Self::Array(HashMap::with_hasher(RandomState::new())), + Self::Array(_) => {} + _ => panic!("Attempted to use scalar as array!"), + } + self + } + + pub fn to_bool(&self) -> bool { + match self { + &Self::Float(f) => f != 0., + &Self::Bool(b) => b, + Self::String(str) => !str.is_empty(), + _ => false, + } + } + + fn to_num(&self) -> f64 { + match self { + &Self::Float(f) => f, + &Self::Bool(b) => b as usize as f64, + Self::String(s) => str::from_utf8(s) + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0.), + _ => 0., + } + } +} + +impl Add for &'_ Value { + type Output = Value; + + fn add(self, rhs: Self) -> Self::Output { + Value::Float(self.to_num() + rhs.to_num()) + } +} + +impl Sub for &'_ Value { + type Output = Value; + + fn sub(self, rhs: Self) -> Self::Output { + Value::Float(self.to_num() - rhs.to_num()) + } +} + +impl Mul for &'_ Value { + type Output = Value; + + fn mul(self, rhs: Self) -> Self::Output { + Value::Float(self.to_num() * rhs.to_num()) + } +} + +impl Div for &'_ Value { + type Output = Value; + + fn div(self, rhs: Self) -> Self::Output { + // TODO: panic "nicely" on div by zero. + Value::Float(self.to_num() / rhs.to_num()) + } +} + +impl Eq for Value {} +impl Hash for Value { + fn hash(&self, state: &mut H) { + discriminant(self).hash(state); + match self { + Self::Float(f) => state.write_u64(f.to_bits()), + Self::String(s) => s.hash(state), + Self::Bool(b) => b.hash(state), + _ => {} + } } } diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index e02a298..353a3a0 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Display}; +use std::{ + fmt::{self, Display}, + mem::replace, +}; use ahash::RandomState; use bumpalo::{Bump, collections::Vec}; @@ -6,15 +9,14 @@ use hashbrown::HashMap; use indexmap_allocator_api::{IndexMap, IndexSet}; use parser::Identifier; -use crate::ir::{ - NonLocal, OpCode, Reg, - lower::{Bytecode, Code}, +use crate::{ + ir::{ + NonLocal, OpCode, Reg, + lower::{Bytecode, Code, ValueContext}, + }, + types::Value, }; -#[derive(Clone, Debug, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct Value(pub f64); // TODO: use NaN-boxing. - #[derive(Debug)] pub enum ExecMode { Uu, @@ -53,7 +55,7 @@ impl<'a> Interpreter<'a> { arena: code.arena, bc: code.bc, program_counter: 0, - registers: Registers(bumpalo::vec![in code.arena; Value(0.); 8]), + registers: Registers(bumpalo::vec![in code.arena; Value::Untyped; 8]), symbols: code.symbols, consts: code.consts, compat, @@ -68,12 +70,18 @@ impl<'a> SymbolTable<'a> { records: HashMap::with_hasher_in(RandomState::new(), arena), } } - fn lookup_user_var(&self, var: NonLocal) -> &Value { - self.user.get_index(var.0 as _).unwrap().1 + + fn lookup_user_var(&mut self, var: NonLocal, ctx: ValueContext) -> &Value { + let v = self.user.get_index_mut(var.0 as _).unwrap().1; + match ctx { + ValueContext::Untyped => v, + ValueContext::Scalar => v.scalar_context(), + ValueContext::Array => v.array_context(), + } } - fn write_user_val(&mut self, var: NonLocal, value: &Value) { - *self.user.get_index_mut(var.0 as _).unwrap().1 = Value::clone(value); + fn write_user_val(&mut self, var: NonLocal, value: Value) { + *self.user.get_index_mut(var.0 as _).unwrap().1 = value; } pub fn register_user_var(&mut self, var: &Identifier, bump: &'a Bump) -> NonLocal { @@ -84,7 +92,7 @@ impl<'a> SymbolTable<'a> { namespace: bump.alloc_str(var.namespace), literal: bump.alloc_str(var.literal), }; - NonLocal(self.user.insert_full(ident, Value(0.)).0 as _) + NonLocal(self.user.insert_full(ident, Value::Untyped).0 as _) } } } @@ -101,37 +109,41 @@ impl Interpreter<'_> { match instr { // ix if let Some(&(dest, src)) = ix.get_unary() => {} ix if let Some(&(dest, lhs, rhs)) = ix.get_binary() => { - let lhs = self.registers.read(lhs); - let rhs = self.registers.read(rhs); + let lhs = self.registers.get(lhs); + let rhs = self.registers.get(rhs); let val = match ix.opcode { - OpCode::Add => Value(lhs.0 + rhs.0), - OpCode::Subtract => Value(lhs.0 - rhs.0), - OpCode::Multiply => Value(lhs.0 * rhs.0), - OpCode::Divide => Value(lhs.0 / rhs.0), + OpCode::Add => lhs + rhs, + OpCode::Subtract => lhs - rhs, + OpCode::Multiply => lhs * rhs, + OpCode::Divide => lhs / rhs, _ => todo!(), }; - self.registers.write(dest, &val); + self.registers.write(dest, val); } ix if let Some(&(dest, src)) = ix.get_load_store() => match ix.opcode { OpCode::LoadConst => self .registers - .write(dest, self.consts.0.get_index(src.0 as _).unwrap()), + .write(dest, self.consts.0.get_index(src.0 as _).unwrap().clone()), OpCode::LoadUser => { - self.registers - .write(dest, self.symbols.lookup_user_var(src)); + self.registers.write( + dest, + self.symbols.lookup_user_var(src, ix.hint.into()).clone(), + ); } OpCode::StoreUser => { - self.symbols.write_user_val(src, self.registers.read(dest)); + self.symbols + .write_user_val(src, self.registers.get(dest).clone()); } _ => todo!(), }, ix if let Some((cond, true_to, false_to)) = ix.get_branch() => { - let label = if self.registers.read(*cond).0 == 0. { - false_to.0 - } else { + let label = if self.registers.get(*cond).to_bool() { true_to.0 + } else { + false_to.0 }; self.program_counter = label as _; + continue; } ix if let Some(label) = ix.get_jump() => { @@ -146,11 +158,18 @@ impl Interpreter<'_> { } impl Registers<'_> { - fn read(&self, src: Reg) -> &Value { + fn replace(&mut self, src: Reg, f: impl FnOnce(Value) -> Value) { + let val = replace(self.get_mut(src), Value::Untyped); + self.write(src, f(val)); + } + fn get(&self, src: Reg) -> &Value { &self.0[src.0 as usize] } - fn write(&mut self, dest: Reg, src: &Value) { - self.0[dest.0 as usize] = Value::clone(src); + fn get_mut(&mut self, src: Reg) -> &mut Value { + &mut self.0[src.0 as usize] + } + fn write(&mut self, dest: Reg, src: Value) { + self.0[dest.0 as usize] = src; } }