2026-06-15 06:01:49 +01:00

1638 lines
49 KiB
Rust

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<AbiType> {
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<AbiType>) {
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<Value>;
#[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<Vec<Value>, 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<Vec<Value>, 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<Option<Vec<Value>>, 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<AbiType>) {
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<f64, IntrinsicExecutionError> {
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<i64, IntrinsicExecutionError> {
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<Vec<Value>, 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<Vec<Value>, 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<Value> {
vec![Value::Int64(color.raw() as i64)]
}
fn expect_color_arg(args: &[Value], index: usize) -> Result<Color, IntrinsicExecutionError> {
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<u8, IntrinsicExecutionError> {
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<Color> {
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<Vec<Value>, 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<Vec<Value>, 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<Vec<Value>, 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<Vec<Value>, 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<Vec<Value>, IntrinsicExecutionError> {
Ok(color_value(Color::gray_scale(expect_channel(args, 0)?)))
}
fn color_hex(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
Ok(vec![Value::Int64(expect_color_arg(args, 0)?.hex() as i64)])
}
fn color_alpha(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
Ok(vec![Value::Int64(expect_color_arg(args, 0)?.alpha() as i64)])
}
fn color_const(color: Color) -> Result<Vec<Value>, IntrinsicExecutionError> {
Ok(color_value(color))
}
fn color_black(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::BLACK)
}
fn color_white(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::WHITE)
}
fn color_red(_: &[Value], _: &mut HostContext<'_>) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::RED)
}
fn color_green(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::GREEN)
}
fn color_blue(_: &[Value], _: &mut HostContext<'_>) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::BLUE)
}
fn color_yellow(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::YELLOW)
}
fn color_orange(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::ORANGE)
}
fn color_indigo(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::INDIGO)
}
fn color_gray(_: &[Value], _: &mut HostContext<'_>) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::GRAY)
}
fn color_cyan(_: &[Value], _: &mut HostContext<'_>) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::CYAN)
}
fn color_magenta(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::MAGENTA)
}
fn color_transparent(
_: &[Value],
_: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
color_const(Color::TRANSPARENT)
}
fn input_pad(
_args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
Ok(vec![Value::Int64(PAD_HANDLE)])
}
fn input_touch(
_args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
Ok(vec![Value::Int64(TOUCH_HANDLE)])
}
fn input_pad_up(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "up")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE)])
}
fn input_pad_down(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "down")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 1)])
}
fn input_pad_left(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "left")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 2)])
}
fn input_pad_right(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "right")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 3)])
}
fn input_pad_a(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "a")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 4)])
}
fn input_pad_b(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "b")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 5)])
}
fn input_pad_x(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "x")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 6)])
}
fn input_pad_y(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "y")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 7)])
}
fn input_pad_l(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "l")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 8)])
}
fn input_pad_r(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "r")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 9)])
}
fn input_pad_start(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "start")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 10)])
}
fn input_pad_select(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_pad_handle(args, "select")?;
Ok(vec![Value::Int64(PAD_BUTTON_BASE + 11)])
}
fn input_touch_button(
args: &[Value],
_ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_touch_handle(args, "button")?;
Ok(vec![Value::Int64(TOUCH_BUTTON_CARRIER)])
}
fn input_touch_x(
args: &[Value],
ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, 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<Vec<Value>, 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<Vec<Value>, IntrinsicExecutionError> {
let button = resolve_button(args, "pressed", ctx)?;
Ok(vec![Value::Boolean(button.pressed)])
}
fn input_button_released(
args: &[Value],
ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
let button = resolve_button(args, "released", ctx)?;
Ok(vec![Value::Boolean(button.released)])
}
fn input_button_down(
args: &[Value],
ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> {
let button = resolve_button(args, "down", ctx)?;
Ok(vec![Value::Boolean(button.down)])
}
fn input_button_hold(
args: &[Value],
ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, 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<i64, IntrinsicExecutionError> {
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<prometeu_hal::button::Button, IntrinsicExecutionError> {
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));
}
}