kure2_cli/
commands.rs

1use std::{collections::BTreeMap, fs, io, ops};
2
3use kure2::{
4    fmt::{parse_matrix, parse_relation},
5    lang::State,
6};
7
8const HELP_MESSAGE: &str = "Available commands:\n\
9   .help - Show this help message\n\
10   .exit - Exit the REPL\n\
11   .load prog <filename> - Load a program from a file\n\
12   .load rel <filename> - Load a relation from a file\n\
13   .load mat <filename> - Load a matrix from a file\n\
14   .save rel <variable> <filename> - Save a relation to a file\n\
15   .save mat <variable> <filename> - Save a matrix to a file";
16
17/// The available commands form a rooted tree where the tokens are represented by edges.
18#[derive(Default)]
19pub struct Node {
20    pub edges: BTreeMap<Edge, Node>,
21    pub func: Option<
22        Box<dyn Fn(&mut State, &mut dyn io::Write, &[&str]) -> io::Result<ops::ControlFlow<()>>>,
23    >,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
27pub enum Edge {
28    Keyword(&'static str),
29    Filename,
30    Variable,
31}
32
33impl Node {
34    pub fn root() -> Self {
35        let mut root = Node::default();
36
37        root.insert(Edge::Keyword(".help"))
38            .with_func(|_state, out, [_]| {
39                writeln!(out, "{HELP_MESSAGE}")?;
40                Ok(ops::ControlFlow::Continue(()))
41            });
42        root.insert(Edge::Keyword(".exit"))
43            .with_func(|_state, _out, [_]| Ok(ops::ControlFlow::Break(())));
44
45        let load = root.insert(Edge::Keyword(".load"));
46        load.insert(Edge::Keyword("prog"))
47            .insert(Edge::Filename)
48            .with_func(|state, out, [_, _, filename]| {
49                match state.load_file(filename) {
50                    Ok(()) => writeln!(out, "Program loaded successfully from '{filename}'")?,
51                    Err(e) => writeln!(out, "Error loading program: {e}")?,
52                }
53                Ok(ops::ControlFlow::Continue(()))
54            });
55        load.insert(Edge::Keyword("rel"))
56            .insert(Edge::Variable)
57            .insert(Edge::Filename)
58            .with_func(|state, out, [_, _, variable, filename]| {
59                let input = fs::read_to_string(filename)?;
60                match parse_relation(&input) {
61                    Ok((name, rel)) => match state.set_relation(variable, &rel) {
62                        Ok(()) => writeln!(
63                            out,
64                            "Relation '{name}' loaded successfully from '{filename}'"
65                        )?,
66                        Err(e) => writeln!(out, "Error assigning variable: {e}")?,
67                    },
68                    Err(e) => writeln!(out, "Error parsing relation from file '{filename}': {e}")?,
69                }
70                Ok(ops::ControlFlow::Continue(()))
71            });
72        load.insert(Edge::Keyword("mat"))
73            .insert(Edge::Variable)
74            .insert(Edge::Filename)
75            .with_func(|state, out, [_, _, variable, filename]| {
76                let input = fs::read_to_string(filename)?;
77                match parse_matrix(&input) {
78                    Ok(rel) => match state.set_relation(variable, &rel) {
79                        Ok(()) => writeln!(
80                            out,
81                            "Matrix '{variable}' loaded successfully from '{filename}'"
82                        )?,
83                        Err(e) => writeln!(out, "Error assigning variable: {e}")?,
84                    },
85                    Err(e) => writeln!(out, "Error parsing matrix from file '{filename}': {e}")?,
86                }
87                Ok(ops::ControlFlow::Continue(()))
88            });
89
90        let save = root.insert(Edge::Keyword(".save"));
91        save.insert(Edge::Keyword("rel"))
92            .insert(Edge::Variable)
93            .insert(Edge::Filename)
94            .with_func(|state, out, [_, _, variable, filename]| {
95                let Some(rel) = state.relation(variable) else {
96                    writeln!(out, "Relation '{variable}' not found")?;
97                    return Ok(ops::ControlFlow::Continue(()));
98                };
99                fs::write(filename, rel.display(variable).to_string())?;
100                writeln!(
101                    out,
102                    "Relation '{variable}' saved successfully to '{filename}'"
103                )?;
104                Ok(ops::ControlFlow::Continue(()))
105            });
106        save.insert(Edge::Keyword("mat"))
107            .insert(Edge::Variable)
108            .insert(Edge::Filename)
109            .with_func(|state, out, [_, _, variable, filename]| {
110                let Some(rel) = state.relation(variable) else {
111                    writeln!(out, "Relation '{variable}' not found")?;
112                    return Ok(ops::ControlFlow::Continue(()));
113                };
114                fs::write(filename, rel.display_matrix().to_string())?;
115                writeln!(
116                    out,
117                    "Matrix '{variable}' saved successfully to '{filename}'"
118                )?;
119                Ok(ops::ControlFlow::Continue(()))
120            });
121
122        root
123    }
124
125    fn insert(&mut self, edge: Edge) -> &mut Self {
126        self.edges.entry(edge).or_default()
127    }
128
129    fn with_func<const N: usize>(
130        &mut self,
131        func: impl Fn(&mut State, &mut dyn io::Write, [&str; N]) -> io::Result<ops::ControlFlow<()>>
132        + 'static,
133    ) {
134        self.func = Some(Box::new(move |state, out, args| {
135            func(state, out, args.try_into().unwrap())
136        }));
137    }
138
139    pub fn traverse(&self, args: &[&str]) -> Option<&Self> {
140        let mut node = self;
141        for arg in args {
142            if let Some(edge) = node.edges.keys().find(|e| {
143                matches!(e, Edge::Keyword(k) if k.starts_with(arg))
144                    || matches!(e, Edge::Variable | Edge::Filename)
145            }) {
146                node = &node.edges[edge];
147            } else {
148                return None;
149            }
150        }
151        Some(node)
152    }
153
154    pub fn next_keywords_by_prefix(&self, prefix: &str) -> impl Iterator<Item = &str> {
155        self.edges.keys().filter_map(move |edge| {
156            if let &Edge::Keyword(keyword) = edge {
157                if keyword.starts_with(prefix) {
158                    Some(keyword)
159                } else {
160                    None
161                }
162            } else {
163                None
164            }
165        })
166    }
167}