nandi/rustnimpublic Fork 0
7db79919131ab55e23a1730bf78c360e05d9977e
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.

prelude.nim · 315 lines · 11.4 KBNim Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago1## 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 ago9# 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 ago15import std/[unicode, strutils]
16
17type
18 RustPanic* = object of CatchableError
19
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 16h ago20 ## `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 ago27 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
37proc rsPanic*(msg: string) {.noreturn.} =
38 raise newException(RustPanic, msg)
39
40proc rsSome*[T](v: T): Option[T] = Option[T](has: true, val: v)
41proc rsNone*[T](): Option[T] = Option[T](has: false)
42proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v)
43proc 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 ago45proc 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
48proc unwrapOr*[T](o: Option[T], d: T): T =
49 if o.has: o.val else: d
50proc 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 ago53proc unwrap*[T](o: Option[T]): T =
54 if not o.has: rsPanic("called `Option::unwrap()` on a `None` value")
55 o.val
56proc 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 14h ago60# ---------------------------------------------------------------------------
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 13h ago61# 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
74type
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
83var rsLogMaxLevel*: int = 0 ## `LevelFilter::Off`, as in log.
84var rsLogSink*: proc (level: RsLogLevel, target, msg: string) {.closure.} = nil
85
86proc rsLogSetLogger*(f: proc (level: RsLogLevel, target, msg: string) {.closure.}) =
87 rsLogSink = f
88
89proc rsLogEnabled*(level: RsLogLevel): bool =
90 rsLogSink != nil and int(level) <= rsLogMaxLevel
91
92proc rsLog*(level: RsLogLevel, target, msg: string) =
93 if rsLogEnabled(level): rsLogSink(level, target, msg)
94
95proc 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
103proc 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
111proc 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
120proc 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 14h ago130# 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
139proc 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
150proc 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
159proc 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
167proc 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
177proc 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
184proc 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 17h ago189# ---------------------------------------------------------------------------
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
197proc rsDisplay*(x: SomeInteger): string = $x
198proc rsDebug*(x: SomeInteger): string = $x
199proc rsDisplay*(x: bool): string = $x
200proc rsDebug*(x: bool): string = $x
201
202proc 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
213proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false)
214proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true)
215
216proc rsDisplay*(x: string): string = x
217proc 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
229proc rsDisplay*(x: Rune): string = $x
230proc 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
239proc 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
246proc rsDebug*[T](o: Option[T]): string =
247 if o.has: "Some(" & rsDebug(o.val) & ")" else: "None"
248proc 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
257proc 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
274proc 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 16h ago284proc 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 15h ago294proc 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 16h ago301proc rsDisplay*(x: openArray[char]): string =
302 result = newStringOfCap(x.len)
303 for c in x: result.add(c)
304
305proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x))
306
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 17h ago307proc 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
313proc newSeqWith*[T](n: int, v: T): seq[T] =
314 result = newSeq[T](n)
315 for i in 0 ..< n: result[i] = v