a2x/ast/
function.rs

1//  SPDX-FileCopyrightText: 2025 Greg Heartsfield <scsibug@imap.cc>
2//  SPDX-License-Identifier: GPL-3.0-or-later
3
4use super::AsAlfa;
5use super::PrettyPrint;
6use super::QualifiedName;
7use std::fmt;
8
9#[derive(Debug, PartialEq, Clone)]
10pub enum FunctionInputArg {
11    /// A reference to a specific atomic type (`string`, `inetAddress`, etc.)
12    Atomic(String),
13    /// A reference to a bag of a specific atomic type (`bag[bool]`)
14    AtomicBag(String),
15    /// A reference to a bag of any atomic type (`bag[anyAtomic]`)
16    AnyAtomicBag,
17    /// A placeholder for any atomic type (`anyAtomic`)
18    AnyAtomic,
19    /// A placeholder for any atomic or bag
20    AnyAtomicOrBag,
21    /// A placeholder for a function
22    Function,
23}
24
25/// Possible function outputs.
26///
27/// Notably, functions cannot produce other functions, or an output
28/// that may be either atomic or bag (`AnyAtomicOrBag`).
29#[derive(Debug, PartialEq, Clone)]
30pub enum FunctionOutputArg {
31    /// A reference to a specific atomic type (string, inetAddress, etc.)
32    Atomic(String),
33    /// A reference to a bag of a specific atomic type (`bag[bool]`)
34    AtomicBag(String),
35    /// A reference to a bag of any atomic type (`bag[anyAtomic]`)
36    AnyAtomicBag,
37    /// A placeholder for any atomic type (`anyAtomic`)
38    AnyAtomic,
39}
40
41/// A collection of inputs to a function definition.
42#[derive(Debug, PartialEq, Clone)]
43pub struct FunctionInputs {
44    /// All the input arguments in order
45    pub args: Vec<FunctionInputArg>,
46    /// Is the last input argument a wildcard?
47    pub wildcard: bool,
48}
49
50/// A function declaration
51#[derive(Debug, Clone)]
52pub struct Function {
53    pub id: String,
54    /// The namespace from general to most specific
55    pub ns: Vec<String>,
56    /// The URN of the function
57    pub function_uri: String,
58    /// Input arguments
59    pub input_args: FunctionInputs,
60    /// Output arguments
61    pub output_arg: FunctionOutputArg,
62}
63
64impl AsAlfa for Function {
65    fn to_alfa(&self, indent_level: usize) -> String {
66        let indent = "  ".repeat(indent_level);
67        // Ex: function stringEqual = "urn:oasis:...:string-equal"
68        //         : string string -> boolean
69        let mut output = format!(
70            "{}function {} = \"{}\" : ",
71            indent, self.id, self.function_uri
72        );
73        for a in &self.input_args.args {
74            output.push_str(&a.to_string());
75            output.push(' ');
76        }
77        if self.input_args.wildcard {
78            output.push_str("* ");
79        }
80        // output
81        output.push_str("-> ");
82        output.push_str(&self.output_arg.to_string());
83        output.push('\n');
84        output
85    }
86}
87
88/// Target equality, ignoring context
89impl PartialEq for Function {
90    fn eq(&self, other: &Self) -> bool {
91        self.id == other.id
92            && self.ns == other.ns
93            && self.function_uri == other.function_uri
94            && self.input_args == other.input_args
95            && self.output_arg == other.output_arg
96    }
97}
98
99/// Generate the fully-qualified name for a function
100impl QualifiedName for Function {
101    fn fully_qualified_name(&self) -> Option<String> {
102        let mut qn = self.ns.join(".");
103        if !self.ns.is_empty() {
104            qn.push('.');
105        }
106        qn.push_str(&self.id);
107        Some(qn.to_string())
108    }
109}
110
111/// Pretty print function
112impl PrettyPrint for Function {
113    fn pretty_print(&self, indent_level: usize) {
114        let indent = "  ".repeat(indent_level);
115        print!("{indent}{self}");
116        println!();
117    }
118}
119
120/// Display the function name, arguments, and output
121impl fmt::Display for Function {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        let mut s: String = String::default();
124        s.push_str(&format!("fn {} <{}> ", self.id, self.function_uri));
125        // print out each arg
126        let args: Vec<String> = self
127            .input_args
128            .args
129            .iter()
130            .map(std::string::ToString::to_string)
131            .collect();
132        s.push('(');
133        s.push_str(&args.join(", "));
134        if self.input_args.wildcard {
135            s.push('*');
136        }
137        s.push(')');
138        // print output type
139        s.push_str(&format!(" ==> {}", self.output_arg));
140        write!(f, "{s}")
141    }
142}
143
144impl fmt::Display for FunctionInputArg {
145    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146        match self {
147            FunctionInputArg::AnyAtomic => write!(f, "anyAtomic"),
148            FunctionInputArg::AnyAtomicBag => write!(f, "bag[anyAtomic]"),
149            FunctionInputArg::AnyAtomicOrBag => write!(f, "anyAtomicOrBag"),
150            FunctionInputArg::Atomic(s) => write!(f, "{s}"),
151            FunctionInputArg::AtomicBag(s) => write!(f, "bag[{s}]"),
152            FunctionInputArg::Function => write!(f, "function"),
153        }
154    }
155}
156
157impl fmt::Display for FunctionOutputArg {
158    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159        match self {
160            FunctionOutputArg::AnyAtomic => write!(f, "anyAtomic"),
161            FunctionOutputArg::AnyAtomicBag => write!(f, "bag[anyAtomic]"),
162            FunctionOutputArg::Atomic(s) => write!(f, "{s}"),
163            FunctionOutputArg::AtomicBag(s) => write!(f, "bag[{s}]"),
164        }
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_serialize() {
174        // ensure that serialization works to ALFA, especially for the
175        // function input args.
176        let args = vec![
177            FunctionInputArg::Atomic("string".to_owned()),
178            FunctionInputArg::AtomicBag("boolean".to_owned()),
179            FunctionInputArg::AnyAtomic,
180            FunctionInputArg::AnyAtomicBag,
181            FunctionInputArg::AnyAtomicOrBag,
182            FunctionInputArg::Function,
183        ];
184        let output_arg = FunctionOutputArg::AtomicBag("string".to_owned());
185        let f = Function {
186            id: "fn_name".to_owned(),
187            ns: vec!["main".to_owned()],
188            function_uri: "urn:oasis:sample".to_owned(),
189            input_args: FunctionInputs {
190                args,
191                wildcard: true,
192            },
193            output_arg,
194        };
195        let a = f.to_alfa(1);
196        assert_eq!(
197            a,
198            "  function fn_name = \"urn:oasis:sample\" : string bag[boolean] anyAtomic bag[anyAtomic] anyAtomicOrBag function * -> bag[string]\n"
199        );
200    }
201}