forked from gvx/Smallfolk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmallfolk.lua
More file actions
204 lines (192 loc) · 4.94 KB
/
smallfolk.lua
File metadata and controls
204 lines (192 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
local M = {}
Smallfolk = M
local expect_object, dump_object
local error, tostring, pairs, type, floor, huge, concat = error, tostring, pairs, type, math.floor, math.huge, table.concat
local dump_type = {}
function dump_type:string(memo, acc)
local nacc = #acc
acc[nacc + 1] = '"'
acc[nacc + 2] = self:gsub('"', '""')
acc[nacc + 3] = '"'
end
function dump_type:number(memo, acc)
acc[#acc + 1] = ("%.17g"):format(self)
end
function dump_type:table(memo, acc)
if memo[self] then
error("table inside itself")
end
memo[self] = true
acc[#acc + 1] = '{'
local nself = #self
for i = 1, nself do -- don't use ipairs here, we need the gaps
dump_object(self[i], memo, acc)
acc[#acc + 1] = ','
end
for k, v in pairs(self) do
if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > nself then
if type(k) == "table" then
error("table key cannot be a table")
end
dump_object(k, memo, acc)
acc[#acc + 1] = ':'
dump_object(v, memo, acc)
acc[#acc + 1] = ','
end
end
acc[#acc] = acc[#acc] == '{' and '{}' or '}'
memo[self] = nil
end
function dump_object(object, memo, acc)
if object == true then
acc[#acc + 1] = 't'
elseif object == false then
acc[#acc + 1] = 'f'
elseif object == nil then
acc[#acc + 1] = 'n'
elseif object ~= object then
if (''..object):sub(1,1) == '-' then
acc[#acc + 1] = 'N'
else
acc[#acc + 1] = 'Q'
end
elseif object == huge then
acc[#acc + 1] = 'I'
elseif object == -huge then
acc[#acc + 1] = 'i'
else
local t = type(object)
if not dump_type[t] then
error('cannot dump type ' .. t)
end
dump_type[t](object, memo, acc)
end
end
function M.dumps(object)
local memo = {}
local acc = {}
dump_object(object, memo, acc)
return concat(acc)
end
local function invalid(i)
error('invalid input at position ' .. i)
end
local nonzero_digit = {['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
local is_digit = {['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
local function expect_number(string, start)
local i = start
local head = string:sub(i, i)
if head == '-' then
i = i + 1
head = string:sub(i, i)
end
if nonzero_digit[head] then
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
elseif head == '0' then
i = i + 1
head = string:sub(i, i)
else
invalid(i)
end
if head == '.' then
local oldi = i
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
if i == oldi + 1 then
invalid(i)
end
end
if head == 'e' or head == 'E' then
i = i + 1
head = string:sub(i, i)
if head == '+' or head == '-' then
i = i + 1
head = string:sub(i, i)
end
if not is_digit[head] then
invalid(i)
end
repeat
i = i + 1
head = string:sub(i, i)
until not is_digit[head]
end
return tonumber(string:sub(start, i - 1)), i
end
local expect_object_head = {
t = function(string, i) return true, i end,
f = function(string, i) return false, i end,
n = function(string, i) return nil, i end,
Q = function(string, i) return -(0/0), i end,
N = function(string, i) return 0/0, i end,
I = function(string, i) return 1/0, i end,
i = function(string, i) return -1/0, i end,
['"'] = function(string, i)
local nexti = i - 1
repeat
nexti = string:find('"', nexti + 1, true) + 1
until string:sub(nexti, nexti) ~= '"'
return string:sub(i, nexti - 2):gsub('""', '"'), nexti
end,
['0'] = function(string, i)
return expect_number(string, i - 1)
end,
['{'] = function(string, i)
local nt, k, v = {}
local j = 1
if string:sub(i, i) == '}' then
return nt, i + 1
end
while true do
k, i = expect_object(string, i)
if string:sub(i, i) == ':' then
if k ~= k or k == nil or type(k) == "table" then
error("Table key was nil or nan or table")
end
v, i = expect_object(string, i + 1)
nt[k] = v
else
nt[j] = k
j = j + 1
end
local head = string:sub(i, i)
if head == ',' then
i = i + 1
elseif head == '}' then
return nt, i + 1
else
invalid(i)
end
end
end,
}
expect_object_head['1'] = expect_object_head['0']
expect_object_head['2'] = expect_object_head['0']
expect_object_head['3'] = expect_object_head['0']
expect_object_head['4'] = expect_object_head['0']
expect_object_head['5'] = expect_object_head['0']
expect_object_head['6'] = expect_object_head['0']
expect_object_head['7'] = expect_object_head['0']
expect_object_head['8'] = expect_object_head['0']
expect_object_head['9'] = expect_object_head['0']
expect_object_head['-'] = expect_object_head['0']
expect_object_head['.'] = expect_object_head['0']
expect_object = function(string, i)
local head = string:sub(i, i)
if expect_object_head[head] then
return expect_object_head[head](string, i + 1)
end
invalid(i)
end
function M.loads(string, maxsize)
if #string > (maxsize or 10000) then
error 'input too large'
end
return (expect_object(string, 1, {}))
end
return M