Skip to content

Instantly share code, notes, and snippets.

@starwing
Created February 22, 2014 13:32
Show Gist options
  • Save starwing/9154859 to your computer and use it in GitHub Desktop.
Save starwing/9154859 to your computer and use it in GitHub Desktop.
a C declaration parser written in Lua
local parse_declare
local function parse_typespec(decl, s, pos)
-- typespec: cv_decl* spec cv_decl*
-- cv_decl: 'const' | 'volatile'
while true do
local spec, newpos = s:match("^%s*([%w_]+)%s*()", pos)
if not spec then break end
if spec == "const" then
decl.const = true
elseif spec == "volatile" then
decl.volatile = true
elseif not decl.base then
decl.base = spec
else
break
end
pos = newpos
end
if decl.base then
return decl, nil, pos
else
return nil, "type spec expected", pos
end
end
local function parse_args(s, pos)
-- args: arglist? '...'?
-- arglist: declare (',' declare)*
local args = {}
while true do
local dot3, newpos = s:match("^%s*(%.%.%.)%s*()", pos)
if dot3 then return args, nil, newpos end
local decl, err, newpos = parse_declare({}, s, pos)
if not decl then return nil, err, newpos end
args[#args + 1] = decl
pos = newpos
if pos > #s then break end
local comma, newpos = s:match("^%s*(,)%s*()", pos)
if not comma then
return nil, "missing ',' after decl", pos
end
pos = newpos
end
return args, nil, pos
end
local function parse_postdecl(decl, s, pos)
-- postdecl: array_decl* | func_decl
while true do
local array_decl, newpos = s:match("^%s*(%b[])%s*()", pos)
if not array_decl then break end
if not decl.array then decl.array = {} end
decl.array[#decl.array+1] = array_decl:sub(2,-2)
pos = newpos
end
if not decl.array then
local func_decl, newpos = s:match("^%s*(%b())%s*()", pos)
if func_decl then
local args, err, nestpos = parse_args(func_decl:sub(2, -2))
if not args then return nil, err, pos+nestpos+1 end
decl.args = args
pos = newpos
end
end
return decl, nil, pos
end
local function parse_declarator(decl, s, pos)
-- declarator: '*'* '(' declarator ')' postdecl
-- | '*'* name postdecl | (empty)
local pointer, newpos = s:match("^%s*([%s%*]+)%s*()", pos)
if pointer then
decl.pointer = #(pointer:gsub("%s+", ""))
pos = newpos
end
local begin, nest, newpos = s:match("^%s*()(%b())%s*()", pos)
if nest then
local nestdecl, err, nestpos = parse_declarator({}, nest:sub(2, -2))
if not nestdecl then return nil, err, newpos end
if nestpos < #nest-1 then
return nil, "trailling tokens in sub declarator", begin+nestpos
end
if not next(nestdecl) then
nestdecl = decl
else
nestdecl.derived = decl
end
local decl, err, pos = parse_postdecl(decl, s, newpos)
if not decl then return nil, err, pos end
return nestdecl, nil, pos
end
local name, newpos = s:match("^%s*([%w_]+)%s*()", pos)
if name then
decl.name = name
return parse_postdecl(decl, s, newpos)
end
return decl, nil, pos or 1
end
function parse_declare(decl, s, pos)
-- declare: typespec declarator
local decl, err, pos = parse_typespec(decl, s, pos)
if not decl then return nil, err, pos end
local decl, err, pos = parse_declarator(decl, s, pos)
if not decl then return nil, err, pos end
return decl, nil, pos
end
local function declare(s, pos)
local decl, err, pos = parse_declare({}, s, pos)
if not decl then
error(err..", near '"..s:sub(pos).."'")
end
pos = s:match("^%s*()", pos)
if pos <= #s then
error("trailling tokens, near '"..s:sub(pos).."'")
end
return decl
end
local serpent = require 'serpent'
local tests = {
[[ int ]],
[[ int a ]],
[[ int *a ]],
[[ int ***a ]],
[[ int ***a[] ]],
[[ int *(*a)[] ]],
[[ int *(*a)[1][2][] ]],
[[ int (*)(int,int,float b) ]],
[[ int *(*a)(int,int,float b) ]],
[[ int *(*a)(int,int()(int)) ]],
}
for i, v in ipairs(tests) do
print("test", v)
print(serpent.block(declare(v)))
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment