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
|
---@class vm
local vm = require 'vm.vm'
local guide = require 'parser.guide'
---@class parser.object
---@field package _tracer? vm.tracer
---@field package _casts? parser.object[]
---@class vm.tracer
---@field source parser.object
---@field assigns parser.object[]
---@field nodes table<parser.object, vm.node|false>
---@field main parser.object
---@field uri uri
local mt = {}
mt.__index = mt
---@return parser.object[]
function mt:getCasts()
local root = guide.getRoot(self.source)
if not root._casts then
root._casts = {}
local docs = root.docs
for _, doc in ipairs(docs) do
if doc.type == 'doc.cast' and doc.loc then
root._casts[#root._casts+1] = doc
end
end
end
return root._casts
end
---@param obj parser.object
---@param mark table
function mt:collectBlock(obj, mark)
while true do
obj = obj.parent
if mark[obj] then
return
end
if not guide.isBlockType(obj) then
return
end
if obj == self.main then
return
end
mark[obj] = true
self.assigns[#self.assigns+1] = obj
end
end
function mt:collectLocal()
local startPos = self.source.start
local finishPos = 0
local mark = {}
self.assigns[#self.assigns+1] = self.source
for _, obj in ipairs(self.source.ref) do
if obj.type == 'setlocal' then
self.assigns[#self.assigns+1] = obj
self:collectBlock(obj, mark)
end
end
local casts = self:getCasts()
for _, cast in ipairs(casts) do
if cast.loc[1] == self.source[1]
and cast.start > startPos
and cast.finish < finishPos
and guide.getLocal(self.source, self.source[1], cast.start) == self.source then
self.assigns[#self.assigns+1] = cast
end
end
table.sort(self.assigns, function (a, b)
return a.start < b.start
end)
end
---@param block parser.object
---@param pos integer
---@return parser.object?
function mt:getLastAssign(block, pos)
if not block then
return nil
end
local assign
for _, obj in ipairs(self.assigns) do
if obj.start >= pos then
break
end
local objBlock = guide.getParentBlock(obj)
if not objBlock then
break
end
if objBlock == block then
assign = obj
end
end
return assign
end
---@param filter parser.object
---@param node vm.node?
---@return vm.node
function mt:narrowByFilter(filter, node)
if not node then
node = vm.createNode()
end
if filter.type == 'filter' then
node = self:narrowByFilter(filter.exp, node)
return node
end
if filter.type == 'getlocal' then
if filter.node == self.source then
node = node:copy()
node:removeOptional()
end
return node
end
return node
end
---@param source parser.object
---@return vm.node?
function mt:calcNode(source)
if source.type == 'getlocal' then
return nil
end
if source.type == 'local' then
if source ~= self.source then
return nil
end
end
if source.type == 'setlocal' then
if source.node ~= self.source then
return nil
end
end
if guide.isSet(source) then
local node = vm.compileNode(source)
return node
end
if source.type == 'do' then
local lastAssign = self:getLastAssign(source, source.finish)
return self:getNode(lastAssign or source.parent)
end
if source.type == 'ifblock' then
local currentNode = self:getNode(source.parent)
local narrowedNode = self:narrowByFilter(source.filter, currentNode)
return narrowedNode
end
if source.type == 'filter' then
local parent = source.parent
---@type parser.object
local outBlock
if parent.type == 'ifblock' then
outBlock = parent.parent.parent
local lastAssign = self:getLastAssign(outBlock, parent.start)
return self:getNode(lastAssign or source.parent)
elseif parent.type == 'elseifblock' then
outBlock = parent.parent.parent
local lastAssign = self:getLastAssign(outBlock, parent.start)
return self:getNode(lastAssign or source.parent)
elseif parent.type == 'while' then
outBlock = parent.parent
local lastAssign = self:getLastAssign(outBlock, parent.start)
return self:getNode(lastAssign or source.parent)
elseif parent.type == 'repeat' then
outBlock = parent.parent
local lastAssign = self:getLastAssign(outBlock, parent.start)
return self:getNode(lastAssign or source.parent)
end
assert(outBlock, parent.type)
end
return nil
end
---@param source parser.object
---@return vm.node?
function mt:getNode(source)
if self.nodes[source] ~= nil then
return self.nodes[source] or nil
end
local parentBlock = guide.getParentBlock(source)
if not parentBlock then
self.nodes[source] = false
return nil
end
if source == self.main then
self.nodes[source] = false
return nil
end
local node = self:calcNode(source)
if node then
self.nodes[source] = node
return node
end
local lastAssign = self:getLastAssign(parentBlock, source.start)
local parentNode = self:getNode(lastAssign or source.parent)
self.nodes[source] = parentNode or false
return parentNode
end
---@param source parser.object
---@return vm.tracer?
local function createTracer(source)
if source._tracer then
return source._tracer
end
local main = guide.getParentBlock(source)
if not main then
return nil
end
local tracer = setmetatable({
source = source,
assigns = {},
nodes = {},
main = main,
uri = guide.getUri(source),
}, mt)
source._tracer = tracer
tracer:collectLocal()
return tracer
end
---@param source parser.object
---@return vm.node?
function vm.traceNode(source)
local loc
if source.type == 'getlocal'
or source.type == 'setlocal' then
loc = source.node
end
local tracer = createTracer(loc)
if not tracer then
return nil
end
local node = tracer:getNode(source)
return node
end
|