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
53struct 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
81struct 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 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 fn get(&self, name: &str) -> Option<&VariableDefInit> {
469 self.orig_to_def.get(name)
470 }
471 fn into_defs(self) -> Vec<VariableDef> {
474 self.orig_to_def.into_iter().map(|x| x.1.def).collect()
475 }
476 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>, 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 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 SendLocalMessage { target: Option<Box<Expr>>, msg_type: Box<Expr>, wait: bool },
736 SendNetworkMessage { target: Box<Expr>, msg_type: CompactString, values: Vec<(CompactString, Expr)> },
739 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 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 And { left: Box<Expr>, right: Box<Expr> },
849 Or { left: Box<Expr>, right: Box<Expr> },
851 Conditional { condition: Box<Expr>, then: Box<Expr>, otherwise: Box<Expr> },
853
854 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 Random { a: Box<Expr>, b: Box<Expr> },
869 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 ListCdr { value: Box<Expr> },
894 ListCons { item: Box<Expr>, list: Box<Expr> },
896 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 StrLen { value: Box<Expr> },
911
912 UnicodeToChar { value: Box<Expr> },
914 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>)>, 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(_) => (), Err(SymbolError::ConflictingTrans { trans_name, names }) => if names.0 != names.1 { 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); 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); 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); 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#[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!(), }
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") }
2589#[inline(never)]
2590fn block_name_from_ref(s: &str) -> CompactString {
2591 replace_ranges(s, ArgIter::new(s), "\t") }
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(); 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); 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 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 pub omit_nonhat_scripts: bool,
2962
2963 pub name_transformer: Box<dyn Fn(&str) -> Result<CompactString, ()>>,
2968
2969 pub autofill_generator: Box<dyn Fn(usize) -> Result<CompactString, ()>>,
2975
2976 pub stmt_replacements: Vec<(CompactString, Box<dyn Fn(Vec<Expr>, Box<BlockInfo>, &LocationRef) -> Result<Vec<Stmt>, Box<Error>>>)>,
2980
2981 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}