• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

andremm / lua-parser / #8

07 Jan 2026 04:52PM UTC coverage: 97.719% (-0.7%) from 98.416%
#8

push

github

andremm
fixing ci syntax

2099 of 2148 relevant lines covered (97.72%)

23.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.4
/lua-parser/validator.lua
1
--[[
2
This module impements a validator for the AST
3
]]
4
local scope = require "lua-parser.scope"
1✔
5

6
local lineno = scope.lineno
1✔
7
local new_scope, end_scope = scope.new_scope, scope.end_scope
1✔
8
local new_function, end_function = scope.new_function, scope.end_function
1✔
9
local begin_loop, end_loop = scope.begin_loop, scope.end_loop
1✔
10
local insideloop = scope.insideloop
1✔
11

12
-- creates an error message for the input string
13
local function syntaxerror (errorinfo, pos, msg)
14
  local l, c = lineno(errorinfo.subject, pos)
225✔
15
  local error_msg = "%s:%d:%d: syntax error, %s"
225✔
16
  return string.format(error_msg, errorinfo.filename, l, c, msg)
225✔
17
end
18

19
local function exist_label (env, scope, stm)
20
  local l = stm[1]
11✔
21
  for s=scope, 0, -1 do
24✔
22
    if env[s]["label"][l] then return true end
21✔
23
  end
24
  return false
3✔
25
end
26

27
local function set_label (env, label, pos)
28
  local scope = env.scope
19✔
29
  local l = env[scope]["label"][label]
19✔
30
  if not l then
19✔
31
    env[scope]["label"][label] = { name = label, pos = pos }
18✔
32
    return true
18✔
33
  else
34
    local msg = "label '%s' already defined at line %d"
1✔
35
    local line = lineno(env.errorinfo.subject, l.pos)
1✔
36
    msg = string.format(msg, label, line)
1✔
37
    return nil, syntaxerror(env.errorinfo, pos, msg)
1✔
38
  end
39
end
40

41
local function set_pending_goto (env, stm)
42
  local scope = env.scope
11✔
43
  table.insert(env[scope]["goto"], stm)
11✔
44
  return true
11✔
45
end
46

47
local function verify_pending_gotos (env)
48
  for s=env.maxscope, 0, -1 do
264✔
49
    for k, v in ipairs(env[s]["goto"]) do
174✔
50
      if not exist_label(env, s, v) then
11✔
51
        local msg = "no visible label '%s' for <goto>"
3✔
52
        msg = string.format(msg, v[1])
3✔
53
        return nil, syntaxerror(env.errorinfo, v.pos, msg)
3✔
54
      end
55
    end
56
  end
57
  return true
98✔
58
end
59

60
local function set_vararg (env, is_vararg)
61
  env["function"][env.fscope].is_vararg = is_vararg
131✔
62
end
63

64
local traverse_stm, traverse_exp, traverse_var
65
local traverse_block, traverse_explist, traverse_varlist, traverse_parlist
66

67
function traverse_parlist (env, parlist)
×
68
  local len = #parlist
22✔
69
  local is_vararg = false
22✔
70
  if len > 0 and parlist[len].tag == "Dots" then
22✔
71
    is_vararg = true
11✔
72
  end
73
  set_vararg(env, is_vararg)
22✔
74
  return true
22✔
75
end
76

77
local function traverse_function (env, exp)
78
  new_function(env)
22✔
79
  new_scope(env)
22✔
80
  local status, msg = traverse_parlist(env, exp[1])
22✔
81
  if not status then return status, msg end
22✔
82
  status, msg = traverse_block(env, exp[2])
22✔
83
  if not status then return status, msg end
22✔
84
  end_scope(env)
16✔
85
  end_function(env)
16✔
86
  return true
16✔
87
end
88

89
local function traverse_op (env, exp)
90
  local status, msg = traverse_exp(env, exp[2])
45✔
91
  if not status then return status, msg end
45✔
92
  if exp[3] then
45✔
93
    status, msg = traverse_exp(env, exp[3])
43✔
94
    if not status then return status, msg end
43✔
95
  end
96
  return true
45✔
97
end
98

99
local function traverse_paren (env, exp)
100
  local status, msg = traverse_exp(env, exp[1])
1✔
101
  if not status then return status, msg end
1✔
102
  return true
1✔
103
end
104

105
local function traverse_table (env, fieldlist)
106
  for k, v in ipairs(fieldlist) do
129✔
107
    local tag = v.tag
68✔
108
    if tag == "Pair" then
68✔
109
      local status, msg = traverse_exp(env, v[1])
8✔
110
      if not status then return status, msg end
8✔
111
      status, msg = traverse_exp(env, v[2])
8✔
112
      if not status then return status, msg end
8✔
113
    else
114
      local status, msg = traverse_exp(env, v)
60✔
115
      if not status then return status, msg end
60✔
116
    end
117
  end
118
  return true
61✔
119
end
120

121
local function traverse_vararg (env, exp)
122
  if not env["function"][env.fscope].is_vararg then
13✔
123
    local msg = "cannot use '...' outside a vararg function"
4✔
124
    return nil, syntaxerror(env.errorinfo, exp.pos, msg)
4✔
125
  end
126
  return true
9✔
127
end
128

129
local function traverse_call (env, call)
130
  local status, msg = traverse_exp(env, call[1])
9✔
131
  if not status then return status, msg end
9✔
132
  for i=2, #call do
15✔
133
    status, msg = traverse_exp(env, call[i])
6✔
134
    if not status then return status, msg end
6✔
135
  end
136
  return true
9✔
137
end
138

139
local function traverse_invoke (env, invoke)
140
  local status, msg = traverse_exp(env, invoke[1])
1✔
141
  if not status then return status, msg end
1✔
142
  for i=3, #invoke do
2✔
143
    status, msg = traverse_exp(env, invoke[i])
1✔
144
    if not status then return status, msg end
1✔
145
  end
146
  return true
1✔
147
end
148

149
local function traverse_assignment (env, stm)
150
  local status, msg = traverse_varlist(env, stm[1])
69✔
151
  if not status then return status, msg end
69✔
152
  status, msg = traverse_explist(env, stm[2])
69✔
153
  if not status then return status, msg end
69✔
154
  return true
65✔
155
end
156

157
local function traverse_break (env, stm)
158
  if not insideloop(env) then
10✔
159
    local msg = "<break> not inside a loop"
3✔
160
    return nil, syntaxerror(env.errorinfo, stm.pos, msg)
3✔
161
  end
162
  return true
7✔
163
end
164

165
local function traverse_forin (env, stm)
166
  begin_loop(env)
1✔
167
  new_scope(env)
1✔
168
  local status, msg = traverse_explist(env, stm[2])
1✔
169
  if not status then return status, msg end
1✔
170
  status, msg = traverse_block(env, stm[3])
1✔
171
  if not status then return status, msg end
1✔
172
  end_scope(env)
1✔
173
  end_loop(env)
1✔
174
  return true
1✔
175
end
176

177
local function traverse_fornum (env, stm)
178
  local status, msg
179
  begin_loop(env)
3✔
180
  new_scope(env)
3✔
181
  status, msg = traverse_exp(env, stm[2])
3✔
182
  if not status then return status, msg end
3✔
183
  status, msg = traverse_exp(env, stm[3])
3✔
184
  if not status then return status, msg end
3✔
185
  if stm[5] then
3✔
186
    status, msg = traverse_exp(env, stm[4])
1✔
187
    if not status then return status, msg end
1✔
188
    status, msg = traverse_block(env, stm[5])
1✔
189
    if not status then return status, msg end
1✔
190
  else
191
    status, msg = traverse_block(env, stm[4])
2✔
192
    if not status then return status, msg end
2✔
193
  end
194
  end_scope(env)
3✔
195
  end_loop(env)
3✔
196
  return true
3✔
197
end
198

199
local function traverse_goto (env, stm)
200
  local status, msg = set_pending_goto(env, stm)
11✔
201
  if not status then return status, msg end
11✔
202
  return true
11✔
203
end
204

205
local function traverse_if (env, stm)
206
  local len = #stm
8✔
207
  if len % 2 == 0 then
8✔
208
    for i=1, len, 2 do
12✔
209
      local status, msg = traverse_exp(env, stm[i])
8✔
210
      if not status then return status, msg end
8✔
211
      status, msg = traverse_block(env, stm[i+1])
8✔
212
      if not status then return status, msg end
8✔
213
    end
214
  else
215
    for i=1, len-1, 2 do
7✔
216
      local status, msg = traverse_exp(env, stm[i])
4✔
217
      if not status then return status, msg end
4✔
218
      status, msg = traverse_block(env, stm[i+1])
4✔
219
      if not status then return status, msg end
4✔
220
    end
221
    local status, msg = traverse_block(env, stm[len])
3✔
222
    if not status then return status, msg end
3✔
223
  end
224
  return true
7✔
225
end
226

227
local function traverse_label (env, stm)
228
  local status, msg = set_label(env, stm[1], stm.pos)
19✔
229
  if not status then return status, msg end
19✔
230
  return true
18✔
231
end
232

233
local function traverse_let (env, stm)
234
  local status, msg = traverse_explist(env, stm[2])
18✔
235
  if not status then return status, msg end
18✔
236
  return true
17✔
237
end
238

239
local function traverse_letrec (env, stm)
240
  local status, msg = traverse_exp(env, stm[2][1])
5✔
241
  if not status then return status, msg end
5✔
242
  return true
4✔
243
end
244

245
local function traverse_repeat (env, stm)
246
  begin_loop(env)
2✔
247
  local status, msg = traverse_block(env, stm[1])
2✔
248
  if not status then return status, msg end
2✔
249
  status, msg = traverse_exp(env, stm[2])
2✔
250
  if not status then return status, msg end
2✔
251
  end_loop(env)
2✔
252
  return true
2✔
253
end
254

255
local function traverse_return (env, stm)
256
  local status, msg = traverse_explist(env, stm)
32✔
257
  if not status then return status, msg end
32✔
258
  return true
28✔
259
end
260

261
local function traverse_while (env, stm)
262
  begin_loop(env)
5✔
263
  local status, msg = traverse_exp(env, stm[1])
5✔
264
  if not status then return status, msg end
5✔
265
  status, msg = traverse_block(env, stm[2])
5✔
266
  if not status then return status, msg end
5✔
267
  end_loop(env)
5✔
268
  return true
5✔
269
end
270

271
function traverse_var (env, var)
×
272
  local tag = var.tag
129✔
273
  if tag == "Id" then -- `Id{ <string> }
129✔
274
    return true
117✔
275
  elseif tag == "Index" then -- `Index{ expr expr }
12✔
276
    local status, msg = traverse_exp(env, var[1])
12✔
277
    if not status then return status, msg end
12✔
278
    status, msg = traverse_exp(env, var[2])
12✔
279
    if not status then return status, msg end
12✔
280
    return true
12✔
281
  else
282
    error("expecting a variable, but got a " .. tag)
×
283
  end
284
end
285

286
function traverse_varlist (env, varlist)
×
287
  for k, v in ipairs(varlist) do
145✔
288
    local status, msg = traverse_var(env, v)
76✔
289
    if not status then return status, msg end
76✔
290
  end
291
  return true
69✔
292
end
293

294
function traverse_exp (env, exp)
×
295
  local tag = exp.tag
363✔
296
  if tag == "Nil" or
363✔
297
     tag == "Boolean" or -- `Boolean{ <boolean> }
362✔
298
     tag == "Number" or -- `Number{ <number> }
360✔
299
     tag == "String" then -- `String{ <string> }
235✔
300
    return true
161✔
301
  elseif tag == "Dots" then
202✔
302
    return traverse_vararg(env, exp)
13✔
303
  elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block }
189✔
304
    return traverse_function(env, exp)
22✔
305
  elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
167✔
306
    return traverse_table(env, exp)
61✔
307
  elseif tag == "Op" then -- `Op{ opid expr expr? }
106✔
308
    return traverse_op(env, exp)
45✔
309
  elseif tag == "Paren" then -- `Paren{ expr }
61✔
310
    return traverse_paren(env, exp)
1✔
311
  elseif tag == "Call" then -- `Call{ expr expr* }
60✔
312
    return traverse_call(env, exp)
6✔
313
  elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
54✔
314
    return traverse_invoke(env, exp)
1✔
315
  elseif tag == "Id" or -- `Id{ <string> }
53✔
316
         tag == "Index" then -- `Index{ expr expr }
6✔
317
    return traverse_var(env, exp)
53✔
318
  else
319
    error("expecting an expression, but got a " .. tag)
×
320
  end
321
end
322

323
function traverse_explist (env, explist)
×
324
  for k, v in ipairs(explist) do
237✔
325
    local status, msg = traverse_exp(env, v)
126✔
326
    if not status then return status, msg end
126✔
327
  end
328
  return true
111✔
329
end
330

331
function traverse_stm (env, stm)
×
332
  local tag = stm.tag
201✔
333
  if tag == "Do" then -- `Do{ stat* }
201✔
334
    return traverse_block(env, stm)
15✔
335
  elseif tag == "Set" then -- `Set{ {lhs+} {expr+} }
186✔
336
    return traverse_assignment(env, stm)
69✔
337
  elseif tag == "While" then -- `While{ expr block }
117✔
338
    return traverse_while(env, stm)
5✔
339
  elseif tag == "Repeat" then -- `Repeat{ block expr }
112✔
340
    return traverse_repeat(env, stm)
2✔
341
  elseif tag == "If" then -- `If{ (expr block)+ block? }
110✔
342
    return traverse_if(env, stm)
8✔
343
  elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block }
102✔
344
    return traverse_fornum(env, stm)
3✔
345
  elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
99✔
346
    return traverse_forin(env, stm)
1✔
347
  elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
98✔
348
    return traverse_let(env, stm)
18✔
349
  elseif tag == "Localrec" then -- `Localrec{ ident expr }
80✔
350
    return traverse_letrec(env, stm)
5✔
351
  elseif tag == "Goto" then -- `Goto{ <string> }
75✔
352
    return traverse_goto(env, stm)
11✔
353
  elseif tag == "Label" then -- `Label{ <string> }
64✔
354
    return traverse_label(env, stm)
19✔
355
  elseif tag == "Return" then -- `Return{ <expr>* }
45✔
356
    return traverse_return(env, stm)
32✔
357
  elseif tag == "Break" then
13✔
358
    return traverse_break(env, stm)
10✔
359
  elseif tag == "Call" then -- `Call{ expr expr* }
3✔
360
    return traverse_call(env, stm)
3✔
361
  elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
×
362
    return traverse_invoke(env, stm)
×
363
  else
364
    error("expecting a statement, but got a " .. tag)
×
365
  end
366
end
367

368
function traverse_block (env, block)
×
369
  local l = {}
172✔
370
  new_scope(env)
172✔
371
  for k, v in ipairs(block) do
358✔
372
    local status, msg = traverse_stm(env, v)
201✔
373
    if not status then return status, msg end
201✔
374
  end
375
  end_scope(env)
157✔
376
  return true
157✔
377
end
378

379

380
local function traverse (ast, errorinfo)
381
  assert(type(ast) == "table")
109✔
382
  assert(type(errorinfo) == "table")
109✔
383
  local env = { errorinfo = errorinfo, ["function"] = {} }
109✔
384
  new_function(env)
109✔
385
  set_vararg(env, true)
109✔
386
  local status, msg = traverse_block(env, ast)
109✔
387
  if not status then return status, msg end
109✔
388
  end_function(env)
101✔
389
  status, msg = verify_pending_gotos(env)
101✔
390
  if not status then return status, msg end
101✔
391
  return ast
98✔
392
end
393

394
return { validate = traverse, syntaxerror = syntaxerror }
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc