summaryrefslogtreecommitdiff
path: root/Data/Libraries/LDoc/ldoc/lang.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Data/Libraries/LDoc/ldoc/lang.lua')
-rw-r--r--Data/Libraries/LDoc/ldoc/lang.lua379
1 files changed, 379 insertions, 0 deletions
diff --git a/Data/Libraries/LDoc/ldoc/lang.lua b/Data/Libraries/LDoc/ldoc/lang.lua
new file mode 100644
index 0000000..4ef1859
--- /dev/null
+++ b/Data/Libraries/LDoc/ldoc/lang.lua
@@ -0,0 +1,379 @@
+------------
+-- Language-dependent parsing of code.
+-- This encapsulates the different strategies needed for parsing C and Lua
+-- source code.
+
+local class = require 'pl.class'
+local utils = require 'pl.utils'
+local List = require 'pl.List'
+local tools = require 'ldoc.tools'
+local lexer = require 'ldoc.lexer'
+local quit = utils.quit
+local tnext = lexer.skipws
+
+
+local Lang = class()
+
+function Lang:trim_comment (s)
+ return s:gsub(self.line_comment,'')
+end
+
+function Lang:start_comment (v)
+ local line = v:match (self.start_comment_)
+ if line and self.end_comment_ and v:match (self.end_comment_) then
+ return nil
+ end
+ local block = v:match(self.block_comment)
+ return line or block, block
+end
+
+function Lang:empty_comment (v)
+ return v:match(self.empty_comment_)
+end
+
+function Lang:grab_block_comment(v,tok)
+ v = v:gsub(self.block_comment,'')
+ return tools.grab_block_comment(v,tok,self.end_comment)
+end
+
+function Lang:find_module(tok,t,v)
+ return '...',t,v
+end
+
+function Lang:item_follows(t,v)
+ return false
+end
+
+function Lang:finalize()
+ self.empty_comment_ = self.start_comment_..'%s*$'
+end
+
+function Lang:search_for_token (tok,type,value,t,v)
+ while t and not (t == type and v == value) do
+ if t == 'comment' and self:start_comment(v) then return nil,t,v end
+ t,v = tnext(tok)
+ end
+ return t ~= nil,t,v
+end
+
+function Lang:parse_extra (tags,tok)
+end
+
+function Lang:is_module_modifier ()
+ return false
+end
+
+function Lang:parse_module_modifier (tags, tok)
+ return nil, "@usage or @exports deduction not implemented for this language"
+end
+
+
+local Lua = class(Lang)
+
+function Lua:_init()
+ self.line_comment = '^%-%-+' -- used for stripping
+ self.start_comment_ = '^%-%-%-+' -- used for doc comment line start
+ self.block_comment = '^%-%-%[=*%[%-+' -- used for block doc comments
+ self.end_comment_ = '[^%-]%-%-+[^-]*\n$' ---- exclude --- this kind of comment ---
+ self.method_call = ':'
+ self:finalize()
+end
+
+function Lua.lexer(fname)
+ local f,e = io.open(fname)
+ if not f then quit(e) end
+ return lexer.lua(f,{}),f
+end
+
+function Lua:grab_block_comment(v,tok)
+ local equals = v:match('^%-%-%[(=*)%[')
+ if not equals then return v end
+ v = v:gsub(self.block_comment,'')
+ return tools.grab_block_comment(v,tok,'%]'..equals..'%]')
+end
+
+
+-- luacheck: push ignore 312
+function Lua:parse_module_call(tok,t,v)
+ t,v = tnext(tok)
+ if t == '(' then t,v = tnext(tok) end
+ if t == 'string' then -- explicit name, cool
+ return v,t,v
+ elseif t == '...' then -- we have to guess!
+ return '...',t,v
+ end
+end
+-- luacheck: pop
+
+-- If a module name was not provided, then we look for an explicit module()
+-- call. However, we should not try too hard; if we hit a doc comment then
+-- we should go back and process it. Likewise, module(...) also means
+-- that we must infer the module name.
+function Lua:find_module(tok,t,v)
+ local res
+ res,t,v = self:search_for_token(tok,'iden','module',t,v)
+ if not res then return nil,t,v end
+ return self:parse_module_call(tok,t,v)
+end
+
+local function parse_lua_parameters (tags,tok)
+ tags.formal_args = tools.get_parameters(tok)
+ tags:add('class','function')
+end
+
+local function parse_lua_function_header (tags,tok)
+ if not tags.name then
+ tags:add('name',tools.get_fun_name(tok))
+ end
+ if not tags.name then return 'function has no name' end
+ parse_lua_parameters(tags,tok)
+end
+
+local function parse_lua_table (tags,tok)
+ tags.formal_args = tools.get_parameters(tok,'}',function(s)
+ return s == ',' or s == ';'
+ end)
+end
+
+--------------- function and variable inferrence -----------
+-- After a doc comment, there may be a local followed by:
+-- [1] (l)function: function NAME
+-- [2] (l)function: NAME = function
+-- [3] table: NAME = {
+-- [4] field: NAME = <anything else> (this is a module-level field)
+--
+-- Depending on the case successfully detected, returns a function which
+-- will be called later to fill in inferred item tags
+function Lua:item_follows(t,v,tok)
+ local parser, case
+ local is_local = t == 'keyword' and v == 'local'
+ if is_local then t,v = tnext(tok) end
+ if t == 'keyword' and v == 'function' then -- case [1]
+ case = 1
+ parser = parse_lua_function_header
+ elseif t == 'iden' then
+ local name,t,_ = tools.get_fun_name(tok,v)
+ if t ~= '=' then return nil,"not 'name = function,table or value'" end
+ t,v = tnext(tok)
+ if t == 'keyword' and v == 'function' then -- case [2]
+ tnext(tok) -- skip '('
+ case = 2
+ parser = function(tags,tok)
+ tags:add('name',name)
+ parse_lua_parameters(tags,tok)
+ end
+ elseif t == '{' then -- case [3]
+ case = 3
+ parser = function(tags,tok)
+ tags:add('class','table')
+ tags:add('name',name)
+ parse_lua_table (tags,tok)
+ end
+ else -- case [4]
+ case = 4
+ parser = function(tags)
+ tags:add('class','field')
+ tags:add('name',name)
+ end
+ end
+ elseif t == 'keyword' and v == 'return' then
+ t, v = tnext(tok)
+ if t == 'keyword' and v == 'function' then
+ -- return function(a, b, c)
+ tnext(tok) -- skip '('
+ case = 2
+ parser = parse_lua_parameters
+ elseif t == '{' then
+ -- return {...}
+ case = 5
+ parser = function(tags,tok)
+ tags:add('class','table')
+ parse_lua_table(tags,tok)
+ end
+ else
+ return nil,'not returning function or table'
+ end
+ else
+ return nil,"not 'name=value' or 'return value'"
+ end
+ return parser, is_local, case
+end
+
+
+-- we only call the function returned by the item_follows above if there
+-- is not already a name and a type.
+-- Otherwise, this is called. Currrently only tries to fill in the fields
+-- of a table from a table definition as identified above
+function Lua:parse_extra (tags,tok,case)
+ if tags.class == 'table' and not tags.field and case == 3 then
+ parse_lua_table(tags,tok)
+ end
+end
+
+-- For Lua, a --- @usage comment means that a long
+-- string containing the usage follows, which we
+-- use to update the module usage tag. Likewise, the @export
+-- tag alone in a doc comment refers to the following returned
+-- Lua table of functions
+
+
+function Lua:is_module_modifier (tags)
+ return tags.summary == '' and (tags.usage or tags.export)
+end
+
+-- Allow for private name convention.
+function Lua:is_private_var (name)
+ return name:match '^_' or name:match '_$'
+end
+
+function Lua:parse_module_modifier (tags, tok, F)
+ if tags.usage then
+ if tags.class ~= 'field' then return nil,"cannot deduce @usage" end
+ local t1= tnext(tok)
+ if t1 ~= '[' then return nil, t1..' '..': not a long string' end
+ local _, v = tools.grab_block_comment('',tok,'%]%]')
+ return true, v, 'usage'
+ elseif tags.export then
+ if tags.class ~= 'table' then return nil, "cannot deduce @export" end
+ for f in tags.formal_args:iter() do
+ if not self:is_private_var(f) then
+ F:export_item(f)
+ end
+ end
+ return true
+ end
+end
+
+
+-- note a difference here: we scan C/C++ code in full-text mode, not line by line.
+-- This is because we can't detect multiline comments in line mode.
+-- Note: this applies to C/C++ code used to generate _Lua_ documentation!
+
+local CC = class(Lang)
+
+function CC:_init()
+ self.line_comment = '^//+'
+ self.start_comment_ = '^///+'
+ self.block_comment = '^/%*%*+'
+ self.method_call = ':'
+ self:finalize()
+end
+
+function CC.lexer(f)
+ local err
+ f,err = utils.readfile(f)
+ if not f then quit(err) end
+ return lexer.cpp(f,{},nil,true)
+end
+
+function CC:grab_block_comment(v,tok)
+ v = v:gsub(self.block_comment,''):gsub('\n%s*%*','\n')
+ return 'comment',v:sub(1,-3)
+end
+
+--- here the argument name is always last, and the type is composed of any tokens before
+function CC:extract_arg (tl,idx)
+ idx = idx or 1
+ local res = List()
+ for i = idx,#tl-1 do
+ res:append(tl[i][2])
+ end
+ local type = res:join ' '
+ return tl[#tl][2], type
+end
+
+function CC:item_follows (t,v,tok)
+ if not self.extra.C then
+ return false
+ end
+ if t == 'iden' or t == 'keyword' then --
+ local _
+ if v == self.extra.export then -- this is not part of the return type!
+ _,v = tnext(tok)
+ end
+ -- types may have multiple tokens: example, const char *bonzo(...)
+ local return_type, name = v
+ _,v = tnext(tok)
+ name = v
+ t,v = tnext(tok)
+ while t ~= '(' do
+ return_type = return_type .. ' ' .. name
+ name = v
+ t,v = tnext(tok)
+ end
+ --print ('got',name,t,v,return_type)
+ return function(tags,tok)
+ if not tags.name then
+ tags:add('name',name)
+ end
+ tags:add('class','function')
+ if t == '(' then
+ tags.formal_args,t,_ = tools.get_parameters(tok,')',',',self)
+ if return_type ~= 'void' then
+ tags.formal_args.return_type = return_type
+ end
+ end
+ end
+ end
+ return false
+end
+
+local Moon = class(Lua)
+
+function Moon:_init()
+ self.line_comment = '^%-%-+' -- used for stripping
+ self.start_comment_ = '^%s*%-%-%-+' -- used for doc comment line start
+ self.block_comment = '^%-%-%[=*%[%-+' -- used for block doc comments
+ self.end_comment_ = '[^%-]%-%-+\n$' ---- exclude --- this kind of comment ---
+ self.method_call = '\\'
+ self:finalize()
+end
+
+--- much like Lua, BUT auto-assign parameters start with @
+function Moon:extract_arg (tl,idx)
+ idx = idx or 1
+ local auto_assign = tl[idx][1] == '@'
+ if auto_assign then idx = idx + 1 end
+ local res = tl[idx][2]
+ return res
+end
+
+function Moon:item_follows (t,v,tok)
+ if t == '.' then -- enclosed in with statement
+ t,v = tnext(tok)
+ end
+ if t == 'iden' then
+ local name,t,v = tools.get_fun_name(tok,v,'')
+ if name == 'class' then
+ local _
+ name,_,_ = tools.get_fun_name(tok,v,'')
+ -- class!
+ return function(tags,tok)
+ tags:add('class','type')
+ tags:add('name',name)
+ end
+ elseif t == '=' or t == ':' then -- function/method
+ local _
+ t,_ = tnext(tok)
+ return function(tags,tok)
+ if not tags.name then
+ tags:add('name',name)
+ end
+ if t == '(' then
+ tags.formal_args,t,_ = tools.get_parameters(tok,')',',',self)
+ else
+ tags.formal_args = List()
+ end
+ t,_ = tnext(tok)
+ tags:add('class','function')
+ if t ~= '>' then
+ tags.static = true
+ end
+ end
+ else
+ return nil, "expecting '=' or ':'"
+ end
+ end
+end
+
+return { lua = Lua(), cc = CC(), moon = Moon() }