1use pflow_tokenmodel::Schema;
4
5use crate::ast::*;
6use crate::interpret;
7use crate::sexpr;
8
9pub struct Builder {
11 node: SchemaNode,
12 current_state: Option<usize>,
13 current_action: Option<usize>,
14 current_arc: Option<usize>,
15}
16
17impl Builder {
18 pub fn new(name: &str) -> Self {
20 Self {
21 node: SchemaNode {
22 name: name.into(),
23 version: "v1.0.0".into(),
24 states: Vec::new(),
25 actions: Vec::new(),
26 arcs: Vec::new(),
27 constraints: Vec::new(),
28 },
29 current_state: None,
30 current_action: None,
31 current_arc: None,
32 }
33 }
34
35 pub fn version(mut self, v: &str) -> Self {
37 self.node.version = v.into();
38 self
39 }
40
41 pub fn data(mut self, id: &str, typ: &str) -> Self {
43 self.clear_current();
44 let idx = self.node.states.len();
45 self.node.states.push(StateNode {
46 id: id.into(),
47 typ: typ.into(),
48 kind: "data".into(),
49 initial: None,
50 exported: false,
51 });
52 self.current_state = Some(idx);
53 self
54 }
55
56 pub fn token(mut self, id: &str, initial: Option<i64>) -> Self {
58 self.clear_current();
59 let idx = self.node.states.len();
60 self.node.states.push(StateNode {
61 id: id.into(),
62 typ: "int".into(),
63 kind: "token".into(),
64 initial: initial.map(InitialValue::Int),
65 exported: false,
66 });
67 self.current_state = Some(idx);
68 self
69 }
70
71 pub fn exported(mut self) -> Self {
73 if let Some(idx) = self.current_state {
74 self.node.states[idx].exported = true;
75 }
76 self
77 }
78
79 pub fn initial(mut self, value: i64) -> Self {
81 if let Some(idx) = self.current_state {
82 self.node.states[idx].initial = Some(InitialValue::Int(value));
83 }
84 self
85 }
86
87 pub fn action(mut self, id: &str) -> Self {
89 self.clear_current();
90 let idx = self.node.actions.len();
91 self.node.actions.push(ActionNode {
92 id: id.into(),
93 guard: String::new(),
94 });
95 self.current_action = Some(idx);
96 self
97 }
98
99 pub fn guard(mut self, expr: &str) -> Self {
101 if let Some(idx) = self.current_action {
102 self.node.actions[idx].guard = expr.into();
103 }
104 self
105 }
106
107 pub fn flow(mut self, source: &str, target: &str) -> Self {
109 self.clear_current();
110 let idx = self.node.arcs.len();
111 self.node.arcs.push(ArcNode {
112 source: source.into(),
113 target: target.into(),
114 keys: Vec::new(),
115 value: String::new(),
116 });
117 self.current_arc = Some(idx);
118 self
119 }
120
121 pub fn arc(self, source: &str, target: &str) -> Self {
123 self.flow(source, target)
124 }
125
126 pub fn keys(mut self, keys: &[&str]) -> Self {
128 if let Some(idx) = self.current_arc {
129 self.node.arcs[idx].keys = keys.iter().map(|s| s.to_string()).collect();
130 }
131 self
132 }
133
134 pub fn value(mut self, v: &str) -> Self {
136 if let Some(idx) = self.current_arc {
137 self.node.arcs[idx].value = v.into();
138 }
139 self
140 }
141
142 pub fn constraint(mut self, id: &str, expr: &str) -> Self {
144 self.clear_current();
145 self.node.constraints.push(ConstraintNode {
146 id: id.into(),
147 expr: expr.into(),
148 });
149 self
150 }
151
152 fn clear_current(&mut self) {
153 self.current_state = None;
154 self.current_action = None;
155 self.current_arc = None;
156 }
157
158 pub fn ast(&self) -> &SchemaNode {
160 &self.node
161 }
162
163 pub fn schema(self) -> Result<Schema, String> {
165 interpret::interpret(&self.node)
166 }
167
168 pub fn must_schema(self) -> Schema {
170 self.schema().expect("schema validation failed")
171 }
172
173 pub fn to_string(&self) -> String {
175 sexpr::to_sexpr(&self.node)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_builder_basic() {
185 let schema = Builder::new("ERC-020")
186 .data("balances", "map[address]uint256")
187 .exported()
188 .data("totalSupply", "uint256")
189 .action("transfer")
190 .guard("balances[from] >= amount")
191 .flow("balances", "transfer")
192 .keys(&["from"])
193 .flow("transfer", "balances")
194 .keys(&["to"])
195 .constraint("conservation", "sum(balances) == totalSupply")
196 .must_schema();
197
198 assert_eq!(schema.name, "ERC-020");
199 assert_eq!(schema.states.len(), 2);
200 assert_eq!(schema.actions.len(), 1);
201 assert_eq!(schema.arcs.len(), 2);
202 assert_eq!(schema.constraints.len(), 1);
203 assert!(schema.states[0].exported);
204 }
205
206 #[test]
207 fn test_builder_token() {
208 let schema = Builder::new("counter")
209 .token("count", Some(5))
210 .action("inc")
211 .flow("inc", "count")
212 .must_schema();
213
214 assert_eq!(schema.states[0].initial_tokens(), 5);
215 assert!(schema.states[0].is_token());
216 }
217
218 #[test]
219 fn test_builder_to_string() {
220 let b = Builder::new("test")
221 .data("balances", "map[address]uint256")
222 .action("transfer")
223 .flow("balances", "transfer")
224 .keys(&["from"]);
225
226 let sexpr = b.to_string();
227 assert!(sexpr.contains("schema test"));
228 assert!(sexpr.contains("balances"));
229 assert!(sexpr.contains("transfer"));
230 }
231}