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 TableOp {
24 Get(Symbol),
26 Set(Symbol, Expr),
28 Has(Symbol),
30 Delete(Symbol),
32 Keys,
34 Entries,
36 Len,
38 Clear,
40 Mkdir(Symbol),
42 Opendir(Symbol),
44 Rmdir(Symbol),
46 IsDir(Symbol),
48}
49
50#[derive(Clone, Debug, PartialEq)]
52pub enum TableOpError {
53 NotATableCall,
55 UnknownOp(String),
57 BadArity(String),
59 BadArg(String),
61}
62
63fn wire_name(op: &TableOp) -> &'static str {
66 match op {
67 TableOp::Get(_) => "get",
68 TableOp::Set(_, _) => "set",
69 TableOp::Has(_) => "has",
70 TableOp::Delete(_) => "del",
71 TableOp::Keys => "keys",
72 TableOp::Entries => "entries",
73 TableOp::Len => "len",
74 TableOp::Clear => "clear",
75 TableOp::Mkdir(_) => "mkdir",
76 TableOp::Opendir(_) => "opendir",
77 TableOp::Rmdir(_) => "rmdir",
78 TableOp::IsDir(_) => "dir?",
79 }
80}
81
82pub fn encode_table_op(op: &TableOp) -> Expr {
84 let args = match op {
85 TableOp::Get(key)
86 | TableOp::Has(key)
87 | TableOp::Delete(key)
88 | TableOp::Mkdir(key)
89 | TableOp::Opendir(key)
90 | TableOp::Rmdir(key)
91 | TableOp::IsDir(key) => vec![Expr::Symbol(key.clone())],
92 TableOp::Set(key, value) => vec![Expr::Symbol(key.clone()), value.clone()],
93 TableOp::Keys | TableOp::Entries | TableOp::Len | TableOp::Clear => Vec::new(),
94 };
95 Expr::Call {
96 operator: Box::new(qsym("table", wire_name(op))),
97 args,
98 }
99}
100
101fn one_key(op: &str, args: &[Expr]) -> Result<Symbol, TableOpError> {
103 match args {
104 [Expr::Symbol(key)] => Ok(key.clone()),
105 [_] => Err(TableOpError::BadArg(op.to_owned())),
106 _ => Err(TableOpError::BadArity(op.to_owned())),
107 }
108}
109
110fn one_dir_segment(op: &str, args: &[Expr]) -> Result<Symbol, TableOpError> {
113 let key = one_key(op, args)?;
114 if key.namespace.is_none() && is_legal_table_segment(key.name.as_ref()) {
115 Ok(key)
116 } else {
117 Err(TableOpError::BadArg(op.to_owned()))
118 }
119}
120
121fn no_args(op: &str, args: &[Expr]) -> Result<(), TableOpError> {
123 if args.is_empty() {
124 Ok(())
125 } else {
126 Err(TableOpError::BadArity(op.to_owned()))
127 }
128}
129
130pub fn decode_table_op(expr: &Expr) -> Result<TableOp, TableOpError> {
132 let Expr::Call { operator, args } = expr else {
133 return Err(TableOpError::NotATableCall);
134 };
135 let Expr::Symbol(symbol) = operator.as_ref() else {
136 return Err(TableOpError::NotATableCall);
137 };
138 if symbol.namespace.as_deref() != Some("table") {
139 return Err(TableOpError::NotATableCall);
140 }
141 let name = symbol.name.as_ref();
142 let op = match name {
143 "get" => TableOp::Get(one_key(name, args)?),
144 "set" => match args.as_slice() {
145 [Expr::Symbol(key), value] => TableOp::Set(key.clone(), value.clone()),
146 [_, _] => return Err(TableOpError::BadArg(name.to_owned())),
147 _ => return Err(TableOpError::BadArity(name.to_owned())),
148 },
149 "has" => TableOp::Has(one_key(name, args)?),
150 "del" => TableOp::Delete(one_key(name, args)?),
151 "keys" => {
152 no_args(name, args)?;
153 TableOp::Keys
154 }
155 "entries" => {
156 no_args(name, args)?;
157 TableOp::Entries
158 }
159 "len" => {
160 no_args(name, args)?;
161 TableOp::Len
162 }
163 "clear" => {
164 no_args(name, args)?;
165 TableOp::Clear
166 }
167 "mkdir" => TableOp::Mkdir(one_dir_segment(name, args)?),
168 "opendir" => TableOp::Opendir(one_dir_segment(name, args)?),
169 "rmdir" => TableOp::Rmdir(one_dir_segment(name, args)?),
170 "dir?" => TableOp::IsDir(one_dir_segment(name, args)?),
171 other => return Err(TableOpError::UnknownOp(other.to_owned())),
172 };
173 Ok(op)
174}