mirror of
https://github.com/uutils/awk.git
synced 2026-06-10 16:15:04 -07:00
interpreter: initial typesystem support
This commit is contained in:
+28
-2
@@ -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<OpCode>, dest: Reg, src: NonLocal) -> Self {
|
||||
fn load_store(opcode: impl Into<OpCode>, 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<ValueContext> for Hint {
|
||||
fn from(value: ValueContext) -> Self {
|
||||
match value {
|
||||
ValueContext::Scalar => Self::ScalarCtx,
|
||||
ValueContext::Array => Self::ArrayCtx,
|
||||
ValueContext::Untyped => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hint> 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(()),
|
||||
}
|
||||
}
|
||||
|
||||
+37
-29
@@ -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<BinaryOperator> for OpCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Value {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write(&self.0.to_ne_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Value {}
|
||||
|
||||
// trait Fold<T> {
|
||||
// type Args;
|
||||
// fn fold(&self, args: Self::Args) -> T;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub(crate) mod ir;
|
||||
mod types;
|
||||
mod vm;
|
||||
|
||||
pub use ir::test_interpreter;
|
||||
|
||||
+104
-9
@@ -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<u8>),
|
||||
Array(HashMap<String, Self, RandomState>),
|
||||
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<H: std::hash::Hasher>(&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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+50
-31
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user