bind/bytecode_encoder/
encode_v2.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::bytecode_constants::*;
6use crate::bytecode_encoder::error::BindRulesEncodeError;
7use crate::bytecode_encoder::instruction_encoder::encode_instructions;
8use crate::bytecode_encoder::symbol_table_encoder::SymbolTableEncoder;
9use crate::compiler::{BindRules, CompositeBindRules, CompositeNode};
10use std::collections::HashSet;
11
12/// Functions for encoding the new bytecode format. When the
13/// old bytecode format is deleted, the "v2" should be removed from the names.
14
15pub fn encode_to_bytecode_v2(bind_rules: BindRules) -> Result<Vec<u8>, BindRulesEncodeError> {
16    let mut debug_symbol_table_encoder_option =
17        if bind_rules.enable_debug { Some(SymbolTableEncoder::new()) } else { None };
18
19    let mut symbol_table_encoder = SymbolTableEncoder::new();
20    let mut instruction_bytecode = encode_instructions(
21        bind_rules.instructions,
22        &mut symbol_table_encoder,
23        &mut debug_symbol_table_encoder_option,
24    )?;
25
26    let mut bytecode: Vec<u8> = vec![];
27
28    // Encode the header.
29    bytecode.extend_from_slice(&BIND_MAGIC_NUM.to_be_bytes());
30    bytecode.extend_from_slice(&BYTECODE_VERSION.to_le_bytes());
31    if bind_rules.enable_debug {
32        bytecode.push(BYTECODE_ENABLE_DEBUG);
33    } else {
34        bytecode.push(BYTECODE_DISABLE_DEBUG);
35    }
36
37    // Encode the symbol table.
38    bytecode.extend_from_slice(&SYMB_MAGIC_NUM.to_be_bytes());
39    bytecode.extend_from_slice(&(symbol_table_encoder.bytecode.len() as u32).to_le_bytes());
40    bytecode.append(&mut symbol_table_encoder.bytecode);
41
42    // Encode the instruction section.
43    bytecode.extend_from_slice(&INSTRUCTION_MAGIC_NUM.to_be_bytes());
44    bytecode.extend_from_slice(&(instruction_bytecode.len() as u32).to_le_bytes());
45    bytecode.append(&mut instruction_bytecode);
46
47    // Encode the debug section when enable_debug flag is true.
48    if let Some(mut debug_symbol_table_encoder) = debug_symbol_table_encoder_option {
49        encode_debug_section(&mut bytecode, &mut debug_symbol_table_encoder)?;
50    }
51
52    Ok(bytecode)
53}
54
55pub fn encode_to_string_v2(bind_rules: BindRules) -> Result<(String, usize), BindRulesEncodeError> {
56    let result = encode_to_bytecode_v2(bind_rules)?;
57    let byte_count = result.len();
58    Ok((
59        result.into_iter().map(|byte| format!("{:#x}", byte)).collect::<Vec<String>>().join(","),
60        byte_count,
61    ))
62}
63
64fn encode_debug_section(
65    bytecode: &mut Vec<u8>,
66    debug_symbol_table_encoder: &mut SymbolTableEncoder,
67) -> Result<(), BindRulesEncodeError> {
68    // Debug information header
69    bytecode.extend_from_slice(&DEBG_MAGIC_NUM.to_be_bytes());
70    if debug_symbol_table_encoder.bytecode.len() == 0 {
71        bytecode
72            .extend_from_slice(&(debug_symbol_table_encoder.bytecode.len() as u32).to_le_bytes());
73    } else {
74        // Extend the debug information section to include the
75        // length of the debug symbol table bytecode and header
76        bytecode.extend_from_slice(
77            &((debug_symbol_table_encoder.bytecode.len() + HEADER_SZ) as u32).to_le_bytes(),
78        );
79        // Debug symbol table section
80        bytecode.extend_from_slice(&DBSY_MAGIC_NUM.to_be_bytes());
81        bytecode
82            .extend_from_slice(&(debug_symbol_table_encoder.bytecode.len() as u32).to_le_bytes());
83        bytecode.append(&mut debug_symbol_table_encoder.bytecode);
84    }
85
86    Ok(())
87}
88
89fn append_composite_node(
90    bytecode: &mut Vec<u8>,
91    node: CompositeNode,
92    node_type: RawNodeType,
93    symbol_table_encoder: &mut SymbolTableEncoder,
94    debug_symbol_table_encoder: &mut Option<SymbolTableEncoder>,
95) -> Result<(), BindRulesEncodeError> {
96    if node.name.is_empty() {
97        return Err(BindRulesEncodeError::MissingCompositeNodeName);
98    }
99
100    bytecode.push(node_type as u8);
101    bytecode.extend_from_slice(&symbol_table_encoder.get_key(node.name)?.to_le_bytes());
102
103    let mut inst_bytecode =
104        encode_instructions(node.instructions, symbol_table_encoder, debug_symbol_table_encoder)?;
105    bytecode.extend_from_slice(&(inst_bytecode.len() as u32).to_le_bytes());
106    bytecode.append(&mut inst_bytecode);
107    Ok(())
108}
109
110pub fn encode_composite_to_bytecode(
111    bind_rules: CompositeBindRules,
112) -> Result<Vec<u8>, BindRulesEncodeError> {
113    let mut symbol_table_encoder = SymbolTableEncoder::new();
114    let mut debug_symbol_table_encoder_option =
115        if bind_rules.enable_debug { Some(SymbolTableEncoder::new()) } else { None };
116
117    if bind_rules.device_name.is_empty() {
118        return Err(BindRulesEncodeError::MissingCompositeDeviceName);
119    }
120
121    // Instruction bytecode begins with the device name ID.
122    let device_name_id = symbol_table_encoder.get_key(bind_rules.device_name)?;
123    let mut inst_bytecode = device_name_id.to_le_bytes().to_vec();
124
125    let mut node_names = HashSet::new();
126
127    // Add instructions from the primary node.
128    node_names.insert(bind_rules.primary_node.name.clone());
129    append_composite_node(
130        &mut inst_bytecode,
131        bind_rules.primary_node,
132        RawNodeType::Primary,
133        &mut symbol_table_encoder,
134        &mut debug_symbol_table_encoder_option,
135    )?;
136
137    // Add instructions from additional nodes.
138    for node in bind_rules.additional_nodes.into_iter() {
139        if node_names.contains(&node.name) {
140            return Err(BindRulesEncodeError::DuplicateCompositeNodeName(node.name));
141        }
142        node_names.insert(node.name.clone());
143        append_composite_node(
144            &mut inst_bytecode,
145            node,
146            RawNodeType::Additional,
147            &mut symbol_table_encoder,
148            &mut debug_symbol_table_encoder_option,
149        )?;
150    }
151
152    // Add instructions from optional nodes.
153    for node in bind_rules.optional_nodes.into_iter() {
154        if node_names.contains(&node.name) {
155            return Err(BindRulesEncodeError::DuplicateCompositeNodeName(node.name));
156        }
157        node_names.insert(node.name.clone());
158        append_composite_node(
159            &mut inst_bytecode,
160            node,
161            RawNodeType::Optional,
162            &mut symbol_table_encoder,
163            &mut debug_symbol_table_encoder_option,
164        )?;
165    }
166
167    // Put all of the sections together.
168    let mut bytecode: Vec<u8> = vec![];
169
170    // Encode the header.
171    bytecode.extend_from_slice(&BIND_MAGIC_NUM.to_be_bytes());
172    bytecode.extend_from_slice(&BYTECODE_VERSION.to_le_bytes());
173    if bind_rules.enable_debug {
174        bytecode.push(BYTECODE_ENABLE_DEBUG);
175    } else {
176        bytecode.push(BYTECODE_DISABLE_DEBUG);
177    }
178
179    // Encode the symbol table.
180    bytecode.extend_from_slice(&SYMB_MAGIC_NUM.to_be_bytes());
181    bytecode.extend_from_slice(&(symbol_table_encoder.bytecode.len() as u32).to_le_bytes());
182    bytecode.append(&mut symbol_table_encoder.bytecode);
183
184    // Encode the instruction section.
185    bytecode.extend_from_slice(&COMPOSITE_MAGIC_NUM.to_be_bytes());
186    bytecode.extend_from_slice(&(inst_bytecode.len() as u32).to_le_bytes());
187    bytecode.append(&mut inst_bytecode);
188
189    // Encode the debug section when enable_debug flag is true.
190    if let Some(mut debug_symbol_table_encoder) = debug_symbol_table_encoder_option {
191        encode_debug_section(&mut bytecode, &mut debug_symbol_table_encoder)?;
192    }
193
194    Ok(bytecode)
195}
196
197#[cfg(test)]
198mod test {
199    use super::*;
200    use crate::bytecode_encoder::bytecode_checker::*;
201    use crate::compiler::{Symbol, SymbolicInstruction, SymbolicInstructionInfo};
202    use crate::parser::bind_library::ValueType;
203    use std::collections::HashMap;
204
205    // Converts a vector of SymbolicInstruction into a vector of SymbolicInstructionInfo.
206    // The location value for each element is set to None.
207    fn to_symbolic_inst_info<'a>(
208        instructions: Vec<SymbolicInstruction>,
209    ) -> Vec<SymbolicInstructionInfo<'a>> {
210        instructions
211            .into_iter()
212            .map(|inst| SymbolicInstructionInfo { location: None, instruction: inst })
213            .collect()
214    }
215
216    fn composite_node<'a>(
217        name: String,
218        instructions: Vec<SymbolicInstruction>,
219    ) -> CompositeNode<'a> {
220        CompositeNode { name: name, instructions: to_symbolic_inst_info(instructions) }
221    }
222
223    #[test]
224    fn test_string_values() {
225        let instructions = vec![
226            SymbolicInstruction::AbortIfNotEqual {
227                lhs: Symbol::DeprecatedKey(5),
228                rhs: Symbol::StringValue("shoveler".to_string()),
229            },
230            SymbolicInstruction::AbortIfEqual {
231                lhs: Symbol::DeprecatedKey(1),
232                rhs: Symbol::BoolValue(false),
233            },
234            SymbolicInstruction::JumpIfEqual {
235                lhs: Symbol::DeprecatedKey(15),
236                rhs: Symbol::StringValue("canvasback".to_string()),
237                label: 1,
238            },
239            SymbolicInstruction::AbortIfEqual {
240                lhs: Symbol::DeprecatedKey(2),
241                rhs: Symbol::StringValue("canvasback".to_string()),
242            },
243            SymbolicInstruction::Label(1),
244            SymbolicInstruction::AbortIfEqual {
245                lhs: Symbol::Key("bufflehead".to_string(), ValueType::Number),
246                rhs: Symbol::NumberValue(1),
247            },
248            SymbolicInstruction::AbortIfEqual {
249                lhs: Symbol::Key("pintail".to_string(), ValueType::Enum),
250                rhs: Symbol::EnumValue("mallard".to_string()),
251            },
252        ];
253
254        let bind_rules = BindRules {
255            instructions: to_symbolic_inst_info(instructions),
256            symbol_table: HashMap::new(),
257            use_new_bytecode: true,
258            enable_debug: false,
259        };
260
261        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
262        checker.verify_bind_rules_header(false);
263        checker.verify_sym_table_header(67);
264
265        checker.verify_symbol_table(&[
266            "shoveler",
267            "canvasback",
268            "bufflehead",
269            "pintail",
270            "mallard",
271        ]);
272
273        checker.verify_instructions_header((COND_ABORT_BYTES * 5) + COND_JMP_BYTES + JMP_PAD_BYTES);
274        checker.verify_abort_not_equal(
275            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
276            EncodedValue { value_type: RawValueType::StringValue, value: 1 },
277        );
278        checker.verify_abort_equal(
279            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
280            EncodedValue { value_type: RawValueType::BoolValue, value: 0 },
281        );
282        checker.verify_jmp_if_equal(
283            COND_ABORT_BYTES,
284            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
285            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
286        );
287        checker.verify_abort_equal(
288            EncodedValue { value_type: RawValueType::NumberValue, value: 2 },
289            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
290        );
291        checker.verify_jmp_pad();
292        checker.verify_abort_equal(
293            EncodedValue { value_type: RawValueType::Key, value: 3 },
294            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
295        );
296        checker.verify_abort_equal(
297            EncodedValue { value_type: RawValueType::Key, value: 4 },
298            EncodedValue { value_type: RawValueType::EnumValue, value: 5 },
299        );
300        checker.verify_end();
301    }
302
303    #[test]
304    fn test_empty_symbol_table() {
305        let bind_rules = BindRules {
306            instructions: to_symbolic_inst_info(vec![SymbolicInstruction::UnconditionalAbort]),
307            symbol_table: HashMap::new(),
308            use_new_bytecode: true,
309            enable_debug: false,
310        };
311
312        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
313
314        checker.verify_bind_rules_header(false);
315        checker.verify_sym_table_header(0);
316        checker.verify_instructions_header(UNCOND_ABORT_BYTES);
317        checker.verify_unconditional_abort();
318        checker.verify_end();
319    }
320
321    #[test]
322    fn test_enable_debug_flag_missing_ast_location() {
323        let bind_rules = BindRules {
324            instructions: to_symbolic_inst_info(vec![SymbolicInstruction::UnconditionalAbort]),
325            symbol_table: HashMap::new(),
326            use_new_bytecode: true,
327            enable_debug: true,
328        };
329
330        assert_eq!(
331            Err(BindRulesEncodeError::MissingAstLocation),
332            encode_to_bytecode_v2(bind_rules)
333        );
334    }
335
336    #[test]
337    fn test_enable_debug_flag_false() {
338        let bind_rules = BindRules {
339            instructions: to_symbolic_inst_info(vec![SymbolicInstruction::UnconditionalAbort]),
340            symbol_table: HashMap::new(),
341            use_new_bytecode: true,
342            enable_debug: false,
343        };
344
345        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
346
347        checker.verify_bind_rules_header(false);
348        checker.verify_sym_table_header(0);
349        checker.verify_instructions_header(UNCOND_ABORT_BYTES);
350        checker.verify_unconditional_abort();
351        checker.verify_end();
352    }
353
354    #[test]
355    fn test_duplicate_symbols() {
356        let instructions = vec![
357            SymbolicInstruction::AbortIfNotEqual {
358                lhs: Symbol::DeprecatedKey(5),
359                rhs: Symbol::StringValue("puffleg".to_string()),
360            },
361            SymbolicInstruction::AbortIfEqual {
362                lhs: Symbol::Key("sunangel".to_string(), ValueType::Number),
363                rhs: Symbol::NumberValue(1),
364            },
365            SymbolicInstruction::AbortIfEqual {
366                lhs: Symbol::Key("puffleg".to_string(), ValueType::Number),
367                rhs: Symbol::NumberValue(1),
368            },
369            SymbolicInstruction::AbortIfNotEqual {
370                lhs: Symbol::DeprecatedKey(5),
371                rhs: Symbol::StringValue("sunangel".to_string()),
372            },
373            SymbolicInstruction::AbortIfEqual {
374                lhs: Symbol::Key("puffleg".to_string(), ValueType::Str),
375                rhs: Symbol::StringValue("sunangel".to_string()),
376            },
377            SymbolicInstruction::AbortIfEqual {
378                lhs: Symbol::Key("mountaingem".to_string(), ValueType::Str),
379                rhs: Symbol::StringValue("mountaingem".to_string()),
380            },
381        ];
382
383        let bind_rules = BindRules {
384            instructions: to_symbolic_inst_info(instructions),
385            symbol_table: HashMap::new(),
386            use_new_bytecode: true,
387            enable_debug: false,
388        };
389
390        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
391        checker.verify_bind_rules_header(false);
392        checker.verify_sym_table_header(41);
393
394        checker.verify_symbol_table(&["puffleg", "sunangel", "mountaingem"]);
395
396        checker.verify_instructions_header(COND_ABORT_BYTES * 6);
397        checker.verify_abort_not_equal(
398            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
399            EncodedValue { value_type: RawValueType::StringValue, value: 1 },
400        );
401        checker.verify_abort_equal(
402            EncodedValue { value_type: RawValueType::Key, value: 2 },
403            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
404        );
405        checker.verify_abort_equal(
406            EncodedValue { value_type: RawValueType::Key, value: 1 },
407            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
408        );
409        checker.verify_abort_not_equal(
410            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
411            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
412        );
413        checker.verify_abort_equal(
414            EncodedValue { value_type: RawValueType::Key, value: 1 },
415            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
416        );
417        checker.verify_abort_equal(
418            EncodedValue { value_type: RawValueType::Key, value: 3 },
419            EncodedValue { value_type: RawValueType::StringValue, value: 3 },
420        );
421    }
422
423    #[test]
424    fn test_long_string() {
425        let long_str = "loooooooooooooooooooooooooo\
426            oooooooooooooooooooooooooooo\
427            ooooooooooooooooooooooooooo\
428            ooooooooooooooooooooooong, \
429            loooooooooooooooooooooooooo\
430            ooooooooooooooooooooooooooo\
431            ooooooooooooooooooooooooooo\
432            ooooooooooooooooooooooooooo\
433            ooooooooooooooooooooooooooo\
434            oooooooong string";
435
436        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
437            lhs: Symbol::DeprecatedKey(5),
438            rhs: Symbol::StringValue(long_str.to_string()),
439        }];
440
441        let bind_rules = BindRules {
442            instructions: to_symbolic_inst_info(instructions),
443            symbol_table: HashMap::new(),
444            use_new_bytecode: true,
445            enable_debug: false,
446        };
447
448        assert_eq!(
449            Err(BindRulesEncodeError::InvalidStringLength(long_str.to_string())),
450            encode_to_bytecode_v2(bind_rules)
451        );
452    }
453
454    #[test]
455    fn test_abort_instructions() {
456        let instructions = vec![
457            SymbolicInstruction::AbortIfNotEqual {
458                lhs: Symbol::DeprecatedKey(5),
459                rhs: Symbol::NumberValue(100),
460            },
461            SymbolicInstruction::AbortIfEqual {
462                lhs: Symbol::DeprecatedKey(1),
463                rhs: Symbol::BoolValue(false),
464            },
465        ];
466
467        let bind_rules = BindRules {
468            instructions: to_symbolic_inst_info(instructions),
469            symbol_table: HashMap::new(),
470            use_new_bytecode: true,
471            enable_debug: false,
472        };
473
474        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
475        checker.verify_bind_rules_header(false);
476        checker.verify_sym_table_header(0);
477
478        checker.verify_instructions_header(COND_ABORT_BYTES + COND_ABORT_BYTES);
479        checker.verify_abort_not_equal(
480            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
481            EncodedValue { value_type: RawValueType::NumberValue, value: 100 },
482        );
483        checker.verify_abort_equal(
484            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
485            EncodedValue { value_type: RawValueType::BoolValue, value: 0 },
486        );
487        checker.verify_end();
488    }
489
490    #[test]
491    fn test_unconditional_jump_statement() {
492        let instructions = vec![
493            SymbolicInstruction::UnconditionalJump { label: 1 },
494            SymbolicInstruction::UnconditionalAbort,
495            SymbolicInstruction::Label(1),
496        ];
497
498        let bind_rules = BindRules {
499            instructions: to_symbolic_inst_info(instructions),
500            symbol_table: HashMap::new(),
501            use_new_bytecode: true,
502            enable_debug: false,
503        };
504
505        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
506        checker.verify_bind_rules_header(false);
507        checker.verify_sym_table_header(0);
508
509        checker.verify_instructions_header(UNCOND_JMP_BYTES + UNCOND_ABORT_BYTES + JMP_PAD_BYTES);
510        checker.verify_unconditional_jmp(UNCOND_ABORT_BYTES);
511        checker.verify_unconditional_abort();
512        checker.verify_jmp_pad();
513        checker.verify_end();
514    }
515
516    #[test]
517    fn test_jump_if_equal_statement() {
518        let instructions = vec![
519            SymbolicInstruction::JumpIfEqual {
520                lhs: Symbol::DeprecatedKey(15),
521                rhs: Symbol::NumberValue(12),
522                label: 1,
523            },
524            SymbolicInstruction::UnconditionalAbort,
525            SymbolicInstruction::AbortIfEqual {
526                lhs: Symbol::DeprecatedKey(10),
527                rhs: Symbol::NumberValue(10),
528            },
529            SymbolicInstruction::Label(1),
530        ];
531
532        let bind_rules = BindRules {
533            instructions: to_symbolic_inst_info(instructions),
534            symbol_table: HashMap::new(),
535            use_new_bytecode: true,
536            enable_debug: false,
537        };
538
539        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
540        checker.verify_bind_rules_header(false);
541        checker.verify_sym_table_header(0);
542
543        // Verify the instructions.
544        checker.verify_instructions_header(
545            COND_JMP_BYTES + UNCOND_ABORT_BYTES + COND_ABORT_BYTES + JMP_PAD_BYTES,
546        );
547        checker.verify_jmp_if_equal(
548            UNCOND_ABORT_BYTES + COND_ABORT_BYTES,
549            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
550            EncodedValue { value_type: RawValueType::NumberValue, value: 12 },
551        );
552        checker.verify_unconditional_abort();
553        checker.verify_abort_equal(
554            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
555            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
556        );
557        checker.verify_jmp_pad();
558
559        checker.verify_end();
560    }
561
562    #[test]
563    fn test_jump_if_not_equal_statement() {
564        let instructions = vec![
565            SymbolicInstruction::JumpIfNotEqual {
566                lhs: Symbol::DeprecatedKey(15),
567                rhs: Symbol::BoolValue(true),
568                label: 2,
569            },
570            SymbolicInstruction::UnconditionalAbort,
571            SymbolicInstruction::AbortIfEqual {
572                lhs: Symbol::DeprecatedKey(5),
573                rhs: Symbol::NumberValue(7),
574            },
575            SymbolicInstruction::AbortIfNotEqual {
576                lhs: Symbol::DeprecatedKey(2),
577                rhs: Symbol::BoolValue(true),
578            },
579            SymbolicInstruction::UnconditionalAbort,
580            SymbolicInstruction::Label(2),
581        ];
582
583        let bind_rules = BindRules {
584            instructions: to_symbolic_inst_info(instructions),
585            symbol_table: HashMap::new(),
586            use_new_bytecode: true,
587            enable_debug: false,
588        };
589
590        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
591        checker.verify_bind_rules_header(false);
592        checker.verify_sym_table_header(0);
593
594        let expected_bytes =
595            COND_JMP_BYTES + (UNCOND_ABORT_BYTES * 2) + (COND_ABORT_BYTES * 2) + JMP_PAD_BYTES;
596        checker.verify_instructions_header(expected_bytes);
597
598        // Verify Jump If Not Equal.
599        let expected_offset = (UNCOND_ABORT_BYTES * 2) + (COND_ABORT_BYTES * 2);
600        checker.verify_jmp_if_not_equal(
601            expected_offset,
602            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
603            EncodedValue { value_type: RawValueType::BoolValue, value: 1 },
604        );
605
606        // Verify abort statements.
607        checker.verify_unconditional_abort();
608        checker.verify_abort_equal(
609            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
610            EncodedValue { value_type: RawValueType::NumberValue, value: 7 },
611        );
612        checker.verify_abort_not_equal(
613            EncodedValue { value_type: RawValueType::NumberValue, value: 2 },
614            EncodedValue { value_type: RawValueType::BoolValue, value: 1 },
615        );
616        checker.verify_unconditional_abort();
617
618        checker.verify_jmp_pad();
619        checker.verify_end();
620    }
621
622    #[test]
623    fn test_nested_jump_statement() {
624        let instructions = vec![
625            SymbolicInstruction::JumpIfEqual {
626                lhs: Symbol::DeprecatedKey(10),
627                rhs: Symbol::NumberValue(11),
628                label: 1,
629            },
630            SymbolicInstruction::UnconditionalAbort,
631            SymbolicInstruction::UnconditionalJump { label: 2 },
632            SymbolicInstruction::AbortIfEqual {
633                lhs: Symbol::DeprecatedKey(5),
634                rhs: Symbol::NumberValue(7),
635            },
636            SymbolicInstruction::UnconditionalAbort,
637            SymbolicInstruction::Label(2),
638            SymbolicInstruction::UnconditionalAbort,
639            SymbolicInstruction::Label(1),
640        ];
641
642        let bind_rules = BindRules {
643            instructions: to_symbolic_inst_info(instructions),
644            symbol_table: HashMap::new(),
645            use_new_bytecode: true,
646            enable_debug: false,
647        };
648
649        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
650
651        checker.verify_bind_rules_header(false);
652        checker.verify_sym_table_header(0);
653
654        // Verify the instructions.
655        let nested_jmp_block_bytes =
656            UNCOND_JMP_BYTES + COND_ABORT_BYTES + UNCOND_ABORT_BYTES + JMP_PAD_BYTES;
657        let instructions_bytes = COND_JMP_BYTES
658            + UNCOND_ABORT_BYTES
659            + nested_jmp_block_bytes
660            + UNCOND_ABORT_BYTES
661            + JMP_PAD_BYTES;
662        checker.verify_instructions_header(instructions_bytes);
663
664        // Verify Jump If Equal.
665        let jmp_offset = UNCOND_ABORT_BYTES + nested_jmp_block_bytes + UNCOND_ABORT_BYTES;
666        checker.verify_jmp_if_equal(
667            jmp_offset,
668            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
669            EncodedValue { value_type: RawValueType::NumberValue, value: 11 },
670        );
671        checker.verify_unconditional_abort();
672
673        // Verify the nested jump block.
674        checker.verify_unconditional_jmp(COND_ABORT_BYTES + UNCOND_ABORT_BYTES);
675        checker.verify_abort_equal(
676            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
677            EncodedValue { value_type: RawValueType::NumberValue, value: 7 },
678        );
679        checker.verify_unconditional_abort();
680        checker.verify_jmp_pad();
681
682        checker.verify_unconditional_abort();
683        checker.verify_jmp_pad();
684        checker.verify_end();
685    }
686
687    #[test]
688    fn test_overlapping_jump_statements() {
689        let instructions = vec![
690            SymbolicInstruction::JumpIfEqual {
691                lhs: Symbol::DeprecatedKey(10),
692                rhs: Symbol::NumberValue(11),
693                label: 1,
694            },
695            SymbolicInstruction::UnconditionalAbort,
696            SymbolicInstruction::UnconditionalJump { label: 2 },
697            SymbolicInstruction::AbortIfEqual {
698                lhs: Symbol::DeprecatedKey(5),
699                rhs: Symbol::NumberValue(7),
700            },
701            SymbolicInstruction::Label(1),
702            SymbolicInstruction::UnconditionalAbort,
703            SymbolicInstruction::Label(2),
704        ];
705
706        let bind_rules = BindRules {
707            instructions: to_symbolic_inst_info(instructions),
708            symbol_table: HashMap::new(),
709            use_new_bytecode: true,
710            enable_debug: false,
711        };
712
713        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
714        checker.verify_bind_rules_header(false);
715        checker.verify_sym_table_header(0);
716
717        let instructions_bytes = COND_JMP_BYTES
718            + UNCOND_ABORT_BYTES
719            + UNCOND_JMP_BYTES
720            + COND_ABORT_BYTES
721            + JMP_PAD_BYTES
722            + UNCOND_ABORT_BYTES
723            + JMP_PAD_BYTES;
724        checker.verify_instructions_header(instructions_bytes);
725
726        let jmp_offset = UNCOND_ABORT_BYTES + UNCOND_JMP_BYTES + COND_ABORT_BYTES;
727        checker.verify_jmp_if_equal(
728            jmp_offset,
729            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
730            EncodedValue { value_type: RawValueType::NumberValue, value: 11 },
731        );
732        checker.verify_unconditional_abort();
733
734        let jmp_offset = COND_ABORT_BYTES + JMP_PAD_BYTES + UNCOND_ABORT_BYTES;
735        checker.verify_unconditional_jmp(jmp_offset);
736        checker.verify_abort_equal(
737            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
738            EncodedValue { value_type: RawValueType::NumberValue, value: 7 },
739        );
740
741        checker.verify_jmp_pad();
742        checker.verify_unconditional_abort();
743        checker.verify_jmp_pad();
744
745        checker.verify_end();
746    }
747
748    #[test]
749    fn test_same_label_statements() {
750        let instructions = vec![
751            SymbolicInstruction::UnconditionalJump { label: 1 },
752            SymbolicInstruction::AbortIfEqual {
753                lhs: Symbol::DeprecatedKey(5),
754                rhs: Symbol::NumberValue(7),
755            },
756            SymbolicInstruction::JumpIfEqual {
757                lhs: Symbol::DeprecatedKey(10),
758                rhs: Symbol::NumberValue(11),
759                label: 1,
760            },
761            SymbolicInstruction::UnconditionalAbort,
762            SymbolicInstruction::Label(1),
763        ];
764
765        let bind_rules = BindRules {
766            instructions: to_symbolic_inst_info(instructions),
767            symbol_table: HashMap::new(),
768            use_new_bytecode: true,
769            enable_debug: false,
770        };
771
772        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
773        checker.verify_bind_rules_header(false);
774        checker.verify_sym_table_header(0);
775
776        let instructions_bytes = UNCOND_JMP_BYTES
777            + COND_ABORT_BYTES
778            + COND_JMP_BYTES
779            + UNCOND_ABORT_BYTES
780            + JMP_PAD_BYTES;
781        checker.verify_instructions_header(instructions_bytes);
782
783        checker.verify_unconditional_jmp(COND_ABORT_BYTES + COND_JMP_BYTES + UNCOND_ABORT_BYTES);
784        checker.verify_abort_equal(
785            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
786            EncodedValue { value_type: RawValueType::NumberValue, value: 7 },
787        );
788        checker.verify_jmp_if_equal(
789            UNCOND_ABORT_BYTES,
790            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
791            EncodedValue { value_type: RawValueType::NumberValue, value: 11 },
792        );
793        checker.verify_unconditional_abort();
794        checker.verify_jmp_pad();
795        checker.verify_end();
796    }
797
798    #[test]
799    fn test_duplicate_label() {
800        let instructions = vec![
801            SymbolicInstruction::UnconditionalJump { label: 1 },
802            SymbolicInstruction::AbortIfEqual {
803                lhs: Symbol::DeprecatedKey(5),
804                rhs: Symbol::NumberValue(7),
805            },
806            SymbolicInstruction::Label(1),
807            SymbolicInstruction::UnconditionalAbort,
808            SymbolicInstruction::Label(1),
809        ];
810
811        let bind_rules = BindRules {
812            instructions: to_symbolic_inst_info(instructions),
813            symbol_table: HashMap::new(),
814            use_new_bytecode: true,
815            enable_debug: false,
816        };
817
818        assert_eq!(Err(BindRulesEncodeError::DuplicateLabel(1)), encode_to_bytecode_v2(bind_rules));
819    }
820
821    #[test]
822    fn test_unused_label() {
823        let instructions = vec![
824            SymbolicInstruction::UnconditionalAbort,
825            SymbolicInstruction::Label(1),
826            SymbolicInstruction::UnconditionalAbort,
827        ];
828
829        let bind_rules = BindRules {
830            instructions: to_symbolic_inst_info(instructions),
831            symbol_table: HashMap::new(),
832            use_new_bytecode: true,
833            enable_debug: false,
834        };
835
836        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
837
838        checker.verify_bind_rules_header(false);
839        checker.verify_sym_table_header(0);
840
841        checker.verify_instructions_header(UNCOND_ABORT_BYTES + JMP_PAD_BYTES + UNCOND_ABORT_BYTES);
842        checker.verify_unconditional_abort();
843        checker.verify_jmp_pad();
844        checker.verify_unconditional_abort();
845    }
846
847    #[test]
848    fn test_label_appears_before_jmp() {
849        let instructions = vec![
850            SymbolicInstruction::Label(1),
851            SymbolicInstruction::UnconditionalJump { label: 1 },
852            SymbolicInstruction::UnconditionalAbort,
853        ];
854
855        let bind_rules = BindRules {
856            instructions: to_symbolic_inst_info(instructions),
857            symbol_table: HashMap::new(),
858            use_new_bytecode: true,
859            enable_debug: false,
860        };
861
862        assert_eq!(
863            Err(BindRulesEncodeError::InvalidGotoLocation(1)),
864            encode_to_bytecode_v2(bind_rules)
865        );
866    }
867
868    #[test]
869    fn test_missing_label() {
870        let instructions = vec![
871            SymbolicInstruction::UnconditionalJump { label: 2 },
872            SymbolicInstruction::UnconditionalAbort,
873            SymbolicInstruction::Label(1),
874        ];
875
876        let bind_rules = BindRules {
877            instructions: to_symbolic_inst_info(instructions),
878            symbol_table: HashMap::new(),
879            use_new_bytecode: true,
880            enable_debug: false,
881        };
882
883        assert_eq!(Err(BindRulesEncodeError::MissingLabel(2)), encode_to_bytecode_v2(bind_rules));
884    }
885
886    #[test]
887    fn test_mismatch_value_types() {
888        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
889            lhs: Symbol::Key("waxwing".to_string(), ValueType::Number),
890            rhs: Symbol::BoolValue(true),
891        }];
892
893        let bind_rules = BindRules {
894            instructions: to_symbolic_inst_info(instructions),
895            symbol_table: HashMap::new(),
896            use_new_bytecode: true,
897            enable_debug: false,
898        };
899
900        assert_eq!(
901            Err(BindRulesEncodeError::MismatchValueTypes(ValueType::Number, ValueType::Bool)),
902            encode_to_bytecode_v2(bind_rules)
903        );
904    }
905
906    #[test]
907    fn test_string_enum_match_value_types() {
908        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
909            lhs: Symbol::Key("waxwing".to_string(), ValueType::Enum),
910            rhs: Symbol::StringValue("bohemian".to_string()),
911        }];
912
913        let bind_rules = BindRules {
914            instructions: to_symbolic_inst_info(instructions),
915            symbol_table: HashMap::new(),
916            use_new_bytecode: true,
917            enable_debug: false,
918        };
919
920        let mut checker = BytecodeChecker::new(encode_to_bytecode_v2(bind_rules).unwrap());
921
922        checker.verify_bind_rules_header(false);
923        checker.verify_sym_table_header(25);
924        checker.verify_symbol_table(&["waxwing", "bohemian"]);
925
926        checker.verify_instructions_header(COND_ABORT_BYTES);
927        checker.verify_abort_not_equal(
928            EncodedValue { value_type: RawValueType::Key, value: 1 },
929            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
930        );
931        checker.verify_end();
932    }
933
934    #[test]
935    fn test_invalid_lhs_symbol() {
936        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
937            lhs: Symbol::NumberValue(5),
938            rhs: Symbol::BoolValue(true),
939        }];
940
941        let bind_rules = BindRules {
942            instructions: to_symbolic_inst_info(instructions),
943            symbol_table: HashMap::new(),
944            use_new_bytecode: true,
945            enable_debug: false,
946        };
947
948        assert_eq!(
949            Err(BindRulesEncodeError::IncorrectTypesInValueComparison),
950            encode_to_bytecode_v2(bind_rules)
951        );
952    }
953
954    #[test]
955    fn test_invalid_rhs_symbol() {
956        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
957            lhs: Symbol::DeprecatedKey(5),
958            rhs: Symbol::DeprecatedKey(6),
959        }];
960        let bind_rules = BindRules {
961            instructions: to_symbolic_inst_info(instructions),
962            symbol_table: HashMap::new(),
963            use_new_bytecode: true,
964            enable_debug: false,
965        };
966        assert_eq!(
967            Err(BindRulesEncodeError::IncorrectTypesInValueComparison),
968            encode_to_bytecode_v2(bind_rules)
969        );
970
971        let instructions = vec![SymbolicInstruction::AbortIfNotEqual {
972            lhs: Symbol::DeprecatedKey(5),
973            rhs: Symbol::Key("wagtail".to_string(), ValueType::Number),
974        }];
975        let bind_rules = BindRules {
976            instructions: to_symbolic_inst_info(instructions),
977            symbol_table: HashMap::new(),
978            use_new_bytecode: true,
979            enable_debug: false,
980        };
981        assert_eq!(
982            Err(BindRulesEncodeError::IncorrectTypesInValueComparison),
983            encode_to_bytecode_v2(bind_rules)
984        );
985    }
986
987    #[test]
988    fn test_missing_match_instruction() {
989        let instructions =
990            vec![SymbolicInstruction::UnconditionalAbort, SymbolicInstruction::UnconditionalBind];
991
992        let bind_rules = BindRules {
993            instructions: to_symbolic_inst_info(instructions),
994            symbol_table: HashMap::new(),
995            use_new_bytecode: true,
996            enable_debug: false,
997        };
998
999        assert_eq!(Err(BindRulesEncodeError::MatchNotSupported), encode_to_bytecode_v2(bind_rules));
1000    }
1001
1002    #[test]
1003    fn test_composite() {
1004        let primary_node = vec![
1005            SymbolicInstruction::AbortIfEqual {
1006                lhs: Symbol::DeprecatedKey(1),
1007                rhs: Symbol::BoolValue(false),
1008            },
1009            SymbolicInstruction::UnconditionalAbort,
1010        ];
1011
1012        let additional_nodes = vec![
1013            SymbolicInstruction::JumpIfEqual {
1014                lhs: Symbol::DeprecatedKey(15),
1015                rhs: Symbol::StringValue("ruff".to_string()),
1016                label: 1,
1017            },
1018            SymbolicInstruction::AbortIfEqual {
1019                lhs: Symbol::Key("plover".to_string(), ValueType::Number),
1020                rhs: Symbol::NumberValue(1),
1021            },
1022            SymbolicInstruction::Label(1),
1023            SymbolicInstruction::UnconditionalAbort,
1024        ];
1025
1026        let bind_rules = CompositeBindRules {
1027            symbol_table: HashMap::new(),
1028            device_name: "wader".to_string(),
1029            primary_node: composite_node("stilt".to_string(), primary_node),
1030            additional_nodes: vec![composite_node("avocet".to_string(), additional_nodes)],
1031            optional_nodes: vec![],
1032            enable_debug: false,
1033        };
1034
1035        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1036        checker.verify_bind_rules_header(false);
1037        checker.verify_sym_table_header(51);
1038
1039        checker.verify_symbol_table(&["wader", "stilt", "avocet", "ruff", "plover"]);
1040
1041        let primary_node_bytes = COND_ABORT_BYTES + UNCOND_ABORT_BYTES;
1042        let additional_node_bytes =
1043            COND_JMP_BYTES + COND_ABORT_BYTES + JMP_PAD_BYTES + UNCOND_ABORT_BYTES;
1044        checker.verify_composite_header(
1045            (NODE_HEADER_BYTES * 2)
1046                + COMPOSITE_NAME_ID_BYTES
1047                + primary_node_bytes
1048                + additional_node_bytes,
1049        );
1050
1051        // Verify primary node.
1052        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1053        checker.verify_abort_equal(
1054            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1055            EncodedValue { value_type: RawValueType::BoolValue, value: 0 },
1056        );
1057        checker.verify_unconditional_abort();
1058
1059        // Verify additional node.
1060        checker.verify_node_header(RawNodeType::Additional, 3, additional_node_bytes);
1061        checker.verify_jmp_if_equal(
1062            COND_ABORT_BYTES,
1063            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
1064            EncodedValue { value_type: RawValueType::StringValue, value: 4 },
1065        );
1066        checker.verify_abort_equal(
1067            EncodedValue { value_type: RawValueType::Key, value: 5 },
1068            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1069        );
1070        checker.verify_jmp_pad();
1071        checker.verify_unconditional_abort();
1072        checker.verify_end();
1073    }
1074
1075    #[test]
1076    fn test_composite_optional() {
1077        let primary_node_inst = vec![
1078            SymbolicInstruction::AbortIfEqual {
1079                lhs: Symbol::DeprecatedKey(1),
1080                rhs: Symbol::BoolValue(false),
1081            },
1082            SymbolicInstruction::UnconditionalAbort,
1083        ];
1084
1085        let optional_node_inst = vec![
1086            SymbolicInstruction::JumpIfEqual {
1087                lhs: Symbol::DeprecatedKey(15),
1088                rhs: Symbol::StringValue("ruff".to_string()),
1089                label: 1,
1090            },
1091            SymbolicInstruction::AbortIfEqual {
1092                lhs: Symbol::Key("plover".to_string(), ValueType::Number),
1093                rhs: Symbol::NumberValue(1),
1094            },
1095            SymbolicInstruction::Label(1),
1096            SymbolicInstruction::UnconditionalAbort,
1097        ];
1098
1099        let bind_rules = CompositeBindRules {
1100            symbol_table: HashMap::new(),
1101            device_name: "wader".to_string(),
1102            primary_node: composite_node("stilt".to_string(), primary_node_inst),
1103            additional_nodes: vec![],
1104            optional_nodes: vec![composite_node("avocet".to_string(), optional_node_inst)],
1105            enable_debug: false,
1106        };
1107
1108        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1109        checker.verify_bind_rules_header(false);
1110        checker.verify_sym_table_header(51);
1111
1112        checker.verify_symbol_table(&["wader", "stilt", "avocet", "ruff", "plover"]);
1113
1114        let primary_node_bytes = COND_ABORT_BYTES + UNCOND_ABORT_BYTES;
1115        let optional_node_bytes =
1116            COND_JMP_BYTES + COND_ABORT_BYTES + JMP_PAD_BYTES + UNCOND_ABORT_BYTES;
1117        checker.verify_composite_header(
1118            (NODE_HEADER_BYTES * 2)
1119                + COMPOSITE_NAME_ID_BYTES
1120                + primary_node_bytes
1121                + optional_node_bytes,
1122        );
1123
1124        // Verify primary node.
1125        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1126        checker.verify_abort_equal(
1127            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1128            EncodedValue { value_type: RawValueType::BoolValue, value: 0 },
1129        );
1130        checker.verify_unconditional_abort();
1131
1132        // Verify optional node.
1133        checker.verify_node_header(RawNodeType::Optional, 3, optional_node_bytes);
1134        checker.verify_jmp_if_equal(
1135            COND_ABORT_BYTES,
1136            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
1137            EncodedValue { value_type: RawValueType::StringValue, value: 4 },
1138        );
1139        checker.verify_abort_equal(
1140            EncodedValue { value_type: RawValueType::Key, value: 5 },
1141            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1142        );
1143        checker.verify_jmp_pad();
1144        checker.verify_unconditional_abort();
1145        checker.verify_end();
1146    }
1147
1148    #[test]
1149    fn test_composite_additional_and_optional() {
1150        let primary_node_inst = vec![
1151            SymbolicInstruction::AbortIfEqual {
1152                lhs: Symbol::DeprecatedKey(1),
1153                rhs: Symbol::BoolValue(false),
1154            },
1155            SymbolicInstruction::UnconditionalAbort,
1156        ];
1157
1158        let additional_node_inst = vec![
1159            SymbolicInstruction::AbortIfEqual {
1160                lhs: Symbol::Key("thrasher".to_string(), ValueType::Str),
1161                rhs: Symbol::StringValue("catbird".to_string()),
1162            },
1163            SymbolicInstruction::AbortIfEqual {
1164                lhs: Symbol::Key("catbird".to_string(), ValueType::Number),
1165                rhs: Symbol::NumberValue(1),
1166            },
1167        ];
1168
1169        let optional_node_inst = vec![
1170            SymbolicInstruction::JumpIfEqual {
1171                lhs: Symbol::DeprecatedKey(15),
1172                rhs: Symbol::StringValue("ruff".to_string()),
1173                label: 1,
1174            },
1175            SymbolicInstruction::AbortIfEqual {
1176                lhs: Symbol::Key("plover".to_string(), ValueType::Number),
1177                rhs: Symbol::NumberValue(1),
1178            },
1179            SymbolicInstruction::Label(1),
1180            SymbolicInstruction::UnconditionalAbort,
1181        ];
1182
1183        let bind_rules = CompositeBindRules {
1184            symbol_table: HashMap::new(),
1185            device_name: "wader".to_string(),
1186            primary_node: composite_node("stilt".to_string(), primary_node_inst),
1187            additional_nodes: vec![composite_node("avocet".to_string(), additional_node_inst)],
1188            optional_nodes: vec![composite_node("mockingbird".to_string(), optional_node_inst)],
1189            enable_debug: false,
1190        };
1191
1192        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1193        checker.verify_bind_rules_header(false);
1194        checker.verify_sym_table_header(92);
1195
1196        checker.verify_symbol_table(&[
1197            "wader",
1198            "stilt",
1199            "avocet",
1200            "thrasher",
1201            "catbird",
1202            "mockingbird",
1203            "ruff",
1204            "plover",
1205        ]);
1206
1207        let primary_node_bytes = COND_ABORT_BYTES + UNCOND_ABORT_BYTES;
1208        let additional_node_bytes = COND_ABORT_BYTES * 2;
1209        let optional_node_bytes =
1210            COND_JMP_BYTES + COND_ABORT_BYTES + JMP_PAD_BYTES + UNCOND_ABORT_BYTES;
1211        checker.verify_composite_header(
1212            (NODE_HEADER_BYTES * 3)
1213                + COMPOSITE_NAME_ID_BYTES
1214                + primary_node_bytes
1215                + additional_node_bytes
1216                + optional_node_bytes,
1217        );
1218
1219        // Verify primary node.
1220        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1221        checker.verify_abort_equal(
1222            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1223            EncodedValue { value_type: RawValueType::BoolValue, value: 0 },
1224        );
1225        checker.verify_unconditional_abort();
1226
1227        // Verify additional node.
1228        checker.verify_node_header(RawNodeType::Additional, 3, additional_node_bytes);
1229        checker.verify_abort_equal(
1230            EncodedValue { value_type: RawValueType::Key, value: 4 },
1231            EncodedValue { value_type: RawValueType::StringValue, value: 5 },
1232        );
1233        checker.verify_abort_equal(
1234            EncodedValue { value_type: RawValueType::Key, value: 5 },
1235            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1236        );
1237
1238        // Verify optional node.
1239        checker.verify_node_header(RawNodeType::Optional, 6, optional_node_bytes);
1240        checker.verify_jmp_if_equal(
1241            COND_ABORT_BYTES,
1242            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
1243            EncodedValue { value_type: RawValueType::StringValue, value: 7 },
1244        );
1245        checker.verify_abort_equal(
1246            EncodedValue { value_type: RawValueType::Key, value: 8 },
1247            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1248        );
1249        checker.verify_jmp_pad();
1250        checker.verify_unconditional_abort();
1251        checker.verify_end();
1252    }
1253
1254    #[test]
1255    fn test_composite_enable_debug_missing_ast_location() {
1256        let primary_node = vec![
1257            SymbolicInstruction::AbortIfEqual {
1258                lhs: Symbol::DeprecatedKey(1),
1259                rhs: Symbol::BoolValue(false),
1260            },
1261            SymbolicInstruction::UnconditionalAbort,
1262        ];
1263
1264        let additional_nodes = vec![
1265            SymbolicInstruction::JumpIfEqual {
1266                lhs: Symbol::DeprecatedKey(15),
1267                rhs: Symbol::StringValue("ruff".to_string()),
1268                label: 1,
1269            },
1270            SymbolicInstruction::AbortIfEqual {
1271                lhs: Symbol::Key("plover".to_string(), ValueType::Number),
1272                rhs: Symbol::NumberValue(1),
1273            },
1274            SymbolicInstruction::Label(1),
1275            SymbolicInstruction::UnconditionalAbort,
1276        ];
1277
1278        let bind_rules = CompositeBindRules {
1279            symbol_table: HashMap::new(),
1280            device_name: "wader".to_string(),
1281            primary_node: composite_node("stilt".to_string(), primary_node),
1282            additional_nodes: vec![composite_node("avocet".to_string(), additional_nodes)],
1283            optional_nodes: vec![],
1284            enable_debug: true,
1285        };
1286
1287        assert_eq!(
1288            Err(BindRulesEncodeError::MissingAstLocation),
1289            encode_composite_to_bytecode(bind_rules)
1290        );
1291    }
1292
1293    #[test]
1294    fn test_composite_sharing_symbols() {
1295        let primary_node_inst = vec![SymbolicInstruction::AbortIfEqual {
1296            lhs: Symbol::Key("trembler".to_string(), ValueType::Str),
1297            rhs: Symbol::StringValue("thrasher".to_string()),
1298        }];
1299
1300        let additional_node_inst = vec![
1301            SymbolicInstruction::AbortIfEqual {
1302                lhs: Symbol::Key("thrasher".to_string(), ValueType::Str),
1303                rhs: Symbol::StringValue("catbird".to_string()),
1304            },
1305            SymbolicInstruction::AbortIfEqual {
1306                lhs: Symbol::Key("catbird".to_string(), ValueType::Number),
1307                rhs: Symbol::NumberValue(1),
1308            },
1309        ];
1310
1311        let bind_rules = CompositeBindRules {
1312            device_name: "mimid".to_string(),
1313            symbol_table: HashMap::new(),
1314            primary_node: composite_node("catbird".to_string(), primary_node_inst),
1315            additional_nodes: vec![composite_node("mockingbird".to_string(), additional_node_inst)],
1316            optional_nodes: vec![],
1317            enable_debug: false,
1318        };
1319
1320        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1321        checker.verify_bind_rules_header(false);
1322
1323        checker.verify_sym_table_header(64);
1324        checker.verify_symbol_table(&["mimid", "catbird", "trembler", "thrasher", "mockingbird"]);
1325
1326        let primary_node_bytes = COND_ABORT_BYTES;
1327        let additional_node_bytes = COND_ABORT_BYTES * 2;
1328        checker.verify_composite_header(
1329            (NODE_HEADER_BYTES * 2)
1330                + COMPOSITE_NAME_ID_BYTES
1331                + primary_node_bytes
1332                + additional_node_bytes,
1333        );
1334
1335        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1336        checker.verify_abort_equal(
1337            EncodedValue { value_type: RawValueType::Key, value: 3 },
1338            EncodedValue { value_type: RawValueType::StringValue, value: 4 },
1339        );
1340
1341        checker.verify_node_header(RawNodeType::Additional, 5, additional_node_bytes);
1342        checker.verify_abort_equal(
1343            EncodedValue { value_type: RawValueType::Key, value: 4 },
1344            EncodedValue { value_type: RawValueType::StringValue, value: 2 },
1345        );
1346        checker.verify_abort_equal(
1347            EncodedValue { value_type: RawValueType::Key, value: 2 },
1348            EncodedValue { value_type: RawValueType::NumberValue, value: 1 },
1349        );
1350        checker.verify_end();
1351    }
1352
1353    #[test]
1354    fn test_composite_same_label_id() {
1355        let primary_node_inst = vec![
1356            SymbolicInstruction::JumpIfEqual {
1357                lhs: Symbol::DeprecatedKey(15),
1358                rhs: Symbol::NumberValue(5),
1359                label: 1,
1360            },
1361            SymbolicInstruction::UnconditionalAbort,
1362            SymbolicInstruction::Label(1),
1363        ];
1364
1365        let additional_node_inst = vec![
1366            SymbolicInstruction::JumpIfEqual {
1367                lhs: Symbol::DeprecatedKey(10),
1368                rhs: Symbol::NumberValue(2),
1369                label: 1,
1370            },
1371            SymbolicInstruction::UnconditionalAbort,
1372            SymbolicInstruction::Label(1),
1373        ];
1374
1375        let bind_rules = CompositeBindRules {
1376            device_name: "currawong".to_string(),
1377            symbol_table: HashMap::new(),
1378            primary_node: composite_node("butcherbird".to_string(), primary_node_inst),
1379            additional_nodes: vec![composite_node("bushshrike".to_string(), additional_node_inst)],
1380            optional_nodes: vec![],
1381            enable_debug: false,
1382        };
1383
1384        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1385        checker.verify_bind_rules_header(false);
1386
1387        checker.verify_sym_table_header(45);
1388        checker.verify_symbol_table(&["currawong", "butcherbird", "bushshrike"]);
1389
1390        let primary_node_bytes = COND_JMP_BYTES + UNCOND_ABORT_BYTES + JMP_PAD_BYTES;
1391        let additional_node_bytes = COND_JMP_BYTES + UNCOND_ABORT_BYTES + JMP_PAD_BYTES;
1392        checker.verify_composite_header(
1393            (NODE_HEADER_BYTES * 2)
1394                + COMPOSITE_NAME_ID_BYTES
1395                + primary_node_bytes
1396                + additional_node_bytes,
1397        );
1398
1399        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1400        checker.verify_jmp_if_equal(
1401            UNCOND_ABORT_BYTES,
1402            EncodedValue { value_type: RawValueType::NumberValue, value: 15 },
1403            EncodedValue { value_type: RawValueType::NumberValue, value: 5 },
1404        );
1405        checker.verify_unconditional_abort();
1406        checker.verify_jmp_pad();
1407
1408        checker.verify_node_header(RawNodeType::Additional, 3, additional_node_bytes);
1409        checker.verify_jmp_if_equal(
1410            UNCOND_ABORT_BYTES,
1411            EncodedValue { value_type: RawValueType::NumberValue, value: 10 },
1412            EncodedValue { value_type: RawValueType::NumberValue, value: 2 },
1413        );
1414        checker.verify_unconditional_abort();
1415        checker.verify_jmp_pad();
1416
1417        checker.verify_end();
1418    }
1419
1420    #[test]
1421    fn test_composite_invalid_label() {
1422        let primary_node_inst = vec![
1423            SymbolicInstruction::JumpIfEqual {
1424                lhs: Symbol::DeprecatedKey(15),
1425                rhs: Symbol::NumberValue(5),
1426                label: 1,
1427            },
1428            SymbolicInstruction::UnconditionalAbort,
1429            SymbolicInstruction::Label(1),
1430        ];
1431
1432        let additional_node_inst = vec![
1433            SymbolicInstruction::JumpIfEqual {
1434                lhs: Symbol::DeprecatedKey(10),
1435                rhs: Symbol::NumberValue(2),
1436                label: 1,
1437            },
1438            SymbolicInstruction::UnconditionalAbort,
1439            SymbolicInstruction::Label(2),
1440        ];
1441
1442        let bind_rules = CompositeBindRules {
1443            device_name: "currawong".to_string(),
1444            symbol_table: HashMap::new(),
1445            primary_node: composite_node("butcherbird".to_string(), primary_node_inst),
1446            additional_nodes: vec![composite_node("bushshrike".to_string(), additional_node_inst)],
1447            optional_nodes: vec![],
1448            enable_debug: false,
1449        };
1450
1451        assert_eq!(
1452            Err(BindRulesEncodeError::MissingLabel(1)),
1453            encode_composite_to_bytecode(bind_rules)
1454        );
1455    }
1456
1457    #[test]
1458    fn test_composite_primary_node_only() {
1459        let primary_node_inst = vec![SymbolicInstruction::UnconditionalAbort];
1460
1461        let bind_rules = CompositeBindRules {
1462            device_name: "treehunter".to_string(),
1463            symbol_table: HashMap::new(),
1464            additional_nodes: vec![],
1465            optional_nodes: vec![],
1466            primary_node: composite_node("bananaquit".to_string(), primary_node_inst),
1467            enable_debug: false,
1468        };
1469
1470        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1471        checker.verify_bind_rules_header(false);
1472
1473        checker.verify_sym_table_header(30);
1474        checker.verify_symbol_table(&["treehunter", "bananaquit"]);
1475
1476        let primary_node_bytes = UNCOND_ABORT_BYTES;
1477        checker.verify_composite_header(
1478            NODE_HEADER_BYTES + COMPOSITE_NAME_ID_BYTES + primary_node_bytes,
1479        );
1480
1481        checker.verify_node_header(RawNodeType::Primary, 2, primary_node_bytes);
1482        checker.verify_unconditional_abort();
1483
1484        checker.verify_end();
1485    }
1486
1487    #[test]
1488    fn test_composite_empty_node_instructions() {
1489        let bind_rules = CompositeBindRules {
1490            device_name: "spiderhunter".to_string(),
1491            symbol_table: HashMap::new(),
1492            primary_node: composite_node("sunbird".to_string(), vec![]),
1493            additional_nodes: vec![],
1494            optional_nodes: vec![],
1495            enable_debug: false,
1496        };
1497
1498        let mut checker = BytecodeChecker::new(encode_composite_to_bytecode(bind_rules).unwrap());
1499        checker.verify_bind_rules_header(false);
1500
1501        checker.verify_sym_table_header(29);
1502        checker.verify_symbol_table(&["spiderhunter", "sunbird"]);
1503
1504        checker.verify_composite_header(NODE_HEADER_BYTES + COMPOSITE_NAME_ID_BYTES);
1505        checker.verify_node_header(RawNodeType::Primary, 2, 0);
1506
1507        checker.verify_end();
1508    }
1509
1510    #[test]
1511    fn test_composite_missing_device_name() {
1512        let bind_rules = CompositeBindRules {
1513            device_name: "".to_string(),
1514            symbol_table: HashMap::new(),
1515            primary_node: composite_node("pewee".to_string(), vec![]),
1516            additional_nodes: vec![],
1517            optional_nodes: vec![],
1518            enable_debug: false,
1519        };
1520
1521        assert_eq!(
1522            Err(BindRulesEncodeError::MissingCompositeDeviceName),
1523            encode_composite_to_bytecode(bind_rules)
1524        );
1525    }
1526
1527    #[test]
1528    fn test_composite_missing_node_name() {
1529        let bind_rules = CompositeBindRules {
1530            device_name: "pewee".to_string(),
1531            symbol_table: HashMap::new(),
1532            primary_node: composite_node("".to_string(), vec![]),
1533            additional_nodes: vec![],
1534            optional_nodes: vec![],
1535            enable_debug: false,
1536        };
1537
1538        assert_eq!(
1539            Err(BindRulesEncodeError::MissingCompositeNodeName),
1540            encode_composite_to_bytecode(bind_rules)
1541        );
1542    }
1543
1544    #[test]
1545    fn test_duplicate_nodes() {
1546        let bind_rules = CompositeBindRules {
1547            device_name: "flycatcher".to_string(),
1548            symbol_table: HashMap::new(),
1549            primary_node: composite_node("pewee".to_string(), vec![]),
1550            additional_nodes: vec![composite_node("pewee".to_string(), vec![])],
1551            optional_nodes: vec![],
1552            enable_debug: false,
1553        };
1554
1555        assert_eq!(
1556            Err(BindRulesEncodeError::DuplicateCompositeNodeName("pewee".to_string())),
1557            encode_composite_to_bytecode(bind_rules)
1558        );
1559
1560        let bind_rules = CompositeBindRules {
1561            device_name: "flycatcher".to_string(),
1562            symbol_table: HashMap::new(),
1563            primary_node: composite_node("pewee".to_string(), vec![]),
1564            additional_nodes: vec![
1565                composite_node("phoebe".to_string(), vec![]),
1566                composite_node("kingbird".to_string(), vec![]),
1567                composite_node("phoebe".to_string(), vec![]),
1568            ],
1569            optional_nodes: vec![],
1570            enable_debug: false,
1571        };
1572
1573        assert_eq!(
1574            Err(BindRulesEncodeError::DuplicateCompositeNodeName("phoebe".to_string())),
1575            encode_composite_to_bytecode(bind_rules)
1576        );
1577    }
1578}