Skip to main content

yulang_native/
native_runtime.rs

1//! Runtime value support used by native value-lane code.
2//!
3//! This module is the API boundary native code calls through helper symbols.
4//! The initial implementation deliberately stores `VmValue` internally so the
5//! native prototype shares VM semantics.  The public helper surface is small
6//! enough that the storage can later move to a compact native value layout.
7
8use std::collections::BTreeMap;
9use std::io::Write;
10use std::rc::Rc;
11
12use yulang_runtime as runtime;
13use yulang_runtime::runtime::list_tree::ListView;
14use yulang_typed_ir as typed_ir;
15
16pub const NATIVE_PRIMITIVE_BOOL_NOT: i64 = 1;
17pub const NATIVE_PRIMITIVE_INT_TO_STRING: i64 = 2;
18pub const NATIVE_PRIMITIVE_INT_TO_HEX: i64 = 3;
19pub const NATIVE_PRIMITIVE_INT_TO_UPPER_HEX: i64 = 4;
20pub const NATIVE_PRIMITIVE_FLOAT_TO_STRING: i64 = 5;
21pub const NATIVE_PRIMITIVE_BOOL_TO_STRING: i64 = 6;
22pub const NATIVE_PRIMITIVE_STRING_LEN: i64 = 7;
23
24pub const NATIVE_PRIMITIVE_BOOL_EQ: i64 = 101;
25pub const NATIVE_PRIMITIVE_INT_ADD: i64 = 102;
26pub const NATIVE_PRIMITIVE_INT_SUB: i64 = 103;
27pub const NATIVE_PRIMITIVE_INT_MUL: i64 = 104;
28pub const NATIVE_PRIMITIVE_INT_DIV: i64 = 105;
29pub const NATIVE_PRIMITIVE_INT_EQ: i64 = 106;
30pub const NATIVE_PRIMITIVE_INT_LT: i64 = 107;
31pub const NATIVE_PRIMITIVE_INT_LE: i64 = 108;
32pub const NATIVE_PRIMITIVE_INT_GT: i64 = 109;
33pub const NATIVE_PRIMITIVE_INT_GE: i64 = 110;
34pub const NATIVE_PRIMITIVE_FLOAT_ADD: i64 = 111;
35pub const NATIVE_PRIMITIVE_FLOAT_SUB: i64 = 112;
36pub const NATIVE_PRIMITIVE_FLOAT_MUL: i64 = 113;
37pub const NATIVE_PRIMITIVE_FLOAT_DIV: i64 = 114;
38pub const NATIVE_PRIMITIVE_FLOAT_EQ: i64 = 115;
39pub const NATIVE_PRIMITIVE_FLOAT_LT: i64 = 116;
40pub const NATIVE_PRIMITIVE_FLOAT_LE: i64 = 117;
41pub const NATIVE_PRIMITIVE_FLOAT_GT: i64 = 118;
42pub const NATIVE_PRIMITIVE_FLOAT_GE: i64 = 119;
43pub const NATIVE_PRIMITIVE_STRING_INDEX: i64 = 120;
44pub const NATIVE_PRIMITIVE_STRING_EQ: i64 = 121;
45
46#[derive(Default)]
47pub struct NativeRuntimeContext {
48    values: Vec<Box<runtime::VmValue>>,
49    closures: Vec<Box<NativeRuntimeClosure>>,
50}
51
52pub struct NativeRuntimeClosure {
53    target: i64,
54    environment: Vec<*mut runtime::VmValue>,
55}
56
57impl NativeRuntimeContext {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    pub fn alloc(&mut self, value: runtime::VmValue) -> *mut runtime::VmValue {
63        self.values.push(Box::new(value));
64        self.values
65            .last_mut()
66            .map(|value| value.as_mut() as *mut runtime::VmValue)
67            .unwrap_or(std::ptr::null_mut())
68    }
69
70    pub fn alloc_closure(&mut self, target: i64) -> *mut NativeRuntimeClosure {
71        self.closures.push(Box::new(NativeRuntimeClosure {
72            target,
73            environment: Vec::new(),
74        }));
75        self.closures
76            .last_mut()
77            .map(|closure| closure.as_mut() as *mut NativeRuntimeClosure)
78            .unwrap_or(std::ptr::null_mut())
79    }
80
81    pub fn is_closure_handle(&self, value: *mut runtime::VmValue) -> bool {
82        let ptr = value.cast::<NativeRuntimeClosure>() as *const NativeRuntimeClosure;
83        self.closures
84            .iter()
85            .any(|closure| std::ptr::eq(closure.as_ref(), ptr))
86    }
87
88    pub fn clone_value(&self, value: *mut runtime::VmValue) -> Option<runtime::VmValue> {
89        if value.is_null() {
90            return None;
91        }
92        Some(unsafe { (*value).clone() })
93    }
94}
95
96pub fn make_int(context: &mut NativeRuntimeContext, bytes: &[u8]) -> Option<*mut runtime::VmValue> {
97    let text = std::str::from_utf8(bytes).ok()?;
98    Some(context.alloc(runtime::VmValue::Int(text.to_string())))
99}
100
101pub fn make_string(
102    context: &mut NativeRuntimeContext,
103    bytes: &[u8],
104) -> Option<*mut runtime::VmValue> {
105    let text = std::str::from_utf8(bytes).ok()?;
106    Some(context.alloc(runtime::VmValue::String(
107        runtime::runtime::string_tree::StringTree::from_str(text),
108    )))
109}
110
111pub fn make_float(
112    context: &mut NativeRuntimeContext,
113    bytes: &[u8],
114) -> Option<*mut runtime::VmValue> {
115    let text = std::str::from_utf8(bytes).ok()?;
116    Some(context.alloc(runtime::VmValue::Float(text.to_string())))
117}
118
119pub fn make_bool(context: &mut NativeRuntimeContext, value: bool) -> *mut runtime::VmValue {
120    context.alloc(runtime::VmValue::Bool(value))
121}
122
123pub fn bool_is_true(value: *mut runtime::VmValue) -> Option<i64> {
124    let value = unsafe { value.as_ref()? };
125    let runtime::VmValue::Bool(value) = value else {
126        return None;
127    };
128    Some(i64::from(*value))
129}
130
131pub fn value_eq(
132    context: &mut NativeRuntimeContext,
133    left: *mut runtime::VmValue,
134    right: *mut runtime::VmValue,
135) -> Option<*mut runtime::VmValue> {
136    let left = unsafe { left.as_ref()? };
137    let right = unsafe { right.as_ref()? };
138    Some(context.alloc(runtime::VmValue::Bool(vm_value_eq(left, right))))
139}
140
141pub fn bool_and(
142    context: &mut NativeRuntimeContext,
143    left: *mut runtime::VmValue,
144    right: *mut runtime::VmValue,
145) -> Option<*mut runtime::VmValue> {
146    let left = unsafe { left.as_ref()? };
147    let right = unsafe { right.as_ref()? };
148    Some(context.alloc(runtime::VmValue::Bool(
149        matches!(left, runtime::VmValue::Bool(true))
150            && matches!(right, runtime::VmValue::Bool(true)),
151    )))
152}
153
154pub fn make_unit(context: &mut NativeRuntimeContext) -> *mut runtime::VmValue {
155    context.alloc(runtime::VmValue::Unit)
156}
157
158pub fn closure_new(context: &mut NativeRuntimeContext, target: i64) -> *mut NativeRuntimeClosure {
159    context.alloc_closure(target)
160}
161
162pub fn closure_push_env(
163    closure: *mut NativeRuntimeClosure,
164    value: *mut runtime::VmValue,
165) -> Option<*mut NativeRuntimeClosure> {
166    let closure = unsafe { closure.as_mut()? };
167    if value.is_null() {
168        return None;
169    }
170    closure.environment.push(value);
171    Some(closure as *mut NativeRuntimeClosure)
172}
173
174pub fn closure_target_id(closure: *mut NativeRuntimeClosure) -> Option<i64> {
175    let closure = unsafe { closure.as_ref()? };
176    Some(closure.target)
177}
178
179pub fn closure_env_get(
180    closure: *mut NativeRuntimeClosure,
181    slot: usize,
182) -> Option<*mut runtime::VmValue> {
183    let closure = unsafe { closure.as_ref()? };
184    closure.environment.get(slot).copied()
185}
186
187pub fn concat_string(
188    context: &mut NativeRuntimeContext,
189    left: *mut runtime::VmValue,
190    right: *mut runtime::VmValue,
191) -> Option<*mut runtime::VmValue> {
192    let left = unsafe { left.as_ref()? };
193    let right = unsafe { right.as_ref()? };
194    let (runtime::VmValue::String(left), runtime::VmValue::String(right)) = (left, right) else {
195        return None;
196    };
197    Some(context.alloc(runtime::VmValue::String(
198        runtime::runtime::string_tree::StringTree::concat(left.clone(), right.clone()),
199    )))
200}
201
202pub fn primitive_unary(
203    context: &mut NativeRuntimeContext,
204    op: i64,
205    value: *mut runtime::VmValue,
206) -> Option<*mut runtime::VmValue> {
207    let value = unsafe { value.as_ref()? };
208    let result = match op {
209        NATIVE_PRIMITIVE_BOOL_NOT => runtime::VmValue::Bool(!bool_value(value)?),
210        NATIVE_PRIMITIVE_INT_TO_STRING => {
211            runtime::VmValue::String(string_tree_from(int_value(value)?.to_string()))
212        }
213        NATIVE_PRIMITIVE_INT_TO_HEX => {
214            runtime::VmValue::String(string_tree_from(format!("{:x}", int_value(value)?)))
215        }
216        NATIVE_PRIMITIVE_INT_TO_UPPER_HEX => {
217            runtime::VmValue::String(string_tree_from(format!("{:X}", int_value(value)?)))
218        }
219        NATIVE_PRIMITIVE_FLOAT_TO_STRING => {
220            runtime::VmValue::String(string_tree_from(format_float_value(float_value(value)?)))
221        }
222        NATIVE_PRIMITIVE_BOOL_TO_STRING => {
223            runtime::VmValue::String(string_tree_from(bool_value(value)?.to_string()))
224        }
225        NATIVE_PRIMITIVE_STRING_LEN => {
226            runtime::VmValue::Int(string_value(value)?.len().to_string())
227        }
228        _ => return None,
229    };
230    Some(context.alloc(result))
231}
232
233pub fn primitive_binary(
234    context: &mut NativeRuntimeContext,
235    op: i64,
236    left: *mut runtime::VmValue,
237    right: *mut runtime::VmValue,
238) -> Option<*mut runtime::VmValue> {
239    let left = unsafe { left.as_ref()? };
240    let right = unsafe { right.as_ref()? };
241    let result = match op {
242        NATIVE_PRIMITIVE_BOOL_EQ => runtime::VmValue::Bool(bool_value(left)? == bool_value(right)?),
243        NATIVE_PRIMITIVE_INT_ADD => {
244            runtime::VmValue::Int((int_value(left)? + int_value(right)?).to_string())
245        }
246        NATIVE_PRIMITIVE_INT_SUB => {
247            runtime::VmValue::Int((int_value(left)? - int_value(right)?).to_string())
248        }
249        NATIVE_PRIMITIVE_INT_MUL => {
250            runtime::VmValue::Int((int_value(left)? * int_value(right)?).to_string())
251        }
252        NATIVE_PRIMITIVE_INT_DIV => {
253            runtime::VmValue::Int((int_value(left)? / int_value(right)?).to_string())
254        }
255        NATIVE_PRIMITIVE_INT_EQ => runtime::VmValue::Bool(int_value(left)? == int_value(right)?),
256        NATIVE_PRIMITIVE_INT_LT => runtime::VmValue::Bool(int_value(left)? < int_value(right)?),
257        NATIVE_PRIMITIVE_INT_LE => runtime::VmValue::Bool(int_value(left)? <= int_value(right)?),
258        NATIVE_PRIMITIVE_INT_GT => runtime::VmValue::Bool(int_value(left)? > int_value(right)?),
259        NATIVE_PRIMITIVE_INT_GE => runtime::VmValue::Bool(int_value(left)? >= int_value(right)?),
260        NATIVE_PRIMITIVE_FLOAT_ADD => {
261            runtime::VmValue::Float(format_float_value(float_value(left)? + float_value(right)?))
262        }
263        NATIVE_PRIMITIVE_FLOAT_SUB => {
264            runtime::VmValue::Float(format_float_value(float_value(left)? - float_value(right)?))
265        }
266        NATIVE_PRIMITIVE_FLOAT_MUL => {
267            runtime::VmValue::Float(format_float_value(float_value(left)? * float_value(right)?))
268        }
269        NATIVE_PRIMITIVE_FLOAT_DIV => {
270            runtime::VmValue::Float(format_float_value(float_value(left)? / float_value(right)?))
271        }
272        NATIVE_PRIMITIVE_FLOAT_EQ => {
273            runtime::VmValue::Bool(float_value(left)? == float_value(right)?)
274        }
275        NATIVE_PRIMITIVE_FLOAT_LT => {
276            runtime::VmValue::Bool(float_value(left)? < float_value(right)?)
277        }
278        NATIVE_PRIMITIVE_FLOAT_LE => {
279            runtime::VmValue::Bool(float_value(left)? <= float_value(right)?)
280        }
281        NATIVE_PRIMITIVE_FLOAT_GT => {
282            runtime::VmValue::Bool(float_value(left)? > float_value(right)?)
283        }
284        NATIVE_PRIMITIVE_FLOAT_GE => {
285            runtime::VmValue::Bool(float_value(left)? >= float_value(right)?)
286        }
287        NATIVE_PRIMITIVE_STRING_INDEX => {
288            let index = usize::try_from(int_value(right)?).ok()?;
289            runtime::VmValue::String(string_tree_from(string_value(left)?.index(index)?))
290        }
291        NATIVE_PRIMITIVE_STRING_EQ => runtime::VmValue::Bool(
292            string_value(left)?.to_flat_string() == string_value(right)?.to_flat_string(),
293        ),
294        _ => return None,
295    };
296    Some(context.alloc(result))
297}
298
299pub fn list_empty(context: &mut NativeRuntimeContext) -> *mut runtime::VmValue {
300    context.alloc(runtime::VmValue::List(
301        runtime::runtime::list_tree::ListTree::empty(),
302    ))
303}
304
305pub fn list_singleton(
306    context: &mut NativeRuntimeContext,
307    value: *mut runtime::VmValue,
308) -> Option<*mut runtime::VmValue> {
309    let value = unsafe { value.as_ref()? };
310    Some(context.alloc(runtime::VmValue::List(
311        runtime::runtime::list_tree::ListTree::singleton(Rc::new(value.clone())),
312    )))
313}
314
315pub fn list_merge(
316    context: &mut NativeRuntimeContext,
317    left: *mut runtime::VmValue,
318    right: *mut runtime::VmValue,
319) -> Option<*mut runtime::VmValue> {
320    let left = unsafe { left.as_ref()? };
321    let right = unsafe { right.as_ref()? };
322    let (runtime::VmValue::List(left), runtime::VmValue::List(right)) = (left, right) else {
323        return None;
324    };
325    Some(context.alloc(runtime::VmValue::List(
326        runtime::runtime::list_tree::ListTree::concat(left.clone(), right.clone()),
327    )))
328}
329
330pub fn list_len(
331    context: &mut NativeRuntimeContext,
332    list: *mut runtime::VmValue,
333) -> Option<*mut runtime::VmValue> {
334    let list = unsafe { list.as_ref()? };
335    let runtime::VmValue::List(list) = list else {
336        return None;
337    };
338    Some(context.alloc(runtime::VmValue::Int(list.len().to_string())))
339}
340
341pub fn list_index(
342    context: &mut NativeRuntimeContext,
343    list: *mut runtime::VmValue,
344    index: *mut runtime::VmValue,
345) -> Option<*mut runtime::VmValue> {
346    let list = unsafe { list.as_ref()? };
347    let index = unsafe { index.as_ref()? };
348    let runtime::VmValue::List(list) = list else {
349        return None;
350    };
351    let runtime::VmValue::Int(index) = index else {
352        return None;
353    };
354    let index = index.parse::<usize>().ok()?;
355    let value = list.index(index)?;
356    Some(context.alloc(value.as_ref().clone()))
357}
358
359pub fn list_index_range(
360    context: &mut NativeRuntimeContext,
361    list: *mut runtime::VmValue,
362    range: *mut runtime::VmValue,
363) -> Option<*mut runtime::VmValue> {
364    let list = unsafe { list.as_ref()? };
365    let range = unsafe { range.as_ref()? };
366    let runtime::VmValue::List(list) = list else {
367        return None;
368    };
369    let (start, end) = normalized_int_range_value(range, list.len())?;
370    let value = list.index_range(start, end)?;
371    Some(context.alloc(runtime::VmValue::List(value)))
372}
373
374pub fn list_splice(
375    context: &mut NativeRuntimeContext,
376    list: *mut runtime::VmValue,
377    range: *mut runtime::VmValue,
378    insert: *mut runtime::VmValue,
379) -> Option<*mut runtime::VmValue> {
380    let list = unsafe { list.as_ref()? };
381    let range = unsafe { range.as_ref()? };
382    let insert = unsafe { insert.as_ref()? };
383    let runtime::VmValue::List(list) = list else {
384        return None;
385    };
386    let runtime::VmValue::List(insert) = insert else {
387        return None;
388    };
389    let (start, end) = normalized_int_range_value(range, list.len())?;
390    let value = list.splice(start, end, insert.clone())?;
391    Some(context.alloc(runtime::VmValue::List(value)))
392}
393
394pub fn list_index_range_raw(
395    context: &mut NativeRuntimeContext,
396    list: *mut runtime::VmValue,
397    start: *mut runtime::VmValue,
398    end: *mut runtime::VmValue,
399) -> Option<*mut runtime::VmValue> {
400    let list = unsafe { list.as_ref()? };
401    let start = unsafe { start.as_ref()? };
402    let end = unsafe { end.as_ref()? };
403    let runtime::VmValue::List(list) = list else {
404        return None;
405    };
406    let runtime::VmValue::Int(start) = start else {
407        return None;
408    };
409    let runtime::VmValue::Int(end) = end else {
410        return None;
411    };
412    let start = start.parse::<usize>().ok()?;
413    let end = end.parse::<usize>().ok()?;
414    let value = list.index_range(start, end)?;
415    Some(context.alloc(runtime::VmValue::List(value)))
416}
417
418pub fn list_splice_raw(
419    context: &mut NativeRuntimeContext,
420    list: *mut runtime::VmValue,
421    start: *mut runtime::VmValue,
422    end: *mut runtime::VmValue,
423    insert: *mut runtime::VmValue,
424) -> Option<*mut runtime::VmValue> {
425    let list = unsafe { list.as_ref()? };
426    let start = unsafe { start.as_ref()? };
427    let end = unsafe { end.as_ref()? };
428    let insert = unsafe { insert.as_ref()? };
429    let runtime::VmValue::List(list) = list else {
430        return None;
431    };
432    let runtime::VmValue::Int(start) = start else {
433        return None;
434    };
435    let runtime::VmValue::Int(end) = end else {
436        return None;
437    };
438    let runtime::VmValue::List(insert) = insert else {
439        return None;
440    };
441    let start = start.parse::<usize>().ok()?;
442    let end = end.parse::<usize>().ok()?;
443    let value = list.splice(start, end, insert.clone())?;
444    Some(context.alloc(runtime::VmValue::List(value)))
445}
446
447pub fn list_view_raw(
448    context: &mut NativeRuntimeContext,
449    list: *mut runtime::VmValue,
450) -> Option<*mut runtime::VmValue> {
451    let list = unsafe { list.as_ref()? };
452    let runtime::VmValue::List(list) = list else {
453        return None;
454    };
455    let value = match list.view() {
456        ListView::Empty => runtime::VmValue::Variant {
457            tag: typed_ir_name("empty"),
458            value: None,
459        },
460        ListView::Leaf(single) => runtime::VmValue::Variant {
461            tag: typed_ir_name("leaf"),
462            value: Some(Box::new((*single).clone())),
463        },
464        ListView::Node { left, right, .. } => runtime::VmValue::Variant {
465            tag: typed_ir_name("node"),
466            value: Some(Box::new(runtime::VmValue::Tuple(vec![
467                runtime::VmValue::List(left),
468                runtime::VmValue::List(right),
469            ]))),
470        },
471    };
472    Some(context.alloc(value))
473}
474
475pub fn string_index_range(
476    context: &mut NativeRuntimeContext,
477    text: *mut runtime::VmValue,
478    range: *mut runtime::VmValue,
479) -> Option<*mut runtime::VmValue> {
480    let text = unsafe { text.as_ref()? };
481    let range = unsafe { range.as_ref()? };
482    let text = string_value(text)?;
483    let (start, end) = normalized_int_range_value(range, text.len())?;
484    let value = text.index_range(start, end)?;
485    Some(context.alloc(runtime::VmValue::String(value)))
486}
487
488pub fn string_splice(
489    context: &mut NativeRuntimeContext,
490    text: *mut runtime::VmValue,
491    range: *mut runtime::VmValue,
492    insert: *mut runtime::VmValue,
493) -> Option<*mut runtime::VmValue> {
494    let text = unsafe { text.as_ref()? };
495    let range = unsafe { range.as_ref()? };
496    let insert = unsafe { insert.as_ref()? };
497    let text = string_value(text)?;
498    let insert = string_value(insert)?;
499    let (start, end) = normalized_int_range_value(range, text.len())?;
500    let value = text.splice(start, end, insert.clone())?;
501    Some(context.alloc(runtime::VmValue::String(value)))
502}
503
504pub fn string_index_range_raw(
505    context: &mut NativeRuntimeContext,
506    text: *mut runtime::VmValue,
507    start: *mut runtime::VmValue,
508    end: *mut runtime::VmValue,
509) -> Option<*mut runtime::VmValue> {
510    let text = unsafe { text.as_ref()? };
511    let start = unsafe { start.as_ref()? };
512    let end = unsafe { end.as_ref()? };
513    let text = string_value(text)?;
514    let runtime::VmValue::Int(start) = start else {
515        return None;
516    };
517    let runtime::VmValue::Int(end) = end else {
518        return None;
519    };
520    let start = start.parse::<usize>().ok()?;
521    let end = end.parse::<usize>().ok()?;
522    let value = text.index_range(start, end)?;
523    Some(context.alloc(runtime::VmValue::String(value)))
524}
525
526pub fn string_splice_raw(
527    context: &mut NativeRuntimeContext,
528    text: *mut runtime::VmValue,
529    start: *mut runtime::VmValue,
530    end: *mut runtime::VmValue,
531    insert: *mut runtime::VmValue,
532) -> Option<*mut runtime::VmValue> {
533    let text = unsafe { text.as_ref()? };
534    let start = unsafe { start.as_ref()? };
535    let end = unsafe { end.as_ref()? };
536    let insert = unsafe { insert.as_ref()? };
537    let text = string_value(text)?;
538    let runtime::VmValue::Int(start) = start else {
539        return None;
540    };
541    let runtime::VmValue::Int(end) = end else {
542        return None;
543    };
544    let insert = string_value(insert)?;
545    let start = start.parse::<usize>().ok()?;
546    let end = end.parse::<usize>().ok()?;
547    let value = text.splice(start, end, insert.clone())?;
548    Some(context.alloc(runtime::VmValue::String(value)))
549}
550
551pub fn tuple_empty(context: &mut NativeRuntimeContext) -> *mut runtime::VmValue {
552    context.alloc(runtime::VmValue::Tuple(Vec::new()))
553}
554
555pub fn tuple_push(
556    context: &mut NativeRuntimeContext,
557    tuple: *mut runtime::VmValue,
558    value: *mut runtime::VmValue,
559) -> Option<*mut runtime::VmValue> {
560    let tuple = unsafe { tuple.as_ref()? };
561    let value = unsafe { value.as_ref()? };
562    let runtime::VmValue::Tuple(items) = tuple else {
563        return None;
564    };
565    let mut items = items.clone();
566    items.push(value.clone());
567    Some(context.alloc(runtime::VmValue::Tuple(items)))
568}
569
570pub fn tuple_get(
571    context: &mut NativeRuntimeContext,
572    tuple: *mut runtime::VmValue,
573    index: usize,
574) -> Option<*mut runtime::VmValue> {
575    let tuple = unsafe { tuple.as_ref()? };
576    let runtime::VmValue::Tuple(items) = tuple else {
577        return None;
578    };
579    Some(context.alloc(items.get(index)?.clone()))
580}
581
582pub fn record_empty(context: &mut NativeRuntimeContext) -> *mut runtime::VmValue {
583    context.alloc(runtime::VmValue::Record(BTreeMap::new()))
584}
585
586pub fn record_insert(
587    context: &mut NativeRuntimeContext,
588    record: *mut runtime::VmValue,
589    name: &[u8],
590    value: *mut runtime::VmValue,
591) -> Option<*mut runtime::VmValue> {
592    let record = unsafe { record.as_ref()? };
593    let value = unsafe { value.as_ref()? };
594    let runtime::VmValue::Record(fields) = record else {
595        return None;
596    };
597    let name = std::str::from_utf8(name).ok()?;
598    let mut fields = fields.clone();
599    fields.insert(typed_ir_name(name), value.clone());
600    Some(context.alloc(runtime::VmValue::Record(fields)))
601}
602
603pub fn record_select(
604    context: &mut NativeRuntimeContext,
605    record: *mut runtime::VmValue,
606    name: &[u8],
607) -> Option<*mut runtime::VmValue> {
608    let record = unsafe { record.as_ref()? };
609    let runtime::VmValue::Record(fields) = record else {
610        return None;
611    };
612    let name = std::str::from_utf8(name).ok()?;
613    let value = fields.get(&typed_ir_name(name))?;
614    Some(context.alloc(value.clone()))
615}
616
617pub fn record_without_field(
618    context: &mut NativeRuntimeContext,
619    record: *mut runtime::VmValue,
620    name: &[u8],
621) -> Option<*mut runtime::VmValue> {
622    let record = unsafe { record.as_ref()? };
623    let runtime::VmValue::Record(fields) = record else {
624        return None;
625    };
626    let name = std::str::from_utf8(name).ok()?;
627    let mut fields = fields.clone();
628    fields.remove(&typed_ir_name(name));
629    Some(context.alloc(runtime::VmValue::Record(fields)))
630}
631
632pub fn variant(
633    context: &mut NativeRuntimeContext,
634    tag: &[u8],
635    value: *mut runtime::VmValue,
636) -> Option<*mut runtime::VmValue> {
637    let tag = std::str::from_utf8(tag).ok()?;
638    let value = if value.is_null() {
639        None
640    } else {
641        Some(Box::new(unsafe { value.as_ref()? }.clone()))
642    };
643    Some(context.alloc(runtime::VmValue::Variant {
644        tag: typed_ir_name(tag),
645        value,
646    }))
647}
648
649pub fn variant_tag_eq(
650    context: &mut NativeRuntimeContext,
651    variant: *mut runtime::VmValue,
652    tag: &[u8],
653) -> Option<*mut runtime::VmValue> {
654    let variant = unsafe { variant.as_ref()? };
655    let runtime::VmValue::Variant {
656        tag: actual_tag, ..
657    } = variant
658    else {
659        return None;
660    };
661    let tag = std::str::from_utf8(tag).ok()?;
662    Some(context.alloc(runtime::VmValue::Bool(actual_tag.0 == tag)))
663}
664
665pub fn variant_payload(
666    context: &mut NativeRuntimeContext,
667    variant: *mut runtime::VmValue,
668) -> Option<*mut runtime::VmValue> {
669    let variant = unsafe { variant.as_ref()? };
670    let runtime::VmValue::Variant {
671        value: Some(payload),
672        ..
673    } = variant
674    else {
675        return None;
676    };
677    Some(context.alloc((**payload).clone()))
678}
679
680#[unsafe(no_mangle)]
681pub extern "C" fn yulang_native_context_new() -> *mut NativeRuntimeContext {
682    Box::into_raw(Box::new(NativeRuntimeContext::new()))
683}
684
685#[unsafe(no_mangle)]
686pub extern "C" fn yulang_native_context_free(context: *mut NativeRuntimeContext) {
687    if context.is_null() {
688        return;
689    }
690    unsafe {
691        drop(Box::from_raw(context));
692    }
693}
694
695#[unsafe(no_mangle)]
696pub extern "C" fn yulang_native_print_value(value: *mut runtime::VmValue) {
697    if value.is_null() {
698        print!("<null>");
699        flush_stdout();
700        return;
701    }
702    let value = unsafe { &*value };
703    print_native_value(value);
704    flush_stdout();
705}
706
707#[unsafe(no_mangle)]
708pub extern "C" fn yulang_native_make_int(
709    context: *mut NativeRuntimeContext,
710    ptr: *const u8,
711    len: usize,
712) -> *mut runtime::VmValue {
713    let Some(context) = (unsafe { context.as_mut() }) else {
714        return std::ptr::null_mut();
715    };
716    let Some(bytes) = bytes_from_raw(ptr, len) else {
717        return std::ptr::null_mut();
718    };
719    make_int(context, bytes).unwrap_or(std::ptr::null_mut())
720}
721
722#[unsafe(no_mangle)]
723pub extern "C" fn yulang_native_make_string(
724    context: *mut NativeRuntimeContext,
725    ptr: *const u8,
726    len: usize,
727) -> *mut runtime::VmValue {
728    let Some(context) = (unsafe { context.as_mut() }) else {
729        return std::ptr::null_mut();
730    };
731    let Some(bytes) = bytes_from_raw(ptr, len) else {
732        return std::ptr::null_mut();
733    };
734    make_string(context, bytes).unwrap_or(std::ptr::null_mut())
735}
736
737#[unsafe(no_mangle)]
738pub extern "C" fn yulang_native_make_float(
739    context: *mut NativeRuntimeContext,
740    ptr: *const u8,
741    len: usize,
742) -> *mut runtime::VmValue {
743    let Some(context) = (unsafe { context.as_mut() }) else {
744        return std::ptr::null_mut();
745    };
746    let Some(bytes) = bytes_from_raw(ptr, len) else {
747        return std::ptr::null_mut();
748    };
749    make_float(context, bytes).unwrap_or(std::ptr::null_mut())
750}
751
752#[unsafe(no_mangle)]
753pub extern "C" fn yulang_native_make_bool(
754    context: *mut NativeRuntimeContext,
755    value: i64,
756) -> *mut runtime::VmValue {
757    let Some(context) = (unsafe { context.as_mut() }) else {
758        return std::ptr::null_mut();
759    };
760    make_bool(context, value != 0)
761}
762
763#[unsafe(no_mangle)]
764pub extern "C" fn yulang_native_bool_is_true(value: *mut runtime::VmValue) -> i64 {
765    bool_is_true(value).unwrap_or(0)
766}
767
768#[unsafe(no_mangle)]
769pub extern "C" fn yulang_native_value_eq(
770    context: *mut NativeRuntimeContext,
771    left: *mut runtime::VmValue,
772    right: *mut runtime::VmValue,
773) -> *mut runtime::VmValue {
774    let Some(context) = (unsafe { context.as_mut() }) else {
775        return std::ptr::null_mut();
776    };
777    value_eq(context, left, right).unwrap_or(std::ptr::null_mut())
778}
779
780#[unsafe(no_mangle)]
781pub extern "C" fn yulang_native_bool_and(
782    context: *mut NativeRuntimeContext,
783    left: *mut runtime::VmValue,
784    right: *mut runtime::VmValue,
785) -> *mut runtime::VmValue {
786    let Some(context) = (unsafe { context.as_mut() }) else {
787        return std::ptr::null_mut();
788    };
789    bool_and(context, left, right).unwrap_or(std::ptr::null_mut())
790}
791
792#[unsafe(no_mangle)]
793pub extern "C" fn yulang_native_make_unit(
794    context: *mut NativeRuntimeContext,
795) -> *mut runtime::VmValue {
796    let Some(context) = (unsafe { context.as_mut() }) else {
797        return std::ptr::null_mut();
798    };
799    make_unit(context)
800}
801
802#[unsafe(no_mangle)]
803pub extern "C" fn yulang_native_closure_new(
804    context: *mut NativeRuntimeContext,
805    target: i64,
806) -> *mut NativeRuntimeClosure {
807    let Some(context) = (unsafe { context.as_mut() }) else {
808        return std::ptr::null_mut();
809    };
810    closure_new(context, target)
811}
812
813#[unsafe(no_mangle)]
814pub extern "C" fn yulang_native_closure_push_env(
815    closure: *mut NativeRuntimeClosure,
816    value: *mut runtime::VmValue,
817) -> *mut NativeRuntimeClosure {
818    closure_push_env(closure, value).unwrap_or(std::ptr::null_mut())
819}
820
821#[unsafe(no_mangle)]
822pub extern "C" fn yulang_native_closure_target_id(closure: *mut NativeRuntimeClosure) -> i64 {
823    closure_target_id(closure).unwrap_or(-1)
824}
825
826#[unsafe(no_mangle)]
827pub extern "C" fn yulang_native_closure_env_get(
828    closure: *mut NativeRuntimeClosure,
829    slot: usize,
830) -> *mut runtime::VmValue {
831    closure_env_get(closure, slot).unwrap_or(std::ptr::null_mut())
832}
833
834#[unsafe(no_mangle)]
835pub extern "C" fn yulang_native_concat_string(
836    context: *mut NativeRuntimeContext,
837    left: *mut runtime::VmValue,
838    right: *mut runtime::VmValue,
839) -> *mut runtime::VmValue {
840    let Some(context) = (unsafe { context.as_mut() }) else {
841        return std::ptr::null_mut();
842    };
843    concat_string(context, left, right).unwrap_or(std::ptr::null_mut())
844}
845
846#[unsafe(no_mangle)]
847pub extern "C" fn yulang_native_primitive_unary(
848    context: *mut NativeRuntimeContext,
849    op: i64,
850    value: *mut runtime::VmValue,
851) -> *mut runtime::VmValue {
852    let Some(context) = (unsafe { context.as_mut() }) else {
853        return std::ptr::null_mut();
854    };
855    primitive_unary(context, op, value).unwrap_or(std::ptr::null_mut())
856}
857
858#[unsafe(no_mangle)]
859pub extern "C" fn yulang_native_primitive_binary(
860    context: *mut NativeRuntimeContext,
861    op: i64,
862    left: *mut runtime::VmValue,
863    right: *mut runtime::VmValue,
864) -> *mut runtime::VmValue {
865    let Some(context) = (unsafe { context.as_mut() }) else {
866        return std::ptr::null_mut();
867    };
868    primitive_binary(context, op, left, right).unwrap_or(std::ptr::null_mut())
869}
870
871#[unsafe(no_mangle)]
872pub extern "C" fn yulang_native_list_empty(
873    context: *mut NativeRuntimeContext,
874) -> *mut runtime::VmValue {
875    let Some(context) = (unsafe { context.as_mut() }) else {
876        return std::ptr::null_mut();
877    };
878    list_empty(context)
879}
880
881#[unsafe(no_mangle)]
882pub extern "C" fn yulang_native_list_singleton(
883    context: *mut NativeRuntimeContext,
884    value: *mut runtime::VmValue,
885) -> *mut runtime::VmValue {
886    let Some(context) = (unsafe { context.as_mut() }) else {
887        return std::ptr::null_mut();
888    };
889    list_singleton(context, value).unwrap_or(std::ptr::null_mut())
890}
891
892#[unsafe(no_mangle)]
893pub extern "C" fn yulang_native_list_merge(
894    context: *mut NativeRuntimeContext,
895    left: *mut runtime::VmValue,
896    right: *mut runtime::VmValue,
897) -> *mut runtime::VmValue {
898    let Some(context) = (unsafe { context.as_mut() }) else {
899        return std::ptr::null_mut();
900    };
901    list_merge(context, left, right).unwrap_or(std::ptr::null_mut())
902}
903
904#[unsafe(no_mangle)]
905pub extern "C" fn yulang_native_list_len(
906    context: *mut NativeRuntimeContext,
907    list: *mut runtime::VmValue,
908) -> *mut runtime::VmValue {
909    let Some(context) = (unsafe { context.as_mut() }) else {
910        return std::ptr::null_mut();
911    };
912    list_len(context, list).unwrap_or(std::ptr::null_mut())
913}
914
915#[unsafe(no_mangle)]
916pub extern "C" fn yulang_native_list_index(
917    context: *mut NativeRuntimeContext,
918    list: *mut runtime::VmValue,
919    index: *mut runtime::VmValue,
920) -> *mut runtime::VmValue {
921    let Some(context) = (unsafe { context.as_mut() }) else {
922        return std::ptr::null_mut();
923    };
924    list_index(context, list, index).unwrap_or(std::ptr::null_mut())
925}
926
927#[unsafe(no_mangle)]
928pub extern "C" fn yulang_native_list_index_range(
929    context: *mut NativeRuntimeContext,
930    list: *mut runtime::VmValue,
931    range: *mut runtime::VmValue,
932) -> *mut runtime::VmValue {
933    let Some(context) = (unsafe { context.as_mut() }) else {
934        return std::ptr::null_mut();
935    };
936    list_index_range(context, list, range).unwrap_or(std::ptr::null_mut())
937}
938
939#[unsafe(no_mangle)]
940pub extern "C" fn yulang_native_list_splice(
941    context: *mut NativeRuntimeContext,
942    list: *mut runtime::VmValue,
943    range: *mut runtime::VmValue,
944    insert: *mut runtime::VmValue,
945) -> *mut runtime::VmValue {
946    let Some(context) = (unsafe { context.as_mut() }) else {
947        return std::ptr::null_mut();
948    };
949    list_splice(context, list, range, insert).unwrap_or(std::ptr::null_mut())
950}
951
952#[unsafe(no_mangle)]
953pub extern "C" fn yulang_native_list_index_range_raw(
954    context: *mut NativeRuntimeContext,
955    list: *mut runtime::VmValue,
956    start: *mut runtime::VmValue,
957    end: *mut runtime::VmValue,
958) -> *mut runtime::VmValue {
959    let Some(context) = (unsafe { context.as_mut() }) else {
960        return std::ptr::null_mut();
961    };
962    list_index_range_raw(context, list, start, end).unwrap_or(std::ptr::null_mut())
963}
964
965#[unsafe(no_mangle)]
966pub extern "C" fn yulang_native_list_splice_raw(
967    context: *mut NativeRuntimeContext,
968    list: *mut runtime::VmValue,
969    start: *mut runtime::VmValue,
970    end: *mut runtime::VmValue,
971    insert: *mut runtime::VmValue,
972) -> *mut runtime::VmValue {
973    let Some(context) = (unsafe { context.as_mut() }) else {
974        return std::ptr::null_mut();
975    };
976    list_splice_raw(context, list, start, end, insert).unwrap_or(std::ptr::null_mut())
977}
978
979#[unsafe(no_mangle)]
980pub extern "C" fn yulang_native_list_view_raw(
981    context: *mut NativeRuntimeContext,
982    list: *mut runtime::VmValue,
983) -> *mut runtime::VmValue {
984    let Some(context) = (unsafe { context.as_mut() }) else {
985        return std::ptr::null_mut();
986    };
987    list_view_raw(context, list).unwrap_or(std::ptr::null_mut())
988}
989
990#[unsafe(no_mangle)]
991pub extern "C" fn yulang_native_string_index_range(
992    context: *mut NativeRuntimeContext,
993    text: *mut runtime::VmValue,
994    range: *mut runtime::VmValue,
995) -> *mut runtime::VmValue {
996    let Some(context) = (unsafe { context.as_mut() }) else {
997        return std::ptr::null_mut();
998    };
999    string_index_range(context, text, range).unwrap_or(std::ptr::null_mut())
1000}
1001
1002#[unsafe(no_mangle)]
1003pub extern "C" fn yulang_native_string_splice(
1004    context: *mut NativeRuntimeContext,
1005    text: *mut runtime::VmValue,
1006    range: *mut runtime::VmValue,
1007    insert: *mut runtime::VmValue,
1008) -> *mut runtime::VmValue {
1009    let Some(context) = (unsafe { context.as_mut() }) else {
1010        return std::ptr::null_mut();
1011    };
1012    string_splice(context, text, range, insert).unwrap_or(std::ptr::null_mut())
1013}
1014
1015#[unsafe(no_mangle)]
1016pub extern "C" fn yulang_native_string_index_range_raw(
1017    context: *mut NativeRuntimeContext,
1018    text: *mut runtime::VmValue,
1019    start: *mut runtime::VmValue,
1020    end: *mut runtime::VmValue,
1021) -> *mut runtime::VmValue {
1022    let Some(context) = (unsafe { context.as_mut() }) else {
1023        return std::ptr::null_mut();
1024    };
1025    string_index_range_raw(context, text, start, end).unwrap_or(std::ptr::null_mut())
1026}
1027
1028#[unsafe(no_mangle)]
1029pub extern "C" fn yulang_native_string_splice_raw(
1030    context: *mut NativeRuntimeContext,
1031    text: *mut runtime::VmValue,
1032    start: *mut runtime::VmValue,
1033    end: *mut runtime::VmValue,
1034    insert: *mut runtime::VmValue,
1035) -> *mut runtime::VmValue {
1036    let Some(context) = (unsafe { context.as_mut() }) else {
1037        return std::ptr::null_mut();
1038    };
1039    string_splice_raw(context, text, start, end, insert).unwrap_or(std::ptr::null_mut())
1040}
1041
1042#[unsafe(no_mangle)]
1043pub extern "C" fn yulang_native_tuple_empty(
1044    context: *mut NativeRuntimeContext,
1045) -> *mut runtime::VmValue {
1046    let Some(context) = (unsafe { context.as_mut() }) else {
1047        return std::ptr::null_mut();
1048    };
1049    tuple_empty(context)
1050}
1051
1052#[unsafe(no_mangle)]
1053pub extern "C" fn yulang_native_tuple_push(
1054    context: *mut NativeRuntimeContext,
1055    tuple: *mut runtime::VmValue,
1056    value: *mut runtime::VmValue,
1057) -> *mut runtime::VmValue {
1058    let Some(context) = (unsafe { context.as_mut() }) else {
1059        return std::ptr::null_mut();
1060    };
1061    tuple_push(context, tuple, value).unwrap_or(std::ptr::null_mut())
1062}
1063
1064#[unsafe(no_mangle)]
1065pub extern "C" fn yulang_native_tuple_get(
1066    context: *mut NativeRuntimeContext,
1067    tuple: *mut runtime::VmValue,
1068    index: usize,
1069) -> *mut runtime::VmValue {
1070    let Some(context) = (unsafe { context.as_mut() }) else {
1071        return std::ptr::null_mut();
1072    };
1073    tuple_get(context, tuple, index).unwrap_or(std::ptr::null_mut())
1074}
1075
1076#[unsafe(no_mangle)]
1077pub extern "C" fn yulang_native_record_empty(
1078    context: *mut NativeRuntimeContext,
1079) -> *mut runtime::VmValue {
1080    let Some(context) = (unsafe { context.as_mut() }) else {
1081        return std::ptr::null_mut();
1082    };
1083    record_empty(context)
1084}
1085
1086#[unsafe(no_mangle)]
1087pub extern "C" fn yulang_native_record_insert(
1088    context: *mut NativeRuntimeContext,
1089    record: *mut runtime::VmValue,
1090    name_ptr: *const u8,
1091    name_len: usize,
1092    value: *mut runtime::VmValue,
1093) -> *mut runtime::VmValue {
1094    let Some(context) = (unsafe { context.as_mut() }) else {
1095        return std::ptr::null_mut();
1096    };
1097    let Some(name) = bytes_from_raw(name_ptr, name_len) else {
1098        return std::ptr::null_mut();
1099    };
1100    record_insert(context, record, name, value).unwrap_or(std::ptr::null_mut())
1101}
1102
1103#[unsafe(no_mangle)]
1104pub extern "C" fn yulang_native_record_select(
1105    context: *mut NativeRuntimeContext,
1106    record: *mut runtime::VmValue,
1107    name_ptr: *const u8,
1108    name_len: usize,
1109) -> *mut runtime::VmValue {
1110    let Some(context) = (unsafe { context.as_mut() }) else {
1111        return std::ptr::null_mut();
1112    };
1113    let Some(name) = bytes_from_raw(name_ptr, name_len) else {
1114        return std::ptr::null_mut();
1115    };
1116    record_select(context, record, name).unwrap_or(std::ptr::null_mut())
1117}
1118
1119#[unsafe(no_mangle)]
1120pub extern "C" fn yulang_native_record_without_field(
1121    context: *mut NativeRuntimeContext,
1122    record: *mut runtime::VmValue,
1123    name_ptr: *const u8,
1124    name_len: usize,
1125) -> *mut runtime::VmValue {
1126    let Some(context) = (unsafe { context.as_mut() }) else {
1127        return std::ptr::null_mut();
1128    };
1129    let Some(name) = bytes_from_raw(name_ptr, name_len) else {
1130        return std::ptr::null_mut();
1131    };
1132    record_without_field(context, record, name).unwrap_or(std::ptr::null_mut())
1133}
1134
1135#[unsafe(no_mangle)]
1136pub extern "C" fn yulang_native_variant(
1137    context: *mut NativeRuntimeContext,
1138    tag_ptr: *const u8,
1139    tag_len: usize,
1140    value: *mut runtime::VmValue,
1141) -> *mut runtime::VmValue {
1142    let Some(context) = (unsafe { context.as_mut() }) else {
1143        return std::ptr::null_mut();
1144    };
1145    let Some(tag) = bytes_from_raw(tag_ptr, tag_len) else {
1146        return std::ptr::null_mut();
1147    };
1148    variant(context, tag, value).unwrap_or(std::ptr::null_mut())
1149}
1150
1151#[unsafe(no_mangle)]
1152pub extern "C" fn yulang_native_variant_tag_eq(
1153    context: *mut NativeRuntimeContext,
1154    variant: *mut runtime::VmValue,
1155    tag_ptr: *const u8,
1156    tag_len: usize,
1157) -> *mut runtime::VmValue {
1158    let Some(context) = (unsafe { context.as_mut() }) else {
1159        return std::ptr::null_mut();
1160    };
1161    let Some(tag) = bytes_from_raw(tag_ptr, tag_len) else {
1162        return std::ptr::null_mut();
1163    };
1164    variant_tag_eq(context, variant, tag).unwrap_or(std::ptr::null_mut())
1165}
1166
1167#[unsafe(no_mangle)]
1168pub extern "C" fn yulang_native_variant_payload(
1169    context: *mut NativeRuntimeContext,
1170    variant: *mut runtime::VmValue,
1171) -> *mut runtime::VmValue {
1172    let Some(context) = (unsafe { context.as_mut() }) else {
1173        return std::ptr::null_mut();
1174    };
1175    variant_payload(context, variant).unwrap_or(std::ptr::null_mut())
1176}
1177
1178fn print_native_value(value: &runtime::VmValue) {
1179    match value {
1180        runtime::VmValue::Int(value) | runtime::VmValue::Float(value) => print!("{value}"),
1181        runtime::VmValue::String(value) => print!("{}", value.to_flat_string()),
1182        runtime::VmValue::Bool(value) => print!("{value}"),
1183        runtime::VmValue::Unit => print!("()"),
1184        runtime::VmValue::List(value) => {
1185            print!("[");
1186            for (index, item) in value.to_vec().into_iter().enumerate() {
1187                if index > 0 {
1188                    print!(", ");
1189                }
1190                print_native_value(item.as_ref());
1191            }
1192            print!("]");
1193        }
1194        runtime::VmValue::Tuple(items) => {
1195            print!("(");
1196            for (index, item) in items.iter().enumerate() {
1197                if index > 0 {
1198                    print!(", ");
1199                }
1200                print_native_value(item);
1201            }
1202            print!(")");
1203        }
1204        runtime::VmValue::Record(fields) => {
1205            print!("{{");
1206            for (index, (name, value)) in fields.iter().enumerate() {
1207                if index > 0 {
1208                    print!(", ");
1209                }
1210                print!("{} = ", name.0);
1211                print_native_value(value);
1212            }
1213            print!("}}");
1214        }
1215        runtime::VmValue::Variant { tag, value } => {
1216            print!(":{}", tag.0);
1217            if let Some(value) = value {
1218                print!(" ");
1219                print_native_value(value);
1220            }
1221        }
1222        other => print!("{other:?}"),
1223    }
1224}
1225
1226fn typed_ir_name(name: &str) -> typed_ir::Name {
1227    typed_ir::Name(name.to_string())
1228}
1229
1230fn normalized_int_range_value(value: &runtime::VmValue, len: usize) -> Option<(usize, usize)> {
1231    let runtime::VmValue::Variant { tag, value } = value else {
1232        return None;
1233    };
1234    if tag.0 != "within" {
1235        return None;
1236    }
1237    let value = value.as_ref()?;
1238    let runtime::VmValue::Tuple(items) = value.as_ref() else {
1239        return None;
1240    };
1241    let [start, end] = items.as_slice() else {
1242        return None;
1243    };
1244    let start = normalized_start_bound_value(start)?;
1245    let end = normalized_end_bound_value(end, len)?;
1246    (start <= end && end <= len).then_some((start, end))
1247}
1248
1249fn normalized_start_bound_value(value: &runtime::VmValue) -> Option<usize> {
1250    let runtime::VmValue::Variant { tag, value } = value else {
1251        return None;
1252    };
1253    match tag.0.as_str() {
1254        "unbounded" => Some(0),
1255        "included" => int_variant_payload(value).and_then(|value| usize::try_from(value).ok()),
1256        "excluded" => int_variant_payload(value).and_then(|value| usize::try_from(value + 1).ok()),
1257        _ => None,
1258    }
1259}
1260
1261fn normalized_end_bound_value(value: &runtime::VmValue, len: usize) -> Option<usize> {
1262    let runtime::VmValue::Variant { tag, value } = value else {
1263        return None;
1264    };
1265    match tag.0.as_str() {
1266        "unbounded" => Some(len),
1267        "included" => int_variant_payload(value).and_then(|value| usize::try_from(value + 1).ok()),
1268        "excluded" => int_variant_payload(value).and_then(|value| usize::try_from(value).ok()),
1269        _ => None,
1270    }
1271}
1272
1273fn int_variant_payload(value: &Option<Box<runtime::VmValue>>) -> Option<i64> {
1274    int_value(value.as_ref()?)
1275}
1276
1277fn int_value(value: &runtime::VmValue) -> Option<i64> {
1278    let runtime::VmValue::Int(value) = value else {
1279        return None;
1280    };
1281    value.parse().ok()
1282}
1283
1284fn float_value(value: &runtime::VmValue) -> Option<f64> {
1285    let runtime::VmValue::Float(value) = value else {
1286        return None;
1287    };
1288    value.parse().ok()
1289}
1290
1291fn bool_value(value: &runtime::VmValue) -> Option<bool> {
1292    let runtime::VmValue::Bool(value) = value else {
1293        return None;
1294    };
1295    Some(*value)
1296}
1297
1298fn vm_value_eq(left: &runtime::VmValue, right: &runtime::VmValue) -> bool {
1299    match (left, right) {
1300        (runtime::VmValue::Int(left), runtime::VmValue::Int(right))
1301        | (runtime::VmValue::Float(left), runtime::VmValue::Float(right)) => left == right,
1302        (runtime::VmValue::String(left), runtime::VmValue::String(right)) => {
1303            left.to_flat_string() == right.to_flat_string()
1304        }
1305        (runtime::VmValue::Bool(left), runtime::VmValue::Bool(right)) => left == right,
1306        (runtime::VmValue::Unit, runtime::VmValue::Unit) => true,
1307        (runtime::VmValue::Tuple(left), runtime::VmValue::Tuple(right)) => {
1308            left.len() == right.len()
1309                && left
1310                    .iter()
1311                    .zip(right)
1312                    .all(|(left, right)| vm_value_eq(left, right))
1313        }
1314        (runtime::VmValue::Record(left), runtime::VmValue::Record(right)) => {
1315            left.len() == right.len()
1316                && left.iter().all(|(name, left)| {
1317                    right
1318                        .get(name)
1319                        .is_some_and(|right| vm_value_eq(left, right))
1320                })
1321        }
1322        (
1323            runtime::VmValue::Variant {
1324                tag: left_tag,
1325                value: left_value,
1326            },
1327            runtime::VmValue::Variant {
1328                tag: right_tag,
1329                value: right_value,
1330            },
1331        ) => {
1332            left_tag == right_tag
1333                && match (left_value, right_value) {
1334                    (Some(left), Some(right)) => vm_value_eq(left, right),
1335                    (None, None) => true,
1336                    _ => false,
1337                }
1338        }
1339        (runtime::VmValue::List(left), runtime::VmValue::List(right)) => {
1340            let left = left.to_vec();
1341            let right = right.to_vec();
1342            left.len() == right.len()
1343                && left
1344                    .iter()
1345                    .zip(&right)
1346                    .all(|(left, right)| vm_value_eq(left, right))
1347        }
1348        _ => left == right,
1349    }
1350}
1351
1352fn string_value(value: &runtime::VmValue) -> Option<&runtime::runtime::string_tree::StringTree> {
1353    let runtime::VmValue::String(value) = value else {
1354        return None;
1355    };
1356    Some(value)
1357}
1358
1359fn string_tree_from(value: impl Into<String>) -> runtime::runtime::string_tree::StringTree {
1360    runtime::runtime::string_tree::StringTree::from_str(&value.into())
1361}
1362
1363fn format_float_value(value: f64) -> String {
1364    let mut rendered = value.to_string();
1365    if !rendered.contains('.') && !rendered.contains('e') && !rendered.contains('E') {
1366        rendered.push_str(".0");
1367    }
1368    rendered
1369}
1370
1371fn flush_stdout() {
1372    let _ = std::io::stdout().flush();
1373}
1374
1375fn bytes_from_raw<'a>(ptr: *const u8, len: usize) -> Option<&'a [u8]> {
1376    if ptr.is_null() {
1377        return None;
1378    }
1379    Some(unsafe { std::slice::from_raw_parts(ptr, len) })
1380}
1381
1382#[cfg(test)]
1383mod tests {
1384    use super::*;
1385
1386    #[test]
1387    fn api_builds_string_concat() {
1388        let mut context = NativeRuntimeContext::new();
1389        let left = make_string(&mut context, b"yu").expect("left");
1390        let right = make_string(&mut context, b"lang").expect("right");
1391        let value = concat_string(&mut context, left, right).expect("concat");
1392
1393        let Some(runtime::VmValue::String(value)) = context.clone_value(value) else {
1394            panic!("expected string");
1395        };
1396        assert_eq!(value.to_flat_string(), "yulang");
1397    }
1398
1399    #[test]
1400    fn api_builds_shared_vm_list() {
1401        let mut context = NativeRuntimeContext::new();
1402        let one = make_int(&mut context, b"1").expect("one");
1403        let two = make_int(&mut context, b"2").expect("two");
1404        let left = list_singleton(&mut context, one).expect("left");
1405        let right = list_singleton(&mut context, two).expect("right");
1406        let value = list_merge(&mut context, left, right).expect("merge");
1407
1408        let Some(runtime::VmValue::List(value)) = context.clone_value(value) else {
1409            panic!("expected list");
1410        };
1411        let items = value
1412            .to_vec()
1413            .into_iter()
1414            .map(|value| match value.as_ref() {
1415                runtime::VmValue::Int(value) => value.clone(),
1416                other => panic!("expected int item, got {other:?}"),
1417            })
1418            .collect::<Vec<_>>();
1419        assert_eq!(items, ["1", "2"]);
1420    }
1421
1422    #[test]
1423    fn api_indexes_list_and_reports_length() {
1424        let mut context = NativeRuntimeContext::new();
1425        let one = make_int(&mut context, b"1").expect("one");
1426        let two = make_int(&mut context, b"2").expect("two");
1427        let left = list_singleton(&mut context, one).expect("left");
1428        let right = list_singleton(&mut context, two).expect("right");
1429        let list = list_merge(&mut context, left, right).expect("merge");
1430        let index = make_int(&mut context, b"1").expect("index");
1431
1432        let len = list_len(&mut context, list).expect("len");
1433        let item = list_index(&mut context, list, index).expect("index value");
1434
1435        assert!(matches!(
1436            context.clone_value(len),
1437            Some(runtime::VmValue::Int(value)) if value == "2"
1438        ));
1439        assert!(matches!(
1440            context.clone_value(item),
1441            Some(runtime::VmValue::Int(value)) if value == "2"
1442        ));
1443    }
1444
1445    #[test]
1446    fn api_indexes_list_range() {
1447        let mut context = NativeRuntimeContext::new();
1448        let one = make_int(&mut context, b"1").expect("one");
1449        let two = make_int(&mut context, b"2").expect("two");
1450        let three = make_int(&mut context, b"3").expect("three");
1451        let one_list = list_singleton(&mut context, one).expect("one list");
1452        let two_list = list_singleton(&mut context, two).expect("two list");
1453        let three_list = list_singleton(&mut context, three).expect("three list");
1454        let first = list_merge(&mut context, one_list, two_list).expect("first merge");
1455        let list = list_merge(&mut context, first, three_list).expect("second merge");
1456        let range = range_value(1, 3);
1457        let range = context.alloc(range);
1458
1459        let value = list_index_range(&mut context, list, range).expect("range");
1460
1461        let Some(runtime::VmValue::List(value)) = context.clone_value(value) else {
1462            panic!("expected list");
1463        };
1464        let items = value
1465            .to_vec()
1466            .into_iter()
1467            .map(|value| match value.as_ref() {
1468                runtime::VmValue::Int(value) => value.clone(),
1469                other => panic!("expected int item, got {other:?}"),
1470            })
1471            .collect::<Vec<_>>();
1472        assert_eq!(items, ["2", "3"]);
1473    }
1474
1475    #[test]
1476    fn api_splices_list_range() {
1477        let mut context = NativeRuntimeContext::new();
1478        let list = int_list(&mut context, ["1", "2", "3", "4"]);
1479        let insert = int_list(&mut context, ["9", "8"]);
1480        let range = context.alloc(range_value(1, 3));
1481
1482        let value = list_splice(&mut context, list, range, insert).expect("splice");
1483
1484        let Some(runtime::VmValue::List(value)) = context.clone_value(value) else {
1485            panic!("expected list");
1486        };
1487        let items = value
1488            .to_vec()
1489            .into_iter()
1490            .map(|value| match value.as_ref() {
1491                runtime::VmValue::Int(value) => value.clone(),
1492                other => panic!("expected int item, got {other:?}"),
1493            })
1494            .collect::<Vec<_>>();
1495        assert_eq!(items, ["1", "9", "8", "4"]);
1496    }
1497
1498    #[test]
1499    fn api_views_raw_list() {
1500        let mut context = NativeRuntimeContext::new();
1501        let empty = list_empty(&mut context);
1502        let leaf = int_list(&mut context, ["1"]);
1503        let node = int_list(&mut context, ["1", "2"]);
1504
1505        let empty = list_view_raw(&mut context, empty).expect("empty view");
1506        let leaf = list_view_raw(&mut context, leaf).expect("leaf view");
1507        let node = list_view_raw(&mut context, node).expect("node view");
1508
1509        assert!(matches!(
1510            context.clone_value(empty),
1511            Some(runtime::VmValue::Variant { tag, value: None }) if tag.0 == "empty"
1512        ));
1513        assert!(matches!(
1514            context.clone_value(leaf),
1515            Some(runtime::VmValue::Variant { tag, value: Some(value) })
1516                if tag.0 == "leaf" && matches!(value.as_ref(), runtime::VmValue::Int(value) if value == "1")
1517        ));
1518        assert!(matches!(
1519            context.clone_value(node),
1520            Some(runtime::VmValue::Variant { tag, value: Some(value) })
1521                if tag.0 == "node" && matches!(value.as_ref(), runtime::VmValue::Tuple(items) if items.len() == 2)
1522        ));
1523    }
1524
1525    #[test]
1526    fn api_indexes_and_splices_string_range() {
1527        let mut context = NativeRuntimeContext::new();
1528        let text = make_string(&mut context, "ać‚šŸ™‚z".as_bytes()).expect("text");
1529        let insert = make_string(&mut context, b"bc").expect("insert");
1530        let range = context.alloc(range_value(1, 3));
1531
1532        let indexed = string_index_range(&mut context, text, range).expect("index range");
1533        let spliced = string_splice(&mut context, text, range, insert).expect("splice");
1534
1535        assert!(matches!(
1536            context.clone_value(indexed),
1537            Some(runtime::VmValue::String(value)) if value.to_flat_string() == "ć‚šŸ™‚"
1538        ));
1539        assert!(matches!(
1540            context.clone_value(spliced),
1541            Some(runtime::VmValue::String(value)) if value.to_flat_string() == "abcz"
1542        ));
1543    }
1544
1545    #[test]
1546    fn api_runs_basic_primitives() {
1547        let mut context = NativeRuntimeContext::new();
1548        let one = make_int(&mut context, b"1").expect("one");
1549        let two = make_int(&mut context, b"2").expect("two");
1550        let sum = primitive_binary(&mut context, NATIVE_PRIMITIVE_INT_ADD, one, two).expect("sum");
1551        let lt = primitive_binary(&mut context, NATIVE_PRIMITIVE_INT_LT, one, two).expect("lt");
1552        let text =
1553            primitive_unary(&mut context, NATIVE_PRIMITIVE_INT_TO_STRING, sum).expect("text");
1554
1555        assert!(matches!(
1556            context.clone_value(sum),
1557            Some(runtime::VmValue::Int(value)) if value == "3"
1558        ));
1559        assert!(matches!(
1560            context.clone_value(lt),
1561            Some(runtime::VmValue::Bool(true))
1562        ));
1563        assert!(matches!(
1564            context.clone_value(text),
1565            Some(runtime::VmValue::String(value)) if value.to_flat_string() == "3"
1566        ));
1567    }
1568
1569    fn range_value(start: i64, end: i64) -> runtime::VmValue {
1570        runtime::VmValue::Variant {
1571            tag: typed_ir_name("within"),
1572            value: Some(Box::new(runtime::VmValue::Tuple(vec![
1573                runtime::VmValue::Variant {
1574                    tag: typed_ir_name("included"),
1575                    value: Some(Box::new(runtime::VmValue::Int(start.to_string()))),
1576                },
1577                runtime::VmValue::Variant {
1578                    tag: typed_ir_name("excluded"),
1579                    value: Some(Box::new(runtime::VmValue::Int(end.to_string()))),
1580                },
1581            ]))),
1582        }
1583    }
1584
1585    fn int_list<const N: usize>(
1586        context: &mut NativeRuntimeContext,
1587        values: [&'static str; N],
1588    ) -> *mut runtime::VmValue {
1589        let mut result = list_empty(context);
1590        for value in values {
1591            let value = make_int(context, value.as_bytes()).expect("int");
1592            let item = list_singleton(context, value).expect("singleton");
1593            result = list_merge(context, result, item).expect("merge");
1594        }
1595        result
1596    }
1597}