Skip to content

Commit 7e1bf81

Browse files
committed
Add new uber-simple vendor/jsonx.jl for true no-dependency JSON functionality that can be directly vendored in
1 parent a86467d commit 7e1bf81

File tree

13 files changed

+1063
-97
lines changed

13 files changed

+1063
-97
lines changed

.github/workflows/vendor-tests.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Vendor Tests
2+
3+
on:
4+
push:
5+
paths:
6+
- 'vendor/**'
7+
pull_request:
8+
paths:
9+
- 'vendor/**'
10+
11+
jobs:
12+
test:
13+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
version:
19+
- '1' # automatically expands to the latest stable 1.x release of Julia
20+
- 'min'
21+
- 'pre'
22+
os:
23+
- ubuntu-latest
24+
- windows-latest
25+
arch:
26+
- x64
27+
include:
28+
- os: macOS-latest
29+
arch: aarch64
30+
version: 1
31+
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
36+
- name: Setup Julia
37+
uses: julia-actions/setup-julia@v2
38+
with:
39+
version: ${{ matrix.version }}
40+
arch: ${{ matrix.arch }}
41+
42+
- name: Cache Julia packages
43+
uses: julia-actions/cache@v2
44+
45+
- name: Run vendor tests
46+
run: |
47+
cd vendor
48+
julia test.jl

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Arrow = "2.8.0"
2222
ArrowTypes = "2.2"
2323
Parsers = "1, 2"
2424
PrecompileTools = "1"
25-
StructUtils = "2.1"
25+
StructUtils = "2.3"
2626
julia = "1.9"
2727

2828
[extras]

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ JSON.isvalidjson(json)
7171
JSON.json("test.json", j)
7272
```
7373

74+
## Vendor Directory
75+
76+
This package includes a `vendor/` directory containing a simplified, no-dependency JSON parser (`JSONX`) that can be vendored (copied) into other projects. See the [vendor README](vendor/README.md) for details.
77+
7478
## Contributing and Questions
7579

7680
Contributions are very welcome, as are feature requests and suggestions. Please open an

src/JSON.jl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export JSONText, StructUtils, @noarg, @defaults, @tags, @choosetype, @nonstruct,
3131
erri += textwidth(c)
3232
st = iterate(snippet, i)
3333
end
34-
3534
snippet = replace(snippet, r"[\b\f\n\r\t]" => " ")
36-
caret = repeat(' ', erri + 2) * "^"
35+
# we call @invoke here to avoid --trim verify errors
36+
caret = @invoke(repeat(" "::String, (erri + 2)::Integer)) * "^"
3737
msg = """
3838
invalid JSON at byte position $(pos) (line $line_no) parsing type $T: $error
3939
$snippet$(error == UnexpectedEOF ? " <EOF>" : "...")
@@ -104,12 +104,10 @@ print(io::IO, obj, indent=nothing) = json(io, obj; pretty=something(indent, 0))
104104
print(a, indent=nothing) = print(stdout, a, indent)
105105
@doc (@doc json) print
106106

107-
json(a, indent::Integer) = json(a; pretty=indent)
108-
109107
@compile_workload begin
110108
x = JSON.parse("{\"a\": 1, \"b\": null, \"c\": true, \"d\": false, \"e\": \"\", \"f\": [1,null,true], \"g\": {\"key\": \"value\"}}")
111-
# json = JSON.json(x)
112-
# isvalid(json)
109+
json = JSON.json(x)
110+
isvalid(json)
113111
end
114112

115113

src/lazy.jl

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ function applyobject(keyvalfunc, x::LazyValues)
269269
@nextbyte
270270
b == UInt8('}') && return pos + 1
271271
while true
272-
# applystring returns key as a PtrString
273-
key, pos = @inline applystring(nothing, LazyValue(buf, pos, JSONTypes.STRING, getopts(x), false))
272+
# parsestring returns key as a PtrString
273+
key, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
274274
@nextbyte
275275
if b != UInt8(':')
276276
error = ExpectedColon
@@ -455,7 +455,7 @@ StructUtils.keyeq(x::PtrString, y::Symbol) = convert(Symbol, x) == y
455455
# or not. It allows materialize, _binary, etc. to deal
456456
# with the string data appropriately without forcing a String allocation
457457
# PtrString should NEVER be visible to users though!
458-
function applystring(f, x::LazyValue)
458+
function parsestring(x::LazyValue)
459459
buf, pos = getbuf(x), getpos(x)
460460
len, b = getlength(buf), getbyte(buf, pos)
461461
if b != UInt8('"')
@@ -483,12 +483,7 @@ function applystring(f, x::LazyValue)
483483
@nextbyte(false)
484484
end
485485
str = PtrString(pointer(buf, spos), pos - spos, escaped)
486-
if f === nothing
487-
return str, pos + 1
488-
else
489-
f(str)
490-
return pos + 1
491-
end
486+
return str, pos + 1
492487

493488
@label invalid
494489
invalid(error, buf, pos, "string")
@@ -519,13 +514,35 @@ macro check_special(special, value)
519514
i += 1
520515
end
521516
if i > length(bytes)
522-
valfunc($value)
523-
return pos
517+
return NumberResult($value), pos
524518
end
525519
end)
526520
end
527521

528-
function applynumber(valfunc, x::LazyValue)
522+
const INT = 0x00
523+
const FLOAT = 0x01
524+
const BIGINT = 0x02
525+
const BIGFLOAT = 0x03
526+
const BIG_ZERO = BigInt(0)
527+
528+
struct NumberResult
529+
tag::UInt8
530+
int::Int64
531+
float::Float64
532+
bigint::BigInt
533+
bigfloat::BigFloat
534+
NumberResult(int::Int64) = new(INT, int)
535+
NumberResult(float::Float64) = new(FLOAT, Int64(0), float)
536+
NumberResult(bigint::BigInt) = new(BIGINT, Int64(0), 0.0, bigint)
537+
NumberResult(bigfloat::BigFloat) = new(BIGFLOAT, Int64(0), 0.0, BIG_ZERO, bigfloat)
538+
end
539+
540+
isint(x::NumberResult) = x.tag == INT
541+
isfloat(x::NumberResult) = x.tag == FLOAT
542+
isbigint(x::NumberResult) = x.tag == BIGINT
543+
isbigfloat(x::NumberResult) = x.tag == BIGFLOAT
544+
545+
@inline function parsenumber(x::LazyValue)
529546
buf = getbuf(x)
530547
pos = getpos(x)
531548
len = getlength(buf)
@@ -610,23 +627,20 @@ function applynumber(valfunc, x::LazyValue)
610627
# if we overflowed, then let's try BigFloat
611628
bres = Parsers.xparse2(BigFloat, buf, startpos, len)
612629
if !Parsers.invalid(bres.code)
613-
valfunc(bres.val)
614-
return startpos + bres.tlen
630+
return NumberResult(bres.val), startpos + bres.tlen
615631
end
616632
end
617633
if Parsers.invalid(res.code)
618634
error = InvalidNumber
619635
@goto invalid
620636
end
621-
valfunc(res.val)
622-
return startpos + res.tlen
637+
return NumberResult(res.val), startpos + res.tlen
623638
else
624639
if overflow
625-
valfunc(isneg ? -bval : bval)
640+
return NumberResult(isneg ? -bval : bval), pos
626641
else
627-
valfunc(isneg ? -val : val)
642+
return NumberResult(isneg ? -val : val), pos
628643
end
629-
return pos
630644
end
631645

632646
@label invalid
@@ -643,9 +657,11 @@ function skip(x::LazyValues)
643657
elseif T == JSONTypes.ARRAY
644658
return applyarray((i, v) -> 0, x)
645659
elseif T == JSONTypes.STRING
646-
return applystring(s -> 0, x)
660+
_, pos = parsestring(x)
661+
return pos
647662
elseif T == JSONTypes.NUMBER
648-
return applynumber(n -> 0, x)
663+
_, pos = parsenumber(x)
664+
return pos
649665
elseif T == JSONTypes.TRUE
650666
return getpos(x) + 4
651667
elseif T == JSONTypes.FALSE
@@ -716,7 +732,7 @@ function Base.show(io::IO, x::LazyValue)
716732
show(io, MIME"text/plain"(), la)
717733
end
718734
elseif T == JSONTypes.STRING
719-
str, _ = applystring(nothing, x)
735+
str, _ = parsestring(x)
720736
Base.print(io, "JSON.LazyValue(", repr(convert(String, str)), ")")
721737
elseif T == JSONTypes.NULL
722738
Base.print(io, "JSON.LazyValue(nothing)")

0 commit comments

Comments
 (0)