aimx/
reference.rs

1//! Reference parsing for AIMX expressions.
2//!
3//! Provides identifier and chained reference parsing used by higher-level
4//! expression and postfix handling. References are resolved against the
5//! evaluation context and used for workflow/workspace path mapping.
6
7#[allow(unused_imports)]
8use crate::Aim;
9use crate::{
10    aim::{ContextLike, WriterLike, Writer},
11    expressions::{ExpressionLike, parse_accessor},
12    functions::FunctionRegistry,
13    values::Value,
14};
15use anyhow::{Result, anyhow};
16use nom::{
17    Err as NomErr, IResult, Parser,
18    branch::alt,
19    bytes::complete::tag,
20    character::complete::{alpha1, alphanumeric1, multispace0},
21    combinator::{map, recognize},
22    error::Error,
23    multi::many0_count,
24    sequence::pair,
25};
26use std::{
27    fmt, path::PathBuf, sync::Arc
28};
29use once_cell::sync::OnceCell;
30
31/// Reference to an identifier or chained scope segments.
32///
33/// Used by expression parsing and evaluation to locate values in the
34/// context and to map references to workflow/workspace paths.
35///
36/// `PartialEq<String>` only matches `Reference::One` against an identical
37/// string; multi-part references never compare equal to `String`.
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum Reference {
40    /// A single identifier reference
41    One(Arc<str>),
42    /// A two-part chained property reference
43    Two(Arc<str>, Arc<str>),
44    /// A three-part chained property reference
45    Three(Arc<str>, Arc<str>, Arc<str>),
46    /// A four-part chained property reference
47    Four(Arc<str>, Arc<str>, Arc<str>, Arc<str>),
48}
49
50impl Reference {
51    /// Creates a new single-part reference from an identifier.
52    #[doc = include_str!("../docs/reference/new.md")]
53    pub fn new(identifier: Arc<str>) -> Arc<Self> {
54        Arc::new(Reference::One(identifier))
55    }
56
57    /// Returns the global singleton reference to the workspace root identifier (`_`).
58    #[doc = include_str!("../docs/reference/workspace.md")]
59    pub fn workspace() -> Arc<Self> {
60        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
61        GLOBAL.get_or_init(|| Reference::new(Arc::from("_"))).clone()
62    }
63
64    /// Returns the global singleton reference to the inference context identifier (`inference`).
65    #[doc = include_str!("../docs/reference/inference.md")]
66    pub fn inference() -> Arc<Self> {
67        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
68        GLOBAL.get_or_init(|| Reference::new(Arc::from("inference"))).clone()
69    }
70
71    /// Returns the global singleton reference to the evaluation context identifier (`context`).
72    #[doc = include_str!("../docs/reference/context.md")]
73    pub fn context() -> Arc<Self> {
74        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
75        GLOBAL.get_or_init(|| Reference::new(Arc::from("context"))).clone()
76    }
77
78    /// Returns the global singleton reference to the task status identifier (`status`).
79    #[doc = include_str!("../docs/reference/status.md")]
80    pub fn status() -> Arc<Self> {
81        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
82        GLOBAL.get_or_init(|| Reference::new(Arc::from("status"))).clone()
83    }
84
85    /// Returns the global singleton reference to the tool identifier (`tool`).
86    #[doc = include_str!("../docs/reference/tool.md")]
87    pub fn tool() -> Arc<Self> {
88        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
89        GLOBAL.get_or_init(|| Reference::new(Arc::from("tool"))).clone()
90    }
91
92    /// Returns the global singleton reference to the workflow identifier (`workflow`).
93    #[doc = include_str!("../docs/reference/workflow.md")]
94    pub fn workflow() -> Arc<Self> {
95        static GLOBAL: OnceCell<Arc<Reference>> = OnceCell::new();
96        GLOBAL.get_or_init(|| Reference::new(Arc::from("workflow"))).clone()
97    }
98
99    /// Parses a reference string into a `Reference` instance.
100    #[doc = include_str!("../docs/reference/parse.md")]
101    pub fn parse(input: &str) -> Result<Arc<Self>> {
102        match parse_reference(input) {
103            Ok((_, reference)) => Ok(reference),
104            Err(_) => Err(anyhow!("Invalid reference ~{}", input)),
105        }
106    }
107
108    /// Converts a reference into a handle string by joining parts with underscores.
109    #[doc = include_str!("../docs/reference/handle.md")]
110    pub fn handle(&self) -> Arc<str> {
111        let mut writer = Writer::formulizer();
112        match self {
113            Reference::One(one) => 
114                writer.write_str(one),
115            Reference::Two(one, two) => {
116                writer.write_str(one);
117                writer.write_char('_');
118                writer.write_str(two);
119            }
120            Reference::Three(one, two, three) => {
121                writer.write_str(one);
122                writer.write_char('_');
123                writer.write_str(two);
124                writer.write_char('_');
125                writer.write_str(three);
126            }
127            Reference::Four(one, two, three, four) => {
128                writer.write_str(one);
129                writer.write_char('_');
130                writer.write_str(two);
131                writer.write_char('_');
132                writer.write_str(three);
133                writer.write_char('_');
134                writer.write_str(four);
135            }
136        }
137        Arc::from(writer.finish())
138    }
139
140    /// Returns the identifier of the terminal segment in a reference chain.
141    #[doc = include_str!("../docs/reference/identifier.md")]
142    pub fn identifier(&self) -> Arc<str> {
143         match self {
144            Reference::One(one) => one.clone(),
145            Reference::Two(_, two) => two.clone(),
146            Reference::Three(_, _, three) => three.clone(),
147            Reference::Four(_, _, _, four) => four.clone(),
148        }       
149    }
150
151    /// Returns the base in a reference chain.
152    pub fn base(&self) -> Arc<str> {
153         match self {
154            Reference::One(one) => one.clone(),
155            Reference::Two(one, _) => one.clone(),
156            Reference::Three(one, _, _) => one.clone(),
157            Reference::Four(one, _, _, _) => one.clone(),
158        }       
159    }
160
161    pub fn depth(&self) -> usize {
162        match self {
163            Reference::One{..} => 1,
164            Reference::Two{..} => 2,
165            Reference::Three{..} => 3,
166            Reference::Four{..} => 4,
167        }
168    }
169
170    pub fn is_level_below(&self, reference: &Reference) -> bool {
171        match self {
172            Reference::One{..} => {
173                match reference {
174                    Reference::One{..} => false,
175                    _ => true,
176                }
177            },
178            Reference::Two{..} => {
179                match reference {
180                    Reference::Three{..}
181                    | Reference::Four{..} => true,
182                    _ => false,
183                }
184            }
185            Reference::Three{..} => {
186                match reference {
187                    Reference::Four{..} => true,
188                    _ => false,
189                }
190            }
191            Reference::Four{..} => false,
192        }
193    }
194
195    pub fn contains_root(&self) -> bool {
196        &*self.base() == "_"
197    }
198
199    pub fn to_path(&self) -> PathBuf {
200        if self.contains_root() {
201            PathBuf::from("workspace.aim")
202        } else {
203            let path = PathBuf::from("workspace");
204            match self {
205                Reference::One(one) => {
206                    path.join(format!("{}.aim", one))
207                }
208                Reference::Two(one, two) => {
209                    path.join(one.as_ref())
210                        .join(format!("{}.aim", two))
211                }
212                Reference::Three(one, two, three) => {
213                    path.join(one.as_ref())
214                        .join(two.as_ref())
215                        .join(format!("{}.aim", three))
216                }
217                Reference::Four(one, two, three, four) => {
218                    path.join(one.as_ref())
219                        .join(two.as_ref())
220                        .join(three.as_ref())
221                        .join(format!("{}.aim", four))
222                }
223            }
224        }
225    }
226
227    /// Creates a new `Reference` instance with the same structure but a different terminal identifier.
228    #[doc = include_str!("../docs/reference/rename.md")]
229    pub fn rename_child(&self, identifier: Arc<str>) -> Reference {
230        match self {
231            Reference::One(_) => Reference::One(
232                identifier
233            ),
234            Reference::Two(one, _) => Reference::Two(
235                one.clone(),
236                identifier,
237            ),
238            Reference::Three(one, two, _) => Reference::Three(
239                one.clone(),
240                two.clone(),
241                identifier,
242            ),
243            Reference::Four(one, two, three, _) => Reference::Four(
244                one.clone(),
245                two.clone(),
246                three.clone(),
247                identifier,
248            ),
249        }
250    }
251
252    /// Returns the parent reference by removing the terminal segment from a multi-part reference chain.
253    #[doc = include_str!("../docs/reference/parent.md")]
254    pub fn get_parent(&self) -> Result<Reference> {
255        match self {
256            Reference::One(..) => Err(anyhow!("Reference {} has no parent", self)),
257            Reference::Two(one, _) => Ok(Reference::One(
258                one.clone(),
259            )),
260            Reference::Three(one, two, _) => Ok(Reference::Two(
261                one.clone(),
262                two.clone(),
263            )),
264            Reference::Four(one, two, three, _) => Ok(Reference::Three(
265                one.clone(),
266                two.clone(),
267                three.clone(),
268            )),
269        }
270    }
271
272    /// Creates a new `Reference` by appending an identifier to extend the reference chain.
273    #[doc = include_str!("../docs/reference/child.md")]
274    pub fn add_child(&self, identifier: Arc<str>) -> Result<Reference> {
275        if self.contains_root() {
276            match self {
277                Reference::One(_) => Ok(Reference::One(
278                    identifier)
279                ),
280                Reference::Two(_, two) => Ok(Reference::Two(
281                    two.clone(),
282                    identifier,
283                )),
284                Reference::Three(_, two, three) => Ok(Reference::Three(
285                    two.clone(),
286                    three.clone(),
287                    identifier,
288                )),
289                Reference::Four(_, two, three, four) => Ok(Reference::Four(
290                    two.clone(),
291                    three.clone(),
292                    four.clone(),
293                    identifier,
294                )),
295            }
296        } else {
297            match self {
298                Reference::One(one) => Ok(Reference::Two(
299                    one.clone(),
300                    identifier)
301                ),
302                Reference::Two(one, two) => Ok(Reference::Three(
303                    one.clone(),
304                    two.clone(),
305                    identifier,
306                )),
307                Reference::Three(one, two, three) => Ok(Reference::Four(
308                    one.clone(),
309                    two.clone(),
310                    three.clone(),
311                    identifier,
312                )),
313                Reference::Four(..) => Err(anyhow!("Cannot append {} to {} scope~{}", identifier, self, identifier)),
314            }
315        }
316    }
317
318    /// Combines a scope reference with another reference to create a normalized, fully-qualified reference.
319    #[doc = include_str!("../docs/reference/normalize.md")]
320    pub fn combine_and_normalize(&self, reference: &Reference) -> Result<Reference> {
321        match self {
322            Reference::One(one) => match reference {
323                Reference::One(ref_one) => Ok(Reference::Two(one.clone(), ref_one.clone())),
324                Reference::Two(ref_one, ref_two) => Ok(Reference::Three(
325                    one.clone(),
326                    ref_one.clone(),
327                    ref_two.clone(),
328                )),
329                Reference::Three(ref_one, ref_two, ref_three) => Ok(Reference::Four(
330                    one.clone(),
331                    ref_one.clone(),
332                    ref_two.clone(),
333                    ref_three.clone(),
334                )),
335                Reference::Four(..) => Err(anyhow!(
336                    "Cannot normalize {} within {} scope~{}",
337                    reference,
338                    self,
339                    reference
340                )),
341            },
342            Reference::Two(one, two) => match reference {
343                Reference::One(ref_one) => {
344                    Ok(Reference::Three(one.clone(), two.clone(), ref_one.clone()))
345                }
346                Reference::Two(ref_one, ref_two) => Ok(Reference::Four(
347                    one.clone(),
348                    two.clone(),
349                    ref_one.clone(),
350                    ref_two.clone(),
351                )),
352                _ => Err(anyhow!(
353                    "Cannot normalize {} within {} scope~{}",
354                    reference,
355                    self,
356                    reference
357                )),
358            },
359            Reference::Three(one, two, three) => match reference {
360                Reference::One(ref_one) => Ok(Reference::Four(
361                    one.clone(),
362                    two.clone(),
363                    three.clone(),
364                    ref_one.clone(),
365                )),
366                _ => Err(anyhow!(
367                    "Cannot normalize {} within {} scope~{}",
368                    reference,
369                    self,
370                    reference
371                )),
372            },
373            Reference::Four(..) => Err(anyhow!(
374                "Cannot normalize {} within {} scope~{}",
375                reference,
376                self,
377                reference
378            )),
379        }
380    }
381
382    /// Serializes a reference into a writer using dot notation.
383    #[doc = include_str!("../docs/reference/print.md")]
384    pub fn print(&self, writer: &mut Writer) {
385        match self {
386            Reference::One(one) => 
387                writer.write_str(one),
388            Reference::Two(one, two) => {
389                writer.write_str(one);
390                writer.write_char('.');
391                writer.write_str(two);
392            }
393            Reference::Three(one, two, three) => {
394                writer.write_str(one);
395                writer.write_char('.');
396                writer.write_str(two);
397                writer.write_char('.');
398                writer.write_str(three);
399            }
400            Reference::Four(one, two, three, four) => {
401                writer.write_str(one);
402                writer.write_char('.');
403                writer.write_str(two);
404                writer.write_char('.');
405                writer.write_str(three);
406                writer.write_char('.');
407                writer.write_str(four);
408            }
409        }
410    }
411}
412
413impl PartialEq<str> for Reference {
414    fn eq(&self, other: &str) -> bool {
415        match self {
416            Reference::One(s) => **s == *other,
417            _ => false,
418        }
419    }
420}
421
422impl PartialEq<Reference> for str {
423    fn eq(&self, other: &Reference) -> bool {
424        match other {
425            Reference::One(s) => **s == *self,
426            _ => false,
427        }
428    }
429}
430
431/// Parse an identifier.
432///
433/// Identifiers in AIMX:
434/// - must start with a letter or underscore
435/// - may contain letters, digits, and underscores after the first character
436///
437/// A leading underscore is commonly used for local or alignable references.
438pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
439    map(
440        recognize(pair(
441            alt((alpha1, tag("_"))),
442            many0_count(alt((alphanumeric1, tag("_")))),
443        )),
444        |s: &str| s,
445    )
446    .parse(input)
447}
448
449pub fn parse_arc_identifier(input: &str) -> IResult<&str, Arc<str>> {
450    let (input, identifer) = parse_identifier(input)?;
451    Ok((input, Arc::from(identifer)))
452}
453
454/// Parse a reference chain: `identifier ('.' identifier){0,3}`.
455///
456/// Supports up to four segments and uses the global `FunctionRegistry` to
457/// distinguish between:
458///
459/// - plain references (e.g. `foo.bar`)
460/// - function/method calls (e.g. `foo()` or `foo.bar()`), which are handled by
461///   higher-level postfix parsing.
462///
463/// Behavior highlights:
464///
465/// - A bare `_` is rejected as an invalid reference.
466/// - If the first identifier is a known function and is immediately followed by
467///   `(`, parsing fails so that the caller can treat it as a function call.
468/// - For chained references, if a later segment is a known function and is
469///   followed by `(`, the parser "rolls back" to the shorter reference so that
470///   constructs like `object.method()` are parsed as a reference to `object`
471///   followed by a method call.
472///
473/// Errors are reported via the `nom::IResult` error variants; resolution-time
474/// errors (e.g. undefined reference) occur later when evaluating.
475pub fn parse_reference(input: &str) -> IResult<&str, Arc<Reference>> {
476    let registry = match FunctionRegistry::read_lock() {
477        Ok(guard) => guard,
478        Err(_) => {
479            return Err(NomErr::Failure(Error::new(
480                input,
481                nom::error::ErrorKind::Fail,
482            )));
483        }
484    };
485
486    // Parse the first identifier
487    let (input, one) = parse_arc_identifier(input)?;
488    let (input, _) = multispace0.parse(input)?;
489
490    // Reject bare `_` and treat known-function + `(` as a function call, not a reference
491    if &*one == "_" || (input.starts_with('(') && registry.handler_exists(one.clone())) {
492        return Err(NomErr::Failure(Error::new(
493            input,
494            nom::error::ErrorKind::Fail,
495        )));
496    }
497
498    // Try to parse `.two` (and optional `.three`)
499    if let Ok((remainder, _)) = parse_accessor(input) {
500        let (remainder, two) = parse_arc_identifier(remainder)?;
501        let (remainder, _) = multispace0.parse(remainder)?;
502
503        // If `two` is a known function followed by `(`, roll back to `one` as the reference
504        if remainder.starts_with('(') && registry.handler_exists(two.clone()) {
505            return Ok((input, Arc::new(Reference::One(one))));
506        }
507
508        let input = remainder;
509
510        if let Ok((remainder, _)) = parse_accessor(input) {
511            let (remainder, three) = parse_arc_identifier(remainder)?;
512            let (remainder, _) = multispace0.parse(remainder)?;
513
514            // If `three` is a known function followed by `(`, roll back to `one.two`
515            if remainder.starts_with('(') && registry.handler_exists(three.clone()) {
516                return Ok((input, Arc::new(Reference::Two(one, two))));
517            }
518
519            let input = remainder;
520
521            if let Ok((remainder, _)) = parse_accessor(input) {
522                let (remainder, four) = parse_arc_identifier(remainder)?;
523                let (remainder, _) = multispace0.parse(remainder)?;
524
525                // If `three` is a known function followed by `(`, roll back to `one.two`
526                if remainder.starts_with('(') && registry.handler_exists(four.clone()) {
527                    return Ok((input, Arc::new(Reference::Three(one, two, three))));
528                }
529
530                Ok((remainder, Arc::new(Reference::Four(one, two, three, four))))
531            } else {
532                Ok((input, Arc::new(Reference::Three(one, two, three))))
533            }
534        } else {
535            Ok((input, Arc::new(Reference::Two(one, two))))
536        }
537    } else {
538        Ok((input, Arc::new(Reference::One(one))))
539    }
540}
541
542impl ExpressionLike for Reference {
543    /// Evaluate this reference by looking up its value in the context.
544    ///
545    /// This returns `Result<Value>` so that resolution failures can unwind
546    /// to the owning rule. At the rule/expression level, these errors are
547    /// converted into `Value::Errata` for consistent propagation.
548    /// If `get_referenced` returns a `Value::Errata`, it is passed through
549    /// unchanged, allowing error values to propagate naturally through
550    /// expressions that reference them.
551    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
552        context.get_value(self)
553    }
554
555    /// Return the formula-string representation (round-trippable by the parser).
556    fn to_formula(&self) -> String {
557        let mut writer = Writer::formulizer();
558        self.print(&mut writer);
559        writer.finish()
560    }
561}
562
563impl WriterLike for Reference {
564    fn write(&self, writer: &mut Writer) {
565        self.print(writer);
566    }
567}
568
569impl fmt::Display for Reference {
570    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
571        write!(f, "{}", self.to_stringized())
572    }
573}