diff --git a/benches/src/test_data.rs b/benches/src/test_data.rs index 8fbcbb2..e64ae51 100644 --- a/benches/src/test_data.rs +++ b/benches/src/test_data.rs @@ -89,6 +89,6 @@ pub fn execute_with_history(source: &str, bars: &[Bar]) -> Result<(), pine::Erro let historical_data = BenchHistoricalData::new(bars.to_vec()); historical_data.set_current_bar(bars.len() - 1); script.set_historical_provider(Box::new(historical_data)); - script.execute(&bars[bars.len() - 1])?; + let _output = script.execute(&bars[bars.len() - 1])?; Ok(()) } diff --git a/crates/pine-builtin-macro/src/lib.rs b/crates/pine-builtin-macro/src/lib.rs index 2b1961c..996c10a 100644 --- a/crates/pine-builtin-macro/src/lib.rs +++ b/crates/pine-builtin-macro/src/lib.rs @@ -367,12 +367,61 @@ fn generate_value_conversion( ) -> proc_macro2::TokenStream { let type_str = quote! { #field_type }.to_string(); - let conversion = if type_str.contains("f64") { + // Check if this is an Option type + let is_option = type_str.contains("Option"); + + let conversion = if is_option { + // For Option types, handle Na values + // Check for Color BEFORE other types since Value might also be in the type string + if type_str.contains("Color") && !type_str.contains("Value") { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value.as_color()?) + } + } + } else if type_str.contains("f64") { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value.as_number()?) + } + } + } else if type_str.contains("String") { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value.as_string()?) + } + } + } else if type_str.contains("bool") { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value.as_bool()?) + } + } + } else { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value) + } + } + } + } else if type_str.contains("f64") { quote! { arg_value.as_number()? } } else if type_str.contains("String") { quote! { arg_value.as_string()? } } else if type_str.contains("bool") { quote! { arg_value.as_bool()? } + } else if type_str.contains("Color") { + quote! { arg_value.as_color()? } } else if type_str.contains("Value") { quote! { arg_value } } else { diff --git a/crates/pine-builtins/src/box/mod.rs b/crates/pine-builtins/src/box/mod.rs index 54f3b9c..1b50a22 100644 --- a/crates/pine-builtins/src/box/mod.rs +++ b/crates/pine-builtins/src/box/mod.rs @@ -1,5 +1,5 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Interpreter, RuntimeError, Value}; +use pine_interpreter::{Color, Interpreter, PineBox, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -12,62 +12,62 @@ struct BoxNew { top: Value, right: Value, bottom: Value, - #[arg(default = Value::Na)] - border_color: Value, - #[arg(default = Value::Number(1.0))] - border_width: Value, - #[arg(default = Value::String("solid".to_string()))] - border_style: Value, - #[arg(default = Value::String("none".to_string()))] - extend: Value, - #[arg(default = Value::String("bar_index".to_string()))] - xloc: Value, - #[arg(default = Value::Na)] - bgcolor: Value, - #[arg(default = Value::String(String::new()))] - text: Value, - #[arg(default = Value::Number(0.0))] - text_size: Value, - #[arg(default = Value::Na)] - text_color: Value, - #[arg(default = Value::String("center".to_string()))] - text_halign: Value, - #[arg(default = Value::String("center".to_string()))] - text_valign: Value, - #[arg(default = Value::String("none".to_string()))] - text_wrap: Value, - #[arg(default = Value::String("default".to_string()))] - text_font_family: Value, + #[arg(default = None)] + border_color: Option, + #[arg(default = 1.0)] + border_width: f64, + #[arg(default = "solid")] + border_style: String, + #[arg(default = "none")] + extend: String, + #[arg(default = "bar_index")] + xloc: String, + #[arg(default = None)] + bgcolor: Option, + #[arg(default = "")] + text: String, + #[arg(default = 0.0)] + text_size: f64, + #[arg(default = None)] + text_color: Option, + #[arg(default = "center")] + text_halign: String, + #[arg(default = "center")] + text_valign: String, + #[arg(default = "none")] + text_wrap: String, + #[arg(default = "default")] + text_font_family: String, } impl BoxNew { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("left".to_string(), self.left.clone()); - fields.insert("top".to_string(), self.top.clone()); - fields.insert("right".to_string(), self.right.clone()); - fields.insert("bottom".to_string(), self.bottom.clone()); - fields.insert("border_color".to_string(), self.border_color.clone()); - fields.insert("border_width".to_string(), self.border_width.clone()); - fields.insert("border_style".to_string(), self.border_style.clone()); - fields.insert("extend".to_string(), self.extend.clone()); - fields.insert("xloc".to_string(), self.xloc.clone()); - fields.insert("bgcolor".to_string(), self.bgcolor.clone()); - fields.insert("text".to_string(), self.text.clone()); - fields.insert("text_size".to_string(), self.text_size.clone()); - fields.insert("text_color".to_string(), self.text_color.clone()); - fields.insert("text_halign".to_string(), self.text_halign.clone()); - fields.insert("text_valign".to_string(), self.text_valign.clone()); - fields.insert("text_wrap".to_string(), self.text_wrap.clone()); - fields.insert( - "text_font_family".to_string(), - self.text_font_family.clone(), - ); - - Ok(Value::Object { - type_name: "box".to_string(), - fields: Rc::new(RefCell::new(fields)), - }) + fn execute(&self, ctx: &mut Interpreter) -> Result { + // Create a box struct + let box_obj = PineBox { + left: self.left.clone(), + top: self.top.clone(), + right: self.right.clone(), + bottom: self.bottom.clone(), + border_color: self.border_color.clone(), + border_width: self.border_width, + border_style: self.border_style.clone(), + extend: self.extend.clone(), + xloc: self.xloc.clone(), + bgcolor: self.bgcolor.clone(), + text: self.text.clone(), + text_size: self.text_size, + text_color: self.text_color.clone(), + text_halign: self.text_halign.clone(), + text_valign: self.text_valign.clone(), + text_wrap: self.text_wrap.clone(), + text_font_family: self.text_font_family.clone(), + }; + + // Add to interpreter and get ID + let id = ctx.output.add_box(box_obj); + + // Return the ID as a number + Ok(Value::Number(id as f64)) } } @@ -75,20 +75,19 @@ impl BoxNew { #[derive(BuiltinFunction)] #[builtin(name = "box.set_left")] struct BoxSetLeft { - id: Value, + id: f64, left: Value, } impl BoxSetLeft { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("left".to_string(), self.left.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.left = self.left.clone(); + Ok(Value::Na) } } @@ -96,20 +95,19 @@ impl BoxSetLeft { #[derive(BuiltinFunction)] #[builtin(name = "box.set_top")] struct BoxSetTop { - id: Value, + id: f64, top: Value, } impl BoxSetTop { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("top".to_string(), self.top.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.top = self.top.clone(); + Ok(Value::Na) } } @@ -117,20 +115,19 @@ impl BoxSetTop { #[derive(BuiltinFunction)] #[builtin(name = "box.set_right")] struct BoxSetRight { - id: Value, + id: f64, right: Value, } impl BoxSetRight { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("right".to_string(), self.right.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.right = self.right.clone(); + Ok(Value::Na) } } @@ -138,20 +135,19 @@ impl BoxSetRight { #[derive(BuiltinFunction)] #[builtin(name = "box.set_bottom")] struct BoxSetBottom { - id: Value, + id: f64, bottom: Value, } impl BoxSetBottom { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("bottom".to_string(), self.bottom.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.bottom = self.bottom.clone(); + Ok(Value::Na) } } @@ -159,21 +155,21 @@ impl BoxSetBottom { #[derive(BuiltinFunction)] #[builtin(name = "box.set_lefttop")] struct BoxSetLefttop { - id: Value, + id: f64, left: Value, top: Value, } impl BoxSetLefttop { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - let mut fields_mut = fields.borrow_mut(); - fields_mut.insert("left".to_string(), self.left.clone()); - fields_mut.insert("top".to_string(), self.top.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.left = self.left.clone(); + box_obj.top = self.top.clone(); + Ok(Value::Na) } } @@ -181,21 +177,21 @@ impl BoxSetLefttop { #[derive(BuiltinFunction)] #[builtin(name = "box.set_rightbottom")] struct BoxSetRightbottom { - id: Value, + id: f64, right: Value, bottom: Value, } impl BoxSetRightbottom { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - let mut fields_mut = fields.borrow_mut(); - fields_mut.insert("right".to_string(), self.right.clone()); - fields_mut.insert("bottom".to_string(), self.bottom.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.right = self.right.clone(); + box_obj.bottom = self.bottom.clone(); + Ok(Value::Na) } } @@ -203,20 +199,19 @@ impl BoxSetRightbottom { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_color")] struct BoxSetBorderColor { - id: Value, - color: Value, + id: f64, + color: Color, } impl BoxSetBorderColor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("border_color".to_string(), self.color.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.border_color = Some(self.color.clone()); + Ok(Value::Na) } } @@ -224,20 +219,19 @@ impl BoxSetBorderColor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_width")] struct BoxSetBorderWidth { - id: Value, - width: Value, + id: f64, + width: f64, } impl BoxSetBorderWidth { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("border_width".to_string(), self.width.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.border_width = self.width; + Ok(Value::Na) } } @@ -245,20 +239,19 @@ impl BoxSetBorderWidth { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_style")] struct BoxSetBorderStyle { - id: Value, - style: Value, + id: f64, + style: String, } impl BoxSetBorderStyle { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("border_style".to_string(), self.style.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.border_style = self.style.clone(); + Ok(Value::Na) } } @@ -266,20 +259,19 @@ impl BoxSetBorderStyle { #[derive(BuiltinFunction)] #[builtin(name = "box.set_extend")] struct BoxSetExtend { - id: Value, - extend: Value, + id: f64, + extend: String, } impl BoxSetExtend { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("extend".to_string(), self.extend.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.extend = self.extend.clone(); + Ok(Value::Na) } } @@ -287,20 +279,19 @@ impl BoxSetExtend { #[derive(BuiltinFunction)] #[builtin(name = "box.set_bgcolor")] struct BoxSetBgcolor { - id: Value, - color: Value, + id: f64, + color: Color, } impl BoxSetBgcolor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("bgcolor".to_string(), self.color.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.bgcolor = Some(self.color.clone()); + Ok(Value::Na) } } @@ -308,20 +299,19 @@ impl BoxSetBgcolor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text")] struct BoxSetText { - id: Value, - text: Value, + id: f64, + text: String, } impl BoxSetText { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text".to_string(), self.text.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text = self.text.clone(); + Ok(Value::Na) } } @@ -329,20 +319,19 @@ impl BoxSetText { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_color")] struct BoxSetTextColor { - id: Value, - color: Value, + id: f64, + color: Color, } impl BoxSetTextColor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_color".to_string(), self.color.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_color = Some(self.color.clone()); + Ok(Value::Na) } } @@ -350,20 +339,19 @@ impl BoxSetTextColor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_size")] struct BoxSetTextSize { - id: Value, - size: Value, + id: f64, + size: f64, } impl BoxSetTextSize { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_size".to_string(), self.size.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_size = self.size; + Ok(Value::Na) } } @@ -371,20 +359,19 @@ impl BoxSetTextSize { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_halign")] struct BoxSetTextHalign { - id: Value, - halign: Value, + id: f64, + halign: String, } impl BoxSetTextHalign { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_halign".to_string(), self.halign.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_halign = self.halign.clone(); + Ok(Value::Na) } } @@ -392,20 +379,19 @@ impl BoxSetTextHalign { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_valign")] struct BoxSetTextValign { - id: Value, - valign: Value, + id: f64, + valign: String, } impl BoxSetTextValign { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_valign".to_string(), self.valign.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_valign = self.valign.clone(); + Ok(Value::Na) } } @@ -413,20 +399,19 @@ impl BoxSetTextValign { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_wrap")] struct BoxSetTextWrap { - id: Value, - wrap: Value, + id: f64, + wrap: String, } impl BoxSetTextWrap { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_wrap".to_string(), self.wrap.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_wrap = self.wrap.clone(); + Ok(Value::Na) } } @@ -434,20 +419,19 @@ impl BoxSetTextWrap { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_font_family")] struct BoxSetTextFontFamily { - id: Value, - font_family: Value, + id: f64, + font_family: String, } impl BoxSetTextFontFamily { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text_font_family".to_string(), self.font_family.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.text_font_family = self.font_family.clone(); + Ok(Value::Na) } } @@ -455,23 +439,23 @@ impl BoxSetTextFontFamily { #[derive(BuiltinFunction)] #[builtin(name = "box.set_xloc")] struct BoxSetXloc { - id: Value, + id: f64, left: Value, right: Value, - xloc: Value, + xloc: String, } impl BoxSetXloc { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - let mut fields_mut = fields.borrow_mut(); - fields_mut.insert("left".to_string(), self.left.clone()); - fields_mut.insert("right".to_string(), self.right.clone()); - fields_mut.insert("xloc".to_string(), self.xloc.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + box_obj.left = self.left.clone(); + box_obj.right = self.right.clone(); + box_obj.xloc = self.xloc.clone(); + Ok(Value::Na) } } @@ -479,16 +463,17 @@ impl BoxSetXloc { #[derive(BuiltinFunction)] #[builtin(name = "box.get_left")] struct BoxGetLeft { - id: Value, + id: f64, } impl BoxGetLeft { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("left").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + Ok(box_obj.left.clone()) } } @@ -496,16 +481,17 @@ impl BoxGetLeft { #[derive(BuiltinFunction)] #[builtin(name = "box.get_top")] struct BoxGetTop { - id: Value, + id: f64, } impl BoxGetTop { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("top").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + Ok(box_obj.top.clone()) } } @@ -513,16 +499,17 @@ impl BoxGetTop { #[derive(BuiltinFunction)] #[builtin(name = "box.get_right")] struct BoxGetRight { - id: Value, + id: f64, } impl BoxGetRight { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("right").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + Ok(box_obj.right.clone()) } } @@ -530,16 +517,17 @@ impl BoxGetRight { #[derive(BuiltinFunction)] #[builtin(name = "box.get_bottom")] struct BoxGetBottom { - id: Value, + id: f64, } impl BoxGetBottom { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("bottom").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + Ok(box_obj.bottom.clone()) } } @@ -547,12 +535,13 @@ impl BoxGetBottom { #[derive(BuiltinFunction)] #[builtin(name = "box.delete")] struct BoxDelete { - _id: Value, + id: f64, } impl BoxDelete { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - // For now, just return na - actual deletion would be handled by state management + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + ctx.output.delete_box(id); Ok(Value::Na) } } @@ -561,20 +550,19 @@ impl BoxDelete { #[derive(BuiltinFunction)] #[builtin(name = "box.copy")] struct BoxCopy { - id: Value, + id: f64, } impl BoxCopy { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { type_name, fields } = &self.id { - let copied_fields = fields.borrow().clone(); - Ok(Value::Object { - type_name: type_name.clone(), - fields: Rc::new(RefCell::new(copied_fields)), - }) - } else { - Err(RuntimeError::TypeError("Expected box object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let box_obj = ctx + .output + .get_box_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Box with id {} not found", id)))?; + let copied_box = box_obj.clone(); + let new_id = ctx.output.add_box(copied_box); + Ok(Value::Number(new_id as f64)) } } diff --git a/crates/pine-builtins/src/color/mod.rs b/crates/pine-builtins/src/color/mod.rs index 40ce4c3..968ea26 100644 --- a/crates/pine-builtins/src/color/mod.rs +++ b/crates/pine-builtins/src/color/mod.rs @@ -11,9 +11,9 @@ struct ColorNew { impl ColorNew { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (r, g, b, _) = self.color.as_color()?; - let transp = self.transp.clamp(0.0, 100.0) as u8; - Ok(Value::Color { r, g, b, t: transp }) + let mut color = self.color.as_color()?; + color.t = self.transp.clamp(0.0, 100.0) as u8; + Ok(Value::Color(color)) } } @@ -34,7 +34,7 @@ impl ColorRgb { let g = self.green.clamp(0.0, 255.0) as u8; let b = self.blue.clamp(0.0, 255.0) as u8; let t = self.transp.clamp(0.0, 100.0) as u8; - Ok(Value::Color { r, g, b, t }) + Ok(Value::Color(pine_interpreter::Color::new(r, g, b, t))) } } @@ -47,8 +47,8 @@ struct ColorR { impl ColorR { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (r, _, _, _) = self.color.as_color()?; - Ok(Value::Number(r as f64)) + let color = self.color.as_color()?; + Ok(Value::Number(color.r as f64)) } } @@ -61,8 +61,8 @@ struct ColorG { impl ColorG { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (_, g, _, _) = self.color.as_color()?; - Ok(Value::Number(g as f64)) + let color = self.color.as_color()?; + Ok(Value::Number(color.g as f64)) } } @@ -75,8 +75,8 @@ struct ColorB { impl ColorB { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (_, _, b, _) = self.color.as_color()?; - Ok(Value::Number(b as f64)) + let color = self.color.as_color()?; + Ok(Value::Number(color.b as f64)) } } @@ -89,8 +89,8 @@ struct ColorT { impl ColorT { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (_, _, _, t) = self.color.as_color()?; - Ok(Value::Number(t as f64)) + let color = self.color.as_color()?; + Ok(Value::Number(color.t as f64)) } } @@ -108,8 +108,8 @@ struct ColorFromGradient { impl ColorFromGradient { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let (r1, g1, b1, t1) = self.bottom_color.as_color()?; - let (r2, g2, b2, t2) = self.top_color.as_color()?; + let c1 = self.bottom_color.as_color()?; + let c2 = self.top_color.as_color()?; // Calculate the position ratio (0.0 to 1.0) let ratio = if (self.top_value - self.bottom_value).abs() < f64::EPSILON { @@ -120,12 +120,12 @@ impl ColorFromGradient { }; // Interpolate each component - let r = (r1 as f64 + (r2 as f64 - r1 as f64) * ratio) as u8; - let g = (g1 as f64 + (g2 as f64 - g1 as f64) * ratio) as u8; - let b = (b1 as f64 + (b2 as f64 - b1 as f64) * ratio) as u8; - let t = (t1 as f64 + (t2 as f64 - t1 as f64) * ratio) as u8; + let r = (c1.r as f64 + (c2.r as f64 - c1.r as f64) * ratio) as u8; + let g = (c1.g as f64 + (c2.g as f64 - c1.g as f64) * ratio) as u8; + let b = (c1.b as f64 + (c2.b as f64 - c1.b as f64) * ratio) as u8; + let t = (c1.t as f64 + (c2.t as f64 - c1.t as f64) * ratio) as u8; - Ok(Value::Color { r, g, b, t }) + Ok(Value::Color(pine_interpreter::Color::new(r, g, b, t))) } } diff --git a/crates/pine-builtins/src/label/mod.rs b/crates/pine-builtins/src/label/mod.rs index 810ae07..52472b0 100644 --- a/crates/pine-builtins/src/label/mod.rs +++ b/crates/pine-builtins/src/label/mod.rs @@ -1,5 +1,5 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Interpreter, RuntimeError, Value}; +use pine_interpreter::{Color, Interpreter, Label, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -10,52 +10,51 @@ use std::rc::Rc; struct LabelNew { x: Value, y: Value, - #[arg(default = Value::String(String::new()))] - text: Value, - #[arg(default = Value::String("bar_index".to_string()))] - xloc: Value, - #[arg(default = Value::String("price".to_string()))] - yloc: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::String("style_label_down".to_string()))] - style: Value, - #[arg(default = Value::Na)] - textcolor: Value, - #[arg(default = Value::String("normal".to_string()))] - size: Value, - #[arg(default = Value::String("center".to_string()))] - textalign: Value, - #[arg(default = Value::Na)] - tooltip: Value, - #[arg(default = Value::String("default".to_string()))] - text_font_family: Value, + #[arg(default = "")] + text: String, + #[arg(default = "bar_index")] + xloc: String, + #[arg(default = "price")] + yloc: String, + #[arg(default = None)] + color: Option, + #[arg(default = "style_label_down")] + style: String, + #[arg(default = None)] + textcolor: Option, + #[arg(default = "normal")] + size: String, + #[arg(default = "center")] + textalign: String, + #[arg(default = None)] + tooltip: Option, + #[arg(default = "default")] + text_font_family: String, } impl LabelNew { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - // Create a label object with all the properties - let mut fields = HashMap::new(); - fields.insert("x".to_string(), self.x.clone()); - fields.insert("y".to_string(), self.y.clone()); - fields.insert("text".to_string(), self.text.clone()); - fields.insert("xloc".to_string(), self.xloc.clone()); - fields.insert("yloc".to_string(), self.yloc.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("style".to_string(), self.style.clone()); - fields.insert("textcolor".to_string(), self.textcolor.clone()); - fields.insert("size".to_string(), self.size.clone()); - fields.insert("textalign".to_string(), self.textalign.clone()); - fields.insert("tooltip".to_string(), self.tooltip.clone()); - fields.insert( - "text_font_family".to_string(), - self.text_font_family.clone(), - ); - - Ok(Value::Object { - type_name: "label".to_string(), - fields: Rc::new(RefCell::new(fields)), - }) + fn execute(&self, ctx: &mut Interpreter) -> Result { + // Create a label struct + let label = Label { + x: self.x.clone(), + y: self.y.clone(), + text: self.text.clone(), + xloc: self.xloc.clone(), + yloc: self.yloc.clone(), + color: self.color.clone(), + style: self.style.clone(), + textcolor: self.textcolor.clone(), + size: self.size.clone(), + textalign: self.textalign.clone(), + tooltip: self.tooltip.clone(), + text_font_family: self.text_font_family.clone(), + }; + + // Add to interpreter and get ID + let id = ctx.output.add_label(label); + + // Return the ID as a number + Ok(Value::Number(id as f64)) } } @@ -63,18 +62,19 @@ impl LabelNew { #[derive(BuiltinFunction)] #[builtin(name = "label.set_x")] struct LabelSetX { - id: Value, + id: f64, x: Value, } impl LabelSetX { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields.borrow_mut().insert("x".to_string(), self.x.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.x = self.x.clone(); + Ok(Value::Na) } } @@ -82,18 +82,19 @@ impl LabelSetX { #[derive(BuiltinFunction)] #[builtin(name = "label.set_y")] struct LabelSetY { - id: Value, + id: f64, y: Value, } impl LabelSetY { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields.borrow_mut().insert("y".to_string(), self.y.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.y = self.y.clone(); + Ok(Value::Na) } } @@ -101,233 +102,223 @@ impl LabelSetY { #[derive(BuiltinFunction)] #[builtin(name = "label.set_xy")] struct LabelSetXy { - id: Value, + id: f64, x: Value, y: Value, } impl LabelSetXy { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - let mut fields_mut = fields.borrow_mut(); - fields_mut.insert("x".to_string(), self.x.clone()); - fields_mut.insert("y".to_string(), self.y.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.x = self.x.clone(); + label.y = self.y.clone(); + Ok(Value::Na) } } -/// label.set_xloc() - Sets the x location mode of a label +/// label.set_xloc() - Sets the x location mode and coordinate #[derive(BuiltinFunction)] #[builtin(name = "label.set_xloc")] struct LabelSetXloc { - id: Value, + id: f64, x: Value, - xloc: Value, + xloc: String, } impl LabelSetXloc { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - let mut fields_mut = fields.borrow_mut(); - fields_mut.insert("x".to_string(), self.x.clone()); - fields_mut.insert("xloc".to_string(), self.xloc.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.x = self.x.clone(); + label.xloc = self.xloc.clone(); + Ok(Value::Na) } } -/// label.set_yloc() - Sets the y location mode of a label +/// label.set_yloc() - Sets the y location mode #[derive(BuiltinFunction)] #[builtin(name = "label.set_yloc")] struct LabelSetYloc { - id: Value, - yloc: Value, + id: f64, + yloc: String, } impl LabelSetYloc { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("yloc".to_string(), self.yloc.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.yloc = self.yloc.clone(); + Ok(Value::Na) } } -/// label.set_color() - Sets the color of a label +/// label.set_color() - Sets the label color #[derive(BuiltinFunction)] #[builtin(name = "label.set_color")] struct LabelSetColor { - id: Value, - color: Value, + id: f64, + color: Color, } impl LabelSetColor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("color".to_string(), self.color.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.color = Some(self.color.clone()); + Ok(Value::Na) } } -/// label.set_style() - Sets the style of a label +/// label.set_style() - Sets the label style #[derive(BuiltinFunction)] #[builtin(name = "label.set_style")] struct LabelSetStyle { - id: Value, - style: Value, + id: f64, + style: String, } impl LabelSetStyle { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("style".to_string(), self.style.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.style = self.style.clone(); + Ok(Value::Na) } } -/// label.set_text() - Sets the text of a label +/// label.set_text() - Sets the label text #[derive(BuiltinFunction)] #[builtin(name = "label.set_text")] struct LabelSetText { - id: Value, - text: Value, + id: f64, + text: String, } impl LabelSetText { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text".to_string(), self.text.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.text = self.text.clone(); + Ok(Value::Na) } } -/// label.set_textcolor() - Sets the text color of a label +/// label.set_textcolor() - Sets the label text color #[derive(BuiltinFunction)] #[builtin(name = "label.set_textcolor")] struct LabelSetTextcolor { - id: Value, - textcolor: Value, + id: f64, + textcolor: Color, } impl LabelSetTextcolor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("textcolor".to_string(), self.textcolor.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.textcolor = Some(self.textcolor.clone()); + Ok(Value::Na) } } -/// label.set_size() - Sets the size of a label +/// label.set_size() - Sets the label size #[derive(BuiltinFunction)] #[builtin(name = "label.set_size")] struct LabelSetSize { - id: Value, - size: Value, + id: f64, + size: String, } impl LabelSetSize { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("size".to_string(), self.size.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.size = self.size.clone(); + Ok(Value::Na) } } -/// label.set_textalign() - Sets the text alignment of a label +/// label.set_textalign() - Sets the label text alignment #[derive(BuiltinFunction)] #[builtin(name = "label.set_textalign")] struct LabelSetTextalign { - id: Value, - textalign: Value, + id: f64, + textalign: String, } impl LabelSetTextalign { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("textalign".to_string(), self.textalign.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.textalign = self.textalign.clone(); + Ok(Value::Na) } } -/// label.set_tooltip() - Sets the tooltip of a label +/// label.set_tooltip() - Sets the label tooltip #[derive(BuiltinFunction)] #[builtin(name = "label.set_tooltip")] struct LabelSetTooltip { - id: Value, - tooltip: Value, + id: f64, + tooltip: String, } impl LabelSetTooltip { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("tooltip".to_string(), self.tooltip.clone()); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.tooltip = Some(self.tooltip.clone()); + Ok(Value::Na) } } -/// label.set_text_font_family() - Sets the font family of a label +/// label.set_text_font_family() - Sets the label text font family #[derive(BuiltinFunction)] #[builtin(name = "label.set_text_font_family")] struct LabelSetTextFontFamily { - id: Value, - text_font_family: Value, + id: f64, + text_font_family: String, } impl LabelSetTextFontFamily { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields.borrow_mut().insert( - "text_font_family".to_string(), - self.text_font_family.clone(), - ); - Ok(Value::Na) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + label.text_font_family = self.text_font_family.clone(); + Ok(Value::Na) } } @@ -335,16 +326,17 @@ impl LabelSetTextFontFamily { #[derive(BuiltinFunction)] #[builtin(name = "label.get_x")] struct LabelGetX { - id: Value, + id: f64, } impl LabelGetX { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("x").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + Ok(label.x.clone()) } } @@ -352,16 +344,17 @@ impl LabelGetX { #[derive(BuiltinFunction)] #[builtin(name = "label.get_y")] struct LabelGetY { - id: Value, + id: f64, } impl LabelGetY { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("y").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + Ok(label.y.clone()) } } @@ -369,16 +362,17 @@ impl LabelGetY { #[derive(BuiltinFunction)] #[builtin(name = "label.get_text")] struct LabelGetText { - id: Value, + id: f64, } impl LabelGetText { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - Ok(fields.borrow().get("text").cloned().unwrap_or(Value::Na)) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + Ok(Value::String(label.text.clone())) } } @@ -386,12 +380,13 @@ impl LabelGetText { #[derive(BuiltinFunction)] #[builtin(name = "label.delete")] struct LabelDelete { - _id: Value, + id: f64, } impl LabelDelete { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - // For now, just return na - actual deletion would be handled by state management + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + ctx.output.delete_label(id); Ok(Value::Na) } } @@ -400,58 +395,112 @@ impl LabelDelete { #[derive(BuiltinFunction)] #[builtin(name = "label.copy")] struct LabelCopy { - id: Value, + id: f64, } impl LabelCopy { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { type_name, fields } = &self.id { - // Create a deep copy of the fields - let copied_fields = fields.borrow().clone(); - Ok(Value::Object { - type_name: type_name.clone(), - fields: Rc::new(RefCell::new(copied_fields)), - }) - } else { - Err(RuntimeError::TypeError("Expected label object".to_string())) - } + fn execute(&self, ctx: &mut Interpreter) -> Result { + let id = self.id as usize; + let label = ctx + .output + .get_label_mut(id) + .ok_or_else(|| RuntimeError::TypeError(format!("Label with id {} not found", id)))?; + let copied_label = label.clone(); + let new_id = ctx.output.add_label(copied_label); + Ok(Value::Number(new_id as f64)) } } -/// Register the label namespace with all label style constants and functions +/// Register the label namespace with all functions pub fn register() -> Value { let mut members = HashMap::new(); - // All label style constants as string values - let styles = [ - "style_arrowdown", - "style_arrowup", - "style_circle", - "style_cross", - "style_diamond", - "style_flag", - "style_label_center", - "style_label_down", - "style_label_left", - "style_label_lower_left", - "style_label_lower_right", - "style_label_right", - "style_label_up", - "style_label_upper_left", - "style_label_upper_right", - "style_none", - "style_square", - "style_text_outline", - "style_triangledown", - "style_triangleup", - "style_xcross", - ]; - - for style in styles { - members.insert(style.to_string(), Value::String(style.to_string())); - } + // Add style constants + members.insert( + "style_arrowdown".to_string(), + Value::String("style_arrowdown".to_string()), + ); + members.insert( + "style_arrowup".to_string(), + Value::String("style_arrowup".to_string()), + ); + members.insert( + "style_circle".to_string(), + Value::String("style_circle".to_string()), + ); + members.insert( + "style_cross".to_string(), + Value::String("style_cross".to_string()), + ); + members.insert( + "style_diamond".to_string(), + Value::String("style_diamond".to_string()), + ); + members.insert( + "style_flag".to_string(), + Value::String("style_flag".to_string()), + ); + members.insert( + "style_label_center".to_string(), + Value::String("style_label_center".to_string()), + ); + members.insert( + "style_label_down".to_string(), + Value::String("style_label_down".to_string()), + ); + members.insert( + "style_label_left".to_string(), + Value::String("style_label_left".to_string()), + ); + members.insert( + "style_label_lower_left".to_string(), + Value::String("style_label_lower_left".to_string()), + ); + members.insert( + "style_label_lower_right".to_string(), + Value::String("style_label_lower_right".to_string()), + ); + members.insert( + "style_label_right".to_string(), + Value::String("style_label_right".to_string()), + ); + members.insert( + "style_label_up".to_string(), + Value::String("style_label_up".to_string()), + ); + members.insert( + "style_label_upper_left".to_string(), + Value::String("style_label_upper_left".to_string()), + ); + members.insert( + "style_label_upper_right".to_string(), + Value::String("style_label_upper_right".to_string()), + ); + members.insert( + "style_none".to_string(), + Value::String("style_none".to_string()), + ); + members.insert( + "style_square".to_string(), + Value::String("style_square".to_string()), + ); + members.insert( + "style_text_outline".to_string(), + Value::String("style_text_outline".to_string()), + ); + members.insert( + "style_triangledown".to_string(), + Value::String("style_triangledown".to_string()), + ); + members.insert( + "style_triangleup".to_string(), + Value::String("style_triangleup".to_string()), + ); + members.insert( + "style_xcross".to_string(), + Value::String("style_xcross".to_string()), + ); - // Register functions members.insert( "new".to_string(), Value::BuiltinFunction(Rc::new(LabelNew::builtin_fn)), diff --git a/crates/pine-builtins/src/matrix/mod.rs b/crates/pine-builtins/src/matrix/mod.rs index 2c8759b..83008ea 100644 --- a/crates/pine-builtins/src/matrix/mod.rs +++ b/crates/pine-builtins/src/matrix/mod.rs @@ -10,29 +10,18 @@ use std::rc::Rc; struct MatrixNew { #[type_param] element_type: String, - #[arg(default = Value::Number(0.0))] - rows: Value, - #[arg(default = Value::Number(0.0))] - columns: Value, + #[arg(default = 0.0)] + rows: f64, + #[arg(default = 0.0)] + columns: f64, #[arg(default = Value::Na)] initial_value: Value, } impl MatrixNew { fn execute(&self, _ctx: &mut Interpreter) -> Result { - let rows = match &self.rows { - Value::Number(n) => *n as usize, - _ => return Err(RuntimeError::TypeError("rows must be a number".to_string())), - }; - - let columns = match &self.columns { - Value::Number(n) => *n as usize, - _ => { - return Err(RuntimeError::TypeError( - "columns must be a number".to_string(), - )) - } - }; + let rows = self.rows as usize; + let columns = self.columns as usize; // Validate element type if !matches!( @@ -67,8 +56,8 @@ impl MatrixNew { #[builtin(name = "matrix.get")] struct MatrixGet { id: Value, - row: Value, - column: Value, + row: f64, + column: f64, } impl MatrixGet { @@ -78,19 +67,8 @@ impl MatrixGet { _ => return Err(RuntimeError::TypeError("Expected matrix".to_string())), }; - let row_idx = match &self.row { - Value::Number(n) => *n as usize, - _ => return Err(RuntimeError::TypeError("row must be a number".to_string())), - }; - - let col_idx = match &self.column { - Value::Number(n) => *n as usize, - _ => { - return Err(RuntimeError::TypeError( - "column must be a number".to_string(), - )) - } - }; + let row_idx = self.row as usize; + let col_idx = self.column as usize; let matrix_ref = matrix.borrow(); if row_idx >= matrix_ref.len() { @@ -109,8 +87,8 @@ impl MatrixGet { #[builtin(name = "matrix.set")] struct MatrixSet { id: Value, - row: Value, - column: Value, + row: f64, + column: f64, value: Value, } @@ -121,19 +99,8 @@ impl MatrixSet { _ => return Err(RuntimeError::TypeError("Expected matrix".to_string())), }; - let row_idx = match &self.row { - Value::Number(n) => *n as usize, - _ => return Err(RuntimeError::TypeError("row must be a number".to_string())), - }; - - let col_idx = match &self.column { - Value::Number(n) => *n as usize, - _ => { - return Err(RuntimeError::TypeError( - "column must be a number".to_string(), - )) - } - }; + let row_idx = self.row as usize; + let col_idx = self.column as usize; let mut matrix_ref = matrix.borrow_mut(); if row_idx >= matrix_ref.len() { @@ -265,7 +232,7 @@ impl MatrixCopy { #[builtin(name = "matrix.add_row")] struct MatrixAddRow { id: Value, - row: Value, + row: f64, #[arg(default = Value::Na)] array_id: Value, } @@ -277,10 +244,7 @@ impl MatrixAddRow { _ => return Err(RuntimeError::TypeError("Expected matrix".to_string())), }; - let row_idx = match &self.row { - Value::Number(n) => *n as usize, - _ => return Err(RuntimeError::TypeError("row must be a number".to_string())), - }; + let row_idx = self.row as usize; let mut matrix_ref = matrix.borrow_mut(); let cols = if matrix_ref.is_empty() { @@ -314,7 +278,7 @@ impl MatrixAddRow { #[builtin(name = "matrix.add_col")] struct MatrixAddCol { id: Value, - column: Value, + column: f64, #[arg(default = Value::Na)] array_id: Value, } @@ -326,14 +290,7 @@ impl MatrixAddCol { _ => return Err(RuntimeError::TypeError("Expected matrix".to_string())), }; - let col_idx = match &self.column { - Value::Number(n) => *n as usize, - _ => { - return Err(RuntimeError::TypeError( - "column must be a number".to_string(), - )) - } - }; + let col_idx = self.column as usize; let mut matrix_ref = matrix.borrow_mut(); diff --git a/crates/pine-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index 803dfd0..ad20410 100644 --- a/crates/pine-builtins/src/plot/mod.rs +++ b/crates/pine-builtins/src/plot/mod.rs @@ -1,6 +1,9 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Interpreter, RuntimeError, Value}; -use std::cell::RefCell; +use pine_interpreter::{ + Color, Interpreter, Plot as PlotOutput, Plotarrow as PlotarrowOutput, Plotbar as PlotbarOutput, + Plotcandle as PlotcandleOutput, Plotchar as PlotcharOutput, Plotshape as PlotshapeOutput, + RuntimeError, Value, +}; use std::collections::HashMap; use std::rc::Rc; @@ -9,62 +12,61 @@ use std::rc::Rc; #[builtin(name = "plot")] struct Plot { series: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::Number(1.0))] - linewidth: Value, - #[arg(default = Value::String("line".to_string()))] - style: Value, - #[arg(default = Value::Bool(false))] - trackprice: Value, - #[arg(default = Value::Number(0.0))] - histbase: Value, - #[arg(default = Value::Number(0.0))] - offset: Value, - #[arg(default = Value::Bool(false))] - join: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, - #[arg(default = Value::String("solid".to_string()))] - linestyle: Value, + #[arg(default = "")] + title: String, + #[arg(default = None)] + color: Option, + #[arg(default = 1.0)] + linewidth: f64, + #[arg(default = "line")] + style: String, + #[arg(default = false)] + trackprice: bool, + #[arg(default = 0.0)] + histbase: f64, + #[arg(default = 0.0)] + offset: f64, + #[arg(default = false)] + join: bool, + #[arg(default = true)] + editable: bool, + #[arg(default = None)] + show_last: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, + #[arg(default = "solid")] + linestyle: String, } impl Plot { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("series".to_string(), self.series.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("linewidth".to_string(), self.linewidth.clone()); - fields.insert("style".to_string(), self.style.clone()); - fields.insert("trackprice".to_string(), self.trackprice.clone()); - fields.insert("histbase".to_string(), self.histbase.clone()); - fields.insert("offset".to_string(), self.offset.clone()); - fields.insert("join".to_string(), self.join.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); - fields.insert("linestyle".to_string(), self.linestyle.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plot = PlotOutput { + series: self.series.clone(), + title: self.title.clone(), + color: self.color.clone(), + linewidth: self.linewidth, + style: self.style.clone(), + trackprice: self.trackprice, + histbase: self.histbase, + offset: self.offset, + join: self.join, + editable: self.editable, + show_last: self.show_last, + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + linestyle: self.linestyle.clone(), + }; - Ok(Value::Object { - type_name: "plot".to_string(), - fields: Rc::new(RefCell::new(fields)), - }) + ctx.output.plots.push(plot); + Ok(Value::Na) } } @@ -73,49 +75,51 @@ impl Plot { #[builtin(name = "plotarrow")] struct Plotarrow { series: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::Na)] - colorup: Value, - #[arg(default = Value::Na)] - colordown: Value, - #[arg(default = Value::Number(0.0))] - offset: Value, - #[arg(default = Value::Number(5.0))] - minheight: Value, - #[arg(default = Value::Number(100.0))] - maxheight: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, + #[arg(default = "")] + title: String, + #[arg(default = None)] + colorup: Option, + #[arg(default = None)] + colordown: Option, + #[arg(default = 0.0)] + offset: f64, + #[arg(default = 5.0)] + minheight: f64, + #[arg(default = 100.0)] + maxheight: f64, + #[arg(default = true)] + editable: bool, + #[arg(default = None)] + show_last: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, } impl Plotarrow { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("series".to_string(), self.series.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("colorup".to_string(), self.colorup.clone()); - fields.insert("colordown".to_string(), self.colordown.clone()); - fields.insert("offset".to_string(), self.offset.clone()); - fields.insert("minheight".to_string(), self.minheight.clone()); - fields.insert("maxheight".to_string(), self.maxheight.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plotarrow = PlotarrowOutput { + series: self.series.clone(), + title: self.title.clone(), + colorup: self.colorup.clone(), + colordown: self.colordown.clone(), + offset: self.offset, + minheight: self.minheight, + maxheight: self.maxheight, + editable: self.editable, + show_last: self.show_last, + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + }; + ctx.output.plotarrows.push(plotarrow); Ok(Value::Na) } } @@ -128,40 +132,42 @@ struct Plotbar { high: Value, low: Value, close: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, + #[arg(default = "")] + title: String, + #[arg(default = None)] + color: Option, + #[arg(default = true)] + editable: bool, + #[arg(default = None)] + show_last: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, } impl Plotbar { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("open".to_string(), self.open.clone()); - fields.insert("high".to_string(), self.high.clone()); - fields.insert("low".to_string(), self.low.clone()); - fields.insert("close".to_string(), self.close.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plotbar = PlotbarOutput { + open: self.open.clone(), + high: self.high.clone(), + low: self.low.clone(), + close: self.close.clone(), + title: self.title.clone(), + color: self.color.clone(), + editable: self.editable, + show_last: self.show_last, + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + }; + ctx.output.plotbars.push(plotbar); Ok(Value::Na) } } @@ -174,46 +180,48 @@ struct Plotcandle { high: Value, low: Value, close: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::Na)] - wickcolor: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::Na)] - bordercolor: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, + #[arg(default = "")] + title: String, + #[arg(default = None)] + color: Option, + #[arg(default = None)] + wickcolor: Option, + #[arg(default = true)] + editable: bool, + #[arg(default = None)] + show_last: Option, + #[arg(default = None)] + bordercolor: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, } impl Plotcandle { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("open".to_string(), self.open.clone()); - fields.insert("high".to_string(), self.high.clone()); - fields.insert("low".to_string(), self.low.clone()); - fields.insert("close".to_string(), self.close.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("wickcolor".to_string(), self.wickcolor.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("bordercolor".to_string(), self.bordercolor.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plotcandle = PlotcandleOutput { + open: self.open.clone(), + high: self.high.clone(), + low: self.low.clone(), + close: self.close.clone(), + title: self.title.clone(), + color: self.color.clone(), + wickcolor: self.wickcolor.clone(), + editable: self.editable, + show_last: self.show_last, + bordercolor: self.bordercolor.clone(), + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + }; + ctx.output.plotcandles.push(plotcandle); Ok(Value::Na) } } @@ -223,55 +231,57 @@ impl Plotcandle { #[builtin(name = "plotchar")] struct Plotchar { series: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::String("★".to_string()))] - char: Value, - #[arg(default = Value::String("bottom".to_string()))] - location: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::Number(0.0))] - offset: Value, - #[arg(default = Value::String(String::new()))] - text: Value, - #[arg(default = Value::Na)] - textcolor: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::String("auto".to_string()))] - size: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, + #[arg(default = "")] + title: String, + #[arg(default = "★")] + char: String, + #[arg(default = "bottom")] + location: String, + #[arg(default = None)] + color: Option, + #[arg(default = 0.0)] + offset: f64, + #[arg(default = "")] + text: String, + #[arg(default = None)] + textcolor: Option, + #[arg(default = true)] + editable: bool, + #[arg(default = "auto")] + size: String, + #[arg(default = None)] + show_last: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, } impl Plotchar { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("series".to_string(), self.series.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("char".to_string(), self.char.clone()); - fields.insert("location".to_string(), self.location.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("offset".to_string(), self.offset.clone()); - fields.insert("text".to_string(), self.text.clone()); - fields.insert("textcolor".to_string(), self.textcolor.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("size".to_string(), self.size.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plotchar = PlotcharOutput { + series: self.series.clone(), + title: self.title.clone(), + char: self.char.clone(), + location: self.location.clone(), + color: self.color.clone(), + offset: self.offset, + text: self.text.clone(), + textcolor: self.textcolor.clone(), + editable: self.editable, + size: self.size.clone(), + show_last: self.show_last, + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + }; + ctx.output.plotchars.push(plotchar); Ok(Value::Na) } } @@ -281,55 +291,57 @@ impl Plotchar { #[builtin(name = "plotshape")] struct Plotshape { series: Value, - #[arg(default = Value::String(String::new()))] - title: Value, - #[arg(default = Value::String("circle".to_string()))] - style: Value, - #[arg(default = Value::String("bottom".to_string()))] - location: Value, - #[arg(default = Value::Na)] - color: Value, - #[arg(default = Value::Number(0.0))] - offset: Value, - #[arg(default = Value::String(String::new()))] - text: Value, - #[arg(default = Value::Na)] - textcolor: Value, - #[arg(default = Value::Bool(true))] - editable: Value, - #[arg(default = Value::String("auto".to_string()))] - size: Value, - #[arg(default = Value::Na)] - show_last: Value, - #[arg(default = Value::String("all".to_string()))] - display: Value, - #[arg(default = Value::Na)] - format: Value, - #[arg(default = Value::Na)] - precision: Value, - #[arg(default = Value::Bool(false))] - force_overlay: Value, + #[arg(default = "")] + title: String, + #[arg(default = "circle")] + style: String, + #[arg(default = "bottom")] + location: String, + #[arg(default = None)] + color: Option, + #[arg(default = 0.0)] + offset: f64, + #[arg(default = "")] + text: String, + #[arg(default = None)] + textcolor: Option, + #[arg(default = true)] + editable: bool, + #[arg(default = "auto")] + size: String, + #[arg(default = None)] + show_last: Option, + #[arg(default = "all")] + display: String, + #[arg(default = None)] + format: Option, + #[arg(default = None)] + precision: Option, + #[arg(default = false)] + force_overlay: bool, } impl Plotshape { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - let mut fields = HashMap::new(); - fields.insert("series".to_string(), self.series.clone()); - fields.insert("title".to_string(), self.title.clone()); - fields.insert("style".to_string(), self.style.clone()); - fields.insert("location".to_string(), self.location.clone()); - fields.insert("color".to_string(), self.color.clone()); - fields.insert("offset".to_string(), self.offset.clone()); - fields.insert("text".to_string(), self.text.clone()); - fields.insert("textcolor".to_string(), self.textcolor.clone()); - fields.insert("editable".to_string(), self.editable.clone()); - fields.insert("size".to_string(), self.size.clone()); - fields.insert("show_last".to_string(), self.show_last.clone()); - fields.insert("display".to_string(), self.display.clone()); - fields.insert("format".to_string(), self.format.clone()); - fields.insert("precision".to_string(), self.precision.clone()); - fields.insert("force_overlay".to_string(), self.force_overlay.clone()); + fn execute(&self, ctx: &mut Interpreter) -> Result { + let plotshape = PlotshapeOutput { + series: self.series.clone(), + title: self.title.clone(), + style: self.style.clone(), + location: self.location.clone(), + color: self.color.clone(), + offset: self.offset, + text: self.text.clone(), + textcolor: self.textcolor.clone(), + editable: self.editable, + size: self.size.clone(), + show_last: self.show_last, + display: self.display.clone(), + format: self.format.clone(), + precision: self.precision, + force_overlay: self.force_overlay, + }; + ctx.output.plotshapes.push(plotshape); Ok(Value::Na) } } diff --git a/crates/pine-builtins/src/str/mod.rs b/crates/pine-builtins/src/str/mod.rs index a48a965..1fe1fae 100644 --- a/crates/pine-builtins/src/str/mod.rs +++ b/crates/pine-builtins/src/str/mod.rs @@ -209,7 +209,9 @@ impl StrToString { Value::Number(n) => n.to_string(), Value::Bool(b) => if *b { "true" } else { "false" }.to_string(), Value::Na => "NaN".to_string(), - Value::Color { r, g, b, t } => format!("rgba({}, {}, {}, {})", r, g, b, t), + Value::Color(color) => { + format!("rgba({}, {}, {}, {})", color.r, color.g, color.b, color.t) + } Value::Array(_) => "[Array]".to_string(), Value::Series(series) => format!("[Series:{}]", series.id), Value::Object { type_name, .. } => format!("[Object:{}]", type_name), diff --git a/crates/pine-interpreter/src/lib.rs b/crates/pine-interpreter/src/lib.rs index 9f4c81b..4c19958 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -78,6 +78,175 @@ pub struct Series { pub current: Box, } +/// Represents a color with RGBA components +#[derive(Clone, Debug, PartialEq)] +pub struct Color { + pub r: u8, // Red component (0-255) + pub g: u8, // Green component (0-255) + pub b: u8, // Blue component (0-255) + pub t: u8, // Transparency (0-100) +} + +impl Color { + pub fn new(r: u8, g: u8, b: u8, t: u8) -> Self { + Color { r, g, b, t } + } +} + +/// Represents a label drawable object +#[derive(Clone, Debug)] +pub struct Label { + pub x: Value, + pub y: Value, + pub text: String, + pub xloc: String, + pub yloc: String, + pub color: Option, + pub style: String, + pub textcolor: Option, + pub size: String, + pub textalign: String, + pub tooltip: Option, + pub text_font_family: String, +} + +/// Represents a box drawable object +#[derive(Clone, Debug)] +pub struct PineBox { + pub left: Value, + pub top: Value, + pub right: Value, + pub bottom: Value, + pub border_color: Option, + pub border_width: f64, + pub border_style: String, + pub extend: String, + pub xloc: String, + pub bgcolor: Option, + pub text: String, + pub text_size: f64, + pub text_color: Option, + pub text_halign: String, + pub text_valign: String, + pub text_wrap: String, + pub text_font_family: String, +} + +/// Represents a plot output +#[derive(Clone, Debug)] +pub struct Plot { + pub series: Value, + pub title: String, + pub color: Option, + pub linewidth: f64, + pub style: String, + pub trackprice: bool, + pub histbase: f64, + pub offset: f64, + pub join: bool, + pub editable: bool, + pub show_last: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, + pub linestyle: String, +} + +/// Represents a plotarrow output +#[derive(Clone, Debug)] +pub struct Plotarrow { + pub series: Value, + pub title: String, + pub colorup: Option, + pub colordown: Option, + pub offset: f64, + pub minheight: f64, + pub maxheight: f64, + pub editable: bool, + pub show_last: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, +} + +/// Represents a plotbar output +#[derive(Clone, Debug)] +pub struct Plotbar { + pub open: Value, + pub high: Value, + pub low: Value, + pub close: Value, + pub title: String, + pub color: Option, + pub editable: bool, + pub show_last: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, +} + +/// Represents a plotcandle output +#[derive(Clone, Debug)] +pub struct Plotcandle { + pub open: Value, + pub high: Value, + pub low: Value, + pub close: Value, + pub title: String, + pub color: Option, + pub wickcolor: Option, + pub editable: bool, + pub show_last: Option, + pub bordercolor: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, +} + +/// Represents a plotchar output +#[derive(Clone, Debug)] +pub struct Plotchar { + pub series: Value, + pub title: String, + pub char: String, + pub location: String, + pub color: Option, + pub offset: f64, + pub text: String, + pub textcolor: Option, + pub editable: bool, + pub size: String, + pub show_last: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, +} + +/// Represents a plotshape output +#[derive(Clone, Debug)] +pub struct Plotshape { + pub series: Value, + pub title: String, + pub style: String, + pub location: String, + pub color: Option, + pub offset: f64, + pub text: String, + pub textcolor: Option, + pub editable: bool, + pub size: String, + pub show_last: Option, + pub display: String, + pub format: Option, + pub precision: Option, + pub force_overlay: bool, +} + /// Value types in the interpreter #[derive(Clone)] pub enum Value { @@ -105,12 +274,7 @@ pub enum Value { field_name: String, // The specific field/member name (e.g., "buy") title: String, // The title of this enum member }, // Enum member value - Color { - r: u8, // Red component (0-255) - g: u8, // Green component (0-255) - b: u8, // Blue component (0-255) - t: u8, // Transparency (0-100) - }, // Color value + Color(Color), // Color value Matrix { element_type: String, // Type of elements: "int", "float", "string", "bool" data: Rc>>>, // 2D matrix - mutable shared reference to rows of columns @@ -119,7 +283,7 @@ pub enum Value { impl Value { pub fn new_color(r: u8, g: u8, b: u8, t: u8) -> Value { - Value::Color { r, g, b, t } + Value::Color(Color::new(r, g, b, t)) } } @@ -142,7 +306,11 @@ impl std::fmt::Debug for Value { field_name, .. } => write!(f, "Enum({}::{})", enum_name, field_name), - Value::Color { r, g, b, t } => write!(f, "Color(rgba({}, {}, {}, {}))", r, g, b, t), + Value::Color(color) => write!( + f, + "Color(rgba({}, {}, {}, {}))", + color.r, color.g, color.b, color.t + ), Value::Matrix { element_type, data } => { write!(f, "Matrix<{}>({:?})", element_type, data) } @@ -181,20 +349,7 @@ impl PartialEq for Value { }, ) => a_enum == b_enum && a_field == b_field, // Colors compare by all components - ( - Value::Color { - r: r1, - g: g1, - b: b1, - t: t1, - }, - Value::Color { - r: r2, - g: g2, - b: b2, - t: t2, - }, - ) => r1 == r2 && g1 == g2 && b1 == b2 && t1 == t2, + (Value::Color(c1), Value::Color(c2)) => c1 == c2, // Matrices compare by reference (Rc pointer equality) (Value::Matrix { data: a, .. }, Value::Matrix { data: b, .. }) => Rc::ptr_eq(a, b), _ => false, @@ -278,9 +433,9 @@ impl Value { } } - pub fn as_color(&self) -> Result<(u8, u8, u8, u8), RuntimeError> { + pub fn as_color(&self) -> Result { match self { - Value::Color { r, g, b, t } => Ok((*r, *g, *b, *t)), + Value::Color(color) => Ok(color.clone()), _ => Err(RuntimeError::TypeError(format!( "Expected color, got {:?}", self @@ -297,6 +452,83 @@ struct MethodDef { body: Vec, } +#[derive(Default, Clone)] +pub struct PineOutput { + /// Label storage for drawable objects + labels: HashMap, + /// Next label ID + next_label_id: usize, + /// Box storage for drawable objects + boxes: HashMap, + /// Next box ID + next_box_id: usize, + /// Plot outputs + pub plots: Vec, + /// Plotarrow outputs + pub plotarrows: Vec, + /// Plotbar outputs + pub plotbars: Vec, + /// Plotcandle outputs + pub plotcandles: Vec, + /// Plotchar outputs + pub plotchars: Vec, + /// Plotshape outputs + pub plotshapes: Vec, +} + +impl PineOutput { + /// Clear all output data for a new iteration + pub fn clear(&mut self) { + self.labels.clear(); + self.boxes.clear(); + self.plots.clear(); + self.plotarrows.clear(); + self.plotbars.clear(); + self.plotcandles.clear(); + self.plotchars.clear(); + self.plotshapes.clear(); + // Reset ID counters + self.next_label_id = 0; + self.next_box_id = 0; + } + + /// Add a label and return its ID + pub fn add_label(&mut self, label: Label) -> usize { + let id = self.next_label_id; + self.next_label_id += 1; + self.labels.insert(id, label); + id + } + + /// Get a mutable reference to a label by ID + pub fn get_label_mut(&mut self, id: usize) -> Option<&mut Label> { + self.labels.get_mut(&id) + } + + /// Delete a label by ID + pub fn delete_label(&mut self, id: usize) { + self.labels.remove(&id); + } + + /// Add a box and return its ID + pub fn add_box(&mut self, box_obj: PineBox) -> usize { + let id = self.next_box_id; + self.next_box_id += 1; + self.boxes.insert(id, box_obj); + id + } + + /// Get a mutable reference to a box by ID + pub fn get_box_mut(&mut self, id: usize) -> Option<&mut PineBox> { + self.boxes.get_mut(&id) + } + + /// Delete a box by ID + pub fn delete_box(&mut self, id: usize) { + self.boxes.remove(&id); + } +} + /// The interpreter executes a program with a given bar pub struct Interpreter { /// Local variables in the current scope @@ -311,6 +543,8 @@ pub struct Interpreter { pub historical_provider: Option>, /// Exported items from this module (for library mode) exports: HashMap, + /// Label storage for drawable objects + pub output: PineOutput, } impl Interpreter { @@ -322,6 +556,7 @@ impl Interpreter { library_loader: None, historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -334,6 +569,7 @@ impl Interpreter { library_loader: None, historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -346,6 +582,7 @@ impl Interpreter { library_loader: Some(loader), historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -361,6 +598,7 @@ impl Interpreter { library_loader: Some(loader), historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -380,11 +618,16 @@ impl Interpreter { } /// Execute a program with a single bar - pub fn execute(&mut self, program: &Program) -> Result<(), RuntimeError> { + pub fn execute(&mut self, program: &Program) -> Result { + // Clear output from previous iteration + self.output.clear(); + for stmt in &program.statements { self.execute_stmt(stmt)?; } - Ok(()) + + // Return a clone of the output + Ok(self.output.clone()) } /// Get a variable value diff --git a/crates/pine/src/lib.rs b/crates/pine/src/lib.rs index 0da8660..af95f62 100644 --- a/crates/pine/src/lib.rs +++ b/crates/pine/src/lib.rs @@ -95,7 +95,7 @@ impl Script { /// /// This maintains interpreter state across multiple calls, /// allowing variables to persist between bars. - pub fn execute(&mut self, bar: &Bar) -> Result<(), Error> { + pub fn execute(&mut self, bar: &Bar) -> Result { // Load bar data as Series variables so TA functions can access historical data use interpreter::{Series, Value}; @@ -135,8 +135,8 @@ impl Script { }), ); - self.interpreter.execute(&self.program)?; - Ok(()) + let output = self.interpreter.execute(&self.program)?; + Ok(output) } /// Execute the script with multiple bars sequentially diff --git a/tests/lib.rs b/tests/lib.rs index 161fd32..71953f1 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -194,9 +194,9 @@ mod tests { script.set_library_loader(Box::new(library_loader)); // Execute with the last bar - script.execute(&bars[bars.len() - 1])?; + let _pine_output = script.execute(&bars[bars.len() - 1])?; - // Clone the output before returning + // Clone the log output before returning let result = output.borrow().clone(); Ok(result) } diff --git a/tests/testdata/arguments/mixed_args.pine b/tests/testdata/arguments/mixed_args.pine index 7ed7a02..dc54c65 100644 --- a/tests/testdata/arguments/mixed_args.pine +++ b/tests/testdata/arguments/mixed_args.pine @@ -1,36 +1,21 @@ // Test mixing positional and named arguments // Pine Script allows positional arguments followed by named arguments, // but once you use a named argument, all following must be named +// Note: plot() now returns void and stores output internally // Positional for required, named for optional -p1 = plot(100.0, title="Mixed", linewidth=2.0) -log.info(p1.series) -log.info(p1.title) -log.info(p1.linewidth) +plot(100.0, title="Mixed", linewidth=2.0) +log.info("Plot 1 created") // First two positional, rest named -p2 = plot(200.0, "Second Plot", linewidth=3.0, style="histogram") -log.info(p2.series) -log.info(p2.title) -log.info(p2.linewidth) -log.info(p2.style) +plot(200.0, "Second Plot", linewidth=3.0, style="histogram") +log.info("Plot 2 created") // Multiple positional, then named -p3 = plot(300.0, "Third", na, trackprice=true, offset=10.0) -log.info(p3.series) -log.info(p3.title) -log.info(p3.trackprice) -log.info(p3.offset) +plot(300.0, "Third", na, trackprice=true, offset=10.0) +log.info("Plot 3 created") // Expected output: -// 100 -// Mixed -// 2 -// 200 -// Second Plot -// 3 -// histogram -// 300 -// Third -// true -// 10 +// Plot 1 created +// Plot 2 created +// Plot 3 created diff --git a/tests/testdata/arguments/named_args.pine b/tests/testdata/arguments/named_args.pine index 3d6a315..effcee1 100644 --- a/tests/testdata/arguments/named_args.pine +++ b/tests/testdata/arguments/named_args.pine @@ -1,35 +1,24 @@ // Test named arguments for plot function +// Note: plot() now returns void and stores output internally // All named arguments -p1 = plot(series=100.0, title="Named Plot", linewidth=2.0) -log.info(p1.series) -log.info(p1.title) -log.info(p1.linewidth) +plot(series=100.0, title="Named Plot", linewidth=2.0) +log.info("Plot 1 created") // Named arguments in different order -p2 = plot(linewidth=5.0, series=200.0, title="Reordered") -log.info(p2.series) -log.info(p2.title) -log.info(p2.linewidth) +plot(linewidth=5.0, series=200.0, title="Reordered") +log.info("Plot 2 created") // Mix of required and optional named arguments -p3 = plot(series=300.0, trackprice=true, style="histogram") -log.info(p3.series) -log.info(p3.trackprice) -log.info(p3.style) +plot(series=300.0, trackprice=true, style="histogram") +log.info("Plot 3 created") // Single named argument (required one) -p4 = plot(series=400.0) -log.info(p4.series) +plot(series=400.0) +log.info("Plot 4 created") // Expected output: -// 100 -// Named Plot -// 2 -// 200 -// Reordered -// 5 -// 300 -// true -// histogram -// 400 +// Plot 1 created +// Plot 2 created +// Plot 3 created +// Plot 4 created diff --git a/tests/testdata/arguments/positional_args.pine b/tests/testdata/arguments/positional_args.pine index c586ecd..e45618b 100644 --- a/tests/testdata/arguments/positional_args.pine +++ b/tests/testdata/arguments/positional_args.pine @@ -1,34 +1,25 @@ // Test all positional arguments for plot function // plot(series, title, color, linewidth, style, trackprice) +// Note: plot() now returns void and stores output internally // Just series (required parameter) -p1 = plot(100.0) -log.info(p1.series) +plot(100.0) +log.info("Plot 1 created") // Series and title -p2 = plot(200.0, "My Plot") -log.info(p2.series) -log.info(p2.title) +plot(200.0, "My Plot") +log.info("Plot 2 created") // Series, title, and color (na) -p3 = plot(300.0, "Colored Plot", na) -log.info(p3.series) -log.info(p3.title) -log.info(p3.color) +plot(300.0, "Colored Plot", na) +log.info("Plot 3 created") // Series, title, color, linewidth -p4 = plot(400.0, "Wide Plot", na, 3.0) -log.info(p4.series) -log.info(p4.title) -log.info(p4.linewidth) +plot(400.0, "Wide Plot", na, 3.0) +log.info("Plot 4 created") // Expected output: -// 100 -// 200 -// My Plot -// 300 -// Colored Plot -// na -// 400 -// Wide Plot -// 3 +// Plot 1 created +// Plot 2 created +// Plot 3 created +// Plot 4 created diff --git a/tests/testdata/box/delete_verification.pine b/tests/testdata/box/delete_verification.pine new file mode 100644 index 0000000..1c697de --- /dev/null +++ b/tests/testdata/box/delete_verification.pine @@ -0,0 +1,15 @@ +// Test that box.delete() actually removes the box +// When we try to access a deleted box, we should get an error + +b = box.new(5, 150.0, 15, 100.0, text="Test Box") +log.info(box.get_left(b)) + +// Delete the box +box.delete(b) + +// Try to access the deleted box - this should error +// Uncomment to test error handling: +// box.get_left(b) + +// Expected output: +// 5 diff --git a/tests/testdata/errors/deleted_box_access.pine b/tests/testdata/errors/deleted_box_access.pine new file mode 100644 index 0000000..a3346a3 --- /dev/null +++ b/tests/testdata/errors/deleted_box_access.pine @@ -0,0 +1,9 @@ +// Test accessing a deleted box - should fail + +b = box.new(5, 150.0, 15, 100.0) +box.delete(b) + +// Accessing deleted box should error +box.get_left(b) + +// Expected error: Box with id 0 not found diff --git a/tests/testdata/errors/deleted_label_access.pine b/tests/testdata/errors/deleted_label_access.pine new file mode 100644 index 0000000..127963b --- /dev/null +++ b/tests/testdata/errors/deleted_label_access.pine @@ -0,0 +1,9 @@ +// Test accessing a deleted label - should fail + +lbl = label.new(5, 150.0, "Test") +label.delete(lbl) + +// Accessing deleted label should error +label.get_text(lbl) + +// Expected error: Label with id 0 not found diff --git a/tests/testdata/labels/delete_verification.pine b/tests/testdata/labels/delete_verification.pine new file mode 100644 index 0000000..adb0b18 --- /dev/null +++ b/tests/testdata/labels/delete_verification.pine @@ -0,0 +1,15 @@ +// Test that label.delete() actually removes the label +// When we try to access a deleted label, we should get an error + +lbl = label.new(5, 150.0, "Test Label") +log.info(label.get_text(lbl)) + +// Delete the label +label.delete(lbl) + +// Try to access the deleted label - this should error +// Uncomment to test error handling: +// label.get_text(lbl) + +// Expected output: +// Test Label