From b43e2dbfc50c1342f3022c9d5358acf64c982111 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 12 Feb 2026 21:38:54 +0800 Subject: [PATCH 1/7] Remove Value types from builtin funcs --- crates/pine-builtin-macro/src/lib.rs | 59 +++- crates/pine-builtins/src/color/mod.rs | 38 +-- crates/pine-builtins/src/label/mod.rs | 108 +++--- crates/pine-builtins/src/plot/mod.rs | 458 +++++++++++++------------- crates/pine-builtins/src/str/mod.rs | 2 +- crates/pine-interpreter/src/lib.rs | 46 +-- 6 files changed, 396 insertions(+), 315 deletions(-) diff --git a/crates/pine-builtin-macro/src/lib.rs b/crates/pine-builtin-macro/src/lib.rs index 2b1961c..91e449b 100644 --- a/crates/pine-builtin-macro/src/lib.rs +++ b/crates/pine-builtin-macro/src/lib.rs @@ -367,12 +367,69 @@ 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 if type_str.contains("Value") { + quote! { + if matches!(arg_value, Value::Na) { + None + } else { + Some(arg_value) + } + } + } 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/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..d20d4ee 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, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -10,26 +10,26 @@ 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 { @@ -38,18 +38,24 @@ impl LabelNew { 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".to_string(), Value::String(self.text.clone())); + fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); + fields.insert("yloc".to_string(), Value::String(self.yloc.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("style".to_string(), Value::String(self.style.clone())); + fields.insert("textcolor".to_string(), match &self.textcolor { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("size".to_string(), Value::String(self.size.clone())); + fields.insert("textalign".to_string(), Value::String(self.textalign.clone())); + fields.insert("tooltip".to_string(), self.tooltip.clone().map(Value::String).unwrap_or(Value::Na)); fields.insert( "text_font_family".to_string(), - self.text_font_family.clone(), + Value::String(self.text_font_family.clone()), ); Ok(Value::Object { @@ -125,7 +131,7 @@ impl LabelSetXy { struct LabelSetXloc { id: Value, x: Value, - xloc: Value, + xloc: String, } impl LabelSetXloc { @@ -133,7 +139,7 @@ impl LabelSetXloc { 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()); + fields_mut.insert("xloc".to_string(), Value::String(self.xloc.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -146,7 +152,7 @@ impl LabelSetXloc { #[builtin(name = "label.set_yloc")] struct LabelSetYloc { id: Value, - yloc: Value, + yloc: String, } impl LabelSetYloc { @@ -154,7 +160,7 @@ impl LabelSetYloc { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("yloc".to_string(), self.yloc.clone()); + .insert("yloc".to_string(), Value::String(self.yloc.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -167,7 +173,7 @@ impl LabelSetYloc { #[builtin(name = "label.set_color")] struct LabelSetColor { id: Value, - color: Value, + color: Color, } impl LabelSetColor { @@ -175,7 +181,7 @@ impl LabelSetColor { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("color".to_string(), self.color.clone()); + .insert("color".to_string(), Value::Color(self.color.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -188,7 +194,7 @@ impl LabelSetColor { #[builtin(name = "label.set_style")] struct LabelSetStyle { id: Value, - style: Value, + style: String, } impl LabelSetStyle { @@ -196,7 +202,7 @@ impl LabelSetStyle { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("style".to_string(), self.style.clone()); + .insert("style".to_string(), Value::String(self.style.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -209,7 +215,7 @@ impl LabelSetStyle { #[builtin(name = "label.set_text")] struct LabelSetText { id: Value, - text: Value, + text: String, } impl LabelSetText { @@ -217,7 +223,7 @@ impl LabelSetText { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text".to_string(), self.text.clone()); + .insert("text".to_string(), Value::String(self.text.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -230,7 +236,7 @@ impl LabelSetText { #[builtin(name = "label.set_textcolor")] struct LabelSetTextcolor { id: Value, - textcolor: Value, + textcolor: Color, } impl LabelSetTextcolor { @@ -238,7 +244,7 @@ impl LabelSetTextcolor { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("textcolor".to_string(), self.textcolor.clone()); + .insert("textcolor".to_string(), Value::Color(self.textcolor.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -251,7 +257,7 @@ impl LabelSetTextcolor { #[builtin(name = "label.set_size")] struct LabelSetSize { id: Value, - size: Value, + size: String, } impl LabelSetSize { @@ -259,7 +265,7 @@ impl LabelSetSize { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("size".to_string(), self.size.clone()); + .insert("size".to_string(), Value::String(self.size.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -272,7 +278,7 @@ impl LabelSetSize { #[builtin(name = "label.set_textalign")] struct LabelSetTextalign { id: Value, - textalign: Value, + textalign: String, } impl LabelSetTextalign { @@ -280,7 +286,7 @@ impl LabelSetTextalign { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("textalign".to_string(), self.textalign.clone()); + .insert("textalign".to_string(), Value::String(self.textalign.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -293,7 +299,7 @@ impl LabelSetTextalign { #[builtin(name = "label.set_tooltip")] struct LabelSetTooltip { id: Value, - tooltip: Value, + tooltip: String, } impl LabelSetTooltip { @@ -301,7 +307,7 @@ impl LabelSetTooltip { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("tooltip".to_string(), self.tooltip.clone()); + .insert("tooltip".to_string(), Value::String(self.tooltip.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected label object".to_string())) @@ -314,7 +320,7 @@ impl LabelSetTooltip { #[builtin(name = "label.set_text_font_family")] struct LabelSetTextFontFamily { id: Value, - text_font_family: Value, + text_font_family: String, } impl LabelSetTextFontFamily { @@ -322,7 +328,7 @@ impl LabelSetTextFontFamily { if let Value::Object { fields, .. } = &self.id { fields.borrow_mut().insert( "text_font_family".to_string(), - self.text_font_family.clone(), + Value::String(self.text_font_family.clone()), ); Ok(Value::Na) } else { diff --git a/crates/pine-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index 803dfd0..9c6ca0b 100644 --- a/crates/pine-builtins/src/plot/mod.rs +++ b/crates/pine-builtins/src/plot/mod.rs @@ -1,5 +1,5 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Interpreter, RuntimeError, Value}; +use pine_interpreter::{Color, Interpreter, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -9,57 +9,63 @@ 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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("linewidth".to_string(), Value::Number(self.linewidth)); + fields.insert("style".to_string(), Value::String(self.style.clone())); + fields.insert("trackprice".to_string(), Value::Bool(self.trackprice)); + fields.insert("histbase".to_string(), Value::Number(self.histbase)); + fields.insert("offset".to_string(), Value::Number(self.offset)); + fields.insert("join".to_string(), Value::Bool(self.join)); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + fields.insert( + "linestyle".to_string(), + Value::String(self.linestyle.clone()), + ); Ok(Value::Object { type_name: "plot".to_string(), @@ -73,48 +79,48 @@ 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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("colorup".to_string(), self.colorup.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("colordown".to_string(), self.colordown.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("offset".to_string(), Value::Number(self.offset)); + fields.insert("minheight".to_string(), Value::Number(self.minheight)); + fields.insert("maxheight".to_string(), Value::Number(self.maxheight)); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); Ok(Value::Na) } @@ -128,22 +134,22 @@ 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 { @@ -153,14 +159,17 @@ impl Plotbar { 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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); Ok(Value::Na) } @@ -174,26 +183,26 @@ 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 { @@ -203,16 +212,19 @@ impl Plotcandle { 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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("wickcolor".to_string(), self.wickcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("bordercolor".to_string(), self.bordercolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); Ok(Value::Na) } @@ -223,54 +235,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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("char".to_string(), Value::String(self.char.clone())); + fields.insert("location".to_string(), Value::String(self.location.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("offset".to_string(), Value::Number(self.offset)); + fields.insert("text".to_string(), Value::String(self.text.clone())); + fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("size".to_string(), Value::String(self.size.clone())); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); Ok(Value::Na) } @@ -281,54 +296,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()); + fields.insert("title".to_string(), Value::String(self.title.clone())); + fields.insert("style".to_string(), Value::String(self.style.clone())); + fields.insert("location".to_string(), Value::String(self.location.clone())); + fields.insert("color".to_string(), match &self.color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("offset".to_string(), Value::Number(self.offset)); + fields.insert("text".to_string(), Value::String(self.text.clone())); + fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); + fields.insert("editable".to_string(), Value::Bool(self.editable)); + fields.insert("size".to_string(), Value::String(self.size.clone())); + fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); + fields.insert("display".to_string(), Value::String(self.display.clone())); + fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); + fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); + fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); Ok(Value::Na) } diff --git a/crates/pine-builtins/src/str/mod.rs b/crates/pine-builtins/src/str/mod.rs index a48a965..40b5c31 100644 --- a/crates/pine-builtins/src/str/mod.rs +++ b/crates/pine-builtins/src/str/mod.rs @@ -209,7 +209,7 @@ 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..88ef8e7 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -78,6 +78,21 @@ 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 } + } +} + /// Value types in the interpreter #[derive(Clone)] pub enum Value { @@ -105,12 +120,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 +129,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 +152,7 @@ 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) } @@ -182,19 +192,9 @@ 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 +278,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 From 3f251e4af303c409b03fed7a9b788833d33ad0fe Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 07:54:51 +0800 Subject: [PATCH 2/7] Remove unnecessary Value refs in builtin functions --- crates/pine-builtins/src/box/mod.rs | 144 +++++++++++++------------ crates/pine-builtins/src/matrix/mod.rs | 79 ++++---------- 2 files changed, 93 insertions(+), 130 deletions(-) diff --git a/crates/pine-builtins/src/box/mod.rs b/crates/pine-builtins/src/box/mod.rs index 54f3b9c..39a4eb5 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, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -12,32 +12,32 @@ 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 { @@ -47,22 +47,28 @@ impl BoxNew { 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(), - ); + fields.insert("border_color".to_string(), match &self.border_color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("border_width".to_string(), Value::Number(self.border_width)); + fields.insert("border_style".to_string(), Value::String(self.border_style.clone())); + fields.insert("extend".to_string(), Value::String(self.extend.clone())); + fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); + fields.insert("bgcolor".to_string(), match &self.bgcolor { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("text".to_string(), Value::String(self.text.clone())); + fields.insert("text_size".to_string(), Value::Number(self.text_size)); + fields.insert("text_color".to_string(), match &self.text_color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + }); + fields.insert("text_halign".to_string(), Value::String(self.text_halign.clone())); + fields.insert("text_valign".to_string(), Value::String(self.text_valign.clone())); + fields.insert("text_wrap".to_string(), Value::String(self.text_wrap.clone())); + fields.insert("text_font_family".to_string(), Value::String(self.text_font_family.clone())); Ok(Value::Object { type_name: "box".to_string(), @@ -204,7 +210,7 @@ impl BoxSetRightbottom { #[builtin(name = "box.set_border_color")] struct BoxSetBorderColor { id: Value, - color: Value, + color: Color, } impl BoxSetBorderColor { @@ -212,7 +218,7 @@ impl BoxSetBorderColor { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("border_color".to_string(), self.color.clone()); + .insert("border_color".to_string(), Value::Color(self.color.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -225,7 +231,7 @@ impl BoxSetBorderColor { #[builtin(name = "box.set_border_width")] struct BoxSetBorderWidth { id: Value, - width: Value, + width: f64, } impl BoxSetBorderWidth { @@ -233,7 +239,7 @@ impl BoxSetBorderWidth { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("border_width".to_string(), self.width.clone()); + .insert("border_width".to_string(), Value::Number(self.width)); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -246,7 +252,7 @@ impl BoxSetBorderWidth { #[builtin(name = "box.set_border_style")] struct BoxSetBorderStyle { id: Value, - style: Value, + style: String, } impl BoxSetBorderStyle { @@ -254,7 +260,7 @@ impl BoxSetBorderStyle { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("border_style".to_string(), self.style.clone()); + .insert("border_style".to_string(), Value::String(self.style.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -267,7 +273,7 @@ impl BoxSetBorderStyle { #[builtin(name = "box.set_extend")] struct BoxSetExtend { id: Value, - extend: Value, + extend: String, } impl BoxSetExtend { @@ -275,7 +281,7 @@ impl BoxSetExtend { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("extend".to_string(), self.extend.clone()); + .insert("extend".to_string(), Value::String(self.extend.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -288,7 +294,7 @@ impl BoxSetExtend { #[builtin(name = "box.set_bgcolor")] struct BoxSetBgcolor { id: Value, - color: Value, + color: Color, } impl BoxSetBgcolor { @@ -296,7 +302,7 @@ impl BoxSetBgcolor { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("bgcolor".to_string(), self.color.clone()); + .insert("bgcolor".to_string(), Value::Color(self.color.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -309,7 +315,7 @@ impl BoxSetBgcolor { #[builtin(name = "box.set_text")] struct BoxSetText { id: Value, - text: Value, + text: String, } impl BoxSetText { @@ -317,7 +323,7 @@ impl BoxSetText { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text".to_string(), self.text.clone()); + .insert("text".to_string(), Value::String(self.text.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -330,7 +336,7 @@ impl BoxSetText { #[builtin(name = "box.set_text_color")] struct BoxSetTextColor { id: Value, - color: Value, + color: Color, } impl BoxSetTextColor { @@ -338,7 +344,7 @@ impl BoxSetTextColor { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_color".to_string(), self.color.clone()); + .insert("text_color".to_string(), Value::Color(self.color.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -351,7 +357,7 @@ impl BoxSetTextColor { #[builtin(name = "box.set_text_size")] struct BoxSetTextSize { id: Value, - size: Value, + size: f64, } impl BoxSetTextSize { @@ -359,7 +365,7 @@ impl BoxSetTextSize { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_size".to_string(), self.size.clone()); + .insert("text_size".to_string(), Value::Number(self.size)); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -372,7 +378,7 @@ impl BoxSetTextSize { #[builtin(name = "box.set_text_halign")] struct BoxSetTextHalign { id: Value, - halign: Value, + halign: String, } impl BoxSetTextHalign { @@ -380,7 +386,7 @@ impl BoxSetTextHalign { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_halign".to_string(), self.halign.clone()); + .insert("text_halign".to_string(), Value::String(self.halign.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -393,7 +399,7 @@ impl BoxSetTextHalign { #[builtin(name = "box.set_text_valign")] struct BoxSetTextValign { id: Value, - valign: Value, + valign: String, } impl BoxSetTextValign { @@ -401,7 +407,7 @@ impl BoxSetTextValign { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_valign".to_string(), self.valign.clone()); + .insert("text_valign".to_string(), Value::String(self.valign.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -414,7 +420,7 @@ impl BoxSetTextValign { #[builtin(name = "box.set_text_wrap")] struct BoxSetTextWrap { id: Value, - wrap: Value, + wrap: String, } impl BoxSetTextWrap { @@ -422,7 +428,7 @@ impl BoxSetTextWrap { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_wrap".to_string(), self.wrap.clone()); + .insert("text_wrap".to_string(), Value::String(self.wrap.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -435,7 +441,7 @@ impl BoxSetTextWrap { #[builtin(name = "box.set_text_font_family")] struct BoxSetTextFontFamily { id: Value, - font_family: Value, + font_family: String, } impl BoxSetTextFontFamily { @@ -443,7 +449,7 @@ impl BoxSetTextFontFamily { if let Value::Object { fields, .. } = &self.id { fields .borrow_mut() - .insert("text_font_family".to_string(), self.font_family.clone()); + .insert("text_font_family".to_string(), Value::String(self.font_family.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) @@ -458,7 +464,7 @@ struct BoxSetXloc { id: Value, left: Value, right: Value, - xloc: Value, + xloc: String, } impl BoxSetXloc { @@ -467,7 +473,7 @@ impl BoxSetXloc { 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()); + fields_mut.insert("xloc".to_string(), Value::String(self.xloc.clone())); Ok(Value::Na) } else { Err(RuntimeError::TypeError("Expected box object".to_string())) 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(); From 51b156807907ab233d5862d61158d3c3fe2cea13 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 08:04:51 +0800 Subject: [PATCH 3/7] color --- crates/pine-builtins/src/box/mod.rs | 22 +++++++++--------- crates/pine-builtins/src/label/mod.rs | 17 +++++++------- crates/pine-builtins/src/plot/mod.rs | 32 ++++++++++----------------- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/crates/pine-builtins/src/box/mod.rs b/crates/pine-builtins/src/box/mod.rs index 39a4eb5..f712ed5 100644 --- a/crates/pine-builtins/src/box/mod.rs +++ b/crates/pine-builtins/src/box/mod.rs @@ -4,6 +4,13 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +fn color_to_value(color: &Option) -> Value { + match color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + } +} + /// box.new() - Creates a new box object #[derive(BuiltinFunction)] #[builtin(name = "box.new")] @@ -47,24 +54,15 @@ impl BoxNew { 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(), match &self.border_color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("border_color".to_string(), color_to_value(&self.border_color)); fields.insert("border_width".to_string(), Value::Number(self.border_width)); fields.insert("border_style".to_string(), Value::String(self.border_style.clone())); fields.insert("extend".to_string(), Value::String(self.extend.clone())); fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); - fields.insert("bgcolor".to_string(), match &self.bgcolor { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("bgcolor".to_string(), color_to_value(&self.bgcolor)); fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("text_size".to_string(), Value::Number(self.text_size)); - fields.insert("text_color".to_string(), match &self.text_color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("text_color".to_string(), color_to_value(&self.text_color)); fields.insert("text_halign".to_string(), Value::String(self.text_halign.clone())); fields.insert("text_valign".to_string(), Value::String(self.text_valign.clone())); fields.insert("text_wrap".to_string(), Value::String(self.text_wrap.clone())); diff --git a/crates/pine-builtins/src/label/mod.rs b/crates/pine-builtins/src/label/mod.rs index d20d4ee..b5574bd 100644 --- a/crates/pine-builtins/src/label/mod.rs +++ b/crates/pine-builtins/src/label/mod.rs @@ -4,6 +4,13 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +fn color_to_value(color: &Option) -> Value { + match color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + } +} + /// label.new() - Creates a new label object #[derive(BuiltinFunction)] #[builtin(name = "label.new")] @@ -41,15 +48,9 @@ impl LabelNew { fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); fields.insert("yloc".to_string(), Value::String(self.yloc.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("style".to_string(), Value::String(self.style.clone())); - fields.insert("textcolor".to_string(), match &self.textcolor { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("textcolor".to_string(), color_to_value(&self.textcolor)); fields.insert("size".to_string(), Value::String(self.size.clone())); fields.insert("textalign".to_string(), Value::String(self.textalign.clone())); fields.insert("tooltip".to_string(), self.tooltip.clone().map(Value::String).unwrap_or(Value::Na)); diff --git a/crates/pine-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index 9c6ca0b..cb16500 100644 --- a/crates/pine-builtins/src/plot/mod.rs +++ b/crates/pine-builtins/src/plot/mod.rs @@ -4,6 +4,13 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +fn color_to_value(color: &Option) -> Value { + match color { + Some(c) => Value::Color(c.clone()), + None => Value::Na, + } +} + /// plot() - Plots a series of data on the chart #[derive(BuiltinFunction)] #[builtin(name = "plot")] @@ -46,10 +53,7 @@ impl Plot { let mut fields = HashMap::new(); fields.insert("series".to_string(), self.series.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("linewidth".to_string(), Value::Number(self.linewidth)); fields.insert("style".to_string(), Value::String(self.style.clone())); fields.insert("trackprice".to_string(), Value::Bool(self.trackprice)); @@ -160,10 +164,7 @@ impl Plotbar { fields.insert("low".to_string(), self.low.clone()); fields.insert("close".to_string(), self.close.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("editable".to_string(), Value::Bool(self.editable)); fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); fields.insert("display".to_string(), Value::String(self.display.clone())); @@ -213,10 +214,7 @@ impl Plotcandle { fields.insert("low".to_string(), self.low.clone()); fields.insert("close".to_string(), self.close.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("wickcolor".to_string(), self.wickcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("editable".to_string(), Value::Bool(self.editable)); fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); @@ -272,10 +270,7 @@ impl Plotchar { fields.insert("title".to_string(), Value::String(self.title.clone())); fields.insert("char".to_string(), Value::String(self.char.clone())); fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("offset".to_string(), Value::Number(self.offset)); fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); @@ -333,10 +328,7 @@ impl Plotshape { fields.insert("title".to_string(), Value::String(self.title.clone())); fields.insert("style".to_string(), Value::String(self.style.clone())); fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), match &self.color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - }); + fields.insert("color".to_string(), color_to_value(&self.color)); fields.insert("offset".to_string(), Value::Number(self.offset)); fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); From c293edede3966b7002ddc83074a6a60cfc8fd4eb Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 09:21:31 +0800 Subject: [PATCH 4/7] Add label output --- crates/pine-builtins/src/label/mod.rs | 476 ++++++++++++++------------ crates/pine-interpreter/src/lib.rs | 64 +++- 2 files changed, 317 insertions(+), 223 deletions(-) diff --git a/crates/pine-builtins/src/label/mod.rs b/crates/pine-builtins/src/label/mod.rs index b5574bd..c48646f 100644 --- a/crates/pine-builtins/src/label/mod.rs +++ b/crates/pine-builtins/src/label/mod.rs @@ -1,16 +1,9 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Color, Interpreter, RuntimeError, Value}; +use pine_interpreter::{Color, Interpreter, Label, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -fn color_to_value(color: &Option) -> Value { - match color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - } -} - /// label.new() - Creates a new label object #[derive(BuiltinFunction)] #[builtin(name = "label.new")] @@ -40,29 +33,28 @@ struct LabelNew { } 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(), Value::String(self.text.clone())); - fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); - fields.insert("yloc".to_string(), Value::String(self.yloc.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); - fields.insert("style".to_string(), Value::String(self.style.clone())); - fields.insert("textcolor".to_string(), color_to_value(&self.textcolor)); - fields.insert("size".to_string(), Value::String(self.size.clone())); - fields.insert("textalign".to_string(), Value::String(self.textalign.clone())); - fields.insert("tooltip".to_string(), self.tooltip.clone().map(Value::String).unwrap_or(Value::Na)); - fields.insert( - "text_font_family".to_string(), - Value::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)) } } @@ -75,13 +67,14 @@ struct LabelSetX { } 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_number()? 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) } } @@ -94,13 +87,14 @@ struct LabelSetY { } 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_number()? 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) } } @@ -114,19 +108,19 @@ struct LabelSetXy { } 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_number()? 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 { @@ -136,19 +130,19 @@ struct LabelSetXloc { } 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(), Value::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_number()? 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 { @@ -157,19 +151,18 @@ struct LabelSetYloc { } impl LabelSetYloc { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("yloc".to_string(), Value::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_number()? 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 { @@ -178,19 +171,18 @@ struct LabelSetColor { } impl LabelSetColor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("color".to_string(), Value::Color(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_number()? 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 { @@ -199,19 +191,18 @@ struct LabelSetStyle { } impl LabelSetStyle { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("style".to_string(), Value::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_number()? 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 { @@ -220,19 +211,18 @@ struct LabelSetText { } impl LabelSetText { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("text".to_string(), Value::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_number()? 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 { @@ -241,19 +231,18 @@ struct LabelSetTextcolor { } impl LabelSetTextcolor { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("textcolor".to_string(), Value::Color(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_number()? 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 { @@ -262,19 +251,18 @@ struct LabelSetSize { } impl LabelSetSize { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("size".to_string(), Value::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_number()? 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 { @@ -283,19 +271,18 @@ struct LabelSetTextalign { } impl LabelSetTextalign { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("textalign".to_string(), Value::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_number()? 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 { @@ -304,19 +291,18 @@ struct LabelSetTooltip { } impl LabelSetTooltip { - fn execute(&self, _ctx: &mut Interpreter) -> Result { - if let Value::Object { fields, .. } = &self.id { - fields - .borrow_mut() - .insert("tooltip".to_string(), Value::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_number()? 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 { @@ -325,16 +311,14 @@ struct LabelSetTextFontFamily { } 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(), - Value::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_number()? 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) } } @@ -346,12 +330,13 @@ struct LabelGetX { } 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_number()? 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()) } } @@ -363,12 +348,13 @@ struct LabelGetY { } 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_number()? 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()) } } @@ -380,12 +366,13 @@ struct LabelGetText { } 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_number()? 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())) } } @@ -393,12 +380,13 @@ impl LabelGetText { #[derive(BuiltinFunction)] #[builtin(name = "label.delete")] struct LabelDelete { - _id: Value, + id: Value, } 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_number()? as usize; + ctx.output.delete_label(id); Ok(Value::Na) } } @@ -411,54 +399,108 @@ struct LabelCopy { } 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_number()? 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-interpreter/src/lib.rs b/crates/pine-interpreter/src/lib.rs index 88ef8e7..a4f33ae 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -93,6 +93,23 @@ impl Color { } } +/// 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, +} + /// Value types in the interpreter #[derive(Clone)] pub enum Value { @@ -120,7 +137,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(Color), // 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 @@ -152,7 +169,11 @@ impl std::fmt::Debug for Value { field_name, .. } => write!(f, "Enum({}::{})", enum_name, field_name), - Value::Color(color) => write!(f, "Color(rgba({}, {}, {}, {}))", color.r, color.g, color.b, color.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) } @@ -191,10 +212,7 @@ impl PartialEq for Value { }, ) => a_enum == b_enum && a_field == b_field, // Colors compare by all components - ( - Value::Color(c1), - Value::Color(c2), - ) => c1 == c2, + (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, @@ -297,6 +315,34 @@ struct MethodDef { body: Vec, } +#[derive(Default)] +pub struct PineOutput { + /// Label storage for drawable objects + labels: HashMap, + /// Next label ID + next_label_id: usize, +} + +impl PineOutput { + /// 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); + } +} + /// The interpreter executes a program with a given bar pub struct Interpreter { /// Local variables in the current scope @@ -311,6 +357,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 +370,7 @@ impl Interpreter { library_loader: None, historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -334,6 +383,7 @@ impl Interpreter { library_loader: None, historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -346,6 +396,7 @@ impl Interpreter { library_loader: Some(loader), historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } @@ -361,6 +412,7 @@ impl Interpreter { library_loader: Some(loader), historical_provider: None, exports: HashMap::new(), + output: PineOutput::default(), } } From ded57168e37c1869cf5da16ded8109c1c8068401 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 10:12:52 +0800 Subject: [PATCH 5/7] More stuff --- crates/pine-builtins/src/box/mod.rs | 466 ++++++++---------- crates/pine-builtins/src/label/mod.rs | 72 +-- crates/pine-builtins/src/plot/mod.rs | 17 +- crates/pine-interpreter/src/lib.rs | 44 ++ tests/testdata/box/delete_verification.pine | 15 + tests/testdata/errors/deleted_box_access.pine | 9 + .../testdata/errors/deleted_label_access.pine | 9 + .../testdata/labels/delete_verification.pine | 15 + 8 files changed, 334 insertions(+), 313 deletions(-) create mode 100644 tests/testdata/box/delete_verification.pine create mode 100644 tests/testdata/errors/deleted_box_access.pine create mode 100644 tests/testdata/errors/deleted_label_access.pine create mode 100644 tests/testdata/labels/delete_verification.pine diff --git a/crates/pine-builtins/src/box/mod.rs b/crates/pine-builtins/src/box/mod.rs index f712ed5..b21a5c3 100644 --- a/crates/pine-builtins/src/box/mod.rs +++ b/crates/pine-builtins/src/box/mod.rs @@ -1,16 +1,9 @@ use pine_builtin_macro::BuiltinFunction; -use pine_interpreter::{Color, Interpreter, RuntimeError, Value}; +use pine_interpreter::{Color, Interpreter, PineBox, RuntimeError, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -fn color_to_value(color: &Option) -> Value { - match color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - } -} - /// box.new() - Creates a new box object #[derive(BuiltinFunction)] #[builtin(name = "box.new")] @@ -48,30 +41,33 @@ struct BoxNew { } 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(), color_to_value(&self.border_color)); - fields.insert("border_width".to_string(), Value::Number(self.border_width)); - fields.insert("border_style".to_string(), Value::String(self.border_style.clone())); - fields.insert("extend".to_string(), Value::String(self.extend.clone())); - fields.insert("xloc".to_string(), Value::String(self.xloc.clone())); - fields.insert("bgcolor".to_string(), color_to_value(&self.bgcolor)); - fields.insert("text".to_string(), Value::String(self.text.clone())); - fields.insert("text_size".to_string(), Value::Number(self.text_size)); - fields.insert("text_color".to_string(), color_to_value(&self.text_color)); - fields.insert("text_halign".to_string(), Value::String(self.text_halign.clone())); - fields.insert("text_valign".to_string(), Value::String(self.text_valign.clone())); - fields.insert("text_wrap".to_string(), Value::String(self.text_wrap.clone())); - fields.insert("text_font_family".to_string(), Value::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)) } } @@ -79,20 +75,17 @@ 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) } } @@ -100,20 +93,17 @@ 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) } } @@ -121,20 +111,17 @@ 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) } } @@ -142,20 +129,17 @@ 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) } } @@ -163,21 +147,19 @@ 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) } } @@ -185,21 +167,19 @@ 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) } } @@ -207,20 +187,17 @@ impl BoxSetRightbottom { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_color")] struct BoxSetBorderColor { - id: 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(), Value::Color(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) } } @@ -228,20 +205,17 @@ impl BoxSetBorderColor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_width")] struct BoxSetBorderWidth { - id: 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(), Value::Number(self.width)); - 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) } } @@ -249,20 +223,17 @@ impl BoxSetBorderWidth { #[derive(BuiltinFunction)] #[builtin(name = "box.set_border_style")] struct BoxSetBorderStyle { - id: 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(), Value::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) } } @@ -270,20 +241,17 @@ impl BoxSetBorderStyle { #[derive(BuiltinFunction)] #[builtin(name = "box.set_extend")] struct BoxSetExtend { - id: 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(), Value::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) } } @@ -291,20 +259,17 @@ impl BoxSetExtend { #[derive(BuiltinFunction)] #[builtin(name = "box.set_bgcolor")] struct BoxSetBgcolor { - id: 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(), Value::Color(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) } } @@ -312,20 +277,17 @@ impl BoxSetBgcolor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text")] struct BoxSetText { - id: 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(), Value::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) } } @@ -333,20 +295,17 @@ impl BoxSetText { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_color")] struct BoxSetTextColor { - id: 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(), Value::Color(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) } } @@ -354,20 +313,17 @@ impl BoxSetTextColor { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_size")] struct BoxSetTextSize { - id: 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(), Value::Number(self.size)); - 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) } } @@ -375,20 +331,17 @@ impl BoxSetTextSize { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_halign")] struct BoxSetTextHalign { - id: 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(), Value::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) } } @@ -396,20 +349,17 @@ impl BoxSetTextHalign { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_valign")] struct BoxSetTextValign { - id: 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(), Value::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) } } @@ -417,20 +367,17 @@ impl BoxSetTextValign { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_wrap")] struct BoxSetTextWrap { - id: 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(), Value::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) } } @@ -438,20 +385,17 @@ impl BoxSetTextWrap { #[derive(BuiltinFunction)] #[builtin(name = "box.set_text_font_family")] struct BoxSetTextFontFamily { - id: 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(), Value::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) } } @@ -459,23 +403,21 @@ impl BoxSetTextFontFamily { #[derive(BuiltinFunction)] #[builtin(name = "box.set_xloc")] struct BoxSetXloc { - id: Value, + id: f64, left: Value, right: 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(), Value::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) } } @@ -483,16 +425,15 @@ 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()) } } @@ -500,16 +441,15 @@ 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()) } } @@ -517,16 +457,15 @@ 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()) } } @@ -534,16 +473,15 @@ 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()) } } @@ -551,12 +489,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) } } @@ -565,20 +504,17 @@ 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/label/mod.rs b/crates/pine-builtins/src/label/mod.rs index c48646f..52472b0 100644 --- a/crates/pine-builtins/src/label/mod.rs +++ b/crates/pine-builtins/src/label/mod.rs @@ -62,13 +62,13 @@ 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 { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -82,13 +82,13 @@ 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 { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -102,14 +102,14 @@ 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 { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -124,14 +124,14 @@ impl LabelSetXy { #[derive(BuiltinFunction)] #[builtin(name = "label.set_xloc")] struct LabelSetXloc { - id: Value, + id: f64, x: Value, xloc: String, } impl LabelSetXloc { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -146,13 +146,13 @@ impl LabelSetXloc { #[derive(BuiltinFunction)] #[builtin(name = "label.set_yloc")] struct LabelSetYloc { - id: Value, + id: f64, yloc: String, } impl LabelSetYloc { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -166,13 +166,13 @@ impl LabelSetYloc { #[derive(BuiltinFunction)] #[builtin(name = "label.set_color")] struct LabelSetColor { - id: Value, + id: f64, color: Color, } impl LabelSetColor { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -186,13 +186,13 @@ impl LabelSetColor { #[derive(BuiltinFunction)] #[builtin(name = "label.set_style")] struct LabelSetStyle { - id: Value, + id: f64, style: String, } impl LabelSetStyle { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -206,13 +206,13 @@ impl LabelSetStyle { #[derive(BuiltinFunction)] #[builtin(name = "label.set_text")] struct LabelSetText { - id: Value, + id: f64, text: String, } impl LabelSetText { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -226,13 +226,13 @@ impl LabelSetText { #[derive(BuiltinFunction)] #[builtin(name = "label.set_textcolor")] struct LabelSetTextcolor { - id: Value, + id: f64, textcolor: Color, } impl LabelSetTextcolor { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -246,13 +246,13 @@ impl LabelSetTextcolor { #[derive(BuiltinFunction)] #[builtin(name = "label.set_size")] struct LabelSetSize { - id: Value, + id: f64, size: String, } impl LabelSetSize { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -266,13 +266,13 @@ impl LabelSetSize { #[derive(BuiltinFunction)] #[builtin(name = "label.set_textalign")] struct LabelSetTextalign { - id: Value, + id: f64, textalign: String, } impl LabelSetTextalign { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -286,13 +286,13 @@ impl LabelSetTextalign { #[derive(BuiltinFunction)] #[builtin(name = "label.set_tooltip")] struct LabelSetTooltip { - id: Value, + id: f64, tooltip: String, } impl LabelSetTooltip { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -306,13 +306,13 @@ impl LabelSetTooltip { #[derive(BuiltinFunction)] #[builtin(name = "label.set_text_font_family")] struct LabelSetTextFontFamily { - id: Value, + id: f64, text_font_family: String, } impl LabelSetTextFontFamily { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -326,12 +326,12 @@ impl LabelSetTextFontFamily { #[derive(BuiltinFunction)] #[builtin(name = "label.get_x")] struct LabelGetX { - id: Value, + id: f64, } impl LabelGetX { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -344,12 +344,12 @@ impl LabelGetX { #[derive(BuiltinFunction)] #[builtin(name = "label.get_y")] struct LabelGetY { - id: Value, + id: f64, } impl LabelGetY { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -362,12 +362,12 @@ impl LabelGetY { #[derive(BuiltinFunction)] #[builtin(name = "label.get_text")] struct LabelGetText { - id: Value, + id: f64, } impl LabelGetText { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) @@ -380,12 +380,12 @@ impl LabelGetText { #[derive(BuiltinFunction)] #[builtin(name = "label.delete")] struct LabelDelete { - id: Value, + id: f64, } impl LabelDelete { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; ctx.output.delete_label(id); Ok(Value::Na) } @@ -395,12 +395,12 @@ impl LabelDelete { #[derive(BuiltinFunction)] #[builtin(name = "label.copy")] struct LabelCopy { - id: Value, + id: f64, } impl LabelCopy { fn execute(&self, ctx: &mut Interpreter) -> Result { - let id = self.id.as_number()? as usize; + let id = self.id as usize; let label = ctx .output .get_label_mut(id) diff --git a/crates/pine-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index cb16500..b233ac0 100644 --- a/crates/pine-builtins/src/plot/mod.rs +++ b/crates/pine-builtins/src/plot/mod.rs @@ -4,13 +4,6 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -fn color_to_value(color: &Option) -> Value { - match color { - Some(c) => Value::Color(c.clone()), - None => Value::Na, - } -} - /// plot() - Plots a series of data on the chart #[derive(BuiltinFunction)] #[builtin(name = "plot")] @@ -53,7 +46,7 @@ impl Plot { let mut fields = HashMap::new(); fields.insert("series".to_string(), self.series.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); + fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("linewidth".to_string(), Value::Number(self.linewidth)); fields.insert("style".to_string(), Value::String(self.style.clone())); fields.insert("trackprice".to_string(), Value::Bool(self.trackprice)); @@ -164,7 +157,7 @@ impl Plotbar { fields.insert("low".to_string(), self.low.clone()); fields.insert("close".to_string(), self.close.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); + fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("editable".to_string(), Value::Bool(self.editable)); fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); fields.insert("display".to_string(), Value::String(self.display.clone())); @@ -214,7 +207,7 @@ impl Plotcandle { fields.insert("low".to_string(), self.low.clone()); fields.insert("close".to_string(), self.close.clone()); fields.insert("title".to_string(), Value::String(self.title.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); + fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("wickcolor".to_string(), self.wickcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("editable".to_string(), Value::Bool(self.editable)); fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); @@ -270,7 +263,7 @@ impl Plotchar { fields.insert("title".to_string(), Value::String(self.title.clone())); fields.insert("char".to_string(), Value::String(self.char.clone())); fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); + fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("offset".to_string(), Value::Number(self.offset)); fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); @@ -328,7 +321,7 @@ impl Plotshape { fields.insert("title".to_string(), Value::String(self.title.clone())); fields.insert("style".to_string(), Value::String(self.style.clone())); fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), color_to_value(&self.color)); + fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); fields.insert("offset".to_string(), Value::Number(self.offset)); fields.insert("text".to_string(), Value::String(self.text.clone())); fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); diff --git a/crates/pine-interpreter/src/lib.rs b/crates/pine-interpreter/src/lib.rs index a4f33ae..2dc2a7d 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -110,6 +110,28 @@ pub struct Label { 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, +} + /// Value types in the interpreter #[derive(Clone)] pub enum Value { @@ -321,6 +343,10 @@ pub struct PineOutput { labels: HashMap, /// Next label ID next_label_id: usize, + /// Box storage for drawable objects + boxes: HashMap, + /// Next box ID + next_box_id: usize, } impl PineOutput { @@ -341,6 +367,24 @@ impl PineOutput { 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 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 From 3ffa49111b6775016acc4352f564a1c4a0f00cdb Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 13:01:31 +0800 Subject: [PATCH 6/7] Latest changes --- benches/src/test_data.rs | 2 +- crates/pine-builtins/src/plot/mod.rs | 221 +++++++++--------- crates/pine-interpreter/src/lib.rs | 153 +++++++++++- crates/pine/src/lib.rs | 6 +- tests/lib.rs | 4 +- tests/testdata/arguments/mixed_args.pine | 35 +-- tests/testdata/arguments/named_args.pine | 37 ++- tests/testdata/arguments/positional_args.pine | 35 ++- 8 files changed, 307 insertions(+), 186 deletions(-) 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-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index b233ac0..2fb1e3a 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::{Color, 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; @@ -42,32 +45,28 @@ struct Plot { } 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(), Value::String(self.title.clone())); - fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("linewidth".to_string(), Value::Number(self.linewidth)); - fields.insert("style".to_string(), Value::String(self.style.clone())); - fields.insert("trackprice".to_string(), Value::Bool(self.trackprice)); - fields.insert("histbase".to_string(), Value::Number(self.histbase)); - fields.insert("offset".to_string(), Value::Number(self.offset)); - fields.insert("join".to_string(), Value::Bool(self.join)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); - fields.insert( - "linestyle".to_string(), - Value::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) } } @@ -103,22 +102,24 @@ struct Plotarrow { } 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(), Value::String(self.title.clone())); - fields.insert("colorup".to_string(), self.colorup.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("colordown".to_string(), self.colordown.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("offset".to_string(), Value::Number(self.offset)); - fields.insert("minheight".to_string(), Value::Number(self.minheight)); - fields.insert("maxheight".to_string(), Value::Number(self.maxheight)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + 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) } } @@ -150,21 +151,23 @@ struct Plotbar { } 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(), Value::String(self.title.clone())); - fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + 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) } } @@ -200,23 +203,25 @@ struct Plotcandle { } 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(), Value::String(self.title.clone())); - fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("wickcolor".to_string(), self.wickcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("bordercolor".to_string(), self.bordercolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + 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) } } @@ -257,24 +262,26 @@ struct Plotchar { } 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(), Value::String(self.title.clone())); - fields.insert("char".to_string(), Value::String(self.char.clone())); - fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("offset".to_string(), Value::Number(self.offset)); - fields.insert("text".to_string(), Value::String(self.text.clone())); - fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("size".to_string(), Value::String(self.size.clone())); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + 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) } } @@ -315,24 +322,26 @@ struct Plotshape { } 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(), Value::String(self.title.clone())); - fields.insert("style".to_string(), Value::String(self.style.clone())); - fields.insert("location".to_string(), Value::String(self.location.clone())); - fields.insert("color".to_string(), self.color.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("offset".to_string(), Value::Number(self.offset)); - fields.insert("text".to_string(), Value::String(self.text.clone())); - fields.insert("textcolor".to_string(), self.textcolor.clone().map(|c| Value::Color(c)).unwrap_or(Value::Na)); - fields.insert("editable".to_string(), Value::Bool(self.editable)); - fields.insert("size".to_string(), Value::String(self.size.clone())); - fields.insert("show_last".to_string(), self.show_last.map_or(Value::Na, Value::Number)); - fields.insert("display".to_string(), Value::String(self.display.clone())); - fields.insert("format".to_string(), self.format.as_ref().map_or(Value::Na, |s| Value::String(s.clone()))); - fields.insert("precision".to_string(), self.precision.map_or(Value::Na, Value::Number)); - fields.insert("force_overlay".to_string(), Value::Bool(self.force_overlay)); + 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-interpreter/src/lib.rs b/crates/pine-interpreter/src/lib.rs index 2dc2a7d..4c19958 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -132,6 +132,121 @@ pub struct PineBox { 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 { @@ -337,7 +452,7 @@ struct MethodDef { body: Vec, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct PineOutput { /// Label storage for drawable objects labels: HashMap, @@ -347,9 +462,36 @@ pub struct PineOutput { 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; @@ -476,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 From 8eed92bd56eca4c31dfbf561bf23d4e55d4294a2 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 13 Feb 2026 13:04:53 +0800 Subject: [PATCH 7/7] Fix lint --- crates/pine-builtin-macro/src/lib.rs | 8 --- crates/pine-builtins/src/box/mod.rs | 96 +++++++++++++++++++++------- crates/pine-builtins/src/plot/mod.rs | 6 +- crates/pine-builtins/src/str/mod.rs | 4 +- 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/crates/pine-builtin-macro/src/lib.rs b/crates/pine-builtin-macro/src/lib.rs index 91e449b..996c10a 100644 --- a/crates/pine-builtin-macro/src/lib.rs +++ b/crates/pine-builtin-macro/src/lib.rs @@ -405,14 +405,6 @@ fn generate_value_conversion( Some(arg_value.as_bool()?) } } - } else if type_str.contains("Value") { - quote! { - if matches!(arg_value, Value::Na) { - None - } else { - Some(arg_value) - } - } } else { quote! { if matches!(arg_value, Value::Na) { diff --git a/crates/pine-builtins/src/box/mod.rs b/crates/pine-builtins/src/box/mod.rs index b21a5c3..1b50a22 100644 --- a/crates/pine-builtins/src/box/mod.rs +++ b/crates/pine-builtins/src/box/mod.rs @@ -82,7 +82,9 @@ struct BoxSetLeft { impl BoxSetLeft { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -100,7 +102,9 @@ struct BoxSetTop { impl BoxSetTop { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -118,7 +122,9 @@ struct BoxSetRight { impl BoxSetRight { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -136,7 +142,9 @@ struct BoxSetBottom { impl BoxSetBottom { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -155,7 +163,9 @@ struct BoxSetLefttop { impl BoxSetLefttop { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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(); @@ -175,7 +185,9 @@ struct BoxSetRightbottom { impl BoxSetRightbottom { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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(); @@ -194,7 +206,9 @@ struct BoxSetBorderColor { impl BoxSetBorderColor { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -212,7 +226,9 @@ struct BoxSetBorderWidth { impl BoxSetBorderWidth { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -230,7 +246,9 @@ struct BoxSetBorderStyle { impl BoxSetBorderStyle { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -248,7 +266,9 @@ struct BoxSetExtend { impl BoxSetExtend { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -266,7 +286,9 @@ struct BoxSetBgcolor { impl BoxSetBgcolor { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -284,7 +306,9 @@ struct BoxSetText { impl BoxSetText { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -302,7 +326,9 @@ struct BoxSetTextColor { impl BoxSetTextColor { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -320,7 +346,9 @@ struct BoxSetTextSize { impl BoxSetTextSize { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -338,7 +366,9 @@ struct BoxSetTextHalign { impl BoxSetTextHalign { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -356,7 +386,9 @@ struct BoxSetTextValign { impl BoxSetTextValign { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -374,7 +406,9 @@ struct BoxSetTextWrap { impl BoxSetTextWrap { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -392,7 +426,9 @@ struct BoxSetTextFontFamily { impl BoxSetTextFontFamily { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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) @@ -412,7 +448,9 @@ struct BoxSetXloc { impl BoxSetXloc { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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(); @@ -431,7 +469,9 @@ struct BoxGetLeft { impl BoxGetLeft { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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()) } @@ -447,7 +487,9 @@ struct BoxGetTop { impl BoxGetTop { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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()) } @@ -463,7 +505,9 @@ struct BoxGetRight { impl BoxGetRight { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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()) } @@ -479,7 +523,9 @@ struct BoxGetBottom { impl BoxGetBottom { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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()) } @@ -510,7 +556,9 @@ struct BoxCopy { impl BoxCopy { fn execute(&self, ctx: &mut Interpreter) -> Result { let id = self.id as usize; - let box_obj = ctx.output.get_box_mut(id) + 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); diff --git a/crates/pine-builtins/src/plot/mod.rs b/crates/pine-builtins/src/plot/mod.rs index 2fb1e3a..ad20410 100644 --- a/crates/pine-builtins/src/plot/mod.rs +++ b/crates/pine-builtins/src/plot/mod.rs @@ -1,8 +1,8 @@ use pine_builtin_macro::BuiltinFunction; 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, + 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; diff --git a/crates/pine-builtins/src/str/mod.rs b/crates/pine-builtins/src/str/mod.rs index 40b5c31..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(color) => format!("rgba({}, {}, {}, {})", color.r, color.g, color.b, color.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),