nandi/rustnimpublic Fork 0
ff34e1b3229df6e21b0c5d77053bb9b7364db5cd
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 · 246 lines · 9.1 KBNim Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h 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 8h 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 8h 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 8h 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 8h 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 8h 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 8h 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 6h ago60# ---------------------------------------------------------------------------
61# Rust's explicit overflow policies.
62#
63# Plain `+` on a signed integer traps in both languages (DESIGN.md item 3), and
64# on an unsigned one wraps in both (item 2). `saturating_*` and `checked_*` are
65# neither, so they are spelled out. Overflow is detected on the unsigned view
66# of the same width, where wrapping is defined, rather than by a range check
67# that would itself trap.
68# ---------------------------------------------------------------------------
69
70proc rsSatAdd*[T: SomeInteger](a, b: T): T =
71 when T is SomeUnsignedInt:
72 let s = a + b
73 if s < a: high(T) else: s
74 else:
75 let s = cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b)))
76 # Overflow iff the operands agree in sign and the result disagrees.
77 if (a >= 0) == (b >= 0) and (s >= 0) != (a >= 0):
78 if a >= 0: high(T) else: low(T)
79 else: s
80
81proc rsSatSub*[T: SomeInteger](a, b: T): T =
82 when T is SomeUnsignedInt:
83 if a < b: T(0) else: a - b
84 else:
85 let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
86 if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0):
87 if a >= 0: high(T) else: low(T)
88 else: s
89
90proc rsSatMul*[T: SomeInteger](a, b: T): T =
91 if a == T(0) or b == T(0): return T(0)
92 let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
93 if s div b != a:
94 when T is SomeUnsignedInt: high(T)
95 else: (if (a >= 0) == (b >= 0): high(T) else: low(T))
96 else: s
97
98proc rsChkAdd*[T: SomeInteger](a, b: T): Option[T] =
99 let s = rsSatAdd(a, b)
100 when T is SomeUnsignedInt:
101 if s == high(T) and not (a + b == high(T)): rsNone[T]() else: rsSome(s)
102 else:
103 if (a >= 0) == (b >= 0) and (s == high(T) or s == low(T)) and
104 cast[T](cast[uint64](int64(a)) + cast[uint64](int64(b))) != s:
105 rsNone[T]()
106 else: rsSome(s)
107
108proc rsChkSub*[T: SomeInteger](a, b: T): Option[T] =
109 when T is SomeUnsignedInt:
110 if a < b: rsNone[T]() else: rsSome(a - b)
111 else:
112 let s = cast[T](cast[uint64](int64(a)) - cast[uint64](int64(b)))
113 if (a >= 0) != (b >= 0) and (s >= 0) != (a >= 0): rsNone[T]() else: rsSome(s)
114
115proc rsChkMul*[T: SomeInteger](a, b: T): Option[T] =
116 if a == T(0) or b == T(0): return rsSome(T(0))
117 let s = cast[T](cast[uint64](int64(a)) * cast[uint64](int64(b)))
118 if s div b != a: rsNone[T]() else: rsSome(s)
119
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago120# ---------------------------------------------------------------------------
121# Display / Debug.
122#
123# Rust's `{}` and `{:?}` are two distinct formats and they differ for floats,
124# strings, chars and sequences. Nim's `$` matches neither consistently, so both
125# are implemented here rather than approximated with `$`.
126# ---------------------------------------------------------------------------
127
128proc rsDisplay*(x: SomeInteger): string = $x
129proc rsDebug*(x: SomeInteger): string = $x
130proc rsDisplay*(x: bool): string = $x
131proc rsDebug*(x: bool): string = $x
132
133proc rsFloatStr(x: float64, debug: bool): string =
134 ## Nim and Rust both print the shortest round-tripping decimal, but they
135 ## spell the result differently in three places.
136 if x != x: return "NaN"
137 if x == Inf: return "inf"
138 if x == -Inf: return "-inf"
139 result = $x
140 result = result.replace("e+", "e") # Nim `1e+21`, Rust `1e21`
141 if not debug and result.endsWith(".0"): # Rust Display drops a bare `.0`
142 result.setLen(result.len - 2)
143
144proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false)
145proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true)
146
147proc rsDisplay*(x: string): string = x
148proc rsDebug*(x: string): string =
149 result = "\""
150 for c in x:
151 case c
152 of '"': result.add("\\\"")
153 of '\\': result.add("\\\\")
154 of '\n': result.add("\\n")
155 of '\t': result.add("\\t")
156 of '\r': result.add("\\r")
157 else: result.add(c)
158 result.add("\"")
159
160proc rsDisplay*(x: Rune): string = $x
161proc rsDebug*(x: Rune): string =
162 case $x
163 of "'": "'\\''"
164 of "\\": "'\\\\'"
165 of "\n": "'\\n'"
166 of "\t": "'\\t'"
167 of "\r": "'\\r'"
168 else: "'" & $x & "'"
169
170proc rsDebug*[T](x: seq[T] | openArray[T]): string =
171 result = "["
172 for i in 0 ..< x.len:
173 if i > 0: result.add(", ")
174 result.add(rsDebug(x[i]))
175 result.add("]")
176
177proc rsDebug*[T](o: Option[T]): string =
178 if o.has: "Some(" & rsDebug(o.val) & ")" else: "None"
179proc rsDebug*[T, E](r: Result[T, E]): string =
180 if r.ok: "Ok(" & rsDebug(r.val) & ")" else: "Err(" & rsDebug(r.err) & ")"
181
182# ---------------------------------------------------------------------------
183# Radix formats: `{:x}`, `{:X}`, `{:b}`, `{:o}`, with Rust's width/zero-fill.
184# Rust formats the *two's-complement bit pattern*, so a negative i8 prints as
185# `ff`, not `-1`. `toHex` on the unsigned view of the same width reproduces it.
186# ---------------------------------------------------------------------------
187
188proc rsRadix*[T: SomeInteger](x: T, base: int, upper: bool): string =
189 var v: uint64 =
190 when T is SomeSignedInt:
191 # Sign-extend then mask to the type's own width, so the printed bit
192 # pattern is the Rust one for this exact integer type.
193 cast[uint64](int64(x)) and (if sizeof(T) == 8: high(uint64) else: (1'u64 shl (sizeof(T) * 8)) - 1)
194 else:
195 uint64(x)
196 if v == 0: return "0"
197 const digits = "0123456789abcdef"
198 while v > 0'u64:
199 result.add(digits[int(v mod uint64(base))])
200 v = v div uint64(base)
201 for i in 0 ..< result.len div 2:
202 swap(result[i], result[result.len - 1 - i])
203 if upper: result = result.toUpperAscii()
204
205proc rsPad*(s: string, width: int, zero: bool): string =
206 if s.len >= width: return s
207 let fill = width - s.len
208 if zero and s.len > 0 and s[0] == '-':
209 "-" & repeat('0', fill) & s[1 .. ^1]
210 elif zero:
211 repeat('0', fill) & s
212 else:
213 repeat(' ', fill) & s
214
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago215proc rsStrView*(b: openArray[uint8]): openArray[char] =
216 ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string
217 ## slice without copying or validating. Nim's `char` and `uint8` are both one
218 ## byte, so the same view is handed back over the same memory -- writes
219 ## through the original are visible here, as they are in Rust.
220 if b.len == 0:
221 result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1)
222 else:
223 result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1)
224
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago225proc rsStringOf*(b: openArray[uint8]): string =
226 ## `String::from_utf8_unchecked` takes ownership of the bytes. Nim's `string`
227 ## is an owned value, so this copies -- which is what the Rust call does to
228 ## the `Vec` it consumes, from the caller's point of view.
229 result = newStringOfCap(b.len)
230 for v in b: result.add(char(v))
231
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago232proc rsDisplay*(x: openArray[char]): string =
233 result = newStringOfCap(x.len)
234 for c in x: result.add(c)
235
236proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x))
237
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago238proc rsBytes*(s: string): seq[uint8] =
239 ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is
240 ## already those bytes, so this is a reinterpretation, not a conversion.
241 result = newSeq[uint8](s.len)
242 for i in 0 ..< s.len: result[i] = uint8(s[i])
243
244proc newSeqWith*[T](n: int, v: T): seq[T] =
245 result = newSeq[T](n)
246 for i in 0 ..< n: result[i] = v