Skip to content

Commit c26d768

Browse files
committed
feat(builtins): bin oct hex
1 parent f326cda commit c26d768

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

Objects/abstract/number.nim

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import ../../Include/cpython/pyerrors
1010
export PyNumber_Index, PyNumber_AsSsize_t, PyNumber_AsClampedSsize_t
1111
import ./op_helpers
1212

13+
proc PyNumber_ToBase*(n: PyObject, base: uint8): PyObject =
14+
if base not_in {2u8, 8, 10, 16}:
15+
return newSystemError newPyAscii"PyNumber_ToBase: base must be 2, 8, 10 or 16"
16+
let index = privatePyNumber_Index(n)
17+
retIfExc index
18+
var s: string
19+
retIfExc PyIntObject(index).format(base, s)
20+
newPyAscii s
21+
1322
proc PyLong_AsLongAndOverflow*(vv: PyObject, overflow: var IntSign, res: var int): PyBaseErrorObject =
1423
if vv.isNil: return PyErr_BadInternalCall()
1524
res = PyIntObject(

Objects/numobjects/intobject/ops.nim

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import ./[
1616
export bit_length, signbit
1717
import ../../stringobject/strformat
1818
import ../../../Modules/unicodedata/[decimal, space]
19+
from ../../../Utils/utils import unreachable
1920
import ../../../Include/internal/pycore_int
2021
export PY_INT_MAX_STR_DIGITS_THRESHOLD, PY_INT_DEFAULT_MAX_STR_DIGITS
2122

@@ -776,6 +777,67 @@ proc PyLong_FromString*[C: char](s: openArray[C]; nParsed: var int; base: int =
776777
result = res.fromStr(s, nParsed, base)
777778
if result.isNil: result = res
778779

780+
proc long_format_binary(a: PyIntObject, base: uint8, alternate: bool, v: var string): PyBaseErrorObject =
781+
assert base in {2u8, 8, 16}
782+
783+
let
784+
size_a = a.digitCount
785+
high_a = size_a - 1
786+
let bits = case base
787+
of 16: 4
788+
of 8: 3
789+
of 2: 1
790+
else: unreachable
791+
let negative = a.negative
792+
var sz: int
793+
if size_a == 0:
794+
v = "0"
795+
return
796+
else:
797+
# Ensure overflow doesn't occur during computation of sz.
798+
if size_a > int.high - 3 div PyLong_SHIFT:
799+
return newOverflowError newPyAscii"int too large to format"
800+
{.push overflowChecks: off.}
801+
let size_a_in_bits = (high_a) * PyLong_SHIFT + bit_length(a.digits[high_a])
802+
# Allow 1 character for a '-' sign.
803+
sz = negative.int + (size_a_in_bits + (bits - 1)) div bits
804+
{.pop.}
805+
if alternate: sz += 2
806+
v = (when declared(newStringUninit): newStringUninit else: newString)(sz)
807+
808+
template WRITE_DIGITS(p) =
809+
# JRH: special case for power-of-2 bases
810+
var accum = TwoDigits 0
811+
var accumbits = 0 # # of bits in accum
812+
for i in 0..<size_a:
813+
accum = accum or ((TwoDigits a.digits[i]) shl accumbits)
814+
accumbits += PyLong_SHIFT
815+
assert accumbits >= bits
816+
while true:
817+
var cdigit = cast[uint8](accum and (base - 1))
818+
cdigit += (if cdigit < 10: uint8'0' else: 87#[uint8('a')-10]#)
819+
*--cast[char](cdigit)
820+
accumbits -= bits
821+
accum = accum shr bits
822+
if not (
823+
if i < high_a: accumbits >= bits
824+
else: accum > 0): break
825+
if alternate:
826+
case bits
827+
of 4: *--'x'
828+
of 3: *--'o'
829+
else: *--'b' # base == 2
830+
*--'0'
831+
if negative: *--'-'
832+
833+
var p = sz
834+
template `*--`(c) =
835+
p.dec
836+
v[p] = c
837+
838+
WRITE_DIGITS p
839+
assert p == 0
840+
779841
proc fill(result: var string, i: PyIntObject) =
780842
if i.zero:
781843
result = "0"
@@ -816,6 +878,12 @@ proc toStringCheckThreshold*(a: PyIntObject, v: var string): PyBaseErrorObject{.
816878
if strlen > PY_INT_MAX_STR_DIGITS_THRESHOLD:
817879
check_max_str_digits strlen - int(negative) > max_str_digits
818880

881+
proc format*(i: PyIntObject, base: uint8, s: var string): PyBaseErrorObject =
882+
# `_PyLong_Format`
883+
# `s` is a `out` param
884+
if base == 10: toStringCheckThreshold(i, s)
885+
else: long_format_binary(i, base, true, s)
886+
819887
proc hash*(self: PyIntObject): Hash {. inline, cdecl .} =
820888
result = hash(self.sign)
821889
for digit in self.digits:

Python/bltinmodule/unarys.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ proc ord*(c: PyObject): PyObject{.bltin_clinicGen.} =
3535
return newTypeError newPyStr fmt"ord() expected a string of length 1, but {c.typeName:.200s} found"
3636
return newTypeError newPyStr fmt"ord() expected a character, but string of length {size} found"
3737

38+
proc bin*(n: PyObject): PyObject{.bltin_clinicGen.} = PyNumber_ToBase n, 2
39+
proc oct*(n: PyObject): PyObject{.bltin_clinicGen.} = PyNumber_ToBase n, 8
40+
proc hex*(n: PyObject): PyObject{.bltin_clinicGen.} = PyNumber_ToBase n, 16
3841

3942
proc chr*(c: PyObject): PyObject{.bltin_clinicGen.} =
4043
var overflow: IntSign
@@ -53,6 +56,9 @@ template register_unarys* =
5356
bind regfunc
5457
regfunc abs
5558
regfunc ord
59+
regfunc bin
60+
regfunc oct
61+
regfunc hex
5662
regfunc chr
5763
regfunc callable
5864

0 commit comments

Comments
 (0)