netsblox_vm/
bytecode.rs

1//! Tools for generating executable [`ByteCode`] from a project's abstract syntax tree.
2//! 
3//! To generate bytecode from a project, you can use [`ByteCode::compile`].
4
5use alloc::collections::{BTreeMap, VecDeque};
6use alloc::string::ToString;
7use alloc::boxed::Box;
8use alloc::vec::Vec;
9use alloc::rc::Rc;
10
11use core::fmt::Write as _;
12use core::mem;
13
14#[cfg(feature = "serde")]
15use serde::{Serialize, Deserialize};
16
17use monostate::MustBeU128;
18use num_traits::FromPrimitive;
19use bin_pool::BinPool;
20
21use crate::*;
22use crate::meta::*;
23use crate::runtime::{Color, Number, NumberError, Event, KeyCode, Property, PrintStyle, Type, CustomTypes, System, ImageProperty, AudioProperty};
24use crate::util::LosslessJoin;
25use crate::vecmap::VecMap;
26use crate::compact_str::{CompactString, ToCompactString};
27
28/// Number of bytes to display on each line of a hex dump
29#[cfg(feature = "std")]
30const BYTES_PER_LINE: usize = 10;
31
32/// Max number of bytes produced by [`encode_u64`].
33const MAX_U64_ENCODED_BYTES: usize = 10;
34
35/// Max number of shrinking cycles to apply to variable width encoded values in an output binary
36const SHRINK_CYCLES: usize = 3;
37
38/// A NetsBlox project compile error generated by [`ByteCode::compile`].
39#[derive(Debug)]
40pub enum CompileError<'a> {
41    UnsupportedStmt { kind: &'a ast::StmtKind },
42    UnsupportedExpr { kind: &'a ast::ExprKind },
43    UnsupportedEvent { kind: &'a ast::HatKind },
44    BadKeycode { key: &'a str },
45    InvalidLocation { loc: &'a str },
46    BadNumber { error: NumberError },
47    UndefinedRef { value: &'a ast::Value },
48    CurrentlyUnsupported { info: CompactString },
49    InvalidBlock { loc: Option<&'a str> },
50}
51impl From<NumberError> for CompileError<'_> { fn from(error: NumberError) -> Self { Self::BadNumber { error } } }
52
53#[derive(Clone, Copy, Debug, FromPrimitive)]
54#[repr(u8)]
55pub(crate) enum Relation {
56    Equal, NotEqual, Less, LessEq, Greater, GreaterEq,
57}
58#[derive(Clone, Copy, Debug, FromPrimitive)]
59#[repr(u8)]
60pub enum AbortMode {
61    All, Current, Others, MyOthers,
62}
63#[derive(Clone, Copy, Debug, FromPrimitive)]
64#[repr(u8)]
65pub(crate) enum TimeQuery {
66    Year, Month, Date, DayOfWeek, Hour, Minute, Second, UnixTimestampMs,
67}
68impl From<&'_ ast::TimeQuery> for TimeQuery {
69    fn from(value: &'_ ast::TimeQuery) -> Self {
70        match value {
71            ast::TimeQuery::Year => TimeQuery::Year,
72            ast::TimeQuery::Month => TimeQuery::Month,
73            ast::TimeQuery::Date => TimeQuery::Date,
74            ast::TimeQuery::DayOfWeek => TimeQuery::DayOfWeek,
75            ast::TimeQuery::Hour => TimeQuery::Hour,
76            ast::TimeQuery::Minute => TimeQuery::Minute,
77            ast::TimeQuery::Second => TimeQuery::Second,
78            ast::TimeQuery::UnixTimestampMs => TimeQuery::UnixTimestampMs,
79        }
80    }
81}
82
83#[derive(Clone, Copy, Debug, FromPrimitive)]
84#[repr(u8)]
85pub(crate) enum BinaryOp {
86    Add, Sub, Mul, Div, Mod, Pow, Log, Atan2,
87    SplitBy,
88    Range, Random,
89    StrGet,
90}
91#[derive(Clone, Copy, Debug, FromPrimitive)]
92#[repr(u8)]
93pub(crate) enum UnaryOp {
94    ToNumber,
95    Not,
96    Abs, Neg,
97    Sqrt,
98    Round, Floor, Ceil,
99    Sin, Cos, Tan,
100    Asin, Acos, Atan,
101    SplitLetter, SplitWord, SplitTab, SplitCR, SplitLF, SplitCsv, SplitJson,
102    StrLen,
103    StrGetLast, StrGetRandom,
104    UnicodeToChar, CharToUnicode,
105}
106
107impl From<Relation> for Instruction<'_> { fn from(relation: Relation) -> Self { Self::Cmp { relation } } }
108impl From<BinaryOp> for Instruction<'_> { fn from(op: BinaryOp) -> Self { Self::BinaryOp { op } } }
109impl From<UnaryOp> for Instruction<'_> { fn from(op: UnaryOp) -> Self { Self::UnaryOp { op } } }
110
111#[derive(Clone, Copy, Debug, FromPrimitive)]
112#[repr(u8)]
113pub(crate) enum VariadicOp {
114    Add, Mul, Min, Max,
115    StrCat,
116    MakeList, ListCat,
117}
118#[derive(Clone, Copy, Debug)]
119#[repr(u8)]
120pub(crate) enum VariadicLen {
121    Fixed(usize), Dynamic,
122}
123impl BinaryRead<'_> for VariadicLen {
124    fn read(code: &[u8], data: &[u8], start: usize) -> (Self, usize) {
125        match BinaryRead::read(code, data, start) {
126            (0, aft) => (VariadicLen::Dynamic, aft),
127            (x, aft) => (VariadicLen::Fixed(x - 1), aft),
128        }
129    }
130}
131impl BinaryWrite for VariadicLen {
132    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>) {
133        let raw = match val { VariadicLen::Fixed(x) => x + 1, VariadicLen::Dynamic => 0 };
134        BinaryWrite::append(&raw, code, data, relocate_info)
135    }
136}
137
138#[derive(Clone, Copy, Debug, FromPrimitive)]
139#[repr(u8)]
140pub(crate) enum BasicType {
141    Number, Text, Bool, List, Entity, Image, Audio,
142}
143impl BasicType {
144    pub fn to_type<C: CustomTypes<S>, S: System<C>>(self) -> Type<C, S> {
145        match self {
146            BasicType::Number => Type::Number,
147            BasicType::Text => Type::Text,
148            BasicType::Bool => Type::Bool,
149            BasicType::List => Type::List,
150            BasicType::Entity => Type::Entity,
151            BasicType::Image => Type::Image,
152            BasicType::Audio => Type::Audio,
153        }
154    }
155}
156
157enum InternalInstruction<'a> {
158    /// Triggers an error when encountered.
159    /// This is an internal value that is only used to denote incomplete linking results for better testing.
160    /// Successfully-linked byte code cannot contain this value.
161    Illegal,
162    /// A valid instruction that will be present in the resulting binary.
163    Valid(Instruction<'a>),
164}
165impl<'a> From<Instruction<'a>> for InternalInstruction<'a> { fn from(ins: Instruction<'a>) -> Self { Self::Valid(ins) } }
166
167#[derive(Debug)]
168#[repr(u8)]
169pub(crate) enum Instruction<'a> {
170    /// Explicitly trigger a yield point. This instruction is otherwise a no-op.
171    Yield,
172    /// Marks the start of a warp section. Note that warp sections can be nested.
173    WarpStart,
174    /// Marks the end of a warp section started by [`Instruction::WarpStart`].
175    WarpStop,
176
177    /// Pushes 1 bool value to the value stack.
178    PushBool { value: bool },
179    /// Pushes 1 number value to the value stack. This is a more compact encoding than [`Instruction::PushNumber`].
180    PushInt { value: i32 },
181    /// Pushes 1 number value to the value stack as a string. This is a more compact encoding than [`Instruction::PushString`] for small numbers.
182    PushIntString { value: i32 },
183    /// Pushes 1 number value to the value stack.
184    PushNumber { value: f64 },
185    /// Pushes 1 color value (encoded as an unsigned argb integer) to the value stack.
186    PushColor { value: Color },
187    /// Pushes 1 string value to the value stack.
188    PushString { value: &'a str },
189    /// Pushes 1 value to the value stack, as looked up from the current execution context.
190    PushVariable { var: &'a str },
191    /// Pushes a reference to a named entity onto the value stack.
192    PushEntity { name: &'a str },
193    /// Pushes a reference to the current running entity onto the value stack.
194    PushSelf,
195    /// Consumes `count` values from the value stack and discards them.
196    PopValue,
197
198    /// Pushes 1 value onto the value stack, which is a copy of item `top_index` from the value stack.
199    /// The top of the stack has `top_index == 0`, the item below it has `top_index == 1`, and so on.
200    DupeValue { top_index: u8 },
201    /// Swaps two values in the value stack, as determined by the specified top index values (see [`Instruction::DupeValue`].
202    SwapValues { top_index_1: u8, top_index_2: u8 },
203
204    /// Consumes 1 value from the value stack and pushes a boolean denoting if the value was (exactly) the specified type.
205    TypeQuery { ty: BasicType },
206    /// Consumes 1 value from the value stack and pushes its bool coercion value onto the stack.
207    ToBool,
208    /// Consumes 1 value from the value stack and pushes its number coercion value onto the stack.
209    ToNumber,
210
211    /// Consumes two values, `list` and `item`, from the value stack and constructs a new list starting with `item` and then containing all the values of `list`.
212    /// The new list is pushed onto the value stack.
213    ListCons,
214    /// Consumes one value, `list`, from the value stack and returns a new list containing all the items of `list` except the first.
215    /// The new list is pushed onto the value stack.
216    ListCdr,
217
218    /// Consumes two values, `list` and `item`, from the value stack and pushes the index of `item` in `list` (or 0 if not present) onto the value stack.
219    /// Note that normal Snap!-style (deep) equality is used to test for the presence of the item.
220    ListFind,
221    /// Consumes two values, `item` and `list`, from the value stack and pushes a boolean representing of `list` contains `item` onto the value stack.
222    /// Note that normal Snap!-style (deep) equality is used to test for the presence of the item.
223    ListContains,
224
225    /// Consumes 1 value, `list`, from the value stack and pushes a bool representing if the list is empty onto the value stack.
226    ListIsEmpty,
227    /// Consumes 1 value, `list`, from the value stack and pushes the length of the list onto the value stack.
228    ListLength,
229    /// Consumes 1 value, `list`, from the value stack and pushes a list containing the dimensions of the list onto the value stack.
230    ListDims,
231    /// Consumes 1 value, `list`, from the value stack and pushes the number of dimensions of the list onto the value stack.
232    ListRank,
233
234    /// Consumes 1 value, `list`, from the value stack and pushes its reversed version onto the value stack.
235    ListRev,
236    /// Consumes 1 value, `list`, from the value stack and pushes a flattened list containing all the same values in order onto the value stack.
237    ListFlatten,
238    /// Consumes `len` values from the value stack (in reverse order) representing target dimensions, as well as another value, `list`.
239    /// Pushes a new tensor (list) with the desired dimensions by sourcing from `list` (with cyclic repetition) onto the value stack.
240    ListReshape { len: VariadicLen },
241    /// Consumes `len` values from the value stack (in reverse order) representing source lists.
242    /// Pushes a new list representing the cartesian product of the source lists onto the value stack.
243    ListCartesianProduct { len: VariadicLen },
244
245    /// Consumes 1 value, `list`, from the value stack and pushes its JSON-encoded string representation onto the value stack.
246    ListJson,
247    /// Consumes 1 value, `list`, from the value stack and pushes its CSV-encoded string representation onto the value stack.
248    ListCsv,
249    /// Consumes 1 value, `list`, from the value stack and pushes a list of its columns onto the value stack. This is a more generalized transpose operation.
250    ListColumns,
251    /// Consumes 1 value, `list`, from the value stack and pushes a string composed of its elements separated by new line characters.
252    ListLines,
253
254    /// Consumes three values, `list`, `index`, and `value, from the value stack and inserts `value` at position `index` of `list`.
255    ListInsert,
256    /// Consumes two values, `list` and `value`, from the value stack and inserts `value` at the end of `list`.
257    ListInsertLast,
258    /// Consumes two values, `list` and `value`, from the values stack and inserts `value` at a random position in the list.
259    ListInsertRandom,
260
261    /// Consumes two values, `list` and `index`, from the value stack and pushes the value `list[index]` onto the value stack.
262    ListGet,
263    /// Consumes 1 value, `list`, from the value stack and pushes the last item in the list onto the value stack.
264    ListGetLast,
265    /// Consumes 1 value, `list`, from the value stack and pushes a random item from the list onto the value stack.
266    ListGetRandom,
267
268    /// Consumes three values, `value`, `list`, and `index`, from the value stack and assigns `list[index] = value`.
269    ListAssign,
270    /// Consumes two values, `value` and `list`, from the value stack and assigns `value` to the last position in the list.
271    ListAssignLast,
272    /// Consumes two values, `value` and `list`, from the value stack and assigns `value` to a random position in the list.
273    ListAssignRandom,
274
275    /// Consumes two values, `list` and `index`, from the value stack and deletes item `index` from `list`.
276    ListRemove,
277    /// Consumes one value, `list`, from the value stack and deletes the last item from it.
278    ListRemoveLast,
279    /// Consumes one value, `list`, from the value stack and deletes all elements from it.
280    ListRemoveAll,
281
282    /// Consumes one value, `list`, from the value stack.
283    /// If the list is empty, jumps to the specified position, otherwise pops the first value from the list and pushes it to the value stack.
284    ListPopFirstOrElse { goto: usize },
285
286    /// Consumes 2 values, `b` and `a`, from the value stack, and pushes the value `f(a, b)` onto the value stack.
287    BinaryOp { op: BinaryOp },
288    /// Consumes `len` values from the value stack (in reverse order) and combines them into one value based on `op`.
289    VariadicOp { op: VariadicOp, len: VariadicLen },
290    /// Consumes 2 values, `b` and `a`, from the value stack, and pushes the (boolean) result of the comparison onto the value stack,
291    Cmp { relation: Relation },
292    /// Consumes 2 values, `b` and `a`, from the value stack, and pushes the (boolean) result of an identicalness check onto the value stack.
293    Identical,
294    /// Consumes 1 value, `x`, from the value stack, and pushes the value `f(x)` onto the value stack.
295    UnaryOp { op: UnaryOp },
296
297    /// Re/Declares a set of local variables, which are initialized to 0.
298    /// Note that this is not equivalent to assigning a value of zero to the variable due to the potential issue of [`Shared::Aliased`].
299    /// A new, [`Shared::Unique`] local variable must be created.
300    DeclareLocal { var: &'a str },
301    /// Initializes an upvar of the given name.
302    /// This should be called by a custom block, which should supply the name of its upvar parameter.
303    /// Upvar parameters are passed the name of the variable in the outer scope to link to (which this instruction does).
304    InitUpvar { var: &'a str },
305    /// Consumes 1 value from the value stack and assigns it to the specified variable.
306    Assign { var: &'a str },
307    /// Consumes 1 value, `b` from the value stack, fetches the variable `a`, and assigns it `f(a, b)`.
308    /// Equivalent to fetching the variable, performing the operation, and re-assigning the new value, but is atomic.
309    BinaryOpAssign { var: &'a str, op: BinaryOp },
310
311    /// Creates or destroys a watcher for the given variable.
312    /// If `create` is true, the process will emit a request to create a new watcher.
313    /// If `create` is false, the process will emit a request to remove a watcher.
314    /// In either case, a weak alias to the watched variable is created.
315    /// It is up to the executor to deduplicate watchers to the same variable, if needed.
316    Watcher { create: bool, var: &'a str },
317    /// Generate a pause request and return it to the executor.
318    Pause,
319
320    /// Unconditionally jumps to the given location.
321    Jump { to: usize },
322    /// Pops a value from the value stack and jumps to the given location if its truthyness value is equal to `when`
323    ConditionalJump { to: usize, when: bool },
324
325    /// `tokens` is a [`LosslessJoin`] of 0+ parameter names.
326    /// Consumes `params` arguments from the value stack (in reverse order) to assign to a new symbol table.
327    /// Pushes the symbol table and return address to the call stack, and finally jumps to the given location.
328    Call { pos: usize, tokens: &'a str },
329    /// `tokens` is a [`LosslessJoin`] sequence of `params` parameter names and 0+ captured variable names.
330    /// Creates a closure targeting some bytecode position with the given settings.
331    /// Captures are looked up and bound immediately based on the current execution context.
332    MakeClosure { pos: usize, params: usize, tokens: &'a str },
333    /// Consumes `args` values, and another value, `closure`, from the value stack and calls the closure with the given arguments.
334    /// If `new_entity` is true, consumes an additional value, `entity`, which is the target entity for the new execution context (otherwise the current entity is used).
335    /// It is an error if the number of supplied arguments does not match the number of parameters.
336    CallClosure { new_entity: bool, args: usize },
337    /// Consumes `args` values, and another value, `closure`, from the value stack and forks a new process that invokes the closure with the given arguments.
338    /// It is an error if the number of supplied arguments does not match the number of parameters.
339    ForkClosure { args: usize },
340    /// Pops a return address from the call stack and jumps to it.
341    /// The return value is left on the top of the value stack.
342    /// If the call stack is empty, this instead terminates the process
343    /// with the reported value being the (only) value remaining in the value stack.
344    Return,
345    /// Stops one of more processes immediately without running any following code.
346    Abort { mode: AbortMode },
347
348    /// Pushes a new error handler onto the handler stack.
349    PushHandler { pos: usize, var: &'a str },
350    /// Pops an error handler from the handler stack.
351    PopHandler,
352    /// Consumes 1 value, msg, from the value stack and converts it into a hard error.
353    Throw,
354
355    /// `tokens` is a [`LosslessJoin`] of service, rpc, and 0+ args, which are popped from the value stack.
356    /// Then calls the given RPC, awaits the result, and pushes the return value onto the value stack.
357    CallRpc { tokens: &'a str },
358    /// Pushes the last RPC error message onto the value stack.
359    PushRpcError,
360
361    /// Consumes `len` values (in reverse order) representing arguments to a system call.
362    /// Consumes 1 addition value representing the system call name.
363    /// Invokes the given syscall, awaits the result, and pushes the result onto the value stack.
364    Syscall { len: VariadicLen },
365    /// Pushes the last syscall error message onto the value stack.
366    PushSyscallError,
367
368    /// Consumes 1 value from the value stack, `msg_type`, and broadcasts a message to all scripts.
369    /// The `wait` flag can be set to denote that the broadcasting script should wait until all receiving scripts have terminated.
370    /// If `target` is `true`, an extra value, `target`, is popped from the value stack (first) and represents the target of the message.
371    SendLocalMessage { wait: bool, target: bool },
372    /// Pushes the name of the currently-received message onto the value stack.
373    PushLocalMessage,
374
375    /// Consumes 1 value `msg` from the value stack and prints it to the stored printer.
376    Print { style: PrintStyle },
377    /// Consumes 1 value, `prompt`, from the value stack and uses it as a query to get input from the user.
378    /// The result is NOT stored on the value stack, and instead [`Instruction::PushAnswer`] must be used to retrieve the result.
379    Ask,
380    /// Pushes the most recent answer from an execution of [`Instruction::Ask`] onto the value stack, or empty string if no question has yet been asked.
381    /// Note that the most recent answer is a process-local value, so this cannot retrieve the answer to a question asked by another process.
382    PushAnswer,
383
384    /// Resets the process-local timer value to the current system time.
385    ResetTimer,
386    /// Gets the current timer value (in seconds) and pushes its value onto the value stack.
387    PushTimer,
388    /// Consumes one value, `secs`, from the value stack and asynchronously waits for that amount of time to elapse.
389    /// Non-positive sleep times are ignored, but still generate a yield point.
390    Sleep,
391    /// Computes a specific query about the real-world time and pushes the result onto the value stack.
392    PushRealTime { query: TimeQuery },
393
394    /// `tokens` is a [`LosslessJoin`] of msg type and 0+ field names.
395    /// Consumes 1 value, `target` from the value stack, which is either a single or a list of targets.
396    /// Then consumes one value from the value stack for each field to be sent.
397    /// The `expect_reply` flag denotes if this is a blocking operation that awaits a response from the target(s).
398    /// If `expect_reply` is true, the reply value (or empty string on timeout) is pushed onto the value stack.
399    SendNetworkMessage { tokens: &'a str, expect_reply: bool },
400    /// Consumes 1 value, `value`, from the value stack and sends it as the response to a received message.
401    /// It is not an error to reply to a message that was not expecting a reply, in which case the value is simply discarded.
402    SendNetworkReply,
403
404    /// Pushes the current value of a property onto the value stack.
405    PushProperty { prop: Property },
406    /// Consumes 1 value, `value`, and assigns it to the specified property.
407    SetProperty { prop: Property },
408    /// Consumes 1 value, `delta`, and adds its value to the specified property.
409    ChangeProperty { prop: Property },
410
411    /// Pushes a reference to the current costume onto the value stack.
412    PushCostume,
413    /// Pushes the current costume number onto the value stack (or zero if using no costume or a non-static costume for the given entity).
414    PushCostumeNumber,
415    /// Pushes a shallow copy of the entity's list of static costumes onto the value stack.
416    PushCostumeList,
417    /// Consumes 1 value, `costume`, from the value stack and pushes one of its properties onto the value stack.
418    PushCostumeProperty { prop: ImageProperty },
419
420    /// Consumes 1 value, `costume`, from the value stack and assigns it as the current costume.
421    /// This can be an image or the name of a static costume on the entity.
422    /// Empty string can be used to remove the current costume.
423    SetCostume,
424    /// If using a static costume, advances to the next costume (if one exists).
425    /// If using dynamic costumes or no costume, does nothing.
426    NextCostume,
427
428    /// Pushes a shallow copy of the entity's list of static sounds onto the value stack.
429    PushSoundList,
430    /// Consumes 1 value, `sound`, from the value stack and pushes one of its properties onto the value stack.
431    PushSoundProperty { prop: AudioProperty },
432
433    /// Consumes 1 value, `sound`, from the value stack and attempts to play it.
434    /// This can be an audio object or the name of a static sound on the entity.
435    /// Empty string can be used as a no-op.
436    PlaySound { blocking: bool },
437    /// Consumes 2 values, `duration` and `notes`, from the value stack and plays them.
438    /// `notes` can be a single or list of notes in either MIDI value form or (extended) English names of notes.
439    PlayNotes { blocking: bool },
440    /// Stops playback of all currently-playing sounds.
441    StopSounds,
442
443    /// Pops one value, `target`, from the value stack and pushes a clone of entity `target` onto the value stack.
444    Clone,
445    /// Deletes the current active entity if it is a clone, and requests to abort all of its associated processes.
446    DeleteClone,
447
448    /// Clears all graphic effects on the entity.
449    ClearEffects,
450    // Requests to erase all drawings made by all sprites.
451    ClearDrawings,
452
453    /// Consumes 2 values, `y` and `x`, and asynchronously moves the entity to that position.
454    GotoXY,
455    /// Consumes 1 value, `target`, and asynchronously moves the entity to the target.
456    /// The target can be an entity or an `[x, y]` position.
457    Goto,
458
459    /// Consumes 2 values, `y` and `x`, and asynchronously points the entity toward the specified location.
460    PointTowardsXY,
461    /// Consumes 1 value, `target`, and asynchronously points the entity toward the target.
462    /// The target can be an entity or an `[x, y]` position.
463    PointTowards,
464
465    /// Consumes 1 value, `dist`, and asynchronously moves the entity forward by that distance (or backwards if negative).
466    Forward,
467
468    /// Consumes `args` values (in reverse order) representing arguments to an unknown function.
469    /// This is then handed over to the system to fulfill or error out.
470    /// The result of the async request is then pushed onto the value stack.
471    UnknownBlock { name: &'a str, args: usize },
472}
473#[test]
474fn test_bin_sizes() {
475    if core::mem::size_of::<Instruction>() > 40 {
476        panic!("instructions are too big!");
477    }
478    if core::mem::size_of::<InternalInstruction>() > 40 {
479        panic!("internal instructions are too big!");
480    }
481}
482
483pub(crate) enum RelocateInfo {
484    Code { code_addr: usize },
485    Data { code_addr: usize },
486}
487
488pub(crate) trait BinaryRead<'a>: Sized {
489    /// Reads a value from `src` starting at `start`.
490    /// Returns the read value and the position of the first byte after the read segment.
491    fn read(code: &'a [u8], data: &'a [u8], start: usize) -> (Self, usize);
492}
493trait BinaryWrite: Sized {
494    /// Appends a binary representation of the value to the given buffers.
495    /// And address/relocation information that is written will be stored in `relocate_info` in strictly ascending order of code address.
496    /// This function is not intended for use outside of [`ByteCode`] linking.
497    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>);
498}
499
500impl BinaryRead<'_> for u8 { fn read(code: &[u8], _: &[u8], start: usize) -> (Self, usize) { (code[start], start + 1) } }
501impl BinaryWrite for u8 { fn append(val: &Self, code: &mut Vec<u8>, _: &mut BinPool, _: &mut Vec<RelocateInfo>) { code.push(*val) } }
502
503impl BinaryRead<'_> for bool { fn read(code: &[u8], _: &[u8], start: usize) -> (Self, usize) { (code[start] != 0, start + 1) } }
504impl BinaryWrite for bool { fn append(val: &Self, code: &mut Vec<u8>, _: &mut BinPool, _: &mut Vec<RelocateInfo>) { code.push(if *val { 1 } else { 0 }) } }
505
506macro_rules! read_write_u8_type {
507    ($($t:ty),+$(,)?) => {$(
508        impl BinaryRead<'_> for $t {
509            fn read(code: &[u8], _: &[u8], start: usize) -> (Self, usize) {
510                (Self::from_u8(code[start]).unwrap(), start + 1)
511            }
512        }
513        impl BinaryWrite for $t {
514            fn append(val: &Self, code: &mut Vec<u8>, _: &mut BinPool, _: &mut Vec<RelocateInfo>) {
515                debug_assert_eq!(mem::size_of::<Self>(), 1);
516                code.push((*val) as u8)
517            }
518        }
519    )*}
520}
521read_write_u8_type! { PrintStyle, ImageProperty, AudioProperty, Property, Relation, TimeQuery, BinaryOp, UnaryOp, VariadicOp, BasicType, AbortMode }
522
523/// encodes values as a sequence of bytes of form [1: next][7: bits] in little-endian order.
524/// `bytes` can be used to force a specific size (too small will panic), otherwise calculates and uses the smallest possible size.
525fn encode_u64(mut val: u64, out: &mut Vec<u8>, bytes: Option<usize>) {
526    let mut blocks = ((64 - val.leading_zeros() as usize + 6) / 7).max(1);
527    if let Some(bytes) = bytes {
528        debug_assert!(bytes >= blocks);
529        blocks = bytes;
530    }
531
532    debug_assert!((1..=MAX_U64_ENCODED_BYTES).contains(&blocks));
533    for _ in 1..blocks {
534        out.push((val as u8 & 0x7f) | 0x80);
535        val >>= 7;
536    }
537    debug_assert!(val <= 0x7f);
538    out.push(val as u8);
539}
540fn decode_u64(data: &[u8], start: usize) -> (u64, usize) {
541    let (mut val, mut aft) = (0, start);
542    for &b in &data[start..] {
543        aft += 1;
544        if b & 0x80 == 0 { break }
545    }
546    for &b in data[start..aft].iter().rev() {
547        val = (val << 7) | (b & 0x7f) as u64;
548    }
549    (val, aft)
550}
551
552impl BinaryRead<'_> for u64 {
553    fn read(code: &[u8], _: &[u8], start: usize) -> (Self, usize) {
554        decode_u64(code, start)
555    }
556}
557impl BinaryWrite for u64 {
558    fn append(val: &Self, code: &mut Vec<u8>, _: &mut BinPool, _: &mut Vec<RelocateInfo>) {
559        encode_u64(*val, code, None)
560    }
561}
562
563#[test]
564fn test_binary_u64() {
565    let mut buf = vec![];
566    let tests = [
567        (0,                 [0x00].as_slice(),                                                       [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
568        (1,                 [0x01].as_slice(),                                                       [0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
569        (2,                 [0x02].as_slice(),                                                       [0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
570        (0x53,              [0x53].as_slice(),                                                       [0xd3, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
571        (0x7f,              [0x7f].as_slice(),                                                       [0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
572        (0x80,              [0x80, 0x01].as_slice(),                                                 [0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00].as_slice()),
573        (0x347462356236574, [0xf4, 0xca, 0x8d, 0xb1, 0xb5, 0xc4, 0xd1, 0xa3, 0x03].as_slice(),       [0xf4, 0xca, 0x8d, 0xb1, 0xb5, 0xc4, 0xd1, 0xa3, 0x83, 0x00].as_slice()),
574        (u64::MAX >> 1,     [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f].as_slice(),       [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00].as_slice()),
575        (u64::MAX,          [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01].as_slice(), [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01].as_slice()),
576    ];
577    for (v, expect_small, expect_large) in tests {
578        for (expect, expanded) in [(expect_small, false), (expect_large, true)] {
579            for prefix_bytes in 0..8 {
580                buf.clear();
581                buf.extend(core::iter::once(0x53).cycle().take(prefix_bytes));
582                encode_u64(v, &mut buf, if expanded { Some(MAX_U64_ENCODED_BYTES) } else { None });
583                assert!(buf[..prefix_bytes].iter().all(|&x| x == 0x53));
584                assert_eq!(&buf[prefix_bytes..], expect);
585                buf.extend(core::iter::once(0xff).cycle().take(8));
586                let (back, aft) = <u64 as BinaryRead>::read(&buf, &[], prefix_bytes);
587                assert_eq!(back, v);
588                assert_eq!(aft, prefix_bytes + expect.len());
589            }
590        }
591    }
592}
593
594// stores the value as u64 by shifting up by one bit and storing a bitwise-not flag in the low order bit.
595// the u64 value is then stored using the variable width algorithm for u64.
596impl BinaryRead<'_> for i32 {
597    fn read(code: &[u8], data: &[u8], start: usize) -> (Self, usize) {
598        let (raw, aft) = <u64 as BinaryRead>::read(code, data, start);
599        let v = (raw >> 1) as u32;
600        (if raw & 1 == 0 { v } else { !v } as i32, aft)
601    }
602}
603impl BinaryWrite for i32 {
604    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>) {
605        let v: u64 = ((*val) as u64) << 1;
606        BinaryWrite::append(&if v & 0x8000000000000000 == 0 { v } else { !v }, code, data, relocate_info)
607    }
608}
609
610// encodes a color using exactly 4 bytes (argb encoded integer).
611// for typical colors, there's no point in doing anything fancy like variable width encodings.
612impl BinaryRead<'_> for Color {
613    fn read(code: &[u8], _: &[u8], start: usize) -> (Self, usize) {
614        let mut res = [0u8; 4];
615        res[..].copy_from_slice(&code[start..start + 4]);
616        let [a, r, g, b] = res;
617        (Color { r, g, b, a }, start + 4)
618    }
619}
620impl BinaryWrite for Color {
621    fn append(val: &Self, code: &mut Vec<u8>, _: &mut BinPool, _: &mut Vec<RelocateInfo>) {
622        let Color { r, g, b, a } = *val;
623        code.extend_from_slice(&[a, r, g ,b]);
624    }
625}
626
627#[test]
628fn test_binary_i32() {
629    let mut buf = vec![];
630    let mut discard = (BinPool::new(), vec![]);
631    let tests = [
632        (0,         [0x00].as_slice()),
633        (-1,        [0x01].as_slice()),
634        (1,         [0x02].as_slice()),
635        (-2,        [0x03].as_slice()),
636        (2,         [0x04].as_slice()),
637        (0x543245,  [0x8a, 0xc9, 0xa1, 0x05].as_slice()),
638        (-0x376224, [0xc7, 0x88, 0xbb, 0x03].as_slice()),
639        (-i32::MAX, [0xfd, 0xff, 0xff, 0xff, 0x0f].as_slice()),
640        (i32::MAX,  [0xfe, 0xff, 0xff, 0xff, 0x0f].as_slice()),
641        (i32::MIN,  [0xff, 0xff, 0xff, 0xff, 0x0f].as_slice()),
642    ];
643    for (v, expect) in tests {
644        for prefix_bytes in 0..8 {
645            buf.clear();
646            buf.extend(core::iter::once(0x53).cycle().take(prefix_bytes));
647            BinaryWrite::append(&v, &mut buf, &mut discard.0, &mut discard.1);
648            assert_eq!(discard.0.len(), 0);
649            assert_eq!(discard.1.len(), 0);
650            assert!(buf[..prefix_bytes].iter().all(|&x| x == 0x53));
651            assert_eq!(&buf[prefix_bytes..], expect);
652            buf.extend(core::iter::once(0xff).cycle().take(8));
653            let (back, aft) = <i32 as BinaryRead>::read(&buf, &[], prefix_bytes);
654            assert_eq!(back, v);
655            assert_eq!(aft, prefix_bytes + expect.len());
656        }
657    }
658}
659
660impl BinaryRead<'_> for f64 {
661    fn read(code: &[u8], data: &[u8], start: usize) -> (Self, usize) {
662        let (v, aft) = <u64 as BinaryRead>::read(code, data, start);
663        (f64::from_bits(v.swap_bytes()), aft)
664    }
665}
666impl BinaryWrite for f64 {
667    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>) {
668        BinaryWrite::append(&val.to_bits().swap_bytes(), code, data, relocate_info)
669    }
670}
671
672impl BinaryRead<'_> for usize {
673    fn read(code: &[u8], data: &[u8], start: usize) -> (Self, usize) {
674        let (v, aft) = <u64 as BinaryRead>::read(code, data, start);
675        debug_assert!(v <= usize::MAX as u64);
676        (v as usize, aft)
677    }
678}
679impl BinaryWrite for usize {
680    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>) {
681        BinaryWrite::append(&(*val as u64), code, data, relocate_info)
682    }
683}
684
685impl<'a> BinaryRead<'a> for &'a str {
686    fn read(code: &'a [u8], data: &'a [u8], start: usize) -> (Self, usize) {
687        let (data_pos, aft) = <usize as BinaryRead>::read(code, data, start);
688        let (data_len, aft) = <usize as BinaryRead>::read(code, data, aft);
689        (core::str::from_utf8(&data[data_pos..data_pos + data_len]).unwrap(), aft)
690    }
691}
692
693impl<'a> BinaryRead<'a> for Instruction<'a> {
694    fn read(code: &'a [u8], data: &'a [u8], start: usize) -> (Self, usize) {
695        macro_rules! read_prefixed {
696            (Instruction::$root:ident) => {
697                (Instruction::$root, start + 1)
698            };
699            (Instruction::$root:ident { $($tt:tt)* } $(: $($vals:ident),+$(,)? )?) => {{
700                #[allow(unused_mut)]
701                let mut parsing_stop = start + 1;
702                $($(let $vals = {
703                    let x = BinaryRead::read(code, data, parsing_stop);
704                    parsing_stop = x.1;
705                    x.0
706                };)*)?
707                (Instruction::$root { $($tt)* $($($vals),+ )? }, parsing_stop)
708            }};
709        }
710        match code[start] {
711            0 => read_prefixed!(Instruction::Yield),
712            1 => read_prefixed!(Instruction::WarpStart),
713            2 => read_prefixed!(Instruction::WarpStop),
714
715            3 => read_prefixed!(Instruction::PushBool { value: false }),
716            4 => read_prefixed!(Instruction::PushBool { value: true }),
717
718            5 => read_prefixed!(Instruction::PushInt {} : value),
719            6 => read_prefixed!(Instruction::PushIntString {} : value),
720            7 => read_prefixed!(Instruction::PushNumber {} : value),
721            8 => read_prefixed!(Instruction::PushColor {} : value),
722
723            9 => read_prefixed!(Instruction::PushString { value: "" }),
724            10 => read_prefixed!(Instruction::PushString {} : value),
725
726            11 => read_prefixed!(Instruction::PushVariable {} : var),
727            12 => read_prefixed!(Instruction::PushEntity {} : name),
728            13 => read_prefixed!(Instruction::PushSelf),
729
730            14 => read_prefixed!(Instruction::PopValue),
731
732            15 => read_prefixed!(Instruction::DupeValue { top_index: 0 }),
733            16 => read_prefixed!(Instruction::DupeValue { top_index: 1 }),
734            17 => read_prefixed!(Instruction::DupeValue { top_index: 2 }),
735            18 => read_prefixed!(Instruction::DupeValue { top_index: 3 }),
736
737            19 => read_prefixed!(Instruction::SwapValues { top_index_1: 0, top_index_2: 1 }),
738            20 => read_prefixed!(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }),
739            21 => read_prefixed!(Instruction::SwapValues { top_index_1: 1, top_index_2: 2 }),
740            22 => read_prefixed!(Instruction::SwapValues { top_index_1: 1, top_index_2: 3 }),
741
742            23 => read_prefixed!(Instruction::TypeQuery {} : ty),
743            24 => read_prefixed!(Instruction::ToBool),
744            25 => read_prefixed!(Instruction::ToNumber),
745
746            26 => read_prefixed!(Instruction::ListCons),
747            27 => read_prefixed!(Instruction::ListCdr),
748
749            28 => read_prefixed!(Instruction::ListFind),
750            29 => read_prefixed!(Instruction::ListContains),
751
752            30 => read_prefixed!(Instruction::ListIsEmpty),
753            31 => read_prefixed!(Instruction::ListLength),
754            32 => read_prefixed!(Instruction::ListDims),
755            33 => read_prefixed!(Instruction::ListRank),
756
757            34 => read_prefixed!(Instruction::ListRev),
758            35 => read_prefixed!(Instruction::ListFlatten),
759            36 => read_prefixed!(Instruction::ListReshape {} : len),
760            37 => read_prefixed!(Instruction::ListCartesianProduct {} : len),
761
762            38 => read_prefixed!(Instruction::ListJson),
763            39 => read_prefixed!(Instruction::ListCsv),
764            40 => read_prefixed!(Instruction::ListColumns),
765            41 => read_prefixed!(Instruction::ListLines),
766
767            42 => read_prefixed!(Instruction::ListInsert),
768            43 => read_prefixed!(Instruction::ListInsertLast),
769            44 => read_prefixed!(Instruction::ListInsertRandom),
770
771            45 => read_prefixed!(Instruction::ListGet),
772            46 => read_prefixed!(Instruction::ListGetLast),
773            47 => read_prefixed!(Instruction::ListGetRandom),
774
775            48 => read_prefixed!(Instruction::ListAssign),
776            49 => read_prefixed!(Instruction::ListAssignLast),
777            50 => read_prefixed!(Instruction::ListAssignRandom),
778
779            51 => read_prefixed!(Instruction::ListRemove),
780            52 => read_prefixed!(Instruction::ListRemoveLast),
781            53 => read_prefixed!(Instruction::ListRemoveAll),
782
783            54 => read_prefixed!(Instruction::ListPopFirstOrElse {} : goto),
784
785            55 => read_prefixed!(Instruction::Cmp { relation: Relation::Equal }),
786            56 => read_prefixed!(Instruction::Cmp { relation: Relation::NotEqual }),
787            57 => read_prefixed!(Instruction::Cmp { relation: Relation::Less }),
788            58 => read_prefixed!(Instruction::Cmp { relation: Relation::LessEq }),
789            59 => read_prefixed!(Instruction::Cmp { relation: Relation::Greater }),
790            60 => read_prefixed!(Instruction::Cmp { relation: Relation::GreaterEq }),
791            61 => read_prefixed!(Instruction::Identical),
792
793            62 => read_prefixed!(Instruction::BinaryOp { op: BinaryOp::Add }),
794            63 => read_prefixed!(Instruction::BinaryOp { op: BinaryOp::Sub }),
795            64 => read_prefixed!(Instruction::BinaryOp { op: BinaryOp::Mul }),
796            65 => read_prefixed!(Instruction::BinaryOp { op: BinaryOp::Div }),
797            66 => read_prefixed!(Instruction::BinaryOp {} : op),
798
799            67 => read_prefixed!(Instruction::VariadicOp { op: VariadicOp::Add, } : len),
800            68 => read_prefixed!(Instruction::VariadicOp { op: VariadicOp::Mul, } : len),
801            69 => read_prefixed!(Instruction::VariadicOp { op: VariadicOp::StrCat, } : len),
802            70 => read_prefixed!(Instruction::VariadicOp { op: VariadicOp::MakeList, } : len),
803            71 => read_prefixed!(Instruction::VariadicOp {} : op, len),
804
805            72 => read_prefixed!(Instruction::UnaryOp { op: UnaryOp::Not }),
806            73 => read_prefixed!(Instruction::UnaryOp { op: UnaryOp::Round }),
807            74 => read_prefixed!(Instruction::UnaryOp {} : op),
808
809            75 => read_prefixed!(Instruction::DeclareLocal {} : var),
810            76 => read_prefixed!(Instruction::InitUpvar {} : var),
811            77 => read_prefixed!(Instruction::Assign {} : var),
812
813            78 => read_prefixed!(Instruction::BinaryOpAssign { op: BinaryOp::Add, } : var),
814            79 => read_prefixed!(Instruction::BinaryOpAssign {} : var, op),
815
816            80 => read_prefixed!(Instruction::Watcher {} : create, var),
817            81 => read_prefixed!(Instruction::Pause),
818
819            82 => read_prefixed!(Instruction::Jump {} : to),
820            83 => read_prefixed!(Instruction::ConditionalJump { when: false, } : to),
821            84 => read_prefixed!(Instruction::ConditionalJump { when: true, } : to),
822
823            85 => read_prefixed!(Instruction::Call { tokens: "", } : pos),
824            86 => read_prefixed!(Instruction::Call {} : pos, tokens),
825            87 => read_prefixed!(Instruction::MakeClosure {} : pos, params, tokens),
826            88 => read_prefixed!(Instruction::CallClosure { new_entity: false, } : args),
827            89 => read_prefixed!(Instruction::CallClosure { new_entity: true, } : args),
828            90 => read_prefixed!(Instruction::ForkClosure {} : args),
829            91 => read_prefixed!(Instruction::Return),
830            92 => read_prefixed!(Instruction::Abort {} : mode),
831
832            93 => read_prefixed!(Instruction::PushHandler {} : pos, var),
833            94 => read_prefixed!(Instruction::PopHandler),
834            95 => read_prefixed!(Instruction::Throw),
835
836            96 => read_prefixed!(Instruction::CallRpc {} : tokens),
837            97 => read_prefixed!(Instruction::PushRpcError),
838
839            98 => read_prefixed!(Instruction::Syscall {} : len),
840            99 => read_prefixed!(Instruction::PushSyscallError),
841
842            100 => read_prefixed!(Instruction::SendLocalMessage { wait: false, target: false }),
843            101 => read_prefixed!(Instruction::SendLocalMessage { wait: false, target: true }),
844            102 => read_prefixed!(Instruction::SendLocalMessage { wait: true, target: false }),
845            103 => read_prefixed!(Instruction::SendLocalMessage { wait: true, target: true }),
846
847            104 => read_prefixed!(Instruction::PushLocalMessage),
848
849            105 => read_prefixed!(Instruction::Print { style: PrintStyle::Say }),
850            106 => read_prefixed!(Instruction::Print { style: PrintStyle::Think }),
851            107 => read_prefixed!(Instruction::Ask),
852            108 => read_prefixed!(Instruction::PushAnswer),
853
854            109 => read_prefixed!(Instruction::ResetTimer),
855            110 => read_prefixed!(Instruction::PushTimer),
856            111 => read_prefixed!(Instruction::Sleep),
857            112 => read_prefixed!(Instruction::PushRealTime {} : query),
858
859            113 => read_prefixed!(Instruction::SendNetworkMessage { expect_reply: false, } : tokens),
860            114 => read_prefixed!(Instruction::SendNetworkMessage { expect_reply: true, } : tokens),
861            115 => read_prefixed!(Instruction::SendNetworkReply),
862
863            116 => read_prefixed!(Instruction::PushProperty {} : prop),
864            117 => read_prefixed!(Instruction::SetProperty {} : prop),
865            118 => read_prefixed!(Instruction::ChangeProperty {} : prop),
866
867            119 => read_prefixed!(Instruction::PushCostume),
868            120 => read_prefixed!(Instruction::PushCostumeNumber),
869            121 => read_prefixed!(Instruction::PushCostumeList),
870            122 => read_prefixed!(Instruction::PushCostumeProperty {} : prop),
871
872            123 => read_prefixed!(Instruction::SetCostume),
873            124 => read_prefixed!(Instruction::NextCostume),
874
875            125 => read_prefixed!(Instruction::PushSoundList),
876            126 => read_prefixed!(Instruction::PushSoundProperty {} : prop),
877
878            127 => read_prefixed!(Instruction::PlaySound { blocking: true }),
879            128 => read_prefixed!(Instruction::PlaySound { blocking: false }),
880            129 => read_prefixed!(Instruction::PlayNotes { blocking: true }),
881            130 => read_prefixed!(Instruction::PlayNotes { blocking: false }),
882            131 => read_prefixed!(Instruction::StopSounds),
883
884            132 => read_prefixed!(Instruction::Clone),
885            133 => read_prefixed!(Instruction::DeleteClone),
886
887            134 => read_prefixed!(Instruction::ClearEffects),
888            135 => read_prefixed!(Instruction::ClearDrawings),
889
890            136 => read_prefixed!(Instruction::GotoXY),
891            137 => read_prefixed!(Instruction::Goto),
892
893            138 => read_prefixed!(Instruction::PointTowardsXY),
894            139 => read_prefixed!(Instruction::PointTowards),
895
896            140 => read_prefixed!(Instruction::Forward),
897
898            141 => read_prefixed!(Instruction::UnknownBlock {} : name, args),
899
900            _ => unreachable!(),
901        }
902    }
903}
904impl BinaryWrite for Instruction<'_> {
905    fn append(val: &Self, code: &mut Vec<u8>, data: &mut BinPool, relocate_info: &mut Vec<RelocateInfo>) {
906        macro_rules! append_prefixed {
907            ($op:literal $(: $($($vals:ident)+),+)?) => {{
908                code.push($op);
909                $($( append_prefixed!(@single $($vals)+); )*)?
910            }};
911            (@single move $val:ident) => {{
912                relocate_info.push(RelocateInfo::Code { code_addr: code.len() });
913                encode_u64(*$val as u64, code, Some(MAX_U64_ENCODED_BYTES));
914            }};
915            (@single move str $val:ident) => {{
916                let pool_index = data.add($val.as_bytes());
917                relocate_info.push(RelocateInfo::Data { code_addr: code.len() });
918                encode_u64(pool_index as u64, code, Some(MAX_U64_ENCODED_BYTES));
919                BinaryWrite::append(&$val.len(), code, data, relocate_info);
920            }};
921            (@single $val:ident) => { BinaryWrite::append($val, code, data, relocate_info) };
922        }
923        match val {
924            Instruction::Yield => append_prefixed!(0),
925            Instruction::WarpStart => append_prefixed!(1),
926            Instruction::WarpStop => append_prefixed!(2),
927
928            Instruction::PushBool { value: false } => append_prefixed!(3),
929            Instruction::PushBool { value: true } => append_prefixed!(4),
930
931            Instruction::PushInt { value } => append_prefixed!(5: value),
932            Instruction::PushIntString { value } => append_prefixed!(6: value),
933            Instruction::PushNumber { value } => append_prefixed!(7: value),
934            Instruction::PushColor { value } => append_prefixed!(8: value),
935
936            Instruction::PushString { value: "" } => append_prefixed!(9),
937            Instruction::PushString { value } => append_prefixed!(10: move str value),
938
939            Instruction::PushVariable { var } => append_prefixed!(11: move str var),
940            Instruction::PushEntity { name } => append_prefixed!(12: move str name),
941            Instruction::PushSelf => append_prefixed!(13),
942
943            Instruction::PopValue => append_prefixed!(14),
944
945            Instruction::DupeValue { top_index: 0 } => append_prefixed!(15),
946            Instruction::DupeValue { top_index: 1 } => append_prefixed!(16),
947            Instruction::DupeValue { top_index: 2 } => append_prefixed!(17),
948            Instruction::DupeValue { top_index: 3 } => append_prefixed!(18),
949            Instruction::DupeValue { top_index: _ } => unreachable!(),
950
951            Instruction::SwapValues { top_index_1: 0, top_index_2: 1 } => append_prefixed!(19),
952            Instruction::SwapValues { top_index_1: 0, top_index_2: 2 } => append_prefixed!(20),
953            Instruction::SwapValues { top_index_1: 1, top_index_2: 2 } => append_prefixed!(21),
954            Instruction::SwapValues { top_index_1: 1, top_index_2: 3 } => append_prefixed!(22),
955            Instruction::SwapValues { top_index_1: _, top_index_2: _ } => unreachable!(),
956
957            Instruction::TypeQuery { ty } => append_prefixed!(23: ty),
958            Instruction::ToBool => append_prefixed!(24),
959            Instruction::ToNumber => append_prefixed!(25),
960
961            Instruction::ListCons => append_prefixed!(26),
962            Instruction::ListCdr => append_prefixed!(27),
963
964            Instruction::ListFind => append_prefixed!(28),
965            Instruction::ListContains => append_prefixed!(29),
966
967            Instruction::ListIsEmpty => append_prefixed!(30),
968            Instruction::ListLength => append_prefixed!(31),
969            Instruction::ListDims => append_prefixed!(32),
970            Instruction::ListRank => append_prefixed!(33),
971
972            Instruction::ListRev => append_prefixed!(34),
973            Instruction::ListFlatten => append_prefixed!(35),
974            Instruction::ListReshape { len } => append_prefixed!(36: len),
975            Instruction::ListCartesianProduct { len } => append_prefixed!(37: len),
976
977            Instruction::ListJson => append_prefixed!(38),
978            Instruction::ListCsv => append_prefixed!(39),
979            Instruction::ListColumns => append_prefixed!(40),
980            Instruction::ListLines => append_prefixed!(41),
981
982            Instruction::ListInsert => append_prefixed!(42),
983            Instruction::ListInsertLast => append_prefixed!(43),
984            Instruction::ListInsertRandom => append_prefixed!(44),
985
986            Instruction::ListGet => append_prefixed!(45),
987            Instruction::ListGetLast => append_prefixed!(46),
988            Instruction::ListGetRandom => append_prefixed!(47),
989
990            Instruction::ListAssign => append_prefixed!(48),
991            Instruction::ListAssignLast => append_prefixed!(49),
992            Instruction::ListAssignRandom => append_prefixed!(50),
993
994            Instruction::ListRemove => append_prefixed!(51),
995            Instruction::ListRemoveLast => append_prefixed!(52),
996            Instruction::ListRemoveAll => append_prefixed!(53),
997
998            Instruction::ListPopFirstOrElse { goto } => append_prefixed!(54: move goto),
999
1000            Instruction::Cmp { relation: Relation::Equal } => append_prefixed!(55),
1001            Instruction::Cmp { relation: Relation::NotEqual } => append_prefixed!(56),
1002            Instruction::Cmp { relation: Relation::Less } => append_prefixed!(57),
1003            Instruction::Cmp { relation: Relation::LessEq } => append_prefixed!(58),
1004            Instruction::Cmp { relation: Relation::Greater } => append_prefixed!(59),
1005            Instruction::Cmp { relation: Relation::GreaterEq } => append_prefixed!(60),
1006            Instruction::Identical => append_prefixed!(61),
1007
1008            Instruction::BinaryOp { op: BinaryOp::Add } => append_prefixed!(62),
1009            Instruction::BinaryOp { op: BinaryOp::Sub } => append_prefixed!(63),
1010            Instruction::BinaryOp { op: BinaryOp::Mul } => append_prefixed!(64),
1011            Instruction::BinaryOp { op: BinaryOp::Div } => append_prefixed!(65),
1012            Instruction::BinaryOp { op } => append_prefixed!(66: op),
1013
1014            Instruction::VariadicOp { op: VariadicOp::Add, len } => append_prefixed!(67: len),
1015            Instruction::VariadicOp { op: VariadicOp::Mul, len } => append_prefixed!(68: len),
1016            Instruction::VariadicOp { op: VariadicOp::StrCat, len } => append_prefixed!(69: len),
1017            Instruction::VariadicOp { op: VariadicOp::MakeList, len } => append_prefixed!(70: len),
1018            Instruction::VariadicOp { op, len } => append_prefixed!(71: op, len),
1019
1020            Instruction::UnaryOp { op: UnaryOp::Not } => append_prefixed!(72),
1021            Instruction::UnaryOp { op: UnaryOp::Round } => append_prefixed!(73),
1022            Instruction::UnaryOp { op } => append_prefixed!(74: op),
1023
1024            Instruction::DeclareLocal { var } => append_prefixed!(75: move str var),
1025            Instruction::InitUpvar { var } => append_prefixed!(76: move str var),
1026            Instruction::Assign { var } => append_prefixed!(77: move str var),
1027
1028            Instruction::BinaryOpAssign { var, op: BinaryOp::Add } => append_prefixed!(78: move str var),
1029            Instruction::BinaryOpAssign { var, op } => append_prefixed!(79: move str var, op),
1030
1031            Instruction::Watcher { create, var } => append_prefixed!(80: create, move str var),
1032            Instruction::Pause => append_prefixed!(81),
1033
1034            Instruction::Jump { to } => append_prefixed!(82: move to),
1035            Instruction::ConditionalJump { to, when: false } => append_prefixed!(83: move to),
1036            Instruction::ConditionalJump { to, when: true } => append_prefixed!(84: move to),
1037
1038            Instruction::Call { pos, tokens: "" } => append_prefixed!(85: move pos),
1039            Instruction::Call { pos, tokens } => append_prefixed!(86: move pos, move str tokens),
1040            Instruction::MakeClosure { pos, params, tokens } => append_prefixed!(87: move pos, params, move str tokens),
1041            Instruction::CallClosure { new_entity: false, args } => append_prefixed!(88: args),
1042            Instruction::CallClosure { new_entity: true, args } => append_prefixed!(89: args),
1043            Instruction::ForkClosure { args } => append_prefixed!(90: args),
1044            Instruction::Return => append_prefixed!(91),
1045            Instruction::Abort { mode } => append_prefixed!(92: mode),
1046
1047            Instruction::PushHandler { pos, var } => append_prefixed!(93: move pos, move str var),
1048            Instruction::PopHandler => append_prefixed!(94),
1049            Instruction::Throw => append_prefixed!(95),
1050
1051            Instruction::CallRpc { tokens } => append_prefixed!(96: move str tokens),
1052            Instruction::PushRpcError => append_prefixed!(97),
1053
1054            Instruction::Syscall { len } => append_prefixed!(98: len),
1055            Instruction::PushSyscallError => append_prefixed!(99),
1056
1057            Instruction::SendLocalMessage { wait: false, target: false } => append_prefixed!(100),
1058            Instruction::SendLocalMessage { wait: false, target: true } => append_prefixed!(101),
1059            Instruction::SendLocalMessage { wait: true, target: false } => append_prefixed!(102),
1060            Instruction::SendLocalMessage { wait: true, target: true } => append_prefixed!(103),
1061
1062            Instruction::PushLocalMessage => append_prefixed!(104),
1063
1064            Instruction::Print { style: PrintStyle::Say } => append_prefixed!(105),
1065            Instruction::Print { style: PrintStyle::Think } => append_prefixed!(106),
1066            Instruction::Ask => append_prefixed!(107),
1067            Instruction::PushAnswer => append_prefixed!(108),
1068
1069            Instruction::ResetTimer => append_prefixed!(109),
1070            Instruction::PushTimer => append_prefixed!(110),
1071            Instruction::Sleep => append_prefixed!(111),
1072            Instruction::PushRealTime { query } => append_prefixed!(112: query),
1073
1074            Instruction::SendNetworkMessage { tokens, expect_reply: false } => append_prefixed!(113: move str tokens),
1075            Instruction::SendNetworkMessage { tokens, expect_reply: true } => append_prefixed!(114: move str tokens),
1076            Instruction::SendNetworkReply => append_prefixed!(115),
1077
1078            Instruction::PushProperty { prop } => append_prefixed!(116: prop),
1079            Instruction::SetProperty { prop } => append_prefixed!(117: prop),
1080            Instruction::ChangeProperty { prop } => append_prefixed!(118: prop),
1081
1082            Instruction::PushCostume => append_prefixed!(119),
1083            Instruction::PushCostumeNumber => append_prefixed!(120),
1084            Instruction::PushCostumeList => append_prefixed!(121),
1085            Instruction::PushCostumeProperty { prop } => append_prefixed!(122: prop),
1086
1087            Instruction::SetCostume => append_prefixed!(123),
1088            Instruction::NextCostume => append_prefixed!(124),
1089
1090            Instruction::PushSoundList => append_prefixed!(125),
1091            Instruction::PushSoundProperty { prop } => append_prefixed!(126: prop),
1092
1093            Instruction::PlaySound { blocking: true } => append_prefixed!(127),
1094            Instruction::PlaySound { blocking: false } => append_prefixed!(128),
1095            Instruction::PlayNotes { blocking: true } => append_prefixed!(129),
1096            Instruction::PlayNotes { blocking: false } => append_prefixed!(130),
1097            Instruction::StopSounds => append_prefixed!(131),
1098
1099            Instruction::Clone => append_prefixed!(132),
1100            Instruction::DeleteClone => append_prefixed!(133),
1101
1102            Instruction::ClearEffects => append_prefixed!(134),
1103            Instruction::ClearDrawings => append_prefixed!(135),
1104
1105            Instruction::GotoXY => append_prefixed!(136),
1106            Instruction::Goto => append_prefixed!(137),
1107
1108            Instruction::PointTowardsXY => append_prefixed!(138),
1109            Instruction::PointTowards => append_prefixed!(139),
1110
1111            Instruction::Forward => append_prefixed!(140),
1112
1113            Instruction::UnknownBlock { name, args } => append_prefixed!(141: move str name, args),
1114        }
1115    }
1116}
1117
1118/// An interpreter-ready sequence of instructions.
1119///
1120/// [`Process`](crate::process::Process) is an execution primitive that can be used to execute generated [`ByteCode`].
1121///
1122/// Generated [`ByteCode`] is highly optimized for size and sees frequent breaking changes.
1123/// Because of this, there is currently no support for touching the actual bytecode itself.
1124/// If you wish to view the generated bytecode, you may use the [`ByteCode::dump_code`] and [`ByteCode::dump_data`] utilities,
1125/// a wrapper for which is included in the standard `netsblox-vm` [`cli`].
1126/// 
1127/// This type supports serde serialization if the `serde` feature flag is enabled.
1128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1129pub struct ByteCode {
1130    #[allow(dead_code)] tag: MustBeU128<FINGERPRINT>,
1131
1132    pub(crate) code: Box<[u8]>,
1133    pub(crate) data: Box<[u8]>,
1134}
1135
1136#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1137pub(crate) enum InitValue {
1138    Bool(bool),
1139    Number(Number),
1140    Ref(usize),
1141}
1142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1143pub(crate) enum RefValue {
1144    List(Vec<InitValue>),
1145    Image(Vec<u8>, Option<(Number, Number)>, CompactString),
1146    Audio(Vec<u8>, CompactString),
1147    Text(CompactString),
1148}
1149#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1150pub(crate) struct EntityInitInfo {
1151    pub(crate) name: CompactString,
1152    pub(crate) fields: VecMap<CompactString, InitValue, false>,
1153    pub(crate) costumes: VecMap<CompactString, InitValue, false>,
1154    pub(crate) sounds: VecMap<CompactString, InitValue, false>,
1155    pub(crate) scripts: Vec<(Event, usize)>,
1156
1157    pub(crate) visible: bool,
1158    pub(crate) active_costume: Option<usize>,
1159    pub(crate) size: Number,
1160    pub(crate) color: (u8, u8, u8, u8),
1161    pub(crate) pos: (Number, Number),
1162    pub(crate) heading: Number,
1163}
1164/// A structure that stores the initialization state of a project.
1165/// 
1166/// When compiling a project via [`ByteCode::compile`], the [`ByteCode`] object will contain all
1167/// the necessary code and logic to run the project.
1168/// However, this does not include the state on which the bytecode acts, which only exists at runtime.
1169/// [`InitInfo`] exists to hold the initial state of a project in a serializable format.
1170/// 
1171/// This type supports serde serialization if the `serde` feature flag is enabled.
1172#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1173pub struct InitInfo {
1174    #[allow(dead_code)] tag: MustBeU128<FINGERPRINT>,
1175
1176    pub(crate) proj_name: CompactString,
1177    pub(crate) ref_values: Vec<RefValue>,
1178    pub(crate) globals: VecMap<CompactString, InitValue, false>,
1179    pub(crate) entities: Vec<EntityInitInfo>,
1180}
1181
1182/// Entity-level [`ScriptInfo`] in a [`ByteCode`] object.
1183pub struct EntityScriptInfo<'a> {
1184    pub funcs: Vec<(&'a ast::Function, usize)>,
1185    pub scripts: Vec<(&'a ast::Script, usize)>,
1186}
1187/// Script information in a [`ByteCode`] object.
1188/// 
1189/// When a project is compiled via [`ByteCode::compile`], a contiguous stream of bytecode instructions is generated.
1190/// This object maps each function (block) and script to its final bytecode location,
1191/// making it possible to execute specific blocks or scripts.
1192/// 
1193/// In typical usage, this info is not needed, and is only used internally for generating bytecode.
1194/// Its primary use would be in executing a single [`Process`](crate::process::Process) rather than an entire [`Project`](crate::project::Project).
1195pub struct ScriptInfo<'a> {
1196    pub funcs: Vec<(&'a ast::Function, usize)>,
1197    pub entities: Vec<(&'a ast::Entity, EntityScriptInfo<'a>)>,
1198}
1199
1200struct LocationTokenizer<'a> {
1201    src: &'a str,
1202    state: bool,
1203}
1204impl<'a> LocationTokenizer<'a> {
1205    fn new(src: &'a str) -> Self {
1206        Self { src, state: false }
1207    }
1208}
1209impl<'a> Iterator for LocationTokenizer<'a> {
1210    type Item = &'a str;
1211    fn next(&mut self) -> Option<Self::Item> {
1212        if self.src.is_empty() {
1213            return None;
1214        }
1215
1216        let p = self.src.char_indices().find(|x| self.state ^ (x.1 == '-' || ('0'..='9').contains(&x.1))).map(|x| x.0).unwrap_or(self.src.len());
1217        let res;
1218        (res, self.src) = self.src.split_at(p);
1219        self.state ^= true;
1220        Some(res)
1221    }
1222}
1223#[test]
1224fn test_locations_tokenizer() {
1225    assert_eq!(LocationTokenizer::new("").collect::<Vec<_>>(), &[] as &[&str]);
1226    assert_eq!(LocationTokenizer::new("x").collect::<Vec<_>>(), &["x"]);
1227    assert_eq!(LocationTokenizer::new("3").collect::<Vec<_>>(), &["", "3"]);
1228    assert_eq!(LocationTokenizer::new("collab_").collect::<Vec<_>>(), &["collab_"]);
1229    assert_eq!(LocationTokenizer::new("collab_-1").collect::<Vec<_>>(), &["collab_", "-1"]);
1230    assert_eq!(LocationTokenizer::new("collab_23_43").collect::<Vec<_>>(), &["collab_", "23", "_", "43"]);
1231    assert_eq!(LocationTokenizer::new("cab_=_2334fhd__43").collect::<Vec<_>>(), &["cab_=_", "2334", "fhd__", "43"]);
1232    assert_eq!(LocationTokenizer::new("cab_=_2334fhd__43__").collect::<Vec<_>>(), &["cab_=_", "2334", "fhd__", "43", "__"]);
1233    assert_eq!(LocationTokenizer::new("-4cab_=_2334fhd__43__").collect::<Vec<_>>(), &["", "-4", "cab_=_", "2334", "fhd__", "43", "__"]);
1234    assert_eq!(LocationTokenizer::new("714cab_=_2334fhd__43__").collect::<Vec<_>>(), &["", "714", "cab_=_", "2334", "fhd__", "43", "__"]);
1235}
1236
1237/// Location lookup table from bytecode address to original AST location.
1238/// 
1239/// When a project is compiled via [`ByteCode::compile`], a stream of instructions is produced.
1240/// However, this conversion loses the information that would be needed to produce human-readable error locations.
1241/// This object acts as a map from bytecode location to original AST location in the source project.
1242/// 
1243/// This type supports serde serialization if the `serde` feature flag is enabled.
1244#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1245#[derive(Debug)]
1246pub struct Locations {
1247    #[allow(dead_code)] tag: MustBeU128<FINGERPRINT>,
1248
1249    prefix: CompactString,
1250    separator: CompactString,
1251    suffix: CompactString,
1252
1253    base_token: isize,
1254    value_data: Vec<u8>,
1255    locs: Vec<(usize, usize)>,
1256}
1257impl Locations {
1258    fn condense<'a>(orig_locs: &BTreeMap<usize, &'a str>) -> Result<Self, CompileError<'a>> {
1259        if orig_locs.is_empty() {
1260            return Ok(Self {
1261                tag: Default::default(),
1262
1263                prefix: CompactString::default(),
1264                separator: CompactString::default(),
1265                suffix: CompactString::default(),
1266
1267                base_token: 0,
1268                value_data: Default::default(),
1269                locs: Default::default(),
1270            });
1271        }
1272
1273        let (prefix, suffix) = {
1274            let mut tokens = LocationTokenizer::new(*orig_locs.values().next().unwrap()).enumerate();
1275            let prefix = tokens.next().map(|x| CompactString::new(x.1)).unwrap_or_default();
1276            let suffix = tokens.last().filter(|x| x.0 & 1 == 0).map(|x| CompactString::new(x.1)).unwrap_or_default();
1277            (prefix, suffix)
1278        };
1279        let mut separator = None;
1280
1281        let mut value_map = Vec::with_capacity(orig_locs.len());
1282        for (&pos, &loc) in orig_locs.iter() {
1283            let mut tokens = LocationTokenizer::new(loc).peekable();
1284            let mut values = vec![];
1285            for i in 0.. {
1286                match tokens.next() {
1287                    Some(token) => {
1288                        if i & 1 != 0 {
1289                            let v: isize = match token.parse() {
1290                                Ok(x) => x,
1291                                Err(_) => return Err(CompileError::InvalidLocation { loc }),
1292                            };
1293                            if v.to_string() != token {
1294                                return Err(CompileError::InvalidLocation { loc });
1295                            }
1296                            values.push(v);
1297                        }
1298                        else if i == 0 {
1299                            if token != prefix {
1300                                return Err(CompileError::InvalidLocation { loc });
1301                            }
1302                        }
1303                        else if tokens.peek().is_none() {
1304                            if token != suffix {
1305                                return Err(CompileError::InvalidLocation { loc });
1306                            }
1307                        } else {
1308                            match &separator {
1309                                Some(separator) => if token != *separator {
1310                                    return Err(CompileError::InvalidLocation { loc });
1311                                }
1312                                None => separator = Some(CompactString::new(token)),
1313                            }
1314                        }
1315                    }
1316                    None => {
1317                        if i < 2 || (i & 1 == 0 && !suffix.is_empty()) {
1318                            return Err(CompileError::InvalidLocation { loc });
1319                        }
1320                        break;
1321                    }
1322                }
1323            }
1324            debug_assert!(values.len() >= 1);
1325            value_map.push((pos, values));
1326        }
1327
1328        let base_token = value_map.iter().flat_map(|x| &x.1).copied().min().unwrap_or(0);
1329
1330        let mut value_data = Vec::with_capacity(value_map.len());
1331        let mut locs = Vec::with_capacity(value_map.len());
1332        for (pos, toks) in value_map {
1333            locs.push((pos, value_data.len()));
1334            for tok in toks {
1335                encode_u64((tok - base_token + 1) as u64, &mut value_data, None);
1336            }
1337            encode_u64(0, &mut value_data, None); // null terminator
1338        }
1339
1340        let res = Self { tag: Default::default(), separator: separator.unwrap_or_default(), prefix, suffix, base_token, value_data, locs };
1341
1342        #[cfg(test)]
1343        {
1344            for pos in 0..orig_locs.iter().last().unwrap().0 + 16 {
1345                let expected = orig_locs.range(pos + 1..).next().map(|x| *x.1);
1346                let actual = res.lookup(pos);
1347                assert_eq!(expected, actual.as_deref());
1348            }
1349        }
1350
1351        Ok(res)
1352    }
1353
1354    /// Looks up a bytecode position and returns the most local block location provided by the ast.
1355    /// If the ast came from a standard NetsBlox XML file, this is the collab id of the looked up bytecode address.
1356    /// In other cases, this could be some other localization mechanism for error reporting (e.g., line number and column).
1357    ///
1358    /// Note that it is possible for blocks to not have location information,
1359    /// hence returning the most local location that was provided in the ast.
1360    pub fn lookup(&self, bytecode_pos: usize) -> Option<CompactString> {
1361        let mut start = {
1362            let p = self.locs.partition_point(|x| x.0 <= bytecode_pos);
1363            debug_assert!(p <= self.locs.len());
1364            self.locs.get(p)?.1
1365        };
1366
1367        let mut res = self.prefix.clone();
1368        let mut first = true;
1369        loop {
1370            let (v, aft) = decode_u64(&self.value_data, start);
1371            if v == 0 {
1372                break;
1373            }
1374
1375            start = aft;
1376            if !first {
1377                res.push_str(&self.separator);
1378            }
1379            first = false;
1380            write!(res, "{}", v as isize - 1 + self.base_token).unwrap();
1381        }
1382        res.push_str(&self.suffix);
1383        Some(res)
1384    }
1385}
1386#[test]
1387fn test_locations_formats() {
1388    fn test_vals<'a>(orig_locs: &BTreeMap<usize, &'a str>) -> Result<(), CompileError<'a>> {
1389        let condensed = Locations::condense(orig_locs)?;
1390
1391        let mut back = BTreeMap::new();
1392        for &k in orig_locs.keys() {
1393            back.insert(k, condensed.lookup(k - 1).unwrap());
1394        }
1395
1396        if orig_locs.len() != back.len() || orig_locs.iter().zip(back.iter()).any(|(a, b)| a.0 != b.0 || a.1 != b.1) {
1397            panic!("expected {orig_locs:?}\ngot {back:?}");
1398        }
1399
1400        Ok(())
1401    }
1402    macro_rules! map {
1403        ($($k:expr => $v:expr),*$(,)?) => {{
1404            #[allow(unused_mut)]
1405            let mut res = BTreeMap::new();
1406            $(res.insert($k, $v);)*
1407            res
1408        }}
1409    }
1410
1411    test_vals(&map! { }).unwrap();
1412    test_vals(&map! { 43 => "collab_3_4" }).unwrap();
1413    test_vals(&map! { 43 => "collab_3_4", 2 => "collab_-2_-34" }).unwrap();
1414    test_vals(&map! { 43 => "collab_3_4", 2 => "collab_-02_-34" }).unwrap_err();
1415    test_vals(&map! { 43 => "collab_3_4", 2 => "coLlab_-2_-34" }).unwrap_err();
1416    test_vals(&map! { 43 => "_31_-24", 2 => "_-2_-342", 6 => "_23" }).unwrap();
1417    test_vals(&map! { 43 => "31_-24", 2 => "-2_-342", 6 => "23" }).unwrap();
1418    test_vals(&map! { 43 => "31_-24", 2 => "-2_-342", 6 => "g23" }).unwrap_err();
1419    test_vals(&map! { 43 => "31_-24", 2 => "g-2_-342", 6 => "23" }).unwrap_err();
1420    test_vals(&map! { 43 => "g31_-24", 2 => "g-2_-342", 6 => "g23" }).unwrap();
1421    test_vals(&map! { 43 => "31_-24", 2 => "-2_-342<", 6 => "23" }).unwrap_err();
1422    test_vals(&map! { 43 => "31_-24", 2 => "-2_-342", 6 => "23&" }).unwrap_err();
1423    test_vals(&map! { 43 => "31_-24&", 2 => "-2_-342&", 6 => "23&" }).unwrap();
1424    test_vals(&map! { 43 => "31::-24&", 2 => "-2::-342&", 6 => "23&" }).unwrap();
1425    test_vals(&map! { 43 => "31::-24&", 2 => "-2::-342&", 6 => "23&", 7 => "&" }).unwrap_err();
1426    test_vals(&map! { 43 => "31::-24&", 2 => "-2::-342&", 6 => "23&", 0 => "&" }).unwrap_err();
1427    test_vals(&map! { 43 => "31::-24&", 2 => "-2::-342&", 6 => "23&" }).unwrap();
1428    test_vals(&map! { 43 => "31::-24&", 2 => "-2::-342&", 6 => "23&&" }).unwrap_err();
1429    test_vals(&map! { 43 => "<>31::-24&", 2 => "<>-2::-342:.:5::-22&", 6 => "<>23&" }).unwrap_err();
1430    test_vals(&map! { 43 => "<>31::-24&", 2 => "<>-2::-342::5::-22&", 6 => "<>23&" }).unwrap();
1431    test_vals(&map! { 43 => "<>31::-24&", 2 => "<>-2::-342::5::-22&", 6 => "<>&" }).unwrap_err();
1432    test_vals(&map! { 43 => "<>31::-24&", 2 => "<>-2::-342::5::-22&", 0 => "<>&" }).unwrap_err();
1433    test_vals(&map! { 43 => "<>31::-24&", 2 => "<>-2::-342::5::-22&", 6 => "<>23" }).unwrap_err();
1434    test_vals(&map! { 43 => "31,-24", 2 => "-2,-342,5,-22", 6 => "23", 7 => "" }).unwrap_err();
1435    test_vals(&map! { 43 => "31,-24", 2 => "-2,-342,5,-22", 6 => "23", 0 => "" }).unwrap_err();
1436}
1437
1438struct ByteCodeBuilder<'a: 'b, 'b> {
1439    ins: Vec<InternalInstruction<'b>>,
1440    call_holes: Vec<(usize, &'a ast::FnRef, Option<&'a ast::Entity>)>, // (hole pos, function, entity)
1441    closure_holes: VecDeque<(usize, &'a [ast::VariableDef], &'a [ast::VariableRef], &'a [ast::Stmt], Option<&'a ast::Entity>)>, // (hole pos, params, captures, stmts, entity)
1442    ins_locations: BTreeMap<usize, &'a str>,
1443    string_arena: &'b typed_arena::Arena<CompactString>,
1444}
1445impl<'a: 'b, 'b> ByteCodeBuilder<'a, 'b> {
1446    fn append_simple_ins(&mut self, entity: Option<&'a ast::Entity>, values: &[&'a ast::Expr], op: Instruction<'a>) -> Result<(), CompileError<'a>> {
1447        for value in values {
1448            self.append_expr(value, entity)?;
1449        }
1450        self.ins.push(op.into());
1451        Ok(())
1452    }
1453    fn append_variadic_op(&mut self, entity: Option<&'a ast::Entity>, src: &'a ast::Expr, op: VariadicOp) -> Result<(), CompileError<'a>> {
1454        let len = self.append_variadic(src, entity)?;
1455        self.ins.push(Instruction::VariadicOp { op, len}.into());
1456        Ok(())
1457    }
1458    fn append_variadic(&mut self, src: &'a ast::Expr, entity: Option<&'a ast::Entity>) -> Result<VariadicLen, CompileError<'a>> {
1459        Ok(match &src.kind {
1460            ast::ExprKind::Value(ast::Value::List(values, _)) => {
1461                for value in values.iter() {
1462                    self.append_value(value, entity)?;
1463                }
1464                VariadicLen::Fixed(values.len())
1465            }
1466            ast::ExprKind::MakeList { values } => {
1467                for value in values.iter() {
1468                    self.append_expr(value, entity)?;
1469                }
1470                VariadicLen::Fixed(values.len())
1471            }
1472            _ => {
1473                self.append_expr(src, entity)?;
1474                VariadicLen::Dynamic
1475            }
1476        })
1477    }
1478    fn append_value(&mut self, value: &'a ast::Value, entity: Option<&'a ast::Entity>) -> Result<(), CompileError<'a>> {
1479        match value {
1480            ast::Value::Number(v) => self.ins.push(Instruction::PushNumber { value: *v }.into()),
1481            ast::Value::String(v) => self.ins.push(Instruction::PushString { value: v }.into()),
1482            ast::Value::Constant(v) => self.ins.push(Instruction::PushNumber { value: match v {
1483                ast::Constant::Pi => core::f64::consts::PI,
1484                ast::Constant::E => core::f64::consts::E,
1485            }}.into()),
1486            ast::Value::Bool(v) => self.ins.push(Instruction::PushBool { value: *v }.into()),
1487            ast::Value::List(values, _) => {
1488                for v in values {
1489                    self.append_value(v, entity)?;
1490                }
1491                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(values.len()) }.into());
1492            }
1493            ast::Value::Image(_) => unreachable!(), // Snap! doesn't have image literals
1494            ast::Value::Audio(_) => unreachable!(), // Snap! doesn't have audio literals
1495            ast::Value::Ref(_) => unreachable!(), // Snap! doesn't have reference literals
1496        }
1497        Ok(())
1498    }
1499    fn append_expr(&mut self, expr: &'a ast::Expr, entity: Option<&'a ast::Entity>) -> Result<(), CompileError<'a>> {
1500        match &expr.kind {
1501            ast::ExprKind::Value(v) => self.append_value(v, entity)?,
1502            ast::ExprKind::Variable { var } => self.ins.push(Instruction::PushVariable { var: &var.trans_name }.into()),
1503            ast::ExprKind::Atan2 { y, x } => self.append_simple_ins(entity, &[y, x], BinaryOp::Atan2.into())?,
1504            ast::ExprKind::Sub { left, right } => self.append_simple_ins(entity, &[left, right], BinaryOp::Sub.into())?,
1505            ast::ExprKind::Div { left, right } => self.append_simple_ins(entity, &[left, right], BinaryOp::Div.into())?,
1506            ast::ExprKind::Pow { base, power } => self.append_simple_ins(entity, &[base, power], BinaryOp::Pow.into())?,
1507            ast::ExprKind::Mod { left, right } => self.append_simple_ins(entity, &[left, right], BinaryOp::Mod.into())?,
1508            ast::ExprKind::Log { base, value } => self.append_simple_ins(entity, &[base, value], BinaryOp::Log.into())?,
1509            ast::ExprKind::Neg { value } => self.append_simple_ins(entity, &[value], UnaryOp::Neg.into())?,
1510            ast::ExprKind::Abs { value } => self.append_simple_ins(entity, &[value], UnaryOp::Abs.into())?,
1511            ast::ExprKind::Sqrt { value } => self.append_simple_ins(entity, &[value], UnaryOp::Sqrt.into())?,
1512            ast::ExprKind::Sin { value } => self.append_simple_ins(entity, &[value], UnaryOp::Sin.into())?,
1513            ast::ExprKind::Cos { value } => self.append_simple_ins(entity, &[value], UnaryOp::Cos.into())?,
1514            ast::ExprKind::Tan { value } => self.append_simple_ins(entity, &[value], UnaryOp::Tan.into())?,
1515            ast::ExprKind::Asin { value } => self.append_simple_ins(entity, &[value], UnaryOp::Asin.into())?,
1516            ast::ExprKind::Acos { value } => self.append_simple_ins(entity, &[value], UnaryOp::Acos.into())?,
1517            ast::ExprKind::Atan { value } => self.append_simple_ins(entity, &[value], UnaryOp::Atan.into())?,
1518            ast::ExprKind::Round { value } => self.append_simple_ins(entity, &[value], UnaryOp::Round.into())?,
1519            ast::ExprKind::Floor { value } => self.append_simple_ins(entity, &[value], UnaryOp::Floor.into())?,
1520            ast::ExprKind::Ceil { value } => self.append_simple_ins(entity, &[value], UnaryOp::Ceil.into())?,
1521            ast::ExprKind::Not { value } => self.append_simple_ins(entity, &[value], UnaryOp::Not.into())?,
1522            ast::ExprKind::StrLen { value } => self.append_simple_ins(entity, &[value], UnaryOp::StrLen.into())?,
1523            ast::ExprKind::UnicodeToChar { value } => self.append_simple_ins(entity, &[value], UnaryOp::UnicodeToChar.into())?,
1524            ast::ExprKind::CharToUnicode { value } => self.append_simple_ins(entity, &[value], UnaryOp::CharToUnicode.into())?,
1525            ast::ExprKind::Greater { left, right } => self.append_simple_ins(entity, &[left, right], Relation::Greater.into())?,
1526            ast::ExprKind::GreaterEq { left, right } => self.append_simple_ins(entity, &[left, right], Relation::GreaterEq.into())?,
1527            ast::ExprKind::Less { left, right } => self.append_simple_ins(entity, &[left, right], Relation::Less.into())?,
1528            ast::ExprKind::LessEq { left, right } => self.append_simple_ins(entity, &[left, right], Relation::LessEq.into())?,
1529            ast::ExprKind::Eq { left, right } => self.append_simple_ins(entity, &[left, right], Relation::Equal.into())?,
1530            ast::ExprKind::Neq { left, right } => self.append_simple_ins(entity, &[left, right], Relation::NotEqual.into())?,
1531            ast::ExprKind::Identical { left, right } => self.append_simple_ins(entity, &[left, right], Instruction::Identical)?,
1532            ast::ExprKind::ListGet { list, index } => self.append_simple_ins(entity, &[index, list], Instruction::ListGet)?,
1533            ast::ExprKind::ListGetLast { list } => self.append_simple_ins(entity, &[list], Instruction::ListGetLast)?,
1534            ast::ExprKind::ListGetRandom { list } => self.append_simple_ins(entity, &[list], Instruction::ListGetRandom)?,
1535            ast::ExprKind::ListLen { value } => self.append_simple_ins(entity, &[value], Instruction::ListLength)?,
1536            ast::ExprKind::ListDims { value } => self.append_simple_ins(entity, &[value], Instruction::ListDims)?,
1537            ast::ExprKind::ListRank { value } => self.append_simple_ins(entity, &[value], Instruction::ListRank)?,
1538            ast::ExprKind::ListRev { value } => self.append_simple_ins(entity, &[value], Instruction::ListRev)?,
1539            ast::ExprKind::ListFlatten { value } => self.append_simple_ins(entity, &[value], Instruction::ListFlatten)?,
1540            ast::ExprKind::ListIsEmpty { value } => self.append_simple_ins(entity, &[value], Instruction::ListIsEmpty)?,
1541            ast::ExprKind::ListCons { item, list } => self.append_simple_ins(entity, &[item, list], Instruction::ListCons)?,
1542            ast::ExprKind::ListCdr { value } => self.append_simple_ins(entity, &[value], Instruction::ListCdr)?,
1543            ast::ExprKind::ListFind { list, value } => self.append_simple_ins(entity, &[value, list], Instruction::ListFind)?,
1544            ast::ExprKind::ListContains { list, value } => self.append_simple_ins(entity, &[list, value], Instruction::ListContains)?,
1545            ast::ExprKind::Range { start, stop } => self.append_simple_ins(entity, &[start, stop], BinaryOp::Range.into())?,
1546            ast::ExprKind::Random { a, b } => self.append_simple_ins(entity, &[a, b], BinaryOp::Random.into())?,
1547            ast::ExprKind::ListColumns { value } => self.append_simple_ins(entity, &[value], Instruction::ListColumns)?,
1548            ast::ExprKind::ListLines { value } => self.append_simple_ins(entity, &[value], Instruction::ListLines)?,
1549            ast::ExprKind::ListJson { value } => self.append_simple_ins(entity, &[value], Instruction::ListJson)?,
1550            ast::ExprKind::ListCsv { value } => self.append_simple_ins(entity, &[value], Instruction::ListCsv)?,
1551            ast::ExprKind::StrGet { string, index } => self.append_simple_ins(entity, &[index, string], BinaryOp::StrGet.into())?,
1552            ast::ExprKind::StrGetLast { string } => self.append_simple_ins(entity, &[string], UnaryOp::StrGetLast.into())?,
1553            ast::ExprKind::StrGetRandom { string } => self.append_simple_ins(entity, &[string], UnaryOp::StrGetRandom.into())?,
1554            ast::ExprKind::Effect { kind } => self.append_simple_ins(entity, &[], Instruction::PushProperty { prop: Property::from_effect(kind) })?,
1555            ast::ExprKind::Clone { target } => self.append_simple_ins(entity, &[target], Instruction::Clone)?,
1556            ast::ExprKind::This => self.ins.push(Instruction::PushSelf.into()),
1557            ast::ExprKind::Heading => self.ins.push(Instruction::PushProperty { prop: Property::Heading }.into()),
1558            ast::ExprKind::RpcError => self.ins.push(Instruction::PushRpcError.into()),
1559            ast::ExprKind::Answer => self.ins.push(Instruction::PushAnswer.into()),
1560            ast::ExprKind::Message => self.ins.push(Instruction::PushLocalMessage.into()),
1561            ast::ExprKind::Timer => self.ins.push(Instruction::PushTimer.into()),
1562            ast::ExprKind::RealTime { query } => self.ins.push(Instruction::PushRealTime { query: query.into() }.into()),
1563            ast::ExprKind::XPos => self.ins.push(Instruction::PushProperty { prop: Property::XPos }.into()),
1564            ast::ExprKind::YPos => self.ins.push(Instruction::PushProperty { prop: Property::YPos }.into()),
1565            ast::ExprKind::PenDown => self.ins.push(Instruction::PushProperty { prop: Property::PenDown }.into()),
1566            ast::ExprKind::PenAttr { attr } => self.ins.push(Instruction::PushProperty { prop: Property::from_pen_attr(attr) }.into()),
1567            ast::ExprKind::Costume => self.ins.push(Instruction::PushCostume.into()),
1568            ast::ExprKind::CostumeNumber => self.ins.push(Instruction::PushCostumeNumber.into()),
1569            ast::ExprKind::CostumeList => self.ins.push(Instruction::PushCostumeList.into()),
1570            ast::ExprKind::CostumeName { costume } => self.append_simple_ins(entity, &[costume], Instruction::PushCostumeProperty { prop: ImageProperty::Name })?,
1571            ast::ExprKind::SoundList => self.ins.push(Instruction::PushSoundList.into()),
1572            ast::ExprKind::SoundName { sound } => self.append_simple_ins(entity, &[sound], Instruction::PushSoundProperty { prop: AudioProperty::Name })?,
1573            ast::ExprKind::Size => self.ins.push(Instruction::PushProperty { prop: Property::Size }.into()),
1574            ast::ExprKind::IsVisible => self.ins.push(Instruction::PushProperty { prop: Property::Visible }.into()),
1575            ast::ExprKind::Entity { trans_name, .. } => self.ins.push(Instruction::PushEntity { name: trans_name }.into()),
1576            ast::ExprKind::Add { values } => self.append_variadic_op(entity, values, VariadicOp::Add)?,
1577            ast::ExprKind::Mul { values } => self.append_variadic_op(entity, values, VariadicOp::Mul)?,
1578            ast::ExprKind::Min { values } => self.append_variadic_op(entity, values, VariadicOp::Min)?,
1579            ast::ExprKind::Max { values } => self.append_variadic_op(entity, values, VariadicOp::Max)?,
1580            ast::ExprKind::StrCat { values } => self.append_variadic_op(entity, values, VariadicOp::StrCat)?,
1581            ast::ExprKind::ListCat { lists } => self.append_variadic_op(entity, lists, VariadicOp::ListCat)?,
1582            ast::ExprKind::CopyList { list } => self.append_variadic_op(entity, list, VariadicOp::MakeList)?,
1583            ast::ExprKind::MakeList { values } => {
1584                for value in values {
1585                    self.append_expr(value, entity)?;
1586                }
1587                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(values.len()) }.into());
1588            }
1589            ast::ExprKind::UnknownBlock { name, args } => match name.as_str() {
1590                "nativeCallSyscall" => {
1591                    let (name, args) = match args.as_slice() {
1592                        [name, args] => (name, args),
1593                        _ => return Err(CompileError::InvalidBlock { loc: expr.info.location.as_deref() }),
1594                    };
1595                    self.append_expr(name, entity)?;
1596                    let len = self.append_variadic(args, entity)?;
1597                    self.ins.push(Instruction::Syscall { len }.into());
1598                }
1599                "nativeSyscallError" => {
1600                    if !args.is_empty() { return Err(CompileError::InvalidBlock { loc: expr.info.location.as_deref() }) }
1601                    self.ins.push(Instruction::PushSyscallError.into());
1602                }
1603                _ => {
1604                    for arg in args {
1605                        self.append_expr(arg, entity)?;
1606                    }
1607                    self.ins.push(Instruction::UnknownBlock { name, args: args.len() }.into());
1608                }
1609            }
1610            ast::ExprKind::TypeQuery { value, ty } => {
1611                self.append_expr(value, entity)?;
1612                let ty = match ty {
1613                    ast::ValueType::Number => BasicType::Number,
1614                    ast::ValueType::Text => BasicType::Text,
1615                    ast::ValueType::Bool => BasicType::Bool,
1616                    ast::ValueType::List => BasicType::List,
1617                    ast::ValueType::Sprite => BasicType::Entity,
1618                    ast::ValueType::Costume => BasicType::Image,
1619                    ast::ValueType::Sound => BasicType::Audio,
1620                    ast::ValueType::Command | ast::ValueType::Reporter | ast::ValueType::Predicate => return Err(CompileError::CurrentlyUnsupported { info: "closure types are indistinguishable".into() }),
1621                };
1622                self.ins.push(Instruction::TypeQuery { ty }.into());
1623            }
1624            ast::ExprKind::ListReshape { value, dims } => {
1625                self.append_expr(value, entity)?;
1626                let len = self.append_variadic(dims, entity)?;
1627                self.ins.push(Instruction::ListReshape { len }.into());
1628            }
1629            ast::ExprKind::ListCombinations { sources } => {
1630                let len = self.append_variadic(sources, entity)?;
1631                self.ins.push(Instruction::ListCartesianProduct { len }.into());
1632            }
1633            ast::ExprKind::Conditional { condition, then, otherwise } => {
1634                self.append_expr(condition, entity)?;
1635                let test_pos = self.ins.len();
1636                self.ins.push(InternalInstruction::Illegal);
1637
1638                self.append_expr(then, entity)?;
1639                let jump_aft_pos = self.ins.len();
1640                self.ins.push(InternalInstruction::Illegal);
1641
1642                let test_false_pos = self.ins.len();
1643                self.append_expr(otherwise, entity)?;
1644                let aft_pos = self.ins.len();
1645
1646                self.ins[test_pos] = Instruction::ConditionalJump { to: test_false_pos, when: false }.into();
1647                self.ins[jump_aft_pos] = Instruction::Jump { to: aft_pos }.into();
1648            }
1649            ast::ExprKind::Or { left, right } => {
1650                self.append_expr(left, entity)?;
1651                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
1652                let check_pos = self.ins.len();
1653                self.ins.push(InternalInstruction::Illegal);
1654                self.ins.push(Instruction::PopValue.into());
1655                self.append_expr(right, entity)?;
1656                let aft = self.ins.len();
1657
1658                self.ins[check_pos] = Instruction::ConditionalJump { to: aft, when: true }.into();
1659
1660                self.ins.push(Instruction::ToBool.into());
1661            }
1662            ast::ExprKind::And { left, right } => {
1663                self.append_expr(left, entity)?;
1664                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
1665                let check_pos = self.ins.len();
1666                self.ins.push(InternalInstruction::Illegal);
1667                self.ins.push(Instruction::PopValue.into());
1668                self.append_expr(right, entity)?;
1669                let aft = self.ins.len();
1670
1671                self.ins[check_pos] = Instruction::ConditionalJump { to: aft, when: false }.into();
1672
1673                self.ins.push(Instruction::ToBool.into());
1674            }
1675            ast::ExprKind::CallFn { function, args, upvars } => {
1676                for upvar in upvars {
1677                    self.ins.push(Instruction::DeclareLocal { var: &upvar.name }.into());
1678                }
1679                for arg in args {
1680                    self.append_expr(arg, entity)?;
1681                }
1682                let call_hole_pos = self.ins.len();
1683                self.ins.push(InternalInstruction::Illegal);
1684
1685                self.call_holes.push((call_hole_pos, function, entity));
1686            }
1687            ast::ExprKind::CallClosure { new_entity, closure, args } => {
1688                if let Some(new_entity) = new_entity {
1689                    self.append_expr(new_entity, entity)?;
1690                }
1691                self.append_expr(closure, entity)?;
1692                for arg in args {
1693                    self.append_expr(arg, entity)?;
1694                }
1695                self.ins.push(Instruction::CallClosure { new_entity: new_entity.is_some(), args: args.len() }.into());
1696            }
1697            ast::ExprKind::CallRpc { host, service, rpc, args } => {
1698                let mut tokens = LosslessJoin::new();
1699                tokens.push(host.as_deref().unwrap_or(""));
1700                tokens.push(service);
1701                tokens.push(rpc);
1702                for (arg_name, arg) in args {
1703                    tokens.push(arg_name);
1704                    self.append_expr(arg, entity)?;
1705                }
1706                self.ins.push(Instruction::CallRpc { tokens: self.string_arena.alloc(tokens.finish()) }.into());
1707            }
1708            ast::ExprKind::NetworkMessageReply { target, msg_type, values } => {
1709                let mut tokens = LosslessJoin::new();
1710                tokens.push(msg_type);
1711                for (field, value) in values {
1712                    self.append_expr(value, entity)?;
1713                    tokens.push(field);
1714                }
1715                self.append_expr(target, entity)?;
1716                self.ins.push(Instruction::SendNetworkMessage { tokens: self.string_arena.alloc(tokens.finish()), expect_reply: true }.into());
1717            }
1718            ast::ExprKind::Closure { kind: _, params, captures, stmts } => {
1719                let closure_hole_pos = self.ins.len();
1720                self.ins.push(InternalInstruction::Illegal);
1721                self.closure_holes.push_back((closure_hole_pos, params, captures, stmts, entity));
1722            }
1723            ast::ExprKind::TextSplit { text, mode } => {
1724                self.append_expr(text, entity)?;
1725                let ins: Instruction = match mode {
1726                    ast::TextSplitMode::Letter => UnaryOp::SplitLetter.into(),
1727                    ast::TextSplitMode::Word => UnaryOp::SplitWord.into(),
1728                    ast::TextSplitMode::Tab => UnaryOp::SplitTab.into(),
1729                    ast::TextSplitMode::CR => UnaryOp::SplitCR.into(),
1730                    ast::TextSplitMode::LF => UnaryOp::SplitLF.into(),
1731                    ast::TextSplitMode::Csv => UnaryOp::SplitCsv.into(),
1732                    ast::TextSplitMode::Json => UnaryOp::SplitJson.into(),
1733                    ast::TextSplitMode::Custom(pattern) => {
1734                        self.append_expr(pattern, entity)?;
1735                        BinaryOp::SplitBy.into()
1736                    }
1737                };
1738                self.ins.push(ins.into());
1739            }
1740            ast::ExprKind::Map { f, list } => {
1741                self.append_expr(f, entity)?;
1742                self.append_expr(list, entity)?;
1743                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Dynamic }.into()); // shallow copy the input list
1744                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(0) }.into()); // push an empty list
1745
1746                let top = self.ins.len();
1747                self.ins.push(Instruction::DupeValue { top_index: 2 }.into());
1748                self.ins.push(Instruction::DupeValue { top_index: 2 }.into());
1749                let exit_jump_pos = self.ins.len();
1750                self.ins.push(InternalInstruction::Illegal);
1751                self.ins.push(Instruction::CallClosure { new_entity: false, args: 1 }.into());
1752                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1753                self.ins.push(Instruction::ListInsertLast.into());
1754                self.ins.push(Instruction::Yield.into());
1755                self.ins.push(Instruction::Jump { to: top }.into());
1756                let aft = self.ins.len();
1757
1758                self.ins[exit_jump_pos] = Instruction::ListPopFirstOrElse { goto: aft }.into();
1759
1760                self.ins.push(Instruction::SwapValues { top_index_1: 1, top_index_2: 3 }.into());
1761                self.ins.push(Instruction::PopValue.into());
1762                self.ins.push(Instruction::PopValue.into());
1763                self.ins.push(Instruction::PopValue.into());
1764            }
1765            ast::ExprKind::Keep { f, list } => {
1766                self.append_expr(f, entity)?;
1767                self.append_expr(list, entity)?;
1768                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Dynamic }.into()); // shallow copy the input list
1769                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(0) }.into()); // push an empty list
1770
1771                let top = self.ins.len();
1772                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1773                let exit_jump_pos = self.ins.len();
1774                self.ins.push(InternalInstruction::Illegal);
1775                self.ins.push(Instruction::DupeValue { top_index: 3 }.into());
1776                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1777                self.ins.push(Instruction::CallClosure { new_entity: false, args: 1 }.into());
1778                let skip_append_pos = self.ins.len();
1779                self.ins.push(InternalInstruction::Illegal);
1780                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1781                self.ins.push(Instruction::ListInsertLast.into());
1782                let kept_jump_pos = self.ins.len();
1783                self.ins.push(InternalInstruction::Illegal);
1784                let pop_cont = self.ins.len();
1785                self.ins.push(Instruction::PopValue.into());
1786                let cont = self.ins.len();
1787                self.ins.push(Instruction::Yield.into());
1788                self.ins.push(Instruction::Jump { to: top }.into());
1789                let aft = self.ins.len();
1790
1791                self.ins[exit_jump_pos] = Instruction::ListPopFirstOrElse { goto: aft }.into();
1792                self.ins[skip_append_pos] = Instruction::ConditionalJump { to: pop_cont, when: false }.into();
1793                self.ins[kept_jump_pos] = Instruction::Jump { to: cont }.into();
1794
1795                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }.into());
1796                self.ins.push(Instruction::PopValue.into());
1797                self.ins.push(Instruction::PopValue.into());
1798            }
1799            ast::ExprKind::FindFirst { f, list } => {
1800                self.append_expr(f, entity)?;
1801                self.append_expr(list, entity)?;
1802                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Dynamic }.into()); // shallow copy the input list
1803
1804                let top = self.ins.len();
1805                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
1806                let exit_jump_pos = self.ins.len();
1807                self.ins.push(InternalInstruction::Illegal);
1808                self.ins.push(Instruction::DupeValue { top_index: 2 }.into());
1809                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1810                self.ins.push(Instruction::CallClosure { new_entity: false, args: 1 }.into());
1811                let skip_jump_pos = self.ins.len();
1812                self.ins.push(InternalInstruction::Illegal);
1813                self.ins.push(Instruction::PopValue.into());
1814                self.ins.push(Instruction::Yield.into());
1815                self.ins.push(Instruction::Jump { to: top }.into());
1816
1817                let aft_loop = self.ins.len();
1818                self.ins.push(Instruction::PushString { value: "" }.into());
1819                let ret = self.ins.len();
1820                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }.into());
1821                self.ins.push(Instruction::PopValue.into());
1822                self.ins.push(Instruction::PopValue.into());
1823
1824                self.ins[exit_jump_pos] = Instruction::ListPopFirstOrElse { goto: aft_loop }.into();
1825                self.ins[skip_jump_pos] = Instruction::ConditionalJump { to: ret, when: true }.into();
1826            }
1827            ast::ExprKind::Combine { f, list } => {
1828                self.append_expr(list, entity)?;
1829                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Dynamic }.into()); // shallow copy the input list
1830                self.append_expr(f, entity)?;
1831
1832                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1833                let first_check_pos = self.ins.len();
1834                self.ins.push(InternalInstruction::Illegal);
1835
1836                let top = self.ins.len();
1837                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
1838                self.ins.push(Instruction::DupeValue { top_index: 3 }.into());
1839                let loop_done_pos = self.ins.len();
1840                self.ins.push(InternalInstruction::Illegal);
1841                self.ins.push(Instruction::SwapValues { top_index_1: 1, top_index_2: 2 }.into());
1842                self.ins.push(Instruction::CallClosure { new_entity: false, args: 2 }.into());
1843                self.ins.push(Instruction::Yield.into());
1844                self.ins.push(Instruction::Jump { to: top }.into());
1845
1846                let clean_inner = self.ins.len();
1847                self.ins.push(Instruction::PopValue.into());
1848                let clean_inner_ret = self.ins.len();
1849                self.ins.push(InternalInstruction::Illegal);
1850
1851                let empty_list = self.ins.len();
1852                self.ins.push(Instruction::PushInt { value: 0 }.into());
1853                let ret = self.ins.len();
1854                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }.into());
1855                self.ins.push(Instruction::PopValue.into());
1856                self.ins.push(Instruction::PopValue.into());
1857
1858                self.ins[first_check_pos] = Instruction::ListPopFirstOrElse { goto: empty_list }.into();
1859                self.ins[loop_done_pos] = Instruction::ListPopFirstOrElse { goto: clean_inner }.into();
1860                self.ins[clean_inner_ret] = Instruction::Jump { to: ret }.into();
1861            }
1862            kind => return Err(CompileError::UnsupportedExpr { kind }),
1863        }
1864
1865        if let Some(location) = expr.info.location.as_deref() {
1866            self.ins_locations.insert(self.ins.len(), location);
1867        }
1868
1869        Ok(())
1870    }
1871    fn append_stmt(&mut self, stmt: &'a ast::Stmt, entity: Option<&'a ast::Entity>) -> Result<(), CompileError<'a>> {
1872        match &stmt.kind {
1873            ast::StmtKind::Assign { var, value } => self.append_simple_ins(entity, &[value], Instruction::Assign { var: &var.trans_name })?,
1874            ast::StmtKind::AddAssign { var, value } => self.append_simple_ins(entity, &[value], Instruction::BinaryOpAssign { var: &var.trans_name, op: BinaryOp::Add })?,
1875            ast::StmtKind::ShowVar { var } => self.ins.push(Instruction::Watcher { create: true, var: &var.trans_name }.into()),
1876            ast::StmtKind::HideVar { var } => self.ins.push(Instruction::Watcher { create: false, var: &var.trans_name }.into()),
1877            ast::StmtKind::Pause => self.ins.push(Instruction::Pause.into()),
1878            ast::StmtKind::ListInsert { list, value, index } => self.append_simple_ins(entity, &[value, index, list], Instruction::ListInsert)?,
1879            ast::StmtKind::ListInsertLast { list, value } => self.append_simple_ins(entity, &[value, list], Instruction::ListInsertLast)?,
1880            ast::StmtKind::ListInsertRandom { list, value } => self.append_simple_ins(entity, &[value, list], Instruction::ListInsertRandom)?,
1881            ast::StmtKind::ListRemove { list, index } => self.append_simple_ins(entity, &[index, list], Instruction::ListRemove)?,
1882            ast::StmtKind::ListRemoveLast { list } => self.append_simple_ins(entity, &[list], Instruction::ListRemoveLast)?,
1883            ast::StmtKind::ListRemoveAll { list } => self.append_simple_ins(entity, &[list], Instruction::ListRemoveAll)?,
1884            ast::StmtKind::ListAssign { list, index, value } => self.append_simple_ins(entity, &[index, list, value], Instruction::ListAssign)?,
1885            ast::StmtKind::ListAssignLast { list, value } => self.append_simple_ins(entity, &[list, value], Instruction::ListAssignLast)?,
1886            ast::StmtKind::ListAssignRandom { list, value } => self.append_simple_ins(entity, &[list, value], Instruction::ListAssignRandom)?,
1887            ast::StmtKind::Return { value } => self.append_simple_ins(entity, &[value], Instruction::Return)?,
1888            ast::StmtKind::Throw { error } => self.append_simple_ins(entity, &[error], Instruction::Throw)?,
1889            ast::StmtKind::Ask { prompt } => self.append_simple_ins(entity, &[prompt], Instruction::Ask)?,
1890            ast::StmtKind::Sleep { seconds } => self.append_simple_ins(entity, &[seconds], Instruction::Sleep)?,
1891            ast::StmtKind::SendNetworkReply { value } => self.append_simple_ins(entity, &[value], Instruction::SendNetworkReply)?,
1892            ast::StmtKind::SetSize { value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::Size })?,
1893            ast::StmtKind::ChangeSize { delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::Size })?,
1894            ast::StmtKind::SetEffect { kind, value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::from_effect(kind) })?,
1895            ast::StmtKind::ChangeEffect { kind, delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::from_effect(kind) })?,
1896            ast::StmtKind::ClearEffects => self.ins.push(Instruction::ClearEffects.into()),
1897            ast::StmtKind::Forward { distance } => self.append_simple_ins(entity, &[distance], Instruction::Forward)?,
1898            ast::StmtKind::ResetTimer => self.ins.push(Instruction::ResetTimer.into()),
1899            ast::StmtKind::ChangePenAttr { attr, delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::from_pen_attr(attr) })?,
1900            ast::StmtKind::SetPenAttr { attr, value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::from_pen_attr(attr) })?,
1901            ast::StmtKind::GotoXY { x, y } => self.append_simple_ins(entity, &[x, y], Instruction::GotoXY)?,
1902            ast::StmtKind::ChangeX { delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::XPos })?,
1903            ast::StmtKind::ChangeY { delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::YPos })?,
1904            ast::StmtKind::SetX { value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::XPos })?,
1905            ast::StmtKind::SetY { value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::YPos })?,
1906            ast::StmtKind::TurnRight { angle } => self.append_simple_ins(entity, &[angle], Instruction::ChangeProperty { prop: Property::Heading })?,
1907            ast::StmtKind::SetHeading { value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::Heading })?,
1908            ast::StmtKind::PointTowards { target } => self.append_simple_ins(entity, &[target], Instruction::PointTowards)?,
1909            ast::StmtKind::PointTowardsXY { x, y } => self.append_simple_ins(entity, &[x, y], Instruction::PointTowardsXY)?,
1910            ast::StmtKind::Goto { target } => self.append_simple_ins(entity, &[target], Instruction::Goto)?,
1911            ast::StmtKind::SetPenSize { value } => self.append_simple_ins(entity, &[value], Instruction::SetProperty { prop: Property::PenSize })?,
1912            ast::StmtKind::ChangePenSize { delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::PenSize })?,
1913            ast::StmtKind::NextCostume => self.ins.push(Instruction::NextCostume.into()),
1914            ast::StmtKind::PenClear => self.ins.push(Instruction::ClearDrawings.into()),
1915            ast::StmtKind::DeleteClone => self.ins.push(Instruction::DeleteClone.into()),
1916            ast::StmtKind::Stop { mode: ast::StopMode::ThisScript } => self.ins.push(Instruction::Abort { mode: AbortMode::Current }.into()),
1917            ast::StmtKind::Stop { mode: ast::StopMode::All } => self.ins.push(Instruction::Abort { mode: AbortMode::All }.into()),
1918            ast::StmtKind::Stop { mode: ast::StopMode::AllButThisScript } => self.ins.push(Instruction::Abort { mode: AbortMode::Others }.into()),
1919            ast::StmtKind::Stop { mode: ast::StopMode::OtherScriptsInSprite } => self.ins.push(Instruction::Abort { mode: AbortMode::MyOthers }.into()),
1920            ast::StmtKind::SetCostume { costume } => self.append_simple_ins(entity, &[costume], Instruction::SetCostume)?,
1921            ast::StmtKind::PlaySound { sound, blocking } => self.append_simple_ins(entity, &[sound], Instruction::PlaySound { blocking: *blocking })?,
1922            ast::StmtKind::PlayNotes { notes, beats, blocking } => self.append_simple_ins(entity, &[notes, beats], Instruction::PlayNotes { blocking: *blocking })?,
1923            ast::StmtKind::Rest { beats } => {
1924                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(0) }.into());
1925                self.append_expr(beats, entity)?;
1926                self.ins.push(Instruction::PlayNotes { blocking: true }.into());
1927            }
1928            ast::StmtKind::StopSounds => self.ins.push(Instruction::StopSounds.into()),
1929            ast::StmtKind::Stop { mode: ast::StopMode::ThisBlock } => {
1930                self.ins.push(Instruction::PushString { value: "" }.into());
1931                self.ins.push(Instruction::Return.into());
1932            }
1933            ast::StmtKind::SetVisible { value } => {
1934                self.ins.push(Instruction::PushBool { value: *value }.into());
1935                self.ins.push(Instruction::SetProperty { prop: Property::Visible }.into());
1936            }
1937            ast::StmtKind::SetPenDown { value } => {
1938                self.ins.push(Instruction::PushBool { value: *value }.into());
1939                self.ins.push(Instruction::SetProperty { prop: Property::PenDown }.into());
1940            }
1941            ast::StmtKind::TurnLeft { angle } => {
1942                self.append_expr(angle, entity)?;
1943                self.ins.push(Instruction::from(UnaryOp::Neg).into());
1944                self.ins.push(Instruction::ChangeProperty { prop: Property::Heading }.into());
1945            }
1946            ast::StmtKind::SetPenColor { color } => {
1947                let (r, g, b, a) = *color;
1948                self.ins.push(Instruction::PushColor { value: Color { r, g, b, a } }.into());
1949                self.ins.push(Instruction::SetProperty { prop: Property::PenColor }.into());
1950            }
1951            x @ (ast::StmtKind::Say { content, duration } | ast::StmtKind::Think { content, duration }) => {
1952                let style = match x {
1953                    ast::StmtKind::Say { .. } => PrintStyle::Say,
1954                    ast::StmtKind::Think { .. } => PrintStyle::Think,
1955                    _ => unreachable!(),
1956                };
1957
1958                self.append_simple_ins(entity, &[content], Instruction::Print { style })?;
1959                if let Some(t) = duration {
1960                    self.append_simple_ins(entity, &[t], Instruction::Sleep)?;
1961                    self.ins.push(Instruction::PushString { value: "" }.into());
1962                    self.ins.push(Instruction::Print { style }.into());
1963                }
1964            }
1965            ast::StmtKind::DeclareLocals { vars } => {
1966                for var in vars {
1967                    self.ins.push(Instruction::DeclareLocal { var: &var.trans_name }.into());
1968                }
1969            }
1970            ast::StmtKind::CallFn { function, args, upvars } => {
1971                for upvar in upvars {
1972                    self.ins.push(Instruction::DeclareLocal { var: &upvar.name }.into());
1973                }
1974                for arg in args {
1975                    self.append_expr(arg, entity)?;
1976                }
1977                let call_hole_pos = self.ins.len();
1978                self.ins.push(InternalInstruction::Illegal);
1979                self.ins.push(Instruction::PopValue.into());
1980
1981                self.call_holes.push((call_hole_pos, function, entity));
1982            }
1983            ast::StmtKind::CallClosure { new_entity, closure, args } => {
1984                if let Some(new_entity) = new_entity {
1985                    self.append_expr(new_entity, entity)?;
1986                }
1987                self.append_expr(closure, entity)?;
1988                for arg in args {
1989                    self.append_expr(arg, entity)?;
1990                }
1991                self.ins.push(Instruction::CallClosure { new_entity: new_entity.is_some(), args: args.len() }.into());
1992                self.ins.push(Instruction::PopValue.into());
1993            }
1994            ast::StmtKind::ForkClosure { closure, args } => {
1995                self.append_expr(closure, entity)?;
1996                for arg in args {
1997                    self.append_expr(arg, entity)?;
1998                }
1999                self.ins.push(Instruction::ForkClosure { args: args.len() }.into());
2000            }
2001            ast::StmtKind::Warp { stmts } => {
2002                self.ins.push(Instruction::WarpStart.into());
2003                for stmt in stmts {
2004                    self.append_stmt(stmt, entity)?;
2005                }
2006                self.ins.push(Instruction::WarpStop.into());
2007            }
2008            ast::StmtKind::InfLoop { stmts } => {
2009                let top = self.ins.len();
2010                for stmt in stmts {
2011                    self.append_stmt(stmt, entity)?;
2012                }
2013                self.ins.push(Instruction::Yield.into());
2014                self.ins.push(Instruction::Jump { to: top }.into());
2015            }
2016            ast::StmtKind::WaitUntil { condition } => {
2017                let top = self.ins.len();
2018                self.append_expr(condition, entity)?;
2019                let jump_pos = self.ins.len();
2020                self.ins.push(InternalInstruction::Illegal);
2021                self.ins.push(Instruction::Yield.into());
2022                self.ins.push(Instruction::Jump { to: top }.into());
2023                let aft = self.ins.len();
2024
2025                self.ins[jump_pos] = Instruction::ConditionalJump { to: aft, when: true }.into();
2026            }
2027            ast::StmtKind::UntilLoop { condition, stmts } => {
2028                let top = self.ins.len();
2029                self.append_expr(condition, entity)?;
2030                let exit_jump_pos = self.ins.len();
2031                self.ins.push(InternalInstruction::Illegal);
2032
2033                for stmt in stmts {
2034                    self.append_stmt(stmt, entity)?;
2035                }
2036                self.ins.push(Instruction::Yield.into());
2037                self.ins.push(Instruction::Jump { to: top }.into());
2038                let aft = self.ins.len();
2039
2040                self.ins[exit_jump_pos] = Instruction::ConditionalJump { to: aft, when: true }.into();
2041            }
2042            ast::StmtKind::Repeat { times, stmts } => {
2043                self.append_expr(times, entity)?;
2044
2045                let top = self.ins.len();
2046                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
2047                self.ins.push(Instruction::PushInt { value: 0 }.into());
2048                self.ins.push(Instruction::from(Relation::Greater).into());
2049                let aft_jump_pos = self.ins.len();
2050                self.ins.push(InternalInstruction::Illegal);
2051
2052                for stmt in stmts {
2053                    self.append_stmt(stmt, entity)?;
2054                }
2055
2056                self.ins.push(Instruction::PushInt { value: 1 }.into());
2057                self.ins.push(Instruction::from(BinaryOp::Sub).into());
2058                self.ins.push(Instruction::Yield.into());
2059                self.ins.push(Instruction::Jump { to: top }.into());
2060                let aft = self.ins.len();
2061
2062                self.ins[aft_jump_pos] = Instruction::ConditionalJump { to: aft, when: false }.into();
2063
2064                self.ins.push(Instruction::PopValue.into());
2065            }
2066            ast::StmtKind::ForLoop { var, start, stop, stmts } => {
2067                self.ins.push(Instruction::DeclareLocal { var: &var.name }.into());
2068
2069                self.append_expr(start, entity)?;
2070                self.ins.push(Instruction::ToNumber.into());
2071                self.append_expr(stop, entity)?;
2072                self.ins.push(Instruction::ToNumber.into());
2073
2074                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
2075                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
2076                self.ins.push(Instruction::from(Relation::Greater).into());
2077                let delta_jump_pos = self.ins.len();
2078                self.ins.push(InternalInstruction::Illegal);
2079
2080                self.ins.push(Instruction::PushInt { value: 1 }.into());
2081                let positive_delta_end = self.ins.len();
2082                self.ins.push(InternalInstruction::Illegal);
2083                let negative_delta_pos = self.ins.len();
2084                self.ins.push(Instruction::PushInt { value: -1 }.into());
2085                let aft_delta = self.ins.len();
2086
2087                self.ins[delta_jump_pos] = Instruction::ConditionalJump { to: negative_delta_pos, when: true }.into();
2088                self.ins[positive_delta_end] = Instruction::Jump { to: aft_delta }.into();
2089
2090                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }.into());
2091                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 1 }.into());
2092                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
2093                self.ins.push(Instruction::from(BinaryOp::Sub).into());
2094                self.ins.push(Instruction::from(UnaryOp::Abs).into());
2095
2096                let top = self.ins.len();
2097                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
2098                self.ins.push(Instruction::PushInt { value: 0 }.into());
2099                self.ins.push(Instruction::from(Relation::Less).into());
2100                let exit_jump_pos = self.ins.len();
2101                self.ins.push(InternalInstruction::Illegal);
2102
2103                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
2104                self.ins.push(Instruction::Assign { var: &var.trans_name }.into());
2105                for stmt in stmts {
2106                    self.append_stmt(stmt, entity)?;
2107                }
2108
2109                self.ins.push(Instruction::PushInt { value: 1 }.into());
2110                self.ins.push(Instruction::from(BinaryOp::Sub).into());
2111                self.ins.push(Instruction::DupeValue { top_index: 1 }.into());
2112                self.ins.push(Instruction::DupeValue { top_index: 3 }.into());
2113                self.ins.push(Instruction::from(BinaryOp::Add).into());
2114                self.ins.push(Instruction::SwapValues { top_index_1: 0, top_index_2: 2 }.into());
2115                self.ins.push(Instruction::PopValue.into());
2116                self.ins.push(Instruction::Yield.into());
2117                self.ins.push(Instruction::Jump { to: top }.into());
2118                let aft = self.ins.len();
2119
2120                self.ins[exit_jump_pos] = Instruction::ConditionalJump { to: aft, when: true }.into();
2121
2122                for _ in 0..3 {
2123                    self.ins.push(Instruction::PopValue.into());
2124                }
2125            }
2126            ast::StmtKind::ForeachLoop { var, items, stmts } => {
2127                self.ins.push(Instruction::DeclareLocal { var: &var.name }.into());
2128
2129                self.append_expr(items, entity)?;
2130                self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Dynamic }.into()); // shallow copy the input list
2131
2132                let top = self.ins.len();
2133                self.ins.push(Instruction::DupeValue { top_index: 0 }.into());
2134                let exit_jump_pos = self.ins.len();
2135                self.ins.push(InternalInstruction::Illegal);
2136                self.ins.push(Instruction::Assign { var: &var.trans_name }.into());
2137                for stmt in stmts {
2138                    self.append_stmt(stmt, entity)?;
2139                }
2140                self.ins.push(Instruction::Yield.into());
2141                self.ins.push(Instruction::Jump { to: top }.into());
2142                let aft = self.ins.len();
2143
2144                self.ins[exit_jump_pos] = Instruction::ListPopFirstOrElse { goto: aft }.into();
2145
2146                self.ins.push(Instruction::PopValue.into());
2147            }
2148            ast::StmtKind::If { condition, then } => {
2149                self.append_expr(condition, entity)?;
2150                let patch_pos = self.ins.len();
2151                self.ins.push(InternalInstruction::Illegal);
2152                for stmt in then {
2153                    self.append_stmt(stmt, entity)?;
2154                }
2155                let else_pos = self.ins.len();
2156
2157                self.ins[patch_pos] = Instruction::ConditionalJump { to: else_pos, when: false }.into();
2158            }
2159            ast::StmtKind::IfElse { condition, then, otherwise } => {
2160                self.append_expr(condition, entity)?;
2161                let check_pos = self.ins.len();
2162                self.ins.push(InternalInstruction::Illegal);
2163                for stmt in then {
2164                    self.append_stmt(stmt, entity)?;
2165                }
2166                let jump_pos = self.ins.len();
2167                self.ins.push(InternalInstruction::Illegal);
2168                let else_pos = self.ins.len();
2169                for stmt in otherwise {
2170                    self.append_stmt(stmt, entity)?;
2171                }
2172                let aft = self.ins.len();
2173
2174                self.ins[check_pos] = Instruction::ConditionalJump { to: else_pos, when: false }.into();
2175                self.ins[jump_pos] = Instruction::Jump { to: aft }.into();
2176            }
2177            ast::StmtKind::TryCatch { code, var, handler } => {
2178                let push_pos = self.ins.len();
2179                self.ins.push(InternalInstruction::Illegal);
2180
2181                for stmt in code {
2182                    self.append_stmt(stmt, entity)?;
2183                }
2184                self.ins.push(Instruction::PopHandler.into());
2185                let success_end_pos = self.ins.len();
2186                self.ins.push(InternalInstruction::Illegal);
2187
2188                let error_handler_pos = self.ins.len();
2189                self.ins.push(Instruction::PopHandler.into());
2190                for stmt in handler {
2191                    self.append_stmt(stmt, entity)?;
2192                }
2193                let aft = self.ins.len();
2194
2195                self.ins[push_pos] = Instruction::PushHandler { pos: error_handler_pos, var: &var.trans_name }.into();
2196                self.ins[success_end_pos] = Instruction::Jump { to: aft }.into();
2197            }
2198            ast::StmtKind::SendLocalMessage { target, msg_type, wait } => match target {
2199                Some(target) => self.append_simple_ins(entity, &[msg_type, target], Instruction::SendLocalMessage { wait: *wait, target: true })?,
2200                None => self.append_simple_ins(entity, &[msg_type], Instruction::SendLocalMessage { wait: *wait, target: false })?,
2201            }
2202            ast::StmtKind::CallRpc { host, service, rpc, args } => {
2203                let mut tokens = LosslessJoin::new();
2204                tokens.push(host.as_deref().unwrap_or(""));
2205                tokens.push(service);
2206                tokens.push(rpc);
2207                for (arg_name, arg) in args {
2208                    tokens.push(arg_name);
2209                    self.append_expr(arg, entity)?;
2210                }
2211                self.ins.push(Instruction::CallRpc { tokens: self.string_arena.alloc(tokens.finish()) }.into());
2212                self.ins.push(Instruction::PopValue.into());
2213            }
2214            ast::StmtKind::SendNetworkMessage { target, msg_type, values } => {
2215                let mut tokens = LosslessJoin::new();
2216                tokens.push(msg_type);
2217                for (field, value) in values {
2218                    self.append_expr(value, entity)?;
2219                    tokens.push(field);
2220                }
2221                self.append_expr(target, entity)?;
2222                self.ins.push(Instruction::SendNetworkMessage { tokens: self.string_arena.alloc(tokens.finish()), expect_reply: false }.into());
2223            }
2224            ast::StmtKind::Clone { target } => {
2225                self.append_expr(target, entity)?;
2226                self.ins.push(Instruction::Clone.into());
2227                self.ins.push(Instruction::PopValue.into());
2228            }
2229            ast::StmtKind::UnknownBlock { name, args } => match name.as_str() {
2230                "nativeRunSyscall" => {
2231                    let (name, args) = match args.as_slice() {
2232                        [name, args] => (name, args),
2233                        _ => return Err(CompileError::InvalidBlock { loc: stmt.info.location.as_deref() }),
2234                    };
2235                    self.append_expr(name, entity)?;
2236                    let len = self.append_variadic(args, entity)?;
2237                    self.ins.push(Instruction::Syscall { len }.into());
2238                    self.ins.push(Instruction::PopValue.into());
2239                }
2240                _ => {
2241                    for arg in args {
2242                        self.append_expr(arg, entity)?;
2243                    }
2244                    self.ins.push(Instruction::UnknownBlock { name, args: args.len() }.into());
2245                    self.ins.push(Instruction::PopValue.into());
2246                }
2247            }
2248            kind => return Err(CompileError::UnsupportedStmt { kind }),
2249        }
2250
2251        if let Some(location) = stmt.info.location.as_deref() {
2252            self.ins_locations.insert(self.ins.len(), location);
2253        }
2254
2255        Ok(())
2256    }
2257    fn append_stmts_ret(&mut self, upvars: &'a [ast::VariableRef], stmts: &'a [ast::Stmt], entity: Option<&'a ast::Entity>) -> Result<(), CompileError<'a>> {
2258        for upvar in upvars {
2259            self.ins.push(Instruction::InitUpvar { var: &upvar.name }.into());
2260        }
2261        for stmt in stmts {
2262            self.append_stmt(stmt, entity)?;
2263        }
2264        self.ins.push(Instruction::PushString { value: "" }.into());
2265        self.ins.push(Instruction::Return.into());
2266        Ok(())
2267    }
2268    fn link(mut self, funcs: Vec<(&'a ast::Function, usize)>, entities: Vec<(&'a ast::Entity, EntityScriptInfo<'a>)>) -> Result<(ByteCode, ScriptInfo<'a>, Locations), CompileError<'a>> {
2269        assert!(self.closure_holes.is_empty());
2270
2271        let global_fn_to_info = {
2272            let mut res = BTreeMap::new();
2273            for (func, pos) in funcs.iter() {
2274                res.insert(&*func.trans_name, (*pos, *func));
2275            }
2276            res
2277        };
2278        let entity_fn_to_info = {
2279            let mut res = BTreeMap::new();
2280            for (entity, entity_locs) in entities.iter() {
2281                let mut inner = BTreeMap::new();
2282                for (func, pos) in entity_locs.funcs.iter() {
2283                    inner.insert(&*func.trans_name, (*pos, *func));
2284                }
2285                res.insert(*entity as *const ast::Entity, inner);
2286            }
2287            res
2288        };
2289
2290        let get_ptr = |x: Option<&ast::Entity>| x.map(|x| x as *const ast::Entity).unwrap_or(core::ptr::null());
2291        for (hole_pos, hole_fn, hole_ent) in self.call_holes.iter() {
2292            let sym = &*hole_fn.trans_name;
2293            let &(pos, fn_info) = entity_fn_to_info.get(&get_ptr(*hole_ent)).and_then(|tab| tab.get(sym)).or_else(|| global_fn_to_info.get(sym)).unwrap();
2294
2295            let mut tokens = LosslessJoin::new();
2296            for param in fn_info.params.iter() {
2297                tokens.push(&param.trans_name);
2298            }
2299
2300            self.ins[*hole_pos] = Instruction::Call { pos, tokens: self.string_arena.alloc(tokens.finish()) }.into();
2301        }
2302
2303        self.optimize();
2304        self.finalize(funcs, entities)
2305    }
2306    fn optimize(&mut self) {
2307        let mut code = vec![];
2308        let mut data = BinPool::new(); // not used
2309        let mut relocate_info = vec![]; // not used
2310
2311        for ins in &mut self.ins {
2312            match ins {
2313                InternalInstruction::Illegal => unreachable!(),
2314                InternalInstruction::Valid(ins) => match ins {
2315                    Instruction::PushString { value: str_value } => match str_value.parse::<i32>() {
2316                        Ok(value) => {
2317                            if value.to_compact_string() != *str_value { continue }
2318
2319                            code.clear();
2320                            BinaryWrite::append(&value, &mut code, &mut data, &mut relocate_info);
2321                            debug_assert!(code.len() > 0);
2322                            if code.len() >= 3 { continue }
2323
2324                            *ins = Instruction::PushIntString { value };
2325                        }
2326                        Err(_) => (),
2327                    }
2328                    _ => (),
2329                }
2330            }
2331        }
2332
2333        debug_assert!(data.is_empty());
2334        debug_assert!(relocate_info.is_empty());
2335    }
2336    fn finalize(self, funcs: Vec<(&'a ast::Function, usize)>, entities: Vec<(&'a ast::Entity, EntityScriptInfo<'a>)>) -> Result<(ByteCode, ScriptInfo<'a>, Locations), CompileError<'a>> {
2337        let mut code = Vec::with_capacity(self.ins.len() * 4);
2338        let mut data = BinPool::new();
2339        let mut relocate_info = Vec::with_capacity(64);
2340
2341        let mut final_ins_pos = Vec::with_capacity(self.ins.len());
2342        for ins in self.ins.iter() {
2343            final_ins_pos.push(code.len());
2344            match ins {
2345                InternalInstruction::Illegal => unreachable!(),
2346                InternalInstruction::Valid(val) => BinaryWrite::append(val, &mut code, &mut data, &mut relocate_info),
2347            }
2348        }
2349
2350        let data_backing = data.into_backing();
2351        let (data, data_backing_pos) = {
2352            let mut data = Vec::with_capacity(data_backing.0.iter().map(Vec::len).sum::<usize>());
2353            let mut data_backing_pos = Vec::with_capacity(data_backing.0.len());
2354            for backing in data_backing.0.iter() {
2355                data_backing_pos.push(data.len());
2356                data.extend_from_slice(backing);
2357            }
2358            (data, data_backing_pos)
2359        };
2360
2361        fn apply_shrinking_plan(plan: &[(usize, usize, usize)], final_relocates: &mut [usize], code: &mut Vec<u8>, final_ins_pos: &mut [usize]) -> usize {
2362            let old_pos_to_ins: BTreeMap<usize, usize> = final_ins_pos.iter().copied().enumerate().map(|(a, b)| (b, a)).collect();
2363            let orig_code_size = code.len();
2364
2365            let mut final_ins_pos_update_iter = final_ins_pos.iter_mut().fuse().peekable();
2366            let mut old_hole_pos_to_new_pos = BTreeMap::default();
2367            let (mut dest_pos, mut src_pos, mut total_shift) = (0, 0, 0);
2368            for (code_addr, prev_size, new_size) in plan.iter().copied() {
2369                debug_assert!(prev_size >= new_size);
2370                debug_assert!(code_addr >= src_pos);
2371
2372                while let Some(old) = final_ins_pos_update_iter.peek() {
2373                    if **old > code_addr { break }
2374                    *final_ins_pos_update_iter.next().unwrap() -= total_shift;
2375                }
2376
2377                code.copy_within(src_pos..code_addr + new_size, dest_pos);
2378                dest_pos += code_addr + new_size - src_pos;
2379                src_pos = code_addr + prev_size;
2380                old_hole_pos_to_new_pos.insert(code_addr, dest_pos - new_size);
2381                total_shift += prev_size - new_size;
2382            }
2383            for old in final_ins_pos_update_iter { *old -= total_shift; }
2384            code.copy_within(src_pos..src_pos + (orig_code_size - total_shift - dest_pos), dest_pos);
2385            code.truncate(orig_code_size - total_shift);
2386
2387            let mut buf = Vec::with_capacity(MAX_U64_ENCODED_BYTES);
2388            for code_addr in final_relocates.iter_mut() {
2389                *code_addr = old_hole_pos_to_new_pos[code_addr];
2390                let old_pos = <usize as BinaryRead>::read(code, &[], *code_addr);
2391                buf.clear();
2392                encode_u64(final_ins_pos[old_pos_to_ins[&old_pos.0]] as u64, &mut buf, Some(old_pos.1 - *code_addr));
2393                debug_assert_eq!(buf.len(), old_pos.1 - *code_addr);
2394                code[*code_addr..old_pos.1].copy_from_slice(&buf);
2395            }
2396
2397            total_shift
2398        }
2399
2400        let mut fmt_buf = Vec::with_capacity(MAX_U64_ENCODED_BYTES);
2401        let mut shrinking_plan = vec![];
2402        let mut final_relocates = vec![];
2403        for info in relocate_info {
2404            fmt_buf.clear();
2405            let (code_addr, prev_size) = match info {
2406                RelocateInfo::Code { code_addr } => {
2407                    final_relocates.push(code_addr);
2408                    let pos = <usize as BinaryRead>::read(&code, &data, code_addr);
2409                    encode_u64(final_ins_pos[pos.0] as u64, &mut fmt_buf, None);
2410                    (code_addr, pos.1 - code_addr)
2411                }
2412                RelocateInfo::Data { code_addr } => {
2413                    let pool_index = <usize as BinaryRead>::read(&code, &data, code_addr);
2414                    let slice = &data_backing.1[pool_index.0];
2415                    encode_u64((data_backing_pos[slice.src] + slice.start) as u64, &mut fmt_buf, None);
2416                    (code_addr, pool_index.1 - code_addr)
2417                }
2418            };
2419            debug_assert!(prev_size >= fmt_buf.len());
2420            shrinking_plan.push((code_addr, prev_size, fmt_buf.len()));
2421            code[code_addr..code_addr + fmt_buf.len()].copy_from_slice(&fmt_buf);
2422        }
2423
2424        apply_shrinking_plan(&shrinking_plan, &mut final_relocates, &mut code, &mut final_ins_pos);
2425
2426        for _ in 0..SHRINK_CYCLES {
2427            shrinking_plan.clear();
2428            for code_addr in final_relocates.iter().copied() {
2429                let val = <usize as BinaryRead>::read(&code, &data, code_addr);
2430                fmt_buf.clear();
2431                encode_u64(val.0 as u64, &mut fmt_buf, None);
2432                debug_assert!(fmt_buf.len() <= val.1 - code_addr);
2433                code[code_addr..code_addr + fmt_buf.len()].copy_from_slice(&fmt_buf);
2434                shrinking_plan.push((code_addr, val.1 - code_addr, fmt_buf.len()));
2435            }
2436            let delta = apply_shrinking_plan(&shrinking_plan, &mut final_relocates, &mut code, &mut final_ins_pos);
2437            if delta == 0 { break }
2438        }
2439
2440        let (mut funcs, mut entities) = (funcs, entities);
2441
2442        for func in funcs.iter_mut() { func.1 = final_ins_pos[func.1]; }
2443        for entity in entities.iter_mut() {
2444            for func in entity.1.funcs.iter_mut() { func.1 = final_ins_pos[func.1]; }
2445            for script in entity.1.scripts.iter_mut() { script.1 = final_ins_pos[script.1]; }
2446        }
2447        let locations = Locations::condense(&self.ins_locations.iter().map(|(p, v)| (final_ins_pos[*p], *v)).collect())?;
2448
2449        Ok((ByteCode { tag: Default::default(), code: code.into_boxed_slice(), data: data.into_boxed_slice() }, ScriptInfo { funcs, entities }, locations))
2450    }
2451}
2452
2453impl ByteCode {
2454    /// Compiles a single project role into an executable form.
2455    /// The core information is stored in [`ByteCode`] (instructions acting on a state) and [`InitInfo`] (the initial project state).
2456    /// 
2457    /// This also emits a [`Locations`] object mapping bytecode index to code location (e.g., the block `collabId` from project xml),
2458    /// which is needed to provide human-readable error locations at runtime,
2459    /// as well as a [`ScriptInfo`] object that contains a symbol table of functions and scripts
2460    /// (needed to execute a specific segment of code).
2461    pub fn compile(role: &ast::Role) -> Result<(ByteCode, InitInfo, Locations, ScriptInfo), CompileError> {
2462        let string_arena = Default::default();
2463        let mut code = ByteCodeBuilder {
2464            ins: Default::default(),
2465            call_holes: Default::default(),
2466            closure_holes: Default::default(),
2467            ins_locations: Default::default(),
2468            string_arena: &string_arena,
2469        };
2470
2471        let mut funcs = Vec::with_capacity(role.funcs.len());
2472        for func in role.funcs.iter() {
2473            funcs.push((func, code.ins.len()));
2474            code.append_stmts_ret(&func.upvars, &func.stmts, None)?;
2475        }
2476
2477        let mut entities = Vec::with_capacity(role.entities.len());
2478        for entity in role.entities.iter() {
2479            let mut funcs = Vec::with_capacity(entity.funcs.len());
2480            for func in entity.funcs.iter() {
2481                funcs.push((func, code.ins.len()));
2482                code.append_stmts_ret(&func.upvars, &func.stmts, Some(entity))?;
2483            }
2484
2485            let mut scripts = Vec::with_capacity(entity.scripts.len());
2486            for script in entity.scripts.iter() {
2487                scripts.push((script, code.ins.len()));
2488                code.append_stmts_ret(&[], &script.stmts, Some(entity))?;
2489            }
2490
2491            entities.push((entity, EntityScriptInfo { funcs, scripts }));
2492        }
2493
2494        while let Some((hole_pos, params, captures, stmts, entity)) = code.closure_holes.pop_front() {
2495            let pos = code.ins.len();
2496            code.append_stmts_ret(&[], stmts, entity)?;
2497
2498            let mut tokens = LosslessJoin::new();
2499            for param in params {
2500                tokens.push(&param.trans_name);
2501            }
2502            for param in captures {
2503                tokens.push(&param.trans_name);
2504            }
2505
2506            code.ins[hole_pos] = Instruction::MakeClosure { pos, params: params.len(), tokens: string_arena.alloc(tokens.finish()) }.into();
2507        }
2508
2509        let (bytecode, script_info, locations) = code.link(funcs, entities)?;
2510        let init_info = Self::extract_init_info(role, &script_info)?;
2511
2512        Ok((bytecode, init_info, locations, script_info))
2513    }
2514    fn extract_init_info<'a>(role: &'a ast::Role, script_info: &ScriptInfo<'a>) -> Result<InitInfo, CompileError<'a>> {
2515        let mut ref_values = vec![];
2516
2517        let mut refs = BTreeMap::new();
2518        let mut string_refs = BTreeMap::new();
2519        let mut image_refs = BTreeMap::new();
2520        let mut audio_refs = BTreeMap::new();
2521
2522        fn register_ref_values<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option<RefValue>, &'a ast::Value)>, refs: &mut BTreeMap<usize, usize>) {
2523            match value {
2524                ast::Value::Bool(_) | ast::Value::Number(_) | ast::Value::Constant(_) => (), // non-ref types
2525                ast::Value::Ref(_) | ast::Value::String(_) | ast::Value::Image(_) | ast::Value::Audio(_) => (), // lazily link
2526                ast::Value::List(values, ref_id) => {
2527                    if let Some(ref_id) = ref_id {
2528                        refs.entry(ref_id.0).or_insert_with(|| {
2529                            ref_values.push((None, value)); // we don't have the value yet (might contain undefined refs, so can't be handled yet)
2530                            ref_values.len() - 1
2531                        });
2532                    }
2533                    for value in values {
2534                        register_ref_values(value, ref_values, refs);
2535                    }
2536                }
2537            }
2538        }
2539
2540        for global in role.globals.iter() {
2541            register_ref_values(&global.init, &mut ref_values, &mut refs);
2542        }
2543        for entity in role.entities.iter() {
2544            for field in entity.fields.iter() {
2545                register_ref_values(&field.init, &mut ref_values, &mut refs);
2546            }
2547            for costume in entity.costumes.iter() {
2548                register_ref_values(&costume.init, &mut ref_values, &mut refs);
2549            }
2550        }
2551
2552        fn get_value<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option<RefValue>, &'a ast::Value)>, refs: &BTreeMap<usize, usize>, string_refs: &mut BTreeMap<&'a str, usize>, image_refs: &mut BTreeMap<*const (Vec<u8>, Option<(f64, f64)>, CompactString), usize>, audio_refs: &mut BTreeMap<*const (Vec<u8>, CompactString), usize>) -> Result<InitValue, CompileError<'a>> {
2553            Ok(match value {
2554                ast::Value::Bool(x) => InitValue::Bool(*x),
2555                ast::Value::Number(x) => InitValue::Number(Number::new(*x)?),
2556                ast::Value::Constant(x) => match x {
2557                    ast::Constant::E => InitValue::Number(Number::new(core::f64::consts::E)?),
2558                    ast::Constant::Pi => InitValue::Number(Number::new(core::f64::consts::PI)?),
2559                }
2560                ast::Value::Ref(x) => {
2561                    let idx = *refs.get(&x.0).ok_or(CompileError::UndefinedRef { value })?;
2562                    InitValue::Ref(idx)
2563                }
2564                ast::Value::String(x) => {
2565                    let idx = *string_refs.entry(x).or_insert_with(|| {
2566                        ref_values.push((Some(RefValue::Text(x.clone())), value));
2567                        ref_values.len() - 1
2568                    });
2569                    InitValue::Ref(idx)
2570                }
2571                ast::Value::Image(x) => {
2572                    let center = x.1.map(|(x, y)| Ok::<_,NumberError>((Number::new(x)?, Number::new(y)?))).transpose()?;
2573                    let idx = *image_refs.entry(Rc::as_ptr(x)).or_insert_with(|| {
2574                        ref_values.push((Some(RefValue::Image(x.0.clone(), center, x.2.clone())), value));
2575                        ref_values.len() - 1
2576                    });
2577                    InitValue::Ref(idx)
2578                }
2579                ast::Value::Audio(x) => {
2580                    let idx = *audio_refs.entry(Rc::as_ptr(x)).or_insert_with(|| {
2581                        ref_values.push((Some(RefValue::Audio(x.0.clone(), x.1.clone())), value));
2582                        ref_values.len() - 1
2583                    });
2584                    InitValue::Ref(idx)
2585                }
2586                ast::Value::List(values, ref_id) => {
2587                    let res = RefValue::List(values.iter().map(|x| get_value(x, ref_values, refs, string_refs, image_refs, audio_refs)).collect::<Result<_,_>>()?);
2588                    match ref_id {
2589                        Some(ref_id) => {
2590                            let idx = *refs.get(&ref_id.0).ok_or(CompileError::UndefinedRef { value })?;
2591                            let target = &mut ref_values[idx];
2592                            debug_assert!(target.0.is_none());
2593                            target.0 = Some(res);
2594                            InitValue::Ref(idx)
2595                        }
2596                        None => {
2597                            ref_values.push((Some(res), value));
2598                            InitValue::Ref(ref_values.len() - 1)
2599                        }
2600                    }
2601                }
2602            })
2603        }
2604
2605        // -------------------------------------------------------------------
2606
2607        let proj_name = role.name.clone();
2608        let mut globals = VecMap::new();
2609        let mut entities = vec![];
2610
2611        for global in role.globals.iter() {
2612            globals.insert(global.def.name.clone(), get_value(&global.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?);
2613        }
2614
2615        for (entity, entity_info) in script_info.entities.iter() {
2616            let name = entity.name.clone();
2617            let mut fields = VecMap::new();
2618            let mut costumes = VecMap::new();
2619            let mut sounds = VecMap::new();
2620            let mut scripts = vec![];
2621
2622            let visible = entity.visible;
2623            let active_costume = entity.active_costume;
2624            let color = entity.color;
2625            let size = Number::new(entity.scale * 100.0)?;
2626            let pos = (Number::new(entity.pos.0)?, Number::new(entity.pos.1)?);
2627            let heading = Number::new(util::modulus(entity.heading, 360.0))?;
2628
2629            for field in entity.fields.iter() {
2630                fields.insert(field.def.name.clone(), get_value(&field.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?);
2631            }
2632            for costume in entity.costumes.iter() {
2633                costumes.insert(costume.def.name.clone(), get_value(&costume.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?);
2634            }
2635            for sound in entity.sounds.iter() {
2636                sounds.insert(sound.def.name.clone(), get_value(&sound.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?);
2637            }
2638
2639            for (script, pos) in entity_info.scripts.iter().copied() {
2640                let hat = match script.hat.as_ref() {
2641                    Some(x) => x,
2642                    None => continue,
2643                };
2644                let event = match &hat.kind {
2645                    ast::HatKind::OnFlag => Event::OnFlag,
2646                    ast::HatKind::OnClone => Event::OnClone,
2647                    ast::HatKind::LocalMessage { msg_type } => Event::LocalMessage { msg_type: msg_type.clone() },
2648                    ast::HatKind::NetworkMessage { msg_type, fields } => Event::NetworkMessage { msg_type: msg_type.clone(), fields: fields.iter().map(|x| x.trans_name.clone()).collect() },
2649                    ast::HatKind::Unknown { name, fields } => Event::Custom { name: name.clone(), fields: fields.iter().map(|x| x.trans_name.clone()).collect() },
2650                    ast::HatKind::OnKey { key } => Event::OnKey {
2651                        key_filter: match key.as_str() {
2652                            "any key" => None,
2653                            "up arrow" => Some(KeyCode::Up),
2654                            "down arrow" => Some(KeyCode::Down),
2655                            "left arrow" => Some(KeyCode::Left),
2656                            "right arrow" => Some(KeyCode::Right),
2657                            "enter" => Some(KeyCode::Enter),
2658                            "space" => Some(KeyCode::Char(' ')),
2659                            _ => {
2660                                let mut chars = key.chars();
2661                                let res = chars.next().map(|x| KeyCode::Char(x.to_ascii_lowercase()));
2662                                if res.is_none() || chars.next().is_some() { return Err(CompileError::BadKeycode { key }); }
2663                                Some(res.unwrap())
2664                            }
2665                        }
2666                    },
2667                    kind => return Err(CompileError::UnsupportedEvent { kind }),
2668                };
2669                scripts.push((event, pos));
2670            }
2671
2672            entities.push(EntityInitInfo { name, fields, sounds, costumes, scripts, active_costume, pos, heading, size, visible, color });
2673        }
2674
2675        let ref_values = ref_values.into_iter().map(|x| x.0.ok_or(CompileError::UndefinedRef { value: x.1 })).collect::<Result<_,_>>()?;
2676
2677        Ok(InitInfo { tag: Default::default(), proj_name, ref_values, globals, entities })
2678    }
2679    /// Generates a hex dump of the stored code, including instructions and addresses.
2680    #[cfg(feature = "std")]
2681    pub fn dump_code(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
2682        let mut pos = 0;
2683        while pos < self.code.len() {
2684            let (ins, aft) = Instruction::read(&self.code, &self.data, pos);
2685            for (i, bytes) in self.code[pos..aft].chunks(BYTES_PER_LINE).enumerate() {
2686                if i == 0 {
2687                    write!(f, "{pos:08}   ")?;
2688                } else {
2689                    write!(f, "           ")?;
2690                }
2691
2692                for &b in bytes {
2693                    write!(f, " {b:02x}")?;
2694                }
2695                for _ in bytes.len()..BYTES_PER_LINE {
2696                    write!(f, "   ")?;
2697                }
2698
2699                if i == 0 {
2700                    write!(f, "    {ins:?}")?;
2701                }
2702                writeln!(f)?;
2703            }
2704            pos = aft;
2705        }
2706        Ok(())
2707    }
2708    /// Generate a hex dump of the stored program data, including string literals and meta values.
2709    #[cfg(feature = "std")]
2710    pub fn dump_data(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
2711        for (i, bytes) in self.data.chunks(BYTES_PER_LINE).enumerate() {
2712            write!(f, "{:08}   ", i * BYTES_PER_LINE)?;
2713            for &b in bytes {
2714                write!(f, " {b:02x}")?;
2715            }
2716            for _ in bytes.len()..BYTES_PER_LINE {
2717                write!(f, "   ")?;
2718            }
2719            write!(f, "    ")?;
2720            for &b in bytes {
2721                write!(f, "{}", if (0x21..=0x7e).contains(&b) { b as char } else { '.' })?;
2722            }
2723            writeln!(f)?;
2724        }
2725        Ok(())
2726    }
2727    /// Returns the total size of the [`ByteCode`] object (in bytes).
2728    pub fn total_size(&self) -> usize {
2729        self.code.len() + self.data.len()
2730    }
2731}