| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago | 1 | ## rustnim prelude — emitted at the top of every generated module. |
| 2 | ## |
| 3 | ## Everything here exists to make Nim's observable behaviour match Rust's |
| 4 | ## exactly. Where the two languages already agree (signed `shr` is arithmetic |
| 5 | ## in both; fixed-width unsigned arithmetic wraps in both; integer `div`/`mod` |
| 6 | ## truncate toward zero in both) there is deliberately nothing here: the |
| 7 | ## operator is mapped directly and no helper is involved. |
| 8 | |
| Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 16h ago | 9 | # Rust's `&[T]` is a borrowed view, not a copy. Nim's view types model exactly |
| 10 | # that, including returning one from a proc: writing through the returned view |
| 11 | # is visible in the original buffer. Probed against Nim 2.2.4 before relying on |
| 12 | # it, because copying instead would silently change aliasing. |
| 13 | {.experimental: "views".} |
| 14 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago | 15 | import std/[unicode, strutils] |
| 16 | |
| 17 | type |
| 18 | RustPanic* = object of CatchableError |
| 19 | |
| Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 16h ago | 20 | ## `core::fmt`'s own error and sink, kept distinct from any user type that |
| 21 | ## happens to be called `Error`. A formatting impl is lowered to a proc that |
| 22 | ## returns the formatted string, so these appear only in signatures. |
| 23 | FmtError* = object |
| 24 | FmtResult* = object |
| 25 | ok*: bool |
| 26 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago | 27 | Option*[T] = object |
| 28 | case has*: bool |
| 29 | of true: val*: T |
| 30 | of false: discard |
| 31 | |
| 32 | Result*[T, E] = object |
| 33 | case ok*: bool |
| 34 | of true: val*: T |
| 35 | of false: err*: E |
| 36 | |
| 37 | proc rsPanic*(msg: string) {.noreturn.} = |
| 38 | raise newException(RustPanic, msg) |
| 39 | |
| 40 | proc rsSome*[T](v: T): Option[T] = Option[T](has: true, val: v) |
| 41 | proc rsNone*[T](): Option[T] = Option[T](has: false) |
| 42 | proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v) |
| 43 | proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e) |
| 44 | |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 16h ago | 45 | proc rsOkOr*[T, E](o: Option[T], e: E): Result[T, E] = |
| 46 | if o.has: Result[T, E](ok: true, val: o.val) else: Result[T, E](ok: false, err: e) |
| 47 | |
| 48 | proc unwrapOr*[T](o: Option[T], d: T): T = |
| 49 | if o.has: o.val else: d |
| 50 | proc unwrapOr*[T, E](r: Result[T, E], d: T): T = |
| 51 | if r.ok: r.val else: d |
| 52 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago | 53 | proc unwrap*[T](o: Option[T]): T = |
| 54 | if not o.has: rsPanic("called `Option::unwrap()` on a `None` value") |
| 55 | o.val |
| 56 | proc unwrap*[T, E](r: Result[T, E]): T = |
| 57 | if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value") |
| 58 | r.val |
| 59 | |
| 60 | # --------------------------------------------------------------------------- |
| 61 | # Display / Debug. |
| 62 | # |
| 63 | # Rust's `{}` and `{:?}` are two distinct formats and they differ for floats, |
| 64 | # strings, chars and sequences. Nim's `$` matches neither consistently, so both |
| 65 | # are implemented here rather than approximated with `$`. |
| 66 | # --------------------------------------------------------------------------- |
| 67 | |
| 68 | proc rsDisplay*(x: SomeInteger): string = $x |
| 69 | proc rsDebug*(x: SomeInteger): string = $x |
| 70 | proc rsDisplay*(x: bool): string = $x |
| 71 | proc rsDebug*(x: bool): string = $x |
| 72 | |
| 73 | proc rsFloatStr(x: float64, debug: bool): string = |
| 74 | ## Nim and Rust both print the shortest round-tripping decimal, but they |
| 75 | ## spell the result differently in three places. |
| 76 | if x != x: return "NaN" |
| 77 | if x == Inf: return "inf" |
| 78 | if x == -Inf: return "-inf" |
| 79 | result = $x |
| 80 | result = result.replace("e+", "e") # Nim `1e+21`, Rust `1e21` |
| 81 | if not debug and result.endsWith(".0"): # Rust Display drops a bare `.0` |
| 82 | result.setLen(result.len - 2) |
| 83 | |
| 84 | proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false) |
| 85 | proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true) |
| 86 | |
| 87 | proc rsDisplay*(x: string): string = x |
| 88 | proc rsDebug*(x: string): string = |
| 89 | result = "\"" |
| 90 | for c in x: |
| 91 | case c |
| 92 | of '"': result.add("\\\"") |
| 93 | of '\\': result.add("\\\\") |
| 94 | of '\n': result.add("\\n") |
| 95 | of '\t': result.add("\\t") |
| 96 | of '\r': result.add("\\r") |
| 97 | else: result.add(c) |
| 98 | result.add("\"") |
| 99 | |
| 100 | proc rsDisplay*(x: Rune): string = $x |
| 101 | proc rsDebug*(x: Rune): string = |
| 102 | case $x |
| 103 | of "'": "'\\''" |
| 104 | of "\\": "'\\\\'" |
| 105 | of "\n": "'\\n'" |
| 106 | of "\t": "'\\t'" |
| 107 | of "\r": "'\\r'" |
| 108 | else: "'" & $x & "'" |
| 109 | |
| 110 | proc rsDebug*[T](x: seq[T] | openArray[T]): string = |
| 111 | result = "[" |
| 112 | for i in 0 ..< x.len: |
| 113 | if i > 0: result.add(", ") |
| 114 | result.add(rsDebug(x[i])) |
| 115 | result.add("]") |
| 116 | |
| 117 | proc rsDebug*[T](o: Option[T]): string = |
| 118 | if o.has: "Some(" & rsDebug(o.val) & ")" else: "None" |
| 119 | proc rsDebug*[T, E](r: Result[T, E]): string = |
| 120 | if r.ok: "Ok(" & rsDebug(r.val) & ")" else: "Err(" & rsDebug(r.err) & ")" |
| 121 | |
| 122 | # --------------------------------------------------------------------------- |
| 123 | # Radix formats: `{:x}`, `{:X}`, `{:b}`, `{:o}`, with Rust's width/zero-fill. |
| 124 | # Rust formats the *two's-complement bit pattern*, so a negative i8 prints as |
| 125 | # `ff`, not `-1`. `toHex` on the unsigned view of the same width reproduces it. |
| 126 | # --------------------------------------------------------------------------- |
| 127 | |
| 128 | proc rsRadix*[T: SomeInteger](x: T, base: int, upper: bool): string = |
| 129 | var v: uint64 = |
| 130 | when T is SomeSignedInt: |
| 131 | # Sign-extend then mask to the type's own width, so the printed bit |
| 132 | # pattern is the Rust one for this exact integer type. |
| 133 | cast[uint64](int64(x)) and (if sizeof(T) == 8: high(uint64) else: (1'u64 shl (sizeof(T) * 8)) - 1) |
| 134 | else: |
| 135 | uint64(x) |
| 136 | if v == 0: return "0" |
| 137 | const digits = "0123456789abcdef" |
| 138 | while v > 0'u64: |
| 139 | result.add(digits[int(v mod uint64(base))]) |
| 140 | v = v div uint64(base) |
| 141 | for i in 0 ..< result.len div 2: |
| 142 | swap(result[i], result[result.len - 1 - i]) |
| 143 | if upper: result = result.toUpperAscii() |
| 144 | |
| 145 | proc rsPad*(s: string, width: int, zero: bool): string = |
| 146 | if s.len >= width: return s |
| 147 | let fill = width - s.len |
| 148 | if zero and s.len > 0 and s[0] == '-': |
| 149 | "-" & repeat('0', fill) & s[1 .. ^1] |
| 150 | elif zero: |
| 151 | repeat('0', fill) & s |
| 152 | else: |
| 153 | repeat(' ', fill) & s |
| 154 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 15h ago | 155 | proc rsStrView*(b: openArray[uint8]): openArray[char] = |
| 156 | ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string |
| 157 | ## slice without copying or validating. Nim's `char` and `uint8` are both one |
| 158 | ## byte, so the same view is handed back over the same memory -- writes |
| 159 | ## through the original are visible here, as they are in Rust. |
| 160 | if b.len == 0: |
| 161 | result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1) |
| 162 | else: |
| 163 | result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1) |
| 164 | |
| Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 15h ago | 165 | proc rsStringOf*(b: openArray[uint8]): string = |
| 166 | ## `String::from_utf8_unchecked` takes ownership of the bytes. Nim's `string` |
| 167 | ## is an owned value, so this copies -- which is what the Rust call does to |
| 168 | ## the `Vec` it consumes, from the caller's point of view. |
| 169 | result = newStringOfCap(b.len) |
| 170 | for v in b: result.add(char(v)) |
| 171 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 15h ago | 172 | proc rsDisplay*(x: openArray[char]): string = |
| 173 | result = newStringOfCap(x.len) |
| 174 | for c in x: result.add(c) |
| 175 | |
| 176 | proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x)) |
| 177 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago | 178 | proc rsBytes*(s: string): seq[uint8] = |
| 179 | ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is |
| 180 | ## already those bytes, so this is a reinterpretation, not a conversion. |
| 181 | result = newSeq[uint8](s.len) |
| 182 | for i in 0 ..< s.len: result[i] = uint8(s[i]) |
| 183 | |
| 184 | proc newSeqWith*[T](n: int, v: T): seq[T] = |
| 185 | result = newSeq[T](n) |
| 186 | for i in 0 ..< n: result[i] = v |