1use sim_kernel::{Expr, Symbol};
17use sim_value::build::qsym;
18
19use crate::path::is_legal_table_segment;
20
21#[derive(Clone, Debug, PartialEq)]
23pub enum CompareExpected {
24 Absent,
26 Value(Expr),
28}
29
30#[derive(Clone, Debug, PartialEq)]
32pub enum CompareReplacement {
33 Delete,
35 Value(Expr),
37}
38
39#[derive(Clone, Debug, PartialEq)]
41pub enum TableOp {
42 Get(Symbol),
44 Set(Symbol, Expr),
46 CompareExchange(Symbol, CompareExpected, CompareReplacement),
48 Has(Symbol),
50 Delete(Symbol),
52 Keys,
54 Entries,
56 Len,
58 Clear,
60 Mkdir(Symbol),
62 Opendir(Symbol),
64 Rmdir(Symbol),
66 IsDir(Symbol),
68}
69
70#[derive(Clone, Debug, PartialEq)]
72pub enum TableOpError {
73 NotATableCall,
75 UnknownOp(String),
77 BadArity(String),
79 BadArg(String),
81}
82
83fn wire_name(op: &TableOp) -> &'static str {
86 match op {
87 TableOp::Get(_) => "get",
88 TableOp::Set(_, _) => "set",
89 TableOp::CompareExchange(_, _, _) => "cas",
90 TableOp::Has(_) => "has",
91 TableOp::Delete(_) => "del",
92 TableOp::Keys => "keys",
93 TableOp::Entries => "entries",
94 TableOp::Len => "len",
95 TableOp::Clear => "clear",
96 TableOp::Mkdir(_) => "mkdir",
97 TableOp::Opendir(_) => "opendir",
98 TableOp::Rmdir(_) => "rmdir",
99 TableOp::IsDir(_) => "dir?",
100 }
101}
102
103pub fn encode_table_op(op: &TableOp) -> Expr {
105 let args = match op {
106 TableOp::Get(key)
107 | TableOp::Has(key)
108 | TableOp::Delete(key)
109 | TableOp::Mkdir(key)
110 | TableOp::Opendir(key)
111 | TableOp::Rmdir(key)
112 | TableOp::IsDir(key) => vec![Expr::Symbol(key.clone())],
113 TableOp::Set(key, value) => vec![Expr::Symbol(key.clone()), value.clone()],
114 TableOp::CompareExchange(key, expected, replacement) => vec![
115 Expr::Symbol(key.clone()),
116 encode_expected(expected),
117 encode_replacement(replacement),
118 ],
119 TableOp::Keys | TableOp::Entries | TableOp::Len | TableOp::Clear => Vec::new(),
120 };
121 Expr::Call {
122 operator: Box::new(qsym("table", wire_name(op))),
123 args,
124 }
125}
126
127fn tagged(name: &str, args: Vec<Expr>) -> Expr {
128 Expr::Call {
129 operator: Box::new(qsym("table", name)),
130 args,
131 }
132}
133
134fn encode_expected(expected: &CompareExpected) -> Expr {
135 match expected {
136 CompareExpected::Absent => tagged("absent", Vec::new()),
137 CompareExpected::Value(value) => tagged("value", vec![value.clone()]),
138 }
139}
140
141fn encode_replacement(replacement: &CompareReplacement) -> Expr {
142 match replacement {
143 CompareReplacement::Delete => tagged("delete", Vec::new()),
144 CompareReplacement::Value(value) => tagged("value", vec![value.clone()]),
145 }
146}
147
148fn decode_tag(expr: &Expr, expected: bool) -> Result<Option<Expr>, TableOpError> {
149 let Expr::Call { operator, args } = expr else {
150 return Err(TableOpError::BadArg("cas".into()));
151 };
152 let Expr::Symbol(symbol) = operator.as_ref() else {
153 return Err(TableOpError::BadArg("cas".into()));
154 };
155 if symbol.namespace.as_deref() != Some("table") {
156 return Err(TableOpError::BadArg("cas".into()));
157 }
158 match (expected, symbol.name.as_ref(), args.as_slice()) {
159 (true, "absent", []) | (false, "delete", []) => Ok(None),
160 (_, "value", [value]) => Ok(Some(value.clone())),
161 _ => Err(TableOpError::BadArg("cas".into())),
162 }
163}
164
165fn one_key(op: &str, args: &[Expr]) -> Result<Symbol, TableOpError> {
167 match args {
168 [Expr::Symbol(key)] => Ok(key.clone()),
169 [_] => Err(TableOpError::BadArg(op.to_owned())),
170 _ => Err(TableOpError::BadArity(op.to_owned())),
171 }
172}
173
174fn one_dir_segment(op: &str, args: &[Expr]) -> Result<Symbol, TableOpError> {
177 let key = one_key(op, args)?;
178 if key.namespace.is_none() && is_legal_table_segment(key.name.as_ref()) {
179 Ok(key)
180 } else {
181 Err(TableOpError::BadArg(op.to_owned()))
182 }
183}
184
185fn no_args(op: &str, args: &[Expr]) -> Result<(), TableOpError> {
187 if args.is_empty() {
188 Ok(())
189 } else {
190 Err(TableOpError::BadArity(op.to_owned()))
191 }
192}
193
194pub fn decode_table_op(expr: &Expr) -> Result<TableOp, TableOpError> {
196 let Expr::Call { operator, args } = expr else {
197 return Err(TableOpError::NotATableCall);
198 };
199 let Expr::Symbol(symbol) = operator.as_ref() else {
200 return Err(TableOpError::NotATableCall);
201 };
202 if symbol.namespace.as_deref() != Some("table") {
203 return Err(TableOpError::NotATableCall);
204 }
205 let name = symbol.name.as_ref();
206 let op = match name {
207 "get" => TableOp::Get(one_key(name, args)?),
208 "set" => match args.as_slice() {
209 [Expr::Symbol(key), value] => TableOp::Set(key.clone(), value.clone()),
210 [_, _] => return Err(TableOpError::BadArg(name.to_owned())),
211 _ => return Err(TableOpError::BadArity(name.to_owned())),
212 },
213 "cas" => match args.as_slice() {
214 [Expr::Symbol(key), expected, replacement] => {
215 let expected = match decode_tag(expected, true)? {
216 None => CompareExpected::Absent,
217 Some(value) => CompareExpected::Value(value),
218 };
219 let replacement = match decode_tag(replacement, false)? {
220 None => CompareReplacement::Delete,
221 Some(value) => CompareReplacement::Value(value),
222 };
223 TableOp::CompareExchange(key.clone(), expected, replacement)
224 }
225 [_, _, _] => return Err(TableOpError::BadArg(name.to_owned())),
226 _ => return Err(TableOpError::BadArity(name.to_owned())),
227 },
228 "has" => TableOp::Has(one_key(name, args)?),
229 "del" => TableOp::Delete(one_key(name, args)?),
230 "keys" => {
231 no_args(name, args)?;
232 TableOp::Keys
233 }
234 "entries" => {
235 no_args(name, args)?;
236 TableOp::Entries
237 }
238 "len" => {
239 no_args(name, args)?;
240 TableOp::Len
241 }
242 "clear" => {
243 no_args(name, args)?;
244 TableOp::Clear
245 }
246 "mkdir" => TableOp::Mkdir(one_dir_segment(name, args)?),
247 "opendir" => TableOp::Opendir(one_dir_segment(name, args)?),
248 "rmdir" => TableOp::Rmdir(one_dir_segment(name, args)?),
249 "dir?" => TableOp::IsDir(one_dir_segment(name, args)?),
250 other => return Err(TableOpError::UnknownOp(other.to_owned())),
251 };
252 Ok(op)
253}