git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitac109bcc9d9997244935307680939a5f24dc6de7
parentcfbd0d1aa3
authorLucas Galante <[email protected]>
date2026-09-24 10:04
refactor: TreeScope is public, with a constructor, for the wrangle's ch()

A script on a node reads channels through the scope its parameters use,
so an expression-valued parameter is evaluated before the script sees it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/geometry.rs | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/geometry.rs b/src/geometry.rs
index b672fc9..12bba38 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -720,13 +720,25 @@ fn find_ref_param<'a>(node: &'a FsNode, name: &str) -> Option<(&'a ParamDef, Opt
 /// What an expression on `node` sees: the tree for its channels, the frame
 /// for `$F`, and the chain of parameters being evaluated, so a reference
 /// that comes back round to itself is an error rather than a stack overflow.
-struct TreeScope<'a> {
+///
+/// Public so a SCRIPT on a node (the wrangle) can read channels through
+/// the same scope its parameters do: a `ch("../a/Radius")` in a script
+/// then sees an expression-valued Radius evaluated, not its text.
+pub struct TreeScope<'a> {
     root: &'a FsNode,
     node: &'a FsNode,
     frame: i32,
     stack: Vec<(String, String)>,
 }
 
+impl<'a> TreeScope<'a> {
+    /// A scope for something on `node` — an expression on one of its
+    /// parameters, or a script it runs — evaluated at `frame`.
+    pub fn new(root: &'a FsNode, node: &'a FsNode, frame: i32) -> Self {
+        TreeScope { root, node, frame, stack: Vec::new() }
+    }
+}
+
 impl<'a> crate::expr::Scope for TreeScope<'a> {
     fn channel(&mut self, path: &str, kind: ChKind) -> Result<Value, String> {
         let (absolute, segs, pname) = split_ref_path(path);