datex_core/compiler/
mod.rs

1use crate::compiler::error::{
2    CompilerError, DetailedCompilerErrors, SimpleOrDetailedCompilerError,
3    SpannedCompilerError,
4};
5use crate::global::dxb_block::DXBBlock;
6use crate::global::operators::assignment::AssignmentOperator;
7use crate::global::protocol_structures::block_header::BlockHeader;
8use crate::global::protocol_structures::encrypted_header::EncryptedHeader;
9use crate::global::protocol_structures::routing_header::RoutingHeader;
10use core::cell::RefCell;
11
12use crate::ast::expressions::{
13    BinaryOperation, ComparisonOperation, DatexExpression, DatexExpressionData,
14    DerefAssignment, RemoteExecution, Slot, Statements, UnaryOperation,
15    UnboundedStatement, VariableAccess, VariableAssignment,
16    VariableDeclaration, VariableKind,
17};
18use crate::compiler::context::{CompilationContext, VirtualSlot};
19use crate::compiler::error::{
20    DetailedCompilerErrorsWithMaybeRichAst,
21    SimpleCompilerErrorOrDetailedCompilerErrorWithRichAst,
22};
23use crate::compiler::metadata::CompileMetadata;
24use crate::compiler::scope::CompilationScope;
25use crate::compiler::type_compiler::compile_type_expression;
26use crate::global::instruction_codes::InstructionCode;
27use crate::global::slots::InternalSlot;
28use crate::libs::core::CoreLibPointerId;
29use crate::parser::parser_result::ValidDatexParseResult;
30
31use crate::ast::resolved_variable::VariableId;
32use crate::core_compiler::value_compiler::{
33    append_boolean, append_decimal, append_encoded_integer, append_endpoint,
34    append_float_as_i16, append_float_as_i32, append_instruction_code,
35    append_integer, append_text, append_typed_decimal, append_typed_integer,
36    append_value_container,
37};
38use crate::core_compiler::value_compiler::{append_get_ref, append_key_string};
39use crate::parser::{Parser, ParserOptions};
40use crate::references::reference::ReferenceMutability;
41use crate::runtime::execution::context::ExecutionMode;
42use crate::stdlib::rc::Rc;
43use crate::stdlib::vec::Vec;
44use crate::time::Instant;
45use crate::utils::buffers::append_u32;
46use crate::utils::buffers::{append_u8, append_u16};
47use crate::values::core_values::decimal::Decimal;
48use crate::values::pointer::PointerAddress;
49use crate::values::value_container::ValueContainer;
50use log::{debug, info};
51use precompiler::options::PrecompilerOptions;
52use precompiler::precompile_ast;
53use precompiler::precompiled_ast::{AstMetadata, RichAst, VariableMetadata};
54
55pub mod context;
56pub mod error;
57pub mod metadata;
58pub mod scope;
59pub mod type_compiler;
60
61pub mod precompiler;
62#[cfg(feature = "std")]
63pub mod workspace;
64
65#[derive(Clone, Default)]
66pub struct CompileOptions {
67    pub compile_scope: CompilationScope,
68    pub parser_options: ParserOptions,
69}
70
71impl CompileOptions {
72    pub fn new_with_scope(compile_scope: CompilationScope) -> Self {
73        CompileOptions {
74            compile_scope,
75            parser_options: ParserOptions::default(),
76        }
77    }
78}
79
80#[derive(Debug, Clone)]
81pub enum StaticValueOrDXB {
82    StaticValue(Option<ValueContainer>),
83    DXB(Vec<u8>),
84}
85
86impl From<Vec<u8>> for StaticValueOrDXB {
87    fn from(dxb: Vec<u8>) -> Self {
88        StaticValueOrDXB::DXB(dxb)
89    }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Copy)]
93pub enum VariableModel {
94    /// A variable that is declared once and never reassigned afterward
95    /// e.g. `const a = 42;`
96    Constant,
97    /// A variable that can be reassigned by updating the slot value
98    /// e.g. `var a = 42; a = 69;`
99    VariableSlot,
100    /// A variable that can be reassigned by updating a reference value. The slot always point to this reference.
101    /// When variables are transferred across realms, `VariableReference` is used for `var` variables instead of `VariableSlot`.
102    /// e.g. `var a = 42; x :: (a)
103    VariableReference,
104}
105
106impl From<VariableRepresentation> for VariableModel {
107    fn from(value: VariableRepresentation) -> Self {
108        match value {
109            VariableRepresentation::Constant(_) => VariableModel::Constant,
110            VariableRepresentation::VariableSlot(_) => {
111                VariableModel::VariableSlot
112            }
113            VariableRepresentation::VariableReference { .. } => {
114                VariableModel::VariableReference
115            }
116        }
117    }
118}
119
120impl VariableModel {
121    /// Determines the variable model based on the variable kind and metadata.
122    pub fn infer(
123        variable_kind: VariableKind,
124        variable_metadata: Option<VariableMetadata>,
125        execution_mode: ExecutionMode,
126    ) -> Self {
127        // const variables are always constant
128        if variable_kind == VariableKind::Const {
129            VariableModel::Constant
130        }
131        // for cross-realm variables, we always use VariableReference
132        // if we don't know the full source text yet (e.g. in a repl), we
133        // must fall back to VariableReference, because we cannot determine if
134        // the variable will be transferred across realms later
135        else if variable_metadata.is_none()
136            || variable_metadata.unwrap().is_cross_realm
137            || execution_mode.is_unbounded()
138        {
139            VariableModel::VariableReference
140        }
141        // otherwise, we use VariableSlot (default for `var` variables)
142        else {
143            VariableModel::VariableSlot
144        }
145    }
146
147    pub fn infer_from_ast_metadata_and_type(
148        ast_metadata: &AstMetadata,
149        variable_id: Option<VariableId>,
150        variable_kind: VariableKind,
151        execution_mode: ExecutionMode,
152    ) -> Self {
153        let variable_metadata =
154            variable_id.and_then(|id| ast_metadata.variable_metadata(id));
155        Self::infer(variable_kind, variable_metadata.cloned(), execution_mode)
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Copy)]
160pub enum VariableRepresentation {
161    Constant(VirtualSlot),
162    VariableSlot(VirtualSlot),
163    VariableReference {
164        /// The slot that contains the reference that is used as the variable
165        variable_slot: VirtualSlot,
166        /// The slot that contains the actual value container used in the script (Note: the value container may also be a reference)
167        container_slot: VirtualSlot,
168    },
169}
170
171/// Represents a variable in the DATEX script.
172#[derive(Debug, Clone)]
173pub struct Variable {
174    pub name: String,
175    pub kind: VariableKind,
176    pub representation: VariableRepresentation,
177}
178
179impl Variable {
180    pub fn new_const(name: String, slot: VirtualSlot) -> Self {
181        Variable {
182            name,
183            kind: VariableKind::Const,
184            representation: VariableRepresentation::Constant(slot),
185        }
186    }
187
188    pub fn new_variable_slot(
189        name: String,
190        kind: VariableKind,
191        slot: VirtualSlot,
192    ) -> Self {
193        Variable {
194            name,
195            kind,
196            representation: VariableRepresentation::VariableSlot(slot),
197        }
198    }
199
200    pub fn new_variable_reference(
201        name: String,
202        kind: VariableKind,
203        variable_slot: VirtualSlot,
204        container_slot: VirtualSlot,
205    ) -> Self {
206        Variable {
207            name,
208            kind,
209            representation: VariableRepresentation::VariableReference {
210                variable_slot,
211                container_slot,
212            },
213        }
214    }
215
216    pub fn slots(&self) -> Vec<VirtualSlot> {
217        match &self.representation {
218            VariableRepresentation::Constant(slot) => vec![*slot],
219            VariableRepresentation::VariableSlot(slot) => vec![*slot],
220            VariableRepresentation::VariableReference {
221                variable_slot,
222                container_slot,
223            } => {
224                vec![*variable_slot, *container_slot]
225            }
226        }
227    }
228}
229
230/// Compiles a DATEX script text into a single DXB block including routing and block headers.
231/// This function is used to create a block that can be sent over the network.
232pub fn compile_block(
233    datex_script: &str,
234) -> Result<Vec<u8>, SimpleOrDetailedCompilerError> {
235    let (body, _) = compile_script(datex_script, CompileOptions::default())?;
236
237    let routing_header = RoutingHeader::default();
238
239    let block_header = BlockHeader::default();
240    let encrypted_header = EncryptedHeader::default();
241
242    let block =
243        DXBBlock::new(routing_header, block_header, encrypted_header, body);
244
245    let bytes = block
246        .to_bytes()
247        .map_err(|e| CompilerError::SerializationError)?;
248    Ok(bytes)
249}
250
251/// Compiles a DATEX script text into a DXB body
252pub fn compile_script(
253    datex_script: &str,
254    options: CompileOptions,
255) -> Result<(Vec<u8>, CompilationScope), SpannedCompilerError> {
256    compile_template(datex_script, &[], options)
257}
258
259/// Directly extracts a static value from a DATEX script as a `ValueContainer`.
260/// This only works if the script does not contain any dynamic values or operations.
261/// All JSON-files can be compiled to static values, but not all DATEX scripts.
262pub fn extract_static_value_from_script(
263    datex_script: &str,
264) -> Result<Option<ValueContainer>, SpannedCompilerError> {
265    let valid_parse_result = Parser::parse_with_default_options(datex_script)?;
266    extract_static_value_from_ast(&valid_parse_result)
267        .map(Some)
268        .map_err(SpannedCompilerError::from)
269}
270
271/// Converts a DATEX script template text with inserted values into an AST with metadata
272/// If the script does not contain any dynamic values or operations, the static result value is
273/// directly returned instead of the AST.
274pub fn compile_script_or_return_static_value<'a>(
275    datex_script: &'a str,
276    mut options: CompileOptions,
277) -> Result<(StaticValueOrDXB, CompilationScope), SpannedCompilerError> {
278    let ast = parse_datex_script_to_rich_ast_simple_error(
279        datex_script,
280        &mut options,
281    )?;
282    let mut compilation_context = CompilationContext::new(
283        Vec::with_capacity(256),
284        vec![],
285        options.compile_scope.execution_mode,
286    );
287    // FIXME #480: no clone here
288    let scope = compile_ast(ast.clone(), &mut compilation_context, options)?;
289    if compilation_context.has_non_static_value {
290        Ok((StaticValueOrDXB::DXB(compilation_context.buffer), scope))
291    } else {
292        // try to extract static value from AST
293        extract_static_value_from_ast(&ast.ast)
294            .map(|value| (StaticValueOrDXB::StaticValue(Some(value)), scope))
295            .map_err(SpannedCompilerError::from)
296    }
297}
298
299/// Ensure that the root ast node is a statements node
300/// Returns if the initial ast was terminated
301fn ensure_statements(
302    ast: &mut DatexExpression,
303    unbounded_section: Option<UnboundedStatement>,
304) -> bool {
305    if let DatexExpressionData::Statements(Statements {
306        is_terminated,
307        unbounded,
308        ..
309    }) = &mut ast.data
310    {
311        *unbounded = unbounded_section;
312        *is_terminated
313    } else {
314        // wrap in statements
315        let original_ast = ast.clone();
316        ast.data = DatexExpressionData::Statements(Statements {
317            statements: vec![original_ast],
318            is_terminated: false,
319            unbounded: unbounded_section,
320        });
321        false
322    }
323}
324
325/// Parses and precompiles a DATEX script template text with inserted values into an AST with metadata
326/// Only returns the first occurring error
327pub fn parse_datex_script_to_rich_ast_simple_error(
328    datex_script: &str,
329    options: &mut CompileOptions,
330) -> Result<RichAst, SpannedCompilerError> {
331    // TODO #481: do this (somewhere else)
332    // // shortcut if datex_script is "?" - call compile_value_container directly
333    // if datex_script == "?" {
334    //     if inserted_values.len() != 1 {
335    //         return Err(CompilerError::InvalidPlaceholderCount);
336    //     }
337    //     let result =
338    //         compile_value_container(inserted_values[0]).map(StaticValueOrAst::from)?;
339    //     return Ok((result, options.compile_scope));
340    // }
341    let parse_start = Instant::now();
342    let mut valid_parse_result =
343        Parser::parse(datex_script, options.parser_options.clone())?;
344
345    // make sure to append a statements block for the first block in ExecutionMode::Unbounded
346    let is_terminated = if let ExecutionMode::Unbounded { has_next } =
347        options.compile_scope.execution_mode
348    {
349        ensure_statements(
350            &mut valid_parse_result,
351            Some(UnboundedStatement {
352                is_first: !options.compile_scope.was_used,
353                is_last: !has_next,
354            }),
355        )
356    } else {
357        matches!(
358            valid_parse_result.data,
359            DatexExpressionData::Statements(Statements {
360                is_terminated: true,
361                ..
362            })
363        )
364    };
365    debug!(" [parse took {} ms]", parse_start.elapsed().as_millis());
366    let precompile_start = Instant::now();
367    let res = precompile_to_rich_ast(
368        valid_parse_result,
369        &mut options.compile_scope,
370        PrecompilerOptions {
371            detailed_errors: false,
372        },
373    )
374    .map_err(|e| match e {
375        SimpleCompilerErrorOrDetailedCompilerErrorWithRichAst::Simple(e) => e,
376        _ => unreachable!(), // because detailed_errors: false
377    })
378    .inspect(|ast| {
379        // store information about termination (last semicolon) in metadata
380        ast.metadata.borrow_mut().is_terminated = is_terminated;
381    });
382    debug!(
383        " [precompile took {} ms]",
384        precompile_start.elapsed().as_millis()
385    );
386    res
387}
388
389/// Parses and precompiles a DATEX script template text with inserted values into an AST with metadata
390/// Returns all occurring errors and the AST if one or more errors occur.
391pub fn parse_datex_script_to_rich_ast_detailed_errors(
392    datex_script: &str,
393    options: &mut CompileOptions,
394) -> Result<RichAst, DetailedCompilerErrorsWithMaybeRichAst> {
395    let (ast, parser_errors) =
396        Parser::parse_collecting_with_default_options(datex_script)
397            .into_ast_and_errors();
398    precompile_to_rich_ast(
399        ast,
400        &mut options.compile_scope,
401        PrecompilerOptions {
402            detailed_errors: true,
403        },
404    )
405    .map_err(|e| match e {
406        SimpleCompilerErrorOrDetailedCompilerErrorWithRichAst::Detailed(
407            mut e,
408        ) => {
409            // append parser errors to detailed errors
410            e.errors.errors.extend(
411                parser_errors.into_iter().map(SpannedCompilerError::from),
412            );
413            e.into()
414        }
415        _ => unreachable!(), // because detailed_errors: true
416    })
417}
418
419/// Compiles a DATEX script template text with inserted values into a DXB body
420pub fn compile_template(
421    datex_script: &str,
422    inserted_values: &[ValueContainer],
423    mut options: CompileOptions,
424) -> Result<(Vec<u8>, CompilationScope), SpannedCompilerError> {
425    let ast = parse_datex_script_to_rich_ast_simple_error(
426        datex_script,
427        &mut options,
428    )?;
429    let mut compilation_context = CompilationContext::new(
430        Vec::with_capacity(256),
431        // TODO #482: no clone here
432        inserted_values.to_vec(),
433        options.compile_scope.execution_mode,
434    );
435    let compile_start = Instant::now();
436    let res = compile_ast(ast, &mut compilation_context, options)
437        .map(|scope| (compilation_context.buffer, scope))
438        .map_err(SpannedCompilerError::from);
439    debug!(
440        " [compile_ast took {} ms]",
441        compile_start.elapsed().as_millis()
442    );
443    res
444}
445
446/// Compiles a precompiled DATEX AST, returning the compilation context and scope
447fn compile_ast(
448    ast: RichAst,
449    compilation_context: &mut CompilationContext,
450    options: CompileOptions,
451) -> Result<CompilationScope, CompilerError> {
452    let compilation_scope =
453        compile_rich_ast(compilation_context, ast, options.compile_scope)?;
454    Ok(compilation_scope)
455}
456
457/// Tries to extract a static value from a DATEX expression AST.
458/// If the expression is not a static value (e.g., contains a placeholder or dynamic operation),
459/// it returns an error.
460fn extract_static_value_from_ast(
461    ast: &DatexExpression,
462) -> Result<ValueContainer, CompilerError> {
463    if let DatexExpressionData::Placeholder = ast.data {
464        return Err(CompilerError::NonStaticValue);
465    }
466    ValueContainer::try_from(&ast.data)
467        .map_err(|_| CompilerError::NonStaticValue)
468}
469
470/// Macro for compiling a DATEX script template text with inserted values into a DXB body,
471/// behaves like the format! macro.
472/// Example:
473/// ```
474/// use datex_core::compile;
475/// compile!("4 + ?", 42);
476/// compile!("? + ?", 1, 2);
477#[macro_export]
478macro_rules! compile {
479    ($fmt:literal $(, $arg:expr )* $(,)?) => {
480        {
481            let script: &str = $fmt.into();
482            let values: &[$crate::values::value_container::ValueContainer] = &[$($arg.into()),*];
483
484            $crate::compiler::compile_template(&script, values, $crate::compiler::CompileOptions::default())
485        }
486    }
487}
488
489/// Precompiles a DATEX expression AST into an AST with metadata.
490fn precompile_to_rich_ast(
491    valid_parse_result: DatexExpression,
492    scope: &mut CompilationScope,
493    precompiler_options: PrecompilerOptions,
494) -> Result<RichAst, SimpleCompilerErrorOrDetailedCompilerErrorWithRichAst> {
495    // if static execution mode and scope already used, return error
496    if scope.execution_mode == ExecutionMode::Static && scope.was_used {
497        return Err(
498            SimpleCompilerErrorOrDetailedCompilerErrorWithRichAst::Simple(
499                SpannedCompilerError::from(
500                    CompilerError::OnceScopeUsedMultipleTimes,
501                ),
502            ),
503        );
504    }
505
506    // set was_used to true
507    scope.was_used = true;
508
509    let rich_ast = if let Some(precompiler_data) = &scope.precompiler_data {
510        // precompile the AST, adding metadata for variables etc.
511        precompile_ast(
512            valid_parse_result,
513            &mut precompiler_data.precompiler_scope_stack.borrow_mut(),
514            precompiler_data.rich_ast.metadata.clone(),
515            precompiler_options,
516        )?
517    } else {
518        // if no precompiler data, just use the AST with default metadata
519        RichAst::new_without_metadata(valid_parse_result)
520    };
521
522    Ok(rich_ast)
523}
524
525pub fn compile_rich_ast(
526    compilation_context: &mut CompilationContext,
527    rich_ast: RichAst,
528    scope: CompilationScope,
529) -> Result<CompilationScope, CompilerError> {
530    let scope = compile_expression(
531        compilation_context,
532        rich_ast,
533        CompileMetadata::outer(),
534        scope,
535    )?;
536
537    // handle scope virtual addr mapping
538    compilation_context.remap_virtual_slots();
539    Ok(scope)
540}
541
542fn compile_expression(
543    compilation_context: &mut CompilationContext,
544    rich_ast: RichAst,
545    meta: CompileMetadata,
546    mut scope: CompilationScope,
547) -> Result<CompilationScope, CompilerError> {
548    let metadata = rich_ast.metadata;
549    // TODO #483: no clone
550    match rich_ast.ast.data.clone() {
551        DatexExpressionData::Integer(int) => {
552            append_integer(&mut compilation_context.buffer, &int);
553        }
554        DatexExpressionData::TypedInteger(typed_int) => {
555            append_encoded_integer(&mut compilation_context.buffer, &typed_int);
556        }
557        DatexExpressionData::Decimal(decimal) => match &decimal {
558            Decimal::Finite(big_decimal) if big_decimal.is_integer() => {
559                if let Some(int) = big_decimal.to_i16() {
560                    append_float_as_i16(&mut compilation_context.buffer, int);
561                } else if let Some(int) = big_decimal.to_i32() {
562                    append_float_as_i32(&mut compilation_context.buffer, int);
563                } else {
564                    append_decimal(&mut compilation_context.buffer, &decimal);
565                }
566            }
567            _ => {
568                append_decimal(&mut compilation_context.buffer, &decimal);
569            }
570        },
571        DatexExpressionData::TypedDecimal(typed_decimal) => {
572            append_typed_decimal(
573                &mut compilation_context.buffer,
574                &typed_decimal,
575            );
576        }
577        DatexExpressionData::Text(text) => {
578            append_text(&mut compilation_context.buffer, &text);
579        }
580        DatexExpressionData::Boolean(boolean) => {
581            append_boolean(&mut compilation_context.buffer, boolean);
582        }
583        DatexExpressionData::Endpoint(endpoint) => {
584            append_endpoint(&mut compilation_context.buffer, &endpoint);
585        }
586        DatexExpressionData::Null => {
587            append_instruction_code(
588                &mut compilation_context.buffer,
589                InstructionCode::NULL,
590            );
591        }
592        DatexExpressionData::List(list) => {
593            match list.items.len() {
594                0..=255 => {
595                    compilation_context
596                        .append_instruction_code(InstructionCode::SHORT_LIST);
597                    append_u8(
598                        &mut compilation_context.buffer,
599                        list.items.len() as u8,
600                    );
601                }
602                _ => {
603                    compilation_context
604                        .append_instruction_code(InstructionCode::LIST);
605                    append_u32(
606                        &mut compilation_context.buffer,
607                        list.items.len() as u32, // FIXME #671: conversion from usize to u32
608                    );
609                }
610            }
611            for item in list.items {
612                scope = compile_expression(
613                    compilation_context,
614                    RichAst::new(item, &metadata),
615                    CompileMetadata::default(),
616                    scope,
617                )?;
618            }
619        }
620        DatexExpressionData::Map(map) => {
621            // TODO #434: Handle string keyed maps (structs)
622            match map.entries.len() {
623                0..=255 => {
624                    compilation_context
625                        .append_instruction_code(InstructionCode::SHORT_MAP);
626                    append_u8(
627                        &mut compilation_context.buffer,
628                        map.entries.len() as u8,
629                    );
630                }
631                _ => {
632                    compilation_context
633                        .append_instruction_code(InstructionCode::MAP);
634                    append_u32(
635                        &mut compilation_context.buffer,
636                        map.entries.len() as u32, // FIXME #672: conversion from usize to u32
637                    );
638                }
639            }
640            for (key, value) in map.entries {
641                scope = compile_key_value_entry(
642                    compilation_context,
643                    key,
644                    value,
645                    &metadata,
646                    scope,
647                )?;
648            }
649        }
650        DatexExpressionData::Placeholder => {
651            append_value_container(
652                &mut compilation_context.buffer,
653                compilation_context
654                    .inserted_values
655                    .get(compilation_context.inserted_value_index)
656                    .unwrap(),
657            );
658            compilation_context.inserted_value_index += 1;
659        }
660
661        // statements
662        DatexExpressionData::Statements(Statements {
663            mut statements,
664            is_terminated,
665            unbounded,
666        }) => {
667            compilation_context.mark_has_non_static_value();
668            // if single statement and not terminated, just compile the expression
669            // (not for unbounded execution mode)
670            if unbounded.is_none() && statements.len() == 1 && !is_terminated {
671                scope = compile_expression(
672                    compilation_context,
673                    RichAst::new(statements.remove(0), &metadata),
674                    CompileMetadata::default(),
675                    scope,
676                )?;
677            } else {
678                let is_outer_context = meta.is_outer_context();
679
680                // if not outer context, new scope
681                let mut child_scope = if is_outer_context {
682                    scope
683                } else {
684                    scope.push()
685                };
686
687                if let Some(UnboundedStatement { is_first, .. }) = unbounded {
688                    // if this is the first section of an unbounded statements block, mark as unbounded
689                    if is_first {
690                        compilation_context.append_instruction_code(
691                            InstructionCode::UNBOUNDED_STATEMENTS,
692                        );
693                    }
694                    // if not first, don't insert any instruction code
695                }
696                // otherwise, statements with fixed length
697                else {
698                    let len = statements.len();
699
700                    match len {
701                        0..=255 => {
702                            compilation_context.append_instruction_code(
703                                InstructionCode::SHORT_STATEMENTS,
704                            );
705                            append_u8(
706                                &mut compilation_context.buffer,
707                                len as u8,
708                            );
709                        }
710                        _ => {
711                            compilation_context.append_instruction_code(
712                                InstructionCode::STATEMENTS,
713                            );
714                            append_u32(
715                                &mut compilation_context.buffer,
716                                len as u32, // FIXME #673: conversion from usize to u32
717                            );
718                        }
719                    }
720
721                    // append termination flag
722                    append_u8(
723                        &mut compilation_context.buffer,
724                        if is_terminated { 1 } else { 0 },
725                    );
726                }
727
728                for (i, statement) in statements.into_iter().enumerate() {
729                    child_scope = compile_expression(
730                        compilation_context,
731                        RichAst::new(statement, &metadata),
732                        CompileMetadata::default(),
733                        child_scope,
734                    )?;
735                }
736                if !meta.is_outer_context() {
737                    let scope_data = child_scope
738                        .pop()
739                        .ok_or(CompilerError::ScopePopError)?;
740                    scope = scope_data.0; // set parent scope
741                    // drop all slot addresses that were allocated in this scope
742                    for slot_address in scope_data.1 {
743                        compilation_context.append_instruction_code(
744                            InstructionCode::DROP_SLOT,
745                        );
746                        // insert virtual slot address for dropping
747                        compilation_context
748                            .insert_virtual_slot_address(slot_address);
749                    }
750                } else {
751                    scope = child_scope;
752                }
753
754                // if this is the last section of an unbounded statements block, add closing instruction
755                if let Some(UnboundedStatement { is_last: true, .. }) =
756                    unbounded
757                {
758                    compilation_context.append_instruction_code(
759                        InstructionCode::UNBOUNDED_STATEMENTS_END,
760                    );
761                    // append termination flag
762                    append_u8(
763                        &mut compilation_context.buffer,
764                        if is_terminated { 1 } else { 0 },
765                    );
766                }
767            }
768        }
769
770        // unary operations (negation, not, etc.)
771        DatexExpressionData::UnaryOperation(UnaryOperation {
772            operator,
773            expression,
774        }) => {
775            compilation_context
776                .append_instruction_code(InstructionCode::from(&operator));
777            scope = compile_expression(
778                compilation_context,
779                RichAst::new(*expression, &metadata),
780                CompileMetadata::default(),
781                scope,
782            )?;
783        }
784
785        // operations (add, subtract, multiply, divide, etc.)
786        DatexExpressionData::BinaryOperation(BinaryOperation {
787            operator,
788            left,
789            right,
790            ..
791        }) => {
792            compilation_context.mark_has_non_static_value();
793            // append binary code for operation if not already current binary operator
794            compilation_context
795                .append_instruction_code(InstructionCode::from(&operator));
796            scope = compile_expression(
797                compilation_context,
798                RichAst::new(*left, &metadata),
799                CompileMetadata::default(),
800                scope,
801            )?;
802            scope = compile_expression(
803                compilation_context,
804                RichAst::new(*right, &metadata),
805                CompileMetadata::default(),
806                scope,
807            )?;
808        }
809
810        // comparisons (e.g., equal, not equal, greater than, etc.)
811        DatexExpressionData::ComparisonOperation(ComparisonOperation {
812            operator,
813            left,
814            right,
815        }) => {
816            compilation_context.mark_has_non_static_value();
817            // append binary code for operation if not already current binary operator
818            compilation_context
819                .append_instruction_code(InstructionCode::from(&operator));
820            scope = compile_expression(
821                compilation_context,
822                RichAst::new(*left, &metadata),
823                CompileMetadata::default(),
824                scope,
825            )?;
826            scope = compile_expression(
827                compilation_context,
828                RichAst::new(*right, &metadata),
829                CompileMetadata::default(),
830                scope,
831            )?;
832        }
833
834        // apply
835        DatexExpressionData::Apply(apply) => {
836            compilation_context.mark_has_non_static_value();
837
838            // append apply instruction code
839            let len = apply.arguments.len();
840            match len {
841                0 => {
842                    compilation_context
843                        .append_instruction_code(InstructionCode::APPLY_ZERO);
844                }
845                1 => {
846                    compilation_context
847                        .append_instruction_code(InstructionCode::APPLY_SINGLE);
848                }
849                // u16 argument count
850                2..=65_535 => {
851                    compilation_context
852                        .append_instruction_code(InstructionCode::APPLY);
853                    // add argument count
854                    append_u16(
855                        &mut compilation_context.buffer,
856                        apply.arguments.len() as u16,
857                    );
858                }
859                _ => return Err(CompilerError::TooManyApplyArguments),
860            }
861
862            // compile arguments
863            for argument in apply.arguments.iter() {
864                scope = compile_expression(
865                    compilation_context,
866                    RichAst::new(argument.clone(), &metadata),
867                    CompileMetadata::default(),
868                    scope,
869                )?;
870            }
871
872            // compile function expression
873            scope = compile_expression(
874                compilation_context,
875                RichAst::new(*apply.base, &metadata),
876                CompileMetadata::default(),
877                scope,
878            )?;
879        }
880
881        DatexExpressionData::PropertyAccess(property_access) => {
882            compilation_context.mark_has_non_static_value();
883
884            // depending on the key, handle different property accesses
885            match &property_access.property.data {
886                // simple text key if length fits in u8
887                DatexExpressionData::Text(key) if key.len() <= 255 => {
888                    compile_text_property_access(compilation_context, key)
889                }
890                // index access if integer fits in u32
891                DatexExpressionData::Integer(index)
892                    if let Some(index) = index.as_u32() =>
893                {
894                    compile_index_property_access(compilation_context, index)
895                }
896                _ => {
897                    scope = compile_dynamic_property_access(
898                        compilation_context,
899                        &property_access.property,
900                        scope,
901                    )?;
902                }
903            }
904
905            // compile base expression
906            scope = compile_expression(
907                compilation_context,
908                RichAst::new(*property_access.base, &metadata),
909                CompileMetadata::default(),
910                scope,
911            )?;
912        }
913
914        DatexExpressionData::GenericInstantiation(generic_instantiation) => {
915            // NOTE: might already be handled in type compilation
916            todo!("#674 Undescribed by author.")
917        }
918
919        DatexExpressionData::PropertyAssignment(property_assignment) => {
920            compilation_context.mark_has_non_static_value();
921
922            // depending on the key, handle different property assignments
923            match &property_assignment.property.data {
924                // simple text key if length fits in u8
925                DatexExpressionData::Text(key) if key.len() <= 255 => {
926                    compile_text_property_assignment(compilation_context, key)
927                }
928                // index access if integer fits in u32
929                DatexExpressionData::Integer(index)
930                    if let Some(index) = index.as_u32() =>
931                {
932                    compile_index_property_assignment(
933                        compilation_context,
934                        index,
935                    )
936                }
937                _ => {
938                    scope = compile_dynamic_property_assignment(
939                        compilation_context,
940                        &property_assignment.property,
941                        scope,
942                    )?;
943                }
944            }
945
946            // compile assigned expression
947            scope = compile_expression(
948                compilation_context,
949                RichAst::new(
950                    *property_assignment.assigned_expression,
951                    &metadata,
952                ),
953                CompileMetadata::default(),
954                scope,
955            )?;
956
957            // compile base expression
958            scope = compile_expression(
959                compilation_context,
960                RichAst::new(*property_assignment.base, &metadata),
961                CompileMetadata::default(),
962                scope,
963            )?;
964        }
965
966        // variables
967        // declaration
968        DatexExpressionData::VariableDeclaration(VariableDeclaration {
969            id,
970            name,
971            kind,
972            type_annotation,
973            init_expression: value,
974        }) => {
975            compilation_context.mark_has_non_static_value();
976
977            // allocate new slot for variable
978            let virtual_slot_addr = scope.get_next_virtual_slot();
979            compilation_context
980                .append_instruction_code(InstructionCode::ALLOCATE_SLOT);
981            compilation_context.insert_virtual_slot_address(
982                VirtualSlot::local(virtual_slot_addr),
983            );
984            // compile expression
985            scope = compile_expression(
986                compilation_context,
987                RichAst::new(*value, &metadata),
988                CompileMetadata::default(),
989                scope,
990            )?;
991
992            let variable_model =
993                VariableModel::infer_from_ast_metadata_and_type(
994                    &metadata.borrow(),
995                    id,
996                    kind,
997                    compilation_context.execution_mode,
998                );
999
1000            // create new variable depending on the model
1001            let variable = match variable_model {
1002                VariableModel::VariableReference => {
1003                    // allocate an additional slot with a reference to the variable
1004                    let virtual_slot_addr_for_var =
1005                        scope.get_next_virtual_slot();
1006                    compilation_context.append_instruction_code(
1007                        InstructionCode::ALLOCATE_SLOT,
1008                    );
1009                    compilation_context.insert_virtual_slot_address(
1010                        VirtualSlot::local(virtual_slot_addr_for_var),
1011                    );
1012                    // indirect reference to the variable
1013                    compilation_context
1014                        .append_instruction_code(InstructionCode::CREATE_REF);
1015                    // append binary code to load variable
1016                    compilation_context
1017                        .append_instruction_code(InstructionCode::GET_SLOT);
1018                    compilation_context.insert_virtual_slot_address(
1019                        VirtualSlot::local(virtual_slot_addr),
1020                    );
1021
1022                    Variable::new_variable_reference(
1023                        name.clone(),
1024                        kind,
1025                        VirtualSlot::local(virtual_slot_addr_for_var),
1026                        VirtualSlot::local(virtual_slot_addr),
1027                    )
1028                }
1029                VariableModel::Constant => Variable::new_const(
1030                    name.clone(),
1031                    VirtualSlot::local(virtual_slot_addr),
1032                ),
1033                VariableModel::VariableSlot => Variable::new_variable_slot(
1034                    name.clone(),
1035                    kind,
1036                    VirtualSlot::local(virtual_slot_addr),
1037                ),
1038            };
1039
1040            scope.register_variable_slot(variable);
1041        }
1042
1043        DatexExpressionData::GetReference(address) => {
1044            compilation_context.mark_has_non_static_value();
1045            append_get_ref(&mut compilation_context.buffer, &address)
1046        }
1047
1048        // assignment
1049        DatexExpressionData::VariableAssignment(VariableAssignment {
1050            operator,
1051            name,
1052            expression,
1053            ..
1054        }) => {
1055            compilation_context.mark_has_non_static_value();
1056            // get variable slot address
1057            let (virtual_slot, kind) = scope
1058                .resolve_variable_name_to_virtual_slot(&name)
1059                .ok_or_else(|| {
1060                    CompilerError::UndeclaredVariable(name.clone())
1061                })?;
1062
1063            // TODO #484: check not needed, is already handled in precompiler - can we guarantee this?
1064            // if const, return error
1065            if kind == VariableKind::Const {
1066                return Err(CompilerError::AssignmentToConst(name.clone()));
1067            }
1068
1069            match operator {
1070                AssignmentOperator::Assign => {
1071                    // append binary code to load variable
1072                    info!(
1073                        "append variable virtual slot: {virtual_slot:?}, name: {name}"
1074                    );
1075                    compilation_context
1076                        .append_instruction_code(InstructionCode::SET_SLOT);
1077                    // compilation_context.append_instruction_code(
1078                    //     InstructionCode::from(&operator),
1079                    // );
1080                }
1081                AssignmentOperator::AddAssign
1082                | AssignmentOperator::SubtractAssign => {
1083                    // TODO #435: handle mut type
1084                    // // if immutable reference, return error
1085                    // if mut_type == Some(ReferenceMutability::Immutable) {
1086                    //     return Err(
1087                    //         CompilerError::AssignmentToImmutableReference(
1088                    //             name.clone(),
1089                    //         ),
1090                    //     );
1091                    // }
1092                    // // if immutable value, return error
1093                    // else if mut_type == None {
1094                    //     return Err(CompilerError::AssignmentToImmutableValue(
1095                    //         name.clone(),
1096                    //     ));
1097                    // }
1098                    compilation_context
1099                        .append_instruction_code(InstructionCode::SET_SLOT);
1100                    compilation_context.append_instruction_code(
1101                        InstructionCode::from(&operator),
1102                    );
1103                }
1104                op => core::todo!("#436 Handle assignment operator: {op:?}"),
1105            }
1106
1107            compilation_context.insert_virtual_slot_address(virtual_slot);
1108            // compile expression
1109            scope = compile_expression(
1110                compilation_context,
1111                RichAst::new(*expression, &metadata),
1112                CompileMetadata::default(),
1113                scope,
1114            )?;
1115        }
1116
1117        DatexExpressionData::DerefAssignment(DerefAssignment {
1118            operator,
1119            deref_expression,
1120            assigned_expression,
1121        }) => {
1122            compilation_context.mark_has_non_static_value();
1123
1124            compilation_context
1125                .append_instruction_code(InstructionCode::SET_REFERENCE_VALUE);
1126
1127            compilation_context
1128                .append_instruction_code(InstructionCode::from(&operator));
1129
1130            // compile deref expression
1131            scope = compile_expression(
1132                compilation_context,
1133                RichAst::new(*deref_expression, &metadata),
1134                CompileMetadata::default(),
1135                scope,
1136            )?;
1137
1138            // compile assigned expression
1139            scope = compile_expression(
1140                compilation_context,
1141                RichAst::new(*assigned_expression, &metadata),
1142                CompileMetadata::default(),
1143                scope,
1144            )?;
1145        }
1146
1147        // variable access
1148        DatexExpressionData::VariableAccess(VariableAccess {
1149            name, ..
1150        }) => {
1151            compilation_context.mark_has_non_static_value();
1152            // get variable slot address
1153            let (virtual_slot, ..) = scope
1154                .resolve_variable_name_to_virtual_slot(&name)
1155                .ok_or_else(|| {
1156                    CompilerError::UndeclaredVariable(name.clone())
1157                })?;
1158            // append binary code to load variable
1159            compilation_context
1160                .append_instruction_code(InstructionCode::GET_SLOT);
1161            compilation_context.insert_virtual_slot_address(virtual_slot);
1162        }
1163
1164        // remote execution
1165        DatexExpressionData::RemoteExecution(RemoteExecution {
1166            left: caller,
1167            right: script,
1168        }) => {
1169            compilation_context.mark_has_non_static_value();
1170
1171            // insert remote execution code
1172            compilation_context
1173                .append_instruction_code(InstructionCode::REMOTE_EXECUTION);
1174
1175            // compile remote execution block
1176            let mut execution_block_ctx = CompilationContext::new(
1177                Vec::with_capacity(256),
1178                vec![],
1179                ExecutionMode::Static,
1180            );
1181            let external_scope = compile_rich_ast(
1182                &mut execution_block_ctx,
1183                RichAst::new(*script, &metadata),
1184                CompilationScope::new_with_external_parent_scope(scope),
1185            )?;
1186            // reset to current scope
1187            scope = external_scope
1188                .pop_external()
1189                .ok_or_else(|| CompilerError::ScopePopError)?;
1190
1191            let external_slots = execution_block_ctx.external_slots();
1192
1193            // --- start block
1194            // set block size (len of compilation_context.buffer)
1195            append_u32(
1196                &mut compilation_context.buffer,
1197                execution_block_ctx.buffer.len() as u32,
1198            );
1199            // set injected slot count
1200            append_u32(
1201                &mut compilation_context.buffer,
1202                external_slots.len() as u32,
1203            );
1204            for slot in external_slots {
1205                compilation_context.insert_virtual_slot_address(slot.upgrade());
1206            }
1207
1208            // insert block body (compilation_context.buffer)
1209            compilation_context
1210                .buffer
1211                .extend_from_slice(&execution_block_ctx.buffer);
1212            // --- end block
1213
1214            // insert compiled caller expression
1215            scope = compile_expression(
1216                compilation_context,
1217                RichAst::new(*caller, &metadata),
1218                CompileMetadata::default(),
1219                scope,
1220            )?;
1221        }
1222
1223        // named slot
1224        DatexExpressionData::Slot(Slot::Named(name)) => {
1225            match name.as_str() {
1226                "endpoint" => {
1227                    compilation_context.append_instruction_code(
1228                        InstructionCode::GET_INTERNAL_SLOT,
1229                    );
1230                    append_u32(
1231                        &mut compilation_context.buffer,
1232                        InternalSlot::ENDPOINT as u32,
1233                    );
1234                }
1235                "core" => append_get_ref(
1236                    &mut compilation_context.buffer,
1237                    &PointerAddress::from(CoreLibPointerId::Core),
1238                ),
1239                _ => {
1240                    // invalid slot name
1241                    return Err(CompilerError::InvalidSlotName(name.clone()));
1242                }
1243            }
1244        }
1245
1246        // pointer address
1247        DatexExpressionData::PointerAddress(address) => {
1248            append_get_ref(&mut compilation_context.buffer, &address);
1249        }
1250
1251        // refs
1252        DatexExpressionData::CreateRef(create_ref) => {
1253            compilation_context.mark_has_non_static_value();
1254            compilation_context.append_instruction_code(
1255                match create_ref.mutability {
1256                    ReferenceMutability::Immutable => {
1257                        InstructionCode::CREATE_REF
1258                    }
1259                    ReferenceMutability::Mutable => {
1260                        InstructionCode::CREATE_REF_MUT
1261                    }
1262                },
1263            );
1264            scope = compile_expression(
1265                compilation_context,
1266                RichAst::new(*create_ref.expression, &metadata),
1267                CompileMetadata::default(),
1268                scope,
1269            )?;
1270        }
1271
1272        DatexExpressionData::TypeExpression(type_expression) => {
1273            compilation_context
1274                .append_instruction_code(InstructionCode::TYPE_EXPRESSION);
1275            scope = compile_type_expression(
1276                compilation_context,
1277                &type_expression,
1278                metadata,
1279                scope,
1280            )?;
1281        }
1282
1283        DatexExpressionData::Deref(deref) => {
1284            compilation_context.mark_has_non_static_value();
1285            compilation_context.append_instruction_code(InstructionCode::DEREF);
1286            scope = compile_expression(
1287                compilation_context,
1288                RichAst::new(*deref.expression, &metadata),
1289                CompileMetadata::default(),
1290                scope,
1291            )?;
1292        }
1293
1294        e => {
1295            println!("Unhandled expression in compiler: {:?}", e);
1296            return Err(CompilerError::UnexpectedTerm(Box::new(rich_ast.ast)));
1297        }
1298    }
1299
1300    Ok(scope)
1301}
1302
1303fn compile_key_value_entry(
1304    compilation_context: &mut CompilationContext,
1305    key: DatexExpression,
1306    value: DatexExpression,
1307    metadata: &Rc<RefCell<AstMetadata>>,
1308    mut scope: CompilationScope,
1309) -> Result<CompilationScope, CompilerError> {
1310    match key.data {
1311        // text -> insert key string
1312        DatexExpressionData::Text(text) => {
1313            append_key_string(&mut compilation_context.buffer, &text);
1314        }
1315        // other -> insert key as dynamic
1316        _ => {
1317            compilation_context
1318                .append_instruction_code(InstructionCode::KEY_VALUE_DYNAMIC);
1319            scope = compile_expression(
1320                compilation_context,
1321                RichAst::new(key, metadata),
1322                CompileMetadata::default(),
1323                scope,
1324            )?;
1325        }
1326    };
1327    // insert value
1328    scope = compile_expression(
1329        compilation_context,
1330        RichAst::new(value, metadata),
1331        CompileMetadata::default(),
1332        scope,
1333    )?;
1334    Ok(scope)
1335}
1336
1337fn compile_text_property_access(
1338    compilation_context: &mut CompilationContext,
1339    key: &str,
1340) {
1341    compilation_context
1342        .append_instruction_code(InstructionCode::GET_PROPERTY_TEXT);
1343    // append key length as u8
1344    append_u8(&mut compilation_context.buffer, key.len() as u8);
1345    // append key bytes
1346    compilation_context.buffer.extend_from_slice(key.as_bytes());
1347}
1348
1349fn compile_text_property_assignment(
1350    compilation_context: &mut CompilationContext,
1351    key: &str,
1352) {
1353    compilation_context
1354        .append_instruction_code(InstructionCode::SET_PROPERTY_TEXT);
1355    // append key length as u8
1356    append_u8(&mut compilation_context.buffer, key.len() as u8);
1357    // append key bytes
1358    compilation_context.buffer.extend_from_slice(key.as_bytes());
1359}
1360
1361fn compile_index_property_access(
1362    compilation_context: &mut CompilationContext,
1363    index: u32,
1364) {
1365    compilation_context
1366        .append_instruction_code(InstructionCode::GET_PROPERTY_INDEX);
1367    append_u32(&mut compilation_context.buffer, index);
1368}
1369
1370fn compile_index_property_assignment(
1371    compilation_context: &mut CompilationContext,
1372    index: u32,
1373) {
1374    compilation_context
1375        .append_instruction_code(InstructionCode::SET_PROPERTY_INDEX);
1376    append_u32(&mut compilation_context.buffer, index);
1377}
1378
1379fn compile_dynamic_property_access(
1380    compilation_context: &mut CompilationContext,
1381    key_expression: &DatexExpression,
1382    scope: CompilationScope,
1383) -> Result<CompilationScope, CompilerError> {
1384    compilation_context
1385        .append_instruction_code(InstructionCode::GET_PROPERTY_DYNAMIC);
1386    // compile key expression
1387    compile_expression(
1388        compilation_context,
1389        RichAst::new(
1390            key_expression.clone(),
1391            &Rc::new(RefCell::new(AstMetadata::default())),
1392        ),
1393        CompileMetadata::default(),
1394        scope,
1395    )
1396}
1397
1398fn compile_dynamic_property_assignment(
1399    compilation_context: &mut CompilationContext,
1400    key_expression: &DatexExpression,
1401    scope: CompilationScope,
1402) -> Result<CompilationScope, CompilerError> {
1403    compilation_context
1404        .append_instruction_code(InstructionCode::SET_PROPERTY_DYNAMIC);
1405    // compile key expression
1406    compile_expression(
1407        compilation_context,
1408        RichAst::new(
1409            key_expression.clone(),
1410            &Rc::new(RefCell::new(AstMetadata::default())),
1411        ),
1412        CompileMetadata::default(),
1413        scope,
1414    )
1415}
1416
1417#[cfg(test)]
1418pub mod tests {
1419    use super::{
1420        CompilationContext, CompileOptions, StaticValueOrDXB, compile_ast,
1421        compile_script, compile_script_or_return_static_value,
1422        compile_template, parse_datex_script_to_rich_ast_simple_error,
1423    };
1424    use crate::stdlib::assert_matches::assert_matches;
1425    use crate::stdlib::io::Read;
1426    use crate::stdlib::vec;
1427
1428    use crate::compiler::scope::CompilationScope;
1429    use crate::global::type_instruction_codes::TypeInstructionCode;
1430    use crate::libs::core::CoreLibPointerId;
1431    use crate::runtime::execution::ExecutionError;
1432    use crate::runtime::execution::context::{
1433        ExecutionContext, ExecutionMode, LocalExecutionContext,
1434    };
1435    use crate::values::core_values::integer::Integer;
1436    use crate::values::pointer::PointerAddress;
1437    use crate::values::value_container::ValueContainer;
1438    use crate::{
1439        global::instruction_codes::InstructionCode, logger::init_logger_debug,
1440    };
1441    use datex_core::compiler::error::CompilerError;
1442    use datex_core::values::core_values::integer::typed_integer::TypedInteger;
1443    use log::*;
1444
1445    fn compile_and_log(datex_script: &str) -> Vec<u8> {
1446        init_logger_debug();
1447        let (result, _) =
1448            compile_script(datex_script, CompileOptions::default()).unwrap();
1449        info!(
1450            "{:?}",
1451            result
1452                .iter()
1453                .map(|x| InstructionCode::try_from(*x).map(|x| x.to_string()))
1454                .map(|x| x.unwrap_or_else(|_| "Unknown".to_string()))
1455                .collect::<Vec<_>>()
1456        );
1457        result
1458    }
1459
1460    fn get_compilation_context(script: &str) -> CompilationContext {
1461        let mut options = CompileOptions::default();
1462        let ast =
1463            parse_datex_script_to_rich_ast_simple_error(script, &mut options)
1464                .unwrap();
1465
1466        let mut compilation_context = CompilationContext::new(
1467            Vec::with_capacity(256),
1468            vec![],
1469            options.compile_scope.execution_mode,
1470        );
1471        compile_ast(ast, &mut compilation_context, options).unwrap();
1472        compilation_context
1473    }
1474
1475    fn compile_datex_script_debug_unbounded(
1476        datex_script_parts: impl Iterator<Item = &'static str>,
1477    ) -> impl Iterator<Item = Vec<u8>> {
1478        let datex_script_parts = datex_script_parts.collect::<Vec<_>>();
1479        gen move {
1480            let mut compilation_scope =
1481                CompilationScope::new(ExecutionMode::unbounded());
1482            let len = datex_script_parts.len();
1483            for (index, script_part) in
1484                datex_script_parts.into_iter().enumerate()
1485            {
1486                // if last part, compile and return static value if possible
1487                if index == len - 1 {
1488                    compilation_scope.mark_as_last_execution();
1489                }
1490                let (dxb, new_compilation_scope) = compile_script(
1491                    script_part,
1492                    CompileOptions::new_with_scope(compilation_scope),
1493                )
1494                .unwrap();
1495                compilation_scope = new_compilation_scope;
1496                yield dxb;
1497            }
1498        }
1499    }
1500
1501    fn assert_unbounded_input_matches_output(
1502        input: Vec<&'static str>,
1503        expected_output: Vec<Vec<u8>>,
1504    ) {
1505        let input = input.into_iter();
1506        let expected_output = expected_output.into_iter();
1507        for (result, expected) in
1508            compile_datex_script_debug_unbounded(input.into_iter())
1509                .zip(expected_output.into_iter())
1510        {
1511            assert_eq!(result, expected);
1512        }
1513    }
1514
1515    #[test]
1516    fn simple_multiplication() {
1517        init_logger_debug();
1518
1519        let lhs: u8 = 1;
1520        let rhs: u8 = 2;
1521        let datex_script = format!("{lhs}u8 * {rhs}u8"); // 1 * 2
1522        let result = compile_and_log(&datex_script);
1523        assert_eq!(
1524            result,
1525            vec![
1526                InstructionCode::MULTIPLY.into(),
1527                InstructionCode::UINT_8.into(),
1528                lhs,
1529                InstructionCode::UINT_8.into(),
1530                rhs,
1531            ]
1532        );
1533    }
1534
1535    #[test]
1536    fn simple_multiplication_close() {
1537        init_logger_debug();
1538
1539        let lhs: u8 = 1;
1540        let rhs: u8 = 2;
1541        let datex_script = format!("{lhs}u8 * {rhs}u8;"); // 1 * 2
1542        let result = compile_and_log(&datex_script);
1543        assert_eq!(
1544            result,
1545            vec![
1546                InstructionCode::SHORT_STATEMENTS.into(),
1547                1,
1548                1, // terminated
1549                InstructionCode::MULTIPLY.into(),
1550                InstructionCode::UINT_8.into(),
1551                lhs,
1552                InstructionCode::UINT_8.into(),
1553                rhs,
1554            ]
1555        );
1556    }
1557
1558    #[test]
1559    fn is_operator() {
1560        init_logger_debug();
1561
1562        // TODO #151: compare refs
1563        let datex_script = "1u8 is 2u8".to_string();
1564        let result = compile_and_log(&datex_script);
1565        assert_eq!(
1566            result,
1567            vec![
1568                InstructionCode::IS.into(),
1569                InstructionCode::UINT_8.into(),
1570                1,
1571                InstructionCode::UINT_8.into(),
1572                2
1573            ]
1574        );
1575
1576        let datex_script =
1577            "const a = &mut 42u8; const b = &mut 69u8; a is b".to_string(); // a is b
1578        let result = compile_and_log(&datex_script);
1579        assert_eq!(
1580            result,
1581            vec![
1582                InstructionCode::SHORT_STATEMENTS.into(),
1583                3,
1584                0, // not terminated
1585                InstructionCode::ALLOCATE_SLOT.into(),
1586                0,
1587                0,
1588                0,
1589                0,
1590                InstructionCode::CREATE_REF_MUT.into(),
1591                InstructionCode::UINT_8.into(),
1592                42,
1593                // val b = 69;
1594                InstructionCode::ALLOCATE_SLOT.into(),
1595                1,
1596                0,
1597                0,
1598                0,
1599                InstructionCode::CREATE_REF_MUT.into(),
1600                InstructionCode::UINT_8.into(),
1601                69,
1602                // a is b
1603                InstructionCode::IS.into(),
1604                InstructionCode::GET_SLOT.into(),
1605                0,
1606                0,
1607                0,
1608                0, // slot address for a
1609                InstructionCode::GET_SLOT.into(),
1610                1,
1611                0,
1612                0,
1613                0, // slot address for b
1614            ]
1615        );
1616    }
1617
1618    #[test]
1619    fn equality_operator() {
1620        init_logger_debug();
1621
1622        let lhs: u8 = 1;
1623        let rhs: u8 = 2;
1624        let datex_script = format!("{lhs}u8 == {rhs}u8"); // 1 == 2
1625        let result = compile_and_log(&datex_script);
1626        assert_eq!(
1627            result,
1628            vec![
1629                InstructionCode::STRUCTURAL_EQUAL.into(),
1630                InstructionCode::UINT_8.into(),
1631                lhs,
1632                InstructionCode::UINT_8.into(),
1633                rhs,
1634            ]
1635        );
1636
1637        let datex_script = format!("{lhs}u8 === {rhs}u8"); // 1 === 2
1638        let result = compile_and_log(&datex_script);
1639        assert_eq!(
1640            result,
1641            vec![
1642                InstructionCode::EQUAL.into(),
1643                InstructionCode::UINT_8.into(),
1644                lhs,
1645                InstructionCode::UINT_8.into(),
1646                rhs,
1647            ]
1648        );
1649
1650        let datex_script = format!("{lhs}u8 != {rhs}u8"); // 1 != 2
1651        let result = compile_and_log(&datex_script);
1652        assert_eq!(
1653            result,
1654            vec![
1655                InstructionCode::NOT_STRUCTURAL_EQUAL.into(),
1656                InstructionCode::UINT_8.into(),
1657                lhs,
1658                InstructionCode::UINT_8.into(),
1659                rhs,
1660            ]
1661        );
1662        let datex_script = format!("{lhs}u8 !== {rhs}u8"); // 1 !== 2
1663        let result = compile_and_log(&datex_script);
1664        assert_eq!(
1665            result,
1666            vec![
1667                InstructionCode::NOT_EQUAL.into(),
1668                InstructionCode::UINT_8.into(),
1669                lhs,
1670                InstructionCode::UINT_8.into(),
1671                rhs,
1672            ]
1673        );
1674    }
1675
1676    #[test]
1677    fn simple_addition() {
1678        init_logger_debug();
1679
1680        let lhs: u8 = 1;
1681        let rhs: u8 = 2;
1682        let datex_script = format!("{lhs}u8 + {rhs}u8"); // 1 + 2
1683        let result = compile_and_log(&datex_script);
1684        assert_eq!(
1685            result,
1686            vec![
1687                InstructionCode::ADD.into(),
1688                InstructionCode::UINT_8.into(),
1689                lhs,
1690                InstructionCode::UINT_8.into(),
1691                rhs
1692            ]
1693        );
1694
1695        let datex_script = format!("{lhs}u8 + {rhs}u8;"); // 1 + 2;
1696        let result = compile_and_log(&datex_script);
1697        assert_eq!(
1698            result,
1699            vec![
1700                InstructionCode::SHORT_STATEMENTS.into(),
1701                1,
1702                1, // terminated
1703                InstructionCode::ADD.into(),
1704                InstructionCode::UINT_8.into(),
1705                lhs,
1706                InstructionCode::UINT_8.into(),
1707                rhs,
1708            ]
1709        );
1710    }
1711
1712    #[test]
1713    fn multi_addition() {
1714        init_logger_debug();
1715
1716        let op1: u8 = 1;
1717        let op2: u8 = 2;
1718        let op3: u8 = 3;
1719        let op4: u8 = 4;
1720
1721        let datex_script = format!("{op1}u8 + {op2}u8 + {op3}u8 + {op4}u8"); // 1 + 2 + 3 + 4
1722        let result = compile_and_log(&datex_script);
1723        assert_eq!(
1724            result,
1725            vec![
1726                InstructionCode::ADD.into(),
1727                InstructionCode::ADD.into(),
1728                InstructionCode::ADD.into(),
1729                InstructionCode::UINT_8.into(),
1730                op1,
1731                InstructionCode::UINT_8.into(),
1732                op2,
1733                InstructionCode::UINT_8.into(),
1734                op3,
1735                InstructionCode::UINT_8.into(),
1736                op4,
1737            ]
1738        );
1739    }
1740
1741    #[test]
1742    fn mixed_calculation() {
1743        init_logger_debug();
1744
1745        let op1: u8 = 1;
1746        let op2: u8 = 2;
1747        let op3: u8 = 3;
1748        let op4: u8 = 4;
1749
1750        let datex_script = format!("{op1}u8 * {op2}u8 + {op3}u8 * {op4}u8"); // 1 + 2 + 3 + 4
1751        let result = compile_and_log(&datex_script);
1752        assert_eq!(
1753            result,
1754            vec![
1755                InstructionCode::ADD.into(),
1756                InstructionCode::MULTIPLY.into(),
1757                InstructionCode::UINT_8.into(),
1758                op1,
1759                InstructionCode::UINT_8.into(),
1760                op2,
1761                InstructionCode::MULTIPLY.into(),
1762                InstructionCode::UINT_8.into(),
1763                op3,
1764                InstructionCode::UINT_8.into(),
1765                op4,
1766            ]
1767        );
1768    }
1769
1770    #[test]
1771    fn complex_addition() {
1772        init_logger_debug();
1773
1774        let a: u8 = 1;
1775        let b: u8 = 2;
1776        let c: u8 = 3;
1777        let datex_script = format!("{a}u8 + ({b}u8 + {c}u8)"); // 1 + (2 + 3)
1778        let result = compile_and_log(&datex_script);
1779
1780        assert_eq!(
1781            result,
1782            vec![
1783                InstructionCode::ADD.into(),
1784                InstructionCode::UINT_8.into(),
1785                a,
1786                InstructionCode::ADD.into(),
1787                InstructionCode::UINT_8.into(),
1788                b,
1789                InstructionCode::UINT_8.into(),
1790                c,
1791            ]
1792        );
1793    }
1794
1795    #[test]
1796    fn complex_addition_and_subtraction() {
1797        init_logger_debug();
1798
1799        let a: u8 = 1;
1800        let b: u8 = 2;
1801        let c: u8 = 3;
1802        let datex_script = format!("{a}u8 + ({b}u8 - {c}u8)"); // 1 + (2 - 3)
1803        let result = compile_and_log(&datex_script);
1804        assert_eq!(
1805            result,
1806            vec![
1807                InstructionCode::ADD.into(),
1808                InstructionCode::UINT_8.into(),
1809                a,
1810                InstructionCode::SUBTRACT.into(),
1811                InstructionCode::UINT_8.into(),
1812                b,
1813                InstructionCode::UINT_8.into(),
1814                c,
1815            ]
1816        );
1817    }
1818
1819    #[test]
1820    fn integer_u8() {
1821        init_logger_debug();
1822        let val = 42;
1823        let datex_script = format!("{val}u8"); // 42
1824        let result = compile_and_log(&datex_script);
1825        assert_eq!(result, vec![InstructionCode::UINT_8.into(), val,]);
1826    }
1827
1828    // Test for decimal
1829    #[test]
1830    fn decimal() {
1831        init_logger_debug();
1832        let datex_script = "42.0";
1833        let result = compile_and_log(datex_script);
1834        let bytes = 42_i16.to_le_bytes();
1835
1836        let mut expected: Vec<u8> =
1837            vec![InstructionCode::DECIMAL_AS_INT_16.into()];
1838        expected.extend(bytes);
1839
1840        assert_eq!(result, expected);
1841    }
1842
1843    /// Test for test that is less than 256 characters
1844    #[test]
1845    fn short_text() {
1846        init_logger_debug();
1847        let val = "unyt";
1848        let datex_script = format!("\"{val}\""); // "unyt"
1849        let result = compile_and_log(&datex_script);
1850        let mut expected: Vec<u8> =
1851            vec![InstructionCode::SHORT_TEXT.into(), val.len() as u8];
1852        expected.extend(val.bytes());
1853        assert_eq!(result, expected);
1854    }
1855
1856    // Test empty list
1857    #[test]
1858    fn empty_list() {
1859        init_logger_debug();
1860        // TODO #437: support list constructor (apply on type)
1861        let datex_script = "[]";
1862        // const x = mut 42;
1863        let result = compile_and_log(datex_script);
1864        let expected: Vec<u8> = vec![
1865            InstructionCode::SHORT_LIST.into(),
1866            0, // length
1867        ];
1868        assert_eq!(result, expected);
1869    }
1870
1871    // Test list with single element
1872    #[test]
1873    fn single_element_list() {
1874        init_logger_debug();
1875        // TODO #438: support list constructor (apply on type)
1876        let datex_script = "[42u8]";
1877        let result = compile_and_log(datex_script);
1878        assert_eq!(
1879            result,
1880            vec![
1881                InstructionCode::SHORT_LIST.into(),
1882                1, // length
1883                InstructionCode::UINT_8.into(),
1884                42,
1885            ]
1886        );
1887    }
1888
1889    // Test list with multiple elements
1890    #[test]
1891    fn multi_element_list() {
1892        init_logger_debug();
1893        let datex_script = "[1u8, 2u8, 3u8]";
1894        let result = compile_and_log(datex_script);
1895        assert_eq!(
1896            result,
1897            vec![
1898                InstructionCode::SHORT_LIST.into(),
1899                3, // length
1900                InstructionCode::UINT_8.into(),
1901                1,
1902                InstructionCode::UINT_8.into(),
1903                2,
1904                InstructionCode::UINT_8.into(),
1905                3,
1906            ]
1907        );
1908
1909        // trailing comma
1910        let datex_script = "[1u8, 2u8, 3u8,]";
1911        let result = compile_and_log(datex_script);
1912        assert_eq!(
1913            result,
1914            vec![
1915                InstructionCode::SHORT_LIST.into(),
1916                3, // length
1917                InstructionCode::UINT_8.into(),
1918                1,
1919                InstructionCode::UINT_8.into(),
1920                2,
1921                InstructionCode::UINT_8.into(),
1922                3,
1923            ]
1924        );
1925    }
1926
1927    // Test list with expressions inside
1928    #[test]
1929    fn list_with_expressions() {
1930        init_logger_debug();
1931        let datex_script = "[1u8 + 2u8, 3u8 * 4u8]";
1932        let result = compile_and_log(datex_script);
1933        assert_eq!(
1934            result,
1935            vec![
1936                InstructionCode::SHORT_LIST.into(),
1937                2, // length
1938                InstructionCode::ADD.into(),
1939                InstructionCode::UINT_8.into(),
1940                1,
1941                InstructionCode::UINT_8.into(),
1942                2,
1943                InstructionCode::MULTIPLY.into(),
1944                InstructionCode::UINT_8.into(),
1945                3,
1946                InstructionCode::UINT_8.into(),
1947                4,
1948            ]
1949        );
1950    }
1951
1952    // Nested lists
1953    #[test]
1954    fn nested_lists() {
1955        init_logger_debug();
1956        let datex_script = "[1u8, [2u8, 3u8], 4u8]";
1957        let result = compile_and_log(datex_script);
1958        assert_eq!(
1959            result,
1960            vec![
1961                InstructionCode::SHORT_LIST.into(),
1962                3, // length
1963                InstructionCode::UINT_8.into(),
1964                1,
1965                InstructionCode::SHORT_LIST.into(),
1966                2, // length
1967                InstructionCode::UINT_8.into(),
1968                2,
1969                InstructionCode::UINT_8.into(),
1970                3,
1971                InstructionCode::UINT_8.into(),
1972                4,
1973            ]
1974        );
1975    }
1976
1977    // map with text key
1978    #[test]
1979    fn map_with_text_key() {
1980        init_logger_debug();
1981        let datex_script = "{\"key\": 42u8}";
1982        let result = compile_and_log(datex_script);
1983        let expected = vec![
1984            InstructionCode::SHORT_MAP.into(),
1985            1, // length
1986            InstructionCode::KEY_VALUE_SHORT_TEXT.into(),
1987            3, // length of "key"
1988            b'k',
1989            b'e',
1990            b'y',
1991            InstructionCode::UINT_8.into(),
1992            42,
1993        ];
1994        assert_eq!(result, expected);
1995    }
1996
1997    // map with integer key
1998    #[test]
1999    fn map_integer_key() {
2000        init_logger_debug();
2001        let datex_script = "{(10u8): 42u8}";
2002        let result = compile_and_log(datex_script);
2003        let expected = vec![
2004            InstructionCode::SHORT_MAP.into(),
2005            1, // length
2006            InstructionCode::KEY_VALUE_DYNAMIC.into(),
2007            InstructionCode::UINT_8.into(),
2008            10,
2009            InstructionCode::UINT_8.into(),
2010            42,
2011        ];
2012        assert_eq!(result, expected);
2013    }
2014
2015    // map with long text key (>255 bytes)
2016    #[test]
2017    fn map_with_long_text_key() {
2018        init_logger_debug();
2019        let long_key = "a".repeat(300);
2020        let datex_script = format!("{{\"{long_key}\": 42u8}}");
2021        let result = compile_and_log(&datex_script);
2022        let mut expected: Vec<u8> = vec![
2023            InstructionCode::SHORT_MAP.into(),
2024            1, // length
2025            InstructionCode::KEY_VALUE_DYNAMIC.into(),
2026            InstructionCode::TEXT.into(),
2027        ];
2028        expected.extend((long_key.len() as u32).to_le_bytes());
2029        expected.extend(long_key.as_bytes());
2030        expected.extend(vec![InstructionCode::UINT_8.into(), 42]);
2031        assert_eq!(result, expected);
2032    }
2033
2034    // map with dynamic key (expression)
2035    #[test]
2036    fn map_with_dynamic_key() {
2037        init_logger_debug();
2038        let datex_script = "{(1u8 + 2u8): 42u8}";
2039        let result = compile_and_log(datex_script);
2040        let expected = [
2041            InstructionCode::SHORT_MAP.into(),
2042            1, // length
2043            InstructionCode::KEY_VALUE_DYNAMIC.into(),
2044            InstructionCode::ADD.into(),
2045            InstructionCode::UINT_8.into(),
2046            1,
2047            InstructionCode::UINT_8.into(),
2048            2,
2049            InstructionCode::UINT_8.into(),
2050            42,
2051        ];
2052        assert_eq!(result, expected);
2053    }
2054
2055    // map with multiple keys (text, integer, expression)
2056    #[test]
2057    fn map_with_multiple_keys() {
2058        init_logger_debug();
2059        let datex_script = "{key: 42u8, (4u8): 43u8, (1u8 + 2u8): 44u8}";
2060        let result = compile_and_log(datex_script);
2061        let expected = vec![
2062            InstructionCode::SHORT_MAP.into(),
2063            3, // length
2064            InstructionCode::KEY_VALUE_SHORT_TEXT.into(),
2065            3, // length of "key"
2066            b'k',
2067            b'e',
2068            b'y',
2069            InstructionCode::UINT_8.into(),
2070            42,
2071            InstructionCode::KEY_VALUE_DYNAMIC.into(),
2072            InstructionCode::UINT_8.into(),
2073            4,
2074            InstructionCode::UINT_8.into(),
2075            43,
2076            InstructionCode::KEY_VALUE_DYNAMIC.into(),
2077            InstructionCode::ADD.into(),
2078            InstructionCode::UINT_8.into(),
2079            1,
2080            InstructionCode::UINT_8.into(),
2081            2,
2082            InstructionCode::UINT_8.into(),
2083            44,
2084        ];
2085        assert_eq!(result, expected);
2086    }
2087
2088    // empty map
2089    #[test]
2090    fn empty_map() {
2091        init_logger_debug();
2092        let datex_script = "{}";
2093        let result = compile_and_log(datex_script);
2094        let expected: Vec<u8> = vec![
2095            InstructionCode::SHORT_MAP.into(),
2096            0, // length
2097        ];
2098        assert_eq!(result, expected);
2099    }
2100
2101    #[test]
2102    fn allocate_slot() {
2103        init_logger_debug();
2104        let script = "const a = 42u8";
2105        let result = compile_and_log(script);
2106        assert_eq!(
2107            result,
2108            vec![
2109                InstructionCode::ALLOCATE_SLOT.into(),
2110                // slot index as u32
2111                0,
2112                0,
2113                0,
2114                0,
2115                InstructionCode::UINT_8.into(),
2116                42,
2117            ]
2118        );
2119    }
2120
2121    #[test]
2122    fn allocate_slot_with_value() {
2123        init_logger_debug();
2124        let script = "const a = 42u8; a + 1u8";
2125        let result = compile_and_log(script);
2126        assert_eq!(
2127            result,
2128            vec![
2129                InstructionCode::SHORT_STATEMENTS.into(),
2130                2,
2131                0, // not terminated
2132                InstructionCode::ALLOCATE_SLOT.into(),
2133                // slot index as u32
2134                0,
2135                0,
2136                0,
2137                0,
2138                InstructionCode::UINT_8.into(),
2139                42,
2140                InstructionCode::ADD.into(),
2141                InstructionCode::GET_SLOT.into(),
2142                // slot index as u32
2143                0,
2144                0,
2145                0,
2146                0,
2147                InstructionCode::UINT_8.into(),
2148                1,
2149            ]
2150        );
2151    }
2152
2153    #[test]
2154    fn allocate_scoped_slots() {
2155        init_logger_debug();
2156        let script = "const a = 42u8; (const a = 43u8; a); a";
2157        let result = compile_and_log(script);
2158        assert_eq!(
2159            result,
2160            vec![
2161                InstructionCode::SHORT_STATEMENTS.into(),
2162                3,
2163                0, // not terminated
2164                InstructionCode::ALLOCATE_SLOT.into(),
2165                0,
2166                0,
2167                0,
2168                0,
2169                InstructionCode::UINT_8.into(),
2170                42,
2171                InstructionCode::SHORT_STATEMENTS.into(),
2172                2,
2173                0, // not terminated
2174                InstructionCode::ALLOCATE_SLOT.into(),
2175                1,
2176                0,
2177                0,
2178                0,
2179                InstructionCode::UINT_8.into(),
2180                43,
2181                InstructionCode::GET_SLOT.into(),
2182                1,
2183                0,
2184                0,
2185                0,
2186                InstructionCode::DROP_SLOT.into(),
2187                1,
2188                0,
2189                0,
2190                0,
2191                InstructionCode::GET_SLOT.into(),
2192                // slot index as u32
2193                0,
2194                0,
2195                0,
2196                0,
2197            ]
2198        );
2199    }
2200
2201    #[test]
2202    fn allocate_scoped_slots_with_parent_variables() {
2203        init_logger_debug();
2204        let script =
2205            "const a = 42u8; const b = 41u8; (const a = 43u8; a; b); a";
2206        let result = compile_and_log(script);
2207        assert_eq!(
2208            result,
2209            vec![
2210                InstructionCode::SHORT_STATEMENTS.into(),
2211                4,
2212                0, // not terminated
2213                InstructionCode::ALLOCATE_SLOT.into(),
2214                0,
2215                0,
2216                0,
2217                0,
2218                InstructionCode::UINT_8.into(),
2219                42,
2220                InstructionCode::ALLOCATE_SLOT.into(),
2221                1,
2222                0,
2223                0,
2224                0,
2225                InstructionCode::UINT_8.into(),
2226                41,
2227                InstructionCode::SHORT_STATEMENTS.into(),
2228                3,
2229                0, // not terminated
2230                InstructionCode::ALLOCATE_SLOT.into(),
2231                2,
2232                0,
2233                0,
2234                0,
2235                InstructionCode::UINT_8.into(),
2236                43,
2237                InstructionCode::GET_SLOT.into(),
2238                2,
2239                0,
2240                0,
2241                0,
2242                InstructionCode::GET_SLOT.into(),
2243                1,
2244                0,
2245                0,
2246                0,
2247                InstructionCode::DROP_SLOT.into(),
2248                2,
2249                0,
2250                0,
2251                0,
2252                InstructionCode::GET_SLOT.into(),
2253                // slot index as u32
2254                0,
2255                0,
2256                0,
2257                0,
2258            ]
2259        );
2260    }
2261
2262    #[test]
2263    fn allocate_ref() {
2264        init_logger_debug();
2265        let script = "const a = &mut 42u8";
2266        let result = compile_and_log(script);
2267        assert_eq!(
2268            result,
2269            vec![
2270                InstructionCode::ALLOCATE_SLOT.into(),
2271                // slot index as u32
2272                0,
2273                0,
2274                0,
2275                0,
2276                InstructionCode::CREATE_REF_MUT.into(),
2277                InstructionCode::UINT_8.into(),
2278                42,
2279            ]
2280        );
2281    }
2282
2283    #[test]
2284    fn read_ref() {
2285        init_logger_debug();
2286        let script = "const a = &mut 42u8; a";
2287        let result = compile_and_log(script);
2288        assert_eq!(
2289            result,
2290            vec![
2291                InstructionCode::SHORT_STATEMENTS.into(),
2292                2,
2293                0, // not terminated
2294                InstructionCode::ALLOCATE_SLOT.into(),
2295                // slot index as u32
2296                0,
2297                0,
2298                0,
2299                0,
2300                InstructionCode::CREATE_REF_MUT.into(),
2301                InstructionCode::UINT_8.into(),
2302                42,
2303                InstructionCode::GET_SLOT.into(),
2304                // slot index as u32
2305                0,
2306                0,
2307                0,
2308                0,
2309            ]
2310        );
2311    }
2312
2313    #[test]
2314    fn compile() {
2315        init_logger_debug();
2316        let result = compile_template(
2317            "? + ?",
2318            &[
2319                TypedInteger::from(1u8).into(),
2320                TypedInteger::from(2u8).into(),
2321            ],
2322            CompileOptions::default(),
2323        );
2324        assert_eq!(
2325            result.unwrap().0,
2326            vec![
2327                InstructionCode::ADD.into(),
2328                InstructionCode::UINT_8.into(),
2329                1,
2330                InstructionCode::UINT_8.into(),
2331                2
2332            ]
2333        );
2334    }
2335
2336    #[test]
2337    fn compile_macro() {
2338        init_logger_debug();
2339        let a = TypedInteger::from(1u8);
2340        let result = compile!("?", a);
2341        assert_eq!(result.unwrap().0, vec![InstructionCode::UINT_8.into(), 1,]);
2342    }
2343
2344    #[test]
2345    fn compile_macro_multi() {
2346        init_logger_debug();
2347        let result =
2348            compile!("? + ?", TypedInteger::from(1u8), TypedInteger::from(2u8));
2349        assert_eq!(
2350            result.unwrap().0,
2351            vec![
2352                InstructionCode::ADD.into(),
2353                InstructionCode::UINT_8.into(),
2354                1,
2355                InstructionCode::UINT_8.into(),
2356                2
2357            ]
2358        );
2359    }
2360
2361    fn get_json_test_string(file_path: &str) -> String {
2362        // read json from test file
2363        let file_path = format!("benches/json/{file_path}");
2364        let file_path = std::path::Path::new(&file_path);
2365        let file =
2366            std::fs::File::open(file_path).expect("Failed to open test.json");
2367        let mut reader = crate::stdlib::io::BufReader::new(file);
2368        let mut json_string = String::new();
2369        reader
2370            .read_to_string(&mut json_string)
2371            .expect("Failed to read test.json");
2372        json_string
2373    }
2374
2375    #[test]
2376    fn json_to_dxb_large_file() {
2377        let json = get_json_test_string("test3.json");
2378        let _ = compile_script(&json, CompileOptions::default())
2379            .expect("Failed to parse JSON string");
2380    }
2381
2382    #[test]
2383    fn static_value_detection() {
2384        init_logger_debug();
2385
2386        // non-static
2387        let script = "1 + 2";
2388        let compilation_scope = get_compilation_context(script);
2389        assert!(compilation_scope.has_non_static_value);
2390
2391        let script = "1 2";
2392        let compilation_scope = get_compilation_context(script);
2393        assert!(compilation_scope.has_non_static_value);
2394
2395        let script = "1;2";
2396        let compilation_scope = get_compilation_context(script);
2397        assert!(compilation_scope.has_non_static_value);
2398
2399        let script = r#"{("x" + "y"): 1}"#;
2400        let compilation_scope = get_compilation_context(script);
2401        assert!(compilation_scope.has_non_static_value);
2402
2403        // static
2404        let script = "1";
2405        let compilation_scope = get_compilation_context(script);
2406        assert!(!compilation_scope.has_non_static_value);
2407
2408        let script = "[]";
2409        let compilation_scope = get_compilation_context(script);
2410        assert!(!compilation_scope.has_non_static_value);
2411
2412        let script = "{}";
2413        let compilation_scope = get_compilation_context(script);
2414        assert!(!compilation_scope.has_non_static_value);
2415
2416        let script = "[1,2,3]";
2417        let compilation_scope = get_compilation_context(script);
2418        assert!(!compilation_scope.has_non_static_value);
2419
2420        let script = "{a: 2}";
2421        let compilation_scope = get_compilation_context(script);
2422        assert!(!compilation_scope.has_non_static_value);
2423
2424        // because of unary - 42
2425        let script = "-42";
2426        let compilation_scope = get_compilation_context(script);
2427        assert!(!compilation_scope.has_non_static_value);
2428    }
2429
2430    #[test]
2431    fn compile_auto_static_value_detection() {
2432        let script = "1u8";
2433        let (res, _) = compile_script_or_return_static_value(
2434            script,
2435            CompileOptions::default(),
2436        )
2437        .unwrap();
2438        assert_matches!(
2439            res,
2440            StaticValueOrDXB::StaticValue(val) if val == Some(TypedInteger::from(1u8).into())
2441        );
2442
2443        let script = "1u8 + 2u8";
2444        let (res, _) = compile_script_or_return_static_value(
2445            script,
2446            CompileOptions::default(),
2447        )
2448        .unwrap();
2449        assert_matches!(
2450            res,
2451            StaticValueOrDXB::DXB(code) if code == vec![
2452                InstructionCode::ADD.into(),
2453                InstructionCode::UINT_8.into(),
2454                1,
2455                InstructionCode::UINT_8.into(),
2456                2,
2457            ]
2458        );
2459    }
2460
2461    #[test]
2462    fn remote_execution() {
2463        let script = "42u8 :: 43u8";
2464        let (res, _) =
2465            compile_script(script, CompileOptions::default()).unwrap();
2466        assert_eq!(
2467            res,
2468            vec![
2469                InstructionCode::REMOTE_EXECUTION.into(),
2470                // --- start of block
2471                // block size (2 bytes)
2472                2,
2473                0,
2474                0,
2475                0,
2476                // injected slots (0)
2477                0,
2478                0,
2479                0,
2480                0,
2481                // literal value 43
2482                InstructionCode::UINT_8.into(),
2483                43,
2484                // --- end of block
2485                // caller (literal value 42 for test)
2486                InstructionCode::UINT_8.into(),
2487                42,
2488            ]
2489        );
2490    }
2491
2492    #[test]
2493    fn remote_execution_expression() {
2494        let script = "42u8 :: 1u8 + 2u8";
2495        let (res, _) =
2496            compile_script(script, CompileOptions::default()).unwrap();
2497        assert_eq!(
2498            res,
2499            vec![
2500                InstructionCode::REMOTE_EXECUTION.into(),
2501                // --- start of block
2502                // block size (5 bytes)
2503                5,
2504                0,
2505                0,
2506                0,
2507                // injected slots (0)
2508                0,
2509                0,
2510                0,
2511                0,
2512                // expression: 1 + 2
2513                InstructionCode::ADD.into(),
2514                InstructionCode::UINT_8.into(),
2515                1,
2516                InstructionCode::UINT_8.into(),
2517                2,
2518                // --- end of block
2519                // caller (literal value 42 for test)
2520                InstructionCode::UINT_8.into(),
2521                42,
2522            ]
2523        );
2524    }
2525
2526    #[test]
2527    fn remote_execution_injected_const() {
2528        init_logger_debug();
2529        let script = "const x = 42u8; 1u8 :: x";
2530        let (res, _) =
2531            compile_script(script, CompileOptions::default()).unwrap();
2532        assert_eq!(
2533            res,
2534            vec![
2535                InstructionCode::SHORT_STATEMENTS.into(),
2536                2,
2537                0, // not terminated
2538                InstructionCode::ALLOCATE_SLOT.into(),
2539                // slot index as u32
2540                0,
2541                0,
2542                0,
2543                0,
2544                InstructionCode::UINT_8.into(),
2545                42,
2546                InstructionCode::REMOTE_EXECUTION.into(),
2547                // --- start of block
2548                // block size (5 bytes)
2549                5,
2550                0,
2551                0,
2552                0,
2553                // injected slots (1)
2554                1,
2555                0,
2556                0,
2557                0,
2558                // slot 0
2559                0,
2560                0,
2561                0,
2562                0,
2563                // slot 0 (mapped from slot 0)
2564                InstructionCode::GET_SLOT.into(),
2565                // slot index as u32
2566                0,
2567                0,
2568                0,
2569                0,
2570                // --- end of block
2571                // caller (literal value 1 for test)
2572                InstructionCode::UINT_8.into(),
2573                1,
2574            ]
2575        );
2576    }
2577
2578    #[test]
2579    fn remote_execution_injected_var() {
2580        init_logger_debug();
2581        // var x only refers to a value, not a ref, but since it is transferred to a
2582        // remote context, its state is synced via a ref (VariableReference model)
2583        let script = "var x = 42u8; 1u8 :: x; x = 43u8;";
2584        let (res, _) =
2585            compile_script(script, CompileOptions::default()).unwrap();
2586        assert_eq!(
2587            res,
2588            vec![
2589                InstructionCode::SHORT_STATEMENTS.into(),
2590                3,
2591                1, // terminated
2592                InstructionCode::ALLOCATE_SLOT.into(),
2593                // slot index as u32
2594                0,
2595                0,
2596                0,
2597                0,
2598                InstructionCode::UINT_8.into(),
2599                42,
2600                InstructionCode::ALLOCATE_SLOT.into(),
2601                // slot index as u32
2602                1,
2603                0,
2604                0,
2605                0,
2606                // create ref
2607                InstructionCode::CREATE_REF.into(),
2608                // slot 0
2609                InstructionCode::GET_SLOT.into(),
2610                // slot index as u32
2611                0,
2612                0,
2613                0,
2614                0,
2615                InstructionCode::REMOTE_EXECUTION.into(),
2616                // --- start of block
2617                // block size (5 bytes)
2618                5,
2619                0,
2620                0,
2621                0,
2622                // injected slots (1)
2623                1,
2624                0,
2625                0,
2626                0,
2627                // slot 0
2628                0,
2629                0,
2630                0,
2631                0,
2632                // slot 0 (mapped from slot 0)
2633                InstructionCode::GET_SLOT.into(),
2634                // slot index as u32
2635                0,
2636                0,
2637                0,
2638                0,
2639                // --- end of block
2640                // caller (literal value 1 for test)
2641                InstructionCode::UINT_8.into(),
2642                1,
2643                // TODO #238: this is not the correct slot assignment for VariableReference model
2644                // set x to 43
2645                InstructionCode::SET_SLOT.into(),
2646                // slot index as u32
2647                0,
2648                0,
2649                0,
2650                0,
2651                InstructionCode::UINT_8.into(),
2652                43,
2653            ]
2654        );
2655    }
2656
2657    #[test]
2658    fn remote_execution_injected_consts() {
2659        let script = "const x = 42u8; const y = 69u8; 1u8 :: x + y";
2660        let (res, _) =
2661            compile_script(script, CompileOptions::default()).unwrap();
2662        assert_eq!(
2663            res,
2664            vec![
2665                InstructionCode::SHORT_STATEMENTS.into(),
2666                3,
2667                0, // not terminated
2668                InstructionCode::ALLOCATE_SLOT.into(),
2669                // slot index as u32
2670                0,
2671                0,
2672                0,
2673                0,
2674                InstructionCode::UINT_8.into(),
2675                42,
2676                InstructionCode::ALLOCATE_SLOT.into(),
2677                // slot index as u32
2678                1,
2679                0,
2680                0,
2681                0,
2682                InstructionCode::UINT_8.into(),
2683                69,
2684                InstructionCode::REMOTE_EXECUTION.into(),
2685                // --- start of block
2686                // block size (11 bytes)
2687                11,
2688                0,
2689                0,
2690                0,
2691                // injected slots (2)
2692                2,
2693                0,
2694                0,
2695                0,
2696                // slot 0
2697                0,
2698                0,
2699                0,
2700                0,
2701                // slot 1
2702                1,
2703                0,
2704                0,
2705                0,
2706                // expression: x + y
2707                InstructionCode::ADD.into(),
2708                InstructionCode::GET_SLOT.into(),
2709                // slot index as u32
2710                0,
2711                0,
2712                0,
2713                0,
2714                InstructionCode::GET_SLOT.into(),
2715                // slot index as u32
2716                1,
2717                0,
2718                0,
2719                0,
2720                // --- end of block
2721                // caller (literal value 1 for test)
2722                InstructionCode::UINT_8.into(),
2723                1,
2724            ]
2725        );
2726    }
2727
2728    #[test]
2729    fn remote_execution_shadow_const() {
2730        let script =
2731            "const x = 42u8; const y = 69u8; 1u8 :: (const x = 5u8; x + y)";
2732        let (res, _) =
2733            compile_script(script, CompileOptions::default()).unwrap();
2734        assert_eq!(
2735            res,
2736            vec![
2737                InstructionCode::SHORT_STATEMENTS.into(),
2738                3,
2739                0, // not terminated
2740                InstructionCode::ALLOCATE_SLOT.into(),
2741                // slot index as u32
2742                0,
2743                0,
2744                0,
2745                0,
2746                InstructionCode::UINT_8.into(),
2747                42,
2748                InstructionCode::ALLOCATE_SLOT.into(),
2749                // slot index as u32
2750                1,
2751                0,
2752                0,
2753                0,
2754                InstructionCode::UINT_8.into(),
2755                69,
2756                InstructionCode::REMOTE_EXECUTION.into(),
2757                // --- start of block
2758                // block size (21 bytes)
2759                21,
2760                0,
2761                0,
2762                0,
2763                // injected slots (1)
2764                1,
2765                0,
2766                0,
2767                0,
2768                // slot 1 (y)
2769                1,
2770                0,
2771                0,
2772                0,
2773                InstructionCode::SHORT_STATEMENTS.into(),
2774                2,
2775                0, // not terminated
2776                // allocate slot for x
2777                InstructionCode::ALLOCATE_SLOT.into(),
2778                // slot index as u32
2779                1,
2780                0,
2781                0,
2782                0,
2783                InstructionCode::UINT_8.into(),
2784                5,
2785                // expression: x + y
2786                InstructionCode::ADD.into(),
2787                InstructionCode::GET_SLOT.into(),
2788                // slot index as u32
2789                1,
2790                0,
2791                0,
2792                0,
2793                InstructionCode::GET_SLOT.into(),
2794                // slot index as u32
2795                0,
2796                0,
2797                0,
2798                0,
2799                // --- end of block
2800                // caller (literal value 1 for test)
2801                InstructionCode::UINT_8.into(),
2802                1,
2803            ]
2804        );
2805    }
2806
2807    #[test]
2808    fn remote_execution_nested() {
2809        let script = "const x = 42u8; (1u8 :: (2u8 :: x))";
2810        let (res, _) =
2811            compile_script(script, CompileOptions::default()).unwrap();
2812
2813        assert_eq!(
2814            res,
2815            vec![
2816                InstructionCode::SHORT_STATEMENTS.into(),
2817                2,
2818                0, // not terminated
2819                InstructionCode::ALLOCATE_SLOT.into(),
2820                // slot index as u32
2821                0,
2822                0,
2823                0,
2824                0,
2825                InstructionCode::UINT_8.into(),
2826                42,
2827                InstructionCode::REMOTE_EXECUTION.into(),
2828                // --- start of block 1
2829                // block size (20 bytes)
2830                20,
2831                0,
2832                0,
2833                0,
2834                // injected slots (1)
2835                1,
2836                0,
2837                0,
2838                0,
2839                // slot 0
2840                0,
2841                0,
2842                0,
2843                0,
2844                // nested remote execution
2845                InstructionCode::REMOTE_EXECUTION.into(),
2846                // --- start of block 2
2847                // block size (5 bytes)
2848                5,
2849                0,
2850                0,
2851                0,
2852                // injected slots (1)
2853                1,
2854                0,
2855                0,
2856                0,
2857                // slot 0
2858                0,
2859                0,
2860                0,
2861                0,
2862                InstructionCode::GET_SLOT.into(),
2863                // slot index as u32
2864                0,
2865                0,
2866                0,
2867                0,
2868                // --- end of block 2
2869                // caller (literal value 2 for test)
2870                InstructionCode::UINT_8.into(),
2871                2,
2872                // -- end of block 1
2873                // caller (literal value 1 for test)
2874                InstructionCode::UINT_8.into(),
2875                1,
2876            ]
2877        );
2878    }
2879
2880    #[test]
2881    fn remote_execution_nested2() {
2882        let script = "const x = 42u8; (1u8 :: (x :: x))";
2883        let (res, _) =
2884            compile_script(script, CompileOptions::default()).unwrap();
2885
2886        assert_eq!(
2887            res,
2888            vec![
2889                InstructionCode::SHORT_STATEMENTS.into(),
2890                2,
2891                0, // not terminated
2892                InstructionCode::ALLOCATE_SLOT.into(),
2893                // slot index as u32
2894                0,
2895                0,
2896                0,
2897                0,
2898                InstructionCode::UINT_8.into(),
2899                42,
2900                InstructionCode::REMOTE_EXECUTION.into(),
2901                // --- start of block 1
2902                // block size (23 bytes)
2903                23,
2904                0,
2905                0,
2906                0,
2907                // injected slots (1)
2908                1,
2909                0,
2910                0,
2911                0,
2912                // slot 0
2913                0,
2914                0,
2915                0,
2916                0,
2917                // nested remote execution
2918                InstructionCode::REMOTE_EXECUTION.into(),
2919                // --- start of block 2
2920                // block size (5 bytes)
2921                5,
2922                0,
2923                0,
2924                0,
2925                // injected slots (1)
2926                1,
2927                0,
2928                0,
2929                0,
2930                // slot 0
2931                0,
2932                0,
2933                0,
2934                0,
2935                InstructionCode::GET_SLOT.into(),
2936                // slot index as u32
2937                0,
2938                0,
2939                0,
2940                0,
2941                // --- end of block 2
2942                // caller (literal value 2 for test)
2943                InstructionCode::GET_SLOT.into(),
2944                0,
2945                0,
2946                0,
2947                0,
2948                // --- end of block 1
2949                // caller (literal value 1 for test)
2950                InstructionCode::UINT_8.into(),
2951                1,
2952            ]
2953        );
2954    }
2955
2956    #[test]
2957    fn assignment_to_const() {
2958        init_logger_debug();
2959        let script = "const a = 42; a = 43";
2960        let result = compile_script(script, CompileOptions::default())
2961            .map_err(|e| e.error);
2962        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2963    }
2964
2965    #[test]
2966    fn assignment_to_const_mut() {
2967        init_logger_debug();
2968        let script = "const a = &mut 42; a = 43";
2969        let result = compile_script(script, CompileOptions::default())
2970            .map_err(|e| e.error);
2971        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2972    }
2973
2974    #[test]
2975    fn internal_assignment_to_const_mut() {
2976        init_logger_debug();
2977        let script = "const a = &mut 42; *a = 43";
2978        let result = compile_script(script, CompileOptions::default());
2979        assert_matches!(result, Ok(_));
2980    }
2981
2982    #[test]
2983    fn addition_to_const_mut_ref() {
2984        init_logger_debug();
2985        let script = "const a = &mut 42; *a += 1;";
2986        let result = compile_script(script, CompileOptions::default());
2987        assert_matches!(result, Ok(_));
2988    }
2989
2990    #[test]
2991    fn addition_to_const_variable() {
2992        init_logger_debug();
2993        let script = "const a = 42; a += 1";
2994        let result = compile_script(script, CompileOptions::default())
2995            .map_err(|e| e.error);
2996        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2997    }
2998
2999    #[test]
3000    fn internal_slot_endpoint() {
3001        let script = "#endpoint";
3002        let (res, _) =
3003            compile_script(script, CompileOptions::default()).unwrap();
3004        assert_eq!(
3005            res,
3006            vec![
3007                InstructionCode::GET_INTERNAL_SLOT.into(),
3008                // slot index as u32
3009                0,
3010                0xff,
3011                0xff,
3012                0xff
3013            ]
3014        );
3015    }
3016
3017    // this is not a valid Datex script, just testing the compiler
3018    #[test]
3019    fn deref() {
3020        let script = "*10u8";
3021        let (res, _) =
3022            compile_script(script, CompileOptions::default()).unwrap();
3023        assert_eq!(
3024            res,
3025            vec![
3026                InstructionCode::DEREF.into(),
3027                InstructionCode::UINT_8.into(),
3028                // integer as u8
3029                10,
3030            ]
3031        );
3032    }
3033
3034    #[test]
3035    fn type_literal_integer() {
3036        let script = "type<1>";
3037        let (res, _) =
3038            compile_script(script, CompileOptions::default()).unwrap();
3039        assert_eq!(
3040            res,
3041            vec![
3042                InstructionCode::TYPE_EXPRESSION.into(),
3043                TypeInstructionCode::TYPE_LITERAL_INTEGER.into(),
3044                // slot index as u32
3045                2,
3046                1,
3047                0,
3048                0,
3049                0,
3050                1
3051            ]
3052        );
3053    }
3054
3055    #[test]
3056    fn type_core_type_integer() {
3057        let script = "integer";
3058        let (res, _) =
3059            compile_script(script, CompileOptions::default()).unwrap();
3060        let mut instructions: Vec<u8> =
3061            vec![InstructionCode::GET_INTERNAL_REF.into()];
3062        // pointer id
3063        instructions.append(
3064            &mut PointerAddress::from(CoreLibPointerId::Integer(None))
3065                .bytes()
3066                .to_vec(),
3067        );
3068        assert_eq!(res, instructions);
3069    }
3070
3071    #[test]
3072    fn compile_continuous_terminated_script() {
3073        let input = vec!["1u8", "2u8", "3u8;"];
3074        let expected_output = vec![
3075            vec![
3076                InstructionCode::UNBOUNDED_STATEMENTS.into(),
3077                InstructionCode::UINT_8.into(),
3078                1,
3079            ],
3080            vec![InstructionCode::UINT_8.into(), 2],
3081            vec![
3082                InstructionCode::UINT_8.into(),
3083                3,
3084                InstructionCode::UNBOUNDED_STATEMENTS_END.into(),
3085                1, // terminated
3086            ],
3087        ];
3088
3089        assert_unbounded_input_matches_output(input, expected_output);
3090    }
3091
3092    #[test]
3093    fn compile_continuous_unterminated_script() {
3094        let input = vec!["1u8", "2u8 + 3u8", "3u8"];
3095        let expected_output = vec![
3096            vec![
3097                InstructionCode::UNBOUNDED_STATEMENTS.into(),
3098                InstructionCode::UINT_8.into(),
3099                1,
3100            ],
3101            vec![
3102                InstructionCode::ADD.into(),
3103                InstructionCode::UINT_8.into(),
3104                2,
3105                InstructionCode::UINT_8.into(),
3106                3,
3107            ],
3108            vec![
3109                InstructionCode::UINT_8.into(),
3110                3,
3111                InstructionCode::UNBOUNDED_STATEMENTS_END.into(),
3112                0, // unterminated
3113            ],
3114        ];
3115
3116        assert_unbounded_input_matches_output(input, expected_output);
3117    }
3118
3119    #[test]
3120    fn compile_continuous_complex() {
3121        let input = vec!["1u8", "integer"];
3122        let expected_output = vec![
3123            vec![
3124                InstructionCode::UNBOUNDED_STATEMENTS.into(),
3125                InstructionCode::UINT_8.into(),
3126                1,
3127            ],
3128            vec![
3129                InstructionCode::GET_INTERNAL_REF.into(),
3130                // pointer id for integer
3131                100,
3132                0,
3133                0,
3134                InstructionCode::UNBOUNDED_STATEMENTS_END.into(),
3135                0, // unterminated
3136            ],
3137        ];
3138
3139        assert_unbounded_input_matches_output(input, expected_output);
3140    }
3141
3142    #[test]
3143    fn test_get_property_text() {
3144        init_logger_debug();
3145        let datex_script = "'test'.example";
3146        let result = compile_and_log(datex_script);
3147        let expected = vec![
3148            InstructionCode::GET_PROPERTY_TEXT.into(),
3149            7, // length of "example"
3150            b'e',
3151            b'x',
3152            b'a',
3153            b'm',
3154            b'p',
3155            b'l',
3156            b'e',
3157            // base value
3158            InstructionCode::SHORT_TEXT.into(),
3159            4, // length of "test"
3160            b't',
3161            b'e',
3162            b's',
3163            b't',
3164        ];
3165        assert_eq!(result, expected);
3166    }
3167
3168    #[test]
3169    fn test_get_property_text_quoted() {
3170        init_logger_debug();
3171        let datex_script = "'test'.'example'";
3172        let result = compile_and_log(datex_script);
3173        let expected = vec![
3174            InstructionCode::GET_PROPERTY_TEXT.into(),
3175            7, // length of "example"
3176            b'e',
3177            b'x',
3178            b'a',
3179            b'm',
3180            b'p',
3181            b'l',
3182            b'e',
3183            // base value
3184            InstructionCode::SHORT_TEXT.into(),
3185            4, // length of "test"
3186            b't',
3187            b'e',
3188            b's',
3189            b't',
3190        ];
3191        assert_eq!(result, expected);
3192    }
3193
3194    #[test]
3195    fn test_get_property_index() {
3196        init_logger_debug();
3197        let datex_script = "'test'.42";
3198        let result = compile_and_log(datex_script);
3199        let expected = vec![
3200            InstructionCode::GET_PROPERTY_INDEX.into(),
3201            // u32 index 42
3202            42,
3203            0,
3204            0,
3205            0,
3206            // base value
3207            InstructionCode::SHORT_TEXT.into(),
3208            4, // length of "test"
3209            b't',
3210            b'e',
3211            b's',
3212            b't',
3213        ];
3214        assert_eq!(result, expected);
3215    }
3216
3217    #[test]
3218    fn test_get_property_dynamic() {
3219        init_logger_debug();
3220        let datex_script = "'test'.(1u8 + 2u8)";
3221        let result = compile_and_log(datex_script);
3222        let expected = vec![
3223            InstructionCode::GET_PROPERTY_DYNAMIC.into(),
3224            // property expression: 1 + 2
3225            InstructionCode::ADD.into(),
3226            InstructionCode::UINT_8.into(),
3227            1,
3228            InstructionCode::UINT_8.into(),
3229            2,
3230            // base value
3231            InstructionCode::SHORT_TEXT.into(),
3232            4, // length of "test"
3233            b't',
3234            b'e',
3235            b's',
3236            b't',
3237        ];
3238        assert_eq!(result, expected);
3239    }
3240
3241    #[test]
3242    fn test_set_property_text() {
3243        init_logger_debug();
3244        let datex_script = "'test'.example = 42u8";
3245        let result = compile_and_log(datex_script);
3246        let expected = vec![
3247            InstructionCode::SET_PROPERTY_TEXT.into(),
3248            7, // length of "example"
3249            b'e',
3250            b'x',
3251            b'a',
3252            b'm',
3253            b'p',
3254            b'l',
3255            b'e',
3256            // value to set
3257            InstructionCode::UINT_8.into(),
3258            42,
3259            // base value
3260            InstructionCode::SHORT_TEXT.into(),
3261            4, // length of "test"
3262            b't',
3263            b'e',
3264            b's',
3265            b't',
3266        ];
3267        assert_eq!(result, expected);
3268    }
3269
3270    #[test]
3271    fn test_set_property_index() {
3272        init_logger_debug();
3273        let datex_script = "'test'.42 = 43u8";
3274        let result = compile_and_log(datex_script);
3275        let expected = vec![
3276            InstructionCode::SET_PROPERTY_INDEX.into(),
3277            // u32 index 42
3278            42,
3279            0,
3280            0,
3281            0,
3282            // value to set
3283            InstructionCode::UINT_8.into(),
3284            43,
3285            // base value
3286            InstructionCode::SHORT_TEXT.into(),
3287            4, // length of "test"
3288            b't',
3289            b'e',
3290            b's',
3291            b't',
3292        ];
3293        assert_eq!(result, expected);
3294    }
3295
3296    #[test]
3297    fn test_set_property_dynamic() {
3298        init_logger_debug();
3299        let datex_script = "'test'.(1u8 + 2u8) = 43u8";
3300        let result = compile_and_log(datex_script);
3301        let expected = vec![
3302            InstructionCode::SET_PROPERTY_DYNAMIC.into(),
3303            // property expression: 1 + 2
3304            InstructionCode::ADD.into(),
3305            InstructionCode::UINT_8.into(),
3306            1,
3307            InstructionCode::UINT_8.into(),
3308            2,
3309            // value to set
3310            InstructionCode::UINT_8.into(),
3311            43,
3312            // base value
3313            InstructionCode::SHORT_TEXT.into(),
3314            4, // length of "test"
3315            b't',
3316            b'e',
3317            b's',
3318            b't',
3319        ];
3320        assert_eq!(result, expected);
3321    }
3322
3323    #[test]
3324    fn test_apply_no_arguments() {
3325        init_logger_debug();
3326        let datex_script = "'test'()";
3327        let result = compile_and_log(datex_script);
3328        let expected = vec![
3329            InstructionCode::APPLY_ZERO.into(),
3330            // base value
3331            InstructionCode::SHORT_TEXT.into(),
3332            4, // length of "test"
3333            b't',
3334            b'e',
3335            b's',
3336            b't',
3337        ];
3338        assert_eq!(result, expected);
3339    }
3340
3341    #[test]
3342    fn test_apply_one_argument() {
3343        init_logger_debug();
3344        let datex_script = "'test' 42u8";
3345        let result = compile_and_log(datex_script);
3346        let expected = vec![
3347            InstructionCode::APPLY_SINGLE.into(),
3348            // argument
3349            InstructionCode::UINT_8.into(),
3350            42,
3351            // base value
3352            InstructionCode::SHORT_TEXT.into(),
3353            4, // length of "test"
3354            b't',
3355            b'e',
3356            b's',
3357            b't',
3358        ];
3359        assert_eq!(result, expected);
3360    }
3361
3362    #[test]
3363    fn test_apply_multiple_arguments() {
3364        init_logger_debug();
3365        let datex_script = "'test'(1u8, 2u8, 3u8)";
3366        let result = compile_and_log(datex_script);
3367        let expected = vec![
3368            InstructionCode::APPLY.into(),
3369            3, // number of arguments
3370            0,
3371            // argument 1
3372            InstructionCode::UINT_8.into(),
3373            1,
3374            // argument 2
3375            InstructionCode::UINT_8.into(),
3376            2,
3377            // argument 3
3378            InstructionCode::UINT_8.into(),
3379            3,
3380            // base value
3381            InstructionCode::SHORT_TEXT.into(),
3382            4, // length of "test"
3383            b't',
3384            b'e',
3385            b's',
3386            b't',
3387        ];
3388        assert_eq!(result, expected);
3389    }
3390}