From ab0e577c6f75f1d51b48ab1ab3bd4bdffc7fbab1 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sun, 24 May 2026 09:44:33 +0100 Subject: [PATCH] add color RGBA8888 VM intrinsics --- crates/console/prometeu-vm/src/builtins.rs | 524 ++++++++++++++++++++- docs/vm-arch/INTRINSICS.csv | 19 + 2 files changed, 535 insertions(+), 8 deletions(-) diff --git a/crates/console/prometeu-vm/src/builtins.rs b/crates/console/prometeu-vm/src/builtins.rs index f4729e71..80c056da 100644 --- a/crates/console/prometeu-vm/src/builtins.rs +++ b/crates/console/prometeu-vm/src/builtins.rs @@ -1,5 +1,5 @@ use prometeu_bytecode::Value; -use prometeu_hal::HostContext; +use prometeu_hal::{HostContext, color::Color}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BuiltinTypeKey { @@ -56,6 +56,7 @@ pub enum BuiltinScalarType { Int, Float, Bool, + Str, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -362,12 +363,63 @@ 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 BUILTIN_CONSTS: [BuiltinConstMeta; 1] = [BuiltinConstMeta { - key: BuiltinConstKey::new("vec2", "zero", 1), - flat_slot_layout: &VEC2_ZERO_LAYOUT, - flat_slot_width: 2, - materializer: BuiltinConstMaterializer::Direct(&VEC2_ZERO_SLOTS), -}]; +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), @@ -379,6 +431,20 @@ 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)]; @@ -388,7 +454,7 @@ 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; 23] = [ +const INTRINSICS: [IntrinsicMeta; 42] = [ IntrinsicMeta { id: 0x1000, owner: "vec2", @@ -411,6 +477,215 @@ const INTRINSICS: [IntrinsicMeta; 23] = [ 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", @@ -698,11 +973,35 @@ fn expect_float_arg(args: &[Value], index: usize) -> Result 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(), } } @@ -755,6 +1054,179 @@ fn vec2_length( 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<'_>, @@ -1067,6 +1539,19 @@ mod tests { 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"); @@ -1105,6 +1590,29 @@ mod tests { 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); diff --git a/docs/vm-arch/INTRINSICS.csv b/docs/vm-arch/INTRINSICS.csv index 74b3d3ef..33779d1e 100644 --- a/docs/vm-arch/INTRINSICS.csv +++ b/docs/vm-arch/INTRINSICS.csv @@ -1,6 +1,25 @@ final_id_hex,final_id_dec,canonical_name,canonical_version,owner,name,arg_slots,ret_slots,arg_layout,ret_layout,deterministic,may_allocate 0x1000,4096,vec2.dot,1,vec2,dot,4,1,float|float|float|float,float,true,false 0x1001,4097,vec2.length,1,vec2,length,2,1,float|float,float,true,false +0x1100,4352,color.from_raw,1,color,from_raw,1,1,int,builtin:color,true,false +0x1101,4353,color.rgb,1,color,rgb,3,1,int|int|int,builtin:color,true,false +0x1102,4354,color.rgba,1,color,rgba,4,1,int|int|int|int,builtin:color,true,false +0x1103,4355,color.html_rgba,1,color,html_rgba,1,1,str,builtin:color,true,false +0x1104,4356,color.gray_scale,1,color,gray_scale,1,1,int,builtin:color,true,false +0x1105,4357,color.hex,1,color,hex,1,1,builtin:color,int,true,false +0x1106,4358,color.alpha,1,color,alpha,1,1,builtin:color,int,true,false +0x1120,4384,color.black,1,color,black,0,1,,builtin:color,true,false +0x1121,4385,color.white,1,color,white,0,1,,builtin:color,true,false +0x1122,4386,color.red,1,color,red,0,1,,builtin:color,true,false +0x1123,4387,color.green,1,color,green,0,1,,builtin:color,true,false +0x1124,4388,color.blue,1,color,blue,0,1,,builtin:color,true,false +0x1125,4389,color.yellow,1,color,yellow,0,1,,builtin:color,true,false +0x1126,4390,color.orange,1,color,orange,0,1,,builtin:color,true,false +0x1127,4391,color.indigo,1,color,indigo,0,1,,builtin:color,true,false +0x1128,4392,color.gray,1,color,gray,0,1,,builtin:color,true,false +0x1129,4393,color.cyan,1,color,cyan,0,1,,builtin:color,true,false +0x112A,4394,color.magenta,1,color,magenta,0,1,,builtin:color,true,false +0x112B,4395,color.transparent,1,color,transparent,0,1,,builtin:color,true,false 0x2000,8192,input.pad,1,input,pad,0,1,,builtin:input.pad,true,false 0x2001,8193,input.touch,1,input,touch,0,1,,builtin:input.touch,true,false 0x2010,8208,input.pad.up,1,input.pad,up,1,1,builtin:input.pad,builtin:input.button,true,false