1local dat_obj = {}
2local match_key = nil
3
4local function dat_lexer(f, fname)
5    local line, err = f:read("*l")
6    local location = {line_no = 1, column = 1, fname = fname}
7    return function()
8        local tok = nil
9        while not tok do
10            if not line then
11                return nil
12            end
13            pre_space, tok, line = string.match(line, "^(%s*)(..-)([()]*%s.*)")
14            if tok and string.match(tok, "^\"") then
15                tok, line = string.match(tok..line, "^\"([^\"]-)\"(.*)")
16            elseif tok and string.match(tok, "^[()]") then
17                line = tok:sub(2) .. line
18                tok = tok:sub(1,1)
19            end
20            location.column = location.column  + #(pre_space or "")
21            tok_loc = {
22                line_no = location.line_no,
23                column = location.column,
24                fname = location.fname
25            }
26            if not line then
27                line = f:read("*l")
28                location.line_no = location.line_no + 1
29                location.column = 1
30            else
31                location.column = location.column + #tok
32            end
33        end
34        -- print(tok)
35        return tok, tok_loc
36    end
37end
38
39local function dat_parse_table(lexer, start_loc)
40    local res = {}
41    local state = "key"
42    local key = nil
43    for tok, loc in lexer do
44        if state == "key" then
45            if tok == ")" then
46                return res
47            elseif tok == "(" then
48                error(string.format(
49                    "%s:%d:%d: fatal error: Unexpected '(' instead of key",
50                    loc.fname,
51                    loc.line_no,
52                    loc.column
53                ))
54            else
55                key = tok
56                state = "value"
57            end
58        else
59            if tok == "(" then
60                res[key] = dat_parse_table(lexer, loc)
61            elseif tok == ")" then
62                error(string.format(
63                    "%s:%d:%d: fatal error: Unexpected ')' instead of value",
64                    loc.fname,
65                    loc.line_no,
66                    loc.column
67                ))
68            else
69                res[key] = tok
70            end
71            state = "key"
72        end
73    end
74    error(string.format(
75        "%s:%d:%d: fatal error: Missing ')' for '('",
76        start_loc.fname,
77        start_loc.line_no,
78        start_loc.column
79    ))
80end
81
82local function dat_parser(lexer)
83    local res = {}
84    local state = "key"
85    local key = nil
86    local skip = true
87    for tok, loc in lexer do
88        if state == "key" then
89            if tok == "game" then
90                skip = false
91            end
92            state = "value"
93        else
94            if tok == "(" then
95                local v = dat_parse_table(lexer, loc)
96                if not skip then
97                    table.insert(res, v)
98                    skip = true
99                end
100            else
101                error(string.format(
102                    "%s:%d:%d: fatal error: Expected '(' found '%s'",
103                    loc.fname,
104                    loc.line_no,
105                    loc.column,
106                    tok
107                ))
108            end
109            state = "key"
110        end
111    end
112    return res
113end
114
115local function unhex(s)
116    if not s then return nil end
117    return (s:gsub('..', function (c)
118        return string.char(tonumber(c, 16))
119    end))
120end
121
122local function get_match_key(mk, t)
123    for p in string.gmatch(mk, "(%w+)[.]?") do
124        if p == nil or t == nil then
125            error("Invalid match key '"..mk.."'")
126        end
127        t = t[p]
128    end
129    return t
130end
131
132table.update = function(a, b)
133    for k,v in pairs(b) do
134        a[k] = v
135    end
136end
137
138function init(...)
139    local args = {...}
140    table.remove(args, 1)
141    if #args == 0 then
142        assert(dat_path, "dat file argument is missing")
143    end
144
145    if #args > 1 then
146        match_key = table.remove(args, 1)
147    end
148
149    local dat_hash = {}
150    for _, dat_path in ipairs(args) do
151        local dat_file, err = io.open(dat_path, "r")
152        if err then
153            error("  could not open dat file '" .. dat_path .. "':" .. err)
154        end
155
156        print("  " .. dat_path)
157        local objs = dat_parser(dat_lexer(dat_file, dat_path))
158        dat_file:close()
159        for _, obj in pairs(objs) do
160            if match_key then
161                local mk = get_match_key(match_key, obj)
162                if mk == nil then
163                    error("  missing match key '" .. match_key .. "' in one of the entries")
164                end
165                if dat_hash[mk] == nil then
166                    dat_hash[mk] = {}
167                    table.insert(dat_obj, dat_hash[mk])
168                end
169                table.update(dat_hash[mk], obj)
170            else
171                table.insert(dat_obj, obj)
172            end
173        end
174    end
175end
176
177function get_value()
178    local t = table.remove(dat_obj)
179    if not t then
180        return
181    else
182        return {
183            name = t.name,
184            description = t.description,
185            rom_name = t.rom.name,
186            size = uint(tonumber(t.rom.size)),
187            users = uint(tonumber(t.users)),
188            releasemonth = uint(tonumber(t.releasemonth)),
189            releaseyear = uint(tonumber(t.releaseyear)),
190            rumble = uint(tonumber(t.rumble)),
191            analog = uint(tonumber(t.analog)),
192
193            famitsu_rating = uint(tonumber(t.famitsu_rating)),
194            edge_rating = uint(tonumber(t.edge_rating)),
195            edge_issue = uint(tonumber(t.edge_issue)),
196            edge_review = t.edge_review,
197
198            enhancement_hw = t.enhancement_hw,
199            barcode = t.barcode,
200            esrb_rating = t.esrb_rating,
201            elspa_rating = t.elspa_rating,
202            pegi_rating = t.pegi_rating,
203            cero_rating = t.cero_rating,
204            franchise   = t.franchise,
205
206            developer = t.developer,
207            publisher = t.publisher,
208            origin = t.origin,
209
210            crc = binary(unhex(t.rom.crc)),
211            md5 = binary(unhex(t.rom.md5)),
212            sha1 = binary(unhex(t.rom.sha1)),
213            serial = binary(t.serial or t.rom.serial),
214        }
215    end
216end
217