aver-lang 0.15.0

VM and transpiler for Aver, a statically-typed language designed for AI-assisted development
Documentation
;; Numeric → string formatters that write into a caller-provided byte
;; buffer (typically the runtime's IO_INT_BUF / IO_FLOAT_BUF scratch
;; area). Both return the digits packed as `(pos << 16) | len`, where
;; `pos` is the offset within the buffer where the first digit lives
;; (digits are written right-to-left toward index 21). Caller copies
;; the [pos, pos+len) slice somewhere durable (e.g. into an
;; OBJ_STRING heap object).

(func $rt_int_to_str (param $val i64) (param $buf i32) (result i32)
  (local $n i64)
  (local $is_neg i32)
  (local $pos i32)

  local.get $val
  local.set $n

  ;; Special case: 0 → write '0' at buf[0], return (0<<16)|1
  local.get $n
  i64.eqz
  if (result i32)
    local.get $buf
    i32.const 0x30   ;; '0'
    i32.store8
    i32.const 1
  else
    ;; is_neg = n < 0; if so, negate.
    local.get $n
    i64.const 0
    i64.lt_s
    local.set $is_neg

    local.get $is_neg
    if
      i64.const 0
      local.get $n
      i64.sub
      local.set $n
    end

    ;; Write digits right-to-left starting at index 21.
    i32.const 21
    local.set $pos

    block
      loop
        local.get $n
        i64.eqz
        br_if 1

        local.get $pos
        i32.const 1
        i32.sub
        local.set $pos

        local.get $buf
        local.get $pos
        i32.add
        local.get $n
        i64.const 10
        i64.rem_u
        i32.wrap_i64
        i32.const 0x30
        i32.add
        i32.store8

        local.get $n
        i64.const 10
        i64.div_u
        local.set $n
        br 0
      end
    end

    ;; Negative sign one byte earlier.
    local.get $is_neg
    if
      local.get $pos
      i32.const 1
      i32.sub
      local.set $pos

      local.get $buf
      local.get $pos
      i32.add
      i32.const 0x2D   ;; '-'
      i32.store8
    end

    ;; (pos << 16) | (21 - pos)
    local.get $pos
    i32.const 16
    i32.shl
    i32.const 21
    local.get $pos
    i32.sub
    i32.or
  end
)
(export "rt_int_to_str" (func $rt_int_to_str))

;; Shortest-roundtrip f64 → string. Whole numbers print without a
;; decimal point. For fractional values we search N = 1..15 for the
;; smallest count of fractional digits where
;; trunc(|val| * 10^N) / 10^N == |val|, then write N digits and strip
;; trailing zeros.
(func $rt_float_to_str (param $val f64) (param $buf i32) (result i32)
  (local $is_neg i32)
  (local $abs_val f64)
  (local $int_part i64)
  (local $pos i32)
  (local $start_pos i32)
  (local $pow f64)
  (local $n i32)
  (local $scaled i64)
  (local $frac_int i64)
  (local $frac_pos i32)
  (local $frac_digits i32)

  local.get $val
  f64.const 0
  f64.lt
  local.set $is_neg

  local.get $val
  f64.abs
  local.set $abs_val

  local.get $abs_val
  f64.floor
  i64.trunc_f64_s
  local.set $int_part

  i32.const 21
  local.set $pos

  ;; Integer part right-to-left at buf[0..21].
  local.get $int_part
  i64.eqz
  if
    local.get $pos
    i32.const 1
    i32.sub
    local.set $pos

    local.get $buf
    local.get $pos
    i32.add
    i32.const 0x30
    i32.store8
  else
    block
      loop
        local.get $int_part
        i64.eqz
        br_if 1

        local.get $pos
        i32.const 1
        i32.sub
        local.set $pos

        local.get $buf
        local.get $pos
        i32.add
        local.get $int_part
        i64.const 10
        i64.rem_u
        i32.wrap_i64
        i32.const 0x30
        i32.add
        i32.store8

        local.get $int_part
        i64.const 10
        i64.div_u
        local.set $int_part
        br 0
      end
    end
  end

  ;; Negative sign.
  local.get $is_neg
  if
    local.get $pos
    i32.const 1
    i32.sub
    local.set $pos

    local.get $buf
    local.get $pos
    i32.add
    i32.const 0x2D
    i32.store8
  end

  local.get $pos
  local.set $start_pos

  ;; Whole number? abs_val == floor(abs_val) → no fractional digits.
  local.get $abs_val
  local.get $abs_val
  f64.floor
  f64.eq
  if (result i32)
    local.get $start_pos
    i32.const 16
    i32.shl
    i32.const 21
    local.get $start_pos
    i32.sub
    i32.or
  else
    ;; Find shortest N (1..15).
    f64.const 1
    local.set $pow
    i32.const 0
    local.set $n

    block
      loop
        ;; n++
        local.get $n
        i32.const 1
        i32.add
        local.set $n

        ;; pow *= 10
        local.get $pow
        f64.const 10
        f64.mul
        local.set $pow

        ;; scaled = trunc(abs_val * pow)
        local.get $abs_val
        local.get $pow
        f64.mul
        f64.floor
        i64.trunc_f64_s
        local.set $scaled

        ;; if (scaled / pow) == abs_val → done
        local.get $scaled
        f64.convert_i64_s
        local.get $pow
        f64.div
        local.get $abs_val
        f64.eq
        br_if 1

        ;; Cap at 15 digits.
        local.get $n
        i32.const 15
        i32.ge_s
        br_if 1

        br 0
      end
    end

    ;; frac_int = ((scaled % pow_i64) + pow_i64) % pow_i64
    local.get $scaled
    local.get $pow
    i64.trunc_f64_s
    i64.rem_s
    local.get $pow
    i64.trunc_f64_s
    i64.add
    local.get $pow
    i64.trunc_f64_s
    i64.rem_s
    local.set $frac_int

    ;; '.' at offset 21
    local.get $buf
    i32.const 0x2E
    i32.store8 offset=21

    ;; Write n frac digits right-to-left into buf[22..22+n].
    i32.const 22
    local.get $n
    i32.add
    i32.const 1
    i32.sub
    local.set $frac_pos

    local.get $n
    local.set $frac_digits

    block
      loop
        local.get $frac_digits
        i32.eqz
        br_if 1

        local.get $buf
        local.get $frac_pos
        i32.add
        local.get $frac_int
        i64.const 10
        i64.rem_u
        i32.wrap_i64
        i32.const 0x30
        i32.add
        i32.store8

        local.get $frac_int
        i64.const 10
        i64.div_u
        local.set $frac_int

        local.get $frac_pos
        i32.const 1
        i32.sub
        local.set $frac_pos

        local.get $frac_digits
        i32.const 1
        i32.sub
        local.set $frac_digits
        br 0
      end
    end

    ;; Strip trailing zeros: frac_end = 22 + n; while frac_end > 22 and
    ;; buf[frac_end-1] == '0', frac_end--.
    i32.const 22
    local.get $n
    i32.add
    local.set $frac_pos   ;; reused as frac_end

    block
      loop
        local.get $frac_pos
        i32.const 22
        i32.le_s
        br_if 1

        local.get $buf
        local.get $frac_pos
        i32.add
        i32.const 1
        i32.sub
        i32.load8_u
        i32.const 0x30
        i32.ne
        br_if 1

        local.get $frac_pos
        i32.const 1
        i32.sub
        local.set $frac_pos
        br 0
      end
    end

    ;; (start_pos << 16) | (frac_end - start_pos)
    local.get $start_pos
    i32.const 16
    i32.shl
    local.get $frac_pos
    local.get $start_pos
    i32.sub
    i32.or
  end
)
(export "rt_float_to_str" (func $rt_float_to_str))