summaryrefslogtreecommitdiff
path: root/love2dToAPK/tools/tools/zbstudio-win/lualibs/luainspect/globals.lua
blob: 0730909139fecdc2b5ab8166184521bf0f2e783f (plain) (blame)
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
-- LuaInspect.globals - identifier scope analysis
-- Locates locals, globals, and their definitions.
--
-- (c) D.Manura, 2008-2010, MIT license.

-- based on http://lua-users.org/wiki/DetectingUndefinedVariables

local M = {}

--! require 'luainspect.typecheck' (context)

local LA = require "luainspect.ast"

local function definelocal(scope, name, ast)
  if scope[name] then
    scope[name].localmasked = true
    ast.localmasking = scope[name]
  end
  scope[name] = ast
  if name == '_' then ast.isignore = true end
end

-- Resolves scoping and usages of variable in AST.
-- Data Notes:
--   ast.localdefinition refers to lexically scoped definition of `Id node `ast`.
--     If ast.localdefinition == ast then ast is a "lexical definition".
--     If ast.localdefinition == nil, then variable is global.
--   ast.functionlevel is the number of functions the AST is contained in.
--     ast.functionlevel is defined iff ast is a lexical definition.
--   ast.isparam is true iff ast is a lexical definition and a function parameter.
--   ast.isset is true iff ast is a lexical definition and exists an assignment on it.
--   ast.isused is true iff ast is a lexical definition and has been referred to.
--   ast.isignore is true if local variable should be ignored (e.g. typically "_")
--   ast.localmasking - for a lexical definition, this is set to the lexical definition
--     this is masking (i.e. same name).  nil if not masking.
--   ast.localmasked - true iff lexical definition masked by another lexical definition.
--   ast.isfield is true iff `String node ast is used for field access on object,
--      e.g. x.y or x['y'].z
--   ast.previous - For `Index{o,s} or `Invoke{o,s,...}, s.previous == o
local function traverse(ast, scope, globals, level, functionlevel)
  scope = scope or {}

  local blockrecurse
  ast.level = level

  -- operations on walking down the AST
  if ast.tag == 'Local' then
    blockrecurse = 1
    -- note: apply new scope after processing values
  elseif ast.tag == 'Localrec' then
    local namelist_ast, valuelist_ast = ast[1], ast[2]
    for _,value_ast in ipairs(namelist_ast) do
      assert(value_ast.tag == 'Id')
      local name = value_ast[1]
      local parentscope = getmetatable(scope).__index
      definelocal(parentscope, name, value_ast)
      value_ast.localdefinition = value_ast
      value_ast.functionlevel = functionlevel
      value_ast.level = level+1
    end
    blockrecurse = 1
  elseif ast.tag == 'Id' then
    local name = ast[1]
    if scope[name] then
      ast.localdefinition = scope[name]
      ast.functionlevel = functionlevel
      scope[name].isused = true
    else -- global, do nothing
    end
  elseif ast.tag == 'Function' then
    local paramlist_ast, body_ast = ast[1], ast[2]
    functionlevel = functionlevel + 1
    for _,param_ast in ipairs(paramlist_ast) do
      local name = param_ast[1]
      assert(param_ast.tag == 'Id' or param_ast.tag == 'Dots')
      if param_ast.tag == 'Id' then
        definelocal(scope, name, param_ast)
        param_ast.localdefinition = param_ast
        param_ast.functionlevel = functionlevel
        param_ast.isparam = true
      end
      param_ast.level = level+1
    end
    blockrecurse = 1
  elseif ast.tag == 'Set' then
    local reflist_ast, valuelist_ast = ast[1], ast[2]
    for _,ref_ast in ipairs(reflist_ast) do
      if ref_ast.tag == 'Id' then
        local name = ref_ast[1]
        if scope[name] then
          scope[name].isset = true
        else
          if not globals[name] then
            globals[name] = {set=ref_ast}
          end
        end
      end
      ref_ast.level = level+1
    end
    --ENHANCE? We could differentiate assignments to x (which indicates that
    --  x is not const) and assignments to a member of x (which indicates that
    --  x is not a pointer to const) and assignments to any nested member of x
    --  (which indicates that x it not a transitive const).
  elseif ast.tag == 'Fornum' then
    blockrecurse = 1
  elseif ast.tag == 'Forin' then
    blockrecurse = 1
  end

  -- recurse (depth-first search down the AST)
  if ast.tag == 'Repeat' then
    local block_ast, cond_ast = ast[1], ast[2]
    local scope = scope
    for _,stat_ast in ipairs(block_ast) do
      scope = setmetatable({}, {__index = scope})
      traverse(stat_ast, scope, globals, level+1, functionlevel)
    end
    scope = setmetatable({}, {__index = scope})
    traverse(cond_ast, scope, globals, level+1, functionlevel)
  elseif ast.tag == 'Fornum' then
    local name_ast, block_ast = ast[1], ast[#ast]
    -- eval value list in current scope
    for i=2, #ast-1 do traverse(ast[i], scope, globals, level+1, functionlevel) end
    -- eval body in next scope
    local name = name_ast[1]
    definelocal(scope, name, name_ast)
    name_ast.localdefinition = name_ast
    name_ast.functionlevel = functionlevel
    traverse(block_ast, scope, globals, level+1, functionlevel)
  elseif ast.tag == 'Forin' then
    local namelist_ast, vallist_ast, block_ast = ast[1], ast[2], ast[3]
    -- eval value list in current scope
    traverse(vallist_ast, scope, globals, level+1, functionlevel)
    -- eval body in next scope
    for _,name_ast in ipairs(namelist_ast) do
      local name = name_ast[1]
      definelocal(scope, name, name_ast)
      name_ast.localdefinition = name_ast
      name_ast.functionlevel = functionlevel
      name_ast.level = level+1
    end
    traverse(block_ast, scope, globals, level+1, functionlevel)
  else -- normal
    for i,v in ipairs(ast) do
      if i ~= blockrecurse and type(v) == 'table' then
        local scope = setmetatable({}, {__index = scope})
        traverse(v, scope, globals, level+1, functionlevel)
      end
    end
  end

  -- operations on walking up the AST
  if ast.tag == 'Local' then
    -- Unlike Localrec, variables come into scope after evaluating values.
    local namelist_ast, valuelist_ast = ast[1], ast[2]
    for _,name_ast in ipairs(namelist_ast) do
      assert(name_ast.tag == 'Id')
      local name = name_ast[1]
      local parentscope = getmetatable(scope).__index
      definelocal(parentscope, name, name_ast)
      name_ast.localdefinition = name_ast
      name_ast.functionlevel = functionlevel
      name_ast.level = level+1
    end
  elseif ast.tag == 'Index' then
    if ast[2].tag == 'String' then
      ast[2].isfield = true
      ast[2].previous = ast[1]
    end
  elseif ast.tag == 'Invoke' then
    assert(ast[2].tag == 'String')
    ast[2].isfield = true
    ast[2].previous = ast[1]
  end
end

function M.globals(ast)
  -- Default list of defined variables.
  local scope = setmetatable({}, {})
  local globals = {}
  traverse(ast, scope, globals, 1, 1) -- Start check.

  return globals
end


-- Gets locals in scope of statement of block ast.  If isafter is true and ast is statement,
-- uses scope just after statement ast.
-- Assumes 'parent' attributes on ast are marked.
-- Returns table mapping name -> AST local definition.
function M.variables_in_scope(ast, isafter)
  local scope = {}
  local cast = ast
  while cast.parent do
    local midx = LA.ast_idx(cast.parent, cast)
    for idx=1,midx do
      local bast = cast.parent[idx]
      if bast.tag == 'Localrec' or bast.tag == 'Local' and (idx < midx or isafter) then
        local names_ast = bast[1]
        for bidx=1,#names_ast do
          local name_ast = names_ast[bidx]
          local name = name_ast[1]
          scope[name] = name_ast
        end
      elseif cast ~= ast and (bast.tag == 'For' or bast.tag == 'Forin' or bast.tag == 'Function') then
        local names_ast = bast[1]
        for bidx=1,#names_ast do
          local name_ast = names_ast[bidx]
          if name_ast.tag == 'Id' then  --Q: or maybe `Dots should be included
            local name = name_ast[1]
            scope[name] = name_ast
          end
        end
      end
    end
    cast = cast.parent
  end
  return scope
end


return M