• 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 TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc