Skip to content

Commit c675173

Browse files
committed
refs m-schmoock#12 add _Pragma as default predefined function
refs m-schmoock#13 fix bug that #x (stringify operator) 's apply order is wrong
1 parent 7fdda9a commit c675173

File tree

1 file changed

+89
-14
lines changed

1 file changed

+89
-14
lines changed

lcpp.lua

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ local __LINE__ = "__LINE__"
163163
local __DATE__ = "__DATE__"
164164
local __TIME__ = "__TIME__"
165165
local __LCPP_INDENT__ = "__LCPP_INDENT__"
166+
local _Pragma = "_Pragma"
166167

167168
-- BNF LEAVES
168169
local ENDL = "$"
@@ -290,12 +291,6 @@ local function findn(input, what)
290291
end
291292
end
292293

293-
-- C literal string concatenation
294-
local function concatStringLiteral(input)
295-
-- screener does remove multiline definition, so just check ".*"%s*".*" pattern
296-
return input:gsub("\"("..STRING_LITERAL..")\""..OPTSPACES.."\"("..STRING_LITERAL..")\"", "\"%1%2\"")
297-
end
298-
299294
-- c style boolean check (thus, 0 will be false)
300295
local function CBoolean(value)
301296
return value and (value ~= 0)
@@ -311,6 +306,12 @@ local function CEval(expr)
311306
end
312307
end
313308

309+
local function process_Pragma(value)
310+
return value:gsub("\\\\", "\\"):gsub("\\\"", "\""):gsub("_Pragma%s*(%b())", function (match)
311+
return "#pragma "..match:sub(3, -3)
312+
end)
313+
end
314+
314315
-- a lightweight and flexible tokenizer
315316
local function _tokenizer(str, setup)
316317
local defsetup = {
@@ -401,6 +402,52 @@ end
401402
-- ------------
402403
-- PARSER
403404
-- ------------
405+
-- C literal string concatenation
406+
local LCPP_TOKENIZE_LITERAL = {
407+
string = true,
408+
keywords = {
409+
},
410+
}
411+
412+
local function concatStringLiteral(input)
413+
--print("input = "..input)
414+
local out = {}
415+
local literal_appears = {}
416+
local last_ignore
417+
local quote
418+
for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_LITERAL) do
419+
if k == "string" then
420+
--print("string:["..input:sub(start + 1, end_ - 1).."]")
421+
table.insert(literal_appears, input:sub(start + 1, end_ - 1))
422+
quote = input:sub(start,start)
423+
elseif k == "ignore" then
424+
if #literal_appears == 0 then
425+
table.insert(out, input:sub(start, end_))
426+
else
427+
last_ignore = input:sub(start, end_)
428+
end
429+
else
430+
if #literal_appears > 0 then
431+
local concat = quote..table.concat(literal_appears)..quote
432+
table.insert(out, concat)
433+
if last_ignore then
434+
table.insert(out, last_ignore)
435+
end
436+
end
437+
table.insert(out, input:sub(start, end_))
438+
literal_appears = {}
439+
last_ignore = nil
440+
end
441+
end
442+
if #literal_appears > 0 then
443+
table.insert(out, quote..table.concat(literal_appears)..quote)
444+
end
445+
local rinput = table.concat(out)
446+
--if rinput ~= input then
447+
-- print("result:["..rinput.."]["..input.."]")
448+
--end
449+
return rinput
450+
end
404451

405452
local LCPP_TOKENIZE_COMMENT = {
406453
string = false,
@@ -533,7 +580,6 @@ local function apply(state, input)
533580
local out = {}
534581
local functions = {}
535582
local expand
536-
537583
for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_APPLY_MACRO) do
538584
-- print('tokenize:'..tostring(k).."|"..tostring(v))
539585
if k == "identifier" then
@@ -550,14 +596,14 @@ local function apply(state, input)
550596
repl = tostring(macro)
551597
expand = (repl ~= v)
552598
elseif type(macro) == "function" then
553-
local decl,cnt = input:sub(start):gsub("^[_%a][_%w]*%s*%b()", "%1")
599+
local decl = input:sub(start):match("^[_%a][_%w]*%s*%b()")
554600
-- print('matching:'..input.."|"..decl.."|"..cnt)
555-
if cnt > 0 then
601+
if decl then
556602
repl = macro(decl)
557603
-- print("d&r:"..decl.."|"..repl)
558604
expand = true
559605
table.insert(out, repl)
560-
table.insert(out, input:sub(end_ + #decl))
606+
table.insert(out, input:sub(start + #decl))
561607
break
562608
else
563609
if input:sub(start):find("^[_%a][_%w]*%s*%(") then
@@ -1217,7 +1263,7 @@ local function prepareMacro(state, input)
12171263
end
12181264

12191265
-- macro args replacement function slower but more torelant for pathological case
1220-
local function replaceArgs(argsstr, repl)
1266+
local function replaceArgs(argsstr, repl, state)
12211267
local args = {}
12221268
argsstr = argsstr:sub(2,-2)
12231269
-- print('argsstr:'..argsstr)
@@ -1236,7 +1282,13 @@ local function replaceArgs(argsstr, repl)
12361282
comma = true
12371283
end
12381284
end
1239-
local v = repl:gsub("%$(%d+)", function (m) return args[tonumber(m)] or "" end)
1285+
local v = repl:gsub("%$(#?)(%d+)", function (stringify, m)
1286+
if #stringify > 0 then
1287+
return args[tonumber(m)] and state:apply(args[tonumber(m)]) or ""
1288+
else
1289+
return args[tonumber(m)] or ""
1290+
end
1291+
end)
12401292
-- print("replaceArgs:" .. repl .. "|" .. tostring(#args) .. "|" .. v)
12411293
return v
12421294
end
@@ -1255,7 +1307,7 @@ local function parseFunction(state, input)
12551307
-- avoid matching substring of another identifier (eg. attrib matches __attribute__ and replace it)
12561308
repl = repl:gsub("(#*)(%s*)("..argname..")([_%w]?)", function (s1, s2, s3, s4)
12571309
if #s4 <= 0 then
1258-
return (#s1 == 1) and ("\"$"..noargs.."\"") or (s1..s2.."$"..noargs)
1310+
return (#s1 == 1) and ("\"$#"..noargs.."\"") or (s1..s2.."$"..noargs)
12591311
else
12601312
return s1..s2..s3..s4
12611313
end
@@ -1267,7 +1319,7 @@ local function parseFunction(state, input)
12671319
-- build macro funcion
12681320
local func = function(input)
12691321
return input:gsub(name.."%s*(%b())", function (match)
1270-
return replaceArgs(match, repl)
1322+
return replaceArgs(match, repl, state)
12711323
end)
12721324
end
12731325

@@ -1344,6 +1396,7 @@ function lcpp.init(input, predefines, macro_sources)
13441396
state:define(__TIME__, os.date("%H:%M:%S"), true)
13451397
state:define(__LINE__, state.lineno, true)
13461398
state:define(__LCPP_INDENT__, state:getIndent(), true)
1399+
state:define(_Pragma, process_Pragma, true)
13471400
predefines = predefines or {}
13481401
for k,v in pairs(lcpp.ENV) do state:define(k, v, true) end -- static ones
13491402
for k,v in pairs(predefines) do state:define(k, v, true) end
@@ -1416,6 +1469,11 @@ function lcpp.test(suppressMsg)
14161469
/*
14171470
assert(false, "multi-line comment not removed")
14181471
*/
1472+
#define PLUS1(x) x + 1
1473+
assert(PLUS1(__LINE__) == 14, "__LINE__ should evaluate before PLUS1 evaluated")
1474+
#define TOSTR(x) # x
1475+
assert(TOSTR(__LINE__) == "15", "__LINE__ should evaluate before TOSTR evaluated")
1476+
assert(TOSTR() == "", "empty stringify should not cause error")
14191477
/* pathological case which contains single line comment start in multiline comments.
14201478
* e.g. this multiline comment should be finish next line.
14211479
* http://foobar.com */ // comment
@@ -1883,6 +1941,23 @@ function lcpp.test(suppressMsg)
18831941
assert(loadstring(testlua, "testlua"))()
18841942
lcpp_test.assertTrueCalls = findn(testlcpp, "lcpp_test.assertTrue()")
18851943
assert(lcpp_test.assertTrueCount == lcpp_test.assertTrueCalls, "assertTrue calls:"..lcpp_test.assertTrueCalls.." count:"..lcpp_test.assertTrueCount)
1944+
1945+
-- pragma test because it causes error if appeared in above testlua code
1946+
local src = [[
1947+
#ifdef DEBUG
1948+
#define DO_PRAGMA(x) _Pragma (#x)
1949+
#else
1950+
#define DO_PRAGMA(x)
1951+
#endif
1952+
DO_PRAGMA (CUSTOM_COMPILER debug_msg "random message")
1953+
]]
1954+
local pragma_statement = '#pragma CUSTOM_COMPILER debug_msg "random message"'
1955+
local r1, r2 = lcpp.compile(src), lcpp.compile(src, { DEBUG = true })
1956+
-- print('r1=[['..r1..']]')
1957+
-- print('r2=[['..r2..']]')
1958+
assert(not r1:match(pragma_statement))
1959+
assert(r2:match(pragma_statement))
1960+
18861961
_G.lcpp_test = nil -- delete ugly global hack
18871962
if not suppressMsg then print("Test run suscessully") end
18881963
end

0 commit comments

Comments
 (0)