Skip to content

Instantly share code, notes, and snippets.

@edubart
Created May 26, 2021 15:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edubart/e905faa40ad1f61726b0b43fa411e259 to your computer and use it in GitHub Desktop.
Save edubart/e905faa40ad1f61726b0b43fa411e259 to your computer and use it in GitHub Desktop.
Nelua doc tool
local fs = require 'nelua.utils.fs'
local traits = require 'nelua.utils.traits'
local stringer = require 'nelua.utils.stringer'
local parser = require 'nelua.syntaxdefs'().parser
local re = require 'nelua.thirdparty.relabel'
local filename = 'lib/string.nelua'
local filecode = fs.ereadfile(filename)
local ast = parser:parse(filecode, filename)
local patt = re.compile([[
comments <- {| (long_comment / short_comment / .)* |}
short_comment <- {| {} '--' '-'* %s* {~(
(!linebreak .) / (linebreak (%s* '--' %s*)->'')
)*~} {} |} linebreak?
long_comment <- {| {} (open ( {contents} close)) {} |}
contents <- (!close .)*
open <- '--[' {:eq: '='*:} '[' '-'* %s*
close <- %s* ']' =eq ']'
]] ..
"linebreak <- [%nl]'\r' / '\r'[%nl] / [%nl] / '\r'")
local comments = {}
local comments_by_line = {}
local captures = patt:match(filecode)
for i,v in ipairs(captures) do
local comment = {}
comments[i] = comment
comment.pos = v[1]
comment.text = v[2]
comment.endpos = v[3]-1
local lineno, colno, line = stringer.calcline(filecode, comment.pos)
comment.lineno = lineno
if v.eq then -- adjust comments indentation
local indentspaces = line:match('^(%s*)--')
comment.text = comment.text:gsub('\n'..indentspaces, '\n')
end
comment.endline = stringer.calcline(filecode, comment.endpos)
for j=comment.lineno, comment.endline do
comments_by_line[j] = comment
end
end
local docsyms = {}
local topcomment = comments_by_line[1]
local modname = filename:match('(%w+).nelua$')
local modsym
if topcomment then
local sym = {
filename = filename,
name = modname,
kind = 'heading',
text = topcomment.text,
lineno = 1
}
table.insert(docsyms, sym)
end
local function typenode2string(node)
if node and node.tag == 'Id' then
return node[1]
end
end
local function idnode2name(node)
if traits.is_string(node) then
return node
else
assert(traits.is_astnode(node))
if node.tag == 'IdDecl' then
return node[1]
elseif node.tag == 'DotIndex' or node.tag == 'ColonIndex' then
local fieldname = node[1]
local indexnode = node[2]
assert(traits.is_string(fieldname))
assert(traits.is_astnode(indexnode) and indexnode.tag == 'Id')
local sep = node.tag == 'DotIndex' and '.' or ':'
return indexnode[1]..sep..fieldname
else
print(node.tag)
error 'unexpected'
end
end
end
local function get_upper_comment(lineno)
local comment = comments_by_line[lineno-1]
if comment then
return comment.text
end
end
local function trimcode(code)
code = code:gsub('%-%-.*', '') -- remove comments
code = code:gsub('%s*$', '') -- remove trailing spaces
-- remove assignment for non types
if not code:match('=%s*@') then
code = code:gsub('%s*=.*', '')
end
code = code:gsub('%s*%b<>', '') -- remove annotations
return code
end
local function getcode(startpos, endpos)
local lineno, colno, line = stringer.calcline(filecode, startpos)
local indentspaces = line:match('^(%s*)--')
local code = filecode:sub(startpos, endpos - 2)
code = trimcode(code)
code = code:gsub('\n'..indentspaces, '\n')
return code, lineno
end
for node in ast:walk_nodes() do
if node.tag == 'FuncDef' then
local varscope, idnode, blocknode = node[1], node[2], node[6]
if varscope ~= 'local' then -- global/record functions
local name = idnode2name(idnode)
local code, lineno = getcode(node.pos, blocknode.pos-1)
local text = get_upper_comment(lineno)
local sym = {
filename = filename,
kind = 'funcdef',
code = code,
name = name,
lineno = lineno,
text = text,
}
table.insert(docsyms, sym)
end
elseif node.tag == 'VarDecl' then
local varscope, varnodes = node[1], node[2]
for _, varnode in ipairs(varnodes) do
if varnode.tag == 'IdDecl' then
local idnode, typenode = varnode[1], varnode[2]
local name = idnode2name(idnode)
if varscope == 'global' or name == modname..'T' then
local typename = typenode2string(typenode)
local code, lineno = getcode(node.pos, node.endpos - 2)
local text = get_upper_comment(lineno)
local sym = {
filename = filename,
kind = 'vardecl',
code = code,
name = name,
typename = typename,
lineno = lineno,
text = text,
}
if name == modname and code:match('record{%s*}') then
modsym = sym
else
table.insert(docsyms, sym)
end
end
end
end
end
end
print[[
---
layout: docs
title: Preview
permalink: /preview/
categories: docs toc
toc: true
order: 10
---
]]
for _,sym in ipairs(docsyms) do
if sym.kind == 'heading' then
print('## ' .. sym.name)
print()
--[[
if modsym then
print('```nelua')
print(modsym.code)
print('```')
print()
end
]]
print(sym.text)
print()
elseif sym.kind == 'vardecl' or sym.kind == 'funcdef' then
print('### ' .. sym.name)
print()
print('```nelua')
print(sym.code)
print('```')
print()
if sym.text then
print(sym.text)
end
print()
end
end
--[[
TODO:
-- add link to file
-- add line number and link to function?
-- add index for each module?
]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment