plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
35ca120
— Peter John
2026-11-01T22:18:05+05:30
refactor: Str + and == are ordinary Plum methods; concat/eq wasm helpers removed
- plum-checker/src/lib.rs +2 -1
- plum-std/Str.plum +28 -0
- plum-wasm-codegen/src/lib.rs +7 -239
- plum-wasm-codegen/tests/codegen_tests.rs +2 -1
plum-checker/src/lib.rs
CHANGED
|
@@ -3498,7 +3498,8 @@ fn startVariant(
|
|
|
3498
3498
|
/// True for an operand whose operators are user methods (`add`, `eq`, ...): a
|
|
3499
3499
|
/// declared class/enum, not a primitive, `Str`, `Bool` or an error name.
|
|
3500
3500
|
pub fn isOperatorOperand(t: &PlumType) -> bool {
|
|
3501
|
+
matches!(t, PlumType::TStr)
|
|
3501
|
-
|
|
3502
|
+
|| matches!(t, PlumType::TNamed(n) | PlumType::TApp(n, _) if n != "Int" && n != "Float" && n != "Bool" && n != "Byte")
|
|
3502
3503
|
}
|
|
3503
3504
|
|
|
3504
3505
|
/// Resolves operator `sym` on a non-primitive left operand to `left.method(right)`,
|
plum-std/Str.plum
CHANGED
|
@@ -137,6 +137,34 @@ record Str(bytes: []Byte) =
|
|
|
137
137
|
fun concat(self, other: Str) -> Str =
|
|
138
138
|
self + other
|
|
139
139
|
|
|
140
|
+
# `a + b`: one exact-length allocation and two bulk copies. Returns an
|
|
141
|
+
# operand unchanged when the other is empty (Strs are immutable).
|
|
142
|
+
fun add(self, o: Str) -> Str =
|
|
143
|
+
n := self.length()
|
|
144
|
+
m := o.length()
|
|
145
|
+
if m == 0
|
|
146
|
+
return self
|
|
147
|
+
if n == 0
|
|
148
|
+
return o
|
|
149
|
+
out := makeBytes(n + m)
|
|
150
|
+
copyBytes(out, 0, self.bytes, 0, n)
|
|
151
|
+
copyBytes(out, n, o.bytes, 0, m)
|
|
152
|
+
return Str(bytes: out)
|
|
153
|
+
|
|
154
|
+
# `a == b` / `a != b`: CONTENT equality (never reference equality).
|
|
155
|
+
fun eq(self, o: Str) -> Bool =
|
|
156
|
+
n := self.length()
|
|
157
|
+
if n != o.length()
|
|
158
|
+
return false
|
|
159
|
+
a := self.bytes
|
|
160
|
+
b := o.bytes
|
|
161
|
+
i := 0
|
|
162
|
+
while i < n
|
|
163
|
+
if a.get(i) != b.get(i)
|
|
164
|
+
return false
|
|
165
|
+
i = i + 1
|
|
166
|
+
return true
|
|
167
|
+
|
|
140
168
|
fun toStr(self) -> Str =
|
|
141
169
|
self
|
|
142
170
|
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -406,11 +406,9 @@ pub struct CompileCtx<'a> {
|
|
|
406
406
|
/// Shared runtime helper `(a: ref Str, b: ref Str) -> ref Str`: allocates a new
|
|
407
407
|
/// `array<i8>` exactly long enough to hold `a`'s bytes followed by `b`'s, for
|
|
408
408
|
/// lowering string interpolation (`"{expr}"`).
|
|
409
|
-
pub string_concat_func: u32,
|
|
410
409
|
/// Shared runtime helper `(a: ref Str, b: ref Str) -> i32`: byte-for-byte
|
|
411
410
|
/// content equality (1/0) — see the comment on its registration site for
|
|
412
411
|
/// why `==`/`!=` on `Str` needs this instead of plain `ref.eq`.
|
|
413
|
-
pub string_eq_func: u32,
|
|
414
412
|
/// Shared runtime helper `(n: i64) -> ref Str`: allocates a new `array<i8>`
|
|
415
413
|
/// holding `n`'s decimal representation, for interpolating an `Int`.
|
|
416
414
|
/// wasm-gc type-section indices for this program's enums/Str/closures —
|
|
@@ -459,8 +457,6 @@ struct LocalCtx<'a> {
|
|
|
459
457
|
closures: &'a HashMap<(usize, usize), ClosureInfo>,
|
|
460
458
|
closure_call_types: &'a HashMap<ClosureSigKey, u32>,
|
|
461
459
|
named_fn_values: &'a HashMap<(String, PlumType), u32>,
|
|
462
|
-
string_concat_func: u32,
|
|
463
|
-
string_eq_func: u32,
|
|
464
460
|
methods: &'a MethodEnv,
|
|
465
461
|
enum_variants: &'a EnumVariants,
|
|
466
462
|
enum_params: &'a EnumParams,
|
|
@@ -2489,8 +2485,6 @@ fn compileSourceInner(
|
|
|
2489
2485
|
let start_slot = (start_func_idx - module.func_import_count) as usize;
|
|
2490
2486
|
module.functions[start_slot].1 = final_start_body;
|
|
2491
2487
|
|
|
2492
|
-
let string_concat_func = registerStringConcatHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2493
|
-
let string_eq_func = registerStringEqHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2494
2488
|
|
|
2495
2489
|
let ctx = CompileCtx {
|
|
2496
2490
|
func_ids,
|
|
@@ -2510,8 +2504,6 @@ fn compileSourceInner(
|
|
|
2510
2504
|
closure_asts,
|
|
2511
2505
|
closure_call_types,
|
|
2512
2506
|
named_fn_values,
|
|
2513
|
-
string_concat_func,
|
|
2514
|
-
string_eq_func,
|
|
2515
2507
|
gc_types,
|
|
2516
2508
|
singleton_globals,
|
|
2517
2509
|
};
|
|
@@ -2637,187 +2629,6 @@ fn getClosureCallType(module: &mut WasmModule, m: &mut HashMap<ClosureSigKey, u3
|
|
|
2637
2629
|
t
|
|
2638
2630
|
}
|
|
2639
2631
|
|
|
2640
|
-
/// Registers `__string_concat(a: ref Str, b: ref Str) -> ref Str`, a hand-written
|
|
2641
|
-
/// runtime helper backing string interpolation. Unwraps both `Str` params down
|
|
2642
|
-
/// to their raw `array<i8>`s (`unwrapStrToBytes`) — which each track their own
|
|
2643
|
-
/// length via `array.len` — allocates a new array sized `len(a) + len(b)`,
|
|
2644
|
-
/// does two `array.copy`s (a whole-array-in-one-instruction bulk copy), then
|
|
2645
|
-
/// wraps the result back into a real `Str` (`wrapBytesAsStr`).
|
|
2646
|
-
fn registerStringConcatHelper(module: &mut WasmModule, byte_array_type_idx: u32) -> u32 {
|
|
2647
|
-
let byte_array_ref = gcRef(byte_array_type_idx);
|
|
2648
|
-
let str_ref = withGcTypes(strGcValtypeOrFallback);
|
|
2649
|
-
let type_idx = module.addType(&[str_ref, str_ref], &[str_ref]);
|
|
2650
|
-
|
|
2651
|
-
// locals: 0=a (param), 1=b (param), 2=arr_a, 3=arr_b, 4=len_a, 5=len_b,
|
|
2652
|
-
// 6=total_len, 7=result
|
|
2653
|
-
const A: u32 = 0;
|
|
2654
|
-
const B: u32 = 1;
|
|
2655
|
-
const ARR_A: u32 = 2;
|
|
2656
|
-
const ARR_B: u32 = 3;
|
|
2657
|
-
const LEN_A: u32 = 4;
|
|
2658
|
-
const LEN_B: u32 = 5;
|
|
2659
|
-
const TOTAL_LEN: u32 = 6;
|
|
2660
|
-
const RESULT: u32 = 7;
|
|
2661
|
-
|
|
2662
|
-
let mut body = Vec::new();
|
|
2663
|
-
body.extend(encodeLeb128U32(3)); // three locals groups
|
|
2664
|
-
body.extend(encodeLeb128U32(2)); // arr_a, arr_b: ref
|
|
2665
|
-
byte_array_ref.encode(&mut body);
|
|
2666
|
-
body.extend(encodeLeb128U32(3)); // len_a, len_b, total_len: i32
|
|
2667
|
-
ValType::I32.encode(&mut body);
|
|
2668
|
-
body.extend(encodeLeb128U32(1)); // result: ref
|
|
2669
|
-
byte_array_ref.encode(&mut body);
|
|
2670
|
-
|
|
2671
|
-
// arr_a = a.bytes; arr_b = b.bytes
|
|
2672
|
-
Instruction::LocalGet(A).encode(&mut body);
|
|
2673
|
-
unwrapStrToBytes(&mut body);
|
|
2674
|
-
Instruction::LocalSet(ARR_A).encode(&mut body);
|
|
2675
|
-
Instruction::LocalGet(B).encode(&mut body);
|
|
2676
|
-
unwrapStrToBytes(&mut body);
|
|
2677
|
-
Instruction::LocalSet(ARR_B).encode(&mut body);
|
|
2678
|
-
|
|
2679
|
-
// len_a = array.len(arr_a); len_b = array.len(arr_b)
|
|
2680
|
-
Instruction::LocalGet(ARR_A).encode(&mut body);
|
|
2681
|
-
Instruction::ArrayLen.encode(&mut body);
|
|
2682
|
-
Instruction::LocalSet(LEN_A).encode(&mut body);
|
|
2683
|
-
Instruction::LocalGet(ARR_B).encode(&mut body);
|
|
2684
|
-
Instruction::ArrayLen.encode(&mut body);
|
|
2685
|
-
Instruction::LocalSet(LEN_B).encode(&mut body);
|
|
2686
|
-
|
|
2687
|
-
// total_len = len_a + len_b; result = array.new_default(byte_array_type_idx, total_len)
|
|
2688
|
-
Instruction::LocalGet(LEN_A).encode(&mut body);
|
|
2689
|
-
Instruction::LocalGet(LEN_B).encode(&mut body);
|
|
2690
|
-
Instruction::I32Add.encode(&mut body);
|
|
2691
|
-
Instruction::LocalSet(TOTAL_LEN).encode(&mut body);
|
|
2692
|
-
Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
|
|
2693
|
-
Instruction::ArrayNewDefault(byte_array_type_idx).encode(&mut body);
|
|
2694
|
-
Instruction::LocalSet(RESULT).encode(&mut body);
|
|
2695
|
-
|
|
2696
|
-
// array.copy(dst: result, dst_offset: 0, src: arr_a, src_offset: 0, len: len_a)
|
|
2697
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2698
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2699
|
-
Instruction::LocalGet(ARR_A).encode(&mut body);
|
|
2700
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2701
|
-
Instruction::LocalGet(LEN_A).encode(&mut body);
|
|
2702
|
-
Instruction::ArrayCopy {
|
|
2703
|
-
array_type_index_dst: byte_array_type_idx,
|
|
2704
|
-
array_type_index_src: byte_array_type_idx,
|
|
2705
|
-
}
|
|
2706
|
-
.encode(&mut body);
|
|
2707
|
-
|
|
2708
|
-
// array.copy(dst: result, dst_offset: len_a, src: arr_b, src_offset: 0, len: len_b)
|
|
2709
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2710
|
-
Instruction::LocalGet(LEN_A).encode(&mut body);
|
|
2711
|
-
Instruction::LocalGet(ARR_B).encode(&mut body);
|
|
2712
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2713
|
-
Instruction::LocalGet(LEN_B).encode(&mut body);
|
|
2714
|
-
Instruction::ArrayCopy {
|
|
2715
|
-
array_type_index_dst: byte_array_type_idx,
|
|
2716
|
-
array_type_index_src: byte_array_type_idx,
|
|
2717
|
-
}
|
|
2718
|
-
.encode(&mut body);
|
|
2719
|
-
|
|
2720
|
-
// wrap result (the raw array) back into a real Str
|
|
2721
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2722
|
-
wrapBytesAsStr(&mut body);
|
|
2723
|
-
Instruction::End.encode(&mut body);
|
|
2724
|
-
|
|
2725
|
-
module.addFunction(type_idx, &body)
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
/// Registers `__string_eq(a: ref Str, b: ref Str) -> i32` (1/0), a hand-written
|
|
2729
|
-
/// runtime helper backing `Str`'s `==`/`!=` operator. `Str` is a wasm-gc `ref` —
|
|
2730
|
-
/// the general `Compare` codegen's default for reference-typed operands is
|
|
2731
|
-
/// `ref.eq` (identity), correct for a payload-free singleton (`End`) or
|
|
2732
|
-
/// class-instance identity, but WRONG for `Str`: two independently-heap-allocated
|
|
2733
|
-
/// `array<i8>` values holding identical bytes (e.g. two `"a"` literals, or one
|
|
2734
|
-
/// literal and a runtime-built string) are different objects and would compare
|
|
2735
|
-
/// unequal — silently breaking the extremely common `someStr == "literal"`
|
|
2736
|
-
/// pattern (e.g. `Map`'s own `Str`-keyed `get`/`set` comparing keys). This does
|
|
2737
|
-
/// a real byte-for-byte comparison instead: same length, then every byte equal.
|
|
2738
|
-
fn registerStringEqHelper(module: &mut WasmModule, byte_array_type_idx: u32) -> u32 {
|
|
2739
|
-
let byte_array_ref = gcRef(byte_array_type_idx);
|
|
2740
|
-
let str_ref = withGcTypes(strGcValtypeOrFallback);
|
|
2741
|
-
let type_idx = module.addType(&[str_ref, str_ref], &[ValType::I32]);
|
|
2742
|
-
|
|
2743
|
-
// locals: 0=a (param), 1=b (param), 2=arr_a, 3=arr_b, 4=len_a, 5=len_b, 6=i
|
|
2744
|
-
const A: u32 = 0;
|
|
2745
|
-
const B: u32 = 1;
|
|
2746
|
-
const ARR_A: u32 = 2;
|
|
2747
|
-
const ARR_B: u32 = 3;
|
|
2748
|
-
const LEN_A: u32 = 4;
|
|
2749
|
-
const LEN_B: u32 = 5;
|
|
2750
|
-
const I: u32 = 6;
|
|
2751
|
-
|
|
2752
|
-
let mut body = Vec::new();
|
|
2753
|
-
body.extend(encodeLeb128U32(2)); // two locals groups
|
|
2754
|
-
body.extend(encodeLeb128U32(2)); // arr_a, arr_b: ref
|
|
2755
|
-
byte_array_ref.encode(&mut body);
|
|
2756
|
-
body.extend(encodeLeb128U32(3)); // len_a, len_b, i: i32
|
|
2757
|
-
ValType::I32.encode(&mut body);
|
|
2758
|
-
|
|
2759
|
-
// arr_a = a.bytes; arr_b = b.bytes
|
|
2760
|
-
Instruction::LocalGet(A).encode(&mut body);
|
|
2761
|
-
unwrapStrToBytes(&mut body);
|
|
2762
|
-
Instruction::LocalSet(ARR_A).encode(&mut body);
|
|
2763
|
-
Instruction::LocalGet(B).encode(&mut body);
|
|
2764
|
-
unwrapStrToBytes(&mut body);
|
|
2765
|
-
Instruction::LocalSet(ARR_B).encode(&mut body);
|
|
2766
|
-
|
|
2767
|
-
Instruction::LocalGet(ARR_A).encode(&mut body);
|
|
2768
|
-
Instruction::ArrayLen.encode(&mut body);
|
|
2769
|
-
Instruction::LocalSet(LEN_A).encode(&mut body);
|
|
2770
|
-
Instruction::LocalGet(ARR_B).encode(&mut body);
|
|
2771
|
-
Instruction::ArrayLen.encode(&mut body);
|
|
2772
|
-
Instruction::LocalSet(LEN_B).encode(&mut body);
|
|
2773
|
-
|
|
2774
|
-
// if len_a != len_b: return 0
|
|
2775
|
-
Instruction::LocalGet(LEN_A).encode(&mut body);
|
|
2776
|
-
Instruction::LocalGet(LEN_B).encode(&mut body);
|
|
2777
|
-
Instruction::I32Ne.encode(&mut body);
|
|
2778
|
-
Instruction::If(BlockType::Empty).encode(&mut body);
|
|
2779
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2780
|
-
Instruction::Return.encode(&mut body);
|
|
2781
|
-
Instruction::End.encode(&mut body);
|
|
2782
|
-
|
|
2783
|
-
// i = 0
|
|
2784
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2785
|
-
Instruction::LocalSet(I).encode(&mut body);
|
|
2786
|
-
|
|
2787
|
-
// block { loop { if i >= len_a: br 1 (exit loop); if a[i] != b[i]: return 0; i++; br 0 } }
|
|
2788
|
-
Instruction::Block(BlockType::Empty).encode(&mut body);
|
|
2789
|
-
Instruction::Loop(BlockType::Empty).encode(&mut body);
|
|
2790
|
-
Instruction::LocalGet(I).encode(&mut body);
|
|
2791
|
-
Instruction::LocalGet(LEN_A).encode(&mut body);
|
|
2792
|
-
Instruction::I32GeU.encode(&mut body);
|
|
2793
|
-
Instruction::BrIf(1).encode(&mut body);
|
|
2794
|
-
|
|
2795
|
-
Instruction::LocalGet(ARR_A).encode(&mut body);
|
|
2796
|
-
Instruction::LocalGet(I).encode(&mut body);
|
|
2797
|
-
Instruction::ArrayGetU(byte_array_type_idx).encode(&mut body);
|
|
2798
|
-
Instruction::LocalGet(ARR_B).encode(&mut body);
|
|
2799
|
-
Instruction::LocalGet(I).encode(&mut body);
|
|
2800
|
-
Instruction::ArrayGetU(byte_array_type_idx).encode(&mut body);
|
|
2801
|
-
Instruction::I32Ne.encode(&mut body);
|
|
2802
|
-
Instruction::If(BlockType::Empty).encode(&mut body);
|
|
2803
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2804
|
-
Instruction::Return.encode(&mut body);
|
|
2805
|
-
Instruction::End.encode(&mut body);
|
|
2806
|
-
|
|
2807
|
-
Instruction::LocalGet(I).encode(&mut body);
|
|
2808
|
-
Instruction::I32Const(1).encode(&mut body);
|
|
2809
|
-
Instruction::I32Add.encode(&mut body);
|
|
2810
|
-
Instruction::LocalSet(I).encode(&mut body);
|
|
2811
|
-
Instruction::Br(0).encode(&mut body);
|
|
2812
|
-
Instruction::End.encode(&mut body); // end loop
|
|
2813
|
-
Instruction::End.encode(&mut body); // end block
|
|
2814
|
-
|
|
2815
|
-
Instruction::I32Const(1).encode(&mut body);
|
|
2816
|
-
Instruction::End.encode(&mut body); // end function
|
|
2817
|
-
|
|
2818
|
-
module.addFunction(type_idx, &body)
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
2632
|
/// Hand-written bodies for a handful of `libs/std/str.plum` primitives that can't
|
|
2822
2633
|
/// be expressed in Plum source at all (byte-level array access, building a new
|
|
2823
2634
|
/// one-element array) — these three are the whole reason every other `Str` method
|
|
@@ -2904,11 +2715,8 @@ fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
|
|
|
2904
2715
|
/// never loaded `str.plum` at all (see `plum-core::loader::loadAndMerge`'s
|
|
2905
2716
|
/// best-effort `std/str` prelude), falling back to the raw `array<i8>` type
|
|
2906
2717
|
/// instead of panicking. Safe specifically because this is only used by the
|
|
2907
|
-
/// three string runtime helpers (`registerStringConcatHelper`/
|
|
2908
|
-
///
|
|
2718
|
+
/// unconditionally registered string runtime helpers, which every compiled
|
|
2909
|
-
/// registered UNCONDITIONALLY in every compiled program regardless of
|
|
2910
|
-
/// whether it actually uses `Str` — a `Str`-less program (which, by
|
|
2911
|
-
///
|
|
2719
|
+
/// program gets regardless of whether it uses `Str`.
|
|
2912
2720
|
fn strGcValtypeOrFallback(r: &GcTypeRegistry) -> ValType {
|
|
2913
2721
|
match r.class_type_idx.get("Str") {
|
|
2914
2722
|
Some(idx) => gcRef(*idx),
|
|
@@ -2929,20 +2737,6 @@ fn wrapBytesAsStr(body: &mut Vec<u8>) {
|
|
|
2929
2737
|
Instruction::Call(mk).encode(body);
|
|
2930
2738
|
}
|
|
2931
2739
|
|
|
2932
|
-
/// The inverse of `wrapBytesAsStr`: given a `Str` value on the stack, leaves
|
|
2933
|
-
/// its raw `array<i8>` (`self.bytes`) there instead: one `struct.get Str 0`.
|
|
2934
|
-
/// A no-op when `Str` isn't registered (the value already IS the array).
|
|
2935
|
-
fn unwrapStrToBytes(body: &mut Vec<u8>) {
|
|
2936
|
-
let Some(str_type_idx) = withGcTypes(|r| r.class_type_idx.get("Str").copied()) else {
|
|
2937
|
-
return;
|
|
2938
|
-
};
|
|
2939
|
-
Instruction::StructGet {
|
|
2940
|
-
struct_type_index: str_type_idx,
|
|
2941
|
-
field_index: 0,
|
|
2942
|
-
}
|
|
2943
|
-
.encode(body);
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
2740
|
/// `Array[T]`'s `init`/`get`/`set`/`length` — the intrinsic is backed by ONE wasm-gc `array<anyref>` type
|
|
2947
2741
|
/// (`GcTypeRegistry::array_type_idx`, see `isArraySpecialization`), so these
|
|
2948
2742
|
/// compile identically regardless of the concrete `T`.
|
|
@@ -4111,8 +3905,6 @@ fn compileFnBodyImpl(
|
|
|
4111
3905
|
closures: &ctx.closures,
|
|
4112
3906
|
closure_call_types: &ctx.closure_call_types,
|
|
4113
3907
|
named_fn_values: &ctx.named_fn_values,
|
|
4114
|
-
string_concat_func: ctx.string_concat_func,
|
|
4115
|
-
string_eq_func: ctx.string_eq_func,
|
|
4116
3908
|
methods: &ctx.methods,
|
|
4117
3909
|
enum_variants: &ctx.enum_variants,
|
|
4118
3910
|
enum_params: &ctx.enum_params,
|
|
@@ -6360,15 +6152,9 @@ fn compileExprCore(
|
|
|
6360
6152
|
ast::Expr::Binary(b) => {
|
|
6361
6153
|
let left_ty = inferLocalType(&b.left, ctx);
|
|
6362
6154
|
let is_float = matches!(left_ty, PlumType::TFloat);
|
|
6363
|
-
let is_str = matches!(left_ty, PlumType::TStr);
|
|
6364
6155
|
compileExpr(&b.left, body, ctx, state)?;
|
|
6365
6156
|
compileExpr(&b.right, body, ctx, state)?;
|
|
6366
6157
|
match b.op {
|
|
6367
|
-
// `Str + Str` (e.g. `libs/std/str.plum`'s `concat`) allocates a new
|
|
6368
|
-
// array holding both operands' bytes via the same runtime helper
|
|
6369
|
-
// string interpolation uses — there's no native wasm "add" for a
|
|
6370
|
-
// GC ref.
|
|
6371
|
-
ast::BinOp::Add if is_str => Instruction::Call(ctx.string_concat_func).encode(body),
|
|
6372
6158
|
ast::BinOp::Add => if is_float {
|
|
6373
6159
|
Instruction::F64Add
|
|
6374
6160
|
} else {
|
|
@@ -6482,25 +6268,6 @@ fn compileExprCore(
|
|
|
6482
6268
|
}
|
|
6483
6269
|
.encode(body);
|
|
6484
6270
|
}
|
|
6485
|
-
// `Str`'s `==`/`!=` needs real byte-for-byte comparison, not
|
|
6486
|
-
// reference identity — see `registerStringEqHelper`'s doc
|
|
6487
|
-
// comment for why plain `ref.eq` silently breaks the (very
|
|
6488
|
-
// common) `someStr == "literal"` pattern.
|
|
6489
|
-
PlumType::TStr => match &c.op {
|
|
6490
|
-
ast::CmpOp::Eq => {
|
|
6491
|
-
Instruction::Call(ctx.string_eq_func).encode(body);
|
|
6492
|
-
}
|
|
6493
|
-
ast::CmpOp::Neq | ast::CmpOp::NotEq2 => {
|
|
6494
|
-
Instruction::Call(ctx.string_eq_func).encode(body);
|
|
6495
|
-
Instruction::I32Eqz.encode(body);
|
|
6496
|
-
}
|
|
6497
|
-
other => {
|
|
6498
|
-
return Err(format!(
|
|
6499
|
-
"codegen: '{:?}' is not supported between Str values",
|
|
6500
|
-
other
|
|
6501
|
-
))
|
|
6502
|
-
}
|
|
6503
|
-
},
|
|
6504
6271
|
PlumType::TBool => match &c.op {
|
|
6505
6272
|
ast::CmpOp::Eq => Instruction::I32Eq.encode(body),
|
|
6506
6273
|
ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::I32Ne.encode(body),
|
|
@@ -7306,7 +7073,7 @@ fn compileStaticString(text: &str, body: &mut Vec<u8>, state: &mut ModuleState)
|
|
|
7306
7073
|
/// `compileStaticString`; `Str`/`Int`/`Bool`/`Float` interpolated values
|
|
7307
7074
|
/// converted at runtime — `Float` via `Float.toStr`, see the `TFloat` arm
|
|
7308
7075
|
/// below), then all parts are left-folded together with the
|
|
7309
|
-
/// `
|
|
7076
|
+
/// `Str.add` method (chained, left to right).
|
|
7310
7077
|
fn compileInterpolatedString(
|
|
7311
7078
|
s: &ast::StringExpr,
|
|
7312
7079
|
body: &mut Vec<u8>,
|
|
@@ -7375,7 +7142,10 @@ fn compileInterpolatedString(
|
|
|
7375
7142
|
}
|
|
7376
7143
|
}
|
|
7377
7144
|
if !first {
|
|
7145
|
+
let add = *ctx.func_ids.get("Str::add").ok_or_else(|| {
|
|
7146
|
+
"codegen: string interpolation requires `Str.add` (import std/Str)".to_string()
|
|
7147
|
+
})?;
|
|
7378
|
-
Instruction::Call(
|
|
7148
|
+
Instruction::Call(add).encode(body);
|
|
7379
7149
|
}
|
|
7380
7150
|
first = false;
|
|
7381
7151
|
}
|
|
@@ -7619,8 +7389,6 @@ fn compileClosureBody(
|
|
|
7619
7389
|
closures: &ctx.closures,
|
|
7620
7390
|
closure_call_types: &ctx.closure_call_types,
|
|
7621
7391
|
named_fn_values: &ctx.named_fn_values,
|
|
7622
|
-
string_concat_func: ctx.string_concat_func,
|
|
7623
|
-
string_eq_func: ctx.string_eq_func,
|
|
7624
7392
|
methods: &ctx.methods,
|
|
7625
7393
|
enum_variants: &ctx.enum_variants,
|
|
7626
7394
|
enum_params: &ctx.enum_params,
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -3029,7 +3029,8 @@ fun main() -> Int =
|
|
|
3029
3029
|
a + b + c + {if d == \"none\" then 4 else 0} + f
|
|
3030
3030
|
";
|
|
3031
3031
|
// a = 2, b = 10, c = 7, d = 4, e = 3
|
|
3032
|
+
// Str `==` is the ordinary `Str.eq` method, so Str.plum must really load.
|
|
3032
|
-
assert_eq!(
|
|
3033
|
+
assert_eq!(runWithCrash("error_slots", &format!("{}{}", STD_HEAD, src)).0, Ok(26));
|
|
3033
3034
|
}
|
|
3034
3035
|
|
|
3035
3036
|
// ---- associated consts, statics and `primitive` ----
|