use shape_value::native::ConcreteType;
use crate::bytecode::OpCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypedArrayKind {
F64,
I64,
I32,
Bool,
}
impl TypedArrayKind {
#[inline]
pub fn new_opcode(self) -> OpCode {
match self {
TypedArrayKind::F64 => OpCode::NewTypedArrayF64,
TypedArrayKind::I64 => OpCode::NewTypedArrayI64,
TypedArrayKind::I32 => OpCode::NewTypedArrayI32,
TypedArrayKind::Bool => OpCode::NewTypedArrayBool,
}
}
#[inline]
pub fn get_opcode(self) -> OpCode {
match self {
TypedArrayKind::F64 => OpCode::TypedArrayGetF64,
TypedArrayKind::I64 => OpCode::TypedArrayGetI64,
TypedArrayKind::I32 => OpCode::TypedArrayGetI32,
TypedArrayKind::Bool => OpCode::TypedArrayGetBool,
}
}
#[inline]
pub fn push_opcode(self) -> OpCode {
match self {
TypedArrayKind::F64 => OpCode::TypedArrayPushF64,
TypedArrayKind::I64 => OpCode::TypedArrayPushI64,
TypedArrayKind::I32 => OpCode::TypedArrayPushI32,
TypedArrayKind::Bool => OpCode::TypedArrayPushBool,
}
}
#[inline]
pub fn set_opcode(self) -> OpCode {
match self {
TypedArrayKind::F64 => OpCode::TypedArraySetF64,
TypedArrayKind::I64 => OpCode::TypedArraySetI64,
TypedArrayKind::I32 => OpCode::TypedArraySetI32,
TypedArrayKind::Bool => OpCode::TypedArraySetBool,
}
}
}
#[inline]
pub fn should_use_typed_array(elem_type: &ConcreteType) -> Option<TypedArrayKind> {
match elem_type {
ConcreteType::F64 => Some(TypedArrayKind::F64),
ConcreteType::I64 => Some(TypedArrayKind::I64),
ConcreteType::I32 => Some(TypedArrayKind::I32),
ConcreteType::Bool => Some(TypedArrayKind::Bool),
_ => None,
}
}
#[inline]
pub fn should_use_typed_array_from_slot_kind(
slot: crate::type_tracking::NativeKind,
) -> Option<TypedArrayKind> {
use crate::type_tracking::NativeKind;
match slot {
NativeKind::Float64 => Some(TypedArrayKind::F64),
NativeKind::Int64 => Some(TypedArrayKind::I64),
NativeKind::Int32 => Some(TypedArrayKind::I32),
NativeKind::Bool => Some(TypedArrayKind::Bool),
_ => None,
}
}
#[inline]
#[allow(dead_code)]
pub fn typed_array_kind_from_type_name(type_name: &str) -> Option<TypedArrayKind> {
let trimmed = type_name.trim();
let inner = trimmed
.strip_prefix("Vec<")
.or_else(|| trimmed.strip_prefix("Array<"))?
.strip_suffix('>')?;
match inner.trim() {
"number" | "f64" => Some(TypedArrayKind::F64),
"int" | "i64" => Some(TypedArrayKind::I64),
"i32" => Some(TypedArrayKind::I32),
"bool" => Some(TypedArrayKind::Bool),
_ => None,
}
}
impl super::BytecodeCompiler {
pub(crate) fn resolve_receiver_typed_array_kind(
&self,
receiver: &shape_ast::ast::Expr,
) -> Option<TypedArrayKind> {
let name = match receiver {
shape_ast::ast::Expr::Identifier(name, _) => name,
_ => return None,
};
if let Some(local_idx) = self.resolve_local(name) {
if let Some(&kind) = self.typed_array_locals.get(&local_idx) {
return Some(kind);
}
return None;
}
let scoped_name = self
.resolve_scoped_module_binding_name(name)
.unwrap_or_else(|| name.to_string());
if let Some(&binding_idx) = self.module_bindings.get(&scoped_name) {
if let Some(&kind) = self.typed_array_module_bindings.get(&binding_idx) {
return Some(kind);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use shape_value::native::concrete_type::{EnumLayoutId, StructLayoutId};
#[test]
fn test_f64_maps_to_typed_array_f64() {
assert_eq!(
should_use_typed_array(&ConcreteType::F64),
Some(TypedArrayKind::F64)
);
}
#[test]
fn test_i64_maps_to_typed_array_i64() {
assert_eq!(
should_use_typed_array(&ConcreteType::I64),
Some(TypedArrayKind::I64)
);
}
#[test]
fn test_i32_maps_to_typed_array_i32() {
assert_eq!(
should_use_typed_array(&ConcreteType::I32),
Some(TypedArrayKind::I32)
);
}
#[test]
fn test_bool_maps_to_typed_array_bool() {
assert_eq!(
should_use_typed_array(&ConcreteType::Bool),
Some(TypedArrayKind::Bool)
);
}
#[test]
fn test_string_falls_back_to_legacy() {
assert_eq!(should_use_typed_array(&ConcreteType::String), None);
}
#[test]
fn test_struct_falls_back_to_legacy() {
assert_eq!(
should_use_typed_array(&ConcreteType::placeholder_struct(StructLayoutId(0))),
None
);
}
#[test]
fn test_enum_falls_back_to_legacy() {
assert_eq!(
should_use_typed_array(&ConcreteType::placeholder_enum(EnumLayoutId(0))),
None
);
}
#[test]
fn test_nested_array_falls_back_to_legacy() {
let nested = ConcreteType::Array(Box::new(ConcreteType::I64));
assert_eq!(should_use_typed_array(&nested), None);
}
#[test]
fn test_u8_falls_back_to_legacy() {
assert_eq!(should_use_typed_array(&ConcreteType::U8), None);
}
#[test]
fn test_option_falls_back_to_legacy() {
let opt = ConcreteType::Option(Box::new(ConcreteType::I64));
assert_eq!(should_use_typed_array(&opt), None);
}
#[test]
fn test_opcode_lookup_round_trip() {
for kind in [
TypedArrayKind::F64,
TypedArrayKind::I64,
TypedArrayKind::I32,
TypedArrayKind::Bool,
] {
let _ = kind.new_opcode();
let _ = kind.get_opcode();
let _ = kind.push_opcode();
let _ = kind.set_opcode();
}
}
#[test]
fn test_slot_kind_float64_maps_to_f64() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Float64),
Some(TypedArrayKind::F64)
);
}
#[test]
fn test_slot_kind_int64_maps_to_i64() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Int64),
Some(TypedArrayKind::I64)
);
}
#[test]
fn test_slot_kind_int32_maps_to_i32() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Int32),
Some(TypedArrayKind::I32)
);
}
#[test]
fn test_slot_kind_bool_maps_to_bool() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Bool),
Some(TypedArrayKind::Bool)
);
}
#[test]
fn test_slot_kind_string_falls_back() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::String),
None
);
}
#[test]
fn test_slot_kind_unknown_falls_back() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Unknown),
None
);
}
#[test]
fn test_slot_kind_dynamic_falls_back() {
use crate::type_tracking::NativeKind;
assert_eq!(
should_use_typed_array_from_slot_kind(NativeKind::Dynamic),
None
);
}
#[test]
fn test_slot_kind_int8_falls_back() {
use crate::type_tracking::NativeKind;
assert_eq!(should_use_typed_array_from_slot_kind(NativeKind::Int8), None);
}
#[test]
fn test_type_name_vec_int_maps_to_i64() {
assert_eq!(
typed_array_kind_from_type_name("Vec<int>"),
Some(TypedArrayKind::I64)
);
}
#[test]
fn test_type_name_vec_number_maps_to_f64() {
assert_eq!(
typed_array_kind_from_type_name("Vec<number>"),
Some(TypedArrayKind::F64)
);
}
#[test]
fn test_type_name_vec_bool_maps_to_bool() {
assert_eq!(
typed_array_kind_from_type_name("Vec<bool>"),
Some(TypedArrayKind::Bool)
);
}
#[test]
fn test_type_name_vec_i32_maps_to_i32() {
assert_eq!(
typed_array_kind_from_type_name("Vec<i32>"),
Some(TypedArrayKind::I32)
);
}
#[test]
fn test_type_name_array_int_maps_to_i64() {
assert_eq!(
typed_array_kind_from_type_name("Array<int>"),
Some(TypedArrayKind::I64)
);
}
#[test]
fn test_type_name_vec_string_falls_back() {
assert_eq!(typed_array_kind_from_type_name("Vec<string>"), None);
}
#[test]
fn test_type_name_non_array_falls_back() {
assert_eq!(typed_array_kind_from_type_name("HashMap<int, int>"), None);
assert_eq!(typed_array_kind_from_type_name("int"), None);
}
fn compiled_opcodes(source: &str) -> Vec<crate::bytecode::OpCode> {
let program = shape_ast::parser::parse_program(source).expect("parse failed");
let compiler = crate::compiler::BytecodeCompiler::new();
let bytecode = compiler.compile(&program).expect("compile failed");
bytecode
.instructions
.iter()
.map(|i| i.opcode)
.collect()
}
#[test]
fn test_float_array_emits_typed_opcodes() {
let ops = compiled_opcodes("[1.0, 2.0, 3.0]");
assert!(
ops.contains(&crate::bytecode::OpCode::NewTypedArrayF64),
"expected NewTypedArrayF64, got opcodes: {:?}",
ops
);
let push_count = ops
.iter()
.filter(|&&op| op == crate::bytecode::OpCode::TypedArrayPushF64)
.count();
assert_eq!(
push_count, 3,
"expected 3 TypedArrayPushF64, got {}",
push_count
);
assert!(
!ops.contains(&crate::bytecode::OpCode::NewArray),
"should NOT fall back to NewArray"
);
}
#[test]
fn test_int_array_emits_typed_opcodes() {
let ops = compiled_opcodes("[1, 2, 3]");
assert!(
ops.contains(&crate::bytecode::OpCode::NewTypedArrayI64),
"expected NewTypedArrayI64, got opcodes: {:?}",
ops
);
let push_count = ops
.iter()
.filter(|&&op| op == crate::bytecode::OpCode::TypedArrayPushI64)
.count();
assert_eq!(
push_count, 3,
"expected 3 TypedArrayPushI64, got {}",
push_count
);
}
#[test]
fn test_bool_array_emits_typed_opcodes() {
let ops = compiled_opcodes("[true, false, true]");
assert!(
ops.contains(&crate::bytecode::OpCode::NewTypedArrayBool),
"expected NewTypedArrayBool, got opcodes: {:?}",
ops
);
let push_count = ops
.iter()
.filter(|&&op| op == crate::bytecode::OpCode::TypedArrayPushBool)
.count();
assert_eq!(
push_count, 3,
"expected 3 TypedArrayPushBool, got {}",
push_count
);
}
#[test]
fn test_mixed_array_falls_back_to_generic() {
let ops = compiled_opcodes(r#"[1, "hello"]"#);
assert!(
ops.contains(&crate::bytecode::OpCode::NewArray),
"expected NewArray for mixed array, got opcodes: {:?}",
ops
);
assert!(
!ops.contains(&crate::bytecode::OpCode::NewTypedArrayI64),
"should NOT emit NewTypedArrayI64 for mixed array"
);
}
#[test]
fn test_empty_array_uses_generic_path() {
let ops = compiled_opcodes("[]");
assert!(
ops.contains(&crate::bytecode::OpCode::NewArray),
"expected NewArray for empty array, got opcodes: {:?}",
ops
);
}
#[test]
fn test_float_array_executes_correctly() {
use crate::test_utils::eval;
use shape_value::value_word::vw_as_f64;
let result = eval("[1.0, 2.0, 3.0].sum()");
assert_eq!(
vw_as_f64(result),
Some(6.0),
"float array sum should be 6.0"
);
}
#[test]
fn test_int_array_executes_correctly() {
use crate::test_utils::eval;
use shape_value::value_word::vw_as_i64;
let result = eval("[10, 20, 30].sum()");
let val = vw_as_i64(result).or_else(|| {
shape_value::value_word::vw_as_f64(result).map(|f| f as i64)
});
assert_eq!(val, Some(60), "int array sum should be 60");
}
#[test]
fn test_bool_array_len_executes_correctly() {
use crate::test_utils::eval;
use shape_value::value_word::vw_as_i64;
let result = eval("[true, false, true].len()");
assert_eq!(
vw_as_i64(result),
Some(3),
"bool array len should be 3"
);
}
}