Skip to content

Commit 5f0fb70

Browse files
authored
Merge pull request #64 from JuliaString/spj/fixtypemin
Fix issue 110 from Formatting.jl
2 parents dffbbd7 + 52a25df commit 5f0fb70

File tree

14 files changed

+856
-144
lines changed

14 files changed

+856
-144
lines changed

.drone.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
kind: pipeline
3+
name: linux - arm64 - Julia 1.9
4+
5+
platform:
6+
os: linux
7+
arch: arm64
8+
9+
steps:
10+
- name: build
11+
image: julia:1.9
12+
commands:
13+
- "julia --project=. --check-bounds=yes --color=yes -e 'using InteractiveUtils; versioninfo(verbose=true); using Pkg; Pkg.build(); Pkg.test(coverage=true)'"
14+
15+
---
16+
kind: pipeline
17+
name: linux - arm64 - Julia 1.9
18+
19+
platform:
20+
os: linux
21+
arch: arm64
22+
23+
steps:
24+
- name: build
25+
image: julia:1.9
26+
commands:
27+
- "julia --project=. --check-bounds=yes --color=yes -e 'using InteractiveUtils; versioninfo(verbose=true); using Pkg; Pkg.build(); Pkg.test(coverage=true)'"

.github/workflows/TagBot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: TagBot
2+
on:
3+
issue_comment:
4+
types:
5+
- created
6+
workflow_dispatch:
7+
jobs:
8+
TagBot:
9+
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: JuliaRegistries/TagBot@v1
13+
with:
14+
token: ${{ secrets.GITHUB_TOKEN }}
15+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
on:
3+
- push
4+
- pull_request
5+
jobs:
6+
test:
7+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
version:
13+
- '1.6'
14+
- '1.9'
15+
- 'nightly'
16+
os:
17+
- ubuntu-latest
18+
- macOS-latest
19+
- windows-latest
20+
arch:
21+
- x64
22+
- x86
23+
exclude:
24+
- os: macOS-latest
25+
arch: x86
26+
steps:
27+
- uses: actions/checkout@v2
28+
- uses: julia-actions/setup-julia@v1
29+
with:
30+
version: ${{ matrix.version }}
31+
arch: ${{ matrix.arch }}
32+
- uses: actions/cache@v1
33+
env:
34+
cache-name: cache-artifacts
35+
with:
36+
path: ~/.julia/artifacts
37+
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
38+
restore-keys: |
39+
${{ runner.os }}-test-${{ env.cache-name }}-
40+
${{ runner.os }}-test-
41+
${{ runner.os }}-
42+
- uses: julia-actions/julia-buildpkg@v1
43+
- uses: julia-actions/julia-runtest@v1

.travis.yml

Lines changed: 0 additions & 23 deletions
This file was deleted.

Project.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
authors = [
2+
"ScottPJones <[email protected]>",
23
"Dahua Lin <[email protected]>",
34
"John M Kuhn <[email protected]>",
4-
"ScottPJones <[email protected]>",
55
"Thomas Breloff <[email protected]>",
66
"Alex Arslan <[email protected]>",
77
"Chuan-Zheng Lee <[email protected]>",
@@ -23,17 +23,17 @@ keywords = ["Strings", "Formatting"]
2323
license = "MIT"
2424
name = "Format"
2525
uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
26-
version = "1.2.0"
26+
version = "1.3.3"
2727

2828
[deps]
29-
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
3029

3130
[extras]
3231
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3332
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
33+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
3434

3535
[targets]
36-
test = ["Test", "Random"]
36+
test = ["Test", "Random", "Printf"]
3737

3838
[compat]
39-
julia = "^1.0.0"
39+
julia = "1.4"

src/Format.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,6 @@ module Format
189189

190190
import Base.show
191191

192-
using Printf
193-
194192
_stdout() = stdout
195193
_codeunits(s) = Vector{UInt8}(codeunits(s))
196194
m_eval(expr) = Core.eval(@__MODULE__, expr)

src/cformat.jl

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,32 @@
1-
formatters = Dict{ ASCIIStr, Function }()
1+
include("printf.jl")
22

3-
cfmt( fmt::ASCIIStr, x ) = m_eval(Expr(:call, generate_formatter( fmt ), x))
3+
const _formatters = Dict{ASCIIStr,FmtSpec}()
44

5-
function checkfmt(fmt)
6-
@static if VERSION > v"1.6.0-DEV.854"
7-
test = Printf.Format(fmt)
8-
length(test.formats) == 1 ||
9-
error( "Only one AND undecorated format string is allowed")
10-
else
11-
test = @static VERSION >= v"1.4.0-DEV.180" ? Printf.parse(fmt) : Base.Printf.parse( fmt )
12-
(length( test ) == 1 && typeof( test[1] ) <: Tuple) ||
13-
error( "Only one AND undecorated format string is allowed")
14-
end
15-
end
5+
function _get_formatter(fmt)
6+
global _formatters
167

17-
function generate_formatter( fmt::ASCIIStr )
18-
global formatters
8+
chkfmt = get(_formatters, fmt, nothing)
9+
chkfmt === nothing || return chkfmt
10+
_formatters[fmt] = FmtSpec(fmt)
11+
end
1912

20-
haskey( formatters, fmt ) && return formatters[fmt]
13+
_cfmt_comma(fspec::FmtSpec, x) = addcommasreal(_cfmt(fspec, x))
14+
_cfmt_comma(fspec::FmtSpec{FmtStr}, x::Rational) = addcommasrat(_cfmt(fspec, x))
15+
_cfmt_comma(fspec::FmtSpec{<:FmtInts}, x) = checkcommas(_cfmt(fspec, x))
2116

22-
if !occursin("'", fmt)
23-
checkfmt(fmt)
24-
formatter = @eval(x->@sprintf( $fmt, x ))
25-
return (formatters[ fmt ] = x->Base.invokelatest(formatter, x))
26-
end
17+
function _cfmt(fspec::FmtSpec, x)
18+
sv = Base.StringVector(23) # Trust that lower level code will expand if necessary
19+
pos = _fmt(sv, 1, fspec, x)
20+
resize!(sv, pos - 1)
21+
String(sv)
22+
end
2723

28-
conversion = fmt[end]
29-
conversion in "sduifF" ||
30-
error( string("thousand separator not defined for ", conversion, " conversion") )
24+
cfmt(fspec::FmtSpec, x) = fspec.tsep == 0 ? _cfmt(fspec, x) : _cfmt_comma(fspec, x)
25+
cfmt(fmtstr::ASCIIStr, x) = cfmt(_get_formatter(fmtstr), x)
3126

32-
fmtactual = replace( fmt, "'" => ""; count=1 )
33-
checkfmt( fmtactual )
34-
formatter =
35-
if !(conversion in "sfF")
36-
@eval(x->checkcommas(@sprintf( $fmtactual, x )))
37-
elseif endswith( fmtactual, 's')
38-
@eval((x::Real)->((eltype(x) <: Rational)
39-
? addcommasrat(@sprintf( $fmtactual, x ))
40-
: addcommasreal(@sprintf( $fmtactual, x ))))
41-
else
42-
@eval((x::Real)->addcommasreal(@sprintf( $fmtactual, x )))
43-
end
44-
return (formatters[ fmt ] = x->Base.invokelatest(formatter, x))
27+
function generate_formatter(fmt::ASCIIStr)
28+
fspec = _get_formatter(fmt)
29+
fspec.tsep == 0 ? x -> _cfmt(fspec, x) : x -> _cfmt_comma(fspec, x)
4530
end
4631

4732
function addcommasreal(s)

src/fmtcore.jl

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@ function _pfmt_s(out::IO, fs::FormatSpec, s::Union{AbstractString,AbstractChar})
1919
slen = length(s)
2020
if wid <= slen
2121
print(out, s)
22+
elseif fs.align == '<'
23+
print(out, s)
24+
_repprint(out, fs.fill, wid-slen)
2225
else
23-
a = fs.align
24-
if a == '<'
25-
print(out, s)
26-
_repprint(out, fs.fill, wid-slen)
27-
else
28-
_repprint(out, fs.fill, wid-slen)
29-
print(out, s)
30-
end
26+
_repprint(out, fs.fill, wid-slen)
27+
print(out, s)
3128
end
3229
end
3330

@@ -44,6 +41,11 @@ _div(x::Integer, ::_Bin) = x >> 1
4441
_div(x::Integer, ::_Oct) = x >> 3
4542
_div(x::Integer, ::Union{_Hex, _HEX}) = x >> 4
4643

44+
_str(x::Integer, ::_Dec) = string(x, base=10)
45+
_str(x::Integer, ::_Bin) = string(x, base=2)
46+
_str(x::Integer, ::_Oct) = string(x, base=8)
47+
_str(x::Integer, ::Union{_Hex, _HEX}) = string(x, base=16)
48+
4749
function _ndigits(x::Integer, op) # suppose x is non-negative
4850
m = 1
4951
q = _div(x, op)
@@ -96,15 +98,53 @@ function _pfmt_intdigits(out::IO, ax::T, op::Op) where {Op, T<:Integer}
9698
end
9799
end
98100

101+
function _pfmt_intmin(out::IO, ip::ASCIIStr, zs::Integer, s::String)
102+
# print sign
103+
print(out, '-')
104+
# print prefix
105+
isempty(ip) || print(out, ip)
106+
# print padding zeros
107+
zs > 0 && _repprint(out, '0', zs)
108+
# print actual digits
109+
print(out, SubString(s, 2))
110+
nothing
111+
end
112+
113+
# Special case were abs would give error
114+
function _pfmt_imin(out::IO, fs::FormatSpec, x::Integer, op::Op) where {Op}
115+
s = _str(x, op)
116+
xlen = length(s)
117+
# prefix (e.g. 0x, 0b, 0o)
118+
ip = ""
119+
if fs.ipre
120+
ip = _ipre(op)
121+
xlen += length(ip)
122+
end
123+
124+
# printing
125+
wid = fs.width
126+
if wid <= xlen
127+
_pfmt_intmin(out, ip, 0, s)
128+
elseif fs.zpad
129+
_pfmt_intmin(out, ip, wid-xlen, s)
130+
elseif fs.align == '<'
131+
_pfmt_intmin(out, ip, 0, s)
132+
_repprint(out, fs.fill, wid-xlen)
133+
else
134+
_repprint(out, fs.fill, wid-xlen)
135+
_pfmt_intmin(out, ip, 0, s)
136+
end
137+
end
138+
99139
function _pfmt_i(out::IO, fs::FormatSpec, x::Integer, op::Op) where {Op}
140+
# Specially handle edge case of typemin
141+
x === typemin(typeof(x)) && x isa Signed && return _pfmt_imin(out, fs, x, op)
100142
# calculate actual length
101143
ax = abs(x)
102-
xlen = _ndigits(abs(x), op)
144+
xlen = _ndigits(ax, op)
103145
# sign char
104146
sch = _signchar(x, fs.sign)
105-
if sch != '\0'
106-
xlen += 1
107-
end
147+
xlen += (sch != '\0')
108148
# prefix (e.g. 0x, 0b, 0o)
109149
ip = ""
110150
if fs.ipre
@@ -118,15 +158,12 @@ function _pfmt_i(out::IO, fs::FormatSpec, x::Integer, op::Op) where {Op}
118158
_pfmt_int(out, sch, ip, 0, ax, op)
119159
elseif fs.zpad
120160
_pfmt_int(out, sch, ip, wid-xlen, ax, op)
161+
elseif fs.align == '<'
162+
_pfmt_int(out, sch, ip, 0, ax, op)
163+
_repprint(out, fs.fill, wid-xlen)
121164
else
122-
a = fs.align
123-
if a == '<'
124-
_pfmt_int(out, sch, ip, 0, ax, op)
125-
_repprint(out, fs.fill, wid-xlen)
126-
else
127-
_repprint(out, fs.fill, wid-xlen)
128-
_pfmt_int(out, sch, ip, 0, ax, op)
129-
end
165+
_repprint(out, fs.fill, wid-xlen)
166+
_pfmt_int(out, sch, ip, 0, ax, op)
130167
end
131168
end
132169

@@ -147,10 +184,9 @@ function _pfmt_float(out::IO, sch::AbstractChar, zs::Integer, intv::Real, decv::
147184
else
148185
_pfmt_intdigits(out, intv, _Dec())
149186
end
150-
# print decimal point
151-
print(out, '.')
152187
# print decimal part
153188
if prec > 0
189+
print(out, '.')
154190
nd = _ndigits(idecv, _Dec())
155191
nd < prec && _repprint(out, '0', prec - nd)
156192
_pfmt_intdigits(out, idecv, _Dec())
@@ -165,7 +201,7 @@ function _pfmt_f(out::IO, fs::FormatSpec, x::AbstractFloat)
165201
decv = rax - intv
166202

167203
# calculate length
168-
xlen = _ndigits(intv, _Dec()) + 1 + fs.prec
204+
xlen = _ndigits(intv, _Dec()) + ifelse(fs.prec > 0, fs.prec + 1, 0)
169205
sch != '\0' && (xlen += 1)
170206

171207
# print
@@ -217,6 +253,14 @@ function _pfmt_e(out::IO, fs::FormatSpec, x::AbstractFloat)
217253
rax = round(ax; sigdigits = fs.prec + 1)
218254
e = floor(Integer, log10(rax)) # exponent
219255
u = rax * exp10(-e) # significand
256+
i = 0
257+
v10 = 1
258+
while isinf(u)
259+
i += 1
260+
i > 18 && (u = 0.0; e = 0; break)
261+
v10 *= 10
262+
u = v10 * rax * exp(-e - i)
263+
end
220264
end
221265

222266
# calculate length

src/fmtspec.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ mutable struct _Bin end
162162

163163
_srepr(x) = repr(x)
164164
_srepr(x::AbstractString) = x
165+
_srepr(x::Symbol) = string(x)
165166
_srepr(x::AbstractChar) = string(x)
166167
_srepr(x::Enum) = string(x)
167168

0 commit comments

Comments
 (0)