use prometeu_bytecode::Value; use prometeu_hal::{HostContext, color::Color}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BuiltinTypeKey { pub name: &'static str, pub version: u16, } impl BuiltinTypeKey { pub const fn new(name: &'static str, version: u16) -> Self { Self { name, version } } pub const fn key(self) -> (&'static str, u16) { (self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BuiltinConstKey { pub target: &'static str, pub name: &'static str, pub version: u16, } impl BuiltinConstKey { pub const fn new(target: &'static str, name: &'static str, version: u16) -> Self { Self { target, name, version } } pub const fn key(self) -> (&'static str, &'static str, u16) { (self.target, self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct IntrinsicKey { pub owner: &'static str, pub name: &'static str, pub version: u16, } impl IntrinsicKey { pub const fn new(owner: &'static str, name: &'static str, version: u16) -> Self { Self { owner, name, version } } pub const fn key(self) -> (&'static str, &'static str, u16) { (self.owner, self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinScalarType { Int, Float, Bool, Str, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AbiType { Scalar(BuiltinScalarType), Builtin(BuiltinTypeKey), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinLayoutType { Scalar(BuiltinScalarType), Builtin(BuiltinTypeKey), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BuiltinTypeShape { Scalar, Aggregate, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuiltinFieldMeta { pub name: &'static str, pub start_slot: u16, pub field_type: BuiltinLayoutType, pub flat_slot_width: u16, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuiltinTypeMeta { pub id: u32, pub name: &'static str, pub version: u16, pub shape: BuiltinTypeShape, pub fields: &'static [BuiltinFieldMeta], pub flat_slot_layout: &'static [AbiType], pub flat_slot_width: u16, } impl BuiltinTypeMeta { pub const fn key(&self) -> BuiltinTypeKey { BuiltinTypeKey::new(self.name, self.version) } pub fn field(&self, name: &str) -> Option<&'static BuiltinFieldMeta> { self.fields.iter().find(|field| field.name == name) } pub fn flatten_layout(&self) -> Vec { let mut out = Vec::with_capacity(self.flat_slot_width as usize); self.flatten_into(&mut out); out } pub fn validate_flat_values(&self, values: &[Value]) -> Result<(), BuiltinValueError> { let layout = self.flatten_layout(); validate_values_against_layout(values, &layout) } fn flatten_into(&self, out: &mut Vec) { match self.shape { BuiltinTypeShape::Scalar => out.extend_from_slice(self.flat_slot_layout), BuiltinTypeShape::Aggregate => { for field in self.fields { flatten_layout_type(field.field_type, out); } } } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum BuiltinConstSlotValue { Int32(i32), Int64(i64), Float(f64), Bool(bool), NominalBuiltin { builtin: BuiltinTypeKey, carrier: i32 }, } pub type BuiltinConstHook = fn() -> Vec; #[derive(Debug, Clone, Copy)] pub enum BuiltinConstMaterializer { Direct(&'static [BuiltinConstSlotValue]), Hook(BuiltinConstHook), } #[derive(Debug, Clone, Copy)] pub struct BuiltinConstMeta { pub key: BuiltinConstKey, pub flat_slot_layout: &'static [AbiType], pub flat_slot_width: u16, pub materializer: BuiltinConstMaterializer, } impl BuiltinConstMeta { pub fn direct_slots(&self) -> Option<&'static [BuiltinConstSlotValue]> { match self.materializer { BuiltinConstMaterializer::Direct(slots) => Some(slots), BuiltinConstMaterializer::Hook(_) => None, } } pub fn materialize(&self) -> Result, BuiltinValueError> { let values = match self.materializer { BuiltinConstMaterializer::Direct(slots) => { slots.iter().copied().map(BuiltinConstSlotValue::into_value).collect() } BuiltinConstMaterializer::Hook(hook) => hook(), }; validate_values_against_layout(&values, self.flat_slot_layout)?; Ok(values) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum IntrinsicExecutionError { ArityMismatch { expected: usize, got: usize }, TypeMismatch { index: usize, expected: AbiType }, InvalidBuiltinCarrier { owner: &'static str, name: &'static str, carrier: i64 }, HardwareUnavailable, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum BuiltinValueError { ArityMismatch { expected: usize, got: usize }, TypeMismatch { index: usize, expected: AbiType }, } pub type IntrinsicImplementation = fn(&[Value], &mut HostContext<'_>) -> Result, IntrinsicExecutionError>; #[derive(Debug, Clone, Copy)] pub struct IntrinsicMeta { pub id: u32, pub owner: &'static str, pub name: &'static str, pub version: u16, pub arg_layout: &'static [AbiType], pub ret_layout: &'static [AbiType], pub deterministic: bool, pub may_allocate: bool, pub implementation: IntrinsicImplementation, } impl IntrinsicMeta { pub const fn key(&self) -> IntrinsicKey { IntrinsicKey::new(self.owner, self.name, self.version) } pub fn arg_slots(&self) -> usize { self.arg_layout.len() } pub fn ret_slots(&self) -> usize { self.ret_layout.len() } pub fn validate_result_values(&self, values: &[Value]) -> Result<(), IntrinsicExecutionError> { if values.len() != self.ret_slots() { return Err(IntrinsicExecutionError::ArityMismatch { expected: self.ret_slots(), got: values.len(), }); } for (index, (value, expected)) in values.iter().zip(self.ret_layout.iter()).enumerate() { if !value_matches_abi_type(value, *expected) { return Err(IntrinsicExecutionError::TypeMismatch { index, expected: *expected }); } } Ok(()) } } impl BuiltinConstSlotValue { pub fn into_value(self) -> Value { match self { BuiltinConstSlotValue::Int32(value) => Value::Int32(value), BuiltinConstSlotValue::Int64(value) => Value::Int64(value), BuiltinConstSlotValue::Float(value) => Value::Float(value), BuiltinConstSlotValue::Bool(value) => Value::Boolean(value), BuiltinConstSlotValue::NominalBuiltin { carrier, .. } => Value::Int32(carrier), } } } const COLOR: BuiltinTypeKey = BuiltinTypeKey::new("color", 1); const VEC2: BuiltinTypeKey = BuiltinTypeKey::new("vec2", 1); const PIXEL: BuiltinTypeKey = BuiltinTypeKey::new("pixel", 1); const INPUT_PAD: BuiltinTypeKey = BuiltinTypeKey::new("input.pad", 1); const INPUT_TOUCH: BuiltinTypeKey = BuiltinTypeKey::new("input.touch", 1); const INPUT_BUTTON: BuiltinTypeKey = BuiltinTypeKey::new("input.button", 1); const COLOR_LAYOUT: [AbiType; 1] = [AbiType::Builtin(COLOR)]; const VEC2_LAYOUT: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const PIXEL_LAYOUT: [AbiType; 3] = [ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Builtin(COLOR), ]; const INPUT_PAD_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const INPUT_TOUCH_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const INPUT_BUTTON_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const VEC2_FIELDS: [BuiltinFieldMeta; 2] = [ BuiltinFieldMeta { name: "x", start_slot: 0, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Float), flat_slot_width: 1, }, BuiltinFieldMeta { name: "y", start_slot: 1, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Float), flat_slot_width: 1, }, ]; const PIXEL_FIELDS: [BuiltinFieldMeta; 3] = [ BuiltinFieldMeta { name: "x", start_slot: 0, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Int), flat_slot_width: 1, }, BuiltinFieldMeta { name: "y", start_slot: 1, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Int), flat_slot_width: 1, }, BuiltinFieldMeta { name: "color", start_slot: 2, field_type: BuiltinLayoutType::Builtin(COLOR), flat_slot_width: 1, }, ]; const BUILTIN_TYPES: [BuiltinTypeMeta; 6] = [ BuiltinTypeMeta { id: 0x0001, name: COLOR.name, version: COLOR.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &COLOR_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0002, name: VEC2.name, version: VEC2.version, shape: BuiltinTypeShape::Aggregate, fields: &VEC2_FIELDS, flat_slot_layout: &VEC2_LAYOUT, flat_slot_width: 2, }, BuiltinTypeMeta { id: 0x0003, name: PIXEL.name, version: PIXEL.version, shape: BuiltinTypeShape::Aggregate, fields: &PIXEL_FIELDS, flat_slot_layout: &PIXEL_LAYOUT, flat_slot_width: 3, }, BuiltinTypeMeta { id: 0x0100, name: INPUT_PAD.name, version: INPUT_PAD.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_PAD_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0101, name: INPUT_TOUCH.name, version: INPUT_TOUCH.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_TOUCH_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0102, name: INPUT_BUTTON.name, version: INPUT_BUTTON.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_BUTTON_LAYOUT, flat_slot_width: 1, }, ]; const VEC2_ZERO_LAYOUT: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const VEC2_ZERO_SLOTS: [BuiltinConstSlotValue; 2] = [BuiltinConstSlotValue::Float(0.0), BuiltinConstSlotValue::Float(0.0)]; const COLOR_BLACK_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::BLACK.raw() as i64)]; const COLOR_WHITE_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::WHITE.raw() as i64)]; const COLOR_RED_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::RED.raw() as i64)]; const COLOR_GREEN_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::GREEN.raw() as i64)]; const COLOR_BLUE_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::BLUE.raw() as i64)]; const COLOR_YELLOW_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::YELLOW.raw() as i64)]; const COLOR_ORANGE_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::ORANGE.raw() as i64)]; const COLOR_INDIGO_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::INDIGO.raw() as i64)]; const COLOR_GRAY_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::GRAY.raw() as i64)]; const COLOR_CYAN_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::CYAN.raw() as i64)]; const COLOR_MAGENTA_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::MAGENTA.raw() as i64)]; const COLOR_TRANSPARENT_SLOTS: [BuiltinConstSlotValue; 1] = [BuiltinConstSlotValue::Int64(Color::TRANSPARENT.raw() as i64)]; const BUILTIN_CONSTS: [BuiltinConstMeta; 13] = [ BuiltinConstMeta { key: BuiltinConstKey::new("vec2", "zero", 1), flat_slot_layout: &VEC2_ZERO_LAYOUT, flat_slot_width: 2, materializer: BuiltinConstMaterializer::Direct(&VEC2_ZERO_SLOTS), }, color_const_meta("black", &COLOR_BLACK_SLOTS), color_const_meta("white", &COLOR_WHITE_SLOTS), color_const_meta("red", &COLOR_RED_SLOTS), color_const_meta("green", &COLOR_GREEN_SLOTS), color_const_meta("blue", &COLOR_BLUE_SLOTS), color_const_meta("yellow", &COLOR_YELLOW_SLOTS), color_const_meta("orange", &COLOR_ORANGE_SLOTS), color_const_meta("indigo", &COLOR_INDIGO_SLOTS), color_const_meta("gray", &COLOR_GRAY_SLOTS), color_const_meta("cyan", &COLOR_CYAN_SLOTS), color_const_meta("magenta", &COLOR_MAGENTA_SLOTS), color_const_meta("transparent", &COLOR_TRANSPARENT_SLOTS), ]; const fn color_const_meta( name: &'static str, slots: &'static [BuiltinConstSlotValue], ) -> BuiltinConstMeta { BuiltinConstMeta { key: BuiltinConstKey::new("color", name, 1), flat_slot_layout: &COLOR_LAYOUT, flat_slot_width: 1, materializer: BuiltinConstMaterializer::Direct(slots), } } const VEC2_DOT_ARGS: [AbiType; 4] = [ AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), ]; const SINGLE_FLOAT_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Float)]; const VEC2_LENGTH_ARGS: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const NO_ARGS: [AbiType; 0] = []; const COLOR_ARGS: [AbiType; 1] = [AbiType::Builtin(COLOR)]; const COLOR_RET: [AbiType; 1] = [AbiType::Builtin(COLOR)]; const COLOR_RGB_ARGS: [AbiType; 3] = [ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), ]; const COLOR_RGBA_ARGS: [AbiType; 4] = [ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), ]; const COLOR_HTML_RGBA_ARGS: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Str)]; const PAD_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const TOUCH_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const BUTTON_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const PAD_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const TOUCH_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const BUTTON_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const BOOL_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Bool)]; const INT_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Int)]; const INTRINSICS: [IntrinsicMeta; 42] = [ IntrinsicMeta { id: 0x1000, owner: "vec2", name: "dot", version: 1, arg_layout: &VEC2_DOT_ARGS, ret_layout: &SINGLE_FLOAT_RET, deterministic: true, may_allocate: false, implementation: vec2_dot, }, IntrinsicMeta { id: 0x1001, owner: "vec2", name: "length", version: 1, arg_layout: &VEC2_LENGTH_ARGS, ret_layout: &SINGLE_FLOAT_RET, deterministic: true, may_allocate: false, implementation: vec2_length, }, IntrinsicMeta { id: 0x1100, owner: "color", name: "from_raw", version: 1, arg_layout: &INT_RET, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_from_raw, }, IntrinsicMeta { id: 0x1101, owner: "color", name: "rgb", version: 1, arg_layout: &COLOR_RGB_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_rgb, }, IntrinsicMeta { id: 0x1102, owner: "color", name: "rgba", version: 1, arg_layout: &COLOR_RGBA_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_rgba, }, IntrinsicMeta { id: 0x1103, owner: "color", name: "html_rgba", version: 1, arg_layout: &COLOR_HTML_RGBA_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_html_rgba, }, IntrinsicMeta { id: 0x1104, owner: "color", name: "gray_scale", version: 1, arg_layout: &INT_RET, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_gray_scale, }, IntrinsicMeta { id: 0x1105, owner: "color", name: "hex", version: 1, arg_layout: &COLOR_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: color_hex, }, IntrinsicMeta { id: 0x1106, owner: "color", name: "alpha", version: 1, arg_layout: &COLOR_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: color_alpha, }, IntrinsicMeta { id: 0x1120, owner: "color", name: "black", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_black, }, IntrinsicMeta { id: 0x1121, owner: "color", name: "white", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_white, }, IntrinsicMeta { id: 0x1122, owner: "color", name: "red", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_red, }, IntrinsicMeta { id: 0x1123, owner: "color", name: "green", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_green, }, IntrinsicMeta { id: 0x1124, owner: "color", name: "blue", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_blue, }, IntrinsicMeta { id: 0x1125, owner: "color", name: "yellow", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_yellow, }, IntrinsicMeta { id: 0x1126, owner: "color", name: "orange", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_orange, }, IntrinsicMeta { id: 0x1127, owner: "color", name: "indigo", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_indigo, }, IntrinsicMeta { id: 0x1128, owner: "color", name: "gray", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_gray, }, IntrinsicMeta { id: 0x1129, owner: "color", name: "cyan", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_cyan, }, IntrinsicMeta { id: 0x112A, owner: "color", name: "magenta", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_magenta, }, IntrinsicMeta { id: 0x112B, owner: "color", name: "transparent", version: 1, arg_layout: &NO_ARGS, ret_layout: &COLOR_RET, deterministic: true, may_allocate: false, implementation: color_transparent, }, IntrinsicMeta { id: 0x2000, owner: "input", name: "pad", version: 1, arg_layout: &NO_ARGS, ret_layout: &PAD_RET, deterministic: true, may_allocate: false, implementation: input_pad, }, IntrinsicMeta { id: 0x2001, owner: "input", name: "touch", version: 1, arg_layout: &NO_ARGS, ret_layout: &TOUCH_RET, deterministic: true, may_allocate: false, implementation: input_touch, }, IntrinsicMeta { id: 0x2010, owner: "input.pad", name: "up", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_up, }, IntrinsicMeta { id: 0x2011, owner: "input.pad", name: "down", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_down, }, IntrinsicMeta { id: 0x2012, owner: "input.pad", name: "left", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_left, }, IntrinsicMeta { id: 0x2013, owner: "input.pad", name: "right", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_right, }, IntrinsicMeta { id: 0x2014, owner: "input.pad", name: "a", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_a, }, IntrinsicMeta { id: 0x2015, owner: "input.pad", name: "b", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_b, }, IntrinsicMeta { id: 0x2016, owner: "input.pad", name: "x", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_x, }, IntrinsicMeta { id: 0x2017, owner: "input.pad", name: "y", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_y, }, IntrinsicMeta { id: 0x2018, owner: "input.pad", name: "l", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_l, }, IntrinsicMeta { id: 0x2019, owner: "input.pad", name: "r", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_r, }, IntrinsicMeta { id: 0x201A, owner: "input.pad", name: "start", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_start, }, IntrinsicMeta { id: 0x201B, owner: "input.pad", name: "select", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_select, }, IntrinsicMeta { id: 0x2020, owner: "input.touch", name: "button", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_touch_button, }, IntrinsicMeta { id: 0x2021, owner: "input.touch", name: "x", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_touch_x, }, IntrinsicMeta { id: 0x2022, owner: "input.touch", name: "y", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_touch_y, }, IntrinsicMeta { id: 0x2030, owner: "input.button", name: "pressed", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_pressed, }, IntrinsicMeta { id: 0x2031, owner: "input.button", name: "released", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_released, }, IntrinsicMeta { id: 0x2032, owner: "input.button", name: "down", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_down, }, IntrinsicMeta { id: 0x2033, owner: "input.button", name: "hold", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_button_hold, }, ]; pub fn lookup_builtin_type(name: &str, version: u16) -> Option<&'static BuiltinTypeMeta> { BUILTIN_TYPES.iter().find(|meta| meta.name == name && meta.version == version) } pub fn lookup_builtin_constant( target: &str, name: &str, version: u16, ) -> Option<&'static BuiltinConstMeta> { BUILTIN_CONSTS.iter().find(|meta| { meta.key.target == target && meta.key.name == name && meta.key.version == version }) } pub fn materialize_builtin_constant( target: &str, name: &str, version: u16, ) -> Result>, BuiltinValueError> { lookup_builtin_constant(target, name, version).map(BuiltinConstMeta::materialize).transpose() } pub fn lookup_intrinsic(owner: &str, name: &str, version: u16) -> Option<&'static IntrinsicMeta> { INTRINSICS .iter() .find(|meta| meta.owner == owner && meta.name == name && meta.version == version) } pub fn lookup_intrinsic_by_id(id: u32) -> Option<&'static IntrinsicMeta> { INTRINSICS.iter().find(|meta| meta.id == id) } fn flatten_layout_type(layout_type: BuiltinLayoutType, out: &mut Vec) { match layout_type { BuiltinLayoutType::Scalar(scalar) => out.push(AbiType::Scalar(scalar)), BuiltinLayoutType::Builtin(key) => { let meta = lookup_builtin_type(key.name, key.version).unwrap_or_else(|| { panic!("missing builtin metadata for {}@{}", key.name, key.version) }); meta.flatten_into(out); } } } fn expect_float_arg(args: &[Value], index: usize) -> Result { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; value.as_float().ok_or(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Float), }) } fn expect_int_arg(args: &[Value], index: usize) -> Result { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; value.as_integer().ok_or(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Int), }) } fn expect_string_arg(args: &[Value], index: usize) -> Result<&str, IntrinsicExecutionError> { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; match value { Value::String(value) => Ok(value.as_ref()), _ => Err(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Str), }), } } fn value_matches_abi_type(value: &Value, expected: AbiType) -> bool { match expected { AbiType::Scalar(BuiltinScalarType::Int) => value.as_integer().is_some(), AbiType::Scalar(BuiltinScalarType::Float) => value.as_float().is_some(), AbiType::Scalar(BuiltinScalarType::Bool) => matches!(value, Value::Boolean(_)), AbiType::Scalar(BuiltinScalarType::Str) => matches!(value, Value::String(_)), AbiType::Builtin(_) => value.as_integer().is_some(), } } fn validate_values_against_layout( values: &[Value], layout: &[AbiType], ) -> Result<(), BuiltinValueError> { if values.len() != layout.len() { return Err(BuiltinValueError::ArityMismatch { expected: layout.len(), got: values.len() }); } for (index, (value, expected)) in values.iter().zip(layout.iter()).enumerate() { if !value_matches_abi_type(value, *expected) { return Err(BuiltinValueError::TypeMismatch { index, expected: *expected }); } } Ok(()) } const PAD_HANDLE: i64 = 1; const TOUCH_HANDLE: i64 = 1; const PAD_BUTTON_BASE: i64 = 0x100; const TOUCH_BUTTON_CARRIER: i64 = 0x200; fn vec2_dot( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { if args.len() != 4 { return Err(IntrinsicExecutionError::ArityMismatch { expected: 4, got: args.len() }); } let ax = expect_float_arg(args, 0)?; let ay = expect_float_arg(args, 1)?; let bx = expect_float_arg(args, 2)?; let by = expect_float_arg(args, 3)?; Ok(vec![Value::Float((ax * bx) + (ay * by))]) } fn vec2_length( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { if args.len() != 2 { return Err(IntrinsicExecutionError::ArityMismatch { expected: 2, got: args.len() }); } let x = expect_float_arg(args, 0)?; let y = expect_float_arg(args, 1)?; Ok(vec![Value::Float((x * x + y * y).sqrt())]) } fn color_value(color: Color) -> Vec { vec![Value::Int64(color.raw() as i64)] } fn expect_color_arg(args: &[Value], index: usize) -> Result { let raw = u32::try_from(expect_int_arg(args, index)?).map_err(|_| { IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Builtin(COLOR) } })?; Ok(Color::from_raw(raw)) } fn expect_channel(args: &[Value], index: usize) -> Result { let value = expect_int_arg(args, index)?; u8::try_from(value).map_err(|_| IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Int), }) } fn parse_html_rgba(value: &str) -> Option { let hex = value.strip_prefix('#').unwrap_or(value); if hex.len() != 8 || !hex.as_bytes().iter().all(u8::is_ascii_hexdigit) { return None; } Some(Color::from_raw(u32::from_str_radix(hex, 16).ok()?)) } fn color_from_raw( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let raw = u32::try_from(expect_int_arg(args, 0)?).map_err(|_| { IntrinsicExecutionError::TypeMismatch { index: 0, expected: AbiType::Scalar(BuiltinScalarType::Int), } })?; Ok(color_value(Color::from_raw(raw))) } fn color_rgb( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(color_value(Color::rgb( expect_channel(args, 0)?, expect_channel(args, 1)?, expect_channel(args, 2)?, ))) } fn color_rgba( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(color_value(Color::rgba( expect_channel(args, 0)?, expect_channel(args, 1)?, expect_channel(args, 2)?, expect_channel(args, 3)?, ))) } fn color_html_rgba( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let color = parse_html_rgba(expect_string_arg(args, 0)?).ok_or({ IntrinsicExecutionError::TypeMismatch { index: 0, expected: AbiType::Scalar(BuiltinScalarType::Str), } })?; Ok(color_value(color)) } fn color_gray_scale( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(color_value(Color::gray_scale(expect_channel(args, 0)?))) } fn color_hex( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(expect_color_arg(args, 0)?.hex() as i64)]) } fn color_alpha( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(expect_color_arg(args, 0)?.alpha() as i64)]) } fn color_const(color: Color) -> Result, IntrinsicExecutionError> { Ok(color_value(color)) } fn color_black( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::BLACK) } fn color_white( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::WHITE) } fn color_red(_: &[Value], _: &mut HostContext<'_>) -> Result, IntrinsicExecutionError> { color_const(Color::RED) } fn color_green( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::GREEN) } fn color_blue(_: &[Value], _: &mut HostContext<'_>) -> Result, IntrinsicExecutionError> { color_const(Color::BLUE) } fn color_yellow( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::YELLOW) } fn color_orange( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::ORANGE) } fn color_indigo( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::INDIGO) } fn color_gray(_: &[Value], _: &mut HostContext<'_>) -> Result, IntrinsicExecutionError> { color_const(Color::GRAY) } fn color_cyan(_: &[Value], _: &mut HostContext<'_>) -> Result, IntrinsicExecutionError> { color_const(Color::CYAN) } fn color_magenta( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::MAGENTA) } fn color_transparent( _: &[Value], _: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { color_const(Color::TRANSPARENT) } fn input_pad( _args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(PAD_HANDLE)]) } fn input_touch( _args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(TOUCH_HANDLE)]) } fn input_pad_up( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "up")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE)]) } fn input_pad_down( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "down")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 1)]) } fn input_pad_left( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "left")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 2)]) } fn input_pad_right( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "right")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 3)]) } fn input_pad_a( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "a")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 4)]) } fn input_pad_b( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "b")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 5)]) } fn input_pad_x( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "x")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 6)]) } fn input_pad_y( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "y")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 7)]) } fn input_pad_l( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "l")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 8)]) } fn input_pad_r( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "r")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 9)]) } fn input_pad_start( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "start")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 10)]) } fn input_pad_select( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "select")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 11)]) } fn input_touch_button( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "button")?; Ok(vec![Value::Int64(TOUCH_BUTTON_CARRIER)]) } fn input_touch_x( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "x")?; let platform = ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; Ok(vec![Value::Int64(platform.input().touch().x() as i64)]) } fn input_touch_y( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "y")?; let platform = ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; Ok(vec![Value::Int64(platform.input().touch().y() as i64)]) } fn input_button_pressed( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "pressed", ctx)?; Ok(vec![Value::Boolean(button.pressed)]) } fn input_button_released( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "released", ctx)?; Ok(vec![Value::Boolean(button.released)]) } fn input_button_down( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "down", ctx)?; Ok(vec![Value::Boolean(button.down)]) } fn input_button_hold( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "hold", ctx)?; Ok(vec![Value::Int64(button.hold_frames as i64)]) } fn expect_pad_handle(args: &[Value], name: &'static str) -> Result<(), IntrinsicExecutionError> { let carrier = expect_builtin_carrier(args, 0)?; if carrier != PAD_HANDLE { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.pad", name, carrier, }); } Ok(()) } fn expect_touch_handle(args: &[Value], name: &'static str) -> Result<(), IntrinsicExecutionError> { let carrier = expect_builtin_carrier(args, 0)?; if carrier != TOUCH_HANDLE { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.touch", name, carrier, }); } Ok(()) } fn expect_builtin_carrier(args: &[Value], index: usize) -> Result { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; value.as_integer().ok_or(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Int), }) } fn resolve_button( args: &[Value], name: &'static str, ctx: &mut HostContext<'_>, ) -> Result { let carrier = expect_builtin_carrier(args, 0)?; let platform = ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; let input = platform.input(); if carrier == TOUCH_BUTTON_CARRIER { return Ok(*input.touch().f()); } let Some(index) = carrier.checked_sub(PAD_BUTTON_BASE) else { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.button", name, carrier, }); }; let button = match index { 0 => *input.pad().up(), 1 => *input.pad().down(), 2 => *input.pad().left(), 3 => *input.pad().right(), 4 => *input.pad().a(), 5 => *input.pad().b(), 6 => *input.pad().x(), 7 => *input.pad().y(), 8 => *input.pad().l(), 9 => *input.pad().r(), 10 => *input.pad().start(), 11 => *input.pad().select(), _ => { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.button", name, carrier, }); } }; Ok(button) } #[cfg(test)] mod tests { use super::*; #[test] fn builtin_lookup_uses_canonical_identity() { let color = lookup_builtin_type("color", 1).expect("color builtin must exist"); assert_eq!(color.key(), COLOR); assert!(lookup_builtin_type("Color", 1).is_none()); assert!(lookup_builtin_type("color", 2).is_none()); } #[test] fn builtin_layout_flattening_matches_expected_widths() { let color = lookup_builtin_type("color", 1).expect("color builtin must exist"); assert_eq!(color.flatten_layout(), vec![AbiType::Builtin(COLOR)]); assert_eq!(color.flat_slot_width, 1); let vec2 = lookup_builtin_type("vec2", 1).expect("vec2 builtin must exist"); assert_eq!(vec2.flatten_layout(), vec![AbiType::Scalar(BuiltinScalarType::Float); 2]); assert_eq!(vec2.flat_slot_width, 2); let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); assert_eq!( pixel.flatten_layout(), vec![ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Builtin(COLOR), ] ); assert_eq!(pixel.flat_slot_width, 3); } #[test] fn builtin_field_offsets_are_stable() { let vec2 = lookup_builtin_type("vec2", 1).expect("vec2 builtin must exist"); assert_eq!(vec2.field("x").map(|field| field.start_slot), Some(0)); assert_eq!(vec2.field("y").map(|field| field.start_slot), Some(1)); let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); let color_field = pixel.field("color").expect("pixel.color must exist"); assert_eq!(color_field.start_slot, 2); assert_eq!(color_field.flat_slot_width, 1); } #[test] fn builtin_constant_registry_is_separate_from_intrinsics() { let zero = lookup_builtin_constant("vec2", "zero", 1).expect("vec2.zero must exist"); assert_eq!(zero.key, BuiltinConstKey::new("vec2", "zero", 1)); assert_eq!(zero.flat_slot_width, 2); assert_eq!(zero.flat_slot_layout, &VEC2_ZERO_LAYOUT); assert_eq!(zero.direct_slots(), Some(&VEC2_ZERO_SLOTS[..])); assert!(lookup_intrinsic("vec2", "zero", 1).is_none()); } #[test] fn builtin_constant_materializes_vec2_zero() { let zero = lookup_builtin_constant("vec2", "zero", 1).expect("vec2.zero must exist"); let materialized = zero.materialize().expect("vec2.zero must materialize"); assert_eq!(materialized, vec![Value::Float(0.0), Value::Float(0.0)]); let looked_up = materialize_builtin_constant("vec2", "zero", 1) .expect("materialization lookup must succeed") .expect("constant must exist"); assert_eq!(looked_up, materialized); } #[test] fn builtin_constant_materializes_color_values() { let red = materialize_builtin_constant("color", "red", 1) .expect("materialization lookup must succeed") .expect("constant must exist"); assert_eq!(red, vec![Value::Int64(Color::RED.raw() as i64)]); let transparent = materialize_builtin_constant("color", "transparent", 1) .expect("materialization lookup must succeed") .expect("constant must exist"); assert_eq!(transparent, vec![Value::Int64(Color::TRANSPARENT.raw() as i64)]); } #[test] fn intrinsic_metadata_exposes_arg_and_return_layouts() { let dot = lookup_intrinsic("vec2", "dot", 1).expect("vec2.dot must exist"); assert_eq!(dot.arg_layout.len(), 4); assert_eq!(dot.ret_layout.len(), 1); assert!(dot.deterministic); assert!(!dot.may_allocate); let length = lookup_intrinsic("vec2", "length", 1).expect("vec2.length must exist"); assert_eq!(length.arg_layout.len(), 2); assert_eq!(length.ret_layout.len(), 1); assert_eq!(lookup_intrinsic_by_id(length.id).map(|meta| meta.key()), Some(length.key())); let hold = lookup_intrinsic("input.button", "hold", 1).expect("input.button.hold must exist"); assert_eq!(hold.arg_layout.len(), 1); assert_eq!(hold.ret_layout.len(), 1); assert!(hold.deterministic); assert!(!hold.may_allocate); } #[test] fn intrinsic_implementations_are_registered_without_syscalls() { let dot = lookup_intrinsic("vec2", "dot", 1).expect("vec2.dot must exist"); let mut ctx = HostContext::new(None); let result = (dot.implementation)( &[Value::Float(1.0), Value::Float(2.0), Value::Float(3.0), Value::Float(4.0)], &mut ctx, ) .expect("dot implementation must execute"); assert_eq!(result, vec![Value::Float(11.0)]); let length = lookup_intrinsic("vec2", "length", 1).expect("vec2.length must exist"); let result = (length.implementation)(&[Value::Float(3.0), Value::Float(4.0)], &mut ctx) .expect("length implementation must execute"); assert_eq!(result, vec![Value::Float(5.0)]); } #[test] fn color_intrinsics_construct_and_project_rgba8888() { let mut ctx = HostContext::new(None); let rgba = lookup_intrinsic("color", "rgba", 1).expect("color.rgba must exist"); let result = (rgba.implementation)( &[Value::Int64(0x11), Value::Int64(0x22), Value::Int64(0x33), Value::Int64(0x44)], &mut ctx, ) .expect("rgba implementation must execute"); assert_eq!(result, vec![Value::Int64(0x11223344)]); let html = lookup_intrinsic("color", "html_rgba", 1).expect("color.html_rgba must exist"); let result = (html.implementation)(&[Value::String("#AABBCCDD".into())], &mut ctx) .expect("html_rgba implementation must execute"); assert_eq!(result, vec![Value::Int64(0xAABBCCDD)]); let alpha = lookup_intrinsic("color", "alpha", 1).expect("color.alpha must exist"); let result = (alpha.implementation)(&[Value::Int64(0xAABBCCDD)], &mut ctx) .expect("alpha implementation must execute"); assert_eq!(result, vec![Value::Int64(0xDD)]); } #[test] fn input_intrinsic_requires_hardware_context() { let mut ctx = HostContext::new(None); let touch_x = lookup_intrinsic("input.touch", "x", 1).expect("input.touch.x must exist"); let err = (touch_x.implementation)(&[Value::Int64(TOUCH_HANDLE)], &mut ctx) .expect_err("touch.x must fail without hardware context"); assert_eq!(err, IntrinsicExecutionError::HardwareUnavailable); } #[test] fn pixel_flat_values_validate_with_color_carrier_slot() { let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); let values = vec![Value::Int32(1), Value::Int32(2), Value::Int32(0xF800)]; pixel.validate_flat_values(&values).expect("pixel flat values must validate"); assert_eq!(pixel.flat_slot_width, 3); assert_eq!(pixel.field("color").map(|field| field.start_slot), Some(2)); } }