Skip to main content

scratch_test_model/blocks/
procedures.rs

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}