| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 10h 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 9h 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 10h 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 9h 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 10h 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 9h 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 10h 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 | |
| Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 7h ago | 60 | # --------------------------------------------------------------------------- |
| Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 6h ago | 61 | # The `log` facade. |
| 62 | # |
| 63 | # `log`'s value to its dependents is 20 `macro_rules!`, which cannot be |
| 64 | # lowered, so the macros are lowered directly against the facade's documented |
| 65 | # behaviour -- see `src/macros.rs` for the same argument made about |
| 66 | # `bitflags!`. Verified against log 0.4.34 by `tests/cases/035-log.rs`. |
| 67 | # |
| 68 | # With no logger installed, every macro is a no-op and `log_enabled!` is false |
| 69 | # even after `set_max_level`, because the facade also consults the logger. A |
| 70 | # transpiled library therefore logs nothing by default, exactly as in Rust. |
| 71 | # `rsLogSetLogger` is the Nim-side analogue of `set_logger`. |
| 72 | # --------------------------------------------------------------------------- |
| 73 | |
| 74 | type |
| 75 | ## Named so that nothing a crate declares can collide: Rust's `log::Level` |
| 76 | ## never appears under that name in the output. |
| 77 | RsLogLevel* = enum |
| 78 | rsLvlError = 1, rsLvlWarn = 2, rsLvlInfo = 3, rsLvlDebug = 4, rsLvlTrace = 5 |
| 79 | RsLogFilter* = enum |
| 80 | rsFltOff = 0, rsFltError = 1, rsFltWarn = 2, rsFltInfo = 3, rsFltDebug = 4, |
| 81 | rsFltTrace = 5 |
| 82 | |
| 83 | var rsLogMaxLevel*: int = 0 ## `LevelFilter::Off`, as in log. |
| 84 | var rsLogSink*: proc (level: RsLogLevel, target, msg: string) {.closure.} = nil |
| 85 | |
| 86 | proc rsLogSetLogger*(f: proc (level: RsLogLevel, target, msg: string) {.closure.}) = |
| 87 | rsLogSink = f |
| 88 | |
| 89 | proc rsLogEnabled*(level: RsLogLevel): bool = |
| 90 | rsLogSink != nil and int(level) <= rsLogMaxLevel |
| 91 | |
| 92 | proc rsLog*(level: RsLogLevel, target, msg: string) = |
| 93 | if rsLogEnabled(level): rsLogSink(level, target, msg) |
| 94 | |
| 95 | proc rsDisplay*(x: RsLogLevel): string = |
| 96 | case x |
| 97 | of rsLvlError: "ERROR" |
| 98 | of rsLvlWarn: "WARN" |
| 99 | of rsLvlInfo: "INFO" |
| 100 | of rsLvlDebug: "DEBUG" |
| 101 | of rsLvlTrace: "TRACE" |
| 102 | |
| 103 | proc rsDebug*(x: RsLogLevel): string = |
| 104 | case x |
| 105 | of rsLvlError: "Error" |
| 106 | of rsLvlWarn: "Warn" |
| 107 | of rsLvlInfo: "Info" |
| 108 | of rsLvlDebug: "Debug" |
| 109 | of rsLvlTrace: "Trace" |
| 110 | |
| 111 | proc rsDisplay*(x: RsLogFilter): string = |
| 112 | case x |
| 113 | of rsFltOff: "OFF" |
| 114 | of rsFltError: "ERROR" |
| 115 | of rsFltWarn: "WARN" |
| 116 | of rsFltInfo: "INFO" |
| 117 | of rsFltDebug: "DEBUG" |
| 118 | of rsFltTrace: "TRACE" |
| 119 | |
| 120 | proc rsDebug*(x: RsLogFilter): string = |
| 121 | case x |
| 122 | of rsFltOff: "Off" |
| 123 | of rsFltError: "Error" |
| 124 | of rsFltWarn: "Warn" |
| 125 | of rsFltInfo: "Info" |
| 126 | of rsFltDebug: "Debug" |
| 127 | of rsFltTrace: "Trace" |
| 128 | |
| 129 | # --------------------------------------------------------------------------- |
| Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 7h ago | 130 | # Rust's explicit overflow policies. |
| 131 | # |
| 132 | # Plain `+` on a signed integer traps in both languages (DESIGN.md item 3), and |
| 133 | # on an unsigned one wraps in both (item 2). `saturating_*` and `checked_*` are |
| 134 | # neither, so they are spelled out. Overflow is detected on the unsigned view |
| 135 | # of the same width, where wrapping is defined, rather than by a range check |
| 136 | # that would itself trap. |
| 137 | # --------------------------------------------------------------------------- |
| 138 | |
| 139 | proc rsSatAdd*[T: SomeInteger](a, b: T): T = |
| 140 | when T is SomeUnsignedInt: |
| 141 | let s = a + b |
| 142 | if s < a: high(T) else: s |
| 143 | else: |
| 144 | let s = cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) |
| 145 | # Overflow iff the operands agree in sign and the result disagrees. |
| 146 | if (a >= 0) == (b >= 0) and (s >= 0) != (a >= 0): |
| 147 | if a >= 0: high(T) else: low(T) |
| 148 | else: s |
| 149 | |
| 150 | proc rsSatSub*[T: SomeInteger](a, b: T): T = |
| 151 | when T is SomeUnsignedInt: |
| 152 | if a < b: T(0) else: a - b |
| 153 | else: |
| 154 | let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b))) |
| 155 | if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): |
| 156 | if a >= 0: high(T) else: low(T) |
| 157 | else: s |
| 158 | |
| 159 | proc rsSatMul*[T: SomeInteger](a, b: T): T = |
| 160 | if a == T(0) or b == T(0): return T(0) |
| 161 | let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b))) |
| 162 | if s div b != a: |
| 163 | when T is SomeUnsignedInt: high(T) |
| 164 | else: (if (a >= 0) == (b >= 0): high(T) else: low(T)) |
| 165 | else: s |
| 166 | |
| 167 | proc rsChkAdd*[T: SomeInteger](a, b: T): Option[T] = |
| 168 | let s = rsSatAdd(a, b) |
| 169 | when T is SomeUnsignedInt: |
| 170 | if s == high(T) and not (a + b == high(T)): rsNone[T]() else: rsSome(s) |
| 171 | else: |
| 172 | if (a >= 0) == (b >= 0) and (s == high(T) or s == low(T)) and |
| 173 | cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) != s: |
| 174 | rsNone[T]() |
| 175 | else: rsSome(s) |
| 176 | |
| 177 | proc rsChkSub*[T: SomeInteger](a, b: T): Option[T] = |
| 178 | when T is SomeUnsignedInt: |
| 179 | if a < b: rsNone[T]() else: rsSome(a - b) |
| 180 | else: |
| 181 | let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b))) |
| 182 | if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): rsNone[T]() else: rsSome(s) |
| 183 | |
| 184 | proc rsChkMul*[T: SomeInteger](a, b: T): Option[T] = |
| 185 | if a == T(0) or b == T(0): return rsSome(T(0)) |
| 186 | let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b))) |
| 187 | if s div b != a: rsNone[T]() else: rsSome(s) |
| 188 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 10h ago | 189 | # --------------------------------------------------------------------------- |
| 190 | # Display / Debug. |
| 191 | # |
| 192 | # Rust's `{}` and `{:?}` are two distinct formats and they differ for floats, |
| 193 | # strings, chars and sequences. Nim's `$` matches neither consistently, so both |
| 194 | # are implemented here rather than approximated with `$`. |
| 195 | # --------------------------------------------------------------------------- |
| 196 | |
| 197 | proc rsDisplay*(x: SomeInteger): string = $x |
| 198 | proc rsDebug*(x: SomeInteger): string = $x |
| 199 | proc rsDisplay*(x: bool): string = $x |
| 200 | proc rsDebug*(x: bool): string = $x |
| 201 | |
| 202 | proc rsFloatStr(x: float64, debug: bool): string = |
| 203 | ## Nim and Rust both print the shortest round-tripping decimal, but they |
| 204 | ## spell the result differently in three places. |
| 205 | if x != x: return "NaN" |
| 206 | if x == Inf: return "inf" |
| 207 | if x == -Inf: return "-inf" |
| 208 | result = $x |
| 209 | result = result.replace("e+", "e") # Nim `1e+21`, Rust `1e21` |
| 210 | if not debug and result.endsWith(".0"): # Rust Display drops a bare `.0` |
| 211 | result.setLen(result.len - 2) |
| 212 | |
| 213 | proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false) |
| 214 | proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true) |
| 215 | |
| 216 | proc rsDisplay*(x: string): string = x |
| 217 | proc rsDebug*(x: string): string = |
| 218 | result = "\"" |
| 219 | for c in x: |
| 220 | case c |
| 221 | of '"': result.add("\\\"") |
| 222 | of '\\': result.add("\\\\") |
| 223 | of '\n': result.add("\\n") |
| 224 | of '\t': result.add("\\t") |
| 225 | of '\r': result.add("\\r") |
| 226 | else: result.add(c) |
| 227 | result.add("\"") |
| 228 | |
| 229 | proc rsDisplay*(x: Rune): string = $x |
| 230 | proc rsDebug*(x: Rune): string = |
| 231 | case $x |
| 232 | of "'": "'\\''" |
| 233 | of "\\": "'\\\\'" |
| 234 | of "\n": "'\\n'" |
| 235 | of "\t": "'\\t'" |
| 236 | of "\r": "'\\r'" |
| 237 | else: "'" & $x & "'" |
| 238 | |
| 239 | proc rsDebug*[T](x: seq[T] | openArray[T]): string = |
| 240 | result = "[" |
| 241 | for i in 0 ..< x.len: |
| 242 | if i > 0: result.add(", ") |
| 243 | result.add(rsDebug(x[i])) |
| 244 | result.add("]") |
| 245 | |
| 246 | proc rsDebug*[T](o: Option[T]): string = |
| 247 | if o.has: "Some(" & rsDebug(o.val) & ")" else: "None" |
| 248 | proc rsDebug*[T, E](r: Result[T, E]): string = |
| 249 | if r.ok: "Ok(" & rsDebug(r.val) & ")" else: "Err(" & rsDebug(r.err) & ")" |
| 250 | |
| 251 | # --------------------------------------------------------------------------- |
| 252 | # Radix formats: `{:x}`, `{:X}`, `{:b}`, `{:o}`, with Rust's width/zero-fill. |
| 253 | # Rust formats the *two's-complement bit pattern*, so a negative i8 prints as |
| 254 | # `ff`, not `-1`. `toHex` on the unsigned view of the same width reproduces it. |
| 255 | # --------------------------------------------------------------------------- |
| 256 | |
| 257 | proc rsRadix*[T: SomeInteger](x: T, base: int, upper: bool): string = |
| 258 | var v: uint64 = |
| 259 | when T is SomeSignedInt: |
| 260 | # Sign-extend then mask to the type's own width, so the printed bit |
| 261 | # pattern is the Rust one for this exact integer type. |
| 262 | cast[uint64](int64(x)) and (if sizeof(T) == 8: high(uint64) else: (1'u64 shl (sizeof(T) * 8)) - 1) |
| 263 | else: |
| 264 | uint64(x) |
| 265 | if v == 0: return "0" |
| 266 | const digits = "0123456789abcdef" |
| 267 | while v > 0'u64: |
| 268 | result.add(digits[int(v mod uint64(base))]) |
| 269 | v = v div uint64(base) |
| 270 | for i in 0 ..< result.len div 2: |
| 271 | swap(result[i], result[result.len - 1 - i]) |
| 272 | if upper: result = result.toUpperAscii() |
| 273 | |
| 274 | proc rsPad*(s: string, width: int, zero: bool): string = |
| 275 | if s.len >= width: return s |
| 276 | let fill = width - s.len |
| 277 | if zero and s.len > 0 and s[0] == '-': |
| 278 | "-" & repeat('0', fill) & s[1 .. ^1] |
| 279 | elif zero: |
| 280 | repeat('0', fill) & s |
| 281 | else: |
| 282 | repeat(' ', fill) & s |
| 283 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago | 284 | proc rsStrView*(b: openArray[uint8]): openArray[char] = |
| 285 | ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string |
| 286 | ## slice without copying or validating. Nim's `char` and `uint8` are both one |
| 287 | ## byte, so the same view is handed back over the same memory -- writes |
| 288 | ## through the original are visible here, as they are in Rust. |
| 289 | if b.len == 0: |
| 290 | result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1) |
| 291 | else: |
| 292 | result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1) |
| 293 | |
| Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 8h ago | 294 | proc rsStringOf*(b: openArray[uint8]): string = |
| 295 | ## `String::from_utf8_unchecked` takes ownership of the bytes. Nim's `string` |
| 296 | ## is an owned value, so this copies -- which is what the Rust call does to |
| 297 | ## the `Vec` it consumes, from the caller's point of view. |
| 298 | result = newStringOfCap(b.len) |
| 299 | for v in b: result.add(char(v)) |
| 300 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago | 301 | proc rsDisplay*(x: openArray[char]): string = |
| 302 | result = newStringOfCap(x.len) |
| 303 | for c in x: result.add(c) |
| 304 | |
| 305 | proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x)) |
| 306 | |
| Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 10h ago | 307 | proc rsBytes*(s: string): seq[uint8] = |
| 308 | ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is |
| 309 | ## already those bytes, so this is a reinterpretation, not a conversion. |
| 310 | result = newSeq[uint8](s.len) |
| 311 | for i in 0 ..< s.len: result[i] = uint8(s[i]) |
| 312 | |
| 313 | proc newSeqWith*[T](n: int, v: T): seq[T] = |
| 314 | result = newSeq[T](n) |
| 315 | for i in 0 ..< n: result[i] = v |