solverforge_core/wasm/
generator.rs

1use crate::domain::DomainModel;
2use crate::error::{SolverForgeError, SolverForgeResult};
3use crate::wasm::expression::Expression;
4use crate::wasm::memory::{LayoutCalculator, WasmMemoryType};
5use base64::{engine::general_purpose::STANDARD, Engine};
6use indexmap::IndexMap;
7use wasm_encoder::{
8    CodeSection, ConstExpr, ExportKind, ExportSection, Function, FunctionSection, GlobalSection,
9    GlobalType, Instruction, MemorySection, MemoryType, Module, TypeSection, ValType,
10};
11
12use crate::wasm::host_functions::{HostFunctionRegistry, WasmType};
13
14pub struct WasmModuleBuilder {
15    layout_calculator: LayoutCalculator,
16    domain_model: Option<DomainModel>,
17    predicates: IndexMap<String, PredicateDefinition>,
18    function_types: Vec<FunctionSignature>,
19    initial_memory_pages: u32,
20    max_memory_pages: Option<u32>,
21    host_functions: HostFunctionRegistry,
22    host_function_indices: IndexMap<String, u32>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26struct FunctionSignature {
27    params: Vec<ValType>,
28    results: Vec<ValType>,
29}
30
31#[derive(Debug, Clone)]
32pub enum PredicateBody {
33    Comparison(Comparison),
34    Expression(Expression),
35}
36
37#[derive(Debug, Clone)]
38pub struct PredicateDefinition {
39    pub name: String,
40    pub arity: u32,
41    pub body: PredicateBody,
42    /// Parameter types for the predicate function. If None, defaults to all i32.
43    pub param_types: Option<Vec<ValType>>,
44}
45
46impl PredicateDefinition {
47    pub fn new(name: impl Into<String>, arity: u32, comparison: Comparison) -> Self {
48        Self {
49            name: name.into(),
50            arity,
51            body: PredicateBody::Comparison(comparison),
52            param_types: None,
53        }
54    }
55
56    pub fn from_expression(name: impl Into<String>, arity: u32, expression: Expression) -> Self {
57        Self {
58            name: name.into(),
59            arity,
60            body: PredicateBody::Expression(expression),
61            param_types: None,
62        }
63    }
64
65    /// Create a predicate from an expression with explicit parameter types.
66    /// Useful for functions that take non-i32 parameters (e.g., f32 for loadBalance unfairness).
67    pub fn from_expression_with_types(
68        name: impl Into<String>,
69        param_types: Vec<ValType>,
70        expression: Expression,
71    ) -> Self {
72        Self {
73            name: name.into(),
74            arity: param_types.len() as u32,
75            body: PredicateBody::Expression(expression),
76            param_types: Some(param_types),
77        }
78    }
79
80    pub fn always_true(name: impl Into<String>, arity: u32) -> Self {
81        Self::new(name, arity, Comparison::AlwaysTrue)
82    }
83
84    pub fn equal(name: impl Into<String>, left: FieldAccess, right: FieldAccess) -> Self {
85        Self::new(name, 2, Comparison::Equal(left, right))
86    }
87}
88
89#[derive(Debug, Clone)]
90pub struct FieldAccess {
91    pub param_index: u32,
92    pub class_name: String,
93    pub field_name: String,
94}
95
96impl FieldAccess {
97    pub fn new(
98        param_index: u32,
99        class_name: impl Into<String>,
100        field_name: impl Into<String>,
101    ) -> Self {
102        Self {
103            param_index,
104            class_name: class_name.into(),
105            field_name: field_name.into(),
106        }
107    }
108}
109
110#[derive(Debug, Clone)]
111pub enum Comparison {
112    Equal(FieldAccess, FieldAccess),
113    NotEqual(FieldAccess, FieldAccess),
114    LessThan(FieldAccess, FieldAccess),
115    LessThanOrEqual(FieldAccess, FieldAccess),
116    GreaterThan(FieldAccess, FieldAccess),
117    GreaterThanOrEqual(FieldAccess, FieldAccess),
118    AlwaysTrue,
119    AlwaysFalse,
120}
121
122impl Default for WasmModuleBuilder {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128impl WasmModuleBuilder {
129    pub fn new() -> Self {
130        Self {
131            layout_calculator: LayoutCalculator::new(),
132            domain_model: None,
133            predicates: IndexMap::new(),
134            function_types: Vec::new(),
135            initial_memory_pages: 16,
136            max_memory_pages: Some(256),
137            host_functions: HostFunctionRegistry::new(),
138            host_function_indices: IndexMap::new(),
139        }
140    }
141
142    pub fn with_host_functions(mut self, registry: HostFunctionRegistry) -> Self {
143        self.host_functions = registry;
144        self
145    }
146
147    pub fn with_domain_model(mut self, model: DomainModel) -> Self {
148        for class in model.classes.values() {
149            self.layout_calculator.calculate_layout(class);
150        }
151        self.domain_model = Some(model);
152        self
153    }
154
155    pub fn with_initial_memory(mut self, pages: u32) -> Self {
156        self.initial_memory_pages = pages;
157        self
158    }
159
160    pub fn with_max_memory(mut self, pages: Option<u32>) -> Self {
161        self.max_memory_pages = pages;
162        self
163    }
164
165    pub fn add_predicate(mut self, predicate: PredicateDefinition) -> Self {
166        self.predicates.insert(predicate.name.clone(), predicate);
167        self
168    }
169
170    pub fn build(mut self) -> SolverForgeResult<Vec<u8>> {
171        let model = self
172            .domain_model
173            .take()
174            .ok_or_else(|| SolverForgeError::WasmGeneration("Domain model not set".to_string()))?;
175
176        let mut module = Module::new();
177
178        let mut type_section = TypeSection::new();
179        let mut import_section = wasm_encoder::ImportSection::new();
180        let mut function_section = FunctionSection::new();
181        let mut code_section = CodeSection::new();
182        let mut export_section = ExportSection::new();
183        let mut memory_section = MemorySection::new();
184        let mut global_section = GlobalSection::new();
185
186        memory_section.memory(MemoryType {
187            minimum: self.initial_memory_pages as u64,
188            maximum: self.max_memory_pages.map(|p| p as u64),
189            memory64: false,
190            shared: false,
191            page_size_log2: None,
192        });
193
194        // Global for bump allocator pointer (starts after first page for safety)
195        global_section.global(
196            GlobalType {
197                val_type: ValType::I32,
198                mutable: true,
199                shared: false,
200            },
201            &ConstExpr::i32_const(1024),
202        );
203
204        // Generate host function imports
205        let mut func_idx: u32 = 0;
206        let func_names: Vec<String> = self
207            .host_functions
208            .function_names()
209            .iter()
210            .map(|s| s.to_string())
211            .collect();
212        for func_name in func_names {
213            if let Some(host_func) = self.host_functions.lookup(&func_name).cloned() {
214                // Add function type for the import
215                let params: Vec<ValType> = host_func
216                    .params
217                    .iter()
218                    .map(|t| wasm_type_to_val_type(*t))
219                    .collect();
220                let results: Vec<ValType> = if matches!(host_func.return_type, WasmType::Void) {
221                    vec![]
222                } else {
223                    vec![wasm_type_to_val_type(host_func.return_type)]
224                };
225
226                let type_idx = self.add_function_type(&mut type_section, params, results);
227
228                // Add import
229                import_section.import(
230                    "host",
231                    &host_func.name,
232                    wasm_encoder::EntityType::Function(type_idx),
233                );
234
235                // Track the function index for call instructions
236                self.host_function_indices
237                    .insert(func_name.clone(), func_idx);
238                func_idx += 1;
239            }
240        }
241
242        // alloc(size: i32) -> i32
243        let alloc_type_idx =
244            self.add_function_type(&mut type_section, vec![ValType::I32], vec![ValType::I32]);
245        function_section.function(alloc_type_idx);
246        code_section.function(&self.generate_allocator());
247        export_section.export("alloc", ExportKind::Func, func_idx);
248        func_idx += 1;
249
250        // dealloc(ptr: i32) - Java expects single argument
251        let dealloc_type_idx =
252            self.add_function_type(&mut type_section, vec![ValType::I32], vec![]);
253        function_section.function(dealloc_type_idx);
254        code_section.function(&self.generate_deallocator());
255        export_section.export("dealloc", ExportKind::Func, func_idx);
256        func_idx += 1;
257
258        // Generate getter/setter functions for each class field
259        for class in model.classes.values() {
260            let layout = self.layout_calculator.calculate_layout(class);
261
262            for field in &class.fields {
263                if let Some(field_layout) = layout.field_offsets.get(&field.name) {
264                    // Getter
265                    let getter_name = format!("get_{}_{}", class.name, field.name);
266                    let result_type = wasm_memory_type_to_val_type(field_layout.wasm_type);
267
268                    let getter_type_idx = self.add_function_type(
269                        &mut type_section,
270                        vec![ValType::I32],
271                        vec![result_type],
272                    );
273                    function_section.function(getter_type_idx);
274                    code_section.function(
275                        &self.generate_getter(field_layout.offset, field_layout.wasm_type),
276                    );
277                    export_section.export(&getter_name, ExportKind::Func, func_idx);
278                    func_idx += 1;
279
280                    // Setter
281                    let setter_name = format!("set_{}_{}", class.name, field.name);
282                    let setter_type_idx = self.add_function_type(
283                        &mut type_section,
284                        vec![ValType::I32, result_type],
285                        vec![],
286                    );
287                    function_section.function(setter_type_idx);
288                    code_section.function(
289                        &self.generate_setter(field_layout.offset, field_layout.wasm_type),
290                    );
291                    export_section.export(&setter_name, ExportKind::Func, func_idx);
292                    func_idx += 1;
293                }
294            }
295        }
296
297        // Generate predicate functions
298        let predicates: Vec<_> = self
299            .predicates
300            .iter()
301            .map(|(k, v)| (k.clone(), v.clone()))
302            .collect();
303
304        for (name, predicate) in &predicates {
305            // Use explicit param_types if specified, otherwise default to all i32
306            let params: Vec<ValType> = predicate
307                .param_types
308                .clone()
309                .unwrap_or_else(|| (0..predicate.arity).map(|_| ValType::I32).collect());
310            let pred_type_idx =
311                self.add_function_type(&mut type_section, params, vec![ValType::I32]);
312            function_section.function(pred_type_idx);
313            code_section.function(&self.generate_predicate(predicate, &model)?);
314            export_section.export(name, ExportKind::Func, func_idx);
315            func_idx += 1;
316        }
317
318        // Solution mapper wrapper functions
319        self.generate_solution_mappers(
320            &mut type_section,
321            &mut function_section,
322            &mut code_section,
323            &mut export_section,
324            &mut func_idx,
325        );
326
327        // List accessor functions
328        self.generate_list_accessors(
329            &mut type_section,
330            &mut function_section,
331            &mut code_section,
332            &mut export_section,
333            &mut func_idx,
334        );
335
336        export_section.export("memory", ExportKind::Memory, 0);
337
338        module.section(&type_section);
339        if !self.host_function_indices.is_empty() {
340            module.section(&import_section);
341        }
342        module.section(&function_section);
343        module.section(&memory_section);
344        module.section(&global_section);
345        module.section(&export_section);
346        module.section(&code_section);
347
348        Ok(module.finish())
349    }
350
351    pub fn build_base64(self) -> SolverForgeResult<String> {
352        let bytes = self.build()?;
353        Ok(STANDARD.encode(&bytes))
354    }
355
356    fn add_function_type(
357        &mut self,
358        type_section: &mut TypeSection,
359        params: Vec<ValType>,
360        results: Vec<ValType>,
361    ) -> u32 {
362        let sig = FunctionSignature {
363            params: params.clone(),
364            results: results.clone(),
365        };
366
367        for (idx, existing) in self.function_types.iter().enumerate() {
368            if *existing == sig {
369                return idx as u32;
370            }
371        }
372
373        let idx = self.function_types.len() as u32;
374        type_section.ty().function(params, results);
375        self.function_types.push(sig);
376        idx
377    }
378
379    fn generate_allocator(&self) -> Function {
380        // Locals: 0=size (param), 1=result, 2=new_end, 3=current_memory_bytes
381        let mut func = Function::new([(3, ValType::I32)]);
382
383        // result = global[0] (current heap pointer)
384        func.instruction(&Instruction::GlobalGet(0));
385        func.instruction(&Instruction::LocalSet(1));
386
387        // new_end = result + size
388        func.instruction(&Instruction::LocalGet(1));
389        func.instruction(&Instruction::LocalGet(0));
390        func.instruction(&Instruction::I32Add);
391        func.instruction(&Instruction::LocalSet(2));
392
393        // current_memory_bytes = memory.size * 65536
394        func.instruction(&Instruction::MemorySize(0));
395        func.instruction(&Instruction::I32Const(65536));
396        func.instruction(&Instruction::I32Mul);
397        func.instruction(&Instruction::LocalSet(3));
398
399        // if new_end > current_memory_bytes: try to grow
400        func.instruction(&Instruction::LocalGet(2));
401        func.instruction(&Instruction::LocalGet(3));
402        func.instruction(&Instruction::I32GtU);
403        func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
404
405        // Grow by 16 pages (1MB) at a time
406        func.instruction(&Instruction::I32Const(16));
407        func.instruction(&Instruction::MemoryGrow(0));
408        // memory.grow returns -1 on failure
409        func.instruction(&Instruction::I32Const(-1));
410        func.instruction(&Instruction::I32Eq);
411        func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
412        // OOM - trap
413        func.instruction(&Instruction::Unreachable);
414        func.instruction(&Instruction::End);
415
416        func.instruction(&Instruction::End);
417
418        // Update heap pointer: global[0] = new_end
419        func.instruction(&Instruction::LocalGet(2));
420        func.instruction(&Instruction::GlobalSet(0));
421
422        // Return result (original heap pointer before allocation)
423        func.instruction(&Instruction::LocalGet(1));
424        func.instruction(&Instruction::End);
425        func
426    }
427
428    fn generate_deallocator(&self) -> Function {
429        let mut func = Function::new([]);
430        // No-op for bump allocator
431        func.instruction(&Instruction::End);
432        func
433    }
434
435    fn generate_getter(&self, offset: u32, wasm_type: WasmMemoryType) -> Function {
436        let mut func = Function::new([]);
437
438        func.instruction(&Instruction::LocalGet(0));
439
440        match wasm_type {
441            WasmMemoryType::I32 | WasmMemoryType::Pointer => {
442                func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
443                    offset: offset as u64,
444                    align: 2,
445                    memory_index: 0,
446                }));
447            }
448            WasmMemoryType::I64 => {
449                func.instruction(&Instruction::I64Load(wasm_encoder::MemArg {
450                    offset: offset as u64,
451                    align: 3,
452                    memory_index: 0,
453                }));
454            }
455            WasmMemoryType::F32 => {
456                func.instruction(&Instruction::F32Load(wasm_encoder::MemArg {
457                    offset: offset as u64,
458                    align: 2,
459                    memory_index: 0,
460                }));
461            }
462            WasmMemoryType::F64 => {
463                func.instruction(&Instruction::F64Load(wasm_encoder::MemArg {
464                    offset: offset as u64,
465                    align: 3,
466                    memory_index: 0,
467                }));
468            }
469            WasmMemoryType::ArrayPointer => {
470                func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
471                    offset: offset as u64,
472                    align: 2,
473                    memory_index: 0,
474                }));
475            }
476        }
477
478        func.instruction(&Instruction::End);
479        func
480    }
481
482    fn generate_setter(&self, offset: u32, wasm_type: WasmMemoryType) -> Function {
483        let mut func = Function::new([]);
484
485        func.instruction(&Instruction::LocalGet(0));
486
487        match wasm_type {
488            WasmMemoryType::I32 | WasmMemoryType::Pointer => {
489                func.instruction(&Instruction::LocalGet(1));
490                func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
491                    offset: offset as u64,
492                    align: 2,
493                    memory_index: 0,
494                }));
495            }
496            WasmMemoryType::I64 => {
497                func.instruction(&Instruction::LocalGet(1));
498                func.instruction(&Instruction::I64Store(wasm_encoder::MemArg {
499                    offset: offset as u64,
500                    align: 3,
501                    memory_index: 0,
502                }));
503            }
504            WasmMemoryType::F32 => {
505                func.instruction(&Instruction::LocalGet(1));
506                func.instruction(&Instruction::F32Store(wasm_encoder::MemArg {
507                    offset: offset as u64,
508                    align: 2,
509                    memory_index: 0,
510                }));
511            }
512            WasmMemoryType::F64 => {
513                func.instruction(&Instruction::LocalGet(1));
514                func.instruction(&Instruction::F64Store(wasm_encoder::MemArg {
515                    offset: offset as u64,
516                    align: 3,
517                    memory_index: 0,
518                }));
519            }
520            WasmMemoryType::ArrayPointer => {
521                func.instruction(&Instruction::LocalGet(1));
522                func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
523                    offset: offset as u64,
524                    align: 2,
525                    memory_index: 0,
526                }));
527            }
528        }
529
530        func.instruction(&Instruction::End);
531        func
532    }
533
534    fn generate_predicate(
535        &self,
536        predicate: &PredicateDefinition,
537        model: &DomainModel,
538    ) -> SolverForgeResult<Function> {
539        let mut func = Function::new([]);
540
541        match &predicate.body {
542            PredicateBody::Comparison(comparison) => match comparison {
543                Comparison::AlwaysTrue => {
544                    func.instruction(&Instruction::I32Const(1));
545                }
546                Comparison::AlwaysFalse => {
547                    func.instruction(&Instruction::I32Const(0));
548                }
549                Comparison::Equal(left, right) => {
550                    self.generate_field_load(&mut func, left, model)?;
551                    self.generate_field_load(&mut func, right, model)?;
552                    func.instruction(&Instruction::I32Eq);
553                }
554                Comparison::NotEqual(left, right) => {
555                    self.generate_field_load(&mut func, left, model)?;
556                    self.generate_field_load(&mut func, right, model)?;
557                    func.instruction(&Instruction::I32Ne);
558                }
559                Comparison::LessThan(left, right) => {
560                    self.generate_field_load(&mut func, left, model)?;
561                    self.generate_field_load(&mut func, right, model)?;
562                    func.instruction(&Instruction::I32LtS);
563                }
564                Comparison::LessThanOrEqual(left, right) => {
565                    self.generate_field_load(&mut func, left, model)?;
566                    self.generate_field_load(&mut func, right, model)?;
567                    func.instruction(&Instruction::I32LeS);
568                }
569                Comparison::GreaterThan(left, right) => {
570                    self.generate_field_load(&mut func, left, model)?;
571                    self.generate_field_load(&mut func, right, model)?;
572                    func.instruction(&Instruction::I32GtS);
573                }
574                Comparison::GreaterThanOrEqual(left, right) => {
575                    self.generate_field_load(&mut func, left, model)?;
576                    self.generate_field_load(&mut func, right, model)?;
577                    func.instruction(&Instruction::I32GeS);
578                }
579            },
580            PredicateBody::Expression(expression) => {
581                self.compile_expression(&mut func, expression, model)?;
582            }
583        }
584
585        func.instruction(&Instruction::End);
586        Ok(func)
587    }
588
589    fn generate_field_load(
590        &self,
591        func: &mut Function,
592        access: &FieldAccess,
593        model: &DomainModel,
594    ) -> SolverForgeResult<()> {
595        let class = model.classes.get(&access.class_name).ok_or_else(|| {
596            SolverForgeError::WasmGeneration(format!("Class not found: {}", access.class_name))
597        })?;
598
599        let layout = self
600            .layout_calculator
601            .get_layout(&class.name)
602            .ok_or_else(|| {
603                SolverForgeError::WasmGeneration(format!(
604                    "Layout not found for class: {}",
605                    class.name
606                ))
607            })?;
608
609        let field_layout = layout
610            .field_offsets
611            .get(&access.field_name)
612            .ok_or_else(|| {
613                SolverForgeError::WasmGeneration(format!(
614                    "Field not found: {}.{}",
615                    access.class_name, access.field_name
616                ))
617            })?;
618
619        func.instruction(&Instruction::LocalGet(access.param_index));
620
621        match field_layout.wasm_type {
622            WasmMemoryType::I32 | WasmMemoryType::Pointer => {
623                func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
624                    offset: field_layout.offset as u64,
625                    align: 2,
626                    memory_index: 0,
627                }));
628            }
629            WasmMemoryType::I64 => {
630                func.instruction(&Instruction::I64Load(wasm_encoder::MemArg {
631                    offset: field_layout.offset as u64,
632                    align: 3,
633                    memory_index: 0,
634                }));
635                func.instruction(&Instruction::I32WrapI64);
636            }
637            WasmMemoryType::F32 => {
638                func.instruction(&Instruction::F32Load(wasm_encoder::MemArg {
639                    offset: field_layout.offset as u64,
640                    align: 2,
641                    memory_index: 0,
642                }));
643                func.instruction(&Instruction::I32TruncF32S);
644            }
645            WasmMemoryType::F64 => {
646                func.instruction(&Instruction::F64Load(wasm_encoder::MemArg {
647                    offset: field_layout.offset as u64,
648                    align: 3,
649                    memory_index: 0,
650                }));
651                func.instruction(&Instruction::I32TruncF64S);
652            }
653            WasmMemoryType::ArrayPointer => {
654                func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
655                    offset: field_layout.offset as u64,
656                    align: 2,
657                    memory_index: 0,
658                }));
659            }
660        }
661
662        Ok(())
663    }
664
665    /// Compile an expression tree into WASM instructions
666    ///
667    /// Generates WASM code that evaluates the expression and leaves the result
668    /// on the stack.
669    fn compile_expression(
670        &self,
671        func: &mut Function,
672        expr: &Expression,
673        model: &DomainModel,
674    ) -> SolverForgeResult<()> {
675        match expr {
676            // ===== Literals =====
677            Expression::IntLiteral { value } => {
678                if *value >= i32::MIN as i64 && *value <= i32::MAX as i64 {
679                    func.instruction(&Instruction::I32Const(*value as i32));
680                } else {
681                    func.instruction(&Instruction::I64Const(*value));
682                    func.instruction(&Instruction::I32WrapI64);
683                }
684            }
685            Expression::BoolLiteral { value } => {
686                func.instruction(&Instruction::I32Const(if *value { 1 } else { 0 }));
687            }
688            Expression::Null => {
689                func.instruction(&Instruction::I32Const(0));
690            }
691
692            // ===== Parameter Access =====
693            Expression::Param { index } => {
694                func.instruction(&Instruction::LocalGet(*index));
695            }
696
697            // ===== Field Access =====
698            Expression::FieldAccess {
699                object,
700                class_name,
701                field_name,
702            } => {
703                // Compile the object expression to get the pointer
704                self.compile_expression(func, object, model)?;
705
706                // Load the field from memory
707                let class = model.classes.get(class_name).ok_or_else(|| {
708                    SolverForgeError::WasmGeneration(format!("Class not found: {}", class_name))
709                })?;
710
711                let layout = self
712                    .layout_calculator
713                    .get_layout(&class.name)
714                    .ok_or_else(|| {
715                        SolverForgeError::WasmGeneration(format!(
716                            "Layout not found for class: {}",
717                            class.name
718                        ))
719                    })?;
720
721                let field_layout = layout.field_offsets.get(field_name).ok_or_else(|| {
722                    SolverForgeError::WasmGeneration(format!(
723                        "Field not found: {}.{}",
724                        class_name, field_name
725                    ))
726                })?;
727
728                match field_layout.wasm_type {
729                    WasmMemoryType::I32 | WasmMemoryType::Pointer => {
730                        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
731                            offset: field_layout.offset as u64,
732                            align: 2,
733                            memory_index: 0,
734                        }));
735                    }
736                    WasmMemoryType::I64 => {
737                        func.instruction(&Instruction::I64Load(wasm_encoder::MemArg {
738                            offset: field_layout.offset as u64,
739                            align: 3,
740                            memory_index: 0,
741                        }));
742                        func.instruction(&Instruction::I32WrapI64);
743                    }
744                    WasmMemoryType::F32 => {
745                        func.instruction(&Instruction::F32Load(wasm_encoder::MemArg {
746                            offset: field_layout.offset as u64,
747                            align: 2,
748                            memory_index: 0,
749                        }));
750                        func.instruction(&Instruction::I32TruncF32S);
751                    }
752                    WasmMemoryType::F64 => {
753                        func.instruction(&Instruction::F64Load(wasm_encoder::MemArg {
754                            offset: field_layout.offset as u64,
755                            align: 3,
756                            memory_index: 0,
757                        }));
758                        func.instruction(&Instruction::I32TruncF64S);
759                    }
760                    WasmMemoryType::ArrayPointer => {
761                        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
762                            offset: field_layout.offset as u64,
763                            align: 2,
764                            memory_index: 0,
765                        }));
766                    }
767                }
768            }
769
770            // ===== Comparisons =====
771            Expression::Eq { left, right } => {
772                self.compile_expression(func, left, model)?;
773                self.compile_expression(func, right, model)?;
774                func.instruction(&Instruction::I32Eq);
775            }
776            Expression::Ne { left, right } => {
777                self.compile_expression(func, left, model)?;
778                self.compile_expression(func, right, model)?;
779                func.instruction(&Instruction::I32Ne);
780            }
781            Expression::Lt { left, right } => {
782                self.compile_expression(func, left, model)?;
783                self.compile_expression(func, right, model)?;
784                func.instruction(&Instruction::I32LtS);
785            }
786            Expression::Le { left, right } => {
787                self.compile_expression(func, left, model)?;
788                self.compile_expression(func, right, model)?;
789                func.instruction(&Instruction::I32LeS);
790            }
791            Expression::Gt { left, right } => {
792                self.compile_expression(func, left, model)?;
793                self.compile_expression(func, right, model)?;
794                func.instruction(&Instruction::I32GtS);
795            }
796            Expression::Ge { left, right } => {
797                self.compile_expression(func, left, model)?;
798                self.compile_expression(func, right, model)?;
799                func.instruction(&Instruction::I32GeS);
800            }
801
802            // ===== Logical Operations =====
803            Expression::And { left, right } => {
804                self.compile_expression(func, left, model)?;
805                self.compile_expression(func, right, model)?;
806                func.instruction(&Instruction::I32And);
807            }
808            Expression::Or { left, right } => {
809                self.compile_expression(func, left, model)?;
810                self.compile_expression(func, right, model)?;
811                func.instruction(&Instruction::I32Or);
812            }
813            Expression::Not { operand } => {
814                self.compile_expression(func, operand, model)?;
815                func.instruction(&Instruction::I32Eqz); // ! in WASM is i32.eqz
816            }
817            Expression::IsNull { operand } => {
818                self.compile_expression(func, operand, model)?;
819                func.instruction(&Instruction::I32Eqz); // null check is ptr == 0
820            }
821            Expression::IsNotNull { operand } => {
822                self.compile_expression(func, operand, model)?;
823                func.instruction(&Instruction::I32Const(0));
824                func.instruction(&Instruction::I32Ne); // not null is ptr != 0
825            }
826
827            // ===== Arithmetic Operations =====
828            Expression::Add { left, right } => {
829                self.compile_expression(func, left, model)?;
830                self.compile_expression(func, right, model)?;
831                func.instruction(&Instruction::I32Add);
832            }
833            Expression::Sub { left, right } => {
834                self.compile_expression(func, left, model)?;
835                self.compile_expression(func, right, model)?;
836                func.instruction(&Instruction::I32Sub);
837            }
838            Expression::Mul { left, right } => {
839                self.compile_expression(func, left, model)?;
840                self.compile_expression(func, right, model)?;
841                func.instruction(&Instruction::I32Mul);
842            }
843            Expression::Div { left, right } => {
844                self.compile_expression(func, left, model)?;
845                self.compile_expression(func, right, model)?;
846                func.instruction(&Instruction::I32DivS);
847            }
848
849            // ===== List Operations =====
850            Expression::ListContains { list, element } => {
851                // For now, use a host function to check list containment
852                // TODO: Generate inline loop for better performance
853                self.compile_expression(func, list, model)?;
854                self.compile_expression(func, element, model)?;
855
856                let func_idx = self
857                    .host_function_indices
858                    .get("hlistContainsString")
859                    .ok_or_else(|| {
860                        SolverForgeError::WasmGeneration(
861                            "Host function 'hlistContainsString' not found".to_string(),
862                        )
863                    })?;
864
865                func.instruction(&Instruction::Call(*func_idx));
866            }
867
868            // ===== Host Function Calls =====
869            Expression::HostCall {
870                function_name,
871                args,
872            } => {
873                // Compile all arguments
874                for arg in args {
875                    self.compile_expression(func, arg, model)?;
876                }
877
878                // Get the function index for the imported function
879                let func_idx = self
880                    .host_function_indices
881                    .get(function_name)
882                    .ok_or_else(|| {
883                        SolverForgeError::WasmGeneration(format!(
884                            "Host function '{}' not found in registry. Available functions: {:?}",
885                            function_name,
886                            self.host_function_indices.keys().collect::<Vec<_>>()
887                        ))
888                    })?;
889
890                // Generate call instruction
891                func.instruction(&Instruction::Call(*func_idx));
892            }
893
894            // ===== Conditional =====
895            Expression::IfThenElse {
896                condition,
897                then_branch,
898                else_branch,
899            } => {
900                // Compile condition
901                self.compile_expression(func, condition, model)?;
902
903                // WASM if-else structure
904                func.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
905                    ValType::I32,
906                )));
907
908                // Compile then branch
909                self.compile_expression(func, then_branch, model)?;
910
911                func.instruction(&Instruction::Else);
912
913                // Compile else branch
914                self.compile_expression(func, else_branch, model)?;
915
916                func.instruction(&Instruction::End);
917            }
918        }
919
920        Ok(())
921    }
922
923    fn generate_solution_mappers(
924        &mut self,
925        type_section: &mut TypeSection,
926        function_section: &mut FunctionSection,
927        code_section: &mut CodeSection,
928        export_section: &mut ExportSection,
929        func_idx: &mut u32,
930    ) {
931        // parseSchedule(length: i32, json: i32) -> i32
932        // Wrapper that calls hparseSchedule host function
933        if let Some(&host_idx) = self.host_function_indices.get("hparseSchedule") {
934            let parse_type = self.add_function_type(
935                type_section,
936                vec![ValType::I32, ValType::I32],
937                vec![ValType::I32],
938            );
939            function_section.function(parse_type);
940
941            let mut func = Function::new(vec![]);
942            func.instruction(&Instruction::LocalGet(0)); // length
943            func.instruction(&Instruction::LocalGet(1)); // json ptr
944            func.instruction(&Instruction::Call(host_idx));
945            func.instruction(&Instruction::End);
946
947            code_section.function(&func);
948            export_section.export("parseSchedule", ExportKind::Func, *func_idx);
949            *func_idx += 1;
950        }
951
952        // scheduleString(schedule: i32) -> i32
953        // Wrapper that calls hscheduleString host function
954        if let Some(&host_idx) = self.host_function_indices.get("hscheduleString") {
955            let string_type =
956                self.add_function_type(type_section, vec![ValType::I32], vec![ValType::I32]);
957            function_section.function(string_type);
958
959            let mut func = Function::new(vec![]);
960            func.instruction(&Instruction::LocalGet(0)); // schedule ptr
961            func.instruction(&Instruction::Call(host_idx));
962            func.instruction(&Instruction::End);
963
964            code_section.function(&func);
965            export_section.export("scheduleString", ExportKind::Func, *func_idx);
966            *func_idx += 1;
967        }
968    }
969
970    fn generate_list_accessors(
971        &mut self,
972        type_section: &mut TypeSection,
973        function_section: &mut FunctionSection,
974        code_section: &mut CodeSection,
975        export_section: &mut ExportSection,
976        func_idx: &mut u32,
977    ) {
978        // newList() -> i32 (ptr) - Wrapper that calls hnewList host function
979        if let Some(&host_idx) = self.host_function_indices.get("hnewList") {
980            let create_type = self.add_function_type(type_section, vec![], vec![ValType::I32]);
981            function_section.function(create_type);
982
983            let mut func = Function::new(vec![]);
984            func.instruction(&Instruction::Call(host_idx));
985            func.instruction(&Instruction::End);
986
987            code_section.function(&func);
988            export_section.export("newList", ExportKind::Func, *func_idx);
989            *func_idx += 1;
990        }
991
992        // getItem(list: i32, index: i32) -> i32 - Wrapper for hgetItem
993        if let Some(&host_idx) = self.host_function_indices.get("hgetItem") {
994            let get_type = self.add_function_type(
995                type_section,
996                vec![ValType::I32, ValType::I32],
997                vec![ValType::I32],
998            );
999            function_section.function(get_type);
1000
1001            let mut func = Function::new(vec![]);
1002            func.instruction(&Instruction::LocalGet(0)); // list
1003            func.instruction(&Instruction::LocalGet(1)); // index
1004            func.instruction(&Instruction::Call(host_idx));
1005            func.instruction(&Instruction::End);
1006
1007            code_section.function(&func);
1008            export_section.export("getItem", ExportKind::Func, *func_idx);
1009            *func_idx += 1;
1010        }
1011
1012        // setItem(list: i32, index: i32, value: i32) - Wrapper for hsetItem
1013        if let Some(&host_idx) = self.host_function_indices.get("hsetItem") {
1014            let set_type = self.add_function_type(
1015                type_section,
1016                vec![ValType::I32, ValType::I32, ValType::I32],
1017                vec![],
1018            );
1019            function_section.function(set_type);
1020
1021            let mut func = Function::new(vec![]);
1022            func.instruction(&Instruction::LocalGet(0)); // list
1023            func.instruction(&Instruction::LocalGet(1)); // index
1024            func.instruction(&Instruction::LocalGet(2)); // value
1025            func.instruction(&Instruction::Call(host_idx));
1026            func.instruction(&Instruction::End);
1027
1028            code_section.function(&func);
1029            export_section.export("setItem", ExportKind::Func, *func_idx);
1030            *func_idx += 1;
1031        }
1032
1033        // size(list: i32) -> i32 - Wrapper for hsize
1034        if let Some(&host_idx) = self.host_function_indices.get("hsize") {
1035            let size_type =
1036                self.add_function_type(type_section, vec![ValType::I32], vec![ValType::I32]);
1037            function_section.function(size_type);
1038
1039            let mut func = Function::new(vec![]);
1040            func.instruction(&Instruction::LocalGet(0)); // list
1041            func.instruction(&Instruction::Call(host_idx));
1042            func.instruction(&Instruction::End);
1043
1044            code_section.function(&func);
1045            export_section.export("size", ExportKind::Func, *func_idx);
1046            *func_idx += 1;
1047        }
1048
1049        // append(list: i32, value: i32) - Wrapper for happend
1050        if let Some(&host_idx) = self.host_function_indices.get("happend") {
1051            let append_type =
1052                self.add_function_type(type_section, vec![ValType::I32, ValType::I32], vec![]);
1053            function_section.function(append_type);
1054
1055            let mut func = Function::new(vec![]);
1056            func.instruction(&Instruction::LocalGet(0)); // list
1057            func.instruction(&Instruction::LocalGet(1)); // value
1058            func.instruction(&Instruction::Call(host_idx));
1059            func.instruction(&Instruction::End);
1060
1061            code_section.function(&func);
1062            export_section.export("append", ExportKind::Func, *func_idx);
1063            *func_idx += 1;
1064        }
1065
1066        // insert(list: i32, index: i32, value: i32) - Wrapper for hinsert
1067        if let Some(&host_idx) = self.host_function_indices.get("hinsert") {
1068            let insert_type = self.add_function_type(
1069                type_section,
1070                vec![ValType::I32, ValType::I32, ValType::I32],
1071                vec![],
1072            );
1073            function_section.function(insert_type);
1074
1075            let mut func = Function::new(vec![]);
1076            func.instruction(&Instruction::LocalGet(0)); // list
1077            func.instruction(&Instruction::LocalGet(1)); // index
1078            func.instruction(&Instruction::LocalGet(2)); // value
1079            func.instruction(&Instruction::Call(host_idx));
1080            func.instruction(&Instruction::End);
1081
1082            code_section.function(&func);
1083            export_section.export("insert", ExportKind::Func, *func_idx);
1084            *func_idx += 1;
1085        }
1086
1087        // remove(list: i32, index: i32) - Wrapper for hremove
1088        if let Some(&host_idx) = self.host_function_indices.get("hremove") {
1089            let remove_type =
1090                self.add_function_type(type_section, vec![ValType::I32, ValType::I32], vec![]);
1091            function_section.function(remove_type);
1092
1093            let mut func = Function::new(vec![]);
1094            func.instruction(&Instruction::LocalGet(0)); // list
1095            func.instruction(&Instruction::LocalGet(1)); // index
1096            func.instruction(&Instruction::Call(host_idx));
1097            func.instruction(&Instruction::End);
1098
1099            code_section.function(&func);
1100            export_section.export("remove", ExportKind::Func, *func_idx);
1101            *func_idx += 1;
1102        }
1103    }
1104
1105    // List structure in memory:
1106    // offset 0: size (i32)
1107    // offset 4: capacity (i32)
1108    // offset 8+: elements (i32 each)
1109
1110    #[allow(dead_code)]
1111    fn generate_create_list(&self) -> Function {
1112        let mut func = Function::new([(1, ValType::I32)]);
1113
1114        // Allocate: 8 bytes header + capacity * 4 bytes for elements
1115        // header_size + capacity * element_size
1116        func.instruction(&Instruction::I32Const(8));
1117        func.instruction(&Instruction::LocalGet(0)); // capacity
1118        func.instruction(&Instruction::I32Const(4));
1119        func.instruction(&Instruction::I32Mul);
1120        func.instruction(&Instruction::I32Add);
1121
1122        // Call allocate via bump pointer
1123        func.instruction(&Instruction::GlobalGet(0));
1124        func.instruction(&Instruction::LocalTee(1)); // Save result ptr
1125
1126        // Update bump pointer
1127        func.instruction(&Instruction::GlobalGet(0));
1128        func.instruction(&Instruction::I32Const(8));
1129        func.instruction(&Instruction::LocalGet(0));
1130        func.instruction(&Instruction::I32Const(4));
1131        func.instruction(&Instruction::I32Mul);
1132        func.instruction(&Instruction::I32Add);
1133        func.instruction(&Instruction::I32Add);
1134        func.instruction(&Instruction::GlobalSet(0));
1135
1136        // Initialize size to 0
1137        func.instruction(&Instruction::LocalGet(1));
1138        func.instruction(&Instruction::I32Const(0));
1139        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1140            offset: 0,
1141            align: 2,
1142            memory_index: 0,
1143        }));
1144
1145        // Initialize capacity
1146        func.instruction(&Instruction::LocalGet(1));
1147        func.instruction(&Instruction::LocalGet(0));
1148        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1149            offset: 4,
1150            align: 2,
1151            memory_index: 0,
1152        }));
1153
1154        // Return ptr
1155        func.instruction(&Instruction::LocalGet(1));
1156        func.instruction(&Instruction::End);
1157        func
1158    }
1159
1160    #[allow(dead_code)]
1161    fn generate_get_item(&self) -> Function {
1162        let mut func = Function::new([]);
1163
1164        // list + 8 + index * 4
1165        func.instruction(&Instruction::LocalGet(0)); // list ptr
1166        func.instruction(&Instruction::I32Const(8));
1167        func.instruction(&Instruction::I32Add);
1168        func.instruction(&Instruction::LocalGet(1)); // index
1169        func.instruction(&Instruction::I32Const(4));
1170        func.instruction(&Instruction::I32Mul);
1171        func.instruction(&Instruction::I32Add);
1172        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
1173            offset: 0,
1174            align: 2,
1175            memory_index: 0,
1176        }));
1177
1178        func.instruction(&Instruction::End);
1179        func
1180    }
1181
1182    #[allow(dead_code)]
1183    fn generate_set_item(&self) -> Function {
1184        let mut func = Function::new([]);
1185
1186        // list + 8 + index * 4
1187        func.instruction(&Instruction::LocalGet(0)); // list ptr
1188        func.instruction(&Instruction::I32Const(8));
1189        func.instruction(&Instruction::I32Add);
1190        func.instruction(&Instruction::LocalGet(1)); // index
1191        func.instruction(&Instruction::I32Const(4));
1192        func.instruction(&Instruction::I32Mul);
1193        func.instruction(&Instruction::I32Add);
1194        func.instruction(&Instruction::LocalGet(2)); // value
1195        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1196            offset: 0,
1197            align: 2,
1198            memory_index: 0,
1199        }));
1200
1201        func.instruction(&Instruction::End);
1202        func
1203    }
1204
1205    #[allow(dead_code)]
1206    fn generate_get_size(&self) -> Function {
1207        let mut func = Function::new([]);
1208
1209        func.instruction(&Instruction::LocalGet(0));
1210        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
1211            offset: 0,
1212            align: 2,
1213            memory_index: 0,
1214        }));
1215
1216        func.instruction(&Instruction::End);
1217        func
1218    }
1219
1220    #[allow(dead_code)]
1221    fn generate_append(&self) -> Function {
1222        let mut func = Function::new([(1, ValType::I32)]);
1223
1224        // Get current size
1225        func.instruction(&Instruction::LocalGet(0));
1226        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
1227            offset: 0,
1228            align: 2,
1229            memory_index: 0,
1230        }));
1231        func.instruction(&Instruction::LocalSet(2)); // current size
1232
1233        // Store value at list + 8 + size * 4
1234        func.instruction(&Instruction::LocalGet(0));
1235        func.instruction(&Instruction::I32Const(8));
1236        func.instruction(&Instruction::I32Add);
1237        func.instruction(&Instruction::LocalGet(2));
1238        func.instruction(&Instruction::I32Const(4));
1239        func.instruction(&Instruction::I32Mul);
1240        func.instruction(&Instruction::I32Add);
1241        func.instruction(&Instruction::LocalGet(1)); // value
1242        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1243            offset: 0,
1244            align: 2,
1245            memory_index: 0,
1246        }));
1247
1248        // Increment size
1249        func.instruction(&Instruction::LocalGet(0));
1250        func.instruction(&Instruction::LocalGet(2));
1251        func.instruction(&Instruction::I32Const(1));
1252        func.instruction(&Instruction::I32Add);
1253        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1254            offset: 0,
1255            align: 2,
1256            memory_index: 0,
1257        }));
1258
1259        func.instruction(&Instruction::End);
1260        func
1261    }
1262
1263    #[allow(dead_code)]
1264    fn generate_insert(&self) -> Function {
1265        // Simplified: just set at index (full impl would shift elements)
1266        let mut func = Function::new([]);
1267
1268        func.instruction(&Instruction::LocalGet(0));
1269        func.instruction(&Instruction::I32Const(8));
1270        func.instruction(&Instruction::I32Add);
1271        func.instruction(&Instruction::LocalGet(1)); // index
1272        func.instruction(&Instruction::I32Const(4));
1273        func.instruction(&Instruction::I32Mul);
1274        func.instruction(&Instruction::I32Add);
1275        func.instruction(&Instruction::LocalGet(2)); // value
1276        func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1277            offset: 0,
1278            align: 2,
1279            memory_index: 0,
1280        }));
1281
1282        func.instruction(&Instruction::End);
1283        func
1284    }
1285
1286    #[allow(dead_code)]
1287    fn generate_remove(&self) -> Function {
1288        // Simplified: return item at index, don't shift
1289        let mut func = Function::new([]);
1290
1291        func.instruction(&Instruction::LocalGet(0));
1292        func.instruction(&Instruction::I32Const(8));
1293        func.instruction(&Instruction::I32Add);
1294        func.instruction(&Instruction::LocalGet(1));
1295        func.instruction(&Instruction::I32Const(4));
1296        func.instruction(&Instruction::I32Mul);
1297        func.instruction(&Instruction::I32Add);
1298        func.instruction(&Instruction::I32Load(wasm_encoder::MemArg {
1299            offset: 0,
1300            align: 2,
1301            memory_index: 0,
1302        }));
1303
1304        func.instruction(&Instruction::End);
1305        func
1306    }
1307
1308    #[allow(dead_code)]
1309    fn generate_deallocate_list(&self) -> Function {
1310        let mut func = Function::new([]);
1311        // No-op for bump allocator
1312        func.instruction(&Instruction::End);
1313        func
1314    }
1315}
1316
1317fn wasm_type_to_val_type(wasm_type: WasmType) -> ValType {
1318    match wasm_type {
1319        WasmType::I32 | WasmType::Ptr => ValType::I32,
1320        WasmType::I64 => ValType::I64,
1321        WasmType::F32 => ValType::F32,
1322        WasmType::F64 => ValType::F64,
1323        WasmType::Void => panic!("Void type cannot be converted to ValType"),
1324    }
1325}
1326
1327fn wasm_memory_type_to_val_type(memory_type: WasmMemoryType) -> ValType {
1328    match memory_type {
1329        WasmMemoryType::I32 | WasmMemoryType::Pointer | WasmMemoryType::ArrayPointer => {
1330            ValType::I32
1331        }
1332        WasmMemoryType::I64 => ValType::I64,
1333        WasmMemoryType::F32 => ValType::F32,
1334        WasmMemoryType::F64 => ValType::F64,
1335    }
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340    use super::*;
1341    use crate::domain::{
1342        DomainClass, DomainModelBuilder, FieldDescriptor, FieldType, PlanningAnnotation,
1343        PrimitiveType, ScoreType,
1344    };
1345
1346    fn create_test_model() -> DomainModel {
1347        DomainModelBuilder::new()
1348            .add_class(
1349                DomainClass::new("Lesson")
1350                    .with_annotation(PlanningAnnotation::PlanningEntity)
1351                    .with_field(FieldDescriptor::new(
1352                        "id",
1353                        FieldType::Primitive(PrimitiveType::Int),
1354                    ))
1355                    .with_field(FieldDescriptor::new(
1356                        "roomId",
1357                        FieldType::Primitive(PrimitiveType::Int),
1358                    )),
1359            )
1360            .add_class(
1361                DomainClass::new("Timetable")
1362                    .with_annotation(PlanningAnnotation::PlanningSolution)
1363                    .with_field(
1364                        FieldDescriptor::new("score", FieldType::Score(ScoreType::HardSoft))
1365                            .with_planning_annotation(PlanningAnnotation::planning_score()),
1366                    ),
1367            )
1368            .build()
1369    }
1370
1371    #[test]
1372    fn test_build_minimal_module() {
1373        let model = create_test_model();
1374        let builder = WasmModuleBuilder::new().with_domain_model(model);
1375        let wasm_bytes = builder.build().unwrap();
1376
1377        assert_eq!(&wasm_bytes[0..4], b"\0asm");
1378        assert_eq!(&wasm_bytes[4..8], &[1, 0, 0, 0]);
1379    }
1380
1381    #[test]
1382    fn test_predicate_generation() {
1383        let model = create_test_model();
1384        let predicate = PredicateDefinition::equal(
1385            "same_room",
1386            FieldAccess::new(0, "Lesson", "roomId"),
1387            FieldAccess::new(1, "Lesson", "roomId"),
1388        );
1389
1390        let builder = WasmModuleBuilder::new()
1391            .with_domain_model(model)
1392            .add_predicate(predicate);
1393
1394        let wasm_bytes = builder.build().unwrap();
1395        assert!(!wasm_bytes.is_empty());
1396    }
1397
1398    #[test]
1399    fn test_build_base64() {
1400        let model = create_test_model();
1401        let builder = WasmModuleBuilder::new().with_domain_model(model);
1402        let base64 = builder.build_base64().unwrap();
1403
1404        assert!(base64.starts_with("AGFzbQ")); // Base64 of "\0asm"
1405    }
1406
1407    #[test]
1408    fn test_always_true_predicate() {
1409        let model = create_test_model();
1410        let predicate = PredicateDefinition::always_true("always_true", 1);
1411
1412        let builder = WasmModuleBuilder::new()
1413            .with_domain_model(model)
1414            .add_predicate(predicate);
1415
1416        let wasm_bytes = builder.build().unwrap();
1417        assert!(!wasm_bytes.is_empty());
1418    }
1419
1420    #[test]
1421    fn test_missing_domain_model() {
1422        let builder = WasmModuleBuilder::new();
1423        let result = builder.build();
1424
1425        assert!(result.is_err());
1426        let err = result.unwrap_err();
1427        assert!(err.to_string().contains("Domain model not set"));
1428    }
1429
1430    #[test]
1431    fn test_predicate_missing_class() {
1432        let model = create_test_model();
1433        let predicate = PredicateDefinition::equal(
1434            "bad_pred",
1435            FieldAccess::new(0, "NonExistent", "field"),
1436            FieldAccess::new(1, "NonExistent", "field"),
1437        );
1438
1439        let builder = WasmModuleBuilder::new()
1440            .with_domain_model(model)
1441            .add_predicate(predicate);
1442
1443        let result = builder.build();
1444        assert!(result.is_err());
1445    }
1446
1447    #[test]
1448    fn test_predicate_missing_field() {
1449        let model = create_test_model();
1450        let predicate = PredicateDefinition::equal(
1451            "bad_pred",
1452            FieldAccess::new(0, "Lesson", "nonexistent"),
1453            FieldAccess::new(1, "Lesson", "nonexistent"),
1454        );
1455
1456        let builder = WasmModuleBuilder::new()
1457            .with_domain_model(model)
1458            .add_predicate(predicate);
1459
1460        let result = builder.build();
1461        assert!(result.is_err());
1462    }
1463
1464    #[test]
1465    fn test_memory_configuration() {
1466        let model = create_test_model();
1467        let builder = WasmModuleBuilder::new()
1468            .with_domain_model(model)
1469            .with_initial_memory(32)
1470            .with_max_memory(Some(512));
1471
1472        let wasm_bytes = builder.build().unwrap();
1473        assert!(!wasm_bytes.is_empty());
1474    }
1475
1476    #[test]
1477    fn test_comparison_variants() {
1478        let model = create_test_model();
1479        let left = FieldAccess::new(0, "Lesson", "id");
1480        let right = FieldAccess::new(1, "Lesson", "id");
1481
1482        let comparisons = vec![
1483            ("eq", Comparison::Equal(left.clone(), right.clone())),
1484            ("ne", Comparison::NotEqual(left.clone(), right.clone())),
1485            ("lt", Comparison::LessThan(left.clone(), right.clone())),
1486            (
1487                "le",
1488                Comparison::LessThanOrEqual(left.clone(), right.clone()),
1489            ),
1490            ("gt", Comparison::GreaterThan(left.clone(), right.clone())),
1491            (
1492                "ge",
1493                Comparison::GreaterThanOrEqual(left.clone(), right.clone()),
1494            ),
1495            ("true", Comparison::AlwaysTrue),
1496            ("false", Comparison::AlwaysFalse),
1497        ];
1498
1499        for (name, comparison) in comparisons {
1500            let predicate = PredicateDefinition::new(name, 2, comparison);
1501            let builder = WasmModuleBuilder::new()
1502                .with_domain_model(model.clone())
1503                .add_predicate(predicate);
1504
1505            let result = builder.build();
1506            assert!(result.is_ok(), "Failed for comparison: {}", name);
1507        }
1508    }
1509
1510    #[test]
1511    fn test_field_access_constructor() {
1512        let access = FieldAccess::new(0, "Lesson", "room");
1513        assert_eq!(access.param_index, 0);
1514        assert_eq!(access.class_name, "Lesson");
1515        assert_eq!(access.field_name, "room");
1516    }
1517
1518    #[test]
1519    fn test_expression_based_predicate() {
1520        use crate::wasm::{Expr, FieldAccessExt};
1521
1522        let model = create_test_model();
1523
1524        // Build expression: param(0).roomId == param(1).roomId
1525        let left = Expr::param(0).get("Lesson", "roomId");
1526        let right = Expr::param(1).get("Lesson", "roomId");
1527        let expr = Expr::eq(left, right);
1528
1529        let predicate = PredicateDefinition::from_expression("same_room_expr", 2, expr);
1530
1531        let builder = WasmModuleBuilder::new()
1532            .with_domain_model(model)
1533            .add_predicate(predicate);
1534
1535        let wasm_bytes = builder.build().unwrap();
1536        assert!(!wasm_bytes.is_empty());
1537        assert_eq!(&wasm_bytes[0..4], b"\0asm");
1538    }
1539
1540    #[test]
1541    fn test_expression_with_host_function() {
1542        use crate::wasm::{Expr, FieldAccessExt, HostFunctionRegistry};
1543
1544        let model = create_test_model();
1545
1546        // Build expression that uses host function: hstringEquals(param(0).field, param(1).field)
1547        // Note: We're using int fields as placeholders since our test model doesn't have string fields
1548        let left = Expr::param(0).get("Lesson", "id");
1549        let right = Expr::param(1).get("Lesson", "id");
1550        let expr = Expr::string_equals(left, right);
1551
1552        let predicate = PredicateDefinition::from_expression("test_host_call", 2, expr);
1553
1554        let registry = HostFunctionRegistry::with_standard_functions();
1555
1556        let builder = WasmModuleBuilder::new()
1557            .with_domain_model(model)
1558            .with_host_functions(registry)
1559            .add_predicate(predicate);
1560
1561        let wasm_bytes = builder.build().unwrap();
1562        assert!(!wasm_bytes.is_empty());
1563        assert_eq!(&wasm_bytes[0..4], b"\0asm");
1564
1565        // Verify the module contains an import section (indicated by section ID 2)
1566        // WASM sections: 1=Type, 2=Import, 3=Function, 5=Memory, 6=Global, 7=Export, 10=Code
1567        assert!(wasm_bytes.windows(2).any(|w| w[0] == 2 && w[1] > 0));
1568    }
1569
1570    #[test]
1571    fn test_expression_with_logical_operations() {
1572        use crate::wasm::{Expr, FieldAccessExt};
1573
1574        let model = create_test_model();
1575
1576        // Build: param(0).id > 0 AND param(0).roomId == param(1).roomId
1577        let id_check = Expr::gt(Expr::param(0).get("Lesson", "id"), Expr::int(0));
1578        let room_match = Expr::eq(
1579            Expr::param(0).get("Lesson", "roomId"),
1580            Expr::param(1).get("Lesson", "roomId"),
1581        );
1582        let expr = Expr::and(id_check, room_match);
1583
1584        let predicate = PredicateDefinition::from_expression("complex_predicate", 2, expr);
1585
1586        let builder = WasmModuleBuilder::new()
1587            .with_domain_model(model)
1588            .add_predicate(predicate);
1589
1590        let wasm_bytes = builder.build().unwrap();
1591        assert!(!wasm_bytes.is_empty());
1592    }
1593
1594    #[test]
1595    fn test_expression_with_if_then_else() {
1596        use crate::wasm::{Expr, FieldAccessExt};
1597
1598        let model = create_test_model();
1599
1600        // Build: if param(0).id > 0 { param(0).roomId } else { 0 }
1601        let expr = Expr::if_then_else(
1602            Expr::gt(Expr::param(0).get("Lesson", "id"), Expr::int(0)),
1603            Expr::param(0).get("Lesson", "roomId"),
1604            Expr::int(0),
1605        );
1606
1607        let predicate = PredicateDefinition::from_expression("conditional_pred", 1, expr);
1608
1609        let builder = WasmModuleBuilder::new()
1610            .with_domain_model(model)
1611            .add_predicate(predicate);
1612
1613        let wasm_bytes = builder.build().unwrap();
1614        assert!(!wasm_bytes.is_empty());
1615        assert_eq!(&wasm_bytes[0..4], b"\0asm");
1616    }
1617}