summaryrefslogtreecommitdiff
path: root/server/src/core/definition.lua
blob: c2a29dae1de1503094d7087ff3511c94ac4c5c05 (plain)
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
local findSource = require 'core.find_source'
local Mode

local function parseValueSimily(callback, vm, source)
    local key = source[1]
    if not key then
        return nil
    end
    vm:eachSource(function (other)
        if other == source then
            goto CONTINUE
        end
        if      other[1] == key
            and not other:bindLocal()
            and other:bindValue()
            and source:bindValue() ~= other:bindValue()
        then
            if Mode == 'definition' then
                if other:action() == 'set' then
                    callback(other)
                end
            elseif Mode == 'reference' then
                if other:action() == 'set' or other:action() == 'get' then
                    callback(other)
                end
            end
        end
        :: CONTINUE ::
    end)
end

local function parseLocal(callback, vm, source)
    ---@type Local
    local loc = source:bindLocal()
    callback(loc:getSource())
    loc:eachInfo(function (info, src)
        if Mode == 'definition' then
            if info.type == 'set' or info.type == 'local' then
                if vm.uri == src:getUri() then
                    if source.id >= src.id then
                        callback(src)
                    end
                end
            end
        elseif Mode == 'reference' then
            if info.type == 'set' or info.type == 'local' or info.type == 'return' or info.type == 'get' then
                callback(src)
            end
        end
    end)
end

local function parseValueByValue(callback, vm, source, value)
    local mark = { [vm] = true }
    local list = {}
    for _ = 1, 5 do
        value:eachInfo(function (info, src)
            if Mode == 'definition' then
                if info.type == 'set' or info.type == 'local' then
                    if vm.uri == src:getUri() then
                        if source.id >= src.id then
                            callback(src)
                        end
                    end
                end
                if info.type == 'return' then
                    if (src.type ~= 'simple' or src[#src].type == 'call')
                    and src.type ~= 'name'
                    then
                        callback(src)
                    end
                    if vm.lsp then
                        local destVM = vm.lsp:getVM(src:getUri())
                        if destVM and not mark[destVM] then
                            mark[destVM] = true
                            list[#list+1] = { destVM, src }
                        end
                    end
                end
            elseif Mode == 'reference' then
                if info.type == 'set' or info.type == 'local' or info.type == 'return' or info.type == 'get' then
                    callback(src)
                end
            end
        end)
        local nextData = table.remove(list, 1)
        if nextData then
            vm, source = nextData[1], nextData[2]
        end
    end
end

local function parseValue(callback, vm, source)
    local value = source:bindValue()
    local isGlobal
    if value then
        isGlobal = value:isGlobal()
        parseValueByValue(callback, vm, source, value)
        local emmy = value:getEmmy()
        if emmy and emmy.type == 'emmy.type' then
            ---@type EmmyType
            local emmyType = emmy
            emmyType:eachClass(function (class)
                if class and class:getValue() then
                    local emmyVM = vm
                    if vm.lsp then
                        local destVM = vm.lsp:getVM(class:getSource():getUri())
                        if destVM then
                            emmyVM = destVM
                        end
                    end
                    parseValueByValue(callback, emmyVM, class:getValue():getSource(), class:getValue())
                end
            end)
        end
    end
    local parent = source:get 'parent'
    for _ = 1, 3 do
        if parent then
            local ok = parent:eachInfo(function (info, src)
                if Mode == 'definition' then
                    if info.type == 'set child' and info[1] == source[1] then
                        callback(src)
                        return true
                    end
                elseif Mode == 'reference' then
                    if (info.type == 'set child' or info.type == 'get child') and info[1] == source[1] then
                        callback(src)
                        return true
                    end
                end
            end)
            if ok then
                break
            end
            parent = parent:getMetaMethod('__index')
        end
    end
    return isGlobal
end

local function parseLabel(callback, vm, label)
    label:eachInfo(function (info, src)
        if Mode == 'definition' then
            if info.type == 'set' then
                callback(src)
            end
        elseif Mode == 'reference' then
            if info.type == 'set' or info.type == 'get' then
                callback(src)
            end
        end
    end)
end

local function jumpUri(callback, vm, source)
    local uri = source:get 'target uri'
    callback {
        start = 0,
        finish = 0,
        uri = uri
    }
end

local function parseClass(callback, vm, source)
    local className = source:get 'emmy class'
    vm.emmyMgr:eachClass(className, function (class)
        if Mode == 'definition' then
            if class.type == 'emmy.class' or class.type == 'emmy.alias' then
                local src = class:getSource()
                callback(src)
            end
        elseif Mode == 'reference' then
            if class.type == 'emmy.class' or class.type == 'emmy.alias' or class.type == 'emmy.typeUnit' then
                local src = class:getSource()
                callback(src)
            end
        end
    end)
end

local function parseSee(callback, vm, source)
    local see = source:get 'emmy see'
    local className = see[1][1]
    local childName = see[2][1]
    vm.emmyMgr:eachClass(className, function (class)
        ---@type value
        local value = class:getValue()
        local child = value:getChild(childName)
        parseValueByValue(callback, vm, source, child)
    end)
end

local function parseFunction(callback, vm, source)
    if Mode == 'definition' then
        callback(source:bindFunction():getSource())
        source:bindFunction():eachInfo(function (info, src)
            if info.type == 'set' or info.type == 'local' then
                if vm.uri == src:getUri() then
                    if source.id >= src.id then
                        callback(src)
                    end
                else
                    callback(src)
                end
            end
        end)
    elseif Mode == 'reference' then
        callback(source:bindFunction():getSource())
        source:bindFunction():eachInfo(function (info, src)
            if info.type == 'set' or info.type == 'local' or info.type == 'get' then
                callback(src)
            end
        end)
    end
end

local function makeList(source)
    local list = {}
    local mark = {}
    return list, function (src)
        if mark[src] then
            return
        end
        mark[src] = true
        list[#list+1] = {
            src.start,
            src.finish,
            src.uri
        }
    end
end

return function (vm, pos, mode)
    local filter = {
        ['name']           = true,
        ['string']         = true,
        ['number']         = true,
        ['boolean']        = true,
        ['label']          = true,
        ['goto']           = true,
        ['function']       = true,
        ['...']            = true,
        ['emmyName']       = true,
        ['emmyIncomplete'] = true,
    }
    local source = findSource(vm, pos, filter)
    if not source then
        return nil
    end
    Mode = mode
    local list, callback = makeList(source)
    local isGlobal
    if source:bindLocal() then
        parseLocal(callback, vm, source)
    end
    if source:bindValue() then
        isGlobal = parseValue(callback, vm, source)
    end
    if source:bindLabel() then
        parseLabel(callback, vm, source:bindLabel())
    end
    if source:bindFunction() then
        parseFunction(callback, vm, source)
    end
    if source:get 'target uri' then
        jumpUri(callback, vm, source)
    end
    if source:get 'in index' then
        isGlobal = parseValue(callback, vm, source)
    end
    if source:get 'emmy class' then
        parseClass(callback, vm, source)
    end
    if source:get 'emmy see' then
        parseSee(callback, vm, source)
    end

    if #list == 0 then
        parseValueSimily(callback, vm, source)
    end

    return list, isGlobal
end