1use scratch_test_model_proc::Block;
2use svalue::SValue;
3
4use crate::{
5 _exports::{BlockKindError, ParseJsonBlock},
6 Id,
7 attrs::{
8 AttributeContentError, AttributeParseError, DoForAttrs, DoForAttrsStrategy, Expression,
9 ParseJsonBlockAttribute, ProcedureArgumentId, RefBlock,
10 },
11 aux::errors::BlockJsonStructureError,
12 blocks::{ExprOrCmpBlockKind, StmtBlockKind},
13};
14
15pub(super) fn procedures_call<'a, 'b>(
16 all_target_blocks: crate::_exports::JsonBlocks<'a>,
17 _opcode: &str,
18 inputs: &'b crate::_exports::BlockProperties<'a>,
19 _fields: &'b crate::_exports::BlockProperties<'a>,
20 mutation: Option<&'b crate::_exports::BlockProperties<'a>>,
21) -> Result<StmtBlockKind, crate::_exports::BlockKindError<'a>>
22where
23 StmtBlockKind: Sized,
24{
25 let mutation = force_mutation(mutation)?;
26 let arguments = inputs
27 .data()
28 .iter()
29 .map(|(id, attribute_value)| {
30 Option::<Expression>::parse_json_block_attr_with_possible_shadow(
31 all_target_blocks,
32 attribute_value,
33 )
34 .map(|value| (ProcedureArgumentId(id.to_string().into()), value))
35 })
36 .collect::<Result<_, _>>()
37 .map_err(|attr_err| BlockKindError::AttrParsing {
38 location: crate::aux::AttrLocation::Inputs,
39 key: "some-with-id-identified-parameter-field",
40 error: attr_err,
41 })?;
42
43 let proccode =
44 mutation
45 .force_static_attr("proccode")?
46 .as_str()
47 .ok_or(BlockKindError::AttrParsing {
48 location: crate::aux::AttrLocation::Mutation,
49 key: "proccode",
50 error: AttributeParseError::Content {
51 shadow_resolved: false,
52 error: crate::attrs::AttributeContentError::Other(
53 "expected string for proccode",
54 ),
55 },
56 })?;
57
58 let ids_data: Option<serde_json::Value> = mutation
59 .force_static_attr("argumentids")?
60 .as_str()
61 .and_then(|val| serde_json::from_str(val).ok());
62
63 let argument_ids = ids_data
64 .as_ref()
65 .and_then(|a| a.as_array())
66 .and_then(|v| v.iter().map(|x| x.as_str()).collect::<Option<Vec<&str>>>())
67 .ok_or(BlockKindError::AttrParsing {
68 location: crate::aux::AttrLocation::Mutation,
69 key: "argumentids",
70 error: AttributeParseError::Content {
71 shadow_resolved: false,
72 error: crate::attrs::AttributeContentError::Other(
73 "expected stringified json array of strings for argumentids",
74 ),
75 },
76 })?;
77 let procedure_id = ProcedureId::generate_from(proccode, argument_ids.into_iter());
78
79 Ok(StmtBlockKind::ProceduresCall {
80 arguments,
81 procedure_id,
82 warp: mutation
83 .data()
84 .get("warp")
85 .and_then(|v| v.as_bool())
86 .unwrap_or_default(),
87 })
88}
89
90#[derive(Debug, PartialEq, Block, Clone)]
91pub enum ProceduresDefinition {
92 #[block(opcode = "procedures_definition")]
93 ProceduresDefinition {
94 #[block(location = inputs, json_name = "custom_block")]
95 prototype: RefBlock<ProceduresPrototype>,
96 },
97}
98impl ProceduresDefinition {
99 pub(crate) fn prototype(&self) -> &RefBlock<ProceduresPrototype> {
100 let ProceduresDefinition::ProceduresDefinition { prototype } = self;
101 prototype
102 }
103}
104
105#[derive(Debug, derive_getters::Getters, PartialEq, Clone)]
106pub struct ProcedureArgumentDef {
107 pub(crate) name: String,
108 pub(crate) argument_id: ProcedureArgumentId,
109 pub(crate) reporter_id_in_this_block: RefBlock<ExprOrCmpBlockKind>,
110 pub(crate) default_value: svalue::SValue,
111}
112
113#[derive(Debug, PartialEq, Clone, derive_getters::Getters)]
114pub struct ProceduresPrototype {
115 proccode: svalue::ARc<str>,
116 procedure_id: ProcedureId,
117 warp: bool,
118 arguments: svalue::ARc<[ProcedureArgumentDef]>,
119}
120
121#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, derive_getters::Getters, Clone)]
122pub struct ProcedureId {
123 proccode: svalue::ARc<str>,
124 procedure_id: svalue::ARc<str>,
125}
126
127impl ProcedureId {
128 pub fn generate_from<'a>(
129 proccode: &'a str,
130 argument_ids: impl Iterator<Item = &'a str>,
131 ) -> Self {
132 use itertools::Itertools;
133 Self {
134 procedure_id: std::iter::once(proccode)
135 .chain(argument_ids.sorted())
136 .join("|")
137 .into(),
138 proccode: proccode.into(),
139 }
140 }
141 pub fn generate_from_fields(
142 proccode: &str,
143 arguments: &svalue::ARc<[(Id, Option<Expression>)]>,
144 ) -> Self {
145 Self::generate_from(proccode, arguments.iter().map(|(a, _)| a.as_ref()))
146 }
147}
148
149impl ParseJsonBlock for ProceduresPrototype {
150 fn parse_json_block<'a, 'b>(
151 all_target_blocks: crate::aux::JsonBlocks<'a>,
152 opcode: &str,
153 inputs: &'b crate::_exports::BlockProperties<'a>,
154 _fields: &'b crate::_exports::BlockProperties<'a>,
155 mutation: Option<&'b crate::_exports::BlockProperties<'a>>,
156 ) -> Result<Self, crate::_exports::BlockKindError<'a>>
157 where
158 Self: Sized,
159 {
160 if opcode != "procedures_prototype" {
161 return Err(BlockKindError::UnknownOpcode("procedures_prototype".into()));
162 }
163 fn content_err<'b>(
164 key: &'static str,
165 err: AttributeContentError<'b>,
166 ) -> crate::_exports::BlockKindError<'b> {
167 BlockKindError::AttrParsing {
168 location: crate::aux::AttrLocation::Mutation,
169 key,
170 error: AttributeParseError::Content {
171 shadow_resolved: false,
172 error: err,
173 },
174 }
175 }
176 let other_err = |key, msg| content_err(key, AttributeContentError::Other(msg));
177 let json_err = |key| other_err(key, "expected stringified json array of values");
178 let mutation = force_mutation(mutation)?;
179
180 macro_rules! parse_array {
181 ($key: ident, svalues($helper: ident)) => {
182 let $helper = mutation
183 .force_static_attr(stringify! {$key})?
184 .as_str()
185 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok());
186 let $key = {
187 let x = $helper
188 .as_ref()
189 .and_then(|s| s.as_array())
190 .ok_or_else(|| json_err(stringify! {$key}))?;
191 x.into_iter()
192 .map(|entry| {
193 svalue::SValue::try_from(entry.clone()).map_err(|_| {
194 BlockKindError::AttrParsing {
195 location: crate::aux::AttrLocation::Mutation,
196 key: stringify! {key},
197 error: AttributeParseError::Content {
198 shadow_resolved: false,
199 error: AttributeContentError::Other(
200 "failed to parse array entry as SValue",
201 ),
202 },
203 }
204 })
205 })
206 .collect::<Result<Vec<_>, _>>()?
207 };
208 };
209 ($key: ident, strings ($helper: ident)) => {
210 let $helper = mutation
211 .force_static_attr(stringify! {$key})?
212 .as_str()
213 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok());
214 let $key = $helper
215 .as_ref()
216 .and_then(|s| s.as_array())
217 .and_then(|s| s.iter().map(|x| x.as_str()).collect::<Option<Vec<_>>>())
218 .ok_or_else(|| {
219 other_err(
220 stringify! {$key},
221 "expected stringified json array of strings",
222 )
223 })?;
224 };
225 }
226
227 parse_array!(argumentdefaults, svalues(_d));
228 parse_array!(argumentids, strings(_i));
229 parse_array!(argumentnames, strings(_n));
230 let proccode = mutation.force_static_attr("proccode")?.as_str().ok_or(
231 BlockKindError::AttrParsing {
232 location: crate::aux::AttrLocation::Mutation,
233 key: "proccode",
234 error: AttributeParseError::Content {
235 shadow_resolved: false,
236 error: AttributeContentError::Other("proccode needs to be string"),
237 },
238 },
239 )?;
240 let warp = mutation
241 .force_static_attr("warp")
242 .ok()
243 .and_then(|s| s.as_bool())
244 .unwrap_or_default();
245
246 let procedure_id = ProcedureId::generate_from(proccode, argumentids.iter().cloned());
247
248 let reporter_blocks = argumentids
249 .iter()
250 .map(|id| {
251 RefBlock::parse_json_block_attr_with_possible_shadow(
252 all_target_blocks,
253 inputs.data().get(*id).unwrap(),
254 )
255 })
256 .collect::<Result<Vec<RefBlock<ExprOrCmpBlockKind>>, _>>()
257 .map_err(|error| BlockKindError::AttrParsing {
258 location: crate::aux::AttrLocation::Inputs,
259 key: "some-with-id-identified-parameter-field",
260 error,
261 })?;
262
263 if argumentids.len() != argumentnames.len() {
264 return Err(BlockKindError::AttrParsing {
265 location: crate::aux::AttrLocation::Mutation,
266 key: "combination-of-procedure-attributes",
267 error: AttributeParseError::Content {
268 shadow_resolved: false,
269 error: AttributeContentError::Other(
270 "procedure components are of different length",
271 ),
272 },
273 });
274 }
275
276 let arguments: svalue::ARc<[_]> = itertools::multizip((
277 argumentids.into_iter(),
278 argumentnames.into_iter(),
279 argumentdefaults
280 .into_iter()
281 .chain(std::iter::repeat(SValue::Text("".into()))),
282 reporter_blocks.into_iter(),
283 ))
284 .map(|(id, name, default, reporter)| ProcedureArgumentDef {
285 name: (*name).into(),
286 reporter_id_in_this_block: reporter,
287 default_value: default,
288 argument_id: ProcedureArgumentId(id.into()),
289 })
290 .collect();
291
292 Ok(Self {
293 proccode: proccode.into(),
294 warp,
295 procedure_id,
296 arguments,
297 })
298 }
299}
300
301fn force_mutation<'a, 'b>(
302 mutation: Option<&'b crate::_exports::BlockProperties<'a>>,
303) -> Result<&'b crate::_exports::BlockProperties<'a>, BlockJsonStructureError> {
304 mutation.ok_or(BlockJsonStructureError::MissingMandatoryAttr("mutation"))
305}
306
307impl<'a, S: DoForAttrsStrategy<'a>> DoForAttrs<'a, S> for ProceduresPrototype
308where
309 bool: DoForAttrs<'a, S>,
310 svalue::ARc<str>: DoForAttrs<'a, S>,
311 ProcedureId: DoForAttrs<'a, S>,
312 svalue::ARc<[ProcedureArgumentDef]>: DoForAttrs<'a, S>,
313{
314 fn do_for_attrs(
315 &'a self,
316 inputs: &<S as DoForAttrsStrategy<'a>>::Inputs,
317 outputs: &mut <S as DoForAttrsStrategy<'a>>::Outputs,
318 ) -> Result<(), <S as DoForAttrsStrategy<'a>>::Error> {
319 self.proccode.do_for_attrs(inputs, outputs)?;
320 self.procedure_id.do_for_attrs(inputs, outputs)?;
321 self.warp.do_for_attrs(inputs, outputs)?;
322 self.arguments.do_for_attrs(inputs, outputs)?;
323
324 Ok(())
325 }
326}