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#[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}