nandi/rustnimpublic Fork 0
4e4d09dcdd22d17ba510de5639fc3a952ac73f6e
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Lower the `log` facade, and add explicit enum discriminants 12c0a01 · on 4e4d09dcdd22d17ba510de5639fc3a952ac73f6e · nandithebull · 5h ago
prelude.nim · 315 lines · 11.4 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
## rustnim prelude — emitted at the top of every generated module.
##
## Everything here exists to make Nim's observable behaviour match Rust's
## exactly. Where the two languages already agree (signed `shr` is arithmetic
## in both; fixed-width unsigned arithmetic wraps in both; integer `div`/`mod`
## truncate toward zero in both) there is deliberately nothing here: the
## operator is mapped directly and no helper is involved.

# Rust's `&[T]` is a borrowed view, not a copy. Nim's view types model exactly
# that, including returning one from a proc: writing through the returned view
# is visible in the original buffer. Probed against Nim 2.2.4 before relying on
# it, because copying instead would silently change aliasing.
{.experimental: "views".}

import std/[unicode, strutils]

type
  RustPanic* = object of CatchableError

  ## `core::fmt`'s own error and sink, kept distinct from any user type that
  ## happens to be called `Error`. A formatting impl is lowered to a proc that
  ## returns the formatted string, so these appear only in signatures.
  FmtError* = object
  FmtResult* = object
    ok*: bool

  Option*[T] = object
    case has*: bool
    of true: val*: T
    of false: discard

  Result*[T, E] = object
    case ok*: bool
    of true: val*: T
    of false: err*: E

proc rsPanic*(msg: string) {.noreturn.} =
  raise newException(RustPanic, msg)

proc rsSome*[T](v: T): Option[T] = Option[T](has: true, val: v)
proc rsNone*[T](): Option[T] = Option[T](has: false)
proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v)
proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e)

proc rsOkOr*[T, E](o: Option[T], e: E): Result[T, E] =
  if o.has: Result[T, E](ok: true, val: o.val) else: Result[T, E](ok: false, err: e)

proc unwrapOr*[T](o: Option[T], d: T): T =
  if o.has: o.val else: d
proc unwrapOr*[T, E](r: Result[T, E], d: T): T =
  if r.ok: r.val else: d

proc unwrap*[T](o: Option[T]): T =
  if not o.has: rsPanic("called `Option::unwrap()` on a `None` value")
  o.val
proc unwrap*[T, E](r: Result[T, E]): T =
  if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value")
  r.val

# ---------------------------------------------------------------------------
# The `log` facade.
#
# `log`'s value to its dependents is 20 `macro_rules!`, which cannot be
# lowered, so the macros are lowered directly against the facade's documented
# behaviour -- see `src/macros.rs` for the same argument made about
# `bitflags!`. Verified against log 0.4.34 by `tests/cases/035-log.rs`.
#
# With no logger installed, every macro is a no-op and `log_enabled!` is false
# even after `set_max_level`, because the facade also consults the logger. A
# transpiled library therefore logs nothing by default, exactly as in Rust.
# `rsLogSetLogger` is the Nim-side analogue of `set_logger`.
# ---------------------------------------------------------------------------

type
  ## Named so that nothing a crate declares can collide: Rust's `log::Level`
  ## never appears under that name in the output.
  RsLogLevel* = enum
    rsLvlError = 1, rsLvlWarn = 2, rsLvlInfo = 3, rsLvlDebug = 4, rsLvlTrace = 5
  RsLogFilter* = enum
    rsFltOff = 0, rsFltError = 1, rsFltWarn = 2, rsFltInfo = 3, rsFltDebug = 4,
    rsFltTrace = 5

var rsLogMaxLevel*: int = 0          ## `LevelFilter::Off`, as in log.
var rsLogSink*: proc (level: RsLogLevel, target, msg: string) {.closure.} = nil

proc rsLogSetLogger*(f: proc (level: RsLogLevel, target, msg: string) {.closure.}) =
  rsLogSink = f

proc rsLogEnabled*(level: RsLogLevel): bool =
  rsLogSink != nil and int(level) <= rsLogMaxLevel

proc rsLog*(level: RsLogLevel, target, msg: string) =
  if rsLogEnabled(level): rsLogSink(level, target, msg)

proc rsDisplay*(x: RsLogLevel): string =
  case x
  of rsLvlError: "ERROR"
  of rsLvlWarn: "WARN"
  of rsLvlInfo: "INFO"
  of rsLvlDebug: "DEBUG"
  of rsLvlTrace: "TRACE"

proc rsDebug*(x: RsLogLevel): string =
  case x
  of rsLvlError: "Error"
  of rsLvlWarn: "Warn"
  of rsLvlInfo: "Info"
  of rsLvlDebug: "Debug"
  of rsLvlTrace: "Trace"

proc rsDisplay*(x: RsLogFilter): string =
  case x
  of rsFltOff: "OFF"
  of rsFltError: "ERROR"
  of rsFltWarn: "WARN"
  of rsFltInfo: "INFO"
  of rsFltDebug: "DEBUG"
  of rsFltTrace: "TRACE"

proc rsDebug*(x: RsLogFilter): string =
  case x
  of rsFltOff: "Off"
  of rsFltError: "Error"
  of rsFltWarn: "Warn"
  of rsFltInfo: "Info"
  of rsFltDebug: "Debug"
  of rsFltTrace: "Trace"

# ---------------------------------------------------------------------------
# Rust's explicit overflow policies.
#
# Plain `+` on a signed integer traps in both languages (DESIGN.md item 3), and
# on an unsigned one wraps in both (item 2). `saturating_*` and `checked_*` are
# neither, so they are spelled out. Overflow is detected on the unsigned view
# of the same width, where wrapping is defined, rather than by a range check
# that would itself trap.
# ---------------------------------------------------------------------------

proc rsSatAdd*[T: SomeInteger](a, b: T): T =
  when T is SomeUnsignedInt:
    let s = a + b
    if s < a: high(T) else: s
  else:
    let s = cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b)))
    # Overflow iff the operands agree in sign and the result disagrees.
    if (a >= 0) == (b >= 0) and (s >= 0) != (a >= 0):
      if a >= 0: high(T) else: low(T)
    else: s

proc rsSatSub*[T: SomeInteger](a, b: T): T =
  when T is SomeUnsignedInt:
    if a < b: T(0) else: a - b
  else:
    let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
    if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0):
      if a >= 0: high(T) else: low(T)
    else: s

proc rsSatMul*[T: SomeInteger](a, b: T): T =
  if a == T(0) or b == T(0): return T(0)
  let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
  if s div b != a:
    when T is SomeUnsignedInt: high(T)
    else: (if (a >= 0) == (b >= 0): high(T) else: low(T))
  else: s

proc rsChkAdd*[T: SomeInteger](a, b: T): Option[T] =
  let s = rsSatAdd(a, b)
  when T is SomeUnsignedInt:
    if s == high(T) and not (a + b == high(T)): rsNone[T]() else: rsSome(s)
  else:
    if (a >= 0) == (b >= 0) and (s == high(T) or s == low(T)) and
       cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) != s:
      rsNone[T]()
    else: rsSome(s)

proc rsChkSub*[T: SomeInteger](a, b: T): Option[T] =
  when T is SomeUnsignedInt:
    if a < b: rsNone[T]() else: rsSome(a - b)
  else:
    let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
    if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): rsNone[T]() else: rsSome(s)

proc rsChkMul*[T: SomeInteger](a, b: T): Option[T] =
  if a == T(0) or b == T(0): return rsSome(T(0))
  let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
  if s div b != a: rsNone[T]() else: rsSome(s)

# ---------------------------------------------------------------------------
# Display / Debug.
#
# Rust's `{}` and `{:?}` are two distinct formats and they differ for floats,
# strings, chars and sequences. Nim's `$` matches neither consistently, so both
# are implemented here rather than approximated with `$`.
# ---------------------------------------------------------------------------

proc rsDisplay*(x: SomeInteger): string = $x
proc rsDebug*(x: SomeInteger): string = $x
proc rsDisplay*(x: bool): string = $x
proc rsDebug*(x: bool): string = $x

proc rsFloatStr(x: float64, debug: bool): string =
  ## Nim and Rust both print the shortest round-tripping decimal, but they
  ## spell the result differently in three places.
  if x != x: return "NaN"
  if x == Inf: return "inf"
  if x == -Inf: return "-inf"
  result = $x
  result = result.replace("e+", "e")        # Nim `1e+21`, Rust `1e21`
  if not debug and result.endsWith(".0"):   # Rust Display drops a bare `.0`
    result.setLen(result.len - 2)

proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false)
proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true)

proc rsDisplay*(x: string): string = x
proc rsDebug*(x: string): string =
  result = "\""
  for c in x:
    case c
    of '"': result.add("\\\"")
    of '\\': result.add("\\\\")
    of '\n': result.add("\\n")
    of '\t': result.add("\\t")
    of '\r': result.add("\\r")
    else: result.add(c)
  result.add("\"")

proc rsDisplay*(x: Rune): string = $x
proc rsDebug*(x: Rune): string =
  case $x
  of "'": "'\\''"
  of "\\": "'\\\\'"
  of "\n": "'\\n'"
  of "\t": "'\\t'"
  of "\r": "'\\r'"
  else: "'" & $x & "'"

proc rsDebug*[T](x: seq[T] | openArray[T]): string =
  result = "["
  for i in 0 ..< x.len:
    if i > 0: result.add(", ")
    result.add(rsDebug(x[i]))
  result.add("]")

proc rsDebug*[T](o: Option[T]): string =
  if o.has: "Some(" & rsDebug(o.val) & ")" else: "None"
proc rsDebug*[T, E](r: Result[T, E]): string =
  if r.ok: "Ok(" & rsDebug(r.val) & ")" else: "Err(" & rsDebug(r.err) & ")"

# ---------------------------------------------------------------------------
# Radix formats: `{:x}`, `{:X}`, `{:b}`, `{:o}`, with Rust's width/zero-fill.
# Rust formats the *two's-complement bit pattern*, so a negative i8 prints as
# `ff`, not `-1`. `toHex` on the unsigned view of the same width reproduces it.
# ---------------------------------------------------------------------------

proc rsRadix*[T: SomeInteger](x: T, base: int, upper: bool): string =
  var v: uint64 =
    when T is SomeSignedInt:
      # Sign-extend then mask to the type's own width, so the printed bit
      # pattern is the Rust one for this exact integer type.
      cast[uint64](int64(x)) and (if sizeof(T) == 8: high(uint64) else: (1'u64 shl (sizeof(T) * 8)) - 1)
    else:
      uint64(x)
  if v == 0: return "0"
  const digits = "0123456789abcdef"
  while v > 0'u64:
    result.add(digits[int(v mod uint64(base))])
    v = v div uint64(base)
  for i in 0 ..< result.len div 2:
    swap(result[i], result[result.len - 1 - i])
  if upper: result = result.toUpperAscii()

proc rsPad*(s: string, width: int, zero: bool): string =
  if s.len >= width: return s
  let fill = width - s.len
  if zero and s.len > 0 and s[0] == '-':
    "-" & repeat('0', fill) & s[1 .. ^1]
  elif zero:
    repeat('0', fill) & s
  else:
    repeat(' ', fill) & s

proc rsStrView*(b: openArray[uint8]): openArray[char] =
  ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string
  ## slice without copying or validating. Nim's `char` and `uint8` are both one
  ## byte, so the same view is handed back over the same memory -- writes
  ## through the original are visible here, as they are in Rust.
  if b.len == 0:
    result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1)
  else:
    result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1)

proc rsStringOf*(b: openArray[uint8]): string =
  ## `String::from_utf8_unchecked` takes ownership of the bytes. Nim's `string`
  ## is an owned value, so this copies -- which is what the Rust call does to
  ## the `Vec` it consumes, from the caller's point of view.
  result = newStringOfCap(b.len)
  for v in b: result.add(char(v))

proc rsDisplay*(x: openArray[char]): string =
  result = newStringOfCap(x.len)
  for c in x: result.add(c)

proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x))

proc rsBytes*(s: string): seq[uint8] =
  ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is
  ## already those bytes, so this is a reinterpretation, not a conversion.
  result = newSeq[uint8](s.len)
  for i in 0 ..< s.len: result[i] = uint8(s[i])

proc newSeqWith*[T](n: int, v: T): seq[T] =
  result = newSeq[T](n)
  for i in 0 ..< n: result[i] = v