datex_core/compiler/
mod.rs

1use crate::ast::assignment_operation::AssignmentOperator;
2use crate::ast::binding::VariableId;
3use crate::compiler::error::CompilerError;
4use crate::global::dxb_block::DXBBlock;
5use crate::global::protocol_structures::block_header::BlockHeader;
6use crate::global::protocol_structures::encrypted_header::EncryptedHeader;
7use crate::global::protocol_structures::routing_header::RoutingHeader;
8
9use crate::ast::{DatexExpression, DatexScriptParser, VariableKind, parse};
10use crate::compiler::context::{CompilationContext, VirtualSlot};
11use crate::compiler::metadata::CompileMetadata;
12use crate::compiler::precompiler::{
13    AstMetadata, AstWithMetadata, VariableMetadata, precompile_ast,
14};
15use crate::compiler::scope::CompilationScope;
16use crate::compiler::type_compiler::compile_type_expression;
17use crate::global::instruction_codes::InstructionCode;
18use crate::global::slots::InternalSlot;
19use crate::libs::core::CoreLibPointerId;
20use crate::values::core_values::decimal::Decimal;
21use crate::values::pointer::PointerAddress;
22use crate::values::value_container::ValueContainer;
23use datex_core::ast::Slot;
24use log::info;
25use std::cell::RefCell;
26use std::rc::Rc;
27
28pub mod context;
29pub mod error;
30pub mod metadata;
31mod precompiler;
32pub mod scope;
33mod type_compiler;
34mod type_inference;
35
36#[derive(Clone, Default)]
37pub struct CompileOptions<'a> {
38    pub parser: Option<&'a DatexScriptParser<'a>>,
39    pub compile_scope: CompilationScope,
40}
41
42impl CompileOptions<'_> {
43    pub fn new_with_scope(compile_scope: CompilationScope) -> Self {
44        CompileOptions {
45            parser: None,
46            compile_scope,
47        }
48    }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum StaticValueOrDXB {
53    StaticValue(Option<ValueContainer>),
54    Dxb(Vec<u8>),
55}
56
57impl From<Vec<u8>> for StaticValueOrDXB {
58    fn from(dxb: Vec<u8>) -> Self {
59        StaticValueOrDXB::Dxb(dxb)
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Copy)]
64pub enum VariableModel {
65    /// A variable that is declared once and never reassigned afterward
66    /// e.g. `const a = 42;`
67    Constant,
68    /// A variable that can be reassigned by updating the slot value
69    /// e.g. `var a = 42; a = 69;`
70    VariableSlot,
71    /// A variable that can be reassigned by updating a reference value. The slot always point to this reference.
72    /// When variables are transferred across realms, `VariableReference` is used for `var` variables instead of `VariableSlot`.
73    /// e.g. `var a = 42; x :: (a)
74    VariableReference,
75}
76
77impl From<VariableRepresentation> for VariableModel {
78    fn from(value: VariableRepresentation) -> Self {
79        match value {
80            VariableRepresentation::Constant(_) => VariableModel::Constant,
81            VariableRepresentation::VariableSlot(_) => {
82                VariableModel::VariableSlot
83            }
84            VariableRepresentation::VariableReference { .. } => {
85                VariableModel::VariableReference
86            }
87        }
88    }
89}
90
91impl VariableModel {
92    /// Determines the variable model based on the variable kind and metadata.
93    pub fn infer(
94        variable_kind: VariableKind,
95        variable_metadata: Option<VariableMetadata>,
96        is_end_of_source_text: bool,
97    ) -> Self {
98        // const variables are always constant
99        if variable_kind == VariableKind::Const {
100            VariableModel::Constant
101        }
102        // for cross-realm variables, we always use VariableReference
103        // if we don't know the full source text yet (e.g. in a repl), we
104        // must fall back to VariableReference, because we cannot determine if
105        // the variable will be transferred across realms later
106        else if variable_metadata.is_none()
107            || variable_metadata.unwrap().is_cross_realm
108            || !is_end_of_source_text
109        {
110            VariableModel::VariableReference
111        }
112        // otherwise, we use VariableSlot (default for `var` variables)
113        else {
114            VariableModel::VariableSlot
115        }
116    }
117
118    pub fn infer_from_ast_metadata_and_type(
119        ast_metadata: &AstMetadata,
120        variable_id: Option<VariableId>,
121        variable_kind: VariableKind,
122        is_end_of_source_text: bool,
123    ) -> Self {
124        let variable_metadata =
125            variable_id.and_then(|id| ast_metadata.variable_metadata(id));
126        Self::infer(
127            variable_kind,
128            variable_metadata.cloned(),
129            is_end_of_source_text,
130        )
131    }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Copy)]
135pub enum VariableRepresentation {
136    Constant(VirtualSlot),
137    VariableSlot(VirtualSlot),
138    VariableReference {
139        /// The slot that contains the reference that is used as the variable
140        variable_slot: VirtualSlot,
141        /// The slot that contains the actual value container used in the script (Note: the value container may also be a reference)
142        container_slot: VirtualSlot,
143    },
144}
145
146/// Represents a variable in the DATEX script.
147#[derive(Debug, Clone)]
148pub struct Variable {
149    pub name: String,
150    pub kind: VariableKind,
151    pub representation: VariableRepresentation,
152}
153
154impl Variable {
155    pub fn new_const(name: String, slot: VirtualSlot) -> Self {
156        Variable {
157            name,
158            kind: VariableKind::Const,
159            representation: VariableRepresentation::Constant(slot),
160        }
161    }
162
163    pub fn new_variable_slot(
164        name: String,
165        kind: VariableKind,
166        slot: VirtualSlot,
167    ) -> Self {
168        Variable {
169            name,
170            kind,
171            representation: VariableRepresentation::VariableSlot(slot),
172        }
173    }
174
175    pub fn new_variable_reference(
176        name: String,
177        kind: VariableKind,
178        variable_slot: VirtualSlot,
179        container_slot: VirtualSlot,
180    ) -> Self {
181        Variable {
182            name,
183            kind,
184            representation: VariableRepresentation::VariableReference {
185                variable_slot,
186                container_slot,
187            },
188        }
189    }
190
191    pub fn slots(&self) -> Vec<VirtualSlot> {
192        match &self.representation {
193            VariableRepresentation::Constant(slot) => vec![*slot],
194            VariableRepresentation::VariableSlot(slot) => vec![*slot],
195            VariableRepresentation::VariableReference {
196                variable_slot,
197                container_slot,
198            } => {
199                vec![*variable_slot, *container_slot]
200            }
201        }
202    }
203}
204
205/// Compiles a DATEX script text into a single DXB block including routing and block headers.
206/// This function is used to create a block that can be sent over the network.
207pub fn compile_block(datex_script: &str) -> Result<Vec<u8>, CompilerError> {
208    let (body, _) = compile_script(datex_script, CompileOptions::default())?;
209
210    let routing_header = RoutingHeader::default();
211
212    let block_header = BlockHeader::default();
213    let encrypted_header = EncryptedHeader::default();
214
215    let block =
216        DXBBlock::new(routing_header, block_header, encrypted_header, body);
217
218    let bytes = block
219        .to_bytes()
220        .map_err(CompilerError::SerializationError)?;
221    Ok(bytes)
222}
223
224/// Compiles a DATEX script text into a DXB body
225pub fn compile_script<'a>(
226    datex_script: &'a str,
227    options: CompileOptions<'a>,
228) -> Result<(Vec<u8>, CompilationScope), CompilerError> {
229    compile_template(datex_script, &[], options)
230}
231
232/// Directly extracts a static value from a DATEX script as a `ValueContainer`.
233/// This only works if the script does not contain any dynamic values or operations.
234/// All JSON-files can be compiled to static values, but not all DATEX scripts.
235pub fn extract_static_value_from_script(
236    datex_script: &str,
237) -> Result<Option<ValueContainer>, CompilerError> {
238    let res = parse(datex_script)?;
239    extract_static_value_from_ast(res).map(Some)
240}
241
242/// Compiles a DATEX script template text with inserted values into a DXB body
243/// The value containers are passed by reference
244pub fn compile_template_with_refs<'a>(
245    datex_script: &'a str,
246    inserted_values: &[&ValueContainer],
247    options: CompileOptions<'a>,
248) -> Result<(Vec<u8>, CompilationScope), CompilerError> {
249    compile_template_or_return_static_value_with_refs(
250        datex_script,
251        inserted_values,
252        false,
253        options,
254    )
255    .map(|result| match result.0 {
256        StaticValueOrDXB::StaticValue(_) => unreachable!(),
257        StaticValueOrDXB::Dxb(dxb) => (dxb, result.1),
258    })
259}
260
261/// Compiles a DATEX script template text with inserted values into a DXB body
262/// If the script does not contain any dynamic values or operations, the static result value is
263/// directly returned instead of the DXB body.
264pub fn compile_script_or_return_static_value<'a>(
265    datex_script: &'a str,
266    options: CompileOptions<'a>,
267) -> Result<(StaticValueOrDXB, CompilationScope), CompilerError> {
268    compile_template_or_return_static_value_with_refs(
269        datex_script,
270        &[],
271        true,
272        options,
273    )
274}
275/// Compiles a DATEX script template text with inserted values into a DXB body
276pub fn compile_template_or_return_static_value_with_refs<'a>(
277    datex_script: &'a str,
278    inserted_values: &[&ValueContainer],
279    return_static_value: bool,
280    options: CompileOptions<'a>,
281) -> Result<(StaticValueOrDXB, CompilationScope), CompilerError> {
282    // shortcut if datex_script is "?" - call compile_value directly
283    if datex_script == "?" {
284        if inserted_values.len() != 1 {
285            return Err(CompilerError::InvalidPlaceholderCount);
286        }
287        let result =
288            compile_value(inserted_values[0]).map(StaticValueOrDXB::from)?;
289        return Ok((result, options.compile_scope));
290    }
291
292    let ast = parse(datex_script)?;
293
294    let buffer = RefCell::new(Vec::with_capacity(256));
295    let compilation_context = CompilationContext::new(
296        buffer,
297        inserted_values,
298        options.compile_scope.once,
299    );
300    let scope =
301        compile_ast(&compilation_context, ast.clone(), options.compile_scope)?;
302    if return_static_value {
303        if !*compilation_context.has_non_static_value.borrow() {
304            if let Ok(value) = ValueContainer::try_from(&ast) {
305                return Ok((
306                    StaticValueOrDXB::StaticValue(Some(value.clone())),
307                    scope,
308                ));
309            }
310            Ok((StaticValueOrDXB::StaticValue(None), scope))
311        } else {
312            // return DXB body
313            Ok((
314                StaticValueOrDXB::Dxb(compilation_context.buffer.take()),
315                scope,
316            ))
317        }
318    } else {
319        // return DXB body
320        Ok((
321            StaticValueOrDXB::Dxb(compilation_context.buffer.take()),
322            scope,
323        ))
324    }
325}
326
327/// Compiles a DATEX script template text with inserted values into a DXB body
328pub fn compile_template<'a>(
329    datex_script: &'a str,
330    inserted_values: &[ValueContainer],
331    options: CompileOptions<'a>,
332) -> Result<(Vec<u8>, CompilationScope), CompilerError> {
333    compile_template_with_refs(
334        datex_script,
335        &inserted_values.iter().collect::<Vec<_>>(),
336        options,
337    )
338}
339
340pub fn compile_value(value: &ValueContainer) -> Result<Vec<u8>, CompilerError> {
341    let buffer = RefCell::new(Vec::with_capacity(256));
342    let compilation_scope = CompilationContext::new(buffer, &[], true);
343
344    compilation_scope.insert_value_container(value);
345
346    Ok(compilation_scope.buffer.take())
347}
348
349/// Tries to extract a static value from a DATEX expression AST.
350/// If the expression is not a static value (e.g., contains a placeholder or dynamic operation),
351/// it returns an error.
352fn extract_static_value_from_ast(
353    ast: DatexExpression,
354) -> Result<ValueContainer, CompilerError> {
355    if let DatexExpression::Placeholder = ast {
356        return Err(CompilerError::NonStaticValue);
357    }
358    ValueContainer::try_from(&ast).map_err(|_| CompilerError::NonStaticValue)
359}
360
361/// Macro for compiling a DATEX script template text with inserted values into a DXB body,
362/// behaves like the format! macro.
363/// Example:
364/// ```
365/// use datex_core::compile;
366/// compile!("4 + ?", 42);
367/// compile!("? + ?", 1, 2);
368#[macro_export]
369macro_rules! compile {
370    ($fmt:literal $(, $arg:expr )* $(,)?) => {
371        {
372            let script: &str = $fmt.into();
373            let values: &[$crate::values::value_container::ValueContainer] = &[$($arg.into()),*];
374
375            $crate::compiler::compile_template(&script, values, $crate::compiler::CompileOptions::default())
376        }
377    }
378}
379
380pub fn compile_ast(
381    compilation_context: &CompilationContext,
382    ast: DatexExpression,
383    mut scope: CompilationScope,
384) -> Result<CompilationScope, CompilerError> {
385    // if once is set to true in already used, return error
386    if scope.once {
387        if scope.was_used {
388            return Err(CompilerError::OnceScopeUsedMultipleTimes);
389        }
390        // set was_used to true
391        scope.was_used = true;
392    }
393    let ast_with_metadata =
394        if let Some(precompiler_data) = &scope.precompiler_data {
395            // precompile the AST, adding metadata for variables etc.
396            precompile_ast(
397                ast,
398                precompiler_data.ast_metadata.clone(),
399                &mut precompiler_data.precompiler_scope_stack.borrow_mut(),
400            )?
401        } else {
402            // if no precompiler data, just use the AST with default metadata
403            AstWithMetadata::new_without_metadata(ast)
404        };
405
406    compile_ast_with_metadata(compilation_context, ast_with_metadata, scope)
407}
408
409pub fn compile_ast_with_metadata(
410    compilation_context: &CompilationContext,
411    ast_with_metadata: AstWithMetadata,
412    scope: CompilationScope,
413) -> Result<CompilationScope, CompilerError> {
414    let scope = compile_expression(
415        compilation_context,
416        ast_with_metadata,
417        CompileMetadata::outer(),
418        scope,
419    )?;
420
421    // handle scope virtual addr mapping
422    compilation_context.remap_virtual_slots();
423    Ok(scope)
424}
425
426fn compile_expression(
427    compilation_context: &CompilationContext,
428    ast_with_metadata: AstWithMetadata,
429    meta: CompileMetadata,
430    mut scope: CompilationScope,
431) -> Result<CompilationScope, CompilerError> {
432    let metadata = ast_with_metadata.metadata;
433    match ast_with_metadata.ast {
434        DatexExpression::Integer(int) => {
435            compilation_context
436                .insert_encoded_integer(&int.to_smallest_fitting());
437        }
438        DatexExpression::TypedInteger(typed_int) => {
439            compilation_context.insert_typed_integer(&typed_int);
440        }
441        DatexExpression::Decimal(decimal) => match &decimal {
442            Decimal::Finite(big_decimal) if big_decimal.is_integer() => {
443                if let Some(int) = big_decimal.to_i16() {
444                    compilation_context.insert_float_as_i16(int);
445                } else if let Some(int) = big_decimal.to_i32() {
446                    compilation_context.insert_float_as_i32(int);
447                } else {
448                    compilation_context.insert_decimal(&decimal);
449                }
450            }
451            _ => {
452                compilation_context.insert_decimal(&decimal);
453            }
454        },
455        DatexExpression::TypedDecimal(typed_decimal) => {
456            compilation_context.insert_typed_decimal(&typed_decimal);
457        }
458        DatexExpression::Text(text) => {
459            compilation_context.insert_text(&text);
460        }
461        DatexExpression::Boolean(boolean) => {
462            compilation_context.insert_boolean(boolean);
463        }
464        DatexExpression::Endpoint(endpoint) => {
465            compilation_context.insert_endpoint(&endpoint);
466        }
467        DatexExpression::Null => {
468            compilation_context.append_instruction_code(InstructionCode::NULL);
469        }
470        DatexExpression::List(list) => {
471            compilation_context
472                .append_instruction_code(InstructionCode::LIST_START);
473            for item in list {
474                scope = compile_expression(
475                    compilation_context,
476                    AstWithMetadata::new(item, &metadata),
477                    CompileMetadata::default(),
478                    scope,
479                )?;
480            }
481            compilation_context
482                .append_instruction_code(InstructionCode::SCOPE_END);
483        }
484        DatexExpression::Map(map) => {
485            // TODO #434: Handle string keyed maps (structs)
486            compilation_context
487                .append_instruction_code(InstructionCode::MAP_START);
488            for (key, value) in map {
489                scope = compile_key_value_entry(
490                    compilation_context,
491                    key,
492                    value,
493                    &metadata,
494                    scope,
495                )?;
496            }
497            compilation_context
498                .append_instruction_code(InstructionCode::SCOPE_END);
499        }
500        DatexExpression::Placeholder => {
501            compilation_context.insert_value_container(
502                compilation_context
503                    .inserted_values
504                    .borrow()
505                    .get(compilation_context.inserted_value_index.get())
506                    .unwrap(),
507            );
508            compilation_context.inserted_value_index.update(|x| x + 1);
509        }
510
511        // statements
512        DatexExpression::Statements(mut statements) => {
513            compilation_context.mark_has_non_static_value();
514            // if single statement and not terminated, just compile the expression
515            if statements.len() == 1 && !statements[0].is_terminated {
516                scope = compile_expression(
517                    compilation_context,
518                    AstWithMetadata::new(
519                        statements.remove(0).expression,
520                        &metadata,
521                    ),
522                    CompileMetadata::default(),
523                    scope,
524                )?;
525            } else {
526                // if not outer context, new scope
527                let mut child_scope = if !meta.is_outer_context() {
528                    compilation_context
529                        .append_instruction_code(InstructionCode::SCOPE_START);
530                    scope.push()
531                } else {
532                    scope
533                };
534                for statement in statements {
535                    child_scope = compile_expression(
536                        compilation_context,
537                        AstWithMetadata::new(statement.expression, &metadata),
538                        CompileMetadata::default(),
539                        child_scope,
540                    )?;
541                    // if statement is terminated, append close and store
542                    if statement.is_terminated {
543                        compilation_context.append_instruction_code(
544                            InstructionCode::CLOSE_AND_STORE,
545                        );
546                    }
547                }
548                if !meta.is_outer_context() {
549                    let scope_data = child_scope
550                        .pop()
551                        .ok_or(CompilerError::ScopePopError)?;
552                    scope = scope_data.0; // set parent scope
553                    // drop all slot addresses that were allocated in this scope
554                    for slot_address in scope_data.1 {
555                        compilation_context.append_instruction_code(
556                            InstructionCode::DROP_SLOT,
557                        );
558                        // insert virtual slot address for dropping
559                        compilation_context
560                            .insert_virtual_slot_address(slot_address);
561                    }
562                    compilation_context
563                        .append_instruction_code(InstructionCode::SCOPE_END);
564                } else {
565                    scope = child_scope;
566                }
567            }
568        }
569
570        // unary operations (negation, not, etc.)
571        DatexExpression::UnaryOperation(operator, expr) => {
572            compilation_context
573                .append_instruction_code(InstructionCode::from(&operator));
574            scope = compile_expression(
575                compilation_context,
576                AstWithMetadata::new(*expr, &metadata),
577                CompileMetadata::default(),
578                scope,
579            )?;
580        }
581
582        // operations (add, subtract, multiply, divide, etc.)
583        DatexExpression::BinaryOperation(operator, a, b, _) => {
584            compilation_context.mark_has_non_static_value();
585            // append binary code for operation if not already current binary operator
586            compilation_context
587                .append_instruction_code(InstructionCode::from(&operator));
588            scope = compile_expression(
589                compilation_context,
590                AstWithMetadata::new(*a, &metadata),
591                CompileMetadata::default(),
592                scope,
593            )?;
594            scope = compile_expression(
595                compilation_context,
596                AstWithMetadata::new(*b, &metadata),
597                CompileMetadata::default(),
598                scope,
599            )?;
600        }
601
602        // comparisons (e.g., equal, not equal, greater than, etc.)
603        DatexExpression::ComparisonOperation(operator, a, b) => {
604            compilation_context.mark_has_non_static_value();
605            // append binary code for operation if not already current binary operator
606            compilation_context
607                .append_instruction_code(InstructionCode::from(&operator));
608            scope = compile_expression(
609                compilation_context,
610                AstWithMetadata::new(*a, &metadata),
611                CompileMetadata::default(),
612                scope,
613            )?;
614            scope = compile_expression(
615                compilation_context,
616                AstWithMetadata::new(*b, &metadata),
617                CompileMetadata::default(),
618                scope,
619            )?;
620        }
621
622        // apply
623        DatexExpression::ApplyChain(val, operands) => {
624            compilation_context.mark_has_non_static_value();
625            // TODO #150
626        }
627
628        // variables
629        // declaration
630        DatexExpression::VariableDeclaration {
631            id,
632            name,
633            kind,
634            type_annotation,
635            init_expression: value,
636        } => {
637            compilation_context.mark_has_non_static_value();
638
639            // allocate new slot for variable
640            let virtual_slot_addr = scope.get_next_virtual_slot();
641            compilation_context
642                .append_instruction_code(InstructionCode::ALLOCATE_SLOT);
643            compilation_context.insert_virtual_slot_address(
644                VirtualSlot::local(virtual_slot_addr),
645            );
646            // compile expression
647            scope = compile_expression(
648                compilation_context,
649                AstWithMetadata::new(*value, &metadata),
650                CompileMetadata::default(),
651                scope,
652            )?;
653
654            let variable_model =
655                VariableModel::infer_from_ast_metadata_and_type(
656                    &metadata.borrow(),
657                    id,
658                    kind,
659                    compilation_context.is_end_of_source_text,
660                );
661            info!("variable model for {name}: {variable_model:?}");
662
663            // create new variable depending on the model
664            let variable = match variable_model {
665                VariableModel::VariableReference => {
666                    // scope end
667                    compilation_context
668                        .append_instruction_code(InstructionCode::SCOPE_END);
669                    // allocate an additional slot with a reference to the variable
670                    let virtual_slot_addr_for_var =
671                        scope.get_next_virtual_slot();
672                    compilation_context.append_instruction_code(
673                        InstructionCode::ALLOCATE_SLOT,
674                    );
675                    compilation_context.insert_virtual_slot_address(
676                        VirtualSlot::local(virtual_slot_addr_for_var),
677                    );
678                    // indirect reference to the variable
679                    compilation_context
680                        .append_instruction_code(InstructionCode::CREATE_REF);
681                    // append binary code to load variable
682                    compilation_context
683                        .append_instruction_code(InstructionCode::GET_SLOT);
684                    compilation_context.insert_virtual_slot_address(
685                        VirtualSlot::local(virtual_slot_addr),
686                    );
687
688                    Variable::new_variable_reference(
689                        name.clone(),
690                        kind,
691                        VirtualSlot::local(virtual_slot_addr_for_var),
692                        VirtualSlot::local(virtual_slot_addr),
693                    )
694                }
695                VariableModel::Constant => Variable::new_const(
696                    name.clone(),
697                    VirtualSlot::local(virtual_slot_addr),
698                ),
699                VariableModel::VariableSlot => Variable::new_variable_slot(
700                    name.clone(),
701                    kind,
702                    VirtualSlot::local(virtual_slot_addr),
703                ),
704            };
705
706            scope.register_variable_slot(variable);
707
708            compilation_context
709                .append_instruction_code(InstructionCode::SCOPE_END);
710        }
711
712        DatexExpression::GetReference(address) => {
713            compilation_context.mark_has_non_static_value();
714            compilation_context.insert_get_ref(address);
715        }
716
717        // assignment
718        DatexExpression::VariableAssignment(operator, id, name, expression) => {
719            compilation_context.mark_has_non_static_value();
720            // get variable slot address
721            let (virtual_slot, kind) = scope
722                .resolve_variable_name_to_virtual_slot(&name)
723                .ok_or_else(|| {
724                    CompilerError::UndeclaredVariable(name.clone())
725                })?;
726
727            // if const, return error
728            if kind == VariableKind::Const {
729                return Err(CompilerError::AssignmentToConst(name.clone()));
730            }
731
732            match operator {
733                AssignmentOperator::Assign => {
734                    // append binary code to load variable
735                    info!(
736                        "append variable virtual slot: {virtual_slot:?}, name: {name}"
737                    );
738                    compilation_context
739                        .append_instruction_code(InstructionCode::SET_SLOT);
740                    // compilation_context.append_instruction_code(
741                    //     InstructionCode::from(&operator),
742                    // );
743                }
744                AssignmentOperator::AddAssign
745                | AssignmentOperator::SubtractAssign => {
746                    // TODO #435: handle mut type
747                    // // if immutable reference, return error
748                    // if mut_type == Some(ReferenceMutability::Immutable) {
749                    //     return Err(
750                    //         CompilerError::AssignmentToImmutableReference(
751                    //             name.clone(),
752                    //         ),
753                    //     );
754                    // }
755                    // // if immutable value, return error
756                    // else if mut_type == None {
757                    //     return Err(CompilerError::AssignmentToImmutableValue(
758                    //         name.clone(),
759                    //     ));
760                    // }
761                    compilation_context
762                        .append_instruction_code(InstructionCode::SET_SLOT);
763                    compilation_context.append_instruction_code(
764                        InstructionCode::from(&operator),
765                    );
766                }
767                op => todo!("#436 Handle assignment operator: {op:?}"),
768            }
769
770            compilation_context.insert_virtual_slot_address(virtual_slot);
771            // compile expression
772            scope = compile_expression(
773                compilation_context,
774                AstWithMetadata::new(*expression, &metadata),
775                CompileMetadata::default(),
776                scope,
777            )?;
778            // close assignment scope
779            compilation_context
780                .append_instruction_code(InstructionCode::SCOPE_END);
781        }
782
783        DatexExpression::DerefAssignment {
784            operator,
785            deref_count,
786            deref_expression,
787            assigned_expression,
788        } => {
789            compilation_context.mark_has_non_static_value();
790
791            compilation_context
792                .append_instruction_code(InstructionCode::ASSIGN_TO_REF);
793
794            compilation_context
795                .append_instruction_code(InstructionCode::from(&operator));
796
797            // "*x" must not be dereferenced, x is already the relevant reference that is modified
798            for _ in 0..deref_count - 1 {
799                compilation_context
800                    .append_instruction_code(InstructionCode::DEREF);
801            }
802
803            // compile deref expression
804            scope = compile_expression(
805                compilation_context,
806                AstWithMetadata::new(*deref_expression, &metadata),
807                CompileMetadata::default(),
808                scope,
809            )?;
810
811            for _ in 0..deref_count - 1 {
812                compilation_context
813                    .append_instruction_code(InstructionCode::SCOPE_END);
814            }
815
816            // compile assigned expression
817            scope = compile_expression(
818                compilation_context,
819                AstWithMetadata::new(*assigned_expression, &metadata),
820                CompileMetadata::default(),
821                scope,
822            )?;
823
824            // close assignment scope
825            compilation_context
826                .append_instruction_code(InstructionCode::SCOPE_END);
827        }
828
829        // variable access
830        DatexExpression::Variable(id, name) => {
831            compilation_context.mark_has_non_static_value();
832            // get variable slot address
833            let (virtual_slot, ..) = scope
834                .resolve_variable_name_to_virtual_slot(&name)
835                .ok_or_else(|| {
836                    CompilerError::UndeclaredVariable(name.clone())
837                })?;
838            // append binary code to load variable
839            compilation_context
840                .append_instruction_code(InstructionCode::GET_SLOT);
841            compilation_context.insert_virtual_slot_address(virtual_slot);
842        }
843
844        // remote execution
845        DatexExpression::RemoteExecution(caller, script) => {
846            compilation_context.mark_has_non_static_value();
847
848            // insert remote execution code
849            compilation_context
850                .append_instruction_code(InstructionCode::REMOTE_EXECUTION);
851            // insert compiled caller expression
852            scope = compile_expression(
853                compilation_context,
854                AstWithMetadata::new(*caller, &metadata),
855                CompileMetadata::default(),
856                scope,
857            )?;
858
859            // compile remote execution block
860            let execution_block_ctx = CompilationContext::new(
861                RefCell::new(Vec::with_capacity(256)),
862                &[],
863                true,
864            );
865            let external_scope = compile_ast_with_metadata(
866                &execution_block_ctx,
867                AstWithMetadata::new(*script, &metadata),
868                CompilationScope::new_with_external_parent_scope(scope),
869            )?;
870            // reset to current scope
871            scope = external_scope
872                .pop_external()
873                .ok_or_else(|| CompilerError::ScopePopError)?;
874
875            let external_slots = execution_block_ctx.external_slots();
876            // start block
877            compilation_context
878                .append_instruction_code(InstructionCode::EXECUTION_BLOCK);
879            // set block size (len of compilation_context.buffer)
880            compilation_context
881                .append_u32(execution_block_ctx.buffer.borrow().len() as u32);
882            // set injected slot count
883            compilation_context.append_u32(external_slots.len() as u32);
884            for slot in external_slots {
885                compilation_context.insert_virtual_slot_address(slot.upgrade());
886            }
887
888            // insert block body (compilation_context.buffer)
889            compilation_context
890                .append_buffer(&execution_block_ctx.buffer.borrow())
891        }
892
893        // named slot
894        DatexExpression::Slot(Slot::Named(name)) => {
895            match name.as_str() {
896                "endpoint" => {
897                    compilation_context
898                        .append_instruction_code(InstructionCode::GET_SLOT);
899                    compilation_context
900                        .append_u32(InternalSlot::ENDPOINT as u32);
901                }
902                "core" => compilation_context.insert_get_ref(
903                    PointerAddress::from(CoreLibPointerId::Core),
904                ),
905                _ => {
906                    // invalid slot name
907                    return Err(CompilerError::InvalidSlotName(name.clone()));
908                }
909            }
910        }
911
912        // pointer address
913        DatexExpression::PointerAddress(address) => {
914            compilation_context.insert_get_ref(address);
915        }
916
917        // refs
918        DatexExpression::CreateRef(expression) => {
919            compilation_context.mark_has_non_static_value();
920            compilation_context
921                .append_instruction_code(InstructionCode::CREATE_REF);
922            scope = compile_expression(
923                compilation_context,
924                AstWithMetadata::new(*expression, &metadata),
925                CompileMetadata::default(),
926                scope,
927            )?;
928        }
929        DatexExpression::CreateRefMut(expression) => {
930            compilation_context.mark_has_non_static_value();
931            compilation_context
932                .append_instruction_code(InstructionCode::CREATE_REF_MUT);
933            scope = compile_expression(
934                compilation_context,
935                AstWithMetadata::new(*expression, &metadata),
936                CompileMetadata::default(),
937                scope,
938            )?;
939        }
940        DatexExpression::CreateRefFinal(expression) => {
941            compilation_context.mark_has_non_static_value();
942            compilation_context
943                .append_instruction_code(InstructionCode::CREATE_REF_FINAL);
944            scope = compile_expression(
945                compilation_context,
946                AstWithMetadata::new(*expression, &metadata),
947                CompileMetadata::default(),
948                scope,
949            )?;
950        }
951
952        DatexExpression::Type(type_expression) => {
953            compilation_context
954                .append_instruction_code(InstructionCode::TYPE_EXPRESSION);
955            scope = compile_type_expression(
956                compilation_context,
957                &type_expression,
958                metadata,
959                scope,
960            )?;
961        }
962
963        DatexExpression::Deref(expression) => {
964            compilation_context.mark_has_non_static_value();
965            compilation_context.append_instruction_code(InstructionCode::DEREF);
966            scope = compile_expression(
967                compilation_context,
968                AstWithMetadata::new(*expression, &metadata),
969                CompileMetadata::default(),
970                scope,
971            )?;
972            compilation_context
973                .append_instruction_code(InstructionCode::SCOPE_END);
974        }
975
976        _ => {
977            return Err(CompilerError::UnexpectedTerm(Box::new(
978                ast_with_metadata.ast,
979            )));
980        }
981    }
982
983    Ok(scope)
984}
985
986fn compile_key_value_entry(
987    compilation_scope: &CompilationContext,
988    key: DatexExpression,
989    value: DatexExpression,
990    metadata: &Rc<RefCell<AstMetadata>>,
991    mut scope: CompilationScope,
992) -> Result<CompilationScope, CompilerError> {
993    match key {
994        // text -> insert key string
995        DatexExpression::Text(text) => {
996            compilation_scope.insert_key_string(&text);
997        }
998        // other -> insert key as dynamic
999        _ => {
1000            compilation_scope
1001                .append_instruction_code(InstructionCode::KEY_VALUE_DYNAMIC);
1002            scope = compile_expression(
1003                compilation_scope,
1004                AstWithMetadata::new(key, metadata),
1005                CompileMetadata::default(),
1006                scope,
1007            )?;
1008        }
1009    };
1010    // insert value
1011    scope = compile_expression(
1012        compilation_scope,
1013        AstWithMetadata::new(value, metadata),
1014        CompileMetadata::default(),
1015        scope,
1016    )?;
1017    Ok(scope)
1018}
1019
1020#[cfg(test)]
1021pub mod tests {
1022    use super::{
1023        CompilationContext, CompilationScope, CompileOptions, StaticValueOrDXB,
1024        compile_ast, compile_script, compile_script_or_return_static_value,
1025        compile_template,
1026    };
1027    use std::assert_matches::assert_matches;
1028    use std::cell::RefCell;
1029    use std::io::Read;
1030    use std::vec;
1031
1032    use crate::ast::parse;
1033    use crate::global::type_instruction_codes::TypeSpaceInstructionCode;
1034    use crate::libs::core::CoreLibPointerId;
1035    use crate::values::core_values::integer::Integer;
1036    use crate::values::pointer::PointerAddress;
1037    use crate::{
1038        global::instruction_codes::InstructionCode, logger::init_logger_debug,
1039    };
1040    use datex_core::compiler::error::CompilerError;
1041    use log::*;
1042
1043    fn compile_and_log(datex_script: &str) -> Vec<u8> {
1044        init_logger_debug();
1045        let (result, _) =
1046            compile_script(datex_script, CompileOptions::default()).unwrap();
1047        info!(
1048            "{:?}",
1049            result
1050                .iter()
1051                .map(|x| InstructionCode::try_from(*x).map(|x| x.to_string()))
1052                .map(|x| x.unwrap_or_else(|_| "Unknown".to_string()))
1053                .collect::<Vec<_>>()
1054        );
1055        result
1056    }
1057
1058    fn get_compilation_scope(script: &str) -> CompilationContext {
1059        let ast = parse(script);
1060        let ast = ast.unwrap();
1061        let buffer = RefCell::new(Vec::with_capacity(256));
1062        let compilation_scope = CompilationContext::new(buffer, &[], true);
1063        compile_ast(&compilation_scope, ast, CompilationScope::default())
1064            .unwrap();
1065        compilation_scope
1066    }
1067
1068    #[test]
1069    fn simple_multiplication() {
1070        init_logger_debug();
1071
1072        let lhs: u8 = 1;
1073        let rhs: u8 = 2;
1074        let datex_script = format!("{lhs} * {rhs}"); // 1 * 2
1075        let result = compile_and_log(&datex_script);
1076        assert_eq!(
1077            result,
1078            vec![
1079                InstructionCode::MULTIPLY.into(),
1080                InstructionCode::INT_8.into(),
1081                lhs,
1082                InstructionCode::INT_8.into(),
1083                rhs,
1084            ]
1085        );
1086    }
1087
1088    #[test]
1089    fn simple_multiplication_close() {
1090        init_logger_debug();
1091
1092        let lhs: u8 = 1;
1093        let rhs: u8 = 2;
1094        let datex_script = format!("{lhs} * {rhs};"); // 1 * 2
1095        let result = compile_and_log(&datex_script);
1096        assert_eq!(
1097            result,
1098            vec![
1099                InstructionCode::MULTIPLY.into(),
1100                InstructionCode::INT_8.into(),
1101                lhs,
1102                InstructionCode::INT_8.into(),
1103                rhs,
1104                InstructionCode::CLOSE_AND_STORE.into()
1105            ]
1106        );
1107    }
1108
1109    #[test]
1110    fn is_operator() {
1111        init_logger_debug();
1112
1113        // TODO #151: compare refs
1114        let datex_script = "1 is 2".to_string();
1115        let result = compile_and_log(&datex_script);
1116        assert_eq!(
1117            result,
1118            vec![
1119                InstructionCode::IS.into(),
1120                InstructionCode::INT_8.into(),
1121                1,
1122                InstructionCode::INT_8.into(),
1123                2
1124            ]
1125        );
1126
1127        let datex_script =
1128            "const a = &mut 42; const b = &mut 69; a is b".to_string(); // a is b
1129        let result = compile_and_log(&datex_script);
1130        assert_eq!(
1131            result,
1132            vec![
1133                // val a = 42;
1134                InstructionCode::ALLOCATE_SLOT.into(),
1135                0,
1136                0,
1137                0,
1138                0,
1139                InstructionCode::CREATE_REF_MUT.into(),
1140                InstructionCode::INT_8.into(),
1141                42,
1142                InstructionCode::SCOPE_END.into(),
1143                InstructionCode::CLOSE_AND_STORE.into(),
1144                // val b = 69;
1145                InstructionCode::ALLOCATE_SLOT.into(),
1146                1,
1147                0,
1148                0,
1149                0,
1150                InstructionCode::CREATE_REF_MUT.into(),
1151                InstructionCode::INT_8.into(),
1152                69,
1153                InstructionCode::SCOPE_END.into(),
1154                InstructionCode::CLOSE_AND_STORE.into(),
1155                // a is b
1156                InstructionCode::IS.into(),
1157                InstructionCode::GET_SLOT.into(),
1158                0,
1159                0,
1160                0,
1161                0, // slot address for a
1162                InstructionCode::GET_SLOT.into(),
1163                1,
1164                0,
1165                0,
1166                0, // slot address for b
1167            ]
1168        );
1169    }
1170
1171    #[test]
1172    fn equality_operator() {
1173        init_logger_debug();
1174
1175        let lhs: u8 = 1;
1176        let rhs: u8 = 2;
1177        let datex_script = format!("{lhs} == {rhs}"); // 1 == 2
1178        let result = compile_and_log(&datex_script);
1179        assert_eq!(
1180            result,
1181            vec![
1182                InstructionCode::STRUCTURAL_EQUAL.into(),
1183                InstructionCode::INT_8.into(),
1184                lhs,
1185                InstructionCode::INT_8.into(),
1186                rhs,
1187            ]
1188        );
1189
1190        let datex_script = format!("{lhs} === {rhs}"); // 1 === 2
1191        let result = compile_and_log(&datex_script);
1192        assert_eq!(
1193            result,
1194            vec![
1195                InstructionCode::EQUAL.into(),
1196                InstructionCode::INT_8.into(),
1197                lhs,
1198                InstructionCode::INT_8.into(),
1199                rhs,
1200            ]
1201        );
1202
1203        let datex_script = format!("{lhs} != {rhs}"); // 1 != 2
1204        let result = compile_and_log(&datex_script);
1205        assert_eq!(
1206            result,
1207            vec![
1208                InstructionCode::NOT_STRUCTURAL_EQUAL.into(),
1209                InstructionCode::INT_8.into(),
1210                lhs,
1211                InstructionCode::INT_8.into(),
1212                rhs,
1213            ]
1214        );
1215        let datex_script = format!("{lhs} !== {rhs}"); // 1 !== 2
1216        let result = compile_and_log(&datex_script);
1217        assert_eq!(
1218            result,
1219            vec![
1220                InstructionCode::NOT_EQUAL.into(),
1221                InstructionCode::INT_8.into(),
1222                lhs,
1223                InstructionCode::INT_8.into(),
1224                rhs,
1225            ]
1226        );
1227    }
1228
1229    #[test]
1230    fn simple_addition() {
1231        init_logger_debug();
1232
1233        let lhs: u8 = 1;
1234        let rhs: u8 = 2;
1235        let datex_script = format!("{lhs} + {rhs}"); // 1 + 2
1236        let result = compile_and_log(&datex_script);
1237        assert_eq!(
1238            result,
1239            vec![
1240                InstructionCode::ADD.into(),
1241                InstructionCode::INT_8.into(),
1242                lhs,
1243                InstructionCode::INT_8.into(),
1244                rhs
1245            ]
1246        );
1247
1248        let datex_script = format!("{lhs} + {rhs};"); // 1 + 2;
1249        let result = compile_and_log(&datex_script);
1250        assert_eq!(
1251            result,
1252            vec![
1253                InstructionCode::ADD.into(),
1254                InstructionCode::INT_8.into(),
1255                lhs,
1256                InstructionCode::INT_8.into(),
1257                rhs,
1258                InstructionCode::CLOSE_AND_STORE.into()
1259            ]
1260        );
1261    }
1262
1263    #[test]
1264    fn multi_addition() {
1265        init_logger_debug();
1266
1267        let op1: u8 = 1;
1268        let op2: u8 = 2;
1269        let op3: u8 = 3;
1270        let op4: u8 = 4;
1271
1272        let datex_script = format!("{op1} + {op2} + {op3} + {op4}"); // 1 + 2 + 3 + 4
1273        let result = compile_and_log(&datex_script);
1274        assert_eq!(
1275            result,
1276            vec![
1277                InstructionCode::ADD.into(),
1278                InstructionCode::ADD.into(),
1279                InstructionCode::ADD.into(),
1280                InstructionCode::INT_8.into(),
1281                op1,
1282                InstructionCode::INT_8.into(),
1283                op2,
1284                InstructionCode::INT_8.into(),
1285                op3,
1286                InstructionCode::INT_8.into(),
1287                op4,
1288            ]
1289        );
1290    }
1291
1292    #[test]
1293    fn mixed_calculation() {
1294        init_logger_debug();
1295
1296        let op1: u8 = 1;
1297        let op2: u8 = 2;
1298        let op3: u8 = 3;
1299        let op4: u8 = 4;
1300
1301        let datex_script = format!("{op1} * {op2} + {op3} * {op4}"); // 1 + 2 + 3 + 4
1302        let result = compile_and_log(&datex_script);
1303        assert_eq!(
1304            result,
1305            vec![
1306                InstructionCode::ADD.into(),
1307                InstructionCode::MULTIPLY.into(),
1308                InstructionCode::INT_8.into(),
1309                op1,
1310                InstructionCode::INT_8.into(),
1311                op2,
1312                InstructionCode::MULTIPLY.into(),
1313                InstructionCode::INT_8.into(),
1314                op3,
1315                InstructionCode::INT_8.into(),
1316                op4,
1317            ]
1318        );
1319    }
1320
1321    #[test]
1322    fn complex_addition() {
1323        init_logger_debug();
1324
1325        let a: u8 = 1;
1326        let b: u8 = 2;
1327        let c: u8 = 3;
1328        let datex_script = format!("{a} + ({b} + {c})"); // 1 + (2 + 3)
1329        let result = compile_and_log(&datex_script);
1330
1331        // note: scope is automatically collapsed by the parser since this is all the same operation
1332        // TODO #152: we might need to change this to support nested additions, or maybe not if we only allow additions
1333        // of values of the same type?...
1334        assert_eq!(
1335            result,
1336            vec![
1337                InstructionCode::ADD.into(),
1338                InstructionCode::INT_8.into(),
1339                a,
1340                InstructionCode::ADD.into(),
1341                InstructionCode::INT_8.into(),
1342                b,
1343                InstructionCode::INT_8.into(),
1344                c,
1345            ]
1346        );
1347    }
1348
1349    #[test]
1350    fn complex_addition_and_subtraction() {
1351        init_logger_debug();
1352
1353        let a: u8 = 1;
1354        let b: u8 = 2;
1355        let c: u8 = 3;
1356        let datex_script = format!("{a} + ({b} - {c})"); // 1 + (2 - 3)
1357        let result = compile_and_log(&datex_script);
1358        assert_eq!(
1359            result,
1360            vec![
1361                InstructionCode::ADD.into(),
1362                InstructionCode::INT_8.into(),
1363                a,
1364                InstructionCode::SUBTRACT.into(),
1365                InstructionCode::INT_8.into(),
1366                b,
1367                InstructionCode::INT_8.into(),
1368                c,
1369            ]
1370        );
1371    }
1372
1373    #[test]
1374    fn integer_u8() {
1375        init_logger_debug();
1376        let val = 42;
1377        let datex_script = format!("{val}"); // 42
1378        let result = compile_and_log(&datex_script);
1379        assert_eq!(result, vec![InstructionCode::INT_8.into(), val,]);
1380    }
1381
1382    // Test for decimal
1383    #[test]
1384    fn decimal() {
1385        init_logger_debug();
1386        let datex_script = "42.0";
1387        let result = compile_and_log(datex_script);
1388        let bytes = 42_i16.to_le_bytes();
1389
1390        let mut expected: Vec<u8> =
1391            vec![InstructionCode::DECIMAL_AS_INT_16.into()];
1392        expected.extend(bytes);
1393
1394        assert_eq!(result, expected);
1395    }
1396
1397    /// Test for test that is less than 256 characters
1398    #[test]
1399    fn short_text() {
1400        init_logger_debug();
1401        let val = "unyt";
1402        let datex_script = format!("\"{val}\""); // "unyt"
1403        let result = compile_and_log(&datex_script);
1404        let mut expected: Vec<u8> =
1405            vec![InstructionCode::SHORT_TEXT.into(), val.len() as u8];
1406        expected.extend(val.bytes());
1407        assert_eq!(result, expected);
1408    }
1409
1410    // Test empty list
1411    #[test]
1412    fn empty_list() {
1413        init_logger_debug();
1414        // TODO #437: support list constructor (apply on type)
1415        let datex_script = "[]";
1416        // const x = mut 42;
1417        let result = compile_and_log(datex_script);
1418        let expected: Vec<u8> = vec![
1419            InstructionCode::LIST_START.into(),
1420            InstructionCode::SCOPE_END.into(),
1421        ];
1422        assert_eq!(result, expected);
1423    }
1424
1425    // Test list with single element
1426    #[test]
1427    fn single_element_list() {
1428        init_logger_debug();
1429        // TODO #438: support list constructor (apply on type)
1430        let datex_script = "[42]";
1431        let result = compile_and_log(datex_script);
1432        assert_eq!(
1433            result,
1434            vec![
1435                InstructionCode::LIST_START.into(),
1436                InstructionCode::INT_8.into(),
1437                42,
1438                InstructionCode::SCOPE_END.into(),
1439            ]
1440        );
1441    }
1442
1443    // Test list with multiple elements
1444    #[test]
1445    fn multi_element_list() {
1446        init_logger_debug();
1447        let datex_script = "[1, 2, 3]";
1448        let result = compile_and_log(datex_script);
1449        assert_eq!(
1450            result,
1451            vec![
1452                InstructionCode::LIST_START.into(),
1453                InstructionCode::INT_8.into(),
1454                1,
1455                InstructionCode::INT_8.into(),
1456                2,
1457                InstructionCode::INT_8.into(),
1458                3,
1459                InstructionCode::SCOPE_END.into(),
1460            ]
1461        );
1462
1463        // trailing comma
1464        let datex_script = "[1, 2, 3,]";
1465        let result = compile_and_log(datex_script);
1466        assert_eq!(
1467            result,
1468            vec![
1469                InstructionCode::LIST_START.into(),
1470                InstructionCode::INT_8.into(),
1471                1,
1472                InstructionCode::INT_8.into(),
1473                2,
1474                InstructionCode::INT_8.into(),
1475                3,
1476                InstructionCode::SCOPE_END.into(),
1477            ]
1478        );
1479    }
1480
1481    // Test list with expressions inside
1482    #[test]
1483    fn list_with_expressions() {
1484        init_logger_debug();
1485        let datex_script = "[1 + 2, 3 * 4]";
1486        let result = compile_and_log(datex_script);
1487        assert_eq!(
1488            result,
1489            vec![
1490                InstructionCode::LIST_START.into(),
1491                InstructionCode::ADD.into(),
1492                InstructionCode::INT_8.into(),
1493                1,
1494                InstructionCode::INT_8.into(),
1495                2,
1496                InstructionCode::MULTIPLY.into(),
1497                InstructionCode::INT_8.into(),
1498                3,
1499                InstructionCode::INT_8.into(),
1500                4,
1501                InstructionCode::SCOPE_END.into(),
1502            ]
1503        );
1504    }
1505
1506    // Nested lists
1507    #[test]
1508    fn nested_lists() {
1509        init_logger_debug();
1510        let datex_script = "[1, [2, 3], 4]";
1511        let result = compile_and_log(datex_script);
1512        assert_eq!(
1513            result,
1514            vec![
1515                InstructionCode::LIST_START.into(),
1516                InstructionCode::INT_8.into(),
1517                1,
1518                InstructionCode::LIST_START.into(),
1519                InstructionCode::INT_8.into(),
1520                2,
1521                InstructionCode::INT_8.into(),
1522                3,
1523                InstructionCode::SCOPE_END.into(),
1524                InstructionCode::INT_8.into(),
1525                4,
1526                InstructionCode::SCOPE_END.into(),
1527            ]
1528        );
1529    }
1530
1531    // map with text key
1532    #[test]
1533    fn map_with_text_key() {
1534        init_logger_debug();
1535        let datex_script = "{\"key\": 42}";
1536        let result = compile_and_log(datex_script);
1537        let expected = vec![
1538            InstructionCode::MAP_START.into(),
1539            InstructionCode::KEY_VALUE_SHORT_TEXT.into(),
1540            3, // length of "key"
1541            b'k',
1542            b'e',
1543            b'y',
1544            InstructionCode::INT_8.into(),
1545            42,
1546            InstructionCode::SCOPE_END.into(),
1547        ];
1548        assert_eq!(result, expected);
1549    }
1550
1551    // map with integer key
1552    #[test]
1553    fn map_integer_key() {
1554        init_logger_debug();
1555        let datex_script = "{(10): 42}";
1556        let result = compile_and_log(datex_script);
1557        let expected = vec![
1558            InstructionCode::MAP_START.into(),
1559            InstructionCode::KEY_VALUE_DYNAMIC.into(),
1560            InstructionCode::INT_8.into(),
1561            10,
1562            InstructionCode::INT_8.into(),
1563            42,
1564            InstructionCode::SCOPE_END.into(),
1565        ];
1566        assert_eq!(result, expected);
1567    }
1568
1569    // map with long text key (>255 bytes)
1570    #[test]
1571    fn map_with_long_text_key() {
1572        init_logger_debug();
1573        let long_key = "a".repeat(300);
1574        let datex_script = format!("{{\"{long_key}\": 42}}");
1575        let result = compile_and_log(&datex_script);
1576        let mut expected: Vec<u8> = vec![
1577            InstructionCode::MAP_START.into(),
1578            InstructionCode::KEY_VALUE_DYNAMIC.into(),
1579            InstructionCode::TEXT.into(),
1580        ];
1581        expected.extend((long_key.len() as u32).to_le_bytes());
1582        expected.extend(long_key.as_bytes());
1583        expected.extend(vec![
1584            InstructionCode::INT_8.into(),
1585            42,
1586            InstructionCode::SCOPE_END.into(),
1587        ]);
1588        assert_eq!(result, expected);
1589    }
1590
1591    // map with dynamic key (expression)
1592    #[test]
1593    fn map_with_dynamic_key() {
1594        init_logger_debug();
1595        let datex_script = "{(1 + 2): 42}";
1596        let result = compile_and_log(datex_script);
1597        let expected = [
1598            InstructionCode::MAP_START.into(),
1599            InstructionCode::KEY_VALUE_DYNAMIC.into(),
1600            InstructionCode::ADD.into(),
1601            InstructionCode::INT_8.into(),
1602            1,
1603            InstructionCode::INT_8.into(),
1604            2,
1605            InstructionCode::INT_8.into(),
1606            42,
1607            InstructionCode::SCOPE_END.into(),
1608        ];
1609        assert_eq!(result, expected);
1610    }
1611
1612    // map with multiple keys (text, integer, expression)
1613    #[test]
1614    fn map_with_multiple_keys() {
1615        init_logger_debug();
1616        let datex_script = "{key: 42, (4): 43, (1 + 2): 44}";
1617        let result = compile_and_log(datex_script);
1618        let expected = vec![
1619            InstructionCode::MAP_START.into(),
1620            InstructionCode::KEY_VALUE_SHORT_TEXT.into(),
1621            3, // length of "key"
1622            b'k',
1623            b'e',
1624            b'y',
1625            InstructionCode::INT_8.into(),
1626            42,
1627            InstructionCode::KEY_VALUE_DYNAMIC.into(),
1628            InstructionCode::INT_8.into(),
1629            4,
1630            InstructionCode::INT_8.into(),
1631            43,
1632            InstructionCode::KEY_VALUE_DYNAMIC.into(),
1633            InstructionCode::ADD.into(),
1634            InstructionCode::INT_8.into(),
1635            1,
1636            InstructionCode::INT_8.into(),
1637            2,
1638            InstructionCode::INT_8.into(),
1639            44,
1640            InstructionCode::SCOPE_END.into(),
1641        ];
1642        assert_eq!(result, expected);
1643    }
1644
1645    // empty map
1646    #[test]
1647    fn empty_map() {
1648        init_logger_debug();
1649        let datex_script = "{}";
1650        let result = compile_and_log(datex_script);
1651        let expected: Vec<u8> = vec![
1652            InstructionCode::MAP_START.into(),
1653            InstructionCode::SCOPE_END.into(),
1654        ];
1655        assert_eq!(result, expected);
1656    }
1657
1658    #[test]
1659    fn allocate_slot() {
1660        init_logger_debug();
1661        let script = "const a = 42";
1662        let result = compile_and_log(script);
1663        assert_eq!(
1664            result,
1665            vec![
1666                InstructionCode::ALLOCATE_SLOT.into(),
1667                // slot index as u32
1668                0,
1669                0,
1670                0,
1671                0,
1672                InstructionCode::INT_8.into(),
1673                42,
1674                InstructionCode::SCOPE_END.into(),
1675            ]
1676        );
1677    }
1678
1679    #[test]
1680    fn allocate_slot_with_value() {
1681        init_logger_debug();
1682        let script = "const a = 42; a + 1";
1683        let result = compile_and_log(script);
1684        assert_eq!(
1685            result,
1686            vec![
1687                InstructionCode::ALLOCATE_SLOT.into(),
1688                // slot index as u32
1689                0,
1690                0,
1691                0,
1692                0,
1693                InstructionCode::INT_8.into(),
1694                42,
1695                InstructionCode::SCOPE_END.into(),
1696                InstructionCode::CLOSE_AND_STORE.into(),
1697                InstructionCode::ADD.into(),
1698                InstructionCode::GET_SLOT.into(),
1699                // slot index as u32
1700                0,
1701                0,
1702                0,
1703                0,
1704                InstructionCode::INT_8.into(),
1705                1,
1706            ]
1707        );
1708    }
1709
1710    #[test]
1711    fn allocate_scoped_slots() {
1712        init_logger_debug();
1713        let script = "const a = 42; (const a = 43; a); a";
1714        let result = compile_and_log(script);
1715        assert_eq!(
1716            result,
1717            vec![
1718                InstructionCode::ALLOCATE_SLOT.into(),
1719                0,
1720                0,
1721                0,
1722                0,
1723                InstructionCode::INT_8.into(),
1724                42,
1725                InstructionCode::SCOPE_END.into(),
1726                InstructionCode::CLOSE_AND_STORE.into(),
1727                InstructionCode::SCOPE_START.into(),
1728                InstructionCode::ALLOCATE_SLOT.into(),
1729                1,
1730                0,
1731                0,
1732                0,
1733                InstructionCode::INT_8.into(),
1734                43,
1735                InstructionCode::SCOPE_END.into(),
1736                InstructionCode::CLOSE_AND_STORE.into(),
1737                InstructionCode::GET_SLOT.into(),
1738                1,
1739                0,
1740                0,
1741                0,
1742                InstructionCode::DROP_SLOT.into(),
1743                1,
1744                0,
1745                0,
1746                0,
1747                InstructionCode::SCOPE_END.into(),
1748                InstructionCode::CLOSE_AND_STORE.into(),
1749                InstructionCode::GET_SLOT.into(),
1750                // slot index as u32
1751                0,
1752                0,
1753                0,
1754                0,
1755            ]
1756        );
1757    }
1758
1759    #[test]
1760    fn allocate_scoped_slots_with_parent_variables() {
1761        init_logger_debug();
1762        let script = "const a = 42; const b = 41; (const a = 43; a; b); a";
1763        let result = compile_and_log(script);
1764        assert_eq!(
1765            result,
1766            vec![
1767                InstructionCode::ALLOCATE_SLOT.into(),
1768                0,
1769                0,
1770                0,
1771                0,
1772                InstructionCode::INT_8.into(),
1773                42,
1774                InstructionCode::SCOPE_END.into(),
1775                InstructionCode::CLOSE_AND_STORE.into(),
1776                InstructionCode::ALLOCATE_SLOT.into(),
1777                1,
1778                0,
1779                0,
1780                0,
1781                InstructionCode::INT_8.into(),
1782                41,
1783                InstructionCode::SCOPE_END.into(),
1784                InstructionCode::CLOSE_AND_STORE.into(),
1785                InstructionCode::SCOPE_START.into(),
1786                InstructionCode::ALLOCATE_SLOT.into(),
1787                2,
1788                0,
1789                0,
1790                0,
1791                InstructionCode::INT_8.into(),
1792                43,
1793                InstructionCode::SCOPE_END.into(),
1794                InstructionCode::CLOSE_AND_STORE.into(),
1795                InstructionCode::GET_SLOT.into(),
1796                2,
1797                0,
1798                0,
1799                0,
1800                InstructionCode::CLOSE_AND_STORE.into(),
1801                InstructionCode::GET_SLOT.into(),
1802                1,
1803                0,
1804                0,
1805                0,
1806                InstructionCode::DROP_SLOT.into(),
1807                2,
1808                0,
1809                0,
1810                0,
1811                InstructionCode::SCOPE_END.into(),
1812                InstructionCode::CLOSE_AND_STORE.into(),
1813                InstructionCode::GET_SLOT.into(),
1814                // slot index as u32
1815                0,
1816                0,
1817                0,
1818                0,
1819            ]
1820        );
1821    }
1822
1823    #[test]
1824    fn allocate_ref() {
1825        init_logger_debug();
1826        let script = "const a = &mut 42";
1827        let result = compile_and_log(script);
1828        assert_eq!(
1829            result,
1830            vec![
1831                InstructionCode::ALLOCATE_SLOT.into(),
1832                // slot index as u32
1833                0,
1834                0,
1835                0,
1836                0,
1837                InstructionCode::CREATE_REF_MUT.into(),
1838                InstructionCode::INT_8.into(),
1839                42,
1840                InstructionCode::SCOPE_END.into(),
1841            ]
1842        );
1843    }
1844
1845    #[test]
1846    fn read_ref() {
1847        init_logger_debug();
1848        let script = "const a = &mut 42; a";
1849        let result = compile_and_log(script);
1850        assert_eq!(
1851            result,
1852            vec![
1853                InstructionCode::ALLOCATE_SLOT.into(),
1854                // slot index as u32
1855                0,
1856                0,
1857                0,
1858                0,
1859                InstructionCode::CREATE_REF_MUT.into(),
1860                InstructionCode::INT_8.into(),
1861                42,
1862                InstructionCode::SCOPE_END.into(),
1863                InstructionCode::CLOSE_AND_STORE.into(),
1864                InstructionCode::GET_SLOT.into(),
1865                // slot index as u32
1866                0,
1867                0,
1868                0,
1869                0,
1870            ]
1871        );
1872    }
1873
1874    #[test]
1875    fn compile() {
1876        init_logger_debug();
1877        let result = compile_template(
1878            "? + ?",
1879            &[Integer::from(1).into(), Integer::from(2).into()],
1880            CompileOptions::default(),
1881        );
1882        assert_eq!(
1883            result.unwrap().0,
1884            vec![
1885                InstructionCode::ADD.into(),
1886                InstructionCode::INT_8.into(),
1887                1,
1888                InstructionCode::INT_8.into(),
1889                2
1890            ]
1891        );
1892    }
1893
1894    #[test]
1895    fn compile_macro() {
1896        init_logger_debug();
1897        let a = Integer::from(1);
1898        let result = compile!("?", a);
1899        assert_eq!(result.unwrap().0, vec![InstructionCode::INT_8.into(), 1,]);
1900    }
1901
1902    #[test]
1903    fn compile_macro_multi() {
1904        init_logger_debug();
1905        let result = compile!("? + ?", Integer::from(1), Integer::from(2));
1906        assert_eq!(
1907            result.unwrap().0,
1908            vec![
1909                InstructionCode::ADD.into(),
1910                InstructionCode::INT_8.into(),
1911                1,
1912                InstructionCode::INT_8.into(),
1913                2
1914            ]
1915        );
1916    }
1917
1918    fn get_json_test_string(file_path: &str) -> String {
1919        // read json from test file
1920        let file_path = format!("benches/json/{file_path}");
1921        let file_path = std::path::Path::new(&file_path);
1922        let file =
1923            std::fs::File::open(file_path).expect("Failed to open test.json");
1924        let mut reader = std::io::BufReader::new(file);
1925        let mut json_string = String::new();
1926        reader
1927            .read_to_string(&mut json_string)
1928            .expect("Failed to read test.json");
1929        json_string
1930    }
1931
1932    #[test]
1933    fn json_to_dxb_large_file() {
1934        let json = get_json_test_string("test2.json");
1935        let _ = compile_script(&json, CompileOptions::default())
1936            .expect("Failed to parse JSON string");
1937    }
1938
1939    #[test]
1940    fn static_value_detection() {
1941        init_logger_debug();
1942
1943        // non-static
1944        let script = "1 + 2";
1945        let compilation_scope = get_compilation_scope(script);
1946        assert!(*compilation_scope.has_non_static_value.borrow());
1947
1948        let script = "1 2";
1949        let compilation_scope = get_compilation_scope(script);
1950        assert!(*compilation_scope.has_non_static_value.borrow());
1951
1952        let script = "1;2";
1953        let compilation_scope = get_compilation_scope(script);
1954        assert!(*compilation_scope.has_non_static_value.borrow());
1955
1956        let script = r#"{("x" + "y"): 1}"#;
1957        let compilation_scope = get_compilation_scope(script);
1958        assert!(*compilation_scope.has_non_static_value.borrow());
1959
1960        // static
1961        let script = "1";
1962        let compilation_scope = get_compilation_scope(script);
1963        assert!(!*compilation_scope.has_non_static_value.borrow());
1964
1965        let script = "[]";
1966        let compilation_scope = get_compilation_scope(script);
1967        assert!(!*compilation_scope.has_non_static_value.borrow());
1968
1969        let script = "{}";
1970        let compilation_scope = get_compilation_scope(script);
1971        assert!(!*compilation_scope.has_non_static_value.borrow());
1972
1973        let script = "[1,2,3]";
1974        let compilation_scope = get_compilation_scope(script);
1975        assert!(!*compilation_scope.has_non_static_value.borrow());
1976
1977        let script = "{a: 2}";
1978        let compilation_scope = get_compilation_scope(script);
1979        assert!(!*compilation_scope.has_non_static_value.borrow());
1980
1981        // because of unary - 42
1982        let script = "-42";
1983        let compilation_scope = get_compilation_scope(script);
1984        assert!(!*compilation_scope.has_non_static_value.borrow());
1985    }
1986
1987    #[test]
1988    fn compile_auto_static_value_detection() {
1989        let script = "1";
1990        let (res, _) = compile_script_or_return_static_value(
1991            script,
1992            CompileOptions::default(),
1993        )
1994        .unwrap();
1995        assert_eq!(
1996            res,
1997            StaticValueOrDXB::StaticValue(Some(Integer::from(1).into()))
1998        );
1999
2000        let script = "1 + 2";
2001        let (res, _) = compile_script_or_return_static_value(
2002            script,
2003            CompileOptions::default(),
2004        )
2005        .unwrap();
2006        assert_eq!(
2007            res,
2008            StaticValueOrDXB::Dxb(vec![
2009                InstructionCode::ADD.into(),
2010                InstructionCode::INT_8.into(),
2011                1,
2012                InstructionCode::INT_8.into(),
2013                2,
2014            ])
2015        );
2016    }
2017
2018    #[test]
2019    fn remote_execution() {
2020        let script = "42 :: 43";
2021        let (res, _) =
2022            compile_script(script, CompileOptions::default()).unwrap();
2023        assert_eq!(
2024            res,
2025            vec![
2026                InstructionCode::REMOTE_EXECUTION.into(),
2027                // caller (literal value 42 for test)
2028                InstructionCode::INT_8.into(),
2029                42,
2030                // start of block
2031                InstructionCode::EXECUTION_BLOCK.into(),
2032                // block size (2 bytes)
2033                2,
2034                0,
2035                0,
2036                0,
2037                // injected slots (0)
2038                0,
2039                0,
2040                0,
2041                0,
2042                // literal value 43
2043                InstructionCode::INT_8.into(),
2044                43,
2045            ]
2046        );
2047    }
2048
2049    #[test]
2050    fn remote_execution_expression() {
2051        let script = "42 :: 1 + 2";
2052        let (res, _) =
2053            compile_script(script, CompileOptions::default()).unwrap();
2054        assert_eq!(
2055            res,
2056            vec![
2057                InstructionCode::REMOTE_EXECUTION.into(),
2058                // caller (literal value 42 for test)
2059                InstructionCode::INT_8.into(),
2060                42,
2061                // start of block
2062                InstructionCode::EXECUTION_BLOCK.into(),
2063                // block size (5 bytes)
2064                5,
2065                0,
2066                0,
2067                0,
2068                // injected slots (0)
2069                0,
2070                0,
2071                0,
2072                0,
2073                // expression: 1 + 2
2074                InstructionCode::ADD.into(),
2075                InstructionCode::INT_8.into(),
2076                1,
2077                InstructionCode::INT_8.into(),
2078                2,
2079            ]
2080        );
2081    }
2082
2083    #[test]
2084    fn remote_execution_injected_const() {
2085        init_logger_debug();
2086        let script = "const x = 42; 1 :: x";
2087        let (res, _) =
2088            compile_script(script, CompileOptions::default()).unwrap();
2089        assert_eq!(
2090            res,
2091            vec![
2092                InstructionCode::ALLOCATE_SLOT.into(),
2093                // slot index as u32
2094                0,
2095                0,
2096                0,
2097                0,
2098                InstructionCode::INT_8.into(),
2099                42,
2100                InstructionCode::SCOPE_END.into(),
2101                InstructionCode::CLOSE_AND_STORE.into(),
2102                InstructionCode::REMOTE_EXECUTION.into(),
2103                // caller (literal value 1 for test)
2104                InstructionCode::INT_8.into(),
2105                1,
2106                // start of block
2107                InstructionCode::EXECUTION_BLOCK.into(),
2108                // block size (5 bytes)
2109                5,
2110                0,
2111                0,
2112                0,
2113                // injected slots (1)
2114                1,
2115                0,
2116                0,
2117                0,
2118                // slot 0
2119                0,
2120                0,
2121                0,
2122                0,
2123                // slot 0 (mapped from slot 0)
2124                InstructionCode::GET_SLOT.into(),
2125                // slot index as u32
2126                0,
2127                0,
2128                0,
2129                0,
2130            ]
2131        );
2132    }
2133
2134    #[test]
2135    fn remote_execution_injected_var() {
2136        init_logger_debug();
2137        // var x only refers to a value, not a ref, but since it is transferred to a
2138        // remote context, its state is synced via a ref (VariableReference model)
2139        let script = "var x = 42; 1 :: x; x = 43;";
2140        let (res, _) =
2141            compile_script(script, CompileOptions::default()).unwrap();
2142        assert_eq!(
2143            res,
2144            vec![
2145                InstructionCode::ALLOCATE_SLOT.into(),
2146                // slot index as u32
2147                0,
2148                0,
2149                0,
2150                0,
2151                InstructionCode::INT_8.into(),
2152                42,
2153                InstructionCode::SCOPE_END.into(),
2154                InstructionCode::ALLOCATE_SLOT.into(),
2155                // slot index as u32
2156                1,
2157                0,
2158                0,
2159                0,
2160                // create ref
2161                InstructionCode::CREATE_REF.into(),
2162                // slot 0
2163                InstructionCode::GET_SLOT.into(),
2164                // slot index as u32
2165                0,
2166                0,
2167                0,
2168                0,
2169                InstructionCode::SCOPE_END.into(),
2170                InstructionCode::CLOSE_AND_STORE.into(),
2171                InstructionCode::REMOTE_EXECUTION.into(),
2172                // caller (literal value 1 for test)
2173                InstructionCode::INT_8.into(),
2174                1,
2175                // start of block
2176                InstructionCode::EXECUTION_BLOCK.into(),
2177                // block size (5 bytes)
2178                5,
2179                0,
2180                0,
2181                0,
2182                // injected slots (1)
2183                1,
2184                0,
2185                0,
2186                0,
2187                // slot 0
2188                0,
2189                0,
2190                0,
2191                0,
2192                // slot 0 (mapped from slot 0)
2193                InstructionCode::GET_SLOT.into(),
2194                // slot index as u32
2195                0,
2196                0,
2197                0,
2198                0,
2199                InstructionCode::CLOSE_AND_STORE.into(),
2200                // TODO #238: this is not the correct slot assignment for VariableReference model
2201                // set x to 43
2202                InstructionCode::SET_SLOT.into(),
2203                // slot index as u32
2204                0,
2205                0,
2206                0,
2207                0,
2208                InstructionCode::INT_8.into(),
2209                43,
2210                InstructionCode::SCOPE_END.into(),
2211                InstructionCode::CLOSE_AND_STORE.into(),
2212            ]
2213        );
2214    }
2215
2216    #[test]
2217    fn remote_execution_injected_consts() {
2218        let script = "const x = 42; const y = 69; 1 :: x + y";
2219        let (res, _) =
2220            compile_script(script, CompileOptions::default()).unwrap();
2221        assert_eq!(
2222            res,
2223            vec![
2224                InstructionCode::ALLOCATE_SLOT.into(),
2225                // slot index as u32
2226                0,
2227                0,
2228                0,
2229                0,
2230                InstructionCode::INT_8.into(),
2231                42,
2232                InstructionCode::SCOPE_END.into(),
2233                InstructionCode::CLOSE_AND_STORE.into(),
2234                InstructionCode::ALLOCATE_SLOT.into(),
2235                // slot index as u32
2236                1,
2237                0,
2238                0,
2239                0,
2240                InstructionCode::INT_8.into(),
2241                69,
2242                InstructionCode::SCOPE_END.into(),
2243                InstructionCode::CLOSE_AND_STORE.into(),
2244                InstructionCode::REMOTE_EXECUTION.into(),
2245                // caller (literal value 1 for test)
2246                InstructionCode::INT_8.into(),
2247                1,
2248                // start of block
2249                InstructionCode::EXECUTION_BLOCK.into(),
2250                // block size (11 bytes)
2251                11,
2252                0,
2253                0,
2254                0,
2255                // injected slots (2)
2256                2,
2257                0,
2258                0,
2259                0,
2260                // slot 0
2261                0,
2262                0,
2263                0,
2264                0,
2265                // slot 1
2266                1,
2267                0,
2268                0,
2269                0,
2270                // expression: x + y
2271                InstructionCode::ADD.into(),
2272                InstructionCode::GET_SLOT.into(),
2273                // slot index as u32
2274                0,
2275                0,
2276                0,
2277                0,
2278                InstructionCode::GET_SLOT.into(),
2279                // slot index as u32
2280                1,
2281                0,
2282                0,
2283                0,
2284            ]
2285        );
2286    }
2287
2288    #[test]
2289    fn remote_execution_shadow_const() {
2290        let script = "const x = 42; const y = 69; 1 :: (const x = 5; x + y)";
2291        let (res, _) =
2292            compile_script(script, CompileOptions::default()).unwrap();
2293        assert_eq!(
2294            res,
2295            vec![
2296                InstructionCode::ALLOCATE_SLOT.into(),
2297                // slot index as u32
2298                0,
2299                0,
2300                0,
2301                0,
2302                InstructionCode::INT_8.into(),
2303                42,
2304                InstructionCode::SCOPE_END.into(),
2305                InstructionCode::CLOSE_AND_STORE.into(),
2306                InstructionCode::ALLOCATE_SLOT.into(),
2307                // slot index as u32
2308                1,
2309                0,
2310                0,
2311                0,
2312                InstructionCode::INT_8.into(),
2313                69,
2314                InstructionCode::SCOPE_END.into(),
2315                InstructionCode::CLOSE_AND_STORE.into(),
2316                InstructionCode::REMOTE_EXECUTION.into(),
2317                // caller (literal value 1 for test)
2318                InstructionCode::INT_8.into(),
2319                1,
2320                // start of block
2321                InstructionCode::EXECUTION_BLOCK.into(),
2322                // block size (20 bytes)
2323                20,
2324                0,
2325                0,
2326                0,
2327                // injected slots (1)
2328                1,
2329                0,
2330                0,
2331                0,
2332                // slot 1 (y)
2333                1,
2334                0,
2335                0,
2336                0,
2337                // allocate slot for x
2338                InstructionCode::ALLOCATE_SLOT.into(),
2339                // slot index as u32
2340                1,
2341                0,
2342                0,
2343                0,
2344                InstructionCode::INT_8.into(),
2345                5,
2346                InstructionCode::SCOPE_END.into(),
2347                InstructionCode::CLOSE_AND_STORE.into(),
2348                // expression: x + y
2349                InstructionCode::ADD.into(),
2350                InstructionCode::GET_SLOT.into(),
2351                // slot index as u32
2352                1,
2353                0,
2354                0,
2355                0,
2356                InstructionCode::GET_SLOT.into(),
2357                // slot index as u32
2358                0,
2359                0,
2360                0,
2361                0,
2362            ]
2363        );
2364    }
2365
2366    #[test]
2367    fn remote_execution_nested() {
2368        let script = "const x = 42; (1 :: (2 :: x))";
2369        let (res, _) =
2370            compile_script(script, CompileOptions::default()).unwrap();
2371
2372        assert_eq!(
2373            res,
2374            vec![
2375                InstructionCode::ALLOCATE_SLOT.into(),
2376                // slot index as u32
2377                0,
2378                0,
2379                0,
2380                0,
2381                InstructionCode::INT_8.into(),
2382                42,
2383                InstructionCode::SCOPE_END.into(),
2384                InstructionCode::CLOSE_AND_STORE.into(),
2385                InstructionCode::REMOTE_EXECUTION.into(),
2386                // caller (literal value 1 for test)
2387                InstructionCode::INT_8.into(),
2388                1,
2389                // start of block
2390                InstructionCode::EXECUTION_BLOCK.into(),
2391                // block size (21 bytes)
2392                21,
2393                0,
2394                0,
2395                0,
2396                // injected slots (1)
2397                1,
2398                0,
2399                0,
2400                0,
2401                // slot 0
2402                0,
2403                0,
2404                0,
2405                0,
2406                // nested remote execution
2407                InstructionCode::REMOTE_EXECUTION.into(),
2408                // caller (literal value 2 for test)
2409                InstructionCode::INT_8.into(),
2410                2,
2411                // start of block
2412                InstructionCode::EXECUTION_BLOCK.into(),
2413                // block size (5 bytes)
2414                5,
2415                0,
2416                0,
2417                0,
2418                // injected slots (1)
2419                1,
2420                0,
2421                0,
2422                0,
2423                // slot 0
2424                0,
2425                0,
2426                0,
2427                0,
2428                InstructionCode::GET_SLOT.into(),
2429                // slot index as u32
2430                0,
2431                0,
2432                0,
2433                0,
2434            ]
2435        );
2436    }
2437
2438    #[test]
2439    fn remote_execution_nested2() {
2440        let script = "const x = 42; (1 :: (x :: x))";
2441        let (res, _) =
2442            compile_script(script, CompileOptions::default()).unwrap();
2443
2444        assert_eq!(
2445            res,
2446            vec![
2447                InstructionCode::ALLOCATE_SLOT.into(),
2448                // slot index as u32
2449                0,
2450                0,
2451                0,
2452                0,
2453                InstructionCode::INT_8.into(),
2454                42,
2455                InstructionCode::SCOPE_END.into(),
2456                InstructionCode::CLOSE_AND_STORE.into(),
2457                InstructionCode::REMOTE_EXECUTION.into(),
2458                // caller (literal value 1 for test)
2459                InstructionCode::INT_8.into(),
2460                1,
2461                // start of block
2462                InstructionCode::EXECUTION_BLOCK.into(),
2463                // block size (21 bytes)
2464                24,
2465                0,
2466                0,
2467                0,
2468                // injected slots (1)
2469                1,
2470                0,
2471                0,
2472                0,
2473                // slot 0
2474                0,
2475                0,
2476                0,
2477                0,
2478                // nested remote execution
2479                InstructionCode::REMOTE_EXECUTION.into(),
2480                // caller (literal value 2 for test)
2481                InstructionCode::GET_SLOT.into(),
2482                0,
2483                0,
2484                0,
2485                0,
2486                // start of block
2487                InstructionCode::EXECUTION_BLOCK.into(),
2488                // block size (5 bytes)
2489                5,
2490                0,
2491                0,
2492                0,
2493                // injected slots (1)
2494                1,
2495                0,
2496                0,
2497                0,
2498                // slot 0
2499                0,
2500                0,
2501                0,
2502                0,
2503                InstructionCode::GET_SLOT.into(),
2504                // slot index as u32
2505                0,
2506                0,
2507                0,
2508                0,
2509            ]
2510        );
2511    }
2512
2513    #[test]
2514    fn assignment_to_const() {
2515        init_logger_debug();
2516        let script = "const a = 42; a = 43";
2517        let result = compile_script(script, CompileOptions::default());
2518        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2519    }
2520
2521    #[test]
2522    fn assignment_to_const_mut() {
2523        init_logger_debug();
2524        let script = "const a = &mut 42; a = 43";
2525        let result = compile_script(script, CompileOptions::default());
2526        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2527    }
2528
2529    #[test]
2530    fn internal_assignment_to_const_mut() {
2531        init_logger_debug();
2532        let script = "const a = &mut 42; *a = 43";
2533        let result = compile_script(script, CompileOptions::default());
2534        assert_matches!(result, Ok(_));
2535    }
2536
2537    #[test]
2538    fn addition_to_const_mut_ref() {
2539        init_logger_debug();
2540        let script = "const a = &mut 42; *a += 1;";
2541        let result = compile_script(script, CompileOptions::default());
2542        assert_matches!(result, Ok(_));
2543    }
2544
2545    #[test]
2546    fn addition_to_const_variable() {
2547        init_logger_debug();
2548        let script = "const a = 42; a += 1";
2549        let result = compile_script(script, CompileOptions::default());
2550        assert_matches!(result, Err(CompilerError::AssignmentToConst { .. }));
2551    }
2552
2553    #[ignore = "implement type inference (precompiler)"]
2554    #[test]
2555    fn mutation_of_immutable_value() {
2556        init_logger_debug();
2557        let script = "const a = {x: 10}; a.x = 20;";
2558        let result = compile_script(script, CompileOptions::default());
2559        assert_matches!(
2560            result,
2561            Err(CompilerError::AssignmentToImmutableValue { .. })
2562        );
2563    }
2564
2565    #[ignore = "implement type inference (precompiler)"]
2566    #[test]
2567    fn mutation_of_mutable_value() {
2568        init_logger_debug();
2569        let script = "const a = mut {x: 10}; a.x = 20;";
2570        let result = compile_script(script, CompileOptions::default());
2571        assert_matches!(
2572            result,
2573            Err(CompilerError::AssignmentToImmutableValue { .. })
2574        );
2575    }
2576
2577    /**
2578     * var a = 10;
2579     * a = 40;
2580     * a += 10; // a = a + 10;
2581     * var a = &mut 42;;
2582     * a = &mut 43; // valid, new ref pointer
2583     * *a = 2; // internal deref assignment
2584     * *a += 1; // internal deref assignment with addition
2585     * a += 1; a = a + 1; // invalid
2586     * var obj = &mut {key: 42};
2587     * obj.key = 43; // valid, internal deref assignment
2588     */
2589    #[ignore = "implement type inference (precompiler)"]
2590    #[test]
2591    fn addition_to_immutable_ref() {
2592        init_logger_debug();
2593        let script = "const a = &42; *a += 1;";
2594        let result = compile_script(script, CompileOptions::default());
2595        assert_matches!(
2596            result,
2597            Err(CompilerError::AssignmentToImmutableReference { .. })
2598        );
2599    }
2600
2601    #[test]
2602    fn slot_endpoint() {
2603        let script = "#endpoint";
2604        let (res, _) =
2605            compile_script(script, CompileOptions::default()).unwrap();
2606        assert_eq!(
2607            res,
2608            vec![
2609                InstructionCode::GET_SLOT.into(),
2610                // slot index as u32
2611                0,
2612                0xff,
2613                0xff,
2614                0xff
2615            ]
2616        );
2617    }
2618
2619    // this is not a valid Datex script, just testing the compiler
2620    #[test]
2621    fn deref() {
2622        let script = "*10";
2623        let (res, _) =
2624            compile_script(script, CompileOptions::default()).unwrap();
2625        assert_eq!(
2626            res,
2627            vec![
2628                InstructionCode::DEREF.into(),
2629                InstructionCode::INT_8.into(),
2630                // integer as u8
2631                10,
2632                InstructionCode::SCOPE_END.into(),
2633            ]
2634        );
2635    }
2636
2637    #[test]
2638    fn type_literal_integer() {
2639        let script = "type(1)";
2640        let (res, _) =
2641            compile_script(script, CompileOptions::default()).unwrap();
2642        assert_eq!(
2643            res,
2644            vec![
2645                InstructionCode::TYPE_EXPRESSION.into(),
2646                TypeSpaceInstructionCode::TYPE_LITERAL_INTEGER.into(),
2647                // slot index as u32
2648                2,
2649                1,
2650                0,
2651                0,
2652                0,
2653                1
2654            ]
2655        );
2656    }
2657
2658    #[test]
2659    fn type_core_type_integer() {
2660        let script = "integer";
2661        let (res, _) =
2662            compile_script(script, CompileOptions::default()).unwrap();
2663        let mut instructions: Vec<u8> =
2664            vec![InstructionCode::GET_INTERNAL_REF.into()];
2665        // pointer id
2666        instructions.append(
2667            &mut PointerAddress::from(CoreLibPointerId::Integer(None))
2668                .bytes()
2669                .to_vec(),
2670        );
2671        assert_eq!(res, instructions);
2672    }
2673}