swamp_script_vm_test/
util.rs

1use std::ptr;
2use swamp_script_code_gen::{CodeGenState, Error};
3use swamp_script_code_gen_program::code_gen_program;
4use swamp_script_compile::Program;
5use swamp_script_compile::compile_string;
6use swamp_script_source_map_lookup::SourceMapWrapper;
7use swamp_vm::host::HostArgs;
8use swamp_vm::{Vm, VmSetup};
9use swamp_vm_disasm::{disasm_instructions_color, disasm_instructions_no_color};
10use swamp_vm_types::InstructionPosition;
11
12fn gen_internal(code: &str) -> Result<(CodeGenState, Program), Error> {
13    let (program, main_module, source_map) = compile_string(code).unwrap();
14
15    let source_map_wrapper = SourceMapWrapper {
16        source_map: &source_map,
17        current_dir: Default::default(),
18    };
19    let code_gen = code_gen_program(&program, &main_module, &source_map_wrapper)?;
20
21    Ok((code_gen, program))
22}
23
24fn gen_internal_debug(code: &str) -> Result<(CodeGenState, Program), Error> {
25    let (code_gen, program) = gen_internal(code)?;
26    let disassembler_output = disasm_instructions_color(
27        code_gen.instructions(),
28        &InstructionPosition(0),
29        code_gen.comments(),
30        &code_gen.create_function_sections(),
31    );
32
33    eprintln!("{disassembler_output}");
34
35    Ok((code_gen, program))
36}
37
38fn exec_code_gen_state(code_gen_state: CodeGenState) -> Vm {
39    let (instructions, mut constants_memory, constant_functions) =
40        code_gen_state.take_instructions_and_constants();
41
42    for (_constant_id, constant_func) in constant_functions {
43        let setup = VmSetup {
44            frame_memory_size: 1024,
45            heap_memory_size: 1024,
46            constant_memory: constants_memory.clone(),
47        };
48
49        let mut vm = Vm::new(instructions.clone(), setup);
50
51        vm.execute_from_ip(&constant_func.ip);
52
53        unsafe {
54            ptr::copy_nonoverlapping(
55                vm.frame_memory().as_ptr(),
56                constants_memory
57                    .as_mut_ptr()
58                    .add(constant_func.target_constant_memory.addr.0 as usize),
59                constant_func.target_constant_memory.size.0 as usize,
60            );
61        }
62    }
63
64    let setup = VmSetup {
65        frame_memory_size: 1024,
66        heap_memory_size: 1024,
67        constant_memory: constants_memory,
68    };
69    let mut vm = Vm::new(instructions, setup);
70
71    vm.execute();
72
73    vm
74}
75
76pub fn exec_internal(code: &str) -> Result<Vm, Error> {
77    let (code_gen, _program) = gen_internal_debug(code)?;
78
79    Ok(exec_code_gen_state(code_gen))
80}
81
82fn exec_internal_debug(code: &str) -> Result<Vm, Error> {
83    exec_internal(code)
84}
85
86fn trim_lines(text: &str) -> String {
87    text.lines()
88        .map(|line| {
89            // Ignore comments that starts with ;
90            line.split(';').next().unwrap_or("").trim()
91        })
92        .filter(|line| !line.is_empty())
93        .collect::<Vec<_>>()
94        .join("\n")
95}
96
97fn compare_line_outputs(encountered: &str, expected: &str) {
98    let encountered_trimmed = trim_lines(encountered);
99    let expected_trimmed = trim_lines(expected);
100
101    eprintln!("{encountered}");
102    assert_eq!(encountered_trimmed, expected_trimmed);
103}
104
105fn compare_hex_outputs(memory: &[u8], expected_hex: &str) {
106    let encountered_hexed = hexify::format_hex(memory);
107    let expected_hex_trimmed = expected_hex.trim();
108
109    compare_line_outputs(&encountered_hexed, expected_hex_trimmed);
110}
111
112fn exec(code: &str, expected_hex: &str) {
113    let vm = exec_internal_debug(code).expect("should work");
114
115    compare_hex_outputs(&vm.stack_memory()[..16], expected_hex);
116}
117
118pub fn exec_with_assembly(code: &str, expected_assembly: &str, expected_hex: &str) {
119    let (generator, _program) = gen_internal_debug(code).expect("should work");
120
121    let disassembler_output = disasm_instructions_no_color(
122        generator.instructions(),
123        generator.comments(),
124        &generator.create_function_sections(),
125        false,
126    );
127    compare_line_outputs(&disassembler_output, expected_assembly);
128
129    let vm = exec_code_gen_state(generator);
130
131    compare_hex_outputs(&vm.frame_memory()[..16], expected_hex);
132}
133
134/// # Panics
135///
136pub fn exec_with_host_function<F>(
137    code: &str,
138    expected_assembly: &str,
139    expected_hex: &str,
140    id: &str,
141    callback: F,
142) where
143    F: 'static + FnMut(HostArgs),
144{
145    let vm = exec_with_host_function_internal(code, expected_assembly, id, callback);
146    compare_hex_outputs(&vm.frame_memory()[..16], expected_hex);
147}
148
149/// # Panics
150///
151pub fn exec_with_host_function_internal<F>(
152    code: &str,
153    expected_assembly: &str,
154    id: &str,
155    callback: F,
156) -> Vm
157where
158    F: 'static + FnMut(HostArgs),
159{
160    let (generator, program) = gen_internal_debug(code).expect("should work");
161
162    let disassembler_output = disasm_instructions_no_color(
163        generator.instructions(),
164        generator.comments(),
165        &generator.create_function_sections(),
166        false,
167    );
168    compare_line_outputs(&disassembler_output, expected_assembly);
169
170    let module = program
171        .modules
172        .get(&["crate".to_string(), "test".to_string()])
173        .unwrap();
174
175    let external_id = module
176        .symbol_table
177        .get_external_function_declaration(id)
178        .unwrap();
179
180    let (instructions, constants, constant_infos) = generator.take_instructions_and_constants();
181    let setup = VmSetup {
182        frame_memory_size: 1024,
183        heap_memory_size: 1024,
184        constant_memory: constants,
185    };
186    let mut vm = Vm::new(instructions, setup);
187
188    vm.add_host_function(external_id.id as u16, callback);
189
190    vm.execute();
191
192    vm
193}
194
195/// # Panics
196///
197pub fn exec_with_host_function_show_heap<F>(
198    code: &str,
199    expected_assembly: &str,
200    start: usize,
201    count: usize,
202    expected_hex: &str,
203    id: &str,
204    callback: F,
205) where
206    F: 'static + FnMut(HostArgs),
207{
208    let vm = exec_with_host_function_internal(code, expected_assembly, id, callback);
209    compare_hex_outputs(&vm.heap_memory()[start..start + count], expected_hex);
210}
211
212fn exec_vars(code: &str, expected_hex: &str) {
213    let vm = exec_internal_debug(code);
214
215    compare_hex_outputs(&vm.unwrap().frame_memory()[..16], expected_hex);
216}
217
218fn gen_code(code: &str, expected_output: &str) {
219    let (generator, _program) = gen_internal_debug(code).expect("should work");
220
221    let disassembler_output = disasm_instructions_no_color(
222        generator.instructions(),
223        generator.comments(),
224        &generator.create_function_sections(),
225        false,
226    );
227
228    compare_line_outputs(&disassembler_output, expected_output);
229}
230
231pub fn exec_show_constants(code: &str, expected_hex: &str, expected_constants: &str) {
232    let vm = exec_internal_debug(code).unwrap();
233
234    compare_hex_outputs(&vm.stack_memory()[..16], expected_hex);
235    compare_hex_outputs(&vm.constants()[..16], expected_constants);
236}