-
Notifications
You must be signed in to change notification settings - Fork 22
/
asdl.lua
401 lines (386 loc) · 12.1 KB
/
asdl.lua
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
local List = require("terralist")
local Context = {}
function Context:__index(idx)
local d = self.definitions[idx] or self.namespaces[idx]
if d ~= nil then return d end
return getmetatable(self)[idx]
end
-- prepare lexer stuff
local tokens = "=|?*,(){}."
local keywords = { attributes = true, unique = true, module = true }
for i = 1,#tokens do
local t = tokens:sub(i,i)
keywords[t] = true
end
-- parser/lexer function
local function parseAll(text)
local pos = 1
local cur = nil -- current token
local value = nil -- current token value
local function err(what)
error(string.format("expected %s but found '%s' here:\n%s",what,value,
text:sub(1,pos).."<--## "..text:sub(pos+1,-1)))
end
local function skip(pattern)
local matched = text:match(pattern,pos)
pos = pos + #matched
if pos <= #text then
return false
end
cur,value = "EOF","EOF"
return true
end
local function next()
if skip("^%s*") then return end -- whitespace
local c = text:sub(pos,pos)
if c == "#" then -- comment
if skip("^[^\n]*\n") then return end
return next()
end
if keywords[c] then
cur,value,pos = c,c,pos+1
return
end
local ident = text:match("^[%a_][%a_%d]*",pos)
if not ident then
value = text:sub(pos,pos)
err("valid token")
end
cur,value = keywords[ident] and ident or "Ident", ident
pos = pos + #ident
end
local function nextif(kind)
if cur ~= kind then return false end
next()
return true
end
local function expect(kind)
if kind ~= cur then err(kind) end
local v = value
next()
return v
end
local namespace = ""
local function parseDefinedName()
return namespace..expect("Ident")
end
local function parseField()
local f = {}
f.type = expect("Ident")
while nextif(".") do
f.type = f.type .. "." .. expect("Ident")
end
f.namespace = namespace -- for resolving the symbol later
if nextif("?") then
f.optional = true
elseif nextif("*") then
f.list = true
end
f.name = expect("Ident")
return f
end
local function parseFields()
local fields = List()
expect("(")
if cur ~= ")" then
repeat
fields:insert(parseField())
until not nextif(",")
end
expect(")")
return fields
end
local function parseProduct()
local p = { kind = "product", fields = parseFields() }
p.unique = nextif("unique")
return p
end
local function parseConstructor()
local c = { name = parseDefinedName() }
if cur == "(" then
c.fields = parseFields()
end
c.unique = nextif("unique")
return c
end
local function parseSum()
local sum = { kind = "sum", constructors = List() }
repeat
sum.constructors:insert(parseConstructor())
until not nextif("|")
if nextif("attributes") then
local attributes = parseFields()
for i,ctor in ipairs(sum.constructors) do
ctor.fields = ctor.fields or List()
for i,a in ipairs(attributes) do
ctor.fields:insert(a)
end
end
end
return sum
end
local function parseType()
if cur == "(" then
return parseProduct()
else
return parseSum()
end
end
local definitions = List()
local parseDefinitions
local function parseModule()
expect("module")
local name = expect("Ident")
expect("{")
local oldnamespace = namespace
namespace = namespace..name.."."
parseDefinitions()
expect("}")
namespace = oldnamespace
end
local function parseDefinition()
local d = { name = parseDefinedName() }
expect("=")
d.type = parseType()
d.namespace = namespace
definitions:insert(d)
end
function parseDefinitions()
while cur ~= "EOF" and cur ~= "}" do
if cur == "module" then parseModule()
else parseDefinition() end
end
end
next()
parseDefinitions()
expect("EOF")
return definitions
end
local function checkbuiltin(t)
return function(v) return type(v) == t end
end
local function checkoptional(checkt)
return function(v) return v == nil or checkt(v) end
end
local function checklist(checkt)
return function(vs)
if not List:isclassof(vs) then return false end
for i,e in ipairs(vs) do
if not checkt(e) then return false,i end
end
return true
end
end
local valuekey,nilkey = {},{}
local function checkuniquelist(checkt,listcache)
return function(vs)
if not List:isclassof(vs) then return false end
local node = listcache
for i,e in ipairs(vs) do
if not checkt(e) then return false,i end
local next = node[e]
if not next then
next = {}
node[e] = next
end
node = next
end
local r = node[valuekey]
if not r then
r = vs
node[valuekey] = r
end
return true,r
end
end
local defaultchecks = {}
for i in string.gmatch("nil number string boolean table thread userdata cdata function","(%S+)") do
defaultchecks[i] = checkbuiltin(i)
end
defaultchecks["any"] = function() return true end
local function NewContext()
return setmetatable({ checks = setmetatable({},{__index = defaultchecks}), members = {}, list = {}, uniquelist = {}, listcache = {}, optional = {}, definitions = {}, namespaces = {}},Context)
end
function Context:GetCheckForField(unique,field)
local check = assert(self.checks[field.type],string.format("type not defined: %s",field.type))
local function get(tbl,ctor)
if not tbl[field.type] then
tbl[field.type] = ctor(check,self.listcache)
end
return tbl[field.type]
end
if field.list and not unique then
return get(self.list,checklist)
elseif field.list and unique then
return get(self.uniquelist,checkuniquelist)
elseif field.optional then
return get(self.optional,checkoptional)
end
return check
end
local function basename(name)
return name:match("([^.]*)$")
end
function Context:_SetDefinition(name,value)
local ctx = self.namespaces
for part in name:gmatch("([^.]*)%.") do
ctx[part] = ctx[part] or {}
ctx = ctx[part]
end
local base = basename(name)
ctx[base],self.definitions[name] = value,value
end
function Context:DeclareClass(name)
assert(not self.definitions[name], "class name already defined")
local m = {}
self:_SetDefinition(name,{ members = m })
self.checks[name] = function(v) return m[getmetatable(v) or false] or false end
end
local function reporterr(i,name,tn,v,ii)
local fmt = "bad argument #%d to '%s' expected '%s' but found '%s'"
if ii then
v,fmt = v[ii],fmt .. " at list index %d"
end
local err = string.format(fmt,i,name,tn,type(v),ii)
local mt = getmetatable(v)
if mt then
err = string.format("%s (metatable = %s)",err,tostring(mt))
end
error(err,3)
end
function Context:DefineClass(name,unique,fields)
local mt = {}
local class = self.definitions[name]
if fields then
for _,f in ipairs(fields) do
if f.namespace then -- resolve field type to fully qualified name
local fullname = f.namespace..f.type
if self.definitions[fullname] then
f.type = fullname
end
f.namespace = nil
end
end
class.__fields = fields -- for reflection in user-defined behavior
local names = List()
local checks = List()
local tns = List()
for i,f in ipairs(fields) do
names:insert(f.name)
tns:insert(f.list and f.type.."*" or f.type)
checks:insert(self:GetCheckForField(unique,f))
end
if unique then
function mt:__call(...)
local node,key = self,"cache"
local obj = {}
for i = 1, #names do
local v = select(i,...)
local c,l = checks[i](v)
if not c then
reporterr(i,name,tns[i],v,l)
end
v = l or v -- use memoized list if it exists
obj[names[i]] = v
local next = node[key]
if not next then
next = {}
node[key] = next
end
node,key = next,v
if key == nil then
key = nilkey
end
end
local next = node[key]
if not next then
next = setmetatable(obj,self)
if next.init then next:init() end
node[key] = next
end
return next
end
else
function mt:__call(...)
local obj = {}
for i = 1, #names do
local v = select(i,...)
local c,ii = checks[i](v)
if not c then
reporterr(i,name,tns[i],v,ii)
end
obj[names[i]] = v
end
local r = setmetatable(obj,self)
if r.init then r:init() end
return r
end
end
function class:__tostring()
local members = List()
for i,f in ipairs(fields) do
local v,r = self[f.name]
if f.list then
local elems = List()
for i,e in ipairs(self[f.name]) do
elems:insert(tostring(e))
end
r = "{"..elems:concat(",").."}"
else
r = tostring(v)
end
if not (f.optional and v == nil) then
members:insert(string.format("%s = %s",f.name,r))
end
end
return string.format("%s(%s)",name,members:concat(","))
end
else
function class:__tostring() return name end
end
function mt:__tostring() return string.format("Class(%s)",name) end
function mt:__newindex(k,v)
for c,_ in pairs(self.members) do
rawset(c,k,v)
end
end
local check = assert(self.checks[name])
function class:isclassof(obj)
return check(obj)
end
class.__index = class
class.members[class] = true
setmetatable(class,mt)
return class
end
function Context:Extern(name,istype)
self.checks[name] = istype
end
function Context:Define(text)
local defs = parseAll(text)
-- register all new type names
for i,d in ipairs(defs) do
self:DeclareClass(d.name)
if d.type.kind == "sum" then
for i,c in ipairs(d.type.constructors) do
self:DeclareClass(c.name)
end
end
end
for i,d in ipairs(defs) do
if d.type.kind == "sum" then
local parent = self:DefineClass(d.name,false,nil)
for i,c in ipairs(d.type.constructors) do
local child = self:DefineClass(c.name,c.unique,c.fields)
parent.members[child] = true --mark that any subclass is a member of its parent
child.kind = basename(c.name)
if not c.fields then --single value, just create it
self:_SetDefinition(c.name, setmetatable({},child))
end
end
else
self:DefineClass(d.name,d.type.unique,d.type.fields)
end
end
end
package.loaded["asdl"] = { NewContext = NewContext, List = List }