aimx/
rule.rs

1//! Agentic Inference Markup (AIM) Rules
2//! 
3//! This module defines the core Rule structure used in AIM workflows.
4//! A Rule represents a single entry in an AIM structure, consisting of
5//! an identifier, type definition, expression, and computed value.
6//! 
7//! # Rule Structure
8//! 
9//! ```text
10//! rule_name: Type = expression @ value
11//! formatted_name: Format = "Instruction" "Example1" "Example2"
12//! evaluation_name: Eval = 80/100
13//! _assignment: Number = @reference
14//! ```
15//! 
16//! # Categories
17//! 
18//! Rules fall into three categories based on their identifier format:
19//! - **Standard rules**: lowercase identifiers (e.g., `temperature`)
20//! - **Modifier rules**: all-uppercase identifiers (e.g., `UNIT`)
21//! - **Assignment rules**: identifiers starting with underscore (e.g., `_result`)
22
23use anyhow::{Result, anyhow};
24use bitflags::bitflags;
25use std::{
26    fmt,
27};
28use nom::{
29    Finish, IResult,
30    character::complete::{char, multispace0},
31    error::{Error, ErrorKind},
32};
33use crate::{
34    ContextLike,
35    ExpressionLike,
36    statically_evaluate, 
37    Expression,
38    parse_expression,
39    expressions::{parse_identifier, parse_reference},
40    Node,
41    Typedef,
42    parse_typedef,
43    Value,
44    values::{Errata, parse_eval, parse_format},
45    parse_value,
46    Writer,
47};
48
49// ----- FLAGS -----
50
51bitflags! {
52    /// Internal flags that track the state and category of a Rule
53    /// 
54    /// These bitflags are used to efficiently track various properties without
55    /// requiring additional storage space. The flags are determined automatically
56    /// from the rule's identifier and updated when the rule changes.
57    #[derive(Debug, Clone, PartialEq)]
58    struct Flags: usize {
59        /// The rule has been modified and needs saving
60        const IS_TOUCHED    = 0b0001;
61        /// Rule identifier is all uppercase (modifier rule)
62        const IS_MODIFIER   = 0b0010;
63        /// Rule identifier starts with underscore (assignment rule)
64        const IS_ASSIGNMENT = 0b0100;
65        /// Expression has been statically evaluated to constant value
66        const IS_STATIC     = 0b1000;
67    }
68}
69
70/// Check if a string contains only uppercase characters.
71pub fn is_uppercase(s: &str) -> bool {
72    s.chars().all(|c| c.is_uppercase())
73}
74
75/// Check if a string contains only lowercase characters.
76pub fn is_lowercase(s: &str) -> bool {
77    s.chars().all(|c| c.is_lowercase())
78}
79
80/// Test if an identifier should be considered a modifier rule.
81pub fn test_for_modifier(identifier: &str) -> bool {
82    if is_uppercase(identifier) {
83        true
84    } else {
85        false
86    }
87}
88
89/// Test if an identifier should be considered an assignment rule.
90pub fn test_for_assignment(identifier: &str) -> bool {
91    identifier.starts_with('_')
92}
93
94/// Initialize rule flags based on an identifier.
95fn init_flags(identifier: &str) -> Flags {
96    let mut flags = Flags::empty();
97    if test_for_modifier(&identifier) {
98        flags.insert(Flags::IS_MODIFIER);
99    }
100    if test_for_assignment(&identifier) {
101        flags.insert(Flags::IS_ASSIGNMENT);
102    }
103    flags
104}
105
106// ----- RULE -----
107
108/// A single rule in an AIM structure representing a workflow component
109/// 
110/// Rules are the fundamental building blocks of AIM workflows, defining
111/// how values are computed, formatted, evaluated, or assigned. Each rule
112/// consists of:
113/// - An identifier that uniquely names the rule
114/// - A type definition that constrains valid values
115/// - An expression that computes the rule's value
116/// - A current value (computed or assigned)
117/// - Internal flags tracking state and category
118/// 
119/// # Examples
120/// 
121/// ```rust
122/// # use aimx::rule::Rule;
123/// # use aimx::{Typedef, Expression, Value, Literal};
124/// // Create a simple rule
125/// let rule = Rule::new(
126///     "temperature".to_string(),
127///     Typedef::Number,
128///     Expression::Empty,
129///     Value::Literal(Literal::from_number(25.0))
130/// );
131/// ```
132#[derive(Debug, Clone, PartialEq)]
133pub struct Rule {
134    /// The rule's identifier, which uniquely identifies it within an AIM structure
135    identifier: String,
136    /// The type definition that constrains what values this rule can hold
137    typedef: Typedef,
138    /// The expression that defines how the rule's value is computed or assigned
139    expression: Expression,
140    /// The current value of the rule, computed from the expression or set directly
141    value: Value,
142    /// Flags indicating the rule's state and category
143    flags: Flags,
144}
145
146impl Rule {
147    /// Create a new Rule with the given components
148    /// 
149    /// # Parameters
150    /// - `identifier`: The rule's unique identifier
151    /// - `typedef`: The type definition for the rule
152    /// - `expression`: The expression that computes the rule's value
153    /// - `value`: The initial value of the rule
154    /// 
155    /// # Returns
156    /// A new Rule instance with appropriate flags set based on the identifier
157    /// 
158    /// # Notes
159    /// - Automatically performs static evaluation of expressions when possible
160    /// - Sets flags based on identifier format (modifier/assignment)
161    pub fn new(identifier: String, typedef: Typedef, expression: Expression, value: Value) -> Self {
162        let mut flags = init_flags(&identifier);
163        if expression.is_empty() {
164            Self {
165                identifier,
166                typedef,
167                expression,
168                value,
169                flags,
170            }
171        } else {
172            let (exp, val) = match statically_evaluate(&expression) {
173                Ok(value) => {
174                    flags.insert(Flags::IS_STATIC);
175                    flags.insert(Flags::IS_TOUCHED);
176                    (Expression::Empty, value)
177                }
178                _ => (expression, Value::Empty),
179            };            
180            Self {
181                identifier,
182                typedef,
183                expression: exp,
184
185                value: val,
186                flags,
187            }
188        }
189    }
190
191    /// Create a new Rule by parsing operands from a string
192    /// 
193    /// This constructor parses the expression/value portion of a rule definition
194    /// based on the rule's type definition and assignment status.
195    /// 
196    /// # Parameters
197    /// - `input`: The string containing the rule's operands
198    /// - `identifier`: The rule's identifier
199    /// - `typedef`: The rule's type definition
200    /// - `is_assignment`: Whether this is an assignment rule
201    /// 
202    /// # Returns
203    /// A new Rule instance with parsed expression and value
204    /// 
205    /// # Errors
206    /// Returns error expressions for incomplete or malformed input
207    pub fn new_from(input: &str, identifier: &str, typedef: &Typedef, is_assignment: bool) -> Self {
208        match parse_rule_operands(input, typedef, is_assignment) {
209            Ok((_, (expression, value))) => {
210                Self::new(identifier.to_string(), typedef.clone(), expression, value)
211            }
212            Err(nom::Err::Incomplete(_)) => {
213                let expression = Errata::new_reason_expression(
214                    "Incomplete Expression".to_owned(),
215                    input.to_string()
216                );
217                Self::new(identifier.to_string(), typedef.clone(), expression, Value::Empty)
218            }
219            Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
220                let expression = Errata::new_reason_expression_location(
221                    format!("Syntax Error ({})", e),
222                    input.to_string(),
223                    e.input.to_string()
224                );
225                Self::new(identifier.to_string(), typedef.clone(), expression, Value::Empty)
226            }
227        }
228    }
229
230    /// Check if the rule is completely empty (no expression and no value)
231    pub fn is_empty(&self) -> bool {
232        match (&self.expression, &self.value) {
233            (Expression::Empty, Value::Empty) => true,
234            _ => false,
235        }
236    }
237
238    /// Check if the rule contains any errors
239    pub fn has_error(&self) -> bool {
240        self.value.has_error() | self.expression.has_error()
241    }
242
243    /// Check if the rule has a non-empty expression
244    pub fn has_expression(&self) -> bool {
245        match &self.expression {
246            Expression::Empty
247            | Expression::Errata {..} => false,
248            _ => true,
249        }
250    }
251
252    /// Get the rule's identifier
253    pub fn identifier(&self) -> &str {
254        self.identifier.as_str()
255    }
256
257    /// Get the uppercase identifier (trimming leading underscore if present)
258    /// 
259    /// Useful for case-insensitive comparisons and display purposes
260    pub fn ucid(&self) -> String {
261        let mut ucid = self.identifier.to_uppercase();
262        // Trim the leading underscore
263        if ucid.starts_with('_') {
264            // Trim first character
265            ucid.remove(0);
266        }
267        ucid
268    }
269
270    /// Get the lowercase identifier (trimming leading underscore if present)
271    /// 
272    /// Useful for case-insensitive comparisons and display purposes
273    pub fn lcid(&self) -> String {
274        let mut ucid = self.identifier.to_lowercase();
275        // Trim the leading underscore
276        if ucid.starts_with('_') {
277            // Trim first character
278            ucid.remove(0);
279        }
280        ucid
281    }
282
283    /// Set a new identifier for the rule
284    /// 
285    /// Updates the identifier and automatically adjusts flags based on the new identifier format
286    pub fn set_identifier(&mut self, identifier: String) {
287        // Updating the identifier potentially changes a lot of flags
288        if self.identifier != identifier {
289            // Update flags
290            if test_for_modifier(&identifier) {
291                self.flags.insert(Flags::IS_MODIFIER);
292            } else {
293                self.flags.remove(Flags::IS_MODIFIER);
294            }
295            if test_for_assignment(&identifier) {
296                self.flags.insert(Flags::IS_ASSIGNMENT);
297            } else {
298                self.flags.remove(Flags::IS_ASSIGNMENT);
299            }
300            self.identifier = identifier;
301            self.flags.insert(Flags::IS_TOUCHED);
302        }
303    }
304
305    /// Check if this is a modifier rule (all uppercase identifier)
306    pub fn is_modifier(&self) -> bool {
307        self.flags.contains(Flags::IS_MODIFIER)
308    }
309
310    /// Check if this is an assignment rule (identifier starts with underscore)
311    pub fn is_assignment(&self) -> bool {
312        self.flags.contains(Flags::IS_ASSIGNMENT)
313    }
314
315    /// Assign a value to this rule
316    /// 
317    /// # Parameters
318    /// - `value`: The value to assign
319    /// - `context`: The evaluation context for resolving references
320    /// 
321    /// # Returns
322    /// `Ok(())` if assignment was successful, `Err` if type mismatch occurs
323    /// 
324    /// # Notes
325    /// - For assignment rules, sets the referenced value in the context
326    /// - For other rules, directly sets the rule's value
327    pub fn assign(&mut self, value: Value, context: &mut dyn ContextLike) -> Result<()> {
328        // Check the value type matches the rule typedef.
329        if value.is_literal() && value.is_of_type(&self.typedef) {
330            // Only literal types are allowed
331            match &self.value {
332                // The rule value is a reference
333                Value::Assignment(reference) => {
334                    context.set_referenced(reference, value)
335                }
336                // The Rule operand is a literal so assign the value
337                _ => self.set_value(value),
338            }
339        } else {
340            Err(anyhow!("Type mismatch with assignment"))
341        }
342    }
343    
344    /// Get the rule's type definition
345    pub fn typedef(&self) -> &Typedef {
346        &self.typedef
347    }
348
349    /// Set a new type definition for the rule
350    /// 
351    /// Resets the value to empty and marks the rule as touched
352    pub fn set_typedef(&mut self, typedef: Typedef) {
353        if self.typedef != typedef {
354            self.typedef = typedef;
355            self.value = Value::Empty;
356            self.flags.insert(Flags::IS_TOUCHED);
357        }
358    }
359
360    /// Get the rule's expression
361    pub fn expression(&self) -> &Expression {
362        &self.expression
363    }
364
365    /// Set a new expression for the rule
366    /// 
367    /// Marks the rule as touched if the expression differs from the current one
368    pub fn set_expression(&mut self, expression: Expression) {
369        if self.expression.to_formula() != expression.to_formula() {
370            self.expression = expression;
371            self.flags.insert(Flags::IS_TOUCHED);
372        }
373    }
374
375    /// Get the rule's current value
376    pub fn value(&self) -> &Value {
377        &self.value
378    }
379
380    /// Set the rule's value
381    /// 
382    /// # Parameters
383    /// - `value`: The value to set
384    /// 
385    /// # Returns
386    /// `Ok(())` if the value matches the rule's type, `Err` otherwise
387    pub fn set_value(&mut self, value: Value) -> Result<()> {
388        if !self.typedef.is_special() && value.is_of_type(&self.typedef) {
389            self.value = value;
390            Ok(())
391        } else {
392            Err(anyhow!("Type mismatch, expecting {} found {}",
393                self.typedef.as_string(), value.type_as_string()))
394        }
395    }
396
397    /// Check if this is a format rule
398    pub fn is_format(&self) -> bool {
399        self.typedef.is_format() && self.value.is_format()
400    }
401
402    /// Check if this is an evaluation rule
403    pub fn is_eval(&self) -> bool {
404        self.typedef.is_eval() && self.value.is_eval()
405    }
406
407    /// Mark an evaluation rule as passing
408    /// 
409    /// Only affects rules with Eval type definitions
410    pub fn set_pass(&mut self) {
411        if self.is_eval() {
412            match &mut self.value {
413                Value::Eval(eval) => {
414                    eval.set_pass();
415                    self.flags.insert(Flags::IS_TOUCHED);
416                }
417                _ => {}
418            }
419        }
420    }
421
422    /// Mark an evaluation rule as failing
423    /// 
424    /// Only affects rules with Eval type definitions
425    pub fn set_fail(&mut self) {
426        if self.is_eval() {
427            match &mut self.value {
428                Value::Eval(eval) => {
429                    eval.set_fail();
430                    self.flags.insert(Flags::IS_TOUCHED);
431                }
432                _ => {}
433            }
434        }
435    }
436
437    /// Check if this is a node rule
438    pub fn is_node(&self) -> bool {
439        self.typedef.is_node() && self.value.is_node()
440    }
441
442    /// Get the node from this rule if it is a node rule
443    /// 
444    /// # Returns
445    /// `Some(Node)` if this is a node rule with a node value, `None` otherwise
446    pub fn get_node(&self) -> Option<Node> {
447        // Check the Rule is a Node
448        if self.is_node() {
449            match &self.value {
450                // Get the Rule Node
451                Value::Node(node) => Some(node.clone()),
452                _ => None,
453            }
454        } else {
455            None
456        }
457    }
458
459    /// Mark the rule as touched (modified and needing saving)
460    pub fn touch(&mut self) {
461        self.flags.insert(Flags::IS_TOUCHED);
462    }
463
464    /// Internal method for displaying rule content
465    fn print(&self, writer: &mut Writer) {        
466        if self.value.is_empty() {
467            self.expression.write(writer);
468        }
469        else {
470            self.value.write(writer);
471        }
472    }
473
474    /// Save the rule to a writer
475    /// 
476    /// Writes the complete rule definition and clears the touched flag
477    pub fn save(&mut self, writer: &mut Writer) {
478        self.write(writer);
479        writer.write_eol();
480        self.flags.remove(Flags::IS_TOUCHED);
481    }
482
483    /// Perform a partial save of the rule
484    /// 
485    /// Only saves if the rule has been touched, prepending the rule index
486    /// 
487    /// # Parameters
488    /// - `index`: The index of this rule in its parent structure
489    /// - `writer`: The writer to save to
490    pub fn partial_save(&mut self, index: usize, writer: &mut Writer) {
491        if self.flags.contains(Flags::IS_TOUCHED) {
492            // prepend the row index
493            writer.write_unsigned(index as u32);
494            writer.write_char(' ');
495            self.write(writer);
496            writer.write_eol();
497            self.flags.remove(Flags::IS_TOUCHED);
498        }
499    }
500
501}
502
503/// Parse a rule from a string according to the AIM grammar.
504/// 
505/// This function parses a complete rule definition from a string, including its identifier,
506/// optional quick evaluation status, type definition, operand, and optional value.
507/// 
508/// The grammar for a rule is:
509/// `rule = WS?, identifier, WS?, ':', WS?, typedef, WS?, '=', WS?, value, WS?, EOL`
510/// 
511/// # Parameters
512/// - `input`: The string to parse as a rule definition
513/// 
514/// # Returns
515/// `Ok(Rule)` if parsing was successful, or an error with details if parsing failed
516/// 
517/// # Examples
518/// 
519/// ```
520/// # use aimx::rule::parse_rule;
521/// let rule = parse_rule(r#"test_rule: Number = 42"#).unwrap();
522/// assert_eq!(rule.identifier(), "test_rule");
523/// ```
524pub fn parse_rule(input: &str) -> Result<Rule> {
525    match parse_rule_elements(input).finish() {
526        Ok((_, rule)) => Ok(rule),
527        Err(e) => Err(anyhow!("Parse error: {}", e)),
528    }
529}
530
531/// Parse the individual elements of a rule from a string according to AIM grammar.
532fn parse_rule_elements(input: &str) -> IResult<&str, Rule> {
533    // Parse identifier
534    let (input, _) = multispace0(input)?; // WS ?
535    let (input, identifier) = parse_identifier(input)?;
536    let is_assignment = identifier.starts_with('_');
537
538    // Parse colon
539    let (input, _) = multispace0(input)?; // WS ?
540    let (input, _) = char(':')(input)?;
541
542    // Parse type declaration
543    let (input, _) = multispace0(input)?; // WS ?
544    let (input, typedef) = parse_typedef(input)?;
545
546    // Parse equals sign
547    let (input, _) = multispace0(input)?; // WS ?
548    let (input, _) = char('=')(input)?;
549
550    let (input, (expression, value)) = parse_rule_operands(input, &typedef, is_assignment)?;
551
552    let rule = Rule::new(identifier, typedef, expression, value);
553    Ok((input, rule))
554}
555
556/// Parse the expression and value operands of a rule.
557fn parse_rule_operands<'a>(input: &'a str, typedef: &Typedef, is_assignment: bool) 
558    -> IResult<&'a str, (Expression, Value)>
559    {
560
561    // Parse expression @ value
562    let input = input.trim(); // WS ?
563
564    // The parsing strategy depends on the typedef:
565    // - Empty input results in `Expression::Empty`
566    let (input, value) = if input.is_empty() {
567        if typedef.is_format() {
568            // Format should never be empty
569            return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)))
570        } else {
571            (input, Value::Empty)
572        }
573    // - Constant values are from statically evaluated expressions
574    } else if input.starts_with('@') {
575        let (input, _) = char('@')(input)?;
576        let (input, value) = parse_value(input)?;
577        (input, value)
578    // - Format types are parsed as "Instruction" <format> "Example", "Example", "Example"...
579    } else if typedef.is_format() {
580        let (input, value) = parse_format(input)?;
581        (input, value)
582    // - Eval types are parsed as ratio, score, count
583    } else if typedef.is_eval() {
584        let (input, value) = parse_eval(input)?;
585        (input, value)
586    // - Node types are parsed as a "Name" (Human readable)
587    } else if typedef.is_node() {
588        // Empty for now
589        (input, Value::Empty)
590    // - Assignment references are rules with a leading underscore
591    } else if is_assignment {
592        // Parse reference
593        if let Ok((input, reference)) = parse_reference(input) {
594            (input, Value::Assignment(reference))
595        }
596        else {
597            let (input, value) = parse_value(input)?;
598            (input, value)
599        }
600    // - Expression types are parsed as normal
601    } else {
602        // Parse expression
603        let (input, expression) = parse_expression(input)?;
604        let (input, _) = multispace0(input)?; // WS ?
605        let (input, value) = if input.starts_with('@') {
606            let (input, _) = char('@')(input)?;
607            let (input, value) = parse_value(input)?;
608            (input, value)
609        } else {
610            (input, Value::Empty)
611        };
612        return Ok((input, (expression, value)))
613    };
614    Ok((input, (Expression::Empty, value)))
615}
616
617impl ExpressionLike for Rule {
618    /// Evaluate the rule's expression or value in the given context
619    /// 
620    /// If the rule has an expression, evaluates it. Otherwise, evaluates the rule's value.
621    /// 
622    /// # Parameters
623    /// - `context`: The evaluation context
624    /// 
625    /// # Returns
626    /// `Ok(Value)` containing the evaluated result
627    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
628        if self.expression.is_empty() {
629            self.value.evaluate(context)
630        }
631        else {
632            self.expression.evaluate(context)
633        }
634    }
635
636    /// Write the rule's complete definition to a writer
637    /// 
638    /// Outputs the rule in AIM format: `identifier: typedef = expression @ value`
639    fn write(&self, writer: &mut Writer) {
640        writer.write_str(&self.identifier);
641        writer.write_str(": ");
642        self.typedef.write(writer);
643        writer.write_str(" = ");
644        if self.expression.is_empty() {
645            if self.value.is_constant() {
646                 writer.write_str("@ ");
647            }
648            self.value.write(writer);
649        } else {
650            self.expression.write(writer);
651            if self.value.is_constant() {
652                writer.write_str(" @ ");
653                self.value.write(writer);
654            }
655        }
656    }
657
658    /// Get a sanitized string representation of the rule
659    /// 
660    /// Removes sensitive information and produces a safe representation
661    fn to_sanitized(&self) -> String {
662        let mut writer = Writer::sanitizer();
663        self.write(&mut writer);
664        writer.finish()
665    }
666
667    /// Get the rule's formula representation
668    /// 
669    /// Produces a string suitable for formula evaluation
670    fn to_formula(&self) -> String {
671        let mut writer = Writer::formulizer();
672        self.write(&mut writer);
673        writer.finish()
674    }
675}
676
677impl fmt::Display for Rule {
678    /// Display the rule's content (expression or value) for user-facing output
679    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680        let mut writer = Writer::formulizer();
681        self.print(&mut writer);
682        write!(f, "{}", writer.finish())
683    }
684}