datex_core/decompiler/
mod.rs

1mod ast_decompiler;
2mod ast_from_value_container;
3mod ast_to_source_code;
4
5use std::collections::HashMap; // FIXME #222 no-std
6use std::collections::HashSet;
7use std::fmt::Write;
8use std::io::Cursor;
9// FIXME #223 no-std
10
11use crate::ast::DatexExpression;
12use crate::global::protocol_structures::instructions::Int128Data;
13use crate::global::protocol_structures::instructions::IntegerData;
14use crate::global::protocol_structures::instructions::UInt8Data;
15use crate::global::protocol_structures::instructions::UInt16Data;
16use crate::global::protocol_structures::instructions::UInt32Data;
17use crate::global::protocol_structures::instructions::UInt64Data;
18use crate::global::protocol_structures::instructions::UInt128Data;
19use crate::global::protocol_structures::instructions::{
20    DecimalData, Float32Data, Float64Data, FloatAsInt16Data, FloatAsInt32Data,
21    Instruction, Int8Data, Int16Data, Int32Data, Int64Data, ShortTextData,
22    TextData,
23};
24use crate::parser::body;
25use crate::parser::body::DXBParserError;
26use crate::values::core_values::decimal::utils::decimal_to_string;
27use crate::values::value_container::ValueContainer;
28use syntect::easy::HighlightLines;
29use syntect::highlighting::{Style, Theme, ThemeSet};
30use syntect::parsing::{SyntaxDefinition, SyntaxSetBuilder};
31use syntect::util::{LinesWithEndings, as_24_bit_terminal_escaped};
32
33/// Decompiles a DXB bytecode body into a human-readable string representation.
34pub fn decompile_body(
35    dxb_body: &[u8],
36    options: DecompileOptions,
37) -> Result<String, DXBParserError> {
38    let mut initial_state = DecompilerState {
39        dxb_body,
40        options,
41
42        scopes: vec![ScopeState {
43            scope_type: (ScopeType::default(), true),
44            ..ScopeState::default()
45        }],
46
47        current_label: 0,
48        labels: HashMap::new(),
49        inserted_labels: HashSet::new(),
50        variables: HashMap::new(),
51    };
52
53    decompile_loop(&mut initial_state)
54}
55
56/// Decompiles a single DATEX value into a human-readable string representation.
57pub fn decompile_value(
58    value: &ValueContainer,
59    options: DecompileOptions,
60) -> String {
61    let ast = DatexExpression::from(value);
62    let source_code = ast_to_source_code::ast_to_source_code(&ast, &options);
63    // add syntax highlighting
64    if options.colorized {
65        apply_syntax_highlighting(source_code).unwrap()
66    } else {
67        source_code
68    }
69}
70
71fn int_to_label(n: i32) -> String {
72    // Convert the integer to a base-26 number, with 'a' being the 0th digit
73    let mut label = String::new();
74    let mut n = n;
75
76    while n > 0 {
77        // Get the remainder when n is divided by 26
78        let r = n % 26;
79
80        // Add the corresponding character (a-z) to the label
81        label.insert(0, (r as u8 + b'a') as char);
82
83        // Divide n by 26 and continue
84        n /= 26;
85    }
86
87    // If the label is empty, it means the input integer was 0, so return "a"
88    if label.is_empty() {
89        label = "a".to_string();
90    }
91
92    label
93}
94
95#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
96pub enum Formatting {
97    #[default]
98    Compact,
99    Multiline {
100        indent: usize,
101    },
102}
103
104impl Formatting {
105    /// Default multiline formatting with 4 spaces indentation
106    pub fn multiline() -> Self {
107        Formatting::Multiline { indent: 4 }
108    }
109}
110
111#[derive(Debug, Clone, Default)]
112pub struct DecompileOptions {
113    pub formatting: Formatting,
114    pub colorized: bool,
115    /// display slots with generated variable names
116    pub resolve_slots: bool,
117    /// TODO #224
118    /// when set to true, the output is generated as compatible as possible with JSON, e.g. by
119    /// always adding double quotes around keys
120    pub json_compat: bool,
121}
122
123impl DecompileOptions {
124    pub fn json() -> Self {
125        DecompileOptions {
126            json_compat: true,
127            ..DecompileOptions::default()
128        }
129    }
130
131    /// Fomarts and colorizes the output
132    pub fn colorized() -> Self {
133        DecompileOptions {
134            colorized: true,
135            formatting: Formatting::Multiline { indent: 4 },
136            resolve_slots: true,
137            ..DecompileOptions::default()
138        }
139    }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
143pub enum ScopeType {
144    #[default]
145    Default,
146    List,
147    Map,
148    SlotAssignment,
149    Transparent,
150}
151
152impl ScopeType {
153    pub fn write_start(
154        &self,
155        output: &mut String,
156        formatting: &Formatting,
157        indentation_levels: usize,
158    ) -> Result<(), DXBParserError> {
159        match self {
160            ScopeType::Default => write!(output, "(")?,
161            ScopeType::List => write!(output, "[")?,
162            ScopeType::Map => write!(output, "{{")?,
163            ScopeType::SlotAssignment => {
164                // do nothing, slot assignment does not have a start
165            }
166            ScopeType::Transparent => {}
167        }
168        match self {
169            ScopeType::Default | ScopeType::List | ScopeType::Map => {
170                match formatting {
171                    Formatting::Multiline { indent } => {
172                        write!(output, "\r\n")?;
173                        for _ in 0..(indentation_levels * indent) {
174                            write!(output, " ")?;
175                        }
176                    }
177                    Formatting::Compact => {}
178                }
179            }
180            _ => {}
181        }
182        Ok(())
183    }
184    pub fn write_end(
185        &self,
186        output: &mut String,
187        formatting: &Formatting,
188        indentation_levels: usize,
189    ) -> Result<(), DXBParserError> {
190        match self {
191            ScopeType::Default | ScopeType::List | ScopeType::Map => {
192                match formatting {
193                    Formatting::Multiline { indent } => {
194                        write!(output, "\r\n")?;
195                        for _ in
196                            0..(indentation_levels.saturating_sub(1) * indent)
197                        {
198                            write!(output, " ")?;
199                        }
200                    }
201                    Formatting::Compact => {}
202                }
203            }
204            _ => {}
205        }
206        match self {
207            ScopeType::Default => write!(output, ")")?,
208            ScopeType::List => write!(output, "]")?,
209            ScopeType::Map => write!(output, "}}")?,
210            ScopeType::SlotAssignment => {
211                // do nothing, slot assignment does not have an end
212            }
213            ScopeType::Transparent => {}
214        }
215        Ok(())
216    }
217}
218
219#[derive(Debug, Clone, Default)]
220struct ScopeState {
221    /// true if this is the outer scope (default scope)
222    is_outer_scope: bool,
223    // TODO #225: use BinaryOperator instead of Instruction
224    active_operator: Option<(Instruction, bool)>,
225    scope_type: (ScopeType, bool),
226    /// skip inserted comma for next item (already inserted before key)
227    skip_comma_for_next_item: bool,
228    /// set to true if next item is a key (e.g. in map)
229    next_item_is_key: bool,
230    /// set to true if the current active scope should be closed after the next term
231    close_scope_after_term: bool,
232}
233
234impl ScopeState {
235    fn write_start(
236        &self,
237        output: &mut String,
238        formatting: &Formatting,
239        indentation_levels: usize,
240    ) -> Result<(), DXBParserError> {
241        self.scope_type
242            .0
243            .write_start(output, formatting, indentation_levels)
244    }
245    fn write_end(
246        &self,
247        output: &mut String,
248        formatting: &Formatting,
249        indentation_levels: usize,
250    ) -> Result<(), DXBParserError> {
251        self.scope_type
252            .0
253            .write_end(output, formatting, indentation_levels)
254    }
255}
256
257#[derive(Debug, Clone)]
258struct DecompilerState<'a> {
259    // stack of scopes
260    scopes: Vec<ScopeState>,
261
262    // dxb
263    dxb_body: &'a [u8],
264
265    // options
266    options: DecompileOptions,
267
268    // state
269    current_label: i32,
270    labels: HashMap<usize, String>,
271    inserted_labels: HashSet<usize>,
272    variables: HashMap<u16, String>,
273}
274
275impl DecompilerState<'_> {
276    fn get_current_scope(&mut self) -> &mut ScopeState {
277        self.scopes.last_mut().unwrap()
278    }
279    fn new_scope(&mut self, scope_type: ScopeType) {
280        self.scopes.push(ScopeState {
281            scope_type: (scope_type, true),
282            ..ScopeState::default()
283        });
284    }
285    fn close_scope(&mut self) {
286        if !self.scopes.is_empty() {
287            self.scopes.pop();
288        }
289    }
290}
291
292impl DecompilerState<'_> {
293    fn get_insert_label(&mut self, index: usize) -> String {
294        // existing
295        if self.labels.contains_key(&index) {
296            self.labels
297                .get(&index)
298                .unwrap_or(&"?invalid?".to_string())
299                .to_string()
300        }
301        // new
302        else {
303            let name = self.current_label.to_string();
304            self.current_label += 1;
305            self.labels.insert(index, name.clone());
306            name
307        }
308    }
309}
310
311#[deprecated]
312fn decompile_loop(
313    state: &mut DecompilerState,
314) -> Result<String, DXBParserError> {
315    let mut output = String::new();
316    let mut indentation_levels = 0;
317    let formatting = state.options.formatting;
318
319    let instruction_iterator = body::iterate_instructions(state.dxb_body);
320
321    for instruction in instruction_iterator {
322        let instruction = instruction?;
323
324        match instruction {
325            Instruction::Int8(Int8Data(i8)) => {
326                handle_before_term(
327                    state,
328                    &mut output,
329                    true,
330                    indentation_levels,
331                )?;
332                write!(output, "{i8}")?;
333                handle_after_term(state, &mut output, false)?;
334            }
335            Instruction::Int16(Int16Data(i16)) => {
336                handle_before_term(
337                    state,
338                    &mut output,
339                    true,
340                    indentation_levels,
341                )?;
342                write!(output, "{i16}")?;
343                handle_after_term(state, &mut output, false)?;
344            }
345            Instruction::Int32(Int32Data(i32)) => {
346                handle_before_term(
347                    state,
348                    &mut output,
349                    true,
350                    indentation_levels,
351                )?;
352                write!(output, "{i32}")?;
353                handle_after_term(state, &mut output, false)?;
354            }
355            Instruction::Int64(Int64Data(i64)) => {
356                handle_before_term(
357                    state,
358                    &mut output,
359                    true,
360                    indentation_levels,
361                )?;
362                write!(output, "{i64}")?;
363                handle_after_term(state, &mut output, false)?;
364            }
365            Instruction::Int128(Int128Data(i128)) => {
366                handle_before_term(
367                    state,
368                    &mut output,
369                    true,
370                    indentation_levels,
371                )?;
372                write!(output, "{i128}")?;
373                handle_after_term(state, &mut output, false)?;
374            }
375            Instruction::UInt8(UInt8Data(u8)) => {
376                handle_before_term(
377                    state,
378                    &mut output,
379                    true,
380                    indentation_levels,
381                )?;
382                write!(output, "{u8}")?;
383                handle_after_term(state, &mut output, false)?;
384            }
385            Instruction::UInt16(UInt16Data(u16)) => {
386                handle_before_term(
387                    state,
388                    &mut output,
389                    true,
390                    indentation_levels,
391                )?;
392                write!(output, "{u16}")?;
393                handle_after_term(state, &mut output, false)?;
394            }
395            Instruction::UInt32(UInt32Data(u32)) => {
396                handle_before_term(
397                    state,
398                    &mut output,
399                    true,
400                    indentation_levels,
401                )?;
402                write!(output, "{u32}")?;
403                handle_after_term(state, &mut output, false)?;
404            }
405            Instruction::UInt64(UInt64Data(u64)) => {
406                handle_before_term(
407                    state,
408                    &mut output,
409                    true,
410                    indentation_levels,
411                )?;
412                write!(output, "{u64}")?;
413                handle_after_term(state, &mut output, false)?;
414            }
415            Instruction::UInt128(UInt128Data(u128)) => {
416                handle_before_term(
417                    state,
418                    &mut output,
419                    true,
420                    indentation_levels,
421                )?;
422                write!(output, "{u128}")?;
423                handle_after_term(state, &mut output, false)?;
424            }
425            Instruction::BigInteger(IntegerData(big_int)) => {
426                handle_before_term(
427                    state,
428                    &mut output,
429                    true,
430                    indentation_levels,
431                )?;
432                write!(output, "{big_int}n")?;
433                handle_after_term(state, &mut output, false)?;
434            }
435            Instruction::DecimalF32(Float32Data(f32)) => {
436                handle_before_term(
437                    state,
438                    &mut output,
439                    true,
440                    indentation_levels,
441                )?;
442                write!(
443                    output,
444                    "{}",
445                    decimal_to_string(f32, state.options.json_compat)
446                )?;
447                handle_after_term(state, &mut output, false)?;
448            }
449            Instruction::DecimalF64(Float64Data(f64)) => {
450                handle_before_term(
451                    state,
452                    &mut output,
453                    true,
454                    indentation_levels,
455                )?;
456                write!(
457                    output,
458                    "{}",
459                    decimal_to_string(f64, state.options.json_compat)
460                )?;
461                handle_after_term(state, &mut output, false)?;
462            }
463            Instruction::DecimalAsInt16(FloatAsInt16Data(i16)) => {
464                handle_before_term(
465                    state,
466                    &mut output,
467                    true,
468                    indentation_levels,
469                )?;
470                write!(
471                    output,
472                    "{}",
473                    decimal_to_string(i16 as f32, state.options.json_compat)
474                )?;
475                handle_after_term(state, &mut output, false)?;
476            }
477            Instruction::DecimalAsInt32(FloatAsInt32Data(i32)) => {
478                handle_before_term(
479                    state,
480                    &mut output,
481                    true,
482                    indentation_levels,
483                )?;
484                write!(
485                    output,
486                    "{}",
487                    decimal_to_string(i32 as f32, state.options.json_compat)
488                )?;
489                handle_after_term(state, &mut output, false)?;
490            }
491            Instruction::Decimal(DecimalData(big_decimal)) => {
492                handle_before_term(
493                    state,
494                    &mut output,
495                    true,
496                    indentation_levels,
497                )?;
498                write!(output, "{big_decimal}")?;
499                handle_after_term(state, &mut output, false)?;
500            }
501            Instruction::ShortText(ShortTextData(text)) => {
502                handle_before_term(
503                    state,
504                    &mut output,
505                    true,
506                    indentation_levels,
507                )?;
508                let text = escape_text(&text);
509                write!(output, "\"{text}\"")?;
510                handle_after_term(state, &mut output, true)?;
511            }
512            Instruction::Text(TextData(text)) => {
513                handle_before_term(
514                    state,
515                    &mut output,
516                    true,
517                    indentation_levels,
518                )?;
519                let text = escape_text(&text);
520                write!(output, "\"{text}\"")?;
521                handle_after_term(state, &mut output, true)?;
522            }
523            Instruction::True => {
524                handle_before_term(
525                    state,
526                    &mut output,
527                    false,
528                    indentation_levels,
529                )?;
530                write!(output, "true")?;
531                handle_after_term(state, &mut output, false)?;
532            }
533            Instruction::False => {
534                handle_before_term(
535                    state,
536                    &mut output,
537                    false,
538                    indentation_levels,
539                )?;
540                write!(output, "false")?;
541                handle_after_term(state, &mut output, false)?;
542            }
543            Instruction::Null => {
544                handle_before_term(
545                    state,
546                    &mut output,
547                    false,
548                    indentation_levels,
549                )?;
550                write!(output, "null")?;
551                handle_after_term(state, &mut output, false)?;
552            }
553            Instruction::Endpoint(endpoint) => {
554                handle_before_term(
555                    state,
556                    &mut output,
557                    false,
558                    indentation_levels,
559                )?;
560                write!(output, "{endpoint}")?;
561                handle_after_term(state, &mut output, false)?;
562            }
563            Instruction::ListStart => {
564                indentation_levels += 1;
565                handle_before_term(
566                    state,
567                    &mut output,
568                    false,
569                    indentation_levels,
570                )?;
571                state.new_scope(ScopeType::List);
572                state.get_current_scope().write_start(
573                    &mut output,
574                    &formatting,
575                    indentation_levels,
576                )?;
577            }
578            Instruction::MapStart => {
579                indentation_levels += 1;
580                handle_before_term(
581                    state,
582                    &mut output,
583                    false,
584                    indentation_levels,
585                )?;
586                state.new_scope(ScopeType::Map);
587                state.get_current_scope().write_start(
588                    &mut output,
589                    &formatting,
590                    indentation_levels,
591                )?;
592            }
593            Instruction::ScopeStart => {
594                indentation_levels += 1;
595                handle_before_term(
596                    state,
597                    &mut output,
598                    true,
599                    indentation_levels,
600                )?;
601                state.new_scope(ScopeType::Default);
602                state.get_current_scope().write_start(
603                    &mut output,
604                    &formatting,
605                    indentation_levels,
606                )?;
607            }
608            Instruction::ScopeEnd => {
609                let current_scope_is_collection = matches!(
610                    state.get_current_scope().scope_type.0,
611                    ScopeType::List | ScopeType::Map
612                );
613                handle_scope_close(state, &mut output, indentation_levels)?;
614                handle_after_term(state, &mut output, true)?;
615                if current_scope_is_collection {
616                    indentation_levels = indentation_levels.saturating_sub(1);
617                }
618            }
619            Instruction::KeyValueShortText(text_data) => {
620                handle_before_term(
621                    state,
622                    &mut output,
623                    false,
624                    indentation_levels,
625                )?;
626                // prevent redundant comma for value
627                state.get_current_scope().skip_comma_for_next_item = true;
628                write_text_key(
629                    state,
630                    &text_data.0,
631                    &mut output,
632                    state.options.formatting,
633                )?;
634            }
635            Instruction::KeyValueDynamic => {
636                handle_before_term(
637                    state,
638                    &mut output,
639                    false,
640                    indentation_levels,
641                )?;
642                state.get_current_scope().skip_comma_for_next_item = true;
643                state.get_current_scope().next_item_is_key = true;
644            }
645            Instruction::CloseAndStore => match state.options.formatting {
646                Formatting::Multiline { .. } => {
647                    write!(output, ";\r\n")?;
648                }
649                Formatting::Compact => {
650                    write!(output, ";")?;
651                }
652            },
653
654            // operations
655            Instruction::Add
656            | Instruction::Subtract
657            | Instruction::Multiply
658            | Instruction::Divide => {
659                handle_before_term(
660                    state,
661                    &mut output,
662                    false,
663                    indentation_levels,
664                )?;
665                state.new_scope(ScopeType::Transparent);
666                state.get_current_scope().active_operator =
667                    Some((instruction, true));
668            }
669
670            Instruction::UnaryMinus
671            | Instruction::UnaryPlus
672            | Instruction::BitwiseNot => {
673                handle_before_term(
674                    state,
675                    &mut output,
676                    false,
677                    indentation_levels,
678                )?;
679                state.new_scope(ScopeType::Transparent);
680                state.get_current_scope().active_operator =
681                    Some((instruction, false));
682            }
683
684            // slots
685            Instruction::AllocateSlot(address) => {
686                handle_before_term(
687                    state,
688                    &mut output,
689                    false,
690                    indentation_levels,
691                )?;
692                state.new_scope(ScopeType::SlotAssignment);
693                // if resolve_slots is enabled, write the slot as variable
694                if state.options.resolve_slots {
695                    // TODO #95: generate variable name for slot
696                    write!(output, "#{} := ", address.0)?;
697                } else {
698                    // otherwise just write the slot address
699                    write!(output, "#{} := ", address.0)?;
700                }
701                handle_after_term(state, &mut output, false)?;
702            }
703            Instruction::GetSlot(address) => {
704                handle_before_term(
705                    state,
706                    &mut output,
707                    false,
708                    indentation_levels,
709                )?;
710                // if resolve_slots is enabled, write the slot as variable
711                if state.options.resolve_slots {
712                    // TODO #96: get variable name for slot
713                    write!(output, "#{}", address.0)?;
714                } else {
715                    // otherwise just write the slot address
716                    write!(output, "#{}", address.0)?;
717                }
718                handle_after_term(state, &mut output, false)?;
719            }
720            Instruction::DropSlot(address) => {
721                // if resolve_slots is enabled, write the slot as variable
722                if state.options.resolve_slots {
723                    // TODO #97: generate variable name for slot
724                    write!(output, "#drop {}", address.0)?;
725                } else {
726                    // otherwise just write the slot address
727                    write!(output, "#drop {}", address.0)?;
728                }
729            }
730            Instruction::SetSlot(address) => {
731                handle_before_term(
732                    state,
733                    &mut output,
734                    false,
735                    indentation_levels,
736                )?;
737                state.new_scope(ScopeType::SlotAssignment);
738                // if resolve_slots is enabled, write the slot as variable
739                if state.options.resolve_slots {
740                    // TODO #98: generate variable name for slot
741                    write!(output, "#{} = ", address.0)?;
742                } else {
743                    // otherwise just write the slot address
744                    write!(output, "#{} = ", address.0)?;
745                }
746            }
747
748            Instruction::GetRef(address) => {
749                handle_before_term(
750                    state,
751                    &mut output,
752                    false,
753                    indentation_levels,
754                )?;
755                let endpoint_hex = address
756                    .endpoint
757                    .to_binary()
758                    .iter()
759                    .map(|b| format!("{:02x}", b))
760                    .collect::<String>();
761                let address_hex = address
762                    .id
763                    .iter()
764                    .map(|b| format!("{:02x}", b))
765                    .collect::<String>();
766                write!(output, "$<{}:{}>", endpoint_hex, address_hex)?;
767                handle_after_term(state, &mut output, false)?;
768            }
769
770            Instruction::GetInternalRef(address) => {
771                handle_before_term(
772                    state,
773                    &mut output,
774                    false,
775                    indentation_levels,
776                )?;
777                let address_hex = address
778                    .id
779                    .iter()
780                    .map(|b| format!("{:02x}", b))
781                    .collect::<String>();
782                write!(output, "$<internal:{}>", address_hex)?;
783                handle_after_term(state, &mut output, false)?;
784            }
785
786            Instruction::GetLocalRef(address) => {
787                handle_before_term(
788                    state,
789                    &mut output,
790                    false,
791                    indentation_levels,
792                )?;
793                let address_hex = address
794                    .id
795                    .iter()
796                    .map(|b| format!("{:02x}", b))
797                    .collect::<String>();
798                write!(output, "$<origin:{}>", address_hex)?;
799                handle_after_term(state, &mut output, false)?;
800            }
801
802            Instruction::AddAssign(address) => {
803                handle_before_term(
804                    state,
805                    &mut output,
806                    false,
807                    indentation_levels,
808                )?;
809                state.new_scope(ScopeType::SlotAssignment);
810                // if resolve_slots is enabled, write the slot as variable
811                if state.options.resolve_slots {
812                    write!(output, "#{} += ", address.0)?;
813                } else {
814                    // otherwise just write the slot address
815                    write!(output, "#{} += ", address.0)?;
816                }
817            }
818
819            Instruction::SubtractAssign(address) => {
820                handle_before_term(
821                    state,
822                    &mut output,
823                    false,
824                    indentation_levels,
825                )?;
826                state.new_scope(ScopeType::SlotAssignment);
827                // if resolve_slots is enabled, write the slot as variable
828                if state.options.resolve_slots {
829                    write!(output, "#{} -= ", address.0)?;
830                } else {
831                    // otherwise just write the slot address
832                    write!(output, "#{} -= ", address.0)?;
833                }
834            }
835
836            Instruction::CreateRef => {
837                handle_before_term(
838                    state,
839                    &mut output,
840                    false,
841                    indentation_levels,
842                )?;
843                state.get_current_scope().skip_comma_for_next_item = true;
844                write!(output, "&")?;
845            }
846
847            Instruction::CreateRefMut => {
848                handle_before_term(
849                    state,
850                    &mut output,
851                    false,
852                    indentation_levels,
853                )?;
854                state.get_current_scope().skip_comma_for_next_item = true;
855                write!(output, "&mut ")?;
856            }
857
858            Instruction::CreateRefFinal => {
859                handle_before_term(
860                    state,
861                    &mut output,
862                    false,
863                    indentation_levels,
864                )?;
865                state.get_current_scope().skip_comma_for_next_item = true;
866                write!(output, "&final ")?;
867            }
868
869            Instruction::RemoteExecution => {
870                handle_before_term(
871                    state,
872                    &mut output,
873                    false,
874                    indentation_levels,
875                )?;
876                state.get_current_scope().active_operator =
877                    Some((instruction, true));
878            }
879
880            Instruction::ExecutionBlock(data) => {
881                handle_before_term(
882                    state,
883                    &mut output,
884                    true,
885                    indentation_levels,
886                )?;
887                // decompile data.body
888                let decompiled_body =
889                    decompile_body(&data.body, state.options.clone())?;
890                let slot_mapping = data
891                    .injected_slots
892                    .iter()
893                    .enumerate()
894                    .map(|(k, v)| format!("#{v} => #{k}"))
895                    .collect::<Vec<_>>()
896                    .join(", ");
897                // write the decompiled body
898                write!(output, "[{slot_mapping}]({decompiled_body})")?;
899            }
900
901            _ => {
902                write!(output, "[[{instruction}]]")?;
903            }
904        }
905    }
906
907    // add syntax highlighting
908    if state.options.colorized {
909        output = apply_syntax_highlighting(output)?;
910    }
911
912    Ok(output)
913}
914
915pub fn apply_syntax_highlighting(
916    datex_script: String,
917) -> Result<String, DXBParserError> {
918    let mut output = String::new();
919
920    // load datex syntax + custom theme
921    static DATEX_SCRIPT_DEF: &str = include_str!(
922        "../../datex-language/datex.tmbundle/Syntaxes/datex.sublime-text"
923    );
924    static DATEX_THEME_DEF: &str =
925        include_str!("../../datex-language/themes/datex-dark.tmTheme");
926    let mut builder = SyntaxSetBuilder::new();
927    let syntax = SyntaxDefinition::load_from_str(DATEX_SCRIPT_DEF, true, None)
928        .expect("Failed to load syntax definition");
929    builder.add(syntax);
930    let theme: Theme =
931        ThemeSet::load_from_reader(&mut Cursor::new(DATEX_THEME_DEF))
932            .expect("Failed to load theme");
933
934    let ps = builder.build();
935    let syntax = ps.find_syntax_by_extension("dx").unwrap();
936    let mut h = HighlightLines::new(syntax, &theme);
937
938    for line in LinesWithEndings::from(&datex_script) {
939        let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps).unwrap();
940        let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
941        write!(output, "{escaped}")?;
942    }
943    // reset style
944    write!(output, "\x1b[0m")?;
945    Ok(output)
946}
947
948fn escape_text(text: &str) -> String {
949    // escape quotes and backslashes in text
950    text.replace('\\', r#"\\"#)
951        .replace('"', r#"\""#)
952        .replace('\u{0008}', r#"\b"#)
953        .replace('\u{000c}', r#"\f"#)
954        .replace('\r', r#"\r"#)
955        .replace('\t', r#"\t"#)
956        .replace('\u{000b}', r#"\v"#)
957        .replace('\n', r#"\n"#)
958}
959
960fn write_text_key(
961    state: &mut DecompilerState,
962    text: &str,
963    output: &mut String,
964    formatting: Formatting,
965) -> Result<(), DXBParserError> {
966    // if text does not just contain a-z, A-Z, 0-9, _, and starts with a-z, A-Z,  _, add quotes
967    let text = if !state.options.json_compat && is_alphanumeric_identifier(text)
968    {
969        text.to_string()
970    } else {
971        format!("\"{}\"", escape_text(text))
972    };
973    match formatting {
974        Formatting::Multiline { .. } => {
975            write!(output, "{text}: ")?;
976        }
977        Formatting::Compact => {
978            write!(output, "{text}:")?;
979        }
980    }
981    Ok(())
982}
983
984fn is_alphanumeric_identifier(s: &str) -> bool {
985    let mut chars = s.chars();
986
987    // First character must be a-z, A-Z, or _
988    match chars.next() {
989        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
990        _ => return false,
991    }
992
993    // Remaining characters: a-z, A-Z, 0-9, _, or -
994    chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
995}
996
997/// insert syntax before a term (e.g. operators, commas, etc.)
998/// if is_standalone_key is set to true, no parenthesis are wrapped around the item if it is a key,
999/// e.g. for text ("key": "value") the parenthesis are not needed
1000fn handle_before_term(
1001    state: &mut DecompilerState,
1002    output: &mut String,
1003    is_standalone_key: bool,
1004    indentation_levels: usize,
1005) -> Result<(), DXBParserError> {
1006    handle_before_operand(state, output)?;
1007    handle_before_item(state, output, is_standalone_key, indentation_levels)?;
1008    Ok(())
1009}
1010
1011/// if is_standalone_key is set to true, no parenthesis are wrapped around the item if it is a key,
1012/// e.g. for text ("key": "value") the parenthesis are not needed
1013fn handle_after_term(
1014    state: &mut DecompilerState,
1015    output: &mut String,
1016    is_standalone_key: bool,
1017) -> Result<(), DXBParserError> {
1018    let close_scope = state.get_current_scope().close_scope_after_term;
1019    if close_scope {
1020        // close scope after term
1021        state.close_scope();
1022    }
1023
1024    // next_item_is_key
1025    if state.get_current_scope().next_item_is_key {
1026        if !is_standalone_key || close_scope {
1027            write!(output, ")")?;
1028        }
1029        // set next_item_is_key to false
1030        state.get_current_scope().next_item_is_key = false;
1031        match state.options.formatting {
1032            Formatting::Multiline { .. } => {
1033                write!(output, ": ")?;
1034            }
1035            Formatting::Compact => {
1036                write!(output, ":")?;
1037            }
1038        }
1039        // prevent redundant comma before value
1040        state.get_current_scope().skip_comma_for_next_item = true;
1041    }
1042
1043    Ok(())
1044}
1045
1046/// before scope close (insert scope closing syntax)
1047fn handle_scope_close(
1048    state: &mut DecompilerState,
1049    output: &mut String,
1050    indentation_levels: usize,
1051) -> Result<(), DXBParserError> {
1052    let formatting = state.options.formatting;
1053    let scope = state.get_current_scope();
1054    // close only if not outer scope
1055    if !scope.is_outer_scope {
1056        state.get_current_scope().write_end(
1057            output,
1058            &formatting,
1059            indentation_levels,
1060        )?;
1061    }
1062    // close scope
1063    state.close_scope();
1064    Ok(())
1065}
1066
1067/// insert comma syntax before a term (e.g. ",")
1068/// if is_standalone_key is set to true, no parenthesis are wrapped around the item if it is a key,
1069/// e.g. for text ("key": "value") the parenthesis are not needed
1070fn handle_before_item(
1071    state: &mut DecompilerState,
1072    output: &mut String,
1073    is_standalone_key: bool,
1074    indentation_levels: usize,
1075) -> Result<(), DXBParserError> {
1076    let formatted = state.options.formatting;
1077    let scope = state.get_current_scope();
1078
1079    // if next_item_is_key, add opening parenthesis
1080    if !is_standalone_key && scope.next_item_is_key {
1081        write!(output, "(")?;
1082    }
1083
1084    match scope.scope_type {
1085        (_, true) => {
1086            // if first is true, set to false
1087            scope.scope_type.1 = false;
1088        }
1089        (ScopeType::List | ScopeType::Map, false)
1090            if !scope.skip_comma_for_next_item =>
1091        {
1092            match formatted {
1093                Formatting::Multiline { indent } => {
1094                    write!(output, ",\r\n")?;
1095                    let current_indent = indentation_levels * indent;
1096                    for _ in 0..current_indent {
1097                        write!(output, " ")?;
1098                    }
1099                }
1100                Formatting::Compact => {
1101                    write!(output, ",")?;
1102                }
1103            }
1104        }
1105        _ => {
1106            // don't insert comma for default scope
1107        }
1108    }
1109
1110    // reset skip_comma_for_next_item flag
1111    scope.skip_comma_for_next_item = false;
1112
1113    Ok(())
1114}
1115
1116/// insert operator syntax before an operand (e.g. +, -, etc.)
1117fn handle_before_operand(
1118    state: &mut DecompilerState,
1119    output: &mut String,
1120) -> Result<(), DXBParserError> {
1121    if let Some(operator) = state.get_current_scope().active_operator.take() {
1122        // handle the operator before the operand
1123        match operator {
1124            (_, true) => {
1125                // if first is true, set to false
1126                state.get_current_scope().active_operator =
1127                    Some((operator.0.clone(), false));
1128            }
1129            (Instruction::Add, false) => {
1130                write_operator(state, output, "+")?;
1131                state.get_current_scope().close_scope_after_term = true;
1132            }
1133            (Instruction::Subtract, false) => {
1134                write_operator(state, output, "-")?;
1135                state.get_current_scope().close_scope_after_term = true;
1136            }
1137            (Instruction::Multiply, false) => {
1138                write_operator(state, output, "*")?;
1139                state.get_current_scope().close_scope_after_term = true;
1140            }
1141            (Instruction::Divide, false) => {
1142                write_operator(state, output, "/")?;
1143                state.get_current_scope().close_scope_after_term = true;
1144            }
1145            (Instruction::RemoteExecution, false) => {
1146                write_operator(state, output, "::")?;
1147                state.get_current_scope().close_scope_after_term = false;
1148            }
1149            (Instruction::UnaryMinus, false) => {
1150                write!(output, "-")?;
1151                state.get_current_scope().close_scope_after_term = true;
1152            }
1153            (Instruction::UnaryPlus, false) => {
1154                write!(output, "+")?;
1155                state.get_current_scope().close_scope_after_term = true;
1156            }
1157            (Instruction::BitwiseNot, false) => {
1158                write!(output, "~")?;
1159                state.get_current_scope().close_scope_after_term = true;
1160            }
1161            _ => {
1162                todo!("#423 Invalid operator: {operator:?}");
1163            }
1164        }
1165    }
1166    Ok(())
1167}
1168
1169fn write_operator(
1170    state: &mut DecompilerState,
1171    output: &mut String,
1172    operator: &str,
1173) -> Result<(), DXBParserError> {
1174    write!(output, " {operator} ")?;
1175    Ok(())
1176}