diff options
author | Paul Emmerich <tandanu@deadlybossmods.com> | 2024-02-01 00:11:12 +0100 |
---|---|---|
committer | Paul Emmerich <tandanu@deadlybossmods.com> | 2024-02-01 00:13:49 +0100 |
commit | 05172fd47c2b41240a0cccbe9511c41c9918a0d2 (patch) | |
tree | bb56430effeb18e5ac01059214269400de9f08fc /script/cli/visualize.lua | |
parent | 9185dfad1286942b4914657e4c0e9ad28cb2aaa8 (diff) | |
download | lua-language-server-05172fd47c2b41240a0cccbe9511c41c9918a0d2.zip |
Add --visualize command that outputs the AST of a given file as graphviz
Diffstat (limited to 'script/cli/visualize.lua')
-rw-r--r-- | script/cli/visualize.lua | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/script/cli/visualize.lua b/script/cli/visualize.lua new file mode 100644 index 00000000..29269b82 --- /dev/null +++ b/script/cli/visualize.lua @@ -0,0 +1,103 @@ +local lang = require 'language' +local parser = require 'parser' +local guide = require 'parser.guide' + +local function nodeId(node) + return node.type .. ':' .. node.start .. ':' .. node.finish +end + +local function shorten(str) + if type(str) ~= 'string' then + return str + end + str = str:gsub('\n', '\\\\n') + if #str <= 20 then + return str + else + return str:sub(1, 17) .. '...' + end +end + +local function getTooltipLine(k, v) + if type(v) == 'table' then + if v.type then + v = '<node ' .. v.type .. '>' + else + v = '<table>' + end + end + v = tostring(v) + v = v:gsub('"', '\\"') + return k .. ': ' .. shorten(v) .. '\\n' +end + +local function getTooltip(node) + local str = '' + local skipNodes = {parent = true, start = true, finish = true, type = true} + str = str .. getTooltipLine('start', node.start) + str = str .. getTooltipLine('finish', node.finish) + for k, v in pairs(node) do + if type(k) ~= 'number' and not skipNodes[k] then + str = str .. getTooltipLine(k, v) + end + end + for i = 1, math.min(#node, 15) do + str = str .. getTooltipLine(i, node[i]) + end + if #node > 15 then + str = str .. getTooltipLine('15..' .. #node, '(...)') + end + return str +end + +local nodeEntry = '\t"%s" [\n\t\tlabel="%s\\l%s\\l"\n\t\ttooltip="%s"\n\t]' +local function getNodeLabel(node) + local keyName = guide.getKeyName(node) + if node.type == 'binary' or node.type == 'unary' then + keyName = node.op.type + elseif node.type == 'label' or node.type == 'goto' then + keyName = node[1] + end + return nodeEntry:format(nodeId(node), node.type, shorten(keyName) or '', getTooltip(node)) +end + +local function getVisualizeVisitor(writer) + local function visitNode(node, parent) + if node == nil then return end + writer:write(getNodeLabel(node)) + writer:write('\n') + if parent then + writer:write(('\t"%s" -> "%s"'):format(nodeId(parent), nodeId(node))) + writer:write('\n') + end + guide.eachChild(node, function(child) + visitNode(child, node) + end) + end + return visitNode +end + + +local export = {} + +function export.visualizeAst(code, writer) + local state = parser.compile(code, 'Lua', _G['LUA_VER'] or 'Lua 5.4') + writer:write('digraph AST {\n') + writer:write('\tnode [shape = rect]\n') + getVisualizeVisitor(writer)(state.ast) + writer:write('}\n') +end + +function export.runCLI() + lang(LOCALE) + local file = _G['VISUALIZE'] + local code, err = io.open(file) + if not code then + io.stderr:write('failed to open ' .. file .. ': ' .. err) + return 1 + end + code = code:read('a') + return export.visualizeAst(code, io.stdout) +end + +return export |