netsblox_ast/
ast.rs

1use alloc::rc::Rc;
2use alloc::vec::Vec;
3use alloc::boxed::Box;
4use alloc::borrow::ToOwned;
5use core::{mem, iter, fmt};
6
7use base64::engine::Engine as Base64Engine;
8use base64::DecodeError as Base64Error;
9
10use crate::*;
11use crate::rpcs::*;
12use crate::util::*;
13
14fn base64_decode(content: &str) -> Result<Vec<u8>, Base64Error> {
15    base64::engine::general_purpose::STANDARD.decode(content)
16}
17
18trait BoxExt<T> {
19    fn new_with<F: FnOnce() -> T>(f: F) -> Self;
20    fn try_new_with<E, F: FnOnce() -> Result<T, E>>(f: F) -> Result<Self, E> where Self: Sized;
21}
22impl<T> BoxExt<T> for Box<T> {
23    #[inline(never)]
24    fn new_with<F: FnOnce() -> T>(f: F) -> Self {
25        Box::new(f())
26    }
27    #[inline(never)]
28    fn try_new_with<E, F: FnOnce() -> Result<T, E>>(f: F) -> Result<Self, E> {
29        f().map(Box::new)
30    }
31}
32
33trait VecExt<T> {
34    fn new_with_single<F: FnOnce() -> T>(f: F) -> Self;
35    fn push_with<F: FnOnce() -> T>(&mut self, f: F);
36    fn push_boxed(&mut self, value: Box<T>);
37}
38impl<T> VecExt<T> for Vec<T> {
39    #[inline(never)]
40    fn new_with_single<F: FnOnce() -> T>(f: F) -> Self {
41        vec![f()]
42    }
43    #[inline(never)]
44    fn push_with<F: FnOnce() -> T>(&mut self, f: F) {
45        self.push(f());
46    }
47    #[inline(never)]
48    fn push_boxed(&mut self, value: Box<T>) {
49        self.push(*value);
50    }
51}
52
53// regex equivalent: r"%'([^']*)'"
54struct ParamIter<'a>(iter::Fuse<core::str::CharIndices<'a>>);
55impl<'a> ParamIter<'a> {
56    fn new(src: &'a str) -> Self {
57        Self(src.char_indices().fuse())
58    }
59}
60impl Iterator for ParamIter<'_> {
61    type Item = (usize, usize);
62    fn next(&mut self) -> Option<Self::Item> {
63        while let Some((i, ch)) = self.0.next() {
64            if ch != '%' || self.0.next().map(|x| x.1) != Some('\'') { continue }
65            while let Some((j, ch)) = self.0.next() {
66                if ch == '\'' { return Some((i, j + 1)) }
67            }
68        }
69        None
70    }
71}
72#[test]
73fn test_param_iter() {
74    assert_eq!(ParamIter::new("hello world").collect::<Vec<_>>(), vec![]);
75    assert_eq!(ParamIter::new("hello %'helo' world").collect::<Vec<_>>(), vec![(6, 13)]);
76    assert_eq!(ParamIter::new("hello %'helo'world").collect::<Vec<_>>(), vec![(6, 13)]);
77    assert_eq!(ParamIter::new("hello %'heloworld").collect::<Vec<_>>(), vec![]);
78    assert_eq!(ParamIter::new("hello %'helo' %'world''''").collect::<Vec<_>>(), vec![(6, 13), (14, 22)]);
79}
80
81// regex equivalent: r"%\S*"
82struct ArgIter<'a>(iter::Fuse<core::str::CharIndices<'a>>, usize);
83impl<'a> ArgIter<'a> {
84    fn new(src: &'a str) -> Self {
85        Self(src.char_indices().fuse(), src.len())
86    }
87}
88impl Iterator for ArgIter<'_> {
89    type Item = (usize, usize);
90    fn next(&mut self) -> Option<Self::Item> {
91        while let Some((i, ch)) = self.0.next() {
92            if ch != '%' { continue }
93            while let Some((j, ch)) = self.0.next() {
94                if ch.is_whitespace() { return Some((i, j)) }
95            }
96            return Some((i, self.1));
97        }
98        None
99    }
100}
101#[test]
102fn test_arg_iter() {
103    assert_eq!(ArgIter::new("hello world").collect::<Vec<_>>(), vec![]);
104    assert_eq!(ArgIter::new("hello %world").collect::<Vec<_>>(), vec![(6, 12)]);
105    assert_eq!(ArgIter::new("hello %world ").collect::<Vec<_>>(), vec![(6, 12)]);
106    assert_eq!(ArgIter::new("hello %world      %gjherg3495830_ ").collect::<Vec<_>>(), vec![(6, 12), (18, 33)]);
107}
108
109struct InlineListIter<'a>(iter::Peekable<iter::Fuse<core::str::Chars<'a>>>);
110impl<'a> InlineListIter<'a> {
111    fn new(s: &'a str) -> Self {
112        Self(s.chars().fuse().peekable())
113    }
114}
115impl<'a> Iterator for InlineListIter<'a> {
116    type Item = CompactString;
117    fn next(&mut self) -> Option<Self::Item> {
118        let mut res = CompactString::default();
119        let mut in_quote = false;
120        while let Some(ch) = self.0.next() {
121            if ch == '"' {
122                if !in_quote {
123                    in_quote = true;
124                    continue;
125                }
126
127                if let Some('"') = self.0.peek() {
128                    res.push(self.0.next().unwrap());
129                } else {
130                    in_quote = false;
131                }
132            }
133            else if ch == ',' && !in_quote {
134                return Some(res.into());
135            } else {
136                res.push(ch);
137            }
138        }
139        if !res.is_empty() { Some(res.into()) } else { None }
140    }
141}
142#[test]
143fn test_inline_list_iter() {
144    assert_eq!(InlineListIter::new(r#""#).collect::<Vec<_>>(), &[] as &[&str]);
145    assert_eq!(InlineListIter::new(r#"1"#).collect::<Vec<_>>(), &["1"]);
146    assert_eq!(InlineListIter::new(r#"1,2"#).collect::<Vec<_>>(), &["1", "2"]);
147    assert_eq!(InlineListIter::new(r#"1,2,test,53"#).collect::<Vec<_>>(), &["1", "2", "test", "53"]);
148    assert_eq!(InlineListIter::new(r#""""test","test""","""test""","""","""""","test""test""#).collect::<Vec<_>>(), &["\"test", "test\"", "\"test\"", "\"", "\"\"", "test\"test"]);
149    assert_eq!(InlineListIter::new(r#",,",",",,",""",",",""",""",""",","","",""#).collect::<Vec<_>>(), &["", "", ",", ",,", "\",", ",\"", "\",\"", ",\",\","]);
150}
151
152#[inline(never)]
153fn clean_newlines(s: &str) -> CompactString {
154    let mut res = alloc::string::String::with_capacity(s.len());
155    let mut chars = s.chars().peekable();
156    loop {
157        match chars.next() {
158            Some('\r') => {
159                res.push('\n');
160                if chars.peek().copied() == Some('\n') { chars.next(); }
161            }
162            Some('\n') => res.push('\n'),
163            Some(x) => res.push(x),
164            None => break,
165        }
166    }
167    res.into()
168}
169#[test]
170fn test_clean_newlines() {
171    assert_eq!(clean_newlines("hello world"), "hello world");
172    assert_eq!(clean_newlines("hello\nworld"), "hello\nworld");
173    assert_eq!(clean_newlines("hello\rworld"), "hello\nworld");
174    assert_eq!(clean_newlines("hello\r\nworld"), "hello\nworld");
175    assert_eq!(clean_newlines("hello\r\n\nworld"), "hello\n\nworld");
176    assert_eq!(clean_newlines("hello\r\n\n\rworld"), "hello\n\n\nworld");
177    assert_eq!(clean_newlines("hello\r\n\n\rworld\n"), "hello\n\n\nworld\n");
178    assert_eq!(clean_newlines("hello\r\n\n\rworld\r"), "hello\n\n\nworld\n");
179    assert_eq!(clean_newlines("hello\r\n\n\rworld\r\n"), "hello\n\n\nworld\n");
180    assert_eq!(clean_newlines("hello,\"one\rtwo\rthree\"\rworld,test,\"one\rtwo\r\"\ragain,\"\rtwo\",\"\rtwo\r\""), "hello,\"one\ntwo\nthree\"\nworld,test,\"one\ntwo\n\"\nagain,\"\ntwo\",\"\ntwo\n\"");
181}
182
183#[inline(never)]
184fn get_collab_id(block: &Xml) -> Option<&str> {
185    block.attr("collabId").map(|x| x.value.as_str()).filter(|x| !x.is_empty())
186}
187
188#[derive(Debug)]
189struct XmlAttr {
190    name: CompactString,
191    value: CompactString,
192}
193#[derive(Debug)]
194struct Xml {
195    name: CompactString,
196    text: CompactString,
197    attrs: Vec<XmlAttr>,
198    children: Vec<Xml>,
199}
200impl Xml {
201    fn get(&self, path: &[&str]) -> Option<&Xml> {
202        match path {
203            [] => Some(self),
204            [first, rest @ ..] => self.children.iter().find(|x| x.name == *first).map(|x| x.get(rest)).flatten(),
205        }
206    }
207    fn attr(&self, name: &str) -> Option<&XmlAttr> {
208        self.attrs.iter().find(|a| a.name == name)
209    }
210}
211fn parse_xml_root<'a>(xml: &mut xmlparser::Tokenizer<'a>, root_name: &'a str) -> Result<Xml, XmlError> {
212    let mut stack = vec![Xml { name: root_name.into(), text: CompactString::default(), attrs: vec![], children: vec![] }];
213    loop {
214        match xml.next() {
215            Some(e) => match e {
216                Err(e) => return Err(XmlError::Read { error: e }),
217                Ok(e) => match e {
218                    xmlparser::Token::Attribute { local, value, .. } => stack.last_mut().unwrap().attrs.push(XmlAttr { name: xml_unescape(local.as_str())?, value: xml_unescape(value.as_str())? }),
219                    xmlparser::Token::Text { text: t } => stack.last_mut().unwrap().text.push_str(&xml_unescape(t.as_str())?),
220                    xmlparser::Token::ElementStart { local, .. } => stack.push(Xml { name: local.as_str().into(), text: CompactString::default(), attrs: vec![], children: vec![] }),
221                    xmlparser::Token::ElementEnd { end, .. } => match end {
222                        xmlparser::ElementEnd::Close(_, _) | xmlparser::ElementEnd::Empty => {
223                            let mut res = stack.pop().unwrap();
224                            res.text = clean_newlines(&res.text);
225                            match stack.last_mut() {
226                                Some(parent) => parent.children.push(res),
227                                None => return Ok(res),
228                            }
229                        }
230                        xmlparser::ElementEnd::Open => (),
231                    }
232                    _ => (),
233                }
234            }
235            None => return Err(XmlError::UnexpectedEof),
236        }
237    }
238}
239
240#[derive(Debug, PartialEq, Eq)]
241pub struct Error {
242    pub kind: ErrorKind,
243    pub location: Location,
244}
245
246#[derive(Debug, PartialEq, Eq)]
247pub struct Location {
248    pub role: Option<CompactString>,
249    pub entity: Option<CompactString>,
250    pub collab_id: Option<CompactString>,
251    pub block_type: Option<CompactString>,
252}
253
254#[derive(Debug)]
255pub struct LocationRef<'a> {
256    pub role: Option<&'a str>,
257    pub entity: Option<&'a str>,
258    pub collab_id: Option<&'a str>,
259    pub block_type: Option<&'a str>,
260}
261impl LocationRef<'_> {
262    pub fn to_owned(&self) -> Location {
263        Location {
264            role: self.role.map(CompactString::new),
265            entity: self.entity.map(CompactString::new),
266            collab_id: self.collab_id.map(CompactString::new),
267            block_type: self.block_type.map(CompactString::new),
268        }
269    }
270}
271
272#[derive(Debug, PartialEq, Eq)]
273pub enum ErrorKind {
274    XmlError(XmlError),
275    Base64Error(Base64Error),
276    ProjectError(ProjectError),
277    CompileError(CompileError),
278}
279impl From<XmlError> for ErrorKind { fn from(e: XmlError) -> Self { Self::XmlError(e) } }
280impl From<Base64Error> for ErrorKind { fn from(e: Base64Error) -> Self { Self::Base64Error(e) } }
281impl From<ProjectError> for ErrorKind { fn from(e: ProjectError) -> Self { Self::ProjectError(e) } }
282impl From<CompileError> for ErrorKind { fn from(e: CompileError) -> Self { Self::CompileError(e) } }
283
284#[derive(Debug, PartialEq, Eq)]
285pub enum XmlError {
286    Read { error: xmlparser::Error },
287    IllegalSequence { sequence: CompactString },
288    UnexpectedEof,
289}
290
291#[derive(Debug, PartialEq, Eq)]
292pub enum ProjectError {
293    NoRoot,
294    NoStage,
295    RoleNoName,
296    RoleNoContent,
297    RefMissingId,
298    ValueNotEvaluated,
299    UpvarNotConst,
300
301    UnnamedGlobal,
302    GlobalsWithSameName { name: CompactString },
303
304    UnnamedEntity,
305    EntitiesWithSameName { name: CompactString },
306
307    UnnamedField,
308    FieldNoValue { name: CompactString },
309    FieldsWithSameName { name: CompactString },
310
311    BlockWithoutType,
312    BlockUnknownType,
313    BlockChildCount { needed: usize, got: usize },
314    BlockMissingOption,
315    BlockOptionUnknown { got: CompactString },
316
317    ImageWithoutId,
318    ImagesWithSameId { id: CompactString },
319    ImageWithoutContent { id: CompactString },
320    ImageUnknownFormat { id: CompactString, content: CompactString },
321
322    SoundWithoutId,
323    SoundsWithSameId { id: CompactString },
324    SoundWithoutContent { id: CompactString },
325    SoundUnknownFormat { id: CompactString, content: CompactString },
326
327    CostumeIdFormat { id: CompactString },
328    CostumeUndefinedRef { id: CompactString },
329    CostumesWithSameName { name: CompactString },
330
331    SoundIdFormat { id: CompactString },
332    SoundUndefinedRef { id: CompactString },
333    SoundsWithSameName { name: CompactString },
334
335    BoolNoValue,
336    BoolUnknownValue { got: CompactString },
337
338    ColorUnknownValue { color: CompactString },
339
340    CustomBlockWithoutName,
341    CustomBlockWithoutInputsMeta,
342    CustomBlockInputsMetaCorrupted,
343    CustomBlockWithoutType,
344    CustomBlockUnknownType { ty: CompactString },
345
346    MessageTypeMissingName,
347    MessageTypeMissingFields { msg_type: CompactString },
348    MessageTypeFieldEmpty { msg_type: CompactString },
349    MessageTypeMultiplyDefined { msg_type: CompactString },
350}
351
352#[derive(Debug, PartialEq, Eq)]
353pub enum CompileError {
354    AutofillGenerateError { input: usize },
355    NameTransformError { name: CompactString},
356    UnknownBlockType,
357    DerefAssignment,
358    UndefinedVariable { name: CompactString },
359    UndefinedFn { name: CompactString },
360    BlockOptionNotConst,
361    BlockOptionNotSelected,
362    UnknownEntity { unknown: CompactString },
363    UnknownEffect { effect: CompactString },
364    UnknownPenAttr { attr: CompactString },
365
366    UnknownMessageType { msg_type: CompactString },
367    MessageTypeWrongNumberArgs { msg_type: CompactString, got: usize, expected: usize },
368
369    UnknownService { service: CompactString },
370    UnknownRPC { service: CompactString, rpc: CompactString },
371
372    GlobalsWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
373    EntitiesWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
374    FieldsWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
375    LocalsWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
376    CostumesWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
377    SoundsWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
378    BlocksWithSameTransName { trans_name: CompactString, names: (CompactString, CompactString) },
379
380    InputsWithSameName { name: CompactString },
381    BlocksWithSameName { name: CompactString, sigs: (CompactString, CompactString) },
382
383    CurrentlyUnsupported { msg: CompactString },
384}
385
386#[derive(Debug)]
387pub enum SymbolError {
388    NameTransformError { name: CompactString },
389    ConflictingTrans { trans_name: CompactString, names: (CompactString, CompactString) },
390}
391
392#[derive(Clone)]
393struct VecMap<K, V>(Vec<(K, V)>);
394impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for VecMap<K, V> {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        write!(f, "{{ {:?} }}", self.0)
397    }
398}
399impl<K, V> Default for VecMap<K, V> {
400    fn default() -> Self {
401        Self(Default::default())
402    }
403}
404impl<K, V> VecMap<K, V> {
405    fn is_empty(&self) -> bool {
406        self.0.is_empty()
407    }
408    fn len(&self) -> usize {
409        self.0.len()
410    }
411    fn into_iter(self) -> alloc::vec::IntoIter<(K, V)> {
412        self.0.into_iter()
413    }
414    fn get<Q: PartialEq + ?Sized>(&self, key: &Q) -> Option<&V> where K: core::borrow::Borrow<Q> {
415        self.0.iter().find(|x| x.0.borrow() == key).map(|x| &x.1)
416    }
417    fn get_mut<Q: PartialEq + ?Sized>(&mut self, key: &Q) -> Option<&mut V> where K: core::borrow::Borrow<Q> {
418        self.0.iter_mut().find(|x| x.0.borrow() == key).map(|x| &mut x.1)
419    }
420    fn insert(&mut self, k: K, v: V) -> Option<V> where K: PartialEq {
421        match self.get_mut(&k) {
422            Some(x) => Some(mem::replace(x, v)),
423            None => {
424                self.0.push((k, v));
425                None
426            }
427        }
428    }
429}
430
431#[derive(Clone)]
432struct SymbolTable<'a> {
433    parser: &'a Parser,
434    orig_to_def: VecMap<CompactString, VariableDefInit>,
435    trans_to_orig: VecMap<CompactString, CompactString>,
436}
437impl fmt::Debug for SymbolTable<'_> {
438    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
439        write!(f, "SymbolTable {{ orig_to_def: {:?}, trans_to_orig: {:?} }}", self.orig_to_def, self.trans_to_orig)
440    }
441}
442impl<'a> SymbolTable<'a> {
443    fn new(parser: &'a Parser) -> Self {
444        Self { parser, orig_to_def: Default::default(), trans_to_orig: Default::default() }
445    }
446    fn transform_name(&self, name: &str) -> Result<CompactString, SymbolError> {
447        match self.parser.name_transformer.as_ref()(name) {
448            Ok(v) => Ok(v),
449            Err(()) => Err(SymbolError::NameTransformError { name: name.into() }),
450        }
451    }
452    /// Defines a new symbol or replaces an existing definition.
453    /// Fails if the name cannot be properly transformed or the transformed name already exists.
454    /// On success, returns the previous definition (if one existed).
455    /// On failure, the symbol table is not modified, and an error context object is returned.
456    fn define(&mut self, name: CompactString, value: Value) -> Result<Option<VariableDefInit>, SymbolError> {
457        let trans_name = self.transform_name(&name)?;
458        if let Some(orig) = self.trans_to_orig.get(&trans_name) {
459            let def = self.orig_to_def.get(orig).unwrap();
460            return Err(SymbolError::ConflictingTrans { trans_name, names: (def.def.name.clone(), name) });
461        }
462
463        let entry = VariableDefInit { def: VariableDef { name: name.clone(), trans_name: trans_name.clone() }, init: value };
464        self.trans_to_orig.insert(trans_name, name.clone());
465        Ok(self.orig_to_def.insert(name, entry))
466    }
467    /// Returns the definition of the given variable if it exists.
468    fn get(&self, name: &str) -> Option<&VariableDefInit> {
469        self.orig_to_def.get(name)
470    }
471    /// Gets the list of all defined variables.
472    /// This is guaranteed to be in order of definition.
473    fn into_defs(self) -> Vec<VariableDef> {
474        self.orig_to_def.into_iter().map(|x| x.1.def).collect()
475    }
476    /// Equivalent to [`SymbolTable::into_defs`] but preserves the initialized value.
477    fn into_def_inits(self) -> Vec<VariableDefInit> {
478        self.orig_to_def.into_iter().map(|x| x.1).collect()
479    }
480    fn len(&self) -> usize {
481        self.orig_to_def.len()
482    }
483    fn is_empty(&self) -> bool {
484        self.orig_to_def.is_empty()
485    }
486}
487#[test]
488fn test_sym_tab() {
489    let parser = Parser { name_transformer: Box::new(crate::util::c_ident), ..Default::default() };
490    let mut sym = SymbolTable::new(&parser);
491    assert!(sym.orig_to_def.is_empty());
492    assert!(sym.trans_to_orig.is_empty());
493    assert!(sym.define("hello world!".into(), 0f64.into()).unwrap().is_none());
494    assert_eq!(sym.orig_to_def.get("hello world!").unwrap().def.name, "hello world!");
495    assert_eq!(sym.orig_to_def.get("hello world!").unwrap().def.trans_name, "hello_world");
496    assert_eq!(sym.trans_to_orig.get("hello_world").unwrap().as_str(), "hello world!");
497}
498
499#[derive(Debug)]
500struct Rpc {
501    host: Option<CompactString>,
502    service: CompactString,
503    rpc: CompactString,
504    args: Vec<(CompactString, Expr)>,
505    info: Box<BlockInfo>,
506}
507#[derive(Debug)]
508struct FnCall {
509    function: FnRef,
510    args: Vec<Expr>,
511    upvars: Vec<VariableRef>,
512    info: Box<BlockInfo>,
513}
514
515#[derive(Debug, Clone)]
516pub struct BlockInfo {
517    pub comment: Option<CompactString>,
518    pub location: Option<CompactString>,
519}
520impl BlockInfo {
521    pub fn none() -> Box<Self> {
522        Box::new_with(|| BlockInfo { comment: None, location: None })
523    }
524}
525
526#[derive(Debug, Clone)]
527pub struct Project {
528    pub name: CompactString,
529    pub roles: Vec<Role>,
530}
531#[derive(Debug, Clone)]
532pub struct Role {
533    pub name: CompactString,
534    pub notes: CompactString,
535    pub stage_size: (usize, usize),
536    pub globals: Vec<VariableDefInit>,
537    pub funcs: Vec<Function>,
538    pub entities: Vec<Entity>,
539}
540#[derive(Debug, Clone)]
541pub struct Function {
542    pub name: CompactString,
543    pub trans_name: CompactString,
544    pub params: Vec<VariableDef>,
545    pub upvars: Vec<VariableRef>, // refer into params
546    pub returns: bool,
547    pub stmts: Vec<Stmt>,
548}
549#[derive(Debug, Clone)]
550pub struct Entity {
551    pub name: CompactString,
552    pub trans_name: CompactString,
553    pub fields: Vec<VariableDefInit>,
554    pub costumes: Vec<VariableDefInit>,
555    pub sounds: Vec<VariableDefInit>,
556    pub funcs: Vec<Function>,
557    pub scripts: Vec<Script>,
558
559    pub active_costume: Option<usize>,
560    pub visible: bool,
561    pub color: (u8, u8, u8, u8),
562    pub pos: (f64, f64),
563    pub heading: f64,
564    pub scale: f64,
565}
566#[derive(Debug, Clone)]
567pub struct VariableDefInit {
568    pub def: VariableDef,
569    pub init: Value,
570}
571#[derive(Debug, Clone)]
572pub struct VariableDef {
573    pub name: CompactString,
574    pub trans_name: CompactString,
575}
576impl VariableDef {
577    #[inline(always)]
578    fn ref_at(&self, location: VarLocation) -> Box<VariableRef> {
579        Box::new_with(|| VariableRef { name: self.name.clone(), trans_name: self.trans_name.clone(), location })
580    }
581    #[inline(always)]
582    fn fn_ref_at(&self, location: FnLocation) -> Box<FnRef> {
583        Box::new_with(|| FnRef { name: self.name.clone(), trans_name: self.trans_name.clone(), location })
584    }
585}
586#[derive(Debug, Clone)]
587pub struct VariableRef {
588    pub name: CompactString,
589    pub trans_name: CompactString,
590    pub location: VarLocation,
591}
592#[derive(Debug, Clone)]
593pub struct FnRef {
594    pub name: CompactString,
595    pub trans_name: CompactString,
596    pub location: FnLocation,
597}
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub enum VarLocation {
600    Global, Field, Local,
601}
602#[derive(Debug, Clone, Copy, PartialEq, Eq)]
603pub enum FnLocation {
604    Global, Method,
605}
606#[derive(Debug, Clone)]
607pub struct Script {
608    pub hat: Option<Box<Hat>>,
609    pub stmts: Vec<Stmt>,
610}
611#[derive(Debug, Clone)]
612pub struct Hat {
613    pub kind: HatKind,
614    pub info: Box<BlockInfo>,
615}
616#[derive(Debug, Clone)]
617pub enum HatKind {
618    OnFlag,
619    OnClone,
620    OnKey { key: CompactString },
621    MouseDown,
622    MouseUp,
623    MouseEnter,
624    MouseLeave,
625    ScrollUp,
626    ScrollDown,
627    Dropped,
628    Stopped,
629    When { condition: Box<Expr> },
630    LocalMessage { msg_type: Option<CompactString> },
631    NetworkMessage { msg_type: CompactString, fields: Vec<VariableRef> },
632    Unknown { name: CompactString, fields: Vec<VariableRef> },
633}
634#[derive(Debug, Clone)]
635pub struct Stmt {
636    pub kind: StmtKind,
637    pub info: Box<BlockInfo>,
638}
639#[derive(Debug, Clone)]
640pub enum StmtKind {
641    DeclareLocals { vars: Vec<VariableDef> },
642    Assign { var: VariableRef, value: Box<Expr> },
643    AddAssign { var: VariableRef, value: Box<Expr> },
644
645    ShowVar { var: VariableRef },
646    HideVar { var: VariableRef },
647
648    Warp { stmts: Vec<Stmt> },
649
650    InfLoop { stmts: Vec<Stmt> },
651    ForeachLoop { var: VariableRef, items: Box<Expr>, stmts: Vec<Stmt> },
652    ForLoop { var: VariableRef, start: Box<Expr>, stop: Box<Expr>, stmts: Vec<Stmt> },
653    UntilLoop { condition: Box<Expr>, stmts: Vec<Stmt> },
654    Repeat { times: Box<Expr>, stmts: Vec<Stmt> },
655
656    If { condition: Box<Expr>, then: Vec<Stmt> },
657    IfElse { condition: Box<Expr>, then: Vec<Stmt>, otherwise: Vec<Stmt> },
658
659    TryCatch { code: Vec<Stmt>, var: VariableRef, handler: Vec<Stmt> },
660    Throw { error: Box<Expr> },
661
662    ListInsert { list: Box<Expr>, value: Box<Expr>, index: Box<Expr> },
663    ListInsertLast { list: Box<Expr>, value: Box<Expr> },
664    ListInsertRandom { list: Box<Expr>, value: Box<Expr> },
665
666    ListRemove { list: Box<Expr>, index: Box<Expr> },
667    ListRemoveLast { list: Box<Expr> },
668    ListRemoveAll { list: Box<Expr> },
669
670    ListAssign { list: Box<Expr>, value: Box<Expr>, index: Box<Expr> },
671    ListAssignLast { list: Box<Expr>, value: Box<Expr> },
672    ListAssignRandom { list: Box<Expr>, value: Box<Expr> },
673
674    Return { value: Box<Expr> },
675
676    Sleep { seconds: Box<Expr> },
677    WaitUntil { condition: Box<Expr> },
678
679    SetCostume { costume: Box<Expr> },
680    NextCostume,
681
682    PlaySound { sound: Box<Expr>, blocking: bool },
683    PlayNotes { notes: Box<Expr>, beats: Box<Expr>, blocking: bool },
684    Rest { beats: Box<Expr> },
685    StopSounds,
686
687    Forward { distance: Box<Expr> },
688    SetX { value: Box<Expr> },
689    ChangeX { delta: Box<Expr> },
690    SetY { value: Box<Expr> },
691    ChangeY { delta: Box<Expr> },
692    GotoXY { x: Box<Expr>, y: Box<Expr> },
693    GotoMouse,
694    GotoRandom,
695    /// Similar to `SetPos` except that the target can be either a list of `[x, y]` coordinates or a entity.
696    Goto { target: Box<Expr> },
697    PointTowards { target: Box<Expr> },
698    PointTowardsXY { x: Box<Expr>, y: Box<Expr> },
699
700    TurnRight { angle: Box<Expr> },
701    TurnLeft { angle: Box<Expr> },
702    SetHeading { value: Box<Expr> },
703    SetHeadingRandom,
704
705    BounceOffEdge,
706
707    SetPenDown { value: bool },
708    PenClear,
709    Stamp,
710    Write { content: Box<Expr>, font_size: Box<Expr> },
711    SetPenColor { color: (u8, u8, u8, u8) },
712
713    Say { content: Box<Expr>, duration: Option<Box<Expr>> },
714    Think { content: Box<Expr>, duration: Option<Box<Expr>> },
715
716    SetVisible { value: bool },
717    ChangeSize { delta: Box<Expr> },
718    SetSize { value: Box<Expr> },
719
720    ChangePenSize { delta: Box<Expr> },
721    SetPenSize { value: Box<Expr> },
722
723    CallRpc { host: Option<CompactString>, service: CompactString, rpc: CompactString, args: Vec<(CompactString, Expr)> },
724    CallFn { function: FnRef, args: Vec<Expr>, upvars: Vec<VariableRef> },
725    CallClosure { new_entity: Option<Box<Expr>>, closure: Box<Expr>, args: Vec<Expr> },
726    ForkClosure { closure: Box<Expr>, args: Vec<Expr> },
727
728    Clone { target: Box<Expr> },
729    DeleteClone,
730
731    /// Sends a message to local entities (not over the network).
732    /// If `target` is `None`, this should broadcast to all entities.
733    /// Otherwise `target` is either a single target or a list of targets to send to.
734    /// The `wait` flag determines if the broadcast should be blocking (wait for receivers to terminate).
735    SendLocalMessage { target: Option<Box<Expr>>, msg_type: Box<Expr>, wait: bool },
736    /// Sends a message over the network to the specified targets.
737    /// `target` may be a single target or a list of targets.
738    SendNetworkMessage { target: Box<Expr>, msg_type: CompactString, values: Vec<(CompactString, Expr)> },
739    /// Sends a reply from a received message that was blocking (sender's `wait` flag was `true`).
740    SendNetworkReply { value: Box<Expr> },
741
742    Ask { prompt: Box<Expr> },
743
744    ResetTimer,
745
746    Pause,
747
748    SetEffect { kind: EffectKind, value: Box<Expr> },
749    ChangeEffect { kind: EffectKind, delta: Box<Expr> },
750    ClearEffects,
751
752    SetPenAttr { attr: PenAttribute, value: Box<Expr> },
753    ChangePenAttr { attr: PenAttribute, delta: Box<Expr> },
754
755    Stop { mode: StopMode },
756
757    UnknownBlock { name: CompactString, args: Vec<Expr> },
758}
759impl From<Rpc> for Stmt {
760    fn from(rpc: Rpc) -> Stmt {
761        let Rpc { host, service, rpc, args, info } = rpc;
762        Stmt { kind: StmtKind::CallRpc { host, service, rpc, args }, info }
763    }
764}
765
766#[derive(Debug, Clone)]
767pub struct RefId(pub usize);
768
769#[derive(Debug, Clone)]
770pub enum Value {
771    Bool(bool),
772    Number(f64),
773    Constant(Constant),
774    String(CompactString),
775    Image(Rc<(Vec<u8>, Option<(f64, f64)>, CompactString)>),
776    Audio(Rc<(Vec<u8>, CompactString)>),
777    List(Vec<Value>, Option<RefId>),
778    Ref(RefId),
779}
780
781impl From<f64> for Value { fn from(v: f64) -> Value { Value::Number(v) } }
782impl From<&str> for Value { fn from(v: &str) -> Value { Value::String(v.into()) } }
783impl From<bool> for Value { fn from(v: bool) -> Value { Value::Bool(v) } }
784impl From<CompactString> for Value { fn from(v: CompactString) -> Value { Value::String(v) } }
785impl From<Constant> for Value { fn from(v: Constant) -> Value { Value::Constant(v) } }
786
787#[derive(Debug, Clone, Copy)]
788pub enum Constant {
789    E, Pi,
790}
791#[derive(Debug, Clone)]
792pub enum TextSplitMode {
793    Letter, Word, Tab, CR, LF, Csv, Json,
794    Custom(Box<Expr>),
795}
796#[derive(Debug, Clone)]
797pub enum EffectKind {
798    Color, Saturation, Brightness, Ghost,
799    Fisheye, Whirl, Pixelate, Mosaic, Negative,
800}
801#[derive(Debug, Clone)]
802pub enum PenAttribute {
803    Size, Hue, Saturation, Brightness, Transparency,
804}
805#[derive(Debug, Clone, PartialEq, Eq)]
806pub enum ClosureKind {
807    Command, Reporter, Predicate,
808}
809#[derive(Debug, Clone)]
810pub enum ValueType {
811    Number, Text, Bool, List, Sprite, Costume, Sound, Command, Reporter, Predicate,
812}
813#[derive(Debug, Clone)]
814pub enum TimeQuery {
815    Year, Month, Date, DayOfWeek, Hour, Minute, Second, UnixTimestampMs,
816}
817#[derive(Debug, Clone)]
818pub enum StopMode {
819    All, AllScenes, ThisScript, ThisBlock, AllButThisScript, OtherScriptsInSprite,
820}
821
822#[derive(Debug, Clone)]
823pub struct Expr {
824    pub kind: ExprKind,
825    pub info: Box<BlockInfo>,
826}
827#[derive(Debug, Clone)]
828pub enum ExprKind {
829    Value(Value),
830    Variable { var: VariableRef },
831
832    Add { values: Box<Expr> },
833    Mul { values: Box<Expr> },
834    Min { values: Box<Expr> },
835    Max { values: Box<Expr> },
836
837    Sub { left: Box<Expr>, right: Box<Expr> },
838    Div { left: Box<Expr>, right: Box<Expr> },
839    /// Mathematical modulus (not remainder!). For instance, `-1 mod 7 == 6`.
840    Mod { left: Box<Expr>, right: Box<Expr> },
841
842    Pow { base: Box<Expr>, power: Box<Expr> },
843    Log { value: Box<Expr>, base: Box<Expr> },
844
845    Atan2 { y: Box<Expr>, x: Box<Expr> },
846
847    /// Short-circuiting logical `or`.
848    And { left: Box<Expr>, right: Box<Expr> },
849    /// Short-circuiting logical `and`.
850    Or { left: Box<Expr>, right: Box<Expr> },
851    /// Lazily-evaluated conditional expression. Returns `then` if `condition` is true, otherwise `otherwise`.
852    Conditional { condition: Box<Expr>, then: Box<Expr>, otherwise: Box<Expr> },
853
854    /// If both values are lists, returns true of they are references to the same list.
855    /// If both values are non-lists, returns true if the values are equal.
856    /// Otherwise returns `false`.
857    Identical { left: Box<Expr>, right: Box<Expr> },
858    Eq { left: Box<Expr>, right: Box<Expr> },
859    Neq { left: Box<Expr>, right: Box<Expr> },
860    Less { left: Box<Expr>, right: Box<Expr> },
861    LessEq { left: Box<Expr>, right: Box<Expr> },
862    Greater { left: Box<Expr>, right: Box<Expr> },
863    GreaterEq { left: Box<Expr>, right: Box<Expr> },
864
865    /// Get a random number between `a` and `b` (inclusive).
866    /// There are no ordering guarantees (swapping `a` and `b` is equivalent).
867    /// If both values are integers, the result is an integer, otherwise continuous floats are returned.
868    Random { a: Box<Expr>, b: Box<Expr> },
869    /// Get a list of all the numbers starting at `start` and stepping towards `stop` (by `+1` or `-1`), but not going past `stop`.
870    Range { start: Box<Expr>, stop: Box<Expr> },
871
872    MakeList { values: Vec<Expr> },
873    CopyList { list: Box<Expr> },
874    ListCat { lists: Box<Expr> },
875
876    ListLen { value: Box<Expr> },
877    ListRank { value: Box<Expr> },
878    ListDims { value: Box<Expr> },
879    ListFlatten { value: Box<Expr> },
880    ListColumns { value: Box<Expr> },
881    ListRev { value: Box<Expr> },
882
883    ListLines { value: Box<Expr> },
884    ListCsv { value: Box<Expr> },
885    ListJson { value: Box<Expr> },
886
887    ListReshape { value: Box<Expr>, dims: Box<Expr> },
888    ListCombinations { sources: Box<Expr> },
889
890    ListIsEmpty { value: Box<Expr> },
891    /// Given a list, returns a new (shallow copy) of all the items except the first.
892    /// If the list is empty, an empty list is returned.
893    ListCdr { value: Box<Expr> },
894    /// Given a value and a list, returns a new list (shallow copy) with the item prepended.
895    ListCons { item: Box<Expr>, list: Box<Expr> },
896    /// Returns the (1-based) index of value in the list, or 0 if not present.
897    ListFind { list: Box<Expr>, value: Box<Expr> },
898    ListContains { list: Box<Expr>, value: Box<Expr> },
899
900    ListGet { list: Box<Expr>, index: Box<Expr> },
901    ListGetLast { list: Box<Expr> },
902    ListGetRandom { list: Box<Expr> },
903
904    StrGet { string: Box<Expr>, index: Box<Expr> },
905    StrGetLast { string: Box<Expr> },
906    StrGetRandom { string: Box<Expr> },
907
908    StrCat { values: Box<Expr> },
909    /// String length in terms of unicode code points (not bytes or grapheme clusters!).
910    StrLen { value: Box<Expr> },
911
912    /// Convert a unicode code point into a 1-character string.
913    UnicodeToChar { value: Box<Expr> },
914    /// Convert a 1-character string into its unicode code point.
915    CharToUnicode { value: Box<Expr> },
916
917    Not { value: Box<Expr> },
918    Neg { value: Box<Expr> },
919    Abs { value: Box<Expr> },
920    Sign { value: Box<Expr> },
921    Sqrt { value: Box<Expr> },
922
923    Floor { value: Box<Expr> },
924    Ceil { value: Box<Expr> },
925    Round { value: Box<Expr> },
926
927    Sin { value: Box<Expr> },
928    Cos { value: Box<Expr> },
929    Tan { value: Box<Expr> },
930
931    Asin { value: Box<Expr> },
932    Acos { value: Box<Expr> },
933    Atan { value: Box<Expr> },
934
935    CallRpc { host: Option<CompactString>, service: CompactString, rpc: CompactString, args: Vec<(CompactString, Expr)> },
936    CallFn { function: FnRef, args: Vec<Expr>, upvars: Vec<VariableRef>, },
937    CallClosure { new_entity: Option<Box<Expr>>, closure: Box<Expr>, args: Vec<Expr> },
938
939    StageWidth,
940    StageHeight,
941
942    MouseX,
943    MouseY,
944
945    Latitude,
946    Longitude,
947
948    KeyDown { key: Box<Expr> },
949
950    YPos,
951    XPos,
952    Heading,
953
954    PenDown,
955
956    Size,
957    IsVisible,
958
959    This,
960    Entity { name: CompactString, trans_name: CompactString },
961
962    ImageOfEntity { entity: Box<Expr> },
963    ImageOfDrawings,
964
965    IsTouchingEntity { entity: Box<Expr> },
966    IsTouchingMouse,
967    IsTouchingEdge,
968    IsTouchingDrawings,
969
970    RpcError,
971
972    Closure { kind: ClosureKind, params: Vec<VariableDef>, captures: Vec<VariableRef>, stmts: Vec<Stmt> },
973
974    TextSplit { text: Box<Expr>, mode: TextSplitMode },
975
976    Answer,
977    Message,
978
979    Timer,
980
981    Map { f: Box<Expr>, list: Box<Expr> },
982    Keep { f: Box<Expr>, list: Box<Expr> },
983    FindFirst { f: Box<Expr>, list: Box<Expr> },
984    Combine { f: Box<Expr>, list: Box<Expr> },
985
986    NetworkMessageReply { target: Box<Expr>, msg_type: CompactString, values: Vec<(CompactString, Expr)> },
987
988    Effect { kind: EffectKind },
989    PenAttr { attr: PenAttribute },
990
991    CostumeList,
992    Costume,
993    CostumeNumber,
994    CostumeName { costume: Box<Expr> },
995    CostumeWidth { costume: Box<Expr> },
996    CostumeHeight { costume: Box<Expr> },
997    CostumePixels { costume: Box<Expr> },
998
999    SoundList,
1000    SoundName { sound: Box<Expr> },
1001    SoundDuration { sound: Box<Expr> },
1002    SoundSampleRate { sound: Box<Expr> },
1003    SoundSamples { sound: Box<Expr> },
1004    SoundSamplesLength { sound: Box<Expr> },
1005    SoundChannelCount { sound: Box<Expr> },
1006
1007    Clone { target: Box<Expr> },
1008
1009    TypeQuery { value: Box<Expr>, ty: ValueType },
1010    RealTime { query: TimeQuery },
1011
1012    UnknownBlock { name: CompactString, args: Vec<Expr> },
1013}
1014impl<T: Into<Value>> From<T> for Expr {
1015    fn from(v: T) -> Expr {
1016        Expr { kind: ExprKind::Value(v.into()), info: BlockInfo::none() }
1017    }
1018}
1019impl From<Rpc> for Expr {
1020    fn from(rpc: Rpc) -> Expr {
1021        let Rpc { host, service, rpc, args, info } = rpc;
1022        Expr { kind: ExprKind::CallRpc { host, service, rpc, args }, info }
1023    }
1024}
1025
1026fn parse_color(value: &str) -> Option<(u8, u8, u8, u8)> {
1027    let vals: Vec<_> = value.split(',').map(|v| v.parse::<f64>().ok()).flatten().collect();
1028    match vals.as_slice() {
1029        [r, g, b] => Some((*r as u8, *g as u8, *b as u8, 255)),
1030        [r, g, b, a] => Some((*r as u8, *g as u8, *b as u8, (*a * 255.0) as u8)),
1031        _ => None,
1032    }
1033}
1034
1035struct NetworkMessage {
1036    target: Box<Expr>,
1037    msg_type: CompactString,
1038    values: Vec<(CompactString, Expr)>,
1039    info: Box<BlockInfo>,
1040}
1041
1042struct ScriptInfo<'a, 'b, 'c> {
1043    parser: &'a Parser,
1044    role: &'c RoleInfo<'a>,
1045    entity: &'c EntityInfo<'a, 'b>,
1046    locals: Vec<(SymbolTable<'a>, Vec<VariableRef>)>, // tuples of (locals, captures)
1047    autofill_args: Option<Vec<VariableRef>>,
1048}
1049impl<'a, 'b, 'c> ScriptInfo<'a, 'b, 'c> {
1050    fn new(entity: &'c EntityInfo<'a, 'b>) -> Box<Self> {
1051        Box::new_with(|| Self {
1052            parser: entity.parser,
1053            role: entity.role,
1054            entity,
1055            locals: vec![(SymbolTable::new(entity.parser), Default::default())],
1056            autofill_args: None,
1057        })
1058    }
1059    #[inline(never)]
1060    fn check_children_get_info(&self, expr: &Xml, req: usize, location: &LocationRef) -> Result<Box<BlockInfo>, Box<Error>> {
1061        if expr.children.len() < req {
1062            return Err(Box::new_with(|| Error { kind: ErrorKind::ProjectError(ProjectError::BlockChildCount { needed: req, got: expr.children.len() }), location: location.to_owned() }));
1063        }
1064        let comment = match expr.children.get(req) {
1065            Some(comment) => if comment.name == "comment" { Some(comment.text.clone()) } else { None },
1066            None => None,
1067        };
1068        Ok(Box::new_with(|| BlockInfo { comment, location: location.collab_id.map(CompactString::new) }))
1069    }
1070    #[inline(never)]
1071    fn decl_local(&mut self, name: CompactString, value: Value, location: &LocationRef) -> Result<&VariableDefInit, Box<Error>> {
1072        let locals = &mut self.locals.last_mut().unwrap().0;
1073        match locals.define(name.clone(), value) {
1074            Ok(_) => (), // redefining locals is fine
1075            Err(SymbolError::ConflictingTrans { trans_name, names }) => if names.0 != names.1 { // redefining locals is fine
1076                return Err(Box::new_with(|| Error { kind: CompileError::LocalsWithSameTransName { trans_name, names }.into(), location: location.to_owned() }));
1077            }
1078            Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
1079        }
1080        Ok(locals.get(&name).unwrap())
1081    }
1082    #[inline(never)]
1083    fn grab_option<'x>(&self, child: &'x Xml, location: &LocationRef) -> Result<&'x str, Box<Error>> {
1084        let res = match child.get(&["option"]) {
1085            None => return Err(Box::new_with(|| Error { kind: ProjectError::BlockMissingOption.into(), location: location.to_owned() })),
1086            Some(f) => {
1087                if f.children.len() != 0 { return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotConst.into(), location: location.to_owned() })) }
1088                f.text.as_str()
1089            }
1090        };
1091        if res == "" { return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })) }
1092        Ok(res)
1093    }
1094    #[inline(never)]
1095    fn grab_entity(&mut self, child: &Xml, info: Box<BlockInfo>, location: &LocationRef) -> Result<Box<Expr>, Box<Error>> {
1096        match child.text.as_str() {
1097            "" => match child.get(&["option"]) {
1098                Some(x) => match x.text.as_str() {
1099                    "myself" => Ok(Box::new_with(|| Expr { kind: ExprKind::This, info })),
1100                    x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1101                }
1102                None => self.parse_expr(child, location),
1103            }
1104            name => match self.role.entities.get(name) {
1105                None => Err(Box::new_with(|| Error { kind: CompileError::UnknownEntity { unknown: name.into() }.into(), location: location.to_owned() })),
1106                Some(entity) => Ok(Box::new_with(|| Expr { kind: ExprKind::Entity { name: entity.def.name.clone(), trans_name: entity.def.trans_name.clone() }, info })),
1107            }
1108        }
1109    }
1110    #[inline(never)]
1111    fn parse(&mut self, script_xml: &Xml) -> Result<Box<Script>, Box<Error>> {
1112        let mut script = match script_xml.children.first() {
1113            Some(x) => Box::try_new_with(|| Ok::<_, Box<Error>>(Script { hat: self.parse_hat(x)?, stmts: vec![] }))?,
1114            None => Box::new_with(|| Script { hat: None, stmts: vec![] }),
1115        };
1116
1117        for stmt in &script_xml.children[if script.hat.is_some() { 1 } else { 0 }..] {
1118            let location = Box::new_with(|| LocationRef {
1119                role: Some(&self.role.name),
1120                entity: Some(&self.entity.name),
1121                collab_id: get_collab_id(stmt),
1122                block_type: Some(&stmt.name),
1123            });
1124            match stmt.name.as_str() {
1125                "block" => {
1126                    script.stmts.append(&mut self.parse_block(stmt)?);
1127                }
1128                "custom-block" => {
1129                    let res = self.parse_fn_call(stmt, &location)?;
1130                    script.stmts.push_with(|| {
1131                        let FnCall { function, args, upvars, info } = *res;
1132                        Stmt { kind: StmtKind::CallFn { function, args, upvars }, info }
1133                    });
1134                }
1135                _ => return Err(Box::new_with(|| Error { kind: ProjectError::BlockUnknownType.into(), location: location.to_owned() })),
1136            }
1137        }
1138
1139        Ok(script)
1140    }
1141    #[inline(never)]
1142    fn parse_hat(&mut self, stmt: &Xml) -> Result<Option<Box<Hat>>, Box<Error>> {
1143        let mut location = Box::new_with(|| LocationRef {
1144            role: Some(&self.role.name),
1145            entity: Some(&self.entity.name),
1146            collab_id: get_collab_id(stmt),
1147            block_type: None,
1148        });
1149        let s = match stmt.attr("s") {
1150            None => return Err(Box::new_with(|| Error { kind: ProjectError::BlockWithoutType.into(), location: location.to_owned() })),
1151            Some(v) => v.value.as_str(),
1152        };
1153        location.block_type = Some(s);
1154
1155        fn parse_fields(script: &mut ScriptInfo, children: &[Xml], location: &LocationRef) -> Result<(Vec<VariableRef>, Option<CompactString>), Box<Error>> {
1156            let mut fields = vec![];
1157            let mut comment = None;
1158            for child in children {
1159                if child.name == "comment" {
1160                    comment = Some(child.text.clone());
1161                }
1162                if child.name != "l" { break }
1163                let var = script.decl_local(child.text.clone(), 0f64.into(), &location)?.def.ref_at(VarLocation::Local);
1164                fields.push_boxed(var);
1165            }
1166            Ok((fields, comment))
1167        }
1168
1169        Ok(Some(match s {
1170            "receiveGo" => {
1171                let info = self.check_children_get_info(stmt, 0, &location)?;
1172                Box::new_with(|| Hat { kind: HatKind::OnFlag, info })
1173            }
1174            "receiveOnClone" => {
1175                let info = self.check_children_get_info(stmt, 0, &location)?;
1176                Box::new_with(|| Hat { kind: HatKind::OnClone, info })
1177            }
1178            "receiveCondition" => {
1179                let info = self.check_children_get_info(stmt, 1, &location)?;
1180                let condition = self.parse_expr(&stmt.children[0], &location)?;
1181                Box::new_with(|| Hat { kind: HatKind::When { condition }, info })
1182            }
1183            "receiveKey" => {
1184                let info = self.check_children_get_info(stmt, 1, &location)?;
1185                let key = self.grab_option(&stmt.children[0], &location)?;
1186                Box::new_with(|| Hat { kind: HatKind::OnKey { key: key.into() }, info })
1187            }
1188            "receiveInteraction" => {
1189                let info = self.check_children_get_info(stmt, 1, &location)?;
1190                match self.grab_option(&stmt.children[0], &location)? {
1191                    "pressed" => Box::new_with(|| Hat { kind: HatKind::MouseDown, info }),
1192                    "clicked" => Box::new_with(|| Hat { kind: HatKind::MouseUp, info }),
1193                    "mouse-entered" => Box::new_with(|| Hat { kind: HatKind::MouseEnter, info }),
1194                    "mouse-departed" => Box::new_with(|| Hat { kind: HatKind::MouseLeave, info }),
1195                    "scrolled-up" => Box::new_with(|| Hat { kind: HatKind::ScrollUp, info }),
1196                    "scrolled-down" => Box::new_with(|| Hat { kind: HatKind::ScrollDown, info }),
1197                    "dropped" => Box::new_with(|| Hat { kind: HatKind::Dropped, info }),
1198                    "stopped" => Box::new_with(|| Hat { kind: HatKind::Stopped, info }),
1199                    x => return Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1200                }
1201            }
1202            "receiveMessage" => {
1203                let info = self.check_children_get_info(stmt, 1, &location)?;
1204                let child = &stmt.children[0];
1205                if child.name != "l" { return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotConst.into(), location: location.to_owned() })) }
1206                let msg_type = match child.text.as_str() {
1207                    "" => match child.get(&["option"]) {
1208                        Some(opt) => match opt.text.as_str() {
1209                            "any message" => None,
1210                            x => return Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1211                        }
1212                        None => return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1213                    }
1214                    x => Some(CompactString::new(x)),
1215                };
1216                Box::new_with(|| Hat { kind: HatKind::LocalMessage { msg_type }, info })
1217            }
1218            "receiveSocketMessage" => {
1219                if stmt.children.is_empty() { return Err(Box::new_with(|| Error { kind: ProjectError::BlockChildCount { needed: 1, got: 0 }.into(), location: location.to_owned() })) }
1220                if stmt.children[0].name != "l" { return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotConst.into(), location: location.to_owned() })) }
1221
1222                let msg_type = match stmt.children[0].text.as_str() {
1223                    "" => return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1224                    x => CompactString::new(x),
1225                };
1226
1227                let (fields, comment) = parse_fields(self, &stmt.children[1..], &location)?;
1228                let info = Box::new_with(|| BlockInfo { comment, location: location.collab_id.map(CompactString::new) });
1229                Box::new_with(|| Hat { kind: HatKind::NetworkMessage { msg_type, fields }, info })
1230            }
1231            x if x.starts_with("receive") => {
1232                let (fields, comment) = parse_fields(self, &stmt.children, &location)?;
1233                let info = Box::new_with(|| BlockInfo { comment, location: location.collab_id.map(CompactString::new) });
1234                Box::new_with(|| Hat { kind: HatKind::Unknown { fields, name: x.into() }, info })
1235            }
1236            _ => return Ok(None),
1237        }))
1238    }
1239    #[inline(never)]
1240    fn parse_effect(&mut self, effect: &Xml, location: &LocationRef) -> Result<EffectKind, Box<Error>> {
1241        Ok(match self.grab_option(effect, location)? {
1242            "color" => EffectKind::Color,
1243            "saturation" => EffectKind::Saturation,
1244            "brightness" => EffectKind::Brightness,
1245            "ghost" => EffectKind::Ghost,
1246            "fisheye" => EffectKind::Fisheye,
1247            "whirl" => EffectKind::Whirl,
1248            "pixelate" => EffectKind::Pixelate,
1249            "mosaic" => EffectKind::Mosaic,
1250            "negative" => EffectKind::Negative,
1251            x => return Err(Box::new_with(|| Error { kind: CompileError::UnknownEffect { effect: CompactString::new(x) }.into(), location: location.to_owned() })),
1252        })
1253    }
1254    #[inline(never)]
1255    fn parse_pen_attr(&mut self, attr: &Xml, location: &LocationRef) -> Result<PenAttribute, Box<Error>> {
1256        Ok(match self.grab_option(attr, location)? {
1257            "size" => PenAttribute::Size,
1258            "hue" => PenAttribute::Hue,
1259            "saturation" => PenAttribute::Saturation,
1260            "brightness" => PenAttribute::Brightness,
1261            "transparency" => PenAttribute::Transparency,
1262            x => return Err(Box::new_with(|| Error { kind: CompileError::UnknownPenAttr { attr: CompactString::new(x) }.into(), location: location.to_owned() })),
1263        })
1264    }
1265    #[inline(never)]
1266    fn parse_rpc(&mut self, stmt: &Xml, location: &LocationRef) -> Result<Box<Rpc>, Box<Error>> {
1267        if stmt.children.len() < 2 { return Err(Box::new_with(|| Error { kind: ProjectError::BlockChildCount { needed: 2, got: stmt.children.len() }.into(), location: location.to_owned() })) }
1268        for child in &stmt.children[..2] {
1269            if child.name != "l" || child.text.is_empty() { return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotConst.into(), location: location.to_owned() })) }
1270        }
1271
1272        let (host, service) = match stmt.children[0].text.rsplit_once('/') {
1273            Some((host, service)) => (Some(CompactString::new(host)), CompactString::new(service)),
1274            None => (None, stmt.children[0].text.clone()),
1275        };
1276        let rpc = stmt.children[1].text.clone();
1277
1278        let arg_names = match stmt.attr("inputNames").map(|x| x.value.split(';').map(str::trim).filter(|v| !v.is_empty()).collect::<Vec<_>>()) {
1279            Some(x) => x,
1280            None => match SERVICE_INFO.iter().find(|x| x.0 == service) {
1281                None => return Err(Box::new_with(|| Error { kind: CompileError::UnknownService { service }.into(), location: location.to_owned() })),
1282                Some(x) => match x.1.iter().find(|x| x.0 == rpc) {
1283                    None => return Err(Box::new_with(|| Error { kind: CompileError::UnknownRPC { service, rpc }.into(), location: location.to_owned() })),
1284                    Some(x) => x.1.to_owned(),
1285                }
1286            }
1287        };
1288
1289        let info = self.check_children_get_info(stmt, 2 + arg_names.len(), location)?;
1290        let mut args = Vec::with_capacity(arg_names.len());
1291        for (&arg_name, child) in arg_names.iter().zip(&stmt.children[2 .. 2 + arg_names.len()]) {
1292            let val = self.parse_expr(child, location)?;
1293            args.push_with(|| (CompactString::new(arg_name), *val));
1294        }
1295        Ok(Box::new_with(|| Rpc { host, service, rpc, args, info }))
1296    }
1297    #[inline(never)]
1298    fn parse_fn_call(&mut self, stmt: &Xml, location: &LocationRef) -> Result<Box<FnCall>, Box<Error>> {
1299        let s = match stmt.attr("s") {
1300            Some(v) => v.value.as_str(),
1301            None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockWithoutName.into(), location: location.to_owned() })),
1302        };
1303        let location = Box::new_with(|| LocationRef {
1304            role: location.role,
1305            entity: location.entity,
1306            collab_id: location.collab_id,
1307            block_type: Some(s),
1308        });
1309
1310        let name = block_name_from_ref(s);
1311        let function = self.reference_fn(&name, &location)?;
1312        let block_info = get_block_info(&function.1);
1313        let argc = block_info.params.len();
1314        debug_assert_eq!(ArgIter::new(s).count(), argc);
1315        let info = self.check_children_get_info(stmt, argc, &location)?;
1316
1317        let mut upvars = vec![];
1318        for upvar in block_info.upvars.iter() {
1319            let i = match block_info.params.iter().position(|x| x.0 == *upvar) {
1320                Some(x) => x,
1321                None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockInputsMetaCorrupted.into(), location: location.to_owned() })),
1322            };
1323            let upvar_target = match stmt.children.get(i) {
1324                Some(x) if x.name == "l" && !x.text.is_empty() => x.text.as_str(),
1325                _ => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockInputsMetaCorrupted.into(), location: location.to_owned() })),
1326            };
1327            let def = self.decl_local(upvar_target.into(), Value::from(0.0f64), &location)?;
1328            upvars.push_boxed(def.def.ref_at(VarLocation::Local));
1329        }
1330
1331        let mut args = Vec::with_capacity(argc);
1332        for (param, expr) in iter::zip(&block_info.params, &stmt.children[..argc]) {
1333            match param.1 {
1334                ParamType::Evaluated => args.push_boxed(self.parse_expr(expr, &location)?),
1335                ParamType::Unevaluated => args.push_boxed(self.parse_closure(expr, ClosureKind::Reporter, true, &location)?),
1336            }
1337        }
1338
1339        Ok(Box::new_with(|| FnCall { function: function.0, args, upvars, info }))
1340    }
1341    #[inline(never)]
1342    fn parse_send_message_common(&mut self, stmt: &Xml, location: &LocationRef) -> Result<Box<NetworkMessage>, Box<Error>> {
1343        let msg_type = match stmt.children.get(0) {
1344            Some(value) if value.name != "comment" => value.text.as_str(),
1345            _ => return Err(Box::new_with(|| Error { kind: ProjectError::BlockMissingOption.into(), location: location.to_owned() })),
1346        };
1347        let fields = match self.role.msg_types.get(msg_type) {
1348            None => return Err(Box::new_with(|| Error { kind: CompileError::UnknownMessageType { msg_type: msg_type.into() }.into(), location: location.to_owned() })),
1349            Some(x) => x,
1350        };
1351
1352        let (argc, comment) = stmt.children.iter().enumerate().find(|(_, x)| x.name == "comment").map(|(i, x)| (i, Some(x.text.as_str()))).unwrap_or((stmt.children.len(), None));
1353        assert!(argc >= 1); // due to msg_type from above
1354
1355        let values = stmt.children[1..argc - 1].iter().map(|x| self.parse_expr(x, location)).collect::<Result<Vec<_>,_>>()?;
1356        if fields.len() != values.len() {
1357            return Err(Box::new_with(|| Error { kind: CompileError::MessageTypeWrongNumberArgs { msg_type: msg_type.into(), got: values.len(), expected: fields.len() }.into(), location: location.to_owned() }));
1358        }
1359
1360        let target_xml = &stmt.children[argc - 1];
1361        let target = match target_xml.get(&["option"]) {
1362            Some(x) => match x.text.as_str() {
1363                "" => return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1364                x => Box::new_with(|| x.into()),
1365            }
1366            None => self.parse_expr(target_xml, location)?,
1367        };
1368
1369        let info = Box::new_with(|| BlockInfo { comment: comment.map(CompactString::new), location: location.collab_id.map(CompactString::new) });
1370        Ok(Box::new_with(|| NetworkMessage { target, msg_type: msg_type.into(), values: fields.iter().map(|&x| CompactString::new(x)).zip(values.into_iter().map(|x| *x)).collect(), info }))
1371    }
1372    #[inline(never)]
1373    fn parse_unknown_common(&mut self, stmt: &Xml, location: &LocationRef) -> Result<(Vec<Expr>, Box<BlockInfo>), Box<Error>> {
1374        let (argc, comment) = stmt.children.iter().enumerate().find(|(_, x)| x.name == "comment").map(|(i, x)| (i, Some(x.text.as_str()))).unwrap_or((stmt.children.len(), None));
1375        let mut args = Vec::with_capacity(argc);
1376        for arg in stmt.children[..argc].iter() {
1377            args.push_boxed(self.parse_expr(arg, &location)?);
1378        }
1379        Ok((args, Box::new_with(|| BlockInfo { comment: comment.map(CompactString::new), location: location.collab_id.map(CompactString::new) })))
1380    }
1381    #[inline(never)]
1382    fn parse_block(&mut self, stmt: &Xml) -> Result<Vec<Stmt>, Box<Error>> {
1383        let mut location = Box::new_with(|| LocationRef {
1384            role: Some(&self.role.name),
1385            entity: Some(&self.entity.name),
1386            collab_id: get_collab_id(stmt),
1387            block_type: None,
1388        });
1389        let s = match stmt.attr("s") {
1390            None => return Err(Box::new_with(|| Error { kind: ProjectError::BlockWithoutType.into(), location: location.to_owned() })),
1391            Some(v) => v.value.as_str(),
1392        };
1393        location.block_type = Some(s);
1394
1395        match s {
1396            "doDeclareVariables" => {
1397                let info = self.check_children_get_info(stmt, 1, &location)?;
1398                let mut vars = vec![];
1399                for var in stmt.children[0].children.iter() {
1400                    let entry = self.decl_local(var.text.clone(), 0f64.into(), &location)?;
1401                    vars.push(entry.def.clone());
1402                }
1403                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::DeclareLocals { vars }, info }))
1404            }
1405            "doSetVar" | "doChangeVar" => {
1406                let info = self.check_children_get_info(stmt, 2, &location)?;
1407                let var = match stmt.children[0].name.as_str() {
1408                    "l" => self.reference_var(&stmt.children[0].text, &location)?,
1409                    _ => return Err(Box::new_with(|| Error { kind: CompileError::DerefAssignment.into(), location: location.to_owned() })),
1410                };
1411                let value = self.parse_expr(&stmt.children[1], &location)?;
1412                match s {
1413                    "doSetVar" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Assign { var: *var, value }, info })),
1414                    "doChangeVar" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::AddAssign { var: *var, value }, info })),
1415                    _ => unreachable!(),
1416                }
1417            }
1418            "doShowVar" | "doHideVar" => {
1419                let info = self.check_children_get_info(stmt, 1, &location)?;
1420                let var = match stmt.children[0].name.as_str() {
1421                    "l" => self.reference_var(&stmt.children[0].text, &location)?,
1422                    _ => return Err(Box::new_with(|| Error { kind: CompileError::DerefAssignment.into(), location: location.to_owned() })),
1423                };
1424                match s {
1425                    "doShowVar" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ShowVar { var: *var }, info })),
1426                    "doHideVar" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::HideVar { var: *var }, info })),
1427                    _ => unreachable!(),
1428                }
1429            }
1430            "doFor" => {
1431                let info = self.check_children_get_info(stmt, 4, &location)?;
1432
1433                let var = match stmt.children[0].name.as_str() {
1434                    "l" => stmt.children[0].text.as_str(),
1435                    _ => return Err(Box::new_with(|| Error { kind: ProjectError::UpvarNotConst.into(), location: location.to_owned() })),
1436                };
1437                let start = self.parse_expr(&stmt.children[1], &location)?;
1438                let stop = self.parse_expr(&stmt.children[2], &location)?;
1439                let var = self.decl_local(CompactString::new(var), 0f64.into(), &location)?.def.ref_at(VarLocation::Local); // define after bounds, but before loop body
1440                let script = self.parse(&stmt.children[3])?;
1441
1442                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ForLoop { var: *var, start, stop, stmts: script.stmts }, info }))
1443            }
1444            "doForEach" => {
1445                let info = self.check_children_get_info(stmt, 3, &location)?;
1446
1447                let var = match stmt.children[0].name.as_str() {
1448                    "l" => stmt.children[0].text.as_str(),
1449                    _ => return Err(Box::new_with(|| Error { kind: ProjectError::UpvarNotConst.into(), location: location.to_owned() })),
1450                };
1451                let items = self.parse_expr(&stmt.children[1], &location)?;
1452                let var = self.decl_local(CompactString::new(var), 0f64.into(), &location)?.def.ref_at(VarLocation::Local); // define after bounds, but before loop body
1453                let script = self.parse(&stmt.children[2])?;
1454
1455                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ForeachLoop { var: *var, items, stmts: script.stmts }, info }))
1456            }
1457            "doRepeat" | "doUntil" | "doIf" => {
1458                let info = self.check_children_get_info(stmt, 2, &location)?;
1459                let expr = self.parse_expr(&stmt.children[0], &location)?;
1460                let script = self.parse(&stmt.children[1])?;
1461
1462                match s {
1463                    "doRepeat" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Repeat { times: expr, stmts: script.stmts }, info })),
1464                    "doUntil" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::UntilLoop { condition: expr, stmts: script.stmts }, info })),
1465                    "doIf" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::If { condition: expr, then: script.stmts }, info })),
1466                    _ => unreachable!(),
1467                }
1468            }
1469            "doForever" => {
1470                let info = self.check_children_get_info(stmt, 1, &location)?;
1471                let script = self.parse(&stmt.children[0])?;
1472                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::InfLoop { stmts: script.stmts }, info }))
1473            }
1474            "doIfElse" => {
1475                let info = self.check_children_get_info(stmt, 3, &location)?;
1476                let condition = self.parse_expr(&stmt.children[0], &location)?;
1477                let then_script = self.parse(&stmt.children[1])?;
1478                let otherwise_script = self.parse(&stmt.children[2])?;
1479                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::IfElse { condition, then: then_script.stmts, otherwise: otherwise_script.stmts }, info }))
1480            }
1481            "doTryCatch" => {
1482                let info = self.check_children_get_info(stmt, 3, &location)?;
1483                let code_script = self.parse(&stmt.children[0])?;
1484                let var = match stmt.children[1].name.as_str() {
1485                    "l" => self.decl_local(stmt.children[1].text.clone(), 0f64.into(), &location)?.def.ref_at(VarLocation::Local),
1486                    _ => return Err(Box::new_with(|| Error { kind: ProjectError::UpvarNotConst.into(), location: location.to_owned() })),
1487                };
1488                let handler_script = self.parse(&stmt.children[2])?;
1489                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::TryCatch { code: code_script.stmts, var: *var, handler: handler_script.stmts }, info }))
1490            }
1491            "doWarp" => {
1492                let info = self.check_children_get_info(stmt, 1, &location)?;
1493                let script = self.parse(&stmt.children[0])?;
1494                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Warp { stmts: script.stmts }, info }))
1495            }
1496            "doDeleteFromList" => {
1497                let info = self.check_children_get_info(stmt, 2, &location)?;
1498                let list = self.parse_expr(&stmt.children[1], &location)?;
1499                match stmt.children[0].get(&["option"]) {
1500                    Some(opt) => match opt.text.as_str() {
1501                        "last" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListRemoveLast { list }, info })),
1502                        "all" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListRemoveAll { list }, info })),
1503                        "" => Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1504                        x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1505                    }
1506                    None => {
1507                        let index = self.parse_expr(&stmt.children[0], &location)?;
1508                        Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListRemove { list, index }, info }))
1509                    }
1510                }
1511            }
1512            "doInsertInList" => {
1513                let info = self.check_children_get_info(stmt, 3, &location)?;
1514                let value = self.parse_expr(&stmt.children[0], &location)?;
1515                let list = self.parse_expr(&stmt.children[2], &location)?;
1516                match stmt.children[1].get(&["option"]) {
1517                    Some(opt) => match opt.text.as_str() {
1518                        "last" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListInsertLast { list, value }, info })),
1519                        "random" | "any" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListInsertRandom { list, value }, info })),
1520                        "" => Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1521                        x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1522                    }
1523                    None => {
1524                        let index = self.parse_expr(&stmt.children[1], &location)?;
1525                        Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListInsert { list, value, index }, info }))
1526                    }
1527                }
1528            }
1529            "doReplaceInList" => {
1530                let info = self.check_children_get_info(stmt, 3, &location)?;
1531                let value = self.parse_expr(&stmt.children[2], &location)?;
1532                let list = self.parse_expr(&stmt.children[1], &location)?;
1533                match stmt.children[0].get(&["option"]) {
1534                    Some(opt) => match opt.text.as_str() {
1535                        "last" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListAssignLast { list, value }, info })),
1536                        "random" | "any" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListAssignRandom { list, value }, info })),
1537                        "" => Err(Box::new_with(|| Error{ kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
1538                        x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
1539                    }
1540                    None => {
1541                        let index = self.parse_expr(&stmt.children[0], &location)?;
1542                        Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ListAssign { list, value, index }, info }))
1543                    }
1544                }
1545            }
1546            "doStopThis" => {
1547                let info = self.check_children_get_info(stmt, 1, &location)?;
1548                let mode = match self.grab_option(&stmt.children[0], &location)? {
1549                    "all" => StopMode::All,
1550                    "all scenes" => StopMode::AllScenes,
1551                    "this script" => StopMode::ThisScript,
1552                    "this block" => StopMode::ThisBlock,
1553                    "all but this script" => StopMode::AllButThisScript,
1554                    "other scripts in sprite" => StopMode::OtherScriptsInSprite,
1555                    x => return Err(Box::new_with(|| Error { kind: CompileError::CurrentlyUnsupported { msg: format_compact!("{s} with stop mode {x:?} is currently not supported") }.into(), location: location.to_owned() })),
1556                };
1557                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Stop { mode }, info }))
1558            }
1559            "doSwitchToCostume" => {
1560                let info = self.check_children_get_info(stmt, 1, &location)?;
1561                let val = &stmt.children[0];
1562
1563                if val.name == "l" && val.get(&["option"]).is_some() {
1564                    match self.grab_option(val, &location)? {
1565                        "Turtle" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetCostume { costume: Box::new_with(|| "".into()) }, info })),
1566                        x => Err(Box::new_with(|| Error { kind: CompileError::CurrentlyUnsupported { msg: format_compact!("{s} with builtin project costume ({x}) currently not supported") }.into(), location: location.to_owned() })),
1567                    }
1568                } else {
1569                    let costume = self.parse_expr(val, &location)?;
1570                    Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetCostume { costume }, info }))
1571                }
1572            }
1573            "playSound" | "doPlaySoundUntilDone" => {
1574                let blocking = s == "doPlaySoundUntilDone";
1575                let info = self.check_children_get_info(stmt, 1, &location)?;
1576                let sound = self.parse_expr(&stmt.children[0], &location)?;
1577                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::PlaySound { sound, blocking }, info }))
1578            }
1579            "doPlayNote" => {
1580                let info = self.check_children_get_info(stmt, 2, &location)?;
1581                let notes = self.parse_expr(&stmt.children[0], &location)?;
1582                let beats = self.parse_expr(&stmt.children[1], &location)?;
1583                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::PlayNotes { notes, beats, blocking: true }, info }))
1584            }
1585            "doRest" => {
1586                let info = self.check_children_get_info(stmt, 1, &location)?;
1587                let beats = self.parse_expr(&stmt.children[0], &location)?;
1588                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Rest { beats }, info }))
1589            }
1590            "setHeading" => {
1591                let info = self.check_children_get_info(stmt, 1, &location)?;
1592                let child = &stmt.children[0];
1593
1594                if child.name == "l" && child.get(&["option"]).is_some() {
1595                    let opt = self.grab_option(child, &location)?;
1596                    match opt {
1597                        "random" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetHeadingRandom, info })),
1598                        _ => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: opt.into() }.into(), location: location.to_owned() })),
1599                    }
1600                } else {
1601                    let value = self.parse_expr(child, &location)?;
1602                    Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetHeading { value }, info }))
1603                }
1604            }
1605            "doGotoObject" => {
1606                let info = self.check_children_get_info(stmt, 1, &location)?;
1607                let child = &stmt.children[0];
1608
1609                if child.name == "l" && child.get(&["option"]).is_some() {
1610                    let opt = self.grab_option(child, &location)?;
1611                    match opt {
1612                        "random position" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::GotoRandom, info })),
1613                        "mouse-pointer" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::GotoMouse, info })),
1614                        "center" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::GotoXY { x: Box::new_with(|| 0f64.into()), y: Box::new_with(|| 0f64.into()) }, info })),
1615                        _ => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: opt.into() }.into(), location: location.to_owned() })),
1616                    }
1617                }
1618                else {
1619                    let target = self.parse_expr(child, &location)?;
1620                    Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Goto { target }, info }))
1621                }
1622            }
1623            "doFaceTowards" => {
1624                let info = self.check_children_get_info(stmt, 1, &location)?;
1625                let child = &stmt.children[0];
1626
1627                if child.name == "l" && child.get(&["option"]).is_some() {
1628                    let opt = self.grab_option(child, &location)?;
1629                    match opt {
1630                        "center" => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::PointTowardsXY { x: Box::new_with(|| 0.0.into()), y: Box::new_with(|| 0.0.into()) }, info })),
1631                        _ => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: opt.into() }.into(), location: location.to_owned() })),
1632                    }
1633                } else {
1634                    let target = self.parse_expr(child, &location)?;
1635                    Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::PointTowards { target }, info }))
1636                }
1637            }
1638            "setColor" => {
1639                let info = self.check_children_get_info(stmt, 1, &location)?;
1640                match stmt.get(&["color"]) {
1641                    Some(color) => match parse_color(&color.text) {
1642                        Some(color) => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetPenColor { color }, info })),
1643                        None => Err(Box::new_with(|| Error { kind: ProjectError::ColorUnknownValue { color: color.text.clone() }.into(), location: location.to_owned() })),
1644                    }
1645                    None => Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotConst.into(), location: location.to_owned() })),
1646                }
1647            }
1648            "doSocketMessage" => {
1649                let res = self.parse_send_message_common(stmt, &location)?;
1650                Ok(Vec::new_with_single(|| {
1651                    let NetworkMessage { target, msg_type, values, info } = *res;
1652                    Stmt { kind: StmtKind::SendNetworkMessage { target, msg_type, values }, info }
1653                }))
1654            }
1655            "doRun" | "fork" => {
1656                let is_run = s == "doRun";
1657                let info = self.check_children_get_info(stmt, 2, &location)?;
1658                let closure = self.parse_expr(&stmt.children[0], &location)?;
1659                let mut args = Vec::with_capacity(stmt.children[1].children.len());
1660                for arg in stmt.children[1].children.iter() {
1661                    args.push_boxed(self.parse_expr(arg, &location)?);
1662                }
1663                match is_run {
1664                    true => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::CallClosure { new_entity: None, closure, args }, info })),
1665                    false => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ForkClosure { closure, args }, info })),
1666                }
1667            }
1668            "doTellTo" => {
1669                let info = self.check_children_get_info(stmt, 3, &location)?;
1670                let entity = self.grab_entity(&stmt.children[0], BlockInfo::none(), &location)?;
1671                let closure = self.parse_expr(&stmt.children[1], &location)?;
1672                let mut args = Vec::with_capacity(stmt.children[2].children.len());
1673                for arg in stmt.children[2].children.iter() {
1674                    args.push_boxed(self.parse_expr(arg, &location)?);
1675                }
1676                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::CallClosure { new_entity: Some(entity), closure, args }, info }))
1677            }
1678            "setEffect" => {
1679                let info = self.check_children_get_info(stmt, 2, &location)?;
1680                let effect = self.parse_effect(&stmt.children[0], &location)?;
1681                let value = self.parse_expr(&stmt.children[1], &location)?;
1682                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetEffect { kind: effect, value }, info }))
1683            }
1684            "changeEffect" => {
1685                let info = self.check_children_get_info(stmt, 2, &location)?;
1686                let effect = self.parse_effect(&stmt.children[0], &location)?;
1687                let delta = self.parse_expr(&stmt.children[1], &location)?;
1688                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ChangeEffect { kind: effect, delta }, info }))
1689            }
1690            "setPenHSVA" => {
1691                let info = self.check_children_get_info(stmt, 2, &location)?;
1692                let attr = self.parse_pen_attr(&stmt.children[0], &location)?;
1693                let value = self.parse_expr(&stmt.children[1], &location)?;
1694                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SetPenAttr { attr, value }, info }))
1695            }
1696            "changePenHSVA" => {
1697                let info = self.check_children_get_info(stmt, 2, &location)?;
1698                let attr = self.parse_pen_attr(&stmt.children[0], &location)?;
1699                let delta = self.parse_expr(&stmt.children[1], &location)?;
1700                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::ChangePenAttr { attr, delta }, info }))
1701            }
1702            "doRunRPC" => {
1703                let rpc = self.parse_rpc(stmt, &location)?;
1704                Ok(Vec::new_with_single(|| (*rpc).into()))
1705            }
1706            "createClone" => {
1707                let info = self.check_children_get_info(stmt, 1, &location)?;
1708                let target = self.grab_entity(&stmt.children[0], BlockInfo::none(), &location)?;
1709                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::Clone { target }, info }))
1710            }
1711            "doSend" => {
1712                let info = self.check_children_get_info(stmt, 2, &location)?;
1713                let msg_type = self.parse_expr(&stmt.children[0], &location)?;
1714                let target = Some(self.grab_entity(&stmt.children[1], BlockInfo::none(), &location)?);
1715                Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::SendLocalMessage { msg_type, target, wait: false }, info }))
1716            }
1717            "doStopAllSounds" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::StopSounds, info })),
1718            "doBroadcast" => self.parse_1_args(stmt, &location).map(|(msg_type, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SendLocalMessage { msg_type, target: None, wait: false }, info })),
1719            "doBroadcastAndWait" => self.parse_1_args(stmt, &location).map(|(msg_type, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SendLocalMessage { msg_type, target: None, wait: true }, info })),
1720            "doPauseAll" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::Pause, info })),
1721            "write" => self.parse_2_args(stmt, &location).map(|(content, font_size, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Write { content, font_size }, info })),
1722            "doSocketResponse" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SendNetworkReply { value }, info })),
1723            "changeScale" => self.parse_1_args(stmt, &location).map(|(delta, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::ChangeSize { delta, }, info })),
1724            "setScale" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SetSize { value }, info })),
1725            "doSayFor" => self.parse_2_args(stmt, &location).map(|(content, duration, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Say { content, duration: Some(duration) }, info })),
1726            "doThinkFor" => self.parse_2_args(stmt, &location).map(|(content, duration, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Think { content, duration: Some(duration) }, info })),
1727            "bubble" => self.parse_1_args(stmt, &location).map(|(content, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Say { content, duration: None }, info })),
1728            "doThink" => self.parse_1_args(stmt, &location).map(|(content, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Think { content, duration: None }, info })),
1729            "doThrow" => self.parse_1_args(stmt, &location).map(|(error, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Throw { error }, info })),
1730            "hide" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::SetVisible { value: false }, info })),
1731            "show" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::SetVisible { value: true }, info })),
1732            "removeClone" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::DeleteClone, info })),
1733            "doWaitUntil" => self.parse_1_args(stmt, &location).map(|(condition, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::WaitUntil { condition, }, info })),
1734            "changeSize" => self.parse_1_args(stmt, &location).map(|(delta, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::ChangePenSize { delta, }, info })),
1735            "setSize" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SetPenSize { value }, info })),
1736            "doAddToList" => self.parse_2_args(stmt, &location).map(|(value, list, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::ListInsertLast { value, list }, info })),
1737            "doReport" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Return { value }, info })),
1738            "doStamp" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::Stamp, info })),
1739            "doWait" => self.parse_1_args(stmt, &location).map(|(seconds, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Sleep { seconds, }, info })),
1740            "forward" => self.parse_1_args(stmt, &location).map(|(distance, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Forward { distance, }, info })),
1741            "turn" => self.parse_1_args(stmt, &location).map(|(angle, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::TurnRight { angle, }, info })),
1742            "turnLeft" => self.parse_1_args(stmt, &location).map(|(angle, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::TurnLeft { angle, }, info })),
1743            "setXPosition" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SetX { value }, info })),
1744            "setYPosition" => self.parse_1_args(stmt, &location).map(|(value, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::SetY { value }, info })),
1745            "changeXPosition" => self.parse_1_args(stmt, &location).map(|(delta, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::ChangeX { delta }, info })),
1746            "changeYPosition" => self.parse_1_args(stmt, &location).map(|(delta, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::ChangeY { delta }, info })),
1747            "gotoXY" => self.parse_2_args(stmt, &location).map(|(x, y, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::GotoXY { x, y }, info })),
1748            "bounceOffEdge" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::BounceOffEdge, info })),
1749            "down" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::SetPenDown { value: true }, info })),
1750            "up" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::SetPenDown { value: false }, info })),
1751            "clear" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::PenClear, info })),
1752            "doAsk" => self.parse_1_args(stmt, &location).map(|(prompt, info)| Vec::new_with_single(|| Stmt { kind: StmtKind::Ask { prompt, }, info })),
1753            "doResetTimer" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::ResetTimer, info })),
1754            "clearEffects" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::ClearEffects, info })),
1755            "doWearNextCostume" => self.parse_0_args(stmt, &location).map(|info| Vec::new_with_single(|| Stmt { kind: StmtKind::NextCostume, info })),
1756            x => {
1757                let (args, info) = self.parse_unknown_common(stmt, &location)?;
1758                match self.parser.stmt_replacements.iter().find(|r| r.0 == x) {
1759                    Some(f) => f.1(args, info, &location),
1760                    None => Ok(Vec::new_with_single(|| Stmt { kind: StmtKind::UnknownBlock { name: x.into(), args }, info }))
1761                }
1762            }
1763        }
1764    }
1765    #[inline(never)]
1766    fn reference_var(&mut self, name: &str, location: &LocationRef) -> Result<Box<VariableRef>, Box<Error>> {
1767        for (i, locals) in self.locals.iter().rev().enumerate() {
1768            if let Some(x) = locals.0.get(name) {
1769                let local_ref = x.def.ref_at(VarLocation::Local);
1770                if i != 0 {
1771                    let (locals, captures) = self.locals.last_mut().unwrap();
1772                    locals.define(local_ref.name.clone(), 0.0.into()).unwrap();
1773                    captures.push_boxed(local_ref.clone());
1774                }
1775                return Ok(local_ref);
1776            }
1777        }
1778        if let Some(x) = self.entity.fields.get(name) {
1779            let field_ref = x.def.ref_at(VarLocation::Field);
1780            if self.locals.len() >= 2 {
1781                let (locals, captures) = self.locals.last_mut().unwrap();
1782                locals.define(field_ref.name.clone(), 0.0.into()).unwrap();
1783                captures.push_boxed(field_ref);
1784                return Ok(x.def.ref_at(VarLocation::Local));
1785            } else {
1786                return Ok(field_ref);
1787            }
1788        }
1789        if let Some(x) = self.role.globals.get(name) {
1790            return Ok(x.def.ref_at(VarLocation::Global));
1791        }
1792        Err(Box::new_with(|| Error { kind: CompileError::UndefinedVariable { name: name.into() }.into(), location: location.to_owned() }))
1793    }
1794    #[inline(never)]
1795    fn reference_fn(&self, name: &str, location: &LocationRef) -> Result<Box<(FnRef, Value)>, Box<Error>> {
1796        let locs = [(&self.entity.funcs, FnLocation::Method), (&self.role.funcs, FnLocation::Global)];
1797        match locs.iter().find_map(|v| v.0.get(name).map(|x| (v, x))) {
1798            Some((v, x)) => Ok(Box::new_with(|| (*x.def.fn_ref_at(v.1), x.init.clone()))),
1799            None => Err(Box::new_with(|| Error { kind: CompileError::UndefinedFn { name: name.into() }.into(), location: location.to_owned() }))
1800        }
1801    }
1802    #[inline(always)]
1803    fn parse_0_args(&mut self, expr: &Xml, location: &LocationRef) -> Result<Box<BlockInfo>, Box<Error>> {
1804        self.check_children_get_info(expr, 0, location)
1805    }
1806    #[inline(always)]
1807    fn parse_1_args(&mut self, expr: &Xml, location: &LocationRef) -> Result<(Box<Expr>, Box<BlockInfo>), Box<Error>> {
1808        let info = self.check_children_get_info(expr, 1, location)?;
1809        let a = self.parse_expr(&expr.children[0], location)?;
1810        Ok((a, info))
1811    }
1812    #[inline(always)]
1813    fn parse_2_args(&mut self, expr: &Xml, location: &LocationRef) -> Result<(Box<Expr>, Box<Expr>, Box<BlockInfo>), Box<Error>> {
1814        let info = self.check_children_get_info(expr, 1, location)?;
1815        let a = self.parse_expr(&expr.children[0], location)?;
1816        let b = self.parse_expr(&expr.children[1], location)?;
1817        Ok((a, b, info))
1818    }
1819    #[inline(never)]
1820    fn parse_bool(&self, val: &str, location: &LocationRef) -> Result<Box<Expr>, Box<Error>> {
1821        match val {
1822            "true" => Ok(Box::new_with(|| true.into())),
1823            "false" => Ok(Box::new_with(|| false.into())),
1824            _ => Err(Box::new_with(|| Error { kind: ProjectError::BoolUnknownValue { got: val.into() }.into(), location: location.to_owned() })),
1825        }
1826    }
1827    #[inline(never)]
1828    fn parse_closure(&mut self, expr: &Xml, kind: ClosureKind, inline_script: bool, location: &LocationRef) -> Result<Box<Expr>, Box<Error>> {
1829        let (info, script) = match inline_script {
1830            false => (self.check_children_get_info(expr, 2, location)?, &expr.children[0]),
1831            true => (BlockInfo::none(), expr),
1832        };
1833
1834        let mut params = SymbolTable::new(self.parser);
1835        fn define_param(params: &mut SymbolTable, name: CompactString, location: &LocationRef) -> Result<(), Box<Error>> {
1836            match params.define(name, 0.0.into()) {
1837                Ok(None) => Ok(()),
1838                Ok(Some(prev)) => Err(Box::new_with(|| Error { kind: CompileError::InputsWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
1839                Err(SymbolError::ConflictingTrans { trans_name, names }) => Err(Box::new_with(|| Error { kind: CompileError::LocalsWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
1840                Err(SymbolError::NameTransformError { name }) => Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
1841            }
1842        }
1843        if !inline_script {
1844            for input in expr.children[1].children.iter() {
1845                define_param(&mut params, input.text.clone(), location)?;
1846            }
1847        }
1848
1849        let prev_autofill_args_len = self.autofill_args.as_ref().map(|x| x.len()).unwrap_or_default();
1850        let prev_autofill_args = match params.is_empty() && !inline_script {
1851            true => Some(mem::replace(&mut self.autofill_args, Some(vec![]))),
1852            false => None,
1853        };
1854
1855        self.locals.push((params.clone(), Default::default()));
1856        let locals_len = self.locals.len();
1857        let stmts = match kind {
1858            ClosureKind::Command => self.parse(script)?.stmts,
1859            ClosureKind::Reporter | ClosureKind::Predicate => match script.name.as_str() {
1860                "autolambda" => {
1861                    let _ = self.check_children_get_info(script, 1, location)?;
1862                    let value = self.parse_expr(&script.children[0], location)?;
1863                    Vec::new_with_single(|| Stmt { kind: StmtKind::Return { value }, info: BlockInfo::none() })
1864                }
1865                "script" => {
1866                    let mut stmts = self.parse(script)?.stmts;
1867                    match self.autofill_args.as_mut() {
1868                        Some(autofill_args) if stmts.is_empty() => {
1869                            let var = new_autofill_arg(autofill_args, &self.parser, &location)?;
1870                            stmts.push_boxed(Box::new_with(|| Stmt { kind: StmtKind::Return { value: Box::new(Expr { kind: ExprKind::Variable { var: *var }, info: BlockInfo::none() }) }, info: BlockInfo::none() }));
1871                        }
1872                        _ => (),
1873                    }
1874                    stmts
1875                }
1876                _ => {
1877                    let value = self.parse_expr(script, location)?;
1878                    Vec::new_with_single(|| Stmt { kind: StmtKind::Return { value }, info: BlockInfo::none() })
1879                }
1880            }
1881        };
1882        assert_eq!(locals_len, self.locals.len());
1883        let (_, mut captures) = self.locals.pop().unwrap();
1884        for var in captures.iter() {
1885            self.reference_var(&var.name, location)?;
1886        }
1887
1888        match prev_autofill_args {
1889            Some(prev_autofill_args) => for autofill_arg in mem::replace(&mut self.autofill_args, prev_autofill_args).unwrap_or_default() {
1890                define_param(&mut params, autofill_arg.name, location)?;
1891            }
1892            None => for autofill_arg in self.autofill_args.as_deref().map(|x| &x[prev_autofill_args_len..]).unwrap_or_default() {
1893                captures.push(autofill_arg.clone());
1894            }
1895        }
1896
1897        Ok(Box::new_with(|| Expr { kind: ExprKind::Closure { params: params.into_defs(), captures, kind, stmts }, info }))
1898    }
1899    #[inline(never)]
1900    fn parse_expr(&mut self, expr: &Xml, location: &LocationRef) -> Result<Box<Expr>, Box<Error>> {
1901        let mut location = Box::new_with(|| LocationRef {
1902            role: location.role,
1903            entity: location.entity,
1904            collab_id: get_collab_id(expr).or(location.collab_id),
1905            block_type: location.block_type,
1906        });
1907
1908        match expr.name.as_str() {
1909            "l" => match expr.children.first() {
1910                Some(child) if child.name == "bool" => self.parse_bool(&child.text, &location),
1911                Some(child) if child.name == "option" => Ok(Box::new_with(|| Expr { kind: ExprKind::Value(child.text.clone().into()), info: BlockInfo::none() })),
1912                _ => match self.autofill_args.as_mut() {
1913                    Some(autofill_args) if expr.text.is_empty() => {
1914                        let var = new_autofill_arg(autofill_args, &self.parser, &location)?;
1915                        Ok(Box::new_with(|| Expr { kind: ExprKind::Variable { var: *var }, info: BlockInfo::none() }))
1916                    }
1917                    _ => Ok(Box::new_with(|| expr.text.clone().into())),
1918                }
1919            }
1920            "bool" => self.parse_bool(&expr.text, &location),
1921            "list" => {
1922                let ref_id = expr.attr("id").and_then(|x| x.value.parse().ok()).map(RefId);
1923                let values = match expr.attr("struct").map(|x| x.value.as_str()) {
1924                    Some("atomic") => InlineListIter::new(&expr.text).map(Into::into).collect(),
1925                    _ => {
1926                        let mut values = Vec::with_capacity(expr.children.len());
1927                        for item in expr.children.iter() {
1928                            values.push_boxed(match item.name.as_str() {
1929                                "item" => match item.children.get(0) {
1930                                    Some(x) => self.parse_expr(x, &location)?,
1931                                    None => Box::new_with(|| Expr { kind: ExprKind::Value(Value::String(item.text.clone())), info: BlockInfo::none() }),
1932                                }
1933                                _ => self.parse_expr(item, &location)?,
1934                            });
1935                        }
1936                        values
1937                    }
1938                };
1939
1940                let mut evaluated = Vec::with_capacity(values.len());
1941                for value in values.iter() {
1942                    match &value.kind {
1943                        ExprKind::Value(x) => evaluated.push_with(|| x.clone()),
1944                        _ => match ref_id {
1945                            Some(_) => return Err(Box::new_with(|| Error { kind: ProjectError::ValueNotEvaluated.into(), location: location.to_owned() })),
1946                            None => return Ok(Box::new_with(|| Expr { kind: ExprKind::MakeList { values }, info: BlockInfo::none() })),
1947                        }
1948                    }
1949                }
1950                Ok(Box::new_with(|| Value::List(evaluated, ref_id).into()))
1951            }
1952            "ref" => match expr.attr("id").and_then(|x| x.value.parse().ok()).map(RefId) {
1953                Some(ref_id) => Ok(Box::new_with(|| Value::Ref(ref_id).into())),
1954                None => return Err(Box::new_with(|| Error { kind: ProjectError::RefMissingId.into(), location: location.to_owned() })),
1955            }
1956            "custom-block" => {
1957                let res = self.parse_fn_call(expr, &location)?;
1958                Ok(Box::new_with(|| {
1959                    let FnCall { function, args, upvars, info } = *res;
1960                    Expr { kind: ExprKind::CallFn { function, args, upvars }, info }
1961                }))
1962            }
1963            "script" => self.parse_closure(expr, ClosureKind::Command, true, &location),
1964            "block" => {
1965                if let Some(var) = expr.attr("var") {
1966                    let info = self.check_children_get_info(expr, 0, &location)?;
1967                    let var = self.reference_var(&var.value, &location)?;
1968                    return Ok(Box::new_with(|| Expr { kind: ExprKind::Variable { var: *var }, info }));
1969                }
1970                let s = match expr.attr("s") {
1971                    None => return Err(Box::new_with(|| Error { kind: ProjectError::BlockWithoutType.into(), location: location.to_owned() })),
1972                    Some(v) => v.value.as_str(),
1973                };
1974                location.block_type = Some(s);
1975
1976                match s {
1977                    "reportVariadicSum" => self.parse_1_args(expr, &location).map(|(values, info)| Box::new_with(|| Expr { kind: ExprKind::Add { values }, info })),
1978                    "reportVariadicProduct" => self.parse_1_args(expr, &location).map(|(values, info)| Box::new_with(|| Expr { kind: ExprKind::Mul { values }, info })),
1979                    "reportVariadicMin" => self.parse_1_args(expr, &location).map(|(values, info)| Box::new_with(|| Expr { kind: ExprKind::Min { values }, info })),
1980                    "reportVariadicMax" => self.parse_1_args(expr, &location).map(|(values, info)| Box::new_with(|| Expr { kind: ExprKind::Max { values }, info })),
1981
1982                    "reportSum" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Add { values: Box::new_with(|| Expr { kind: ExprKind::MakeList { values: vec![*left, *right] }, info: BlockInfo::none() }) }, info })),
1983                    "reportProduct" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Mul { values: Box::new_with(|| Expr { kind: ExprKind::MakeList { values: vec![*left, *right] }, info: BlockInfo::none() }) }, info })),
1984                    "reportMin" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Min { values: Box::new_with(|| Expr { kind: ExprKind::MakeList { values: vec![*left, *right] }, info: BlockInfo::none() }) }, info })),
1985                    "reportMax" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Max { values: Box::new_with(|| Expr { kind: ExprKind::MakeList { values: vec![*left, *right] }, info: BlockInfo::none() }) }, info })),
1986
1987                    "reportDifference" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Sub { left, right }, info })),
1988                    "reportQuotient" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Div { left, right }, info })),
1989                    "reportModulus" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Mod { left, right }, info })),
1990                    "reportPower" => self.parse_2_args(expr, &location).map(|(base, power, info)| Box::new_with(|| Expr { kind: ExprKind::Pow { base, power }, info })),
1991                    "reportAtan2" => self.parse_2_args(expr, &location).map(|(y, x, info)| Box::new_with(|| Expr { kind: ExprKind::Atan2 { y, x }, info })),
1992
1993                    "reportAnd" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::And { left, right }, info })),
1994                    "reportOr" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Or { left, right }, info })),
1995
1996                    "reportIsIdentical" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Identical { left, right }, info })),
1997                    "reportEquals" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Eq { left, right }, info })),
1998                    "reportNotEquals" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Neq { left, right }, info })),
1999                    "reportLessThan" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Less { left, right }, info })),
2000                    "reportLessThanOrEquals" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::LessEq { left, right }, info })),
2001                    "reportGreaterThan" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::Greater { left, right }, info })),
2002                    "reportGreaterThanOrEquals" => self.parse_2_args(expr, &location).map(|(left, right, info)| Box::new_with(|| Expr { kind: ExprKind::GreaterEq { left, right }, info })),
2003
2004                    "reportRandom" => self.parse_2_args(expr, &location).map(|(a, b, info)| Box::new_with(|| Expr { kind: ExprKind::Random { a, b }, info })),
2005                    "reportNumbers" => self.parse_2_args(expr, &location).map(|(start, stop, info)| Box::new_with(|| Expr { kind: ExprKind::Range { start, stop }, info })),
2006
2007                    "reportNot" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::Not { value }, info })),
2008                    "reportRound" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::Round { value }, info })),
2009
2010                    "reportListLength" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::ListLen { value }, info })),
2011                    "reportListIsEmpty" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::ListIsEmpty { value }, info })),
2012
2013                    "reportStringSize" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::StrLen { value }, info })),
2014                    "reportUnicodeAsLetter" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::UnicodeToChar { value }, info })),
2015                    "reportUnicode" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::CharToUnicode { value }, info })),
2016
2017                    "reportCDR" => self.parse_1_args(expr, &location).map(|(value, info)| Box::new_with(|| Expr { kind: ExprKind::ListCdr { value }, info })),
2018                    "reportCONS" => self.parse_2_args(expr, &location).map(|(item, list, info)| Box::new_with(|| Expr { kind: ExprKind::ListCons { item, list }, info })),
2019
2020                    "reportJoinWords" => self.parse_1_args(expr, &location).map(|(values, info)| Box::new_with(|| Expr { kind: ExprKind::StrCat { values }, info })),
2021                    "reportConcatenatedLists" => self.parse_1_args(expr, &location).map(|(lists, info)| Box::new_with(|| Expr { kind: ExprKind::ListCat { lists }, info })),
2022                    "reportCrossproduct" => self.parse_1_args(expr, &location).map(|(sources, info)| Box::new_with(|| Expr { kind: ExprKind::ListCombinations { sources }, info })),
2023
2024                    "reportStageWidth" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::StageWidth, info })),
2025                    "reportStageHeight" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::StageHeight, info })),
2026
2027                    "reportMouseX" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::MouseX, info })),
2028                    "reportMouseY" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::MouseY, info })),
2029
2030                    "reportLatitude" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Latitude, info })),
2031                    "reportLongitude" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Longitude, info })),
2032
2033                    "reportKeyPressed" => self.parse_1_args(expr, &location).map(|(key, info)| Box::new_with(|| Expr { kind: ExprKind::KeyDown { key }, info })),
2034
2035                    "reportPenTrailsAsCostume" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::ImageOfDrawings, info })),
2036
2037                    "reportListContainsItem" => self.parse_2_args(expr, &location).map(|(list, value, info)| Box::new_with(|| Expr { kind: ExprKind::ListContains { list, value }, info })),
2038
2039                    "reportRPCError" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::RpcError, info })),
2040
2041                    "getScale" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Size, info })),
2042                    "reportShown" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::IsVisible, info })),
2043
2044                    "xPosition" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::XPos, info })),
2045                    "yPosition" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::YPos, info })),
2046                    "direction" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Heading, info })),
2047
2048                    "getPenDown" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::PenDown, info })),
2049
2050                    "getLastAnswer" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Answer, info })),
2051                    "getLastMessage" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Message, info })),
2052
2053                    "getTimer" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::Timer, info })),
2054
2055                    "reportMap" => self.parse_2_args(expr, &location).map(|(f, list, info)| Box::new_with(|| Expr { kind: ExprKind::Map { f, list }, info })),
2056                    "reportKeep" => self.parse_2_args(expr, &location).map(|(f, list, info)| Box::new_with(|| Expr { kind: ExprKind::Keep { f, list }, info })),
2057                    "reportFindFirst" => self.parse_2_args(expr, &location).map(|(f, list, info)| Box::new_with(|| Expr { kind: ExprKind::FindFirst { f, list }, info })),
2058                    "reportCombine" => self.parse_2_args(expr, &location).map(|(list, f, info)| Box::new_with(|| Expr { kind: ExprKind::Combine { list, f }, info })),
2059
2060                    "reifyScript" => self.parse_closure(expr, ClosureKind::Command, false, &location),
2061                    "reifyReporter" => self.parse_closure(expr, ClosureKind::Reporter, false, &location),
2062                    "reifyPredicate" => self.parse_closure(expr, ClosureKind::Predicate, false, &location),
2063
2064                    "getCostumeIdx" => self.parse_0_args(expr, &location).map(|info| Box::new_with(|| Expr { kind: ExprKind::CostumeNumber, info })),
2065
2066                    "reportNewList" => {
2067                        let (mut list, info) = self.parse_1_args(expr, &location)?;
2068                        let already_owning = match &list.kind {
2069                            ExprKind::Value(Value::List( .. )) => true,
2070                            ExprKind::MakeList { .. } => true,
2071                            _ => false,
2072                        };
2073                        Ok(match already_owning {
2074                            true => {
2075                                list.info = info;
2076                                list
2077                            }
2078                            false => Box::new_with(|| Expr { kind: ExprKind::CopyList { list }, info }),
2079                        })
2080                    }
2081
2082                    "reportGetImageAttribute" => {
2083                        let info = self.check_children_get_info(expr, 2, &location)?;
2084                        let costume = if expr.children[1].name == "l" && expr.children[1].get(&["option"]).is_some() {
2085                            match self.grab_option(&expr.children[1], &location)? {
2086                                "Turtle" => Box::new_with(|| Expr { kind: ExprKind::Value(Value::String(CompactString::default())), info: BlockInfo::none() }),
2087                                "current" => Box::new_with(|| Expr { kind: ExprKind::Costume, info: BlockInfo::none() }),
2088                                x => return Err(Box::new_with(|| Error { kind: CompileError::CurrentlyUnsupported { msg: format_compact!("{s} with builtin project costume ({x}) currently not supported") }.into(), location: location.to_owned() })),
2089                            }
2090                        } else {
2091                            self.parse_expr(&expr.children[1], &location)?
2092                        };
2093                        match self.grab_option(&expr.children[0], &location)? {
2094                            "name" => Ok(Box::new_with(|| Expr { kind: ExprKind::CostumeName { costume }, info })),
2095                            "width" => Ok(Box::new_with(|| Expr { kind: ExprKind::CostumeWidth { costume }, info })),
2096                            "height" => Ok(Box::new_with(|| Expr { kind: ExprKind::CostumeHeight { costume }, info })),
2097                            "pixels" => Ok(Box::new_with(|| Expr { kind: ExprKind::CostumePixels { costume }, info })),
2098                            x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2099                        }
2100                    }
2101                    "reportGetSoundAttribute" => {
2102                        let info = self.check_children_get_info(expr, 2, &location)?;
2103                        let sound = self.parse_expr(&expr.children[1], &location)?;
2104                        match self.grab_option(&expr.children[0], &location)? {
2105                            "name" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundName { sound }, info })),
2106                            "duration" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundDuration { sound }, info })),
2107                            "length" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundSamplesLength { sound }, info })),
2108                            "number of channels" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundChannelCount { sound }, info })),
2109                            "sample rate" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundSampleRate { sound }, info })),
2110                            "samples" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundSamples { sound }, info })),
2111                            x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2112                        }
2113                    }
2114
2115                    "reportListIndex" => self.parse_2_args(expr, &location).map(|(value, list, info)| Box::new_with(|| Expr { kind: ExprKind::ListFind { value, list }, info })),
2116                    "reportListItem" => {
2117                        let info = self.check_children_get_info(expr, 2, &location)?;
2118                        let list = self.parse_expr(&expr.children[1], &location)?.into();
2119                        match expr.children[0].get(&["option"]) {
2120                            Some(opt) => match opt.text.as_str() {
2121                                "last" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListGetLast { list }, info })),
2122                                "any" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListGetRandom { list }, info })),
2123                                "" => Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
2124                                x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2125                            }
2126                            None => {
2127                                let index = self.parse_expr(&expr.children[0], &location)?;
2128                                Ok(Box::new_with(|| Expr { kind: ExprKind::ListGet { list, index }, info }))
2129                            }
2130                        }
2131                    }
2132                    "reportLetter" => {
2133                        let info = self.check_children_get_info(expr, 2, &location)?;
2134                        let string = self.parse_expr(&expr.children[1], &location)?.into();
2135                        match expr.children[0].get(&["option"]) {
2136                            Some(opt) => match opt.text.as_str() {
2137                                "last" => Ok(Box::new_with(|| Expr { kind: ExprKind::StrGetLast { string }, info })),
2138                                "any" => Ok(Box::new_with(|| Expr { kind: ExprKind::StrGetRandom { string }, info })),
2139                                "" => Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
2140                                x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2141                            }
2142                            None => {
2143                                let index = self.parse_expr(&expr.children[0], &location)?;
2144                                Ok(Box::new_with(|| Expr { kind: ExprKind::StrGet { string, index }, info }))
2145                            }
2146                        }
2147                    }
2148                    "reportTextSplit" => {
2149                        let info = self.check_children_get_info(expr, 2, &location)?;
2150                        let text = self.parse_expr(&expr.children[0], &location)?.into();
2151                        let mode = match expr.children[1].get(&["option"]) {
2152                            Some(opt) => match opt.text.as_str() {
2153                                "letter" => TextSplitMode::Letter,
2154                                "word" => TextSplitMode::Word,
2155                                "line" => TextSplitMode::LF,
2156                                "tab" => TextSplitMode::Tab,
2157                                "cr" => TextSplitMode::CR,
2158                                "csv" => TextSplitMode::Csv,
2159                                "json" => TextSplitMode::Json,
2160                                "" => return Err(Box::new_with(|| Error { kind: CompileError::BlockOptionNotSelected.into(), location: location.to_owned() })),
2161                                x => return Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2162                            }
2163                            None => TextSplitMode::Custom(self.parse_expr(&expr.children[1], &location)?.into()),
2164                        };
2165                        Ok(Box::new_with(|| Expr { kind: ExprKind::TextSplit { text, mode }, info }))
2166                    }
2167                    "reportBoolean" => match expr.get(&["l", "bool"]) {
2168                        Some(x) => self.parse_bool(&x.text, &location),
2169                        None => Err(Box::new_with(|| Error { kind: ProjectError::BoolNoValue.into(), location: location.to_owned() })),
2170                    }
2171                    "reportMonadic" => {
2172                        let info = self.check_children_get_info(expr, 2, &location)?;
2173                        let func = self.grab_option(&expr.children[0], &location)?;
2174                        let value = self.parse_expr(&expr.children[1], &location)?;
2175                        match func {
2176                            "id" => Ok(value),
2177
2178                            "neg" => Ok(Box::new_with(|| Expr { kind: ExprKind::Neg { value }, info })),
2179                            "abs" => Ok(Box::new_with(|| Expr { kind: ExprKind::Abs { value }, info })),
2180                            "sign" => Ok(Box::new_with(|| Expr { kind: ExprKind::Sign { value }, info })),
2181                            "sqrt" => Ok(Box::new_with(|| Expr { kind: ExprKind::Sqrt { value }, info })),
2182                            "floor" => Ok(Box::new_with(|| Expr { kind: ExprKind::Floor { value }, info })),
2183                            "ceiling" => Ok(Box::new_with(|| Expr { kind: ExprKind::Ceil { value }, info })),
2184
2185                            "sin" => Ok(Box::new_with(|| Expr { kind: ExprKind::Sin { value }, info })),
2186                            "cos" => Ok(Box::new_with(|| Expr { kind: ExprKind::Cos { value }, info })),
2187                            "tan" => Ok(Box::new_with(|| Expr { kind: ExprKind::Tan { value }, info })),
2188
2189                            "asin" => Ok(Box::new_with(|| Expr { kind: ExprKind::Asin { value }, info })),
2190                            "acos" => Ok(Box::new_with(|| Expr { kind: ExprKind::Acos { value }, info })),
2191                            "atan" => Ok(Box::new_with(|| Expr { kind: ExprKind::Atan { value }, info })),
2192
2193                            "ln" => Ok(Box::new_with(|| Expr { kind: ExprKind::Log { value, base: Box::new_with(|| Constant::E.into()) }, info })),
2194                            "lg" => Ok(Box::new_with(|| Expr { kind: ExprKind::Log { value, base: Box::new_with(|| 2f64.into()) }, info })),
2195                            "log" => Ok(Box::new_with(|| Expr { kind: ExprKind::Log { value, base: Box::new_with(|| 10f64.into()) }, info })),
2196
2197                            "e^" => Ok(Box::new_with(|| Expr { kind: ExprKind::Pow { base: Box::new_with(|| Constant::E.into()), power: value }, info })),
2198                            "2^" => Ok(Box::new_with(|| Expr { kind: ExprKind::Pow { base: Box::new_with(|| 2f64.into()), power: value }, info })),
2199                            "10^" => Ok(Box::new_with(|| Expr { kind: ExprKind::Pow { base: Box::new_with(|| 10f64.into()), power: value }, info })),
2200
2201                            _ => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: func.into() }.into(), location: location.to_owned() })),
2202                        }
2203                    }
2204                    "reportListAttribute" => {
2205                        let info = self.check_children_get_info(expr, 2, &location)?;
2206                        let func = self.grab_option(&expr.children[0], &location)?;
2207                        let value = self.parse_expr(&expr.children[1], &location)?;
2208                        match func {
2209                            "length" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListLen { value }, info })),
2210                            "rank" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListRank { value }, info })),
2211                            "dimensions" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListDims { value }, info })),
2212                            "flatten" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListFlatten { value }, info })),
2213                            "columns" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListColumns { value }, info })),
2214                            "reverse" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListRev { value }, info })),
2215
2216                            "lines" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListLines { value }, info })),
2217                            "csv" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListCsv { value }, info })),
2218                            "json" => Ok(Box::new_with(|| Expr { kind: ExprKind::ListJson { value }, info })),
2219
2220                            _ => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: func.into() }.into(), location: location.to_owned() })),
2221                        }
2222                    }
2223                    "reportReshape" => {
2224                        let info = self.check_children_get_info(expr, 2, &location)?;
2225                        let value = self.parse_expr(&expr.children[0], &location)?;
2226                        let dims = self.parse_expr(&expr.children[1], &location)?;
2227                        Ok(Box::new_with(|| Expr { kind: ExprKind::ListReshape { value, dims }, info }))
2228                    }
2229                    "reportIfElse" => {
2230                        let info = self.check_children_get_info(expr, 3, &location)?;
2231                        let condition = self.parse_expr(&expr.children[0], &location)?;
2232                        let then = self.parse_expr(&expr.children[1], &location)?;
2233                        let otherwise = self.parse_expr(&expr.children[2], &location)?;
2234                        Ok(Box::new_with(|| Expr { kind: ExprKind::Conditional { condition, then, otherwise }, info }))
2235                    }
2236                    "getJSFromRPCStruct" => {
2237                        let rpc = self.parse_rpc(expr, &location)?;
2238                        Ok(Box::new_with(|| (*rpc).into()))
2239                    }
2240                    "reportImageOfObject" => {
2241                        let info = self.check_children_get_info(expr, 1, &location)?;
2242                        let entity = self.grab_entity(&expr.children[0], BlockInfo::none(), &location)?.into();
2243                        Ok(Box::new_with(|| Expr { kind: ExprKind::ImageOfEntity { entity }, info }))
2244                    }
2245                    "reportTouchingObject" => {
2246                        let info = self.check_children_get_info(expr, 1, &location)?;
2247                        let child = &expr.children[0];
2248                        if child.name == "l" && child.get(&["option"]).is_some() {
2249                            match self.grab_option(child, &location)? {
2250                                "mouse-pointer" => Ok(Box::new_with(|| Expr { kind: ExprKind::IsTouchingMouse, info })),
2251                                "pen trails" => Ok(Box::new_with(|| Expr { kind: ExprKind::IsTouchingDrawings, info })),
2252                                "edge" => Ok(Box::new_with(|| Expr { kind: ExprKind::IsTouchingEdge, info })),
2253                                x => Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2254                            }
2255                        }
2256                        else {
2257                            let entity = self.grab_entity(child, BlockInfo::none(), &location)?.into();
2258                            Ok(Box::new_with(|| Expr { kind: ExprKind::IsTouchingEntity { entity }, info }))
2259                        }
2260                    }
2261                    "evaluate" => {
2262                        let info = self.check_children_get_info(expr, 2, &location)?;
2263                        let closure = self.parse_expr(&expr.children[0], &location)?;
2264                        let mut args = Vec::with_capacity(expr.children[1].children.len());
2265                        for input in expr.children[1].children.iter() {
2266                            args.push_boxed(self.parse_expr(input, &location)?);
2267                        }
2268                        Ok(Box::new_with(|| Expr { kind: ExprKind::CallClosure { new_entity: None, closure, args }, info }))
2269                    }
2270                    "reportAskFor" => {
2271                        let info = self.check_children_get_info(expr, 3, &location)?;
2272                        let entity = self.grab_entity(&expr.children[0], BlockInfo::none(), &location)?;
2273                        let closure = self.parse_expr(&expr.children[1], &location)?;
2274                        let mut args = Vec::with_capacity(expr.children[2].children.len());
2275                        for input in expr.children[2].children.iter() {
2276                            args.push_boxed(self.parse_expr(input, &location)?);
2277                        }
2278                        Ok(Box::new_with(|| Expr { kind: ExprKind::CallClosure { new_entity: Some(entity), closure, args }, info }))
2279                    }
2280                    "doSocketRequest" => {
2281                        let res = self.parse_send_message_common(expr, &location)?;
2282                        Ok(Box::new_with(|| {
2283                            let NetworkMessage { target, msg_type, values, info } = *res;
2284                            Expr { kind: ExprKind::NetworkMessageReply { target, msg_type, values }, info }
2285                        }))
2286                    }
2287                    "getEffect" => {
2288                        let info = self.check_children_get_info(expr, 1, &location)?;
2289                        let effect = self.parse_effect(&expr.children[0], &location)?;
2290                        Ok(Box::new_with(|| Expr { kind: ExprKind::Effect { kind: effect }, info }))
2291                    }
2292                    "getPenAttribute" => {
2293                        let info = self.check_children_get_info(expr, 1, &location)?;
2294                        let attr = self.parse_pen_attr(&expr.children[0], &location)?;
2295                        Ok(Box::new_with(|| Expr { kind: ExprKind::PenAttr { attr }, info }))
2296                    }
2297                    "reportGet" => {
2298                        let info = self.check_children_get_info(expr, 1, &location)?;
2299                        match self.grab_option(&expr.children[0], &location)? {
2300                            "costumes" => Ok(Box::new_with(|| Expr { kind: ExprKind::CostumeList, info })),
2301                            "costume" => Ok(Box::new_with(|| Expr { kind: ExprKind::Costume, info })),
2302                            "sounds" => Ok(Box::new_with(|| Expr { kind: ExprKind::SoundList, info })),
2303                            m => Err(Box::new_with(|| Error { kind: CompileError::CurrentlyUnsupported { msg: format_compact!("the {s} block with option '{m}' is currently not supported") }.into(), location: location.to_owned() })),
2304                        }
2305                    }
2306                    "reportObject" => {
2307                        let info = self.check_children_get_info(expr, 1, &location)?;
2308                        self.grab_entity(&expr.children[0], info, &location)
2309                    }
2310                    "newClone" => {
2311                        let info = self.check_children_get_info(expr, 1, &location)?;
2312                        let target = self.grab_entity(&expr.children[0], BlockInfo::none(), &location)?;
2313                        Ok(Box::new_with(|| Expr { kind: ExprKind::Clone { target }, info }))
2314                    }
2315                    "reportIsA" => {
2316                        let info = self.check_children_get_info(expr, 2, &location)?;
2317                        let value = self.parse_expr(&expr.children[0], &location)?;
2318                        let ty = match self.grab_option(&expr.children[1], &location)? {
2319                            "number" => ValueType::Number,
2320                            "text" => ValueType::Text,
2321                            "Boolean" => ValueType::Bool,
2322                            "list" => ValueType::List,
2323                            "sprite" => ValueType::Sprite,
2324                            "costume" => ValueType::Costume,
2325                            "sound" => ValueType::Sound,
2326                            "command" => ValueType::Command,
2327                            "reporter" => ValueType::Reporter,
2328                            "predicate" => ValueType::Predicate,
2329                            x => return Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2330                        };
2331                        Ok(Box::new_with(|| Expr { kind: ExprKind::TypeQuery { value, ty }, info }))
2332                    }
2333                    "reportDate" => {
2334                        let info = self.check_children_get_info(expr, 1, &location)?;
2335                        let query = match self.grab_option(&expr.children[0], &location)? {
2336                            "year" => TimeQuery::Year,
2337                            "month" => TimeQuery::Month,
2338                            "date" => TimeQuery::Date,
2339                            "day of week" => TimeQuery::DayOfWeek,
2340                            "hour" => TimeQuery::Hour,
2341                            "minute" => TimeQuery::Minute,
2342                            "second" => TimeQuery::Second,
2343                            "time in milliseconds" => TimeQuery::UnixTimestampMs,
2344                            x => return Err(Box::new_with(|| Error { kind: ProjectError::BlockOptionUnknown { got: x.into() }.into(), location: location.to_owned() })),
2345                        };
2346                        Ok(Box::new_with(|| Expr { kind: ExprKind::RealTime { query }, info }))
2347                    }
2348                    x => {
2349                        let (args, info) = self.parse_unknown_common(expr, &location)?;
2350                        match self.parser.expr_replacements.iter().find(|r| r.0 == x) {
2351                            Some(f) => f.1(args, info, &location),
2352                            None => Ok(Box::new_with(|| Expr { kind: ExprKind::UnknownBlock { name: x.into(), args }, info })),
2353                        }
2354                    }
2355                }
2356            }
2357            _ => Err(Box::new_with(|| Error { kind: CompileError::UnknownBlockType.into(), location: location.to_owned() })),
2358        }
2359    }
2360}
2361
2362struct EntityInfo<'a, 'b> {
2363    parser: &'a Parser,
2364    role: &'b RoleInfo<'a>,
2365    name: CompactString,
2366    trans_name: CompactString,
2367    fields: SymbolTable<'a>,
2368    funcs: SymbolTable<'a>,
2369    costumes: SymbolTable<'a>,
2370    sounds: SymbolTable<'a>,
2371}
2372impl<'a, 'b> EntityInfo<'a, 'b> {
2373    fn new(role: &'b RoleInfo<'a>, name: VariableRef) -> Box<Self> {
2374        Box::new_with(|| Self {
2375            parser: role.parser,
2376            role,
2377            name: name.name,
2378            trans_name: name.trans_name,
2379            fields: SymbolTable::new(role.parser),
2380            funcs: SymbolTable::new(role.parser),
2381            costumes: SymbolTable::new(role.parser),
2382            sounds: SymbolTable::new(role.parser),
2383        })
2384    }
2385    fn parse(mut self, entity: &'a Xml) -> Result<Entity, Box<Error>> {
2386        let location = Box::new_with(|| LocationRef {
2387            role: Some(&self.role.name),
2388            entity: Some(&self.name),
2389            collab_id: None,
2390            block_type: None,
2391        });
2392
2393        for costume in entity.get(&["costumes", "list"]).map(|c| c.children.as_slice()).unwrap_or(&[]) {
2394            if let Some(ident) = costume.get(&["ref"]).and_then(|r| r.attr("mediaID")) {
2395                let ident = ident.value.as_str();
2396                if !ident.starts_with(self.name.as_str()) || !ident[self.name.len()..].starts_with("_cst_") {
2397                    return Err(Box::new_with(|| Error { kind: ProjectError::CostumeIdFormat { id: ident.into() }.into(), location: location.to_owned() }));
2398                }
2399                let name = &ident[self.name.len() + 5..];
2400
2401                let img = match self.role.images.get(ident) {
2402                    Some(x) => x.clone(),
2403                    None => return Err(Box::new_with(|| Error { kind: ProjectError::CostumeUndefinedRef { id: ident.into() }.into(), location: location.to_owned() })),
2404                };
2405
2406                match self.costumes.define(name.into(), Value::Image(img)) {
2407                    Ok(None) => (),
2408                    Ok(Some(prev)) => return Err(Box::new_with(|| Error { kind: ProjectError::CostumesWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
2409                    Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2410                    Err(SymbolError::ConflictingTrans { trans_name, names }) => return Err(Box::new_with(|| Error { kind: CompileError::CostumesWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2411                }
2412            }
2413        }
2414
2415        for sound in entity.get(&["sounds", "list"]).map(|c| c.children.as_slice()).unwrap_or(&[]) {
2416            if let Some(ident) = sound.get(&["ref"]).and_then(|r| r.attr("mediaID")) {
2417                let ident = ident.value.as_str();
2418                if !ident.starts_with(self.name.as_str()) || !ident[self.name.len()..].starts_with("_snd_") {
2419                    return Err(Box::new_with(|| Error { kind: ProjectError::SoundIdFormat { id: ident.into() }.into(), location: location.to_owned() }));
2420                }
2421                let name = &ident[self.name.len() + 5..];
2422
2423                let sound = match self.role.sounds.get(ident) {
2424                    Some(x) => x.clone(),
2425                    None => return Err(Box::new_with(|| Error { kind: ProjectError::SoundUndefinedRef { id: ident.into() }.into(), location: location.to_owned() })),
2426                };
2427
2428                match self.sounds.define(name.into(), Value::Audio(sound)) {
2429                    Ok(None) => (),
2430                    Ok(Some(prev)) => return Err(Box::new_with(|| Error { kind: ProjectError::SoundsWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
2431                    Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2432                    Err(SymbolError::ConflictingTrans { trans_name, names }) => return Err(Box::new_with(|| Error { kind: CompileError::SoundsWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2433                }
2434            }
2435        }
2436
2437        let blocks = entity.get(&["blocks"]).map(|v| v.children.as_slice()).unwrap_or(&[]);
2438        for block in blocks {
2439            parse_block_header(block, &mut self.funcs, &location)?;
2440        }
2441
2442        let active_costume = match entity.attr("costume").map(|v| v.value.parse::<usize>().ok()).flatten() {
2443            Some(idx) if idx >= 1 && idx <= self.costumes.len() => Some(idx - 1),
2444            _ => None,
2445        };
2446        let color = entity.attr("color").map(|v| parse_color(&v.value)).flatten().unwrap_or((0, 0, 0, 255));
2447        let visible = !entity.attr("hidden").and_then(|s| s.value.parse::<bool>().ok()).unwrap_or(false);
2448
2449        let float_attr = |attr: &str| entity.attr(attr).map(|v| v.value.parse::<f64>().ok().filter(|v| v.is_finite())).flatten();
2450        let pos = (float_attr("x").unwrap_or(0.0), float_attr("y").unwrap_or(0.0));
2451        let heading = float_attr("heading").unwrap_or(0.0);
2452        let scale = float_attr("scale").unwrap_or(1.0);
2453
2454        if let Some(fields) = entity.get(&["variables"]) {
2455            let mut dummy_script = ScriptInfo::new(&self);
2456
2457            let mut defs = vec![];
2458            for def in fields.children.iter().filter(|v| v.name == "variable") {
2459                let name = match def.attr("name") {
2460                    None => return Err(Box::new_with(|| Error { kind: ProjectError::UnnamedField.into(), location: location.to_owned() })),
2461                    Some(x) => x.value.clone(),
2462                };
2463                let value = match def.children.get(0) {
2464                    None => return Err(Box::new_with(|| Error { kind: ProjectError::FieldNoValue { name }.into(), location: location.to_owned() })),
2465                    Some(x) => match dummy_script.parse_expr(x, &location)?.kind {
2466                        ExprKind::Value(v) => v,
2467                        _ => return Err(Box::new_with(|| Error { kind: ProjectError::ValueNotEvaluated.into(), location: location.to_owned() })),
2468                    }
2469                };
2470                defs.push((name, value));
2471            }
2472
2473            for (name, value) in defs {
2474                match self.fields.define(name.clone(), value) {
2475                    Ok(None) => (),
2476                    Ok(Some(prev)) => return Err(Box::new_with(|| Error { kind: ProjectError::FieldsWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
2477                    Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2478                    Err(SymbolError::ConflictingTrans { trans_name, names }) => return Err(Box::new_with(|| Error { kind: CompileError::FieldsWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2479                }
2480            }
2481        }
2482
2483        let mut funcs = vec![];
2484        for block in blocks {
2485            funcs.push(parse_block(block, &self.funcs, self.role, Some(&self))?);
2486        }
2487
2488        let mut scripts = vec![];
2489        if let Some(scripts_xml) = entity.get(&["scripts"]) {
2490            for script_xml in scripts_xml.children.iter() {
2491                match script_xml.children.as_slice() {
2492                    [] => continue,
2493                    [stmt, rest @ ..] => {
2494                        if rest.is_empty() && (stmt.attr("var").is_some() || stmt.attr("s").map(|s| s.value.starts_with("report")).unwrap_or(false)) {
2495                            continue
2496                        }
2497                        if self.parser.omit_nonhat_scripts && ScriptInfo::new(&self).parse_hat(stmt)?.is_none() {
2498                            continue
2499                        }
2500                    }
2501                }
2502
2503                scripts.push_boxed(ScriptInfo::new(&self).parse(script_xml)?);
2504            }
2505        }
2506
2507        Ok(Entity {
2508            name: self.name,
2509            trans_name: self.trans_name,
2510            fields: self.fields.into_def_inits(),
2511            costumes: self.costumes.into_def_inits(),
2512            sounds: self.sounds.into_def_inits(),
2513            funcs,
2514            scripts,
2515
2516            active_costume,
2517            visible,
2518            color,
2519            pos,
2520            heading,
2521            scale,
2522        })
2523    }
2524}
2525
2526enum ParamType {
2527    Evaluated, Unevaluated,
2528}
2529struct BlockHeaderInfo<'a> {
2530    s: &'a str,
2531    returns: bool,
2532    params: Vec<(CompactString, ParamType)>,
2533    upvars: Vec<CompactString>,
2534}
2535
2536// returns the signature and returns flag of the block header value
2537#[inline(never)]
2538fn get_block_info(value: &Value) -> Box<BlockHeaderInfo> {
2539    match value {
2540        Value::List(vals, _) => {
2541            assert_eq!(vals.len(), 4);
2542            let s = match &vals[0] {
2543                Value::String(v) => v.as_str(),
2544                _ => panic!(),
2545            };
2546            let returns = match &vals[1] {
2547                Value::Bool(v) => *v,
2548                _ => panic!(),
2549            };
2550            let params = match &vals[2] {
2551                Value::List(x, None) => x.iter().map(|x| match x {
2552                    Value::List(x, None) => match x.as_slice() {
2553                        [Value::String(v1), Value::Bool(v2)] => (v1.clone(), if *v2 { ParamType::Evaluated } else { ParamType::Unevaluated }),
2554                        _ => panic!(),
2555                    }
2556                    _ => panic!(),
2557                }).collect(),
2558                _ => panic!(),
2559            };
2560            let upvars = match &vals[3] {
2561                Value::List(x, None) => x.iter().map(|x| match x {
2562                    Value::String(x) => x.clone(),
2563                    _ => panic!(),
2564                }).collect(),
2565                _ => panic!(),
2566            };
2567            Box::new_with(|| BlockHeaderInfo { s, returns, params, upvars })
2568        }
2569        _ => panic!(), // header parser would never do this
2570    }
2571}
2572
2573fn replace_ranges<It>(s: &str, ranges: It, with: &str) -> CompactString where It: Iterator<Item = (usize, usize)>{
2574    let mut res = alloc::string::String::with_capacity(s.len());
2575    let mut last_stop = 0;
2576    for (a, b) in ranges {
2577        res += &s[last_stop..a];
2578        last_stop = b;
2579        res += with;
2580    }
2581    res += &s[last_stop..];
2582    res.into()
2583}
2584
2585#[inline(never)]
2586fn block_name_from_def(s: &str) -> CompactString {
2587    replace_ranges(s, ParamIter::new(s), "\t") // tabs leave a marker for args which disappears after ident renaming
2588}
2589#[inline(never)]
2590fn block_name_from_ref(s: &str) -> CompactString {
2591    replace_ranges(s, ArgIter::new(s), "\t") // tabs leave a marker for args which disappears after ident renaming
2592}
2593
2594#[test]
2595fn test_block_name_from_def() {
2596    assert_eq!(block_name_from_def("hello world"), "hello world");
2597    assert_eq!(block_name_from_def("hello %'wor'ld"), "hello \tld");
2598    assert_eq!(block_name_from_def("hello %'wor' ld "), "hello \t ld ");
2599    assert_eq!(block_name_from_def("hello %'wor'l%'d'"), "hello \tl\t");
2600    assert_eq!(block_name_from_def("hello %'wor'l%'d' "), "hello \tl\t ");
2601    assert_eq!(block_name_from_def("hello %'wor'l%'d'%' "), "hello \tl\t%' ");
2602}
2603#[test]
2604fn test_block_name_from_ref() {
2605    assert_eq!(block_name_from_ref("hello world"), "hello world");
2606    assert_eq!(block_name_from_ref("hello %world"), "hello \t");
2607    assert_eq!(block_name_from_ref("hello %world "), "hello \t ");
2608}
2609
2610#[inline(never)]
2611fn parse_block_header<'a>(block: &'a Xml, funcs: &mut SymbolTable<'a>, location: &LocationRef) -> Result<(), Box<Error>> {
2612    let mut location = Box::new_with(|| LocationRef {
2613        role: location.role,
2614        entity: location.entity,
2615        collab_id: get_collab_id(block),
2616        block_type: None,
2617    });
2618    let s = match block.attr("s") {
2619        Some(v) => v.value.as_str(),
2620        None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockWithoutName.into(), location: location.to_owned() })),
2621    };
2622    location.block_type = Some(s);
2623
2624    let returns = match block.attr("type") {
2625        Some(v) => match v.value.as_str() {
2626            "command" => false,
2627            "reporter" | "predicate" => true,
2628            x => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockUnknownType { ty: x.into() }.into(), location: location.to_owned() })),
2629        }
2630        None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockWithoutType.into(), location: location.to_owned() })),
2631    };
2632
2633    let (params, upvars) = match block.get(&["inputs"]) {
2634        Some(inputs) => {
2635            let mut params = vec![];
2636            let mut upvars = vec![];
2637
2638            let param_names: Vec<_> = ParamIter::new(s).map(|(a, b)| &s[a+2..b-1]).collect();
2639            if param_names.len() != inputs.children.len() {
2640                return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockInputsMetaCorrupted.into(), location: location.to_owned() }));
2641            }
2642            for (param, input) in iter::zip(param_names, &inputs.children) {
2643                let t = match input.attr("type") {
2644                    Some(x) if !x.value.is_empty() => x.value.as_str(),
2645                    _ => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockInputsMetaCorrupted.into(), location: location.to_owned() })),
2646                };
2647                let evaluated = match t {
2648                    "%anyUE" | "%boolUE" => false,
2649                    _ => true,
2650                };
2651
2652                params.push(Value::List(vec![CompactString::new(param).into(), evaluated.into()], None));
2653                if t == "%upvar" {
2654                    upvars.push(Value::String(CompactString::new(param)));
2655                }
2656            }
2657
2658            (params, upvars)
2659        }
2660        None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockWithoutInputsMeta.into(), location: location.to_owned() })),
2661    };
2662
2663    let name = block_name_from_def(s);
2664    match funcs.define(name, Value::List(vec![Value::from(s), Value::from(returns), Value::List(params, None), Value::List(upvars, None)], None)) {
2665        Ok(None) => Ok(()),
2666        Ok(Some(prev)) => Err(Box::new_with(|| Error { kind: CompileError::BlocksWithSameName { name: prev.def.name, sigs: (get_block_info(&prev.init).s.into(), s.into()) }.into(), location: location.to_owned() })),
2667        Err(SymbolError::NameTransformError { name }) => Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2668        Err(SymbolError::ConflictingTrans { trans_name, names }) => Err(Box::new_with(|| Error { kind: CompileError::BlocksWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2669    }
2670}
2671#[inline(never)]
2672fn parse_block<'a>(block: &'a Xml, funcs: &SymbolTable<'a>, role: &RoleInfo, entity: Option<&EntityInfo>) -> Result<Function, Box<Error>> {
2673    let s = block.attr("s").unwrap().value.as_str(); // unwrap ok because we assume parse_block_header() was called before
2674    let entry = funcs.get(&block_name_from_def(s)).unwrap();
2675    let block_header = get_block_info(&entry.init);
2676    assert_eq!(s, block_header.s);
2677
2678    let location = Box::new_with(|| LocationRef {
2679        role: Some(&role.name),
2680        entity: entity.map(|x| x.name.as_str()),
2681        collab_id: get_collab_id(block),
2682        block_type: Some(s),
2683    });
2684
2685    let finalize = |entity_info: &EntityInfo| {
2686        let mut script_info = ScriptInfo::new(entity_info);
2687        for param in block_header.params {
2688            script_info.decl_local(param.0, 0f64.into(), &location)?;
2689        }
2690        debug_assert_eq!(script_info.locals.len(), 1);
2691        debug_assert_eq!(script_info.locals[0].1.len(), 0);
2692        let params = script_info.locals[0].0.clone().into_defs();
2693
2694        let stmts = match block.get(&["script"]) {
2695            Some(script) => script_info.parse(script)?.stmts,
2696            None => vec![],
2697        };
2698
2699        let upvars = {
2700            let mut res = vec![];
2701            for upvar in block_header.upvars.iter() {
2702                match params.iter().find(|x| x.name == *upvar) {
2703                    Some(x) => res.push_boxed(x.ref_at(VarLocation::Local)),
2704                    None => return Err(Box::new_with(|| Error { kind: ProjectError::CustomBlockInputsMetaCorrupted.into(), location: location.to_owned() })),
2705                };
2706            }
2707            res
2708        };
2709
2710        Ok(Function {
2711            name: entry.def.name.clone(),
2712            trans_name: entry.def.trans_name.clone(),
2713            upvars,
2714            params,
2715            returns: block_header.returns,
2716            stmts,
2717        })
2718    };
2719    match entity {
2720        Some(v) => finalize(v),
2721        None => {
2722            let entity = EntityInfo::new(role, VariableRef { name: "global".into(), trans_name: "global".into(), location: VarLocation::Global });
2723            finalize(&entity)
2724        }
2725    }
2726}
2727#[inline(never)]
2728fn new_autofill_arg(autofill_args: &mut Vec<VariableRef>, parser: &Parser, location: &LocationRef) -> Result<Box<VariableRef>, Box<Error>> {
2729    let var = Box::try_new_with(|| {
2730        let input = autofill_args.len() + 1;
2731        let name = match parser.autofill_generator.as_ref()(input) {
2732            Ok(x) => x,
2733            Err(()) => return Err(Box::new_with(|| Error { kind: CompileError::AutofillGenerateError { input }.into(), location: location.to_owned() })),
2734        };
2735        let trans_name = match parser.name_transformer.as_ref()(&name) {
2736            Ok(x) => x,
2737            Err(()) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2738        };
2739        Ok(VariableRef { name, trans_name, location: VarLocation::Local })
2740    })?;
2741
2742    autofill_args.push_with(|| (*var).clone());
2743
2744    Ok(var)
2745}
2746
2747struct RoleInfo<'a> {
2748    parser: &'a Parser,
2749    name: CompactString,
2750    globals: SymbolTable<'a>,
2751    entities: SymbolTable<'a>,
2752    funcs: SymbolTable<'a>,
2753    images: VecMap<&'a str, Rc<(Vec<u8>, Option<(f64, f64)>, CompactString)>>,
2754    sounds: VecMap<&'a str, Rc<(Vec<u8>, CompactString)>>,
2755    msg_types: VecMap<&'a str, Vec<&'a str>>,
2756}
2757impl<'a> RoleInfo<'a> {
2758    fn new(parser: &'a Parser, name: CompactString) -> Box<Self> {
2759        Box::new_with(|| Self {
2760            parser,
2761            name,
2762            globals: SymbolTable::new(parser),
2763            entities: SymbolTable::new(parser),
2764            funcs: SymbolTable::new(parser),
2765            images: Default::default(),
2766            sounds: Default::default(),
2767            msg_types: Default::default(),
2768        })
2769    }
2770    fn parse(mut self, role_root: &'a Xml) -> Result<Role, Box<Error>> {
2771        let mut location = Box::new_with(|| LocationRef {
2772            role: None,
2773            entity: None,
2774            collab_id: None,
2775            block_type: None,
2776        });
2777
2778        assert_eq!(role_root.name, "role");
2779        let role = match role_root.attr("name") {
2780            None => return Err(Box::new_with(|| Error { kind: ProjectError::RoleNoName.into(), location: location.to_owned() })),
2781            Some(x) => x.value.clone(),
2782        };
2783        location.role = Some(&role);
2784
2785        let content = match role_root.get(&["project"]) {
2786            None => return Err(Box::new_with(|| Error { kind: ProjectError::RoleNoContent.into(), location: location.to_owned() })),
2787            Some(x) => x,
2788        };
2789        let notes = CompactString::new(content.get(&["notes"]).map(|v| v.text.as_str()).unwrap_or(""));
2790        let stage = match content.get(&["stage"]) {
2791            None => return Err(Box::new_with(|| Error { kind: ProjectError::NoStage.into(), location: location.to_owned() })),
2792            Some(x) => x,
2793        };
2794        let stage_width = stage.attr("width").and_then(|x| x.value.parse::<usize>().ok()).unwrap_or(480);
2795        let stage_height = stage.attr("height").and_then(|x| x.value.parse::<usize>().ok()).unwrap_or(360);
2796
2797        let msg_types = stage.get(&["messageTypes"]).map(|x| x.children.as_slice()).unwrap_or(&[]);
2798        for msg_type in msg_types {
2799            let name = match msg_type.get(&["name"]) {
2800                None => return Err(Box::new_with(|| Error { kind: ProjectError::MessageTypeMissingName.into(), location: location.to_owned() })),
2801                Some(x) => x.text.as_str(),
2802            };
2803            let fields = match msg_type.get(&["fields"]) {
2804                None => return Err(Box::new_with(|| Error { kind: ProjectError::MessageTypeMissingFields { msg_type: name.into() }.into(), location: location.to_owned() })),
2805                Some(x) => {
2806                    let mut res = vec![];
2807                    for field in x.children.iter() {
2808                        if field.name != "field" { continue }
2809                        res.push(match field.text.as_str() {
2810                            "" => return Err(Box::new_with(|| Error { kind: ProjectError::MessageTypeFieldEmpty { msg_type: name.into() }.into(), location: location.to_owned() })),
2811                            x => x,
2812                        });
2813                    }
2814                    res
2815                }
2816            };
2817
2818            if self.msg_types.insert(name, fields).is_some() {
2819                return Err(Box::new_with(|| Error { kind: ProjectError::MessageTypeMultiplyDefined { msg_type: name.into() }.into(), location: location.to_owned() }));
2820            }
2821        }
2822
2823        for entry in role_root.get(&["media"]).map(|v| v.children.as_slice()).unwrap_or(&[]) {
2824            match entry.name.as_str() {
2825                "costume" => {
2826                    let id = match entry.attr("mediaID") {
2827                        Some(x) => x.value.as_str(),
2828                        None => return Err(Box::new_with(|| Error { kind: ProjectError::ImageWithoutId.into(), location: location.to_owned() })),
2829                    };
2830
2831                    let name = match entry.attr("name") {
2832                        Some(x) => x.value.clone(),
2833                        None => "untitled".into(),
2834                    };
2835
2836                    let center = match (entry.attr("center-x").and_then(|x| x.value.parse().ok()), entry.attr("center-y").and_then(|y| y.value.parse().ok())) {
2837                        (Some(x), Some(y)) => Some((x, y)),
2838                        _ => None,
2839                    };
2840
2841                    let content = match entry.attr("image") {
2842                        Some(x) => match x.value.as_str().starts_with("data:image/").then(|| x.value.as_str().split(";base64,").nth(1)).flatten() {
2843                            Some(x) => match base64_decode(x) {
2844                                Ok(x) => x,
2845                                Err(e) => return Err(Box::new_with(|| Error { kind: e.into(), location: location.to_owned() })),
2846                            }
2847                            _ => return Err(Box::new_with(|| Error { kind: ProjectError::ImageUnknownFormat { id: id.into(), content: x.value.clone() }.into(), location: location.to_owned() })),
2848                        }
2849                        None => return Err(Box::new_with(|| Error { kind: ProjectError::ImageWithoutContent { id: id.into() }.into(), location: location.to_owned() })),
2850                    };
2851
2852                    if self.images.insert(id, Rc::new((content, center, name))).is_some() {
2853                        return Err(Box::new_with(|| Error { kind: ProjectError::ImagesWithSameId { id: id.into() }.into(), location: location.to_owned() }));
2854                    }
2855                }
2856                "sound" => {
2857                    let id = match entry.attr("mediaID") {
2858                        Some(x) => x.value.as_str(),
2859                        None => return Err(Box::new_with(|| Error { kind: ProjectError::SoundWithoutId.into(), location: location.to_owned() })),
2860                    };
2861
2862                    let name = match entry.attr("name") {
2863                        Some(x) => x.value.clone(),
2864                        None => "untitled".into(),
2865                    };
2866
2867                    let content = match entry.attr("sound") {
2868                        Some(x) => match x.value.as_str().starts_with("data:audio/").then(|| x.value.as_str().split(";base64,").nth(1)).flatten() {
2869                            Some(x) => match base64_decode(x) {
2870                                Ok(x) => x,
2871                                Err(e) => return Err(Box::new_with(|| Error { kind: e.into(), location: location.to_owned() })),
2872                            }
2873                            _ => return Err(Box::new_with(|| Error { kind: ProjectError::SoundUnknownFormat { id: id.into(), content: x.value.clone() }.into(), location: location.to_owned() })),
2874                        }
2875                        None => return Err(Box::new_with(|| Error { kind: ProjectError::SoundWithoutContent { id: id.into() }.into(), location: location.to_owned() })),
2876                    };
2877
2878                    if self.sounds.insert(id, Rc::new((content, name))).is_some() {
2879                        return Err(Box::new_with(|| Error { kind: ProjectError::SoundsWithSameId { id: id.into() }.into(), location: location.to_owned() }));
2880                    }
2881                }
2882                _ => (),
2883            }
2884        }
2885
2886        if let Some(globals) = content.get(&["variables"]) {
2887            let dummy_name = VariableRef { name: "global".into(), trans_name: "global".into(), location: VarLocation::Global };
2888            let dummy_entity = EntityInfo::new(&self, dummy_name); // fine to do before entities/blocks/etc. since globals are just values (not stmts or exprs)
2889            let mut dummy_script = ScriptInfo::new(&dummy_entity);
2890
2891            let mut defs = vec![];
2892            for def in globals.children.iter().filter(|v| v.name == "variable") {
2893                let name = match def.attr("name") {
2894                    None => return Err(Box::new_with(|| Error { kind: ProjectError::UnnamedGlobal.into(), location: location.to_owned() })),
2895                    Some(x) => x.value.clone(),
2896                };
2897                let value = match def.children.get(0) {
2898                    None => Value::Number(0.0),
2899                    Some(x) => match dummy_script.parse_expr(x, &location)?.kind {
2900                        ExprKind::Value(v) => v,
2901                        _ => return Err(Box::new_with(|| Error { kind: ProjectError::ValueNotEvaluated.into(), location: location.to_owned() })),
2902                    }
2903                };
2904                defs.push((name, value));
2905            }
2906
2907            for (name, value) in defs {
2908                match self.globals.define(name.clone(), value) {
2909                    Ok(None) => (),
2910                    Ok(Some(prev)) => return Err(Box::new_with(|| Error { kind: ProjectError::GlobalsWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
2911                    Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2912                    Err(SymbolError::ConflictingTrans { trans_name, names }) => return Err(Box::new_with(|| Error { kind: CompileError::GlobalsWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2913                }
2914            }
2915        }
2916
2917        let mut entities_raw = vec![];
2918        if let Some(entities_xml) = stage.get(&["sprites"]) {
2919            for entity in iter::once(stage).chain(entities_xml.children.iter().filter(|s| s.name == "sprite")) {
2920                let name = match entity.attr("name") {
2921                    None => return Err(Box::new_with(|| Error { kind: ProjectError::UnnamedEntity.into(), location: location.to_owned() })),
2922                    Some(x) => match self.entities.define(x.value.clone(), 0f64.into()) {
2923                        Ok(None) => self.entities.get(&x.value).unwrap().def.ref_at(VarLocation::Global),
2924                        Ok(Some(prev)) => return Err(Box::new_with(|| Error { kind: ProjectError::EntitiesWithSameName { name: prev.def.name }.into(), location: location.to_owned() })),
2925                        Err(SymbolError::NameTransformError { name }) => return Err(Box::new_with(|| Error { kind: CompileError::NameTransformError { name }.into(), location: location.to_owned() })),
2926                        Err(SymbolError::ConflictingTrans { trans_name, names }) => return Err(Box::new_with(|| Error { kind: CompileError::EntitiesWithSameTransName { trans_name, names }.into(), location: location.to_owned() })),
2927                    }
2928                };
2929                entities_raw.push((entity, name));
2930            }
2931        }
2932
2933        let blocks = content.get(&["blocks"]).map(|v| v.children.as_slice()).unwrap_or(&[]);
2934        for block in blocks {
2935            parse_block_header(block, &mut self.funcs, &location)?;
2936        }
2937
2938        // ----------------------------------------------------------------------------------- //
2939        // -- we now have all the necessary items defined to parse exprs, stmts, and entity -- //
2940        // ----------------------------------------------------------------------------------- //
2941
2942        let funcs = blocks.iter().map(|block| parse_block(block, &self.funcs, &self, None)).collect::<Result<Vec<_>,_>>()?;
2943        let entities = entities_raw.into_iter().map(|(entity, name)| EntityInfo::new(&self, *name).parse(entity)).collect::<Result<Vec<_>,_>>()?;
2944
2945        Ok(Role {
2946            name: role,
2947            notes,
2948            stage_size: (stage_width, stage_height),
2949            globals: self.globals.into_def_inits(),
2950            funcs,
2951            entities,
2952        })
2953    }
2954}
2955
2956pub struct Parser {
2957    /// If `true`, the parser will skip script blocks that lack a hat block.
2958    /// This is typically desirable since free floating blocks are never automatically executed,
2959    /// and thus are typically not needed for translation efforts.
2960    /// Defaults to `true`.
2961    pub omit_nonhat_scripts: bool,
2962
2963    /// All symbol names in the program will be passed through this function,
2964    /// allowing easy conversion of Snap! names to, e.g., valid C-like identifiers.
2965    /// The default operation performs no conversion.
2966    /// Note that non-default transform strategies may also require a custom [`Parser::autofill_generator`].
2967    pub name_transformer: Box<dyn Fn(&str) -> Result<CompactString, ()>>,
2968
2969    /// A generator used to produce symbol names for auto-fill closure arguments.
2970    /// The function receives a number that can be used to differentiate different generated arguments.
2971    /// It is expected that multiple calls to this function with the same input will produce the same output symbol name.
2972    /// The default is to produce a string of format `%n` where `n` is the input number.
2973    /// Note that, after generation, symbol names are still passed through [`Parser::name_transformer`] as usual.
2974    pub autofill_generator: Box<dyn Fn(usize) -> Result<CompactString, ()>>,
2975
2976    /// A mapping of unknown stmt blocks to functions that replace them with a sequence of zero or more other statements.
2977    /// The mapping function receives as input the arguments list to the original block with replacements already recursively applied, as well as the block info for the original block and its code location.
2978    /// Note that replacements are not further applied to the result of this function.
2979    pub stmt_replacements: Vec<(CompactString, Box<dyn Fn(Vec<Expr>, Box<BlockInfo>, &LocationRef) -> Result<Vec<Stmt>, Box<Error>>>)>,
2980
2981    /// A mapping of unknown expr blocks to functions that replace them with another expression, which could be composed of several sub-expressions.
2982    /// The mapping function receives as input the arguments list to the original block with replacements already recursively applied, as well as the block info for the original block and its code location.
2983    /// Note that replacements are not further applied to the result of this function.
2984    pub expr_replacements: Vec<(CompactString, Box<dyn Fn(Vec<Expr>, Box<BlockInfo>, &LocationRef) -> Result<Box<Expr>, Box<Error>>>)>,
2985}
2986impl Default for Parser {
2987    fn default() -> Self {
2988        Self {
2989            omit_nonhat_scripts: true,
2990            name_transformer: Box::new(|v| Ok(v.into())),
2991            autofill_generator: Box::new(|v| Ok(format_compact!("%{}", v))),
2992            stmt_replacements: vec![],
2993            expr_replacements: vec![],
2994        }
2995    }
2996}
2997impl Parser {
2998    pub fn parse(&self, xml: &str) -> Result<Project, Box<Error>> {
2999        let location = Box::new_with(|| LocationRef {
3000            role: None,
3001            entity: None,
3002            collab_id: None,
3003            block_type: None,
3004        });
3005
3006        let mut xml = xmlparser::Tokenizer::from(xml);
3007        while let Some(Ok(e)) = xml.next() {
3008            if let xmlparser::Token::ElementStart { local, .. } = e {
3009                let (proj_name, roles) = match local.as_str() {
3010                    "room" => {
3011                        let project_xml = match parse_xml_root(&mut xml, local.as_str()) {
3012                            Ok(x) => x,
3013                            Err(e) => return Err(Box::new_with(|| Error { kind: e.into(), location: location.to_owned() })),
3014                        };
3015                        let proj_name = CompactString::new(project_xml.attr("name").map(|v| v.value.as_str()).unwrap_or("untitled"));
3016
3017                        let mut roles = Vec::with_capacity(project_xml.children.len());
3018                        for child in project_xml.children.iter() {
3019                            if child.name == "role" {
3020                                let role_name = match child.attr("name") {
3021                                    None => return Err(Box::new_with(|| Error { kind: ProjectError::RoleNoName.into(), location: location.to_owned() })),
3022                                    Some(x) => x.value.clone(),
3023                                };
3024                                roles.push(RoleInfo::new(self, role_name).parse(child)?);
3025                            }
3026                        }
3027
3028                        (proj_name, roles)
3029                    }
3030                    "role" => {
3031                        let role_xml = match parse_xml_root(&mut xml, local.as_str()) {
3032                            Ok(x) => x,
3033                            Err(e) => return Err(Box::new_with(|| Error { kind: e.into(), location: location.to_owned() })),
3034                        };
3035                        let proj_name = CompactString::new(role_xml.attr("name").map(|v| v.value.as_str()).unwrap_or("untitled"));
3036
3037                        let role = RoleInfo::new(self, proj_name.clone()).parse(&role_xml)?;
3038
3039                        (proj_name, vec![role])
3040                    }
3041                    "project" => {
3042                        let project_xml = match parse_xml_root(&mut xml, local.as_str()) {
3043                            Ok(x) => x,
3044                            Err(e) => return Err(Box::new_with(|| Error { kind: e.into(), location: location.to_owned() })),
3045                        };
3046                        let proj_name = CompactString::new(project_xml.attr("name").map(|v| v.value.as_str()).unwrap_or("untitled").to_owned());
3047
3048                        let role_xml = Xml {
3049                            name: "role".into(),
3050                            text: "".into(),
3051                            attrs: vec![XmlAttr { name: "name".into(), value: proj_name.clone() }],
3052                            children: vec![project_xml]
3053                        };
3054                        let role = RoleInfo::new(self, proj_name.clone()).parse(&role_xml)?;
3055
3056                        (proj_name, vec![role])
3057                    }
3058                    _ => continue,
3059                };
3060
3061                return Ok(Project { name: proj_name, roles })
3062            }
3063        }
3064        Err(Box::new_with(|| Error { kind: ProjectError::NoRoot.into(), location: location.to_owned() }))
3065    }
3066}