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 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 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_section.global(
196 GlobalType {
197 val_type: ValType::I32,
198 mutable: true,
199 shared: false,
200 },
201 &ConstExpr::i32_const(1024),
202 );
203
204 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 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 import_section.import(
230 "host",
231 &host_func.name,
232 wasm_encoder::EntityType::Function(type_idx),
233 );
234
235 self.host_function_indices
237 .insert(func_name.clone(), func_idx);
238 func_idx += 1;
239 }
240 }
241
242 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 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 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 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 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 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 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 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 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 let mut func = Function::new([(3, ValType::I32)]);
382
383 func.instruction(&Instruction::GlobalGet(0));
385 func.instruction(&Instruction::LocalSet(1));
386
387 func.instruction(&Instruction::LocalGet(1));
389 func.instruction(&Instruction::LocalGet(0));
390 func.instruction(&Instruction::I32Add);
391 func.instruction(&Instruction::LocalSet(2));
392
393 func.instruction(&Instruction::MemorySize(0));
395 func.instruction(&Instruction::I32Const(65536));
396 func.instruction(&Instruction::I32Mul);
397 func.instruction(&Instruction::LocalSet(3));
398
399 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 func.instruction(&Instruction::I32Const(16));
407 func.instruction(&Instruction::MemoryGrow(0));
408 func.instruction(&Instruction::I32Const(-1));
410 func.instruction(&Instruction::I32Eq);
411 func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
412 func.instruction(&Instruction::Unreachable);
414 func.instruction(&Instruction::End);
415
416 func.instruction(&Instruction::End);
417
418 func.instruction(&Instruction::LocalGet(2));
420 func.instruction(&Instruction::GlobalSet(0));
421
422 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 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 fn compile_expression(
670 &self,
671 func: &mut Function,
672 expr: &Expression,
673 model: &DomainModel,
674 ) -> SolverForgeResult<()> {
675 match expr {
676 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 Expression::Param { index } => {
694 func.instruction(&Instruction::LocalGet(*index));
695 }
696
697 Expression::FieldAccess {
699 object,
700 class_name,
701 field_name,
702 } => {
703 self.compile_expression(func, object, model)?;
705
706 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 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 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); }
817 Expression::IsNull { operand } => {
818 self.compile_expression(func, operand, model)?;
819 func.instruction(&Instruction::I32Eqz); }
821 Expression::IsNotNull { operand } => {
822 self.compile_expression(func, operand, model)?;
823 func.instruction(&Instruction::I32Const(0));
824 func.instruction(&Instruction::I32Ne); }
826
827 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 Expression::ListContains { list, element } => {
851 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 Expression::HostCall {
870 function_name,
871 args,
872 } => {
873 for arg in args {
875 self.compile_expression(func, arg, model)?;
876 }
877
878 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 func.instruction(&Instruction::Call(*func_idx));
892 }
893
894 Expression::IfThenElse {
896 condition,
897 then_branch,
898 else_branch,
899 } => {
900 self.compile_expression(func, condition, model)?;
902
903 func.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
905 ValType::I32,
906 )));
907
908 self.compile_expression(func, then_branch, model)?;
910
911 func.instruction(&Instruction::Else);
912
913 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 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)); func.instruction(&Instruction::LocalGet(1)); 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 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)); 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 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 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)); func.instruction(&Instruction::LocalGet(1)); 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 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)); func.instruction(&Instruction::LocalGet(1)); func.instruction(&Instruction::LocalGet(2)); 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 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)); 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 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)); func.instruction(&Instruction::LocalGet(1)); 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 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)); func.instruction(&Instruction::LocalGet(1)); func.instruction(&Instruction::LocalGet(2)); 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 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)); func.instruction(&Instruction::LocalGet(1)); 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 #[allow(dead_code)]
1111 fn generate_create_list(&self) -> Function {
1112 let mut func = Function::new([(1, ValType::I32)]);
1113
1114 func.instruction(&Instruction::I32Const(8));
1117 func.instruction(&Instruction::LocalGet(0)); func.instruction(&Instruction::I32Const(4));
1119 func.instruction(&Instruction::I32Mul);
1120 func.instruction(&Instruction::I32Add);
1121
1122 func.instruction(&Instruction::GlobalGet(0));
1124 func.instruction(&Instruction::LocalTee(1)); 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 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 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 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 func.instruction(&Instruction::LocalGet(0)); func.instruction(&Instruction::I32Const(8));
1167 func.instruction(&Instruction::I32Add);
1168 func.instruction(&Instruction::LocalGet(1)); 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 func.instruction(&Instruction::LocalGet(0)); func.instruction(&Instruction::I32Const(8));
1189 func.instruction(&Instruction::I32Add);
1190 func.instruction(&Instruction::LocalGet(1)); func.instruction(&Instruction::I32Const(4));
1192 func.instruction(&Instruction::I32Mul);
1193 func.instruction(&Instruction::I32Add);
1194 func.instruction(&Instruction::LocalGet(2)); 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 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)); 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)); func.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
1243 offset: 0,
1244 align: 2,
1245 memory_index: 0,
1246 }));
1247
1248 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 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)); func.instruction(&Instruction::I32Const(4));
1273 func.instruction(&Instruction::I32Mul);
1274 func.instruction(&Instruction::I32Add);
1275 func.instruction(&Instruction::LocalGet(2)); 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 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 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")); }
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 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 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 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 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 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}