Skip to content

Commit 356e768

Browse files
committed
perf: use string.byte and string.match
string.byte is faster as it doesn't need to convert the strings to their characters to compare them. string.match is *likely* faster than string.find since it doesn't automatically return the whole string in case of no capture groups being given.
1 parent 50247cd commit 356e768

File tree

1 file changed

+78
-69
lines changed

1 file changed

+78
-69
lines changed

qjson.lua

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
local NULL = {}
22

3-
local find, sub, tonumber, error = string.find, string.sub, tonumber, error
3+
local find, match, sub, byte, tonumber, error = string.find, string.match, string.sub, string.byte, tonumber, error
44
local function decode(json --[[@param json string]]) ---@return any
55
local ptr = 1
66

7-
local function consume(pattern --[[@param pattern string]]) ---@return string?
8-
local _, finish, match = find(json, pattern, ptr)
7+
local function skip(pattern --[[@param pattern string]]) ---@return true?
8+
local finish = match(json, pattern, ptr)
99
if finish then
10-
ptr = finish + 1
11-
return match or true
10+
ptr = finish
11+
return true
1212
end
1313
end
1414

1515
local function slowstring() -- string parser that supports escapes.
16-
local start = ptr + 1 -- skip initial quote
17-
ptr = start
18-
16+
local start = ptr
1917
while true do
20-
local start, finish = find(json, "\\*\"", ptr)
21-
if finish then
22-
ptr = finish + 1
23-
if (finish - start) % 2 == 0 then
24-
return sub(json, start, finish - 1) -- return (gsub(sub(json, start, finish - 1), "\\([bfnrt\\\"])", ESCAPES))
18+
local _start, before_quote, after_quote = match(json, "()\\*()\"()", ptr)
19+
if _start then
20+
ptr = after_quote
21+
if (before_quote - _start) % 2 == 0 then
22+
return sub(json, start, after_quote) -- return (gsub(sub(json, start, finish - 1), "\\([bfnrt\\\"])", ESCAPES))
2523
end
2624
else
2725
error("Missing end quote for string at char " .. ptr)
@@ -31,67 +29,80 @@ local function decode(json --[[@param json string]]) ---@return any
3129

3230

3331
local function number()
34-
return tonumber(consume("^(-?%d*.?%d+[eE]?[+-]?%d*)"))
32+
local number, finish = match(json, "^(-?%d*.?%d+[eE]?[+-]?%d*)()", ptr - 1)
33+
if finish then
34+
ptr = finish
35+
return tonumber(number)
36+
end
3537
end
3638

3739
local value
3840
local function whitespace()
39-
ptr = find(json, "%S", ptr) or ptr -- skip past whitespace, return immediate value
41+
ptr = match(json, "^%s*()", ptr) or ptr -- skip past whitespace, return immediate value
4042
return value()
4143
end
4244

43-
local function string()
44-
local start = ptr + 1
45-
local quot = find(json, '"', start, true)
46-
local prev = quot - 1
47-
if sub(json, prev, prev) ~= "\\" then
48-
ptr = quot + 1
49-
return sub(json, start, prev)
50-
else
51-
return slowstring()
52-
end
53-
end
54-
5545
local peek = {
56-
["\""] = string,
57-
["t"] = function()
58-
if sub(json, ptr, ptr + 3) == "true" then
59-
ptr = ptr + 4
46+
[34 --[["]]] = function()
47+
local start = ptr
48+
local quot = find(json, '"', start, true)
49+
local prev = quot - 1
50+
if byte(json, prev) ~= 92 --[[\]] then
51+
ptr = quot + 1
52+
return sub(json, start, prev)
53+
else
54+
return slowstring()
55+
end
56+
end,
57+
[116 --[[t]]] = function()
58+
if sub(json, ptr, ptr + 2) == "rue" then
59+
ptr = ptr + 3
6060
return true
6161
end
6262
end,
63-
["f"] = function()
64-
if sub(json, ptr, ptr + 4) == "false" then
65-
ptr = ptr + 5
63+
[102 --[[f]]] = function()
64+
if sub(json, ptr, ptr + 3) == "alse" then
65+
ptr = ptr + 4
6666
return false
6767
end
6868
end,
69-
["n"] = function()
70-
if sub(json, ptr, ptr + 3) == "null" then
71-
ptr = ptr + 4
69+
[110 --[[n]]] = function()
70+
if sub(json, ptr, ptr + 2) == "ull" then
71+
ptr = ptr + 3
7272
return NULL
7373
end
7474
end,
7575

76-
["0"] = number, ["1"] = number, ["2"] = number,
77-
["3"] = number, ["4"] = number, ["5"] = number,
78-
["6"] = number, ["7"] = number, ["8"] = number,
79-
["9"] = number, ["-"] = number,
80-
81-
["{"] = function()
82-
ptr = ptr + 1
76+
[48 --[[0]]] = number, [49] = number, [50] = number,
77+
[51] = number, [52] = number, [53] = number,
78+
[54] = number, [55] = number, [56] = number,
79+
[57 --[[9]]] = number, [45 --[[-]]] = number,
8380

81+
[123 --[[{]]] = function()
8482
local fields = {}
85-
if consume("^%s*}") then return fields end
83+
if skip("^%s*}()") then return fields end
8684

8785
repeat
88-
ptr = find(json, "%S", ptr) or ptr -- skip whitespace inline
89-
local key = string()
90-
if not key then
86+
ptr = match(json, "()%S", ptr) or ptr -- skip whitespace inline
87+
88+
if byte(json, ptr) ~= 34 --[["]] then
9189
error("Expected field for object at char " .. ptr)
9290
end
9391

94-
if not consume("^%s*:") then
92+
local start = ptr + 1
93+
local quot = find(json, '"', start, true)
94+
95+
local prev = quot - 1
96+
local key
97+
98+
if byte(json, prev) ~= 92 --[[\]] then
99+
ptr = quot + 1
100+
key = sub(json, start, prev)
101+
else
102+
key = slowstring()
103+
end
104+
105+
if not skip("^%s*:()") then
95106
error("Expected : to follow key for object at char " .. ptr)
96107
end
97108

@@ -102,38 +113,38 @@ local function decode(json --[[@param json string]]) ---@return any
102113
error("Expected value for field " .. key .. " at char " .. ptr)
103114
end
104115

105-
consume("^%s*,")
106-
until consume("^%s*}")
116+
ptr = match(json, "^%s*,()", ptr) or ptr
117+
until skip("^%s*}()")
107118

108119
return fields
109120
end,
110121

111-
["["] = function()
112-
ptr = ptr + 1 -- Already know we're at the [ from value()
113-
122+
[91 --[=[]]=]] = function()
114123
local values, nvalues = {}, 0
115-
if consume("^%s*%]") then return values end
124+
if skip("^%s*%]()") then return values end
116125

117126
repeat
118-
nvalues = nvalues + 1
119127
local value = value()
120128
if value ~= nil then
129+
nvalues = nvalues + 1
121130
values[nvalues] = value
122131
else
123-
error("Expected value for field #" .. nvalues + 1 .. " at char " .. ptr)
132+
error("Expected value for field #" .. nvalues .. " at char " .. ptr)
124133
end
125-
consume("^%s*,")
126-
until consume("^%s*%]")
134+
135+
ptr = match(json, "^%s*,()", ptr) or ptr
136+
until skip("^%s*%]()")
127137

128138
return values
129139
end,
130140

131-
[" "] = whitespace, ["\t"] = whitespace, ["\n"] = whitespace, ["\r"] = whitespace,
141+
[9 --[[\t]]] = whitespace, [10 --[[\n]]] = whitespace, [13 --[[\r]]] = whitespace, [32 --[[ ]]] = whitespace,
132142
}
133143

134144
function value()
135-
local p = peek[sub(json, ptr, ptr)]
145+
local p = peek[byte(json, ptr)]
136146
if p then
147+
ptr = ptr + 1
137148
return p()
138149
else
139150
error("Failed parsing at character " .. ptr)
@@ -183,12 +194,11 @@ function _encode(tbl --[[@param tbl table]], buffer --[[@param buffer table]], n
183194
buffer[nbuffer] = ","
184195
end
185196

186-
if len ~= 0 then
187-
buffer[nbuffer] = "]"
188-
else
197+
if len == 0 then -- no trailing comma to replace. need to increment ptr
189198
nbuffer = nbuffer + 1
190-
buffer[nbuffer] = "]"
191199
end
200+
201+
buffer[nbuffer] = "]"
192202
else
193203
nbuffer = nbuffer + 1
194204
buffer[nbuffer] = "{"
@@ -201,12 +211,11 @@ function _encode(tbl --[[@param tbl table]], buffer --[[@param buffer table]], n
201211
buffer[nbuffer] = ","
202212
end
203213

204-
if nbuffer ~= prev then
205-
buffer[nbuffer] = "}"
206-
else
214+
if nbuffer == prev then -- no trailing comma to replace. need to increment ptr
207215
nbuffer = nbuffer + 1
208-
buffer[nbuffer] = "}"
209216
end
217+
218+
buffer[nbuffer] = "}"
210219
end
211220

212221
return nbuffer

0 commit comments

Comments
 (0)