diff --git a/ldoc.lua b/ldoc.lua
index 838caeeac94b5124bcb93620ddfc6033442c8647..69d6f119a50e0d040a7d1592199367ae42db8eaf 100644
--- a/ldoc.lua
+++ b/ldoc.lua
@@ -198,7 +198,7 @@ local ldoc_contents = {
    'readme','all','manual_url', 'ignore', 'colon', 'sort', 'module_file','vars',
    'boilerplate','merge', 'wrap', 'not_luadoc', 'template_escape','merge_error_groups',
    'no_return_or_parms','no_summary','full_description','backtick_references', 'custom_see_handler',
-   'no_space_before_args',
+   'no_space_before_args','parse_extra',
 }
 ldoc_contents = tablex.makeset(ldoc_contents)
 
@@ -355,6 +355,7 @@ local function process_file (f, flist)
    local ftype = file_types[ext]
    if ftype then
       if args.verbose then print(f) end
+      ftype.extra = ldoc.parse_extra or {}
       local F,err = parse.file(f,ftype,args)
       if err then
          if F then
@@ -544,6 +545,7 @@ end)
 
 ldoc.single = modcount == 1 and first_module or nil
 
+--do return end
 
 -------- three ways to dump the object graph after processing -----
 
diff --git a/ldoc/doc.lua b/ldoc/doc.lua
index 615abb8e853ef24d135e974de9e7f988545b2976..b184c7e6228e97728d2c38c8e707496f1ae87a84 100644
--- a/ldoc/doc.lua
+++ b/ldoc/doc.lua
@@ -659,6 +659,21 @@ function Item:finish()
                   self:warning("undocumented formal argument: "..quote(fargs[i]))
                end end
             end
+            -- formal arguments may come with types, inferred by the
+            -- appropriate code in ldoc.lang
+            if fargs.types then
+               self.modifiers[field] = List()
+               for t in fargs.types:iter() do
+                  self:add_type(field,t)
+               end
+               if fargs.return_type then
+                  if not self.ret then -- type, but no comment; no worries
+                     self.ret = List{''}
+                  end
+                  self.modifiers['return'] = List()
+                  self:add_type('return',fargs.return_type)
+               end
+            end
          end -- #fargs > 0
       end -- fargs
 
@@ -701,6 +716,10 @@ function Item:finish()
    end
 end
 
+function Item:add_type(field,type)
+   self.modifiers[field]:append {type = type}
+end
+
 -- ldoc allows comments in the formal arg list to be used, if they aren't specified with @param
 -- Further, these comments may start with a type followed by a colon, and are then equivalent
 -- to a @tparam
@@ -709,7 +728,7 @@ function Item:parse_argument_comment (comment,field)
       comment = comment:gsub('^%-+%s*','')
       local type,rest = comment:match '([^:]+):(.*)'
       if type then
-         self.modifiers[field]:append {type = type}
+         self:add_type(field,type)
          comment = rest
       end
    end
diff --git a/ldoc/lang.lua b/ldoc/lang.lua
index 95a261f33ba8aa946823b6c3bb41e1aba78a3c40..da7500230a4f4bc9850e6c8c98bed3b48ae9f536 100644
--- a/ldoc/lang.lua
+++ b/ldoc/lang.lua
@@ -266,6 +266,54 @@ function CC:grab_block_comment(v,tok)
    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 --
+      if v == self.extra.export then -- this is not part of the return type!
+         t,v = tnext(tok)
+      end
+      -- TBD collecting types which are not single tokens (may contain '*')
+      local return_type = v
+      t,v = tnext(tok)
+      if t == 'iden' or t=='keyword' then
+         local name = v
+         t,v = tnext(tok)
+         if t ~= '(' then
+            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,v = tools.get_parameters(tok,')',',',self)
+               if return_type ~= 'void' then
+                  tags.formal_args.return_type = return_type
+               end
+            end
+         end
+      end
+   end
+   return false
+end
+
 local Moon = class(Lua)
 
 function Moon:_init()
diff --git a/ldoc/lexer.lua b/ldoc/lexer.lua
index bad9f77d509b5052494cb670fecbd5504b114002..fa50ac8766eec4ddfc726f38a50f8cfa26dc8b42 100644
--- a/ldoc/lexer.lua
+++ b/ldoc/lexer.lua
@@ -371,6 +371,7 @@ function lexer.cpp(s,filter,options)
             {'^|=',tdump},
             {'^%^=',tdump},
             {'^::',tdump},
+            {'^%.%.%.',tdump},
             {'^.',tdump}
         }
     end
diff --git a/ldoc/project.ldoc.mode b/ldoc/project.ldoc.mode
new file mode 100644
index 0000000000000000000000000000000000000000..dae53bfc55c05b7c3111c87285fec57e090b3341
--- /dev/null
+++ b/ldoc/project.ldoc.mode
@@ -0,0 +1,2 @@
+mode=lua
+tabs=use=false,size=3
diff --git a/ldoc/tools.lua b/ldoc/tools.lua
index 9aeb0b9f72c4d8e7b663d98ea8f57203815e8ff0..cc4343235ad90c83c4077a458a3322e9d2c5ffa7 100644
--- a/ldoc/tools.lua
+++ b/ldoc/tools.lua
@@ -284,7 +284,7 @@ local function value_of (tok) return tok[2] end
 -- following the arguments. ldoc will use these in addition to explicit
 -- param tags.
 
-function M.get_parameters (tok,endtoken,delim)
+function M.get_parameters (tok,endtoken,delim,lang)
    tok = M.space_skip_getter(tok)
    local args = List()
    args.comments = {}
@@ -292,8 +292,18 @@ function M.get_parameters (tok,endtoken,delim)
 
    if not ltl or not ltl[1] or #ltl[1] == 0 then return args end -- no arguments
 
-   local function strip_comment (text)
-      return text:match("%s*%-%-+%s*(.*)")
+   local strip_comment, extract_arg
+
+   if lang then
+      strip_comment = utils.bind1(lang.trim_comment,lang)
+      extract_arg = utils.bind1(lang.extract_arg,lang)
+   else
+      strip_comment = function(text)
+         return text:match("%s*%-%-+%s*(.*)")
+      end
+      extract_arg = function(tl,idx)
+         return value_of(tl[idx or 1])
+      end
    end
 
    local function set_comment (idx,tok)
@@ -307,6 +317,15 @@ function M.get_parameters (tok,endtoken,delim)
       args.comments[arg] = text
    end
 
+   local function add_arg (tl,idx)
+      local name, type = extract_arg(tl,idx)
+      args:append(name)
+      if type then
+         if not args.types then args.types = List() end
+         args.types:append(type)
+      end
+   end
+
    for i = 1,#ltl do
       local tl = ltl[i] -- token list for argument
       if #tl > 0 then
@@ -323,10 +342,10 @@ function M.get_parameters (tok,endtoken,delim)
                j = j + 1
             end
             if #tl > 1 then
-               args:append(value_of(tl[j]))
+               add_arg(tl,j)
             end
          else
-            args:append(value_of(tl[1]))
+            add_arg(tl,1)
          end
          if i == #ltl and #tl > 1 then
             while j <= #tl and type_of(tl[j]) ~= 'comment' do