├── .gitignore ├── Makefile ├── README.md ├── config.ld ├── htdocs ├── lcpp-scm-1.rockspec ├── lcpp.lua └── resources ├── lua-logo-label.ps ├── lua-logo-lcpp.png └── plugin.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | 19 | # Lua doc 20 | /luadoc 21 | 22 | # vim 23 | .*.swp 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: all 2 | all: test doc 3 | 4 | 5 | prepare: 6 | mkdir -p luadoc 7 | clean: 8 | rm -rf luadoc 9 | test: 10 | lua -e 'local lcpp = require("lcpp"); lcpp.test();' 11 | test_file: 12 | lua -e 'lcpp = require("lcpp"); local out = lcpp.compileFile("resources/plugin.h", {UNICODE=1}); print(out);' 13 | 14 | run: 15 | lua -e 'lcpp = require("lcpp"); print("### entered lcpp interactive mode ###")' -i 16 | file: 17 | lua -e 'lcpp = require("lcpp"); local out = lcpp.compileFile("${FILE}"); print(out);' 18 | 19 | # DOC 20 | doc: luadoc 21 | ldoc: luadoc 22 | luadoc: prepare 23 | ldoc.lua ${PWD} 24 | cp resources/lua-logo-lcpp.png luadoc 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | === 3 | 4 | ## lcpp - a C-PreProcessor for Lua 5.1 and LuaJIT ffi integration 5 | 6 | This module offers a standard preprocessor for C code in pure Lua. 7 | The primary usecase is to enable LuaJIT ffi preprocessing. 8 | But you can also preprocess any other stuff (even Lua code itself) 9 | 10 | ### Links 11 | * Project page http://lcpp.schmoock.net 12 | * GitHub Page https://github.com/m-schmoock/lcpp 13 | * Lua http://www.lua.org/ 14 | * LuaJIT http://luajit.org 15 | 16 | ### Usage 17 | -- load lcpp (ffi.cdef wrapper turned on per default) 18 | local lcpp = require("lcpp") 19 | 20 | -- just use LuaJIT ffi and lcpp together 21 | ffi.cdef("#include ") 22 | 23 | -- or compile some code by hand 24 | local result = lcpp.compile("...") 25 | local out = lcpp.compile([[ 26 | #define MAXPATH 260 27 | typedef struct somestruct_t { 28 | void* base; 29 | size_t size; 30 | wchar_t path[MAXPATH]; 31 | } t_exe; 32 | ]]) 33 | 34 | ### Supports 35 | 36 | features: 37 | transparent LuaJIT ffi extension 38 | replacement macros 39 | macro chaining 40 | functional macros 41 | multiline macros 42 | expressions (defined(xyz) && ...) 43 | concat operator ("##") 44 | screen C single- and multiline comments 45 | 46 | directives: 47 | #include 48 | #define 49 | #undef 50 | #pragma 51 | #error 52 | #if 53 | #ifdef 54 | #ifndef 55 | #else 56 | #else if 57 | #elif 58 | #endif 59 | 60 | macros: 61 | __LINE__ 62 | __FILE__ 63 | __DATE__ 64 | __TIME__ 65 | __LCPP_INDENT__ 66 | 67 | ### Make targets 68 | make test # run the included test cases 69 | make doc # ldoc must be installed 70 | 71 | ### License 72 | MIT licencse included in lua modue. 73 | 74 | 2012-2014 Michael Schmoock 75 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | title = "lcpp" 2 | project = "API Doc"..[[ 3 | 4 | 5 | 6 | 14 | 17 | 18 | ]] 19 | 20 | description = [[ 21 | -- single module has no description 22 | ]] 23 | 24 | readme = { 25 | "README.md", 26 | } 27 | 28 | file = { 29 | ".", 30 | } 31 | dir = "luadoc" 32 | ext = "html" 33 | format = "discount" 34 | 35 | -------------------------------------------------------------------------------- /htdocs: -------------------------------------------------------------------------------- 1 | luadoc -------------------------------------------------------------------------------- /lcpp-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lcpp" 2 | version = "scm-1" 3 | rockspec_format = "1.0" 4 | 5 | source = { 6 | url = "git://github.com/willsteel/lcpp.git", 7 | } 8 | 9 | description = { 10 | summary = "a C PreProcessor in Lua 5.1 for LuaJIT ffi", 11 | detailed = [[ 12 | This module offers a standard preprocessor for C code in pure Lua. 13 | The primary usecase is to enable LuaJIT ffi preprocessing. 14 | But you can also preprocess any other data (even Lua code itself). 15 | github site: (https://github.com/willsteel/lcpp) 16 | ]], 17 | homepage = "http://lcpp.schmoock.net/", 18 | license = "MIT/X11", 19 | } 20 | 21 | dependencies = { 22 | "lua >= 5.1", -- luajit >= 2.0.0 is recommended but not required 23 | } 24 | 25 | build = { 26 | type = "none", 27 | install = { 28 | lua = { 29 | lcpp = "lcpp.lua", 30 | }, 31 | }, 32 | copy_directories = {}, 33 | } 34 | -------------------------------------------------------------------------------- /lcpp.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | --## lcpp - a C-PreProcessor for Lua 5.1 and LuaJIT ffi integration 3 | -- 4 | -- Copyright (C) 2012-2014 Michael Schmoock 5 | -- 6 | --### Links 7 | -- * GitHub page: [http://github.com/willsteel/lcpp](http://github.com/willsteel/lcpp) 8 | -- * Project page: [http://lcpp.schmoock.net](http://lcpp.schmoock.net) 9 | -- * Lua: [http://www.lua.org](http://www.lua.org) 10 | -- * LuaJIT: [http://luajit.org](http://luajit.org) 11 | -- * Sponsored by: [http://mmbbq.org](http://mmbbq.org) 12 | -- 13 | -- It can be used to pre-process LuaJIT ffi C header file input. 14 | -- It can also be used to preprocess any other code (i.e. Lua itself) 15 | -- 16 | -- git clone https://github.com/willsteel/lcpp.git 17 | ---------------------------------------------------------------------------- 18 | --## USAGE 19 | -- -- load lcpp 20 | -- local lcpp = require("lcpp") 21 | -- 22 | -- -- use LuaJIT ffi and lcpp to parse cpp code 23 | -- local ffi = require("ffi") 24 | -- ffi.cdef("#include ") 25 | -- 26 | -- -- use lcpp manually but add some predefines 27 | -- local lcpp = require("lcpp"); 28 | -- local out = lcpp.compileFile("your_header.h", {UNICODE=1}); 29 | -- print(out); 30 | -- 31 | -- -- compile some input manually 32 | -- local out = lcpp.compile([[ 33 | -- #include "myheader.h" 34 | -- #define MAXPATH 260 35 | -- typedef struct somestruct_t { 36 | -- void* base; 37 | -- size_t size; 38 | -- wchar_t path[MAXPATH]; 39 | -- } t_exe; 40 | -- ]]) 41 | -- -- the result should be like this 42 | -- out == [[ 43 | -- // 44 | -- typedef struct somestruct_t { 45 | -- void* base; 46 | -- size_t size; 47 | -- wchar_t path[260]; 48 | -- } t_exe; 49 | -- ]] 50 | -- 51 | -- -- access lcpp defines dynamically (i.e. if used with ffi) 52 | -- local ffi = require("ffi") 53 | -- local lcpp = require("lcpp") 54 | -- ffi.cdef("#include ") 55 | -- =ffi.lcpp_defs.YOUR_DEFINE 56 | -- 57 | -- 58 | --## This CPPs BNF: 59 | -- RULES: 60 | -- CODE := {LINE} 61 | -- LINE := {STUFF NEWML} STUFF NEWL 62 | -- STUFF := DIRECTIVE | IGNORED_CONTENT 63 | -- DIRECTIVE := OPTSPACES CMD OPTSPACES DIRECTIVE_NAME WHITESPACES DIRECTIVE_CONTENT WHITESPACES NEWL 64 | -- 65 | -- LEAVES: 66 | -- NEWL := "\n" 67 | -- NEWL_ESC := "\\n" 68 | -- WHITESPACES := "[ \t]+" 69 | -- OPTSPACES := "[ \t]*" 70 | -- COMMENT := "//(.-)$" 71 | -- MLCOMMENT := "/[*](.-)[*]/" 72 | -- IGNORED_CONTENT := "[^#].*" 73 | -- CMD := "#" 74 | -- DIRECTIVE_NAME := "include"|"define"|"undef"|"if"|"else"|"elif"|"else if"|"endif"|"ifdef"|"ifndef"|"pragma" 75 | -- DIRECTIVE_CONTENT := ".*?" 76 | -- 77 | --## TODOs: 78 | -- - lcpp.LCPP_LUA for: load, loadfile 79 | -- 80 | --## License (MIT) 81 | -- ----------------------------------------------------------------------------- 82 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 83 | -- of this software and associated documentation files (the "Software"), to deal 84 | -- in the Software without restriction, including without limitation the rights 85 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 86 | -- copies of the Software, and to permit persons to whom the Software is 87 | -- furnished to do so, subject to the following conditions: 88 | -- 89 | -- The above copyright notice and this permission notice shall be included in 90 | -- all copies or substantial portions of the Software. 91 | -- 92 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 93 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 94 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 95 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 96 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 97 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 98 | -- THE SOFTWARE. 99 | -- 100 | -- MIT license: http://www.opensource.org/licenses/mit-license.php 101 | -- ----------------------------------------------------------------------------- 102 | -- 103 | -- @module lcpp 104 | local lcpp = {} 105 | -- check bit is avail or not 106 | local ok, bit = pcall(require, 'bit') 107 | if not ok then 108 | bit = { 109 | lshift = function (x, y) 110 | if y < 0 then return bit.rshift(x,-y) end 111 | return (x * 2^y) % (2^32) 112 | end, 113 | rshift = function (x, y) 114 | if y < 0 then return bit.lshift(x,-y) end 115 | return math.floor(x % (2^32) / (2^y)) 116 | end, 117 | bxor = function (x, y) 118 | -- from http://lua-users.org/wiki/BitUtils 119 | local z = 0 120 | for i = 0, 31 do 121 | if (x % 2 == 0) then -- x had a '0' in bit i 122 | if ( y % 2 == 1) then -- y had a '1' in bit i 123 | y = y - 1 124 | z = z + 2 ^ i -- set bit i of z to '1' 125 | end 126 | else -- x had a '1' in bit i 127 | x = x - 1 128 | if (y % 2 == 0) then -- y had a '0' in bit i 129 | z = z + 2 ^ i -- set bit i of z to '1' 130 | else 131 | y = y - 1 132 | end 133 | end 134 | y = y / 2 135 | x = x / 2 136 | end 137 | return z 138 | end, 139 | bnot = function (x) 140 | -- if word size is not defined, I think it better than 0xFFFFFFFF - x. 141 | return -1 - x 142 | end, 143 | band = function (x, y) 144 | return ((x + y) - bit.bxor(x, y)) / 2 145 | end, 146 | bor = function (x, y) 147 | return bit.bnot(bit.band(bit.bnot(x), bit.bnot(y))) 148 | end, 149 | } 150 | end 151 | 152 | -- CONFIG 153 | lcpp.LCPP_LUA = false -- whether to use lcpp to preprocess Lua code (load, loadfile, loadstring...) 154 | lcpp.LCPP_FFI = true -- whether to use lcpp as LuaJIT ffi PreProcessor (if used in luaJIT) 155 | lcpp.LCPP_TEST = false -- whether to run lcpp unit tests when loading lcpp module 156 | lcpp.ENV = {} -- static predefines (env-like) 157 | lcpp.FAST = false -- perf. tweaks when enabled. con: breaks minor stuff like __LINE__ macros 158 | lcpp.DEBUG = false 159 | 160 | -- PREDEFINES 161 | local __FILE__ = "__FILE__" 162 | local __LINE__ = "__LINE__" 163 | local __DATE__ = "__DATE__" 164 | local __TIME__ = "__TIME__" 165 | local __LCPP_INDENT__ = "__LCPP_INDENT__" 166 | 167 | -- BNF LEAVES 168 | local ENDL = "$" 169 | local STARTL = "^" 170 | local NEWL = "\n" 171 | local NEWL_BYTE = NEWL:byte(1) 172 | local NEWL_ESC = "\\" 173 | local NEWML = "\\\n" 174 | local CMD = "#" 175 | local CMD_BYTE = CMD:byte(1) 176 | local COMMENT = "^(.-)//.-$" 177 | local MLCOMMENT = "/[*].-[*]/" 178 | local WHITESPACES = "%s+" 179 | local OPTSPACES = "%s*" 180 | local IDENTIFIER = "[_%a][_%w]*" 181 | local NOIDENTIFIER = "[^%w_]+" 182 | local FILENAME = "[0-9a-zA-Z.%-_/\\]+" 183 | local TEXT = ".+" 184 | local STRINGIFY = "#" 185 | local STRINGIFY_BYTE = STRINGIFY:byte(1) 186 | local STRING_LITERAL = ".*" 187 | 188 | -- BNF WORDS 189 | local _INCLUDE = "include" 190 | local _INCLUDE_NEXT = "include_next" 191 | local _DEFINE = "define" 192 | local _IFDEF = "ifdef" 193 | local _IFNDEF = "ifndef" 194 | local _ENDIF = "endif" 195 | local _UNDEF = "undef" 196 | local _IF = "if" 197 | local _ELSE = "else" 198 | local _ELIF = "elif" 199 | local _NOT = "!" 200 | local _ERROR = "error" 201 | local _WARNING = "warning" 202 | local _PRAGMA = "pragma" 203 | 204 | -- BNF RULES 205 | local INCLUDE = STARTL.._INCLUDE..WHITESPACES.."[<]("..FILENAME..")[>]"..OPTSPACES..ENDL 206 | local LOCAL_INCLUDE = STARTL.._INCLUDE..WHITESPACES.."[\"]("..FILENAME..")[\"]"..OPTSPACES..ENDL 207 | local INCLUDE_NEXT = STARTL.._INCLUDE_NEXT..WHITESPACES.."[\"<]("..FILENAME..")[\">]"..OPTSPACES..ENDL 208 | local DEFINE = STARTL.._DEFINE 209 | local IFDEF = STARTL.._IFDEF..WHITESPACES.."("..IDENTIFIER..")"..OPTSPACES..ENDL 210 | local IFNDEF = STARTL.._IFNDEF..WHITESPACES.."("..IDENTIFIER..")"..OPTSPACES..ENDL 211 | local ENDIF = STARTL.._ENDIF..OPTSPACES..ENDL 212 | local UNDEF = STARTL.._UNDEF..WHITESPACES.."("..IDENTIFIER..")"..OPTSPACES..ENDL 213 | local IF = STARTL.._IF..WHITESPACES.."(.*)"..ENDL 214 | local ELSE = STARTL.._ELSE..OPTSPACES..ENDL 215 | local ELIF = STARTL.._ELIF..WHITESPACES.."(.*)"..ENDL 216 | local ELSEIF = STARTL.._ELSE..WHITESPACES.._IF..WHITESPACES.."(.*)"..ENDL 217 | local ERROR = STARTL.._ERROR..WHITESPACES.."("..TEXT..")"..OPTSPACES..ENDL 218 | local WARNING = STARTL.._WARNING..WHITESPACES.."("..TEXT..")"..OPTSPACES..ENDL 219 | local ERROR_NOTEXT = STARTL.._ERROR..OPTSPACES..ENDL --> not required when we have POSIX regex 220 | local PRAGMA = STARTL.._PRAGMA 221 | 222 | -- speedups 223 | local TRUEMACRO = STARTL.."("..IDENTIFIER..")%s*$" 224 | local REPLMACRO = STARTL.."("..IDENTIFIER..")"..WHITESPACES.."(.+)$" 225 | local FUNCMACRO = STARTL.."("..IDENTIFIER..")%(([_%s%w,]*)%)%s*(.*)" 226 | 227 | 228 | -- ------------ 229 | -- LOCAL UTILS 230 | -- ------------ 231 | lcpp.STATE = {lineno = 0} -- current state for debugging the last operation 232 | local function error(msg) _G.print(debug.traceback()); _G.error(string.format("lcpp ERR [%04i] %s", lcpp.STATE.lineno, msg)) end 233 | local function print(msg) _G.print(string.format("lcpp INF [%04i] %s", lcpp.STATE.lineno, msg)) end 234 | 235 | -- splits a string using a pattern into a table of substrings 236 | local function gsplit(str, pat) 237 | local function _split(str, pat) 238 | local t = {} -- NOTE: use {n = 0} in Lua-5.0 239 | local fpat = "(.-)"..pat 240 | local last_end = 1 241 | local s, e, cap = str:find(fpat, 1) 242 | while s do 243 | if s ~= 1 or cap ~= "" then 244 | coroutine.yield(cap) 245 | end 246 | last_end = e + 1 247 | s, e, cap = str:find(fpat, last_end) 248 | end 249 | if last_end <= #str then 250 | cap = str:sub(last_end) 251 | coroutine.yield(cap) 252 | end 253 | end 254 | return coroutine.wrap(function() _split(str, pat) end) 255 | end 256 | local function split(str, pat) 257 | local t = {} 258 | for str in gsplit(str, pat) do table.insert(t, str) end 259 | return t 260 | end 261 | 262 | -- Checks whether a string starts with a given substring 263 | -- offset is optional 264 | local function strsw(str, pat, offset) 265 | if not str then return false end 266 | if not offset then offset = 0 end 267 | return string.sub(str, 1+offset, string.len(pat)+offset) == pat 268 | end 269 | 270 | -- Checks whether a string ends with a given substring 271 | local function strew(str, pat) 272 | if not str then return false end 273 | return pat=='' or string.sub(str,-string.len(pat)) == pat 274 | end 275 | 276 | -- string trim12 from lua wiki 277 | local function trim(str) 278 | local from = str:match"^%s*()" 279 | return from > #str and "" or str:match(".*%S", from) 280 | end 281 | 282 | -- returns the number of string occurrences 283 | local function findn(input, what) 284 | local count = 0 285 | local offset = 0 286 | local _ 287 | while true do 288 | _, offset = string.find(input, what, offset+1, true) 289 | if not offset then return count end 290 | count = count + 1 291 | end 292 | end 293 | 294 | -- C literal string concatenation 295 | local function concatStringLiteral(input) 296 | -- screener does remove multiline definition, so just check ".*"%s*".*" pattern 297 | return input:gsub("\"("..STRING_LITERAL..")\""..OPTSPACES.."\"("..STRING_LITERAL..")\"", "\"%1%2\"") 298 | end 299 | 300 | -- c style boolean check (thus, 0 will be false) 301 | local function CBoolean(value) 302 | return value and (value ~= 0) 303 | end 304 | 305 | -- eval with c style number parse (UL, LL, L) 306 | local function CEval(expr) 307 | local ok, r = pcall(loadstring, "return " .. parseCInteger(expr)) 308 | if ok and r then 309 | return r() 310 | else 311 | error(r) 312 | end 313 | end 314 | 315 | -- a lightweight and flexible tokenizer 316 | local function _tokenizer(str, setup) 317 | local defsetup = { 318 | -- EXAMPLE patterns have to be pretended with "^" for the tokenizer 319 | ["identifier"] = '^[_%a][_%w]*', 320 | ["number"] = '^[%+%-]?%d+[%.]?%d*[UL]*', 321 | ["ignore"] = '^%s+', 322 | ["string"] = true, 323 | ["keywords"] = { 324 | -- ["NAME"] = '^pattern', 325 | -- ... 326 | }, 327 | } 328 | if not setup then 329 | setup = defsetup 330 | end 331 | setup.identifier = setup.identifier or defsetup.identifier 332 | setup.number = setup.number or defsetup.number 333 | setup.ignore = setup.ignore or defsetup.ignore 334 | if nil == setup.string then setup.string = true end 335 | setup.keywords = setup.keywords or {} 336 | 337 | local strlen = #str 338 | local i = 1 339 | local i1, i2 340 | local keyword 341 | 342 | local function find(pat) 343 | i1, i2 = str:find(pat,i) 344 | return i1 ~= nil 345 | end 346 | 347 | local function cut() 348 | return str:sub(i, i2) 349 | end 350 | 351 | local findKeyword 352 | if setup.keywords_order then 353 | findKeyword = function () 354 | for _, name in ipairs(setup.keywords_order) do 355 | assert(setup.keywords[name]) 356 | local pat = setup.keywords[name] 357 | local result = find(pat) 358 | if result then 359 | keyword = name 360 | return true 361 | end 362 | end 363 | end 364 | else 365 | findKeyword = function () 366 | for name, pat in pairs(setup.keywords) do 367 | local result = find(pat) 368 | if result then 369 | keyword = name 370 | return true 371 | end 372 | end 373 | end 374 | end 375 | 376 | while true do 377 | if i > strlen then return 'eof', nil, strlen, strlen end 378 | if findKeyword() then 379 | coroutine.yield(keyword, cut(), i1, i2) 380 | elseif find(setup.ignore) then 381 | coroutine.yield("ignore", cut(), i1, i2) 382 | elseif find(setup.number) then 383 | coroutine.yield('number', tonumber(cut()), i1, i2) 384 | elseif find(setup.identifier) then 385 | coroutine.yield('identifier', cut(), i1, i2) 386 | elseif setup.string and (find('^"[^"]*"') or find("^'[^']*'")) then 387 | -- strip the quotes 388 | coroutine.yield('string', cut():sub(2,-2), i1, i2) 389 | else -- any other unknown character 390 | i1 = i 391 | i2 = i 392 | coroutine.yield('unknown', cut(), i1, i2) 393 | end 394 | i = i2+1 395 | end 396 | end 397 | local function tokenizer(str, setup) 398 | return coroutine.wrap(function() _tokenizer(str, setup) end) 399 | end 400 | 401 | 402 | -- ------------ 403 | -- PARSER 404 | -- ------------ 405 | 406 | local LCPP_TOKENIZE_COMMENT = { 407 | string = false, 408 | keywords = { 409 | MLCOMMENT = "^/%*.-%*/", 410 | SLCOMMENT = "^//.-\n", 411 | STRING_LITERAL = '^"[^"]*"', 412 | }, 413 | } 414 | -- hint: LuaJIT ffi does not rely on us to remove the comments, but maybe other usecases 415 | local function removeComments(input) 416 | local out = {} 417 | for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_COMMENT) do 418 | if k == "MLCOMMENT" then 419 | local newlineCount = findn(input:sub(start, end_), "\n") 420 | local newlines = string.rep("\n", newlineCount) 421 | table.insert(out, newlines) 422 | elseif k == "SLCOMMENT" then 423 | table.insert(out, "\n") 424 | else 425 | table.insert(out, input:sub(start, end_)) 426 | end 427 | end 428 | return table.concat(out) 429 | end 430 | 431 | -- C style number parse (UL, LL, L) and (octet, hex, binary) 432 | local LCPP_TOKENIZE_INTEGER = { 433 | string = false, 434 | keywords_order = { 435 | "STRING_LITERAL", 436 | "CHAR_LITERAL", 437 | "HEX_LITERAL", 438 | "BIN_LITERAL", 439 | "OCT_LITERAL", 440 | "FPNUM_LITERAL", 441 | "NUMBER_LITERAL", 442 | }, 443 | keywords = { 444 | STRING_LITERAL = '^"[^"]*"', 445 | CHAR_LITERAL = "^L'.*'", 446 | HEX_LITERAL = '^[%+%-]?%s*0x[a-fA-F%d]+[UL]*', 447 | BIN_LITERAL = '^[%+%-]?%s*0b%d+[UL]*', 448 | OCT_LITERAL = '^[%+%-]?%s*0%d+[UL]*', 449 | FPNUM_LITERAL = '^[%+%-]?%s*%d+[%.]?%d*e[%+%-]%d*', 450 | NUMBER_LITERAL = '^[%+%-]?%s*%d+[%.]?%d*[UL]+', 451 | }, 452 | } 453 | local function parseCInteger(input) 454 | -- print('parseCInteger:input:' .. input) 455 | local out = {} 456 | local unary 457 | for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_INTEGER) do 458 | -- print('parseCInteger:' .. k .. "|" .. v) 459 | if k == "CHAR_LITERAL" then 460 | table.insert(out, tostring(string.byte(loadstring("return \"" .. v:gsub("^L%'(.+)%'", "%1") .. "\"")()))) 461 | elseif k == "HEX_LITERAL" then 462 | unary, v = v:match('([%+%-]?)0x([a-fA-F%d]+)[UL]*') 463 | local n = tonumber(v, 16) 464 | table.insert(out, unary..tostring(n)) 465 | elseif k == "NUMBER_LITERAL" then 466 | v = v:match('([^UL]+)[UL]+') 467 | table.insert(out, v) 468 | elseif k == "BIN_LITERAL" then 469 | unary, v = v:match('([%+%-]?)0b([01]+)[UL]*') 470 | local n = tonumber(v, 2) 471 | table.insert(out, unary..tostring(n)) 472 | elseif k == "OCT_LITERAL" then 473 | unary, v = v:match('([%+%-]?)(0%d+)[UL]*') 474 | local n = tonumber(v, 8) 475 | table.insert(out, unary..tostring(n)) 476 | else 477 | table.insert(out, input:sub(start, end_)) 478 | end 479 | end 480 | local str = table.concat(out) 481 | -- print('parseCInteger:result:'..str) 482 | return str 483 | end 484 | 485 | -- screener: revmoce comments, trim, ml concat... 486 | -- it only splits to cpp input lines and removes comments. it does not tokenize. 487 | local function screener(input) 488 | local function _screener(input) 489 | input = removeComments(input) 490 | 491 | -- concat mulit-line input. 492 | local count = 1 493 | while count > 0 do input, count = string.gsub(input, "^(.-)\\\n(.-)$", "%1 %2\n") end 494 | 495 | -- trim and join blocks not starting with "#" 496 | local buffer = {} 497 | for line in gsplit(input, NEWL) do 498 | --print('newline:'..line) 499 | line = trim(line) 500 | if #line > 0 then 501 | if line:byte(1) == CMD_BYTE then 502 | line = line:gsub("#%s*(.*)", "#%1") -- remove optinal whitespaces after "#". reduce triming later. 503 | if #buffer > 0 then 504 | coroutine.yield(table.concat(buffer, NEWL)) 505 | buffer = {} 506 | end 507 | coroutine.yield(line) 508 | else 509 | if lcpp.FAST then 510 | table.insert(buffer, line) 511 | else 512 | coroutine.yield(line) 513 | end 514 | end 515 | elseif not lcpp.FAST then 516 | coroutine.yield(line) 517 | end 518 | end 519 | if #buffer > 0 then 520 | coroutine.yield(table.concat(buffer, NEWL)) 521 | end 522 | end 523 | 524 | return coroutine.wrap(function() _screener(input) end) 525 | end 526 | 527 | -- apply currently known macros to input (and returns it) 528 | local LCPP_TOKENIZE_APPLY_MACRO = { 529 | keywords = { 530 | DEFINED = "^defined%s*%(%s*"..IDENTIFIER.."%s*%)" , 531 | }, 532 | } 533 | local function apply(state, input) 534 | while true do 535 | local out = {} 536 | local functions = {} 537 | local expand 538 | 539 | for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_APPLY_MACRO) do 540 | -- print('tokenize:'..tostring(k).."|"..tostring(v)) 541 | if k == "identifier" then 542 | local repl = v 543 | local macro = state.defines[v] 544 | if macro then 545 | if type(macro) == "boolean" then 546 | repl = "" 547 | expand = true 548 | elseif type(macro) == "string" then 549 | repl = macro 550 | expand = (repl ~= v) 551 | elseif type(macro) == "number" then 552 | repl = tostring(macro) 553 | expand = (repl ~= v) 554 | elseif type(macro) == "function" then 555 | local decl,cnt = input:sub(start):gsub("^[_%a][_%w]*%s*%b()", "%1") 556 | -- print('matching:'..input.."|"..decl.."|"..cnt) 557 | if cnt > 0 then 558 | repl = macro(decl) 559 | -- print("d&r:"..decl.."|"..repl) 560 | expand = true 561 | table.insert(out, repl) 562 | table.insert(out, input:sub(end_ + #decl)) 563 | break 564 | else 565 | if input:sub(start):find("^[_%a][_%w]*%s*%(") then 566 | -- that is part of functional macro declaration. 567 | -- print(v ..': cannot replace:<'..input..'> read more line') 568 | return input,true 569 | else 570 | -- on macro name is also used as the symbol of some C declaration 571 | -- (e.g. /usr/include/spawn.h, /usr/include/sys/select.h on centos 6.4) 572 | -- no need to preprocess. 573 | print(v .. ': macro name but used as C declaration in:' .. input) 574 | end 575 | end 576 | end 577 | end 578 | table.insert(out, repl) 579 | elseif k == "DEFINED" then 580 | table.insert(out, input:sub(start, end_)) 581 | else 582 | table.insert(out, input:sub(start, end_)) 583 | end 584 | end 585 | input = table.concat(out) 586 | if not expand then 587 | break 588 | end 589 | end 590 | 591 | -- C liberal string concatenation, processing U,L,UL,LL 592 | return parseCInteger(concatStringLiteral(input)),false 593 | end 594 | 595 | -- processes an input line. called from lcpp doWork loop 596 | local function processLine(state, line) 597 | if not line or #line == 0 then return line end 598 | local cmd = nil 599 | if line:byte(1) == CMD_BYTE then cmd = line:sub(2) end 600 | -- print("process:"..line)--.."|"..tostring(state:skip())) 601 | 602 | --[[ IF/THEN/ELSE STRUCTURAL BLOCKS ]]-- 603 | if cmd then 604 | local ifdef = cmd:match(IFDEF) 605 | local ifexp = cmd:match(IF) 606 | local ifndef = cmd:match(IFNDEF) 607 | local elif = cmd:match(ELIF) 608 | local elseif_ = cmd:match(ELSEIF) 609 | local else_ = cmd:match(ELSE) 610 | local endif = cmd:match(ENDIF) 611 | local struct = ifdef or ifexp or ifndef or elif or elseif_ or else_ or endif 612 | 613 | if struct then 614 | local skip = state:skip() 615 | if ifdef then state:openBlock(state:defined(ifdef)) end 616 | -- if skipped, it may have undefined expression. so not parse them 617 | if ifexp then state:openBlock(skip and true or CBoolean(state:parseExpr(ifexp))) end 618 | if ifndef then state:openBlock(not state:defined(ifndef)) end 619 | if elif then state:elseBlock((skip and skip < #state.stack) and true or CBoolean(state:parseExpr(elif))) end 620 | if elseif_ then state:elseBlock((skip and skip < #state.stack) and true or CBoolean(state:parseExpr(elseif_))) end 621 | if else_ then state:elseBlock(true) end 622 | if endif then state:closeBlock() end 623 | return -- remove structural directives 624 | end 625 | end 626 | 627 | 628 | --[[ SKIPPING ]]-- 629 | if state:skip() then 630 | -- print('skip:' .. line) 631 | return 632 | end 633 | 634 | 635 | --[[ READ NEW DIRECTIVES ]]-- 636 | if cmd then 637 | -- handle #undef ... 638 | local key = cmd:match(UNDEF) 639 | if type(key) == "string" then 640 | state:undefine(key) 641 | return 642 | end 643 | 644 | -- read "#define >FooBar...<" directives 645 | if cmd:match(DEFINE) then 646 | local define = trim(cmd:sub(DEFINE:len()+1)) 647 | local macroname, replacement 648 | 649 | -- simple "true" defines 650 | macroname = define:match(TRUEMACRO) 651 | if macroname then 652 | state:define(macroname, true) 653 | else 654 | 655 | -- replace macro defines 656 | macroname, replacement = define:match(REPLMACRO) 657 | if macroname and replacement then 658 | state:define(macroname, replacement) 659 | else 660 | 661 | -- read functional macros 662 | macroname, replacement, source = state:parseFunction(define) 663 | if macroname and replacement then 664 | -- add original text for definition to check identify 665 | state:define(macroname, replacement, false, source) 666 | end 667 | 668 | end 669 | end 670 | 671 | return 672 | end 673 | 674 | -- handle #include ... 675 | local filename = cmd:match(INCLUDE) 676 | if filename then 677 | return state:includeFile(filename) 678 | end 679 | local filename = cmd:match(LOCAL_INCLUDE) 680 | if filename then 681 | return state:includeFile(filename, false, true) 682 | end 683 | local filename = cmd:match(INCLUDE_NEXT) 684 | if filename then 685 | --print("include_next:"..filename) 686 | return state:includeFile(filename, true) 687 | end 688 | 689 | -- ignore, because we dont have any pragma directives yet 690 | if cmd:match(PRAGMA) then return end 691 | 692 | -- handle #error 693 | local errMsg = cmd:match(ERROR) 694 | local errNoTxt = cmd:match(ERROR_NOTEXT) 695 | local warnMsg = cmd:match(WARNING) 696 | if errMsg then error(errMsg) end 697 | if errNoTxt then error("") end 698 | if warnMsg then 699 | print(warnMsg) 700 | return 701 | end 702 | 703 | -- abort on unknown keywords 704 | error("unknown directive: "..line) 705 | end 706 | 707 | if state.incompleteLine then 708 | --print('merge with incompleteLine:'..state.incompleteLine) 709 | line = (state.incompleteLine .. line) 710 | state.incompleteLine = nil 711 | end 712 | 713 | 714 | --[[ APPLY MACROS ]]-- 715 | -- print(line) 716 | local _line,more = state:apply(line); 717 | -- print('endprocess:'.._line) 718 | if more then 719 | state.incompleteLine = line 720 | return "" 721 | else 722 | return _line 723 | end 724 | 725 | return line 726 | end 727 | 728 | local function doWork(state) 729 | local function _doWork(state) 730 | if not state:defined(__FILE__) then state:define(__FILE__, "", true) end 731 | local oldIndent = state:getIndent() 732 | while true do 733 | local input = state:getLine() 734 | if not input then break end 735 | local output = processLine(state, input) 736 | if not lcpp.FAST and not output then output = "" end -- output empty skipped lines 737 | if lcpp.DEBUG then output = output.." -- "..input end -- input as comment when DEBUG 738 | if output then coroutine.yield(output) end 739 | end 740 | if (oldIndent ~= state:getIndent()) then error("indentation level must be balanced within a file. was:"..oldIndent.." is:"..state:getIndent()) end 741 | end 742 | return coroutine.wrap(function() _doWork(state) end) 743 | end 744 | 745 | local function includeFile(state, filename, next, _local) 746 | local result, result_state = lcpp.compileFile(filename, state.defines, state.macro_sources, next, _local) 747 | -- now, we take the define table of the sub file for further processing 748 | state.defines = result_state.defines 749 | -- and return the compiled result 750 | return result 751 | end 752 | 753 | -- sets a global define 754 | local function define(state, key, value, override, macro_source) 755 | --print("define:"..key.." type:"..tostring(value).." value:"..tostring(pval)) 756 | if value and not override then 757 | if type(value) == 'function' then 758 | assert(macro_source, "macro source should specify to check identity") 759 | local pval = state.macro_sources[key] 760 | if pval and (pval ~= macro_source) then error("already defined: "..key) end 761 | state.macro_sources[key] = macro_source 762 | else 763 | local pval = state.defines[key] 764 | if pval and (pval ~= value) then error("already defined: "..key) end 765 | end 766 | end 767 | state.defines[key] = state:prepareMacro(value) 768 | end 769 | 770 | -- parses CPP exressions 771 | -- i.e.: #if !defined(_UNICODE) && !defined(UNICODE) 772 | -- 773 | --BNF: 774 | -- EXPR -> (BRACKET_OPEN)(EXPR)(BRACKET_CLOSE) 775 | -- EXPR -> (EXPR)(OR)(EXPR) 776 | -- EXPR -> (EXPR)(AND)(EXPR) 777 | -- EXPR -> (NOT)(EXPR) 778 | -- EXPR -> (FUNCTION) 779 | -- FUNCTION -> (IDENTIFIER)(BRACKET_OPEN)(ARGS)(BRACKET_CLOSE) 780 | -- ARGS -> ((IDENTIFIER)[(COMMA)(IDENTIFIER)])? 781 | --LEAVES: 782 | -- IGNORE -> " \t" 783 | -- BRACKET_OPEN -> "(" 784 | -- BRACKET_CLOSE -> ")" 785 | -- OR -> "||" 786 | -- AND -> "&&" 787 | -- NOT -> "!" 788 | -- IDENTIFIER -> "[0-9a-zA-Z_]" 789 | -- 790 | 791 | local LCPP_TOKENIZE_MACRO = { 792 | string = true, 793 | keywords_order = { 794 | "CONCAT", 795 | "SPACE", 796 | }, 797 | keywords = { 798 | CONCAT = "^%s*##%s*", 799 | SPACE = "^%s", 800 | }, 801 | } 802 | local LCPP_TOKENIZE_MACRO_ARGS = { 803 | string = true, 804 | keywords_order = { 805 | "STRING_LITERAL", 806 | "PARENTHESE", 807 | "FUNCTIONAL", 808 | "ARGS", 809 | "SINGLE_CHARACTER_ARGS", 810 | "COMMA", 811 | }, 812 | keywords = { 813 | PARENTHESE = "^%s*%b()", 814 | FUNCTIONAL = "^".. IDENTIFIER .. "%s*%b()", 815 | STRING_LITERAL = '^"[^"]*"', 816 | ARGS = "^[^,%s][^,]*[^,%s]", 817 | SINGLE_CHARACTER_ARGS = "^[^,%s]", 818 | COMMA = "^,", 819 | }, 820 | } 821 | local LCPP_TOKENIZE_EXPR = { 822 | string = false, 823 | keywords_order = { 824 | "DEFINED", 825 | "FUNCTIONAL_MACRO", 826 | "BROPEN", 827 | "BRCLOSE", 828 | 829 | "TENARY_START", 830 | "TENARY_MIDDLE", 831 | -- binary operators 832 | "EQUAL", 833 | "NOT_EQUAL", 834 | "AND", 835 | "OR", 836 | "BAND", 837 | "BOR", 838 | "BXOR", 839 | "PLUS", 840 | "MINUS", 841 | "MULTIPLY", 842 | "DIV", 843 | "MOD", 844 | "LTE", 845 | "MTE", 846 | "LSHIFT", 847 | "RSHIFT", 848 | "LT", 849 | "MT", 850 | -- unary operator 851 | "NOT", 852 | "BNOT", 853 | -- literal 854 | "STRING_LITERAL", 855 | "CHAR_LITERAL", 856 | "HEX_LITERAL", 857 | "FPNUM_LITERAL", 858 | "NUMBER_LITERAL", 859 | }, 860 | keywords = { 861 | DEFINED = '^defined', 862 | FUNCTIONAL_MACRO = '^' .. IDENTIFIER .. "%s*%b()", 863 | BROPEN = '^[(]', 864 | BRCLOSE = '^[)]', 865 | 866 | TENARY_START = '^%?', 867 | TENARY_MIDDLE = '^%:', 868 | 869 | EQUAL = '^==', 870 | NOT_EQUAL = '^!=', 871 | AND = '^&&', 872 | OR = '^||', 873 | BAND = '^&', 874 | BOR = '^|', 875 | BXOR = '^%^', 876 | PLUS = '^%+', 877 | MINUS = '^%-', 878 | MULTIPLY = '^%*', 879 | DIV = '^%/', 880 | MOD = '^%%', 881 | LTE = '^<=', 882 | MTE = '^>=', 883 | LSHIFT = '^<<', 884 | RSHIFT = '^>>', 885 | LT = '^<', 886 | MT = '^>', 887 | 888 | NOT = '^!', 889 | BNOT = '^~', 890 | 891 | STRING_LITERAL = '^L?"[^"]*"', 892 | CHAR_LITERAL = "^L?'.*'", 893 | HEX_LITERAL = '^[%+%-]?0?x[a-fA-F%d]+[UL]*', 894 | FPNUM_LITERAL = '^[%+%-]?%d+[%.]?%d*e[%+%-]%d*', 895 | NUMBER_LITERAL = '^[%+%-]?0?b?%d+[%.]?%d*[UL]*', 896 | }, 897 | } 898 | 899 | local function parseDefined(state, input) 900 | local result = false 901 | local bropen = false 902 | local brclose = false 903 | local ident = nil 904 | 905 | for key, value in input do 906 | if key == "BROPEN" then 907 | bropen = true 908 | end 909 | if key == "identifier" then 910 | ident = value 911 | if not bropen then break end 912 | end 913 | if key == "BRCLOSE" and ident then 914 | brclose = true 915 | break 916 | end 917 | end 918 | 919 | -- wiht and w/o brackets allowed 920 | if ident and ((bropen and brclose) or (not bropen and not brclose)) then 921 | return state:defined(ident) 922 | end 923 | 924 | error("expression parse error: defined(ident)") 925 | end 926 | 927 | 928 | --[[ 929 | order : smaller is higher priority 930 | 1 () [] -> . 931 | 2 ! ~ - + * & sizeof type cast ++ -- 932 | 3 * / % 933 | 4 + - 934 | 5 << >> 935 | 6 < <= > >= 936 | 7 == != 937 | 8 & 938 | 9 ^ 939 | 10 | 940 | 11 && 941 | 12 || 942 | 13 ?: = += -= *= /= %= &= |= ^= <<= >>= 943 | 14 , 944 | ]] 945 | local combination_order = function (op, unary) 946 | if unary then 947 | if op == '-' or op == '!' or op == '~' then 948 | return 2 949 | else 950 | assert(false, 'unsupported unary operator:' .. op) 951 | end 952 | else 953 | if op == '*' or op == '/' or op == '%' then 954 | return 3 955 | elseif op == '+' or op == '-' then 956 | return 4 957 | elseif op == '>>' or op == '<<' then 958 | return 5 959 | elseif op == '<' or op == '>' or op == '<=' or op == '>=' then 960 | return 6 961 | elseif op == '==' or op == '!=' then 962 | return 7 963 | elseif op == '&' then 964 | return 8 965 | elseif op == '^' then 966 | return 9 967 | elseif op == '|' then 968 | return 10 969 | elseif op == '&&' then 970 | return 11 971 | elseif op == '||' then 972 | return 12 973 | elseif op == '?' or op == ':' then 974 | return 13 975 | else 976 | assert(false, 'unsupported operator:' .. op) 977 | end 978 | end 979 | end 980 | 981 | local evaluate 982 | evaluate = function (node) 983 | if not node.op then -- leaf node or leaf node with unary operators 984 | local v = node.v 985 | if node.uops then 986 | for _, uop in ipairs(node.uops) do 987 | -- print('apply uop:'..uop.."|"..tostring(v)) 988 | if uop == '-' then 989 | v = -v 990 | elseif uop == '!' then 991 | v = (not v) 992 | elseif uop == '~' then 993 | v = bit.bnot(v) 994 | else 995 | assert(false, 'invalid uop:' .. tostring(uop)) 996 | end 997 | end 998 | end 999 | -- print('after apply:'..tostring(v)) 1000 | return v 1001 | end 1002 | -- print(node.op..':'..tostring(node.l.v or node.l.op).."("..type(node.l.v)..")|"..tostring(node.r.v or node.r.op).."("..type(node.r.v)..")") 1003 | if node.op == '+' then -- binary operators 1004 | return (evaluate(node.l) + evaluate(node.r)) 1005 | elseif node.op == '-' then 1006 | return (evaluate(node.l) - evaluate(node.r)) 1007 | elseif node.op == '*' then 1008 | return (evaluate(node.l) * evaluate(node.r)) 1009 | elseif node.op == '/' then 1010 | return (evaluate(node.l) / evaluate(node.r)) 1011 | elseif node.op == '%' then 1012 | return (evaluate(node.l) % evaluate(node.r)) 1013 | elseif node.op == '==' then 1014 | return (evaluate(node.l) == evaluate(node.r)) 1015 | elseif node.op == '!=' then 1016 | return (evaluate(node.l) ~= evaluate(node.r)) 1017 | elseif node.op == '<<' then 1018 | return bit.lshift(evaluate(node.l), evaluate(node.r)) 1019 | elseif node.op == '>>' then 1020 | return bit.rshift(evaluate(node.l), evaluate(node.r)) 1021 | elseif node.op == '&&' then 1022 | return (CBoolean(evaluate(node.l)) and CBoolean(evaluate(node.r))) 1023 | elseif node.op == '||' then 1024 | return (CBoolean(evaluate(node.l)) or CBoolean(evaluate(node.r))) 1025 | elseif node.op == '&' then 1026 | return bit.band(evaluate(node.l), evaluate(node.r)) 1027 | elseif node.op == '|' then 1028 | return bit.bor(evaluate(node.l), evaluate(node.r)) 1029 | elseif node.op == '^' then 1030 | return bit.bxor(evaluate(node.l), evaluate(node.r)) 1031 | elseif node.op == '<=' then 1032 | return (evaluate(node.l) <= evaluate(node.r)) 1033 | elseif node.op == '>=' then 1034 | return (evaluate(node.l) >= evaluate(node.r)) 1035 | elseif node.op == '<' then 1036 | return (evaluate(node.l) < evaluate(node.r)) 1037 | elseif node.op == '>' then 1038 | return (evaluate(node.l) > evaluate(node.r)) 1039 | else 1040 | assert(false, 'invalid op:' .. tostring(node.op)) 1041 | end 1042 | end 1043 | 1044 | local function setValue(node, v) 1045 | -- print('setValue:' .. tostring(v).."|"..tostring(node.uops))-- .. "\t" .. debug.traceback()) 1046 | if not node.op then 1047 | assert(not node.v, debug.traceback()) 1048 | node.v = v 1049 | else 1050 | assert(node.l and (not node.r)) 1051 | node.r = {v = v, uops = node.uops} 1052 | end 1053 | end 1054 | 1055 | local function setUnaryOp(node, uop) 1056 | -- print('setUnaryOp:' .. tostring(uop))-- .. "\t" .. debug.traceback()) 1057 | if not node.uops then node.uops = {} end 1058 | table.insert(node.uops, 1, uop) 1059 | end 1060 | 1061 | local function parseExpr(state, input) 1062 | local node = {} 1063 | local root = node 1064 | -- first call gets string input. rest uses tokenizer 1065 | if type(input) == "string" then 1066 | -- print('parse:' .. input) 1067 | input = tokenizer(input, LCPP_TOKENIZE_EXPR) 1068 | end 1069 | 1070 | for type, value in input do 1071 | -- print("type:"..type.." value:"..value) 1072 | -- unary operator 1073 | if type == "NOT" or 1074 | type == "BNOT" then 1075 | setUnaryOp(node, value) 1076 | end 1077 | if type == "BROPEN" then 1078 | setValue(node, state:parseExpr(input)) 1079 | end 1080 | if type == "BRCLOSE" then 1081 | --print('BRCLOSE:' .. tostring(result)) 1082 | break 1083 | end 1084 | if type == "STRING_LITERAL" then 1085 | setValue(node, value:sub(value[1] == 'L' and 3 or 2,-2)) 1086 | end 1087 | if type == "NUMBER_LITERAL" or type == "HEX_LITERAL" or type == "FPNUM_LITERAL" or type == "CHAR_LITERAL" then 1088 | setValue(node, tonumber(parseCInteger(value))) 1089 | end 1090 | -- tenary operator 1091 | -- tenary has lowest priority, so any other operation can be calculate now. 1092 | if type == "TENARY_START" then 1093 | local l = state:parseExpr(input) 1094 | local r = state:parseExpr(input) 1095 | if evaluate(root) then 1096 | return l 1097 | else 1098 | return r 1099 | end 1100 | end 1101 | if type == "TENARY_MIDDLE" then 1102 | break 1103 | end 1104 | -- binary operator 1105 | if type == "EQUAL" or 1106 | type == "NOT_EQUAL" or 1107 | type == "AND" or 1108 | type == "OR" or 1109 | type == "BAND" or 1110 | type == "BOR" or 1111 | type == "BXOR" or 1112 | type == "PLUS" or 1113 | type == "MINUS" or 1114 | type == "MULTIPLY" or 1115 | type == "DIV" or 1116 | type == "MOD" or 1117 | type == "LTE" or 1118 | type == "MTE" or 1119 | type == "LSHIFT" or 1120 | type == "RSHIFT" or 1121 | type == "LT" or 1122 | type == "MT" then 1123 | if node.op then 1124 | if not node.r then -- during parse right operand : uop1 uop2 ... uopN operand1 op1 uop(N+1) uop(N+2) ... [uop(N+K)] 1125 | assert(type == "MINUS", "error: operators come consequently: " .. tostring(node.op) .. " and " .. tostring(value)) 1126 | -- unary operater after binary operator 1127 | setUnaryOp(node, value) 1128 | else -- uop1 uop2 ... uopN operand1 op1 uop(N+1) uop(N+2) ... uop(N+M) operand2 [op2] 1129 | -- print("operator processing:" .. tostring(node.op) .. "|" .. value .. "|" .. tostring(node.l) .. "|" .. tostring(node.r)) 1130 | local tmp = node 1131 | while tmp do 1132 | -- print('compare ' .. value..' and ' .. tmp.op) 1133 | if combination_order(tmp.op) > combination_order(value) then 1134 | -- print(value..' is stronger than ' .. tmp.op) 1135 | break 1136 | end 1137 | tmp = tmp.parent 1138 | end 1139 | if tmp then 1140 | node = { 1141 | op = value, 1142 | l = tmp.r, 1143 | parent = tmp 1144 | } 1145 | tmp.r.parent = node 1146 | tmp.r = node 1147 | else 1148 | node = { 1149 | op = value, 1150 | l = root, 1151 | } 1152 | root.parent = node 1153 | root = node 1154 | end 1155 | end 1156 | elseif node.v ~= nil then -- uop1 uop2 ... uopN operand1 [op] 1157 | local devided 1158 | if node.uops then 1159 | for _, uop in ipairs(node.uops) do 1160 | if combination_order(uop, true) > combination_order(value) then 1161 | -- there is a binary operator which has stronger than any of the unary 1162 | devided = uop 1163 | end 1164 | end 1165 | end 1166 | if devided then 1167 | assert(false, "TODO: can we do something about this case??:"..value.." is stronger than "..devided) 1168 | else 1169 | node.l = { v = node.v, uops = node.uops } 1170 | node.v = nil 1171 | node.uops = nil 1172 | node.op = value 1173 | end 1174 | else -- unary operator : uop1 uop2 ... [uopN] 1175 | assert(type == "MINUS", "error: invalid unary operator:" .. value) 1176 | setUnaryOp(node, value) 1177 | end 1178 | end 1179 | if type == "DEFINED" then 1180 | setValue(node, parseDefined(state, input)) 1181 | elseif type == "identifier" or type == "FUNCTIONAL_MACRO" then 1182 | -- print('ident:' .. value) 1183 | local eval = state:apply(value) 1184 | -- print('apply result ' .. eval .. "|" .. tostring(unprocessed)) 1185 | if eval ~= value then 1186 | eval = state:parseExpr(eval) 1187 | -- print('re-evaluate expr ' .. tostring(eval)) 1188 | setValue(node, eval) 1189 | else 1190 | -- undefined macro symbol is always treated as 0. 1191 | -- http://gcc.gnu.org/onlinedocs/cpp/If.html#If 1192 | setValue(node, 0) 1193 | end 1194 | end 1195 | end 1196 | 1197 | local r = evaluate(root) 1198 | -- print('evaluate:' .. tostring(r)) 1199 | return r 1200 | end 1201 | 1202 | -- apply string ops "##" 1203 | local function prepareMacro(state, input) 1204 | if type(input) ~= "string" then return input end 1205 | repeat 1206 | local out = {} 1207 | local concat 1208 | for k, v, start, end_ in tokenizer(input, LCPP_TOKENIZE_MACRO) do 1209 | if k == "CONCAT" then 1210 | -- remove concat op "##" 1211 | concat = true 1212 | else 1213 | table.insert(out, input:sub(start, end_)) 1214 | end 1215 | end 1216 | input = table.concat(out) 1217 | until not concat 1218 | return input 1219 | end 1220 | 1221 | -- macro args replacement function slower but more torelant for pathological case 1222 | local function replaceArgs(argsstr, repl) 1223 | local args = {} 1224 | argsstr = argsstr:sub(2,-2) 1225 | -- print('argsstr:'..argsstr) 1226 | local comma 1227 | for k, v, start, end_ in tokenizer(argsstr, LCPP_TOKENIZE_MACRO_ARGS) do 1228 | -- print("replaceArgs:" .. k .. "|" .. v) 1229 | if k == "ARGS" or k == "PARENTHESE" or k == "STRING_LITERAL" or 1230 | k == "FUNCTIONAL" or k == "SINGLE_CHARACTER_ARGS" then 1231 | table.insert(args, v) 1232 | comma = false 1233 | elseif k == "COMMA" then 1234 | if comma then 1235 | -- continued comma means empty parameter 1236 | table.insert(args, "") 1237 | end 1238 | comma = true 1239 | end 1240 | end 1241 | local v = repl:gsub("%$(%d+)", function (m) return args[tonumber(m)] or "" end) 1242 | -- print("replaceArgs:" .. repl .. "|" .. tostring(#args) .. "|" .. v) 1243 | return v 1244 | end 1245 | 1246 | -- i.e.: "MAX(x, y) (((x) > (y)) ? (x) : (y))" 1247 | local function parseFunction(state, input) 1248 | if not input then return end 1249 | local concat 1250 | local name, argsstr, repl = input:match(FUNCMACRO) 1251 | if not name or not argsstr or not repl then return end 1252 | 1253 | -- rename args to $1,$2... for later gsub 1254 | local noargs = 0 1255 | for argname in argsstr:gmatch(IDENTIFIER) do 1256 | noargs = noargs + 1 1257 | -- avoid matching substring of another identifier (eg. attrib matches __attribute__ and replace it) 1258 | repl = repl:gsub("(#*)(%s*)("..argname..")([_%w]?)", function (s1, s2, s3, s4) 1259 | if #s4 <= 0 then 1260 | return (#s1 == 1) and ("\"$"..noargs.."\"") or (s1..s2.."$"..noargs) 1261 | else 1262 | return s1..s2..s3..s4 1263 | end 1264 | end) 1265 | end 1266 | -- remove concat (after replace matching argument name to $1, $2, ...) 1267 | repl = repl:gsub("%s*##%s*", "") 1268 | 1269 | -- build macro funcion 1270 | local func = function(input) 1271 | return input:gsub(name.."%s*(%b())", function (match) 1272 | return replaceArgs(match, repl) 1273 | end) 1274 | end 1275 | 1276 | return name, func, repl 1277 | end 1278 | 1279 | 1280 | -- ------------ 1281 | -- LCPP INTERFACE 1282 | -- ------------ 1283 | 1284 | --- initialies a lcpp state. not needed manually. handy for testing 1285 | function lcpp.init(input, predefines, macro_sources) 1286 | -- create sate var 1287 | local state = {} -- init the state object 1288 | state.defines = {} -- the table of known defines and replacements 1289 | state.screener = screener(input) 1290 | state.lineno = 0 -- the current line number 1291 | state.stack = {} -- stores wether the current stack level is to be included 1292 | state.once = {} -- stack level was once true (first if that evals to true) 1293 | state.macro_sources = macro_sources or {} -- original replacement text for functional macro 1294 | 1295 | -- funcs 1296 | state.define = define 1297 | state.undefine = function(state, key) 1298 | state:define(key, nil) 1299 | state.macro_sources[key] = nil 1300 | end 1301 | state.defined = function(state, key) 1302 | return state.defines[key] ~= nil 1303 | end 1304 | state.apply = apply 1305 | state.includeFile = includeFile 1306 | state.doWork = doWork 1307 | state.getIndent = function(state) 1308 | return #state.stack 1309 | end 1310 | state.openBlock = function(state, bool) 1311 | state.stack[#state.stack+1] = bool 1312 | state.once [#state.once+1] = bool 1313 | state:define(__LCPP_INDENT__, state:getIndent(), true) 1314 | end 1315 | state.elseBlock = function(state, bool) 1316 | if state.once[#state.once] then 1317 | state.stack[#state.stack] = false 1318 | else 1319 | state.stack[#state.stack] = bool 1320 | if bool then state.once[#state.once] = true end 1321 | end 1322 | end 1323 | state.closeBlock = function(state) 1324 | state.stack[#state.stack] = nil 1325 | state.once [#state.once] = nil 1326 | state:define(__LCPP_INDENT__, state:getIndent(), true) 1327 | if state:getIndent() < 0 then error("Unopened block detected. Indentaion problem.") end 1328 | end 1329 | state.skip = function(state) 1330 | for i = 1, #state.stack do 1331 | if not state.stack[i] then return i end 1332 | end 1333 | return false 1334 | end 1335 | state.getLine = function(state) 1336 | state.lineno = state.lineno + 1 1337 | state:define(__LINE__, state.lineno, true) 1338 | return state.screener() 1339 | end 1340 | state.prepareMacro = prepareMacro 1341 | state.parseExpr = parseExpr 1342 | state.parseFunction = parseFunction 1343 | 1344 | -- predefines 1345 | state:define(__DATE__, os.date("%B %d %Y"), true) 1346 | state:define(__TIME__, os.date("%H:%M:%S"), true) 1347 | state:define(__LINE__, state.lineno, true) 1348 | state:define(__LCPP_INDENT__, state:getIndent(), true) 1349 | predefines = predefines or {} 1350 | for k,v in pairs(lcpp.ENV) do state:define(k, v, true) end -- static ones 1351 | for k,v in pairs(predefines) do state:define(k, v, true) end 1352 | 1353 | if lcpp.LCPP_TEST then lcpp.STATE = state end -- activate static state debugging 1354 | 1355 | return state 1356 | end 1357 | 1358 | --- the preprocessors main function. 1359 | -- returns the preprocessed output as a string. 1360 | -- @param code data as string 1361 | -- @param predefines OPTIONAL a table of predefined variables 1362 | -- @usage lcpp.compile("#define bar 0x1337\nstatic const int foo = bar;") 1363 | -- @usage lcpp.compile("#define bar 0x1337\nstatic const int foo = bar;", {["bar"] = "0x1338"}) 1364 | function lcpp.compile(code, predefines, macro_sources) 1365 | local state = lcpp.init(code, predefines, macro_sources) 1366 | local buf = {} 1367 | for output in state:doWork() do 1368 | table.insert(buf, output) 1369 | end 1370 | local output = table.concat(buf, NEWL) 1371 | if lcpp.DEBUG then print(output) end 1372 | return output, state 1373 | end 1374 | 1375 | --- preprocesses a file 1376 | -- @param filename the file to read 1377 | -- @param predefines OPTIONAL a table of predefined variables 1378 | -- @usage out, state = lcpp.compileFile("../odbg/plugin.h", {["MAX_PAH"]=260, ["UNICODE"]=true}) 1379 | function lcpp.compileFile(filename, predefines, macro_sources, next, _local) 1380 | if not filename then error("processFile() arg1 has to be a string") end 1381 | local file = io.open(filename, 'r') 1382 | if not file then error("file not found: "..filename) end 1383 | local code = file:read('*a') 1384 | predefines = predefines or {} 1385 | predefines[__FILE__] = filename 1386 | return lcpp.compile(code, predefines, macro_sources) 1387 | end 1388 | 1389 | 1390 | -- ------------ 1391 | -- SATIC UNIT TESTS 1392 | -- ------------ 1393 | function lcpp.test(suppressMsg) 1394 | local testLabelCount = 0 1395 | local function getTestLabel() 1396 | testLabelCount = testLabelCount + 1 1397 | return " lcpp_assert_"..testLabelCount 1398 | end 1399 | 1400 | -- this ugly global is required so our testcode can find it 1401 | _G.lcpp_test = { 1402 | assertTrueCalls = 0; 1403 | assertTrueCount = 0; 1404 | assertTrue = function() 1405 | lcpp_test.assertTrueCount = lcpp_test.assertTrueCount + 1; 1406 | end 1407 | } 1408 | 1409 | local testlcpp = [[ 1410 | assert(__LINE__ == 1, "_LINE_ macro test 1: __LINE__") 1411 | // This test uses LCPP with lua code (uncommon but possible) 1412 | assert(__LINE__ == 3, "_LINE_ macro test 3: __LINE__") 1413 | /* 1414 | * It therefore asserts any if/else/macro functions and various syntaxes 1415 | * (including this comment, that would cause errors if not filtered) 1416 | */ 1417 | assert(__LINE__ == 8, "_LINE_ macro test 8: __LINE__") 1418 | /* 1419 | assert(false, "multi-line comment not removed") 1420 | */ 1421 | /* pathological case which contains single line comment start in multiline comments. 1422 | * e.g. this multiline comment should be finish next line. 1423 | * http://foobar.com */ // comment 1424 | /* if singleline comment processes first, sometimes indicator of end of multiline loss */ #define THIS_SHOULD_ENABLE 111 /* 1425 | continuous multiline comment after macro definition 1426 | //*/ 1427 | ///* this removed. 1428 | assert(THIS_SHOULD_ENABLE == 111, "pathological multiline comment test") 1429 | 1430 | 1431 | #define TRUE 1432 | #define ONE (1) 1433 | #define TWO (2) 1434 | #define THREE_FUNC(x) (3) 1435 | #define LEET 0x1337 1436 | #define CLONG 123456789L 1437 | #define CLONGLONG 123456789123456789LL 1438 | #define CULONG 12345678UL 1439 | #define CUINT 123456U 1440 | #define BINARY -0b1001 /* -9 */ 1441 | #define OCTET 075 /* 61 */ 1442 | #define NON_OCTET 75 1443 | #define HEX 0xffffU 1444 | #define __P(x) x 1445 | #define WCHAR_ZERO L'\0' 1446 | # define NCURSES_IMPEXP 1447 | # define NCURSES_API 1448 | # define NCURSES_EXPORT(type) NCURSES_IMPEXP type NCURSES_API 1449 | #define MACRO_TO_ITSELF MACRO_TO_ITSELF 1450 | local MACRO_TO_ITSELF = 111 1451 | assert(MACRO_TO_ITSELF == 111, "can process macro to itself") 1452 | assert(WCHAR_ZERO == 0, "wchar evaluate fails") 1453 | assert(CLONG == 123456789, "read *L fails") 1454 | assert(CLONGLONG == 123456789123456789, "read *LL fails") 1455 | assert(CULONG == 12345678, "read *UL fails") 1456 | assert(HEX == 65535, "read hex fails") 1457 | #if CUINT != 123456U 1458 | assert(false, "cannot evaluate number which contains Unsinged postfix correctly") 1459 | #else 1460 | assert(CUINT == 123456, "read *U fails") 1461 | #endif 1462 | #pragma ignored 1463 | assert __P((BINARY == -9, "parse, binary literal fails")) 1464 | assert(OCTET == 61 and NON_OCTET == 75, "parse octet literal fails") 1465 | 1466 | lcpp_test.assertTrue() 1467 | assert(LEET == 0x1337, "simple #define replacement") 1468 | local msg 1469 | /* function to check macro expand to empty */ 1470 | local function check_argnum(...) 1471 | return select('#', ...) 1472 | end 1473 | NCURSES_EXPORT(function) test_export(a) 1474 | return a + 1 1475 | end 1476 | assert(test_export(2) == 3, "EXPORT style macro") 1477 | local macrofunc = function __P(( 1478 | a, b)) 1479 | return a + b 1480 | end 1481 | assert(macrofunc(1, 2) == 3, "macro arg contains parenthese") 1482 | 1483 | msg = "tenary operator test" 1484 | #if (HEX % 2 == 1 ? CUINT : CULONG) == 123456 1485 | lcpp_test.assertTrue() 1486 | #else 1487 | assert(false, msg.."1") 1488 | #endif 1489 | #if (OCTET % 2 == 0 ? CUINT : CULONG) == 123456 1490 | assert(false, msg.."1") 1491 | #else 1492 | lcpp_test.assertTrue() 1493 | #endif 1494 | 1495 | 1496 | 1497 | 1498 | # if defined TRUE 1499 | lcpp_test.assertTrue() -- valid strange syntax test (spaces and missing brackets) 1500 | # endif 1501 | 1502 | 1503 | msg = "#define if/else test" 1504 | #ifdef TRUE 1505 | lcpp_test.assertTrue() 1506 | #else 1507 | assert(false, msg.."1") 1508 | #endif 1509 | #ifdef NOTDEFINED 1510 | assert(false, msg.."2") 1511 | #else 1512 | lcpp_test.assertTrue() 1513 | #endif 1514 | #ifndef NOTDEFINED 1515 | lcpp_test.assertTrue() 1516 | #else 1517 | assert(false, msg.."3") 1518 | #endif 1519 | 1520 | 1521 | msg = "#if defined statement test" 1522 | #if defined(TRUE) 1523 | lcpp_test.assertTrue() 1524 | #else 1525 | assert(false, msg.."1") 1526 | #endif 1527 | #if !defined(LEET) && !defined(TRUE) 1528 | assert(false, msg.."2") 1529 | #endif 1530 | #if !defined(NOTLEET) && !defined(NOTDEFINED) 1531 | lcpp_test.assertTrue() 1532 | #else 1533 | assert(false, msg.."3") 1534 | #endif 1535 | #if !(defined(LEET) && defined(TRUE)) 1536 | assert(false, msg.."4") 1537 | #else 1538 | lcpp_test.assertTrue() 1539 | #endif 1540 | #if !defined(LEET) && !defined(TRUE) 1541 | assert(false, msg.."5") 1542 | #endif 1543 | #if defined(LEET) && defined(TRUE) && defined(NOTDEFINED) 1544 | assert(false, msg.."6") 1545 | #endif 1546 | #if ONE + TWO * TWO == 5 1547 | lcpp_test.assertTrue() 1548 | #else 1549 | assert(false, msg.."7") 1550 | #endif 1551 | #if (ONE + TWO) * TWO == 0x6 1552 | lcpp_test.assertTrue() 1553 | #else 1554 | assert(false, msg.."8") 1555 | #endif 1556 | #if ONE * TWO + ONE / TWO == 2.5 1557 | lcpp_test.assertTrue() 1558 | #else 1559 | assert(false, msg.."9") 1560 | #endif 1561 | #if ONE + ONE * TWO / TWO == 2 1562 | lcpp_test.assertTrue() 1563 | #else 1564 | assert(false, msg.."10") 1565 | #endif 1566 | #if TWO - - TWO == 4 1567 | lcpp_test.assertTrue() 1568 | #else 1569 | assert(false, msg.."11") 1570 | #endif 1571 | #if (TWO - - TWO) % (ONE + TWO) == 1 1572 | lcpp_test.assertTrue() 1573 | #else 1574 | assert(false, msg.."12") 1575 | #endif 1576 | #if ONE << TWO + TWO >> ONE == 8 1577 | lcpp_test.assertTrue() 1578 | #else 1579 | assert(false, msg.."13") 1580 | #endif 1581 | #if (ONE << TWO) + (TWO >> ONE) == 5 1582 | lcpp_test.assertTrue() 1583 | #else 1584 | assert(false, msg.."14") 1585 | #endif 1586 | #if (ONE << TWO) + TWO >> ONE == 3 1587 | lcpp_test.assertTrue() 1588 | #else 1589 | assert(false, msg.."15") 1590 | #endif 1591 | #if (THREE_FUNC(0xfffffU) & 4) == 0 1592 | lcpp_test.assertTrue() 1593 | #else 1594 | assert(false, msg.."16") 1595 | #endif 1596 | #if (0x3 & THREE_FUNC("foobar")) == 0b11 1597 | lcpp_test.assertTrue() 1598 | #else 1599 | assert(false, msg.."17") 1600 | #endif 1601 | #if defined(TWO) && ((TWO-0) < 3) 1602 | lcpp_test.assertTrue() 1603 | #else 1604 | assert(false, msg.."17") 1605 | #endif 1606 | #if TWO == 1--1 1607 | lcpp_test.assertTrue() 1608 | #else 1609 | assert(false, msg.."18") 1610 | #endif 1611 | #if HEX > 0xfFfFU 1612 | assert(false, msg.."18") 1613 | #else 1614 | lcpp_test.assertTrue() 1615 | #endif 1616 | #define TRUE_DEFINED defined(TRUE) 1617 | #if TRUE_DEFINED 1618 | lcpp_test.assertTrue() 1619 | #else 1620 | assert(false, msg.."19") 1621 | #endif 1622 | #define NOTDEFINED_DEFINED defined(TRUE) && defined(NOTDEFINED) 1623 | #if NOTDEFINED_DEFINED 1624 | assert(false, msg.."20") 1625 | #else 1626 | lcpp_test.assertTrue() 1627 | #endif 1628 | #if LEET && LEET > 0x1336 1629 | lcpp_test.assertTrue() 1630 | #else 1631 | assert(false, msg.."20") 1632 | #endif 1633 | #if NOTLEET && NOTLEET > 0x1336 1634 | assert(false, msg.."21") 1635 | #else 1636 | lcpp_test.assertTrue() 1637 | #endif 1638 | #if defined(NOTLEET) || BINARY + 0 >= 10L || !defined(TRUE) 1639 | assert(false, msg.."22") 1640 | #else 1641 | lcpp_test.assertTrue() 1642 | #endif 1643 | 1644 | 1645 | 1646 | msg = "macro chaining" 1647 | #define FOO 0x7B 1648 | #define BAR (FOO+0x7B) 1649 | assert(-BAR == -0x7B*2, msg) 1650 | #define BAZ 456 1651 | #define BLUR BA##Z 1652 | assert(BLUR == 456, msg) 1653 | local testfunc = function (x) return "["..tostring(x).."]" end 1654 | #define FOOBAR(x) testfunc(x) 1655 | assert(FOOBAR(1) == "[1]", msg) 1656 | #define testfunc(x) 1657 | #define FOOBAZ(x) testfunc(x) 1658 | assert(check_argnum(FOOBAR(1)) == 0, msg) 1659 | assert(check_argnum(FOOBAZ(1)) == 0, msg) 1660 | #undef testfunc 1661 | assert(FOOBAR(1) == "[1]", msg) 1662 | assert(FOOBAZ(1) == "[1]", msg) 1663 | 1664 | 1665 | msg = "indentation test" 1666 | assert(__LCPP_INDENT__ == 0, msg.."1") 1667 | #if defined(TRUE) 1668 | assert(__LCPP_INDENT__ == 1, msg.."2") 1669 | #endif 1670 | assert(__LCPP_INDENT__ == 0, msg.."3") 1671 | 1672 | 1673 | #define LCPP_FUNCTION_1(x, y) (x and not y) 1674 | assert(LCPP_FUNCTION_1(true, false), "function macro") 1675 | #define LCPP_FUNCTION_2(x, y) \ 1676 | (not x \ 1677 | and y) 1678 | assert(LCPP_FUNCTION_2(false, true), "multiline function macro") 1679 | #define LCPP_FUNCTION_3(_x, _y) LCPP_FUNCTION_2(_x, _y) 1680 | assert(LCPP_FUNCTION_3(false, true), "function macro with argname contains _") 1681 | 1682 | #define LCPP_FUNCTION_4_CHILD() false 1683 | #define LCPP_FUNCTION_4(_x) 1684 | 1685 | assert(check_argnum(LCPP_FUNCTION_4(LCPP_FUNCTION_4_CHILD())) == 0, "functional macro which receives functional macro as argument") 1686 | assert(check_argnum(LCPP_FUNCTION_4(LCPP_FUNCTION_3(true, true))) == 0, "functional macro which receives functional macro as argument2") 1687 | 1688 | #define LCPP_FUNCTION_5(x, y) (x) + (x) + (y) + (y) 1689 | assert(LCPP_FUNCTION_5(10, 20) == 60, "macro argument multiple usage") 1690 | 1691 | #define LCPP_NOT_FUNCTION (BLUR) 1692 | assert(LCPP_NOT_FUNCTION == 456, "if space between macro name and argument definition exists, it is regarded as replacement macro") 1693 | 1694 | #define __CONCAT(x, y, z, w) x ## y ## z (w) 1695 | local __fncall = function (x) return x + 111 end 1696 | assert(111 == __CONCAT( __ , fnc , all, 0 ), "funcall fails") 1697 | assert(222 == __CONCAT( __ ,, fncall, 111 ), "funcall fails2") 1698 | #define __ATTRIB_CALL(x, y, attrib) __attribute__(x, y, attrib) 1699 | local __attribute__ = function (x, y, attr) 1700 | return attr * (x + y) 1701 | end 1702 | assert(__ATTRIB_CALL( 1, 2 , 100 ) == 300, "funcall fails3") 1703 | 1704 | 1705 | msg = "#elif test" 1706 | #if defined(NOTDEFINED) 1707 | -- it should not be processed 1708 | #if NOTDEFINED 1709 | #else 1710 | assert(false, msg.."1") 1711 | #endif 1712 | #elif defined(NOTDEFINED) 1713 | assert(false, msg.."2") 1714 | #elif defined(TRUE) 1715 | lcpp_test.assertTrue() 1716 | #else 1717 | assert(false, msg.."3") 1718 | #endif 1719 | 1720 | 1721 | msg = "#else if test" 1722 | #if defined(NOTDEFINED) 1723 | assert(false, msg.."1") 1724 | #else if defined(NOTDEFINED) 1725 | assert(false, msg.."2") 1726 | #else if defined(TRUE) 1727 | lcpp_test.assertTrue() 1728 | #else 1729 | assert(false, msg.."3") 1730 | #endif 1731 | 1732 | 1733 | msg = "bock stack tree test" 1734 | #ifdef TRUE 1735 | #ifdef NOTDEFINED 1736 | assert(false, msg.."1") 1737 | #elif defined(TRUE) 1738 | lcpp_test.assertTrue() 1739 | #else 1740 | assert(false, msg.."2") 1741 | #endif 1742 | #else 1743 | assert(false, msg.."3") 1744 | #endif 1745 | #ifdef NOTDEFINED 1746 | #ifdef TRUE 1747 | assert(false, msg.."4") 1748 | #endif 1749 | #endif 1750 | 1751 | msg = "test concat ## operator" 1752 | #define CONCAT_TEST1 foo##bar 1753 | local CONCAT_TEST1 = "blubb" 1754 | assert(foobar == "blubb", msg) 1755 | #define CONCAT_TEST2() bar##foo 1756 | local CONCAT_TEST2() = "blubb" 1757 | assert(barfoo == "blubb", msg) 1758 | -- dont process ## within strings 1759 | #define CONCAT_TEST3 "foo##bar" 1760 | assert(CONCAT_TEST3 == "foo##bar", msg) 1761 | msg = "test concat inside func type macro" 1762 | #define CONCAT_TEST4(baz) CONCAT_TEST##baz 1763 | local CONCAT_TEST4(1) = "bazbaz" 1764 | assert(foobar == "bazbaz", msg) 1765 | 1766 | msg = "#undef test" 1767 | #define UNDEF_TEST 1768 | #undef UNDEF_TEST 1769 | #ifdef UNDEF_TEST 1770 | assert(false, msg) 1771 | #endif 1772 | 1773 | msg = "stringify operator(#) test" 1774 | #define STRINGIFY(str) #str 1775 | assert(STRINGIFY(abcde) == "abcde", msg) 1776 | #define STRINGIFY_AND_CONCAT(str1, str2) #str1 ## \ 1777 | #str2 1778 | assert(STRINGIFY_AND_CONCAT(fgh, ij) == "fghij", msg) 1779 | 1780 | #define msg_concat(msg1, msg2) msg1 ## msg2 1781 | assert("I, am, lcpp" == msg_concat("I, am", ", lcpp"), "processing macro argument which includes ,") 1782 | 1783 | #define FUNC__ARG 500 1784 | #define __ARG 100 1785 | #define FUNC(x) FUNC##x 1786 | assert(FUNC(__ARG) == 500, "create new macro symbol by concat") 1787 | 1788 | msg = "same macro definition which has exactly same value allows. (checked with gcc 4.4.7 __WORDSIZE)" 1789 | #define DUP_MACRO_DEF (111) 1790 | #define DUP_MACRO_DEF (111) 1791 | #define DUP_FUNC_MACRO(x, y) x + y 1792 | #define DUP_FUNC_MACRO(x, y) x + y 1793 | 1794 | 1795 | msg = "check #if conditional check" 1796 | #define VALUE1 (123) 1797 | #if VALUE1 != 123 1798 | assert(false, msg .." #if " .. tostring(VALUE1) .. " != 123") 1799 | #endif 1800 | #if 123 != VALUE1 1801 | assert(false, msg .." #if " .. tostring(VALUE1) .. " != 123 (2)") 1802 | #endif 1803 | 1804 | #define VALUE2 ("hoge") 1805 | #if VALUE2 == "hoge" 1806 | #define VALUE3 (true) 1807 | #endif 1808 | assert(VALUE3 == true, msg .. " #if " .. tostring(VALUE3) .. " == true") 1809 | 1810 | #define VALUE4 (VALUE1 + VALUE1) 1811 | #if VALUE4 != 246 1812 | assert(false, msg .." #if check for nested definition:" .. tostring(VALUE4)) 1813 | #endif 1814 | 1815 | msg = "+-*/<> in #if expression:" 1816 | #define CALC_VALUE_A (1) 1817 | #define CALC_VALUE_B (2) 1818 | #if (CALC_VALUE_A + CALC_VALUE_B) != 3 1819 | assert(false, msg .. " + not work:") 1820 | #endif 1821 | #if (CALC_VALUE_A * CALC_VALUE_B) != 2 1822 | assert(false, msg .. " * not work:") 1823 | #endif 1824 | #if (CALC_VALUE_A - CALC_VALUE_B) != -1 1825 | assert(false, msg .. " - not work:") 1826 | #endif 1827 | #if (CALC_VALUE_A / CALC_VALUE_B) != 0.5 1828 | assert(false, msg .. " / not work:") 1829 | #endif 1830 | 1831 | #if (CALC_VALUE_A >= CALC_VALUE_A) 1832 | #else 1833 | assert(false, msg .. " >= not work1") 1834 | #endif 1835 | #if (CALC_VALUE_B >= CALC_VALUE_A) 1836 | #else 1837 | assert(false, msg .. " >= not work2") 1838 | #endif 1839 | 1840 | #if (CALC_VALUE_B <= CALC_VALUE_B) 1841 | #else 1842 | assert(false, msg .. " <= not work1") 1843 | #endif 1844 | #if (CALC_VALUE_A <= CALC_VALUE_B) 1845 | #else 1846 | assert(false, msg .. " <= not work2") 1847 | #endif 1848 | 1849 | #if (CALC_VALUE_B > CALC_VALUE_A) 1850 | #else 1851 | assert(false, msg .. " > not work1") 1852 | #endif 1853 | #if (CALC_VALUE_A > CALC_VALUE_A) 1854 | assert(false, msg .. " > not work2") 1855 | #endif 1856 | 1857 | #if (CALC_VALUE_A < CALC_VALUE_B) 1858 | #else 1859 | assert(false, msg .. " < not work1") 1860 | #endif 1861 | #if (CALC_VALUE_B < CALC_VALUE_B) 1862 | assert(false, msg .. " < not work2") 1863 | #endif 1864 | #if (CALC_VALUE_B | CALC_VALUE_A) != 3 1865 | assert(false, msg .. " | not work") 1866 | #endif 1867 | #if (CALC_VALUE_B & CALC_VALUE_A) != 0 1868 | assert(false, msg .. " & not work") 1869 | #endif 1870 | #if (CALC_VALUE_B ^ CALC_VALUE_A) != 3 1871 | assert(false, msg .. " ^ not work") 1872 | #endif 1873 | #if -CALC_VALUE_B != -2 1874 | assert(false, msg .. " unary - not work") 1875 | #endif 1876 | #if ~CALC_VALUE_B != -3 1877 | assert(false, msg .. " unary ~ not work") 1878 | #endif 1879 | ]] 1880 | lcpp.FAST = false -- enable full valid output for testing 1881 | lcpp.SELF_TEST = true 1882 | local testlua = lcpp.compile(testlcpp) 1883 | lcpp.SELF_TEST = nil 1884 | -- print(testlua) 1885 | assert(loadstring(testlua, "testlua"))() 1886 | lcpp_test.assertTrueCalls = findn(testlcpp, "lcpp_test.assertTrue()") 1887 | assert(lcpp_test.assertTrueCount == lcpp_test.assertTrueCalls, "assertTrue calls:"..lcpp_test.assertTrueCalls.." count:"..lcpp_test.assertTrueCount) 1888 | _G.lcpp_test = nil -- delete ugly global hack 1889 | if not suppressMsg then print("Test run suscessully") end 1890 | end 1891 | if lcpp.LCPP_TEST then lcpp.test(true) end 1892 | 1893 | 1894 | -- ------------ 1895 | -- REGISTER LCPP 1896 | -- ------------ 1897 | 1898 | --- disable lcpp processing for ffi, loadstring and such 1899 | lcpp.disable = function() 1900 | if lcpp.LCPP_LUA then 1901 | -- activate LCPP_LUA actually does anything useful 1902 | -- _G.loadstring = _G.loadstring_lcpp_backup 1903 | end 1904 | 1905 | if lcpp.LCPP_FFI and pcall(require, "ffi") then 1906 | ffi = require("ffi") 1907 | if ffi.lcpp_cdef_backup then 1908 | ffi.cdef = ffi.lcpp_cdef_backup 1909 | ffi.lcpp_cdef_backup = nil 1910 | end 1911 | end 1912 | end 1913 | 1914 | --- (re)enable lcpp processing for ffi, loadstring and such 1915 | lcpp.enable = function() 1916 | -- Use LCPP to process Lua code (load, loadfile, loadstring...) 1917 | if lcpp.LCPP_LUA then 1918 | -- TODO: make it properly work on all functions 1919 | error("lcpp.LCPP_LUA = true -- not properly implemented yet"); 1920 | _G.loadstring_lcpp_backup = _G.loadstring 1921 | _G.loadstring = function(str, chunk) 1922 | return loadstring_lcpp_backup(lcpp.compile(str), chunk) 1923 | end 1924 | end 1925 | -- Use LCPP as LuaJIT PreProcessor if used inside LuaJIT. i.e. Hook ffi.cdef 1926 | if lcpp.LCPP_FFI and pcall(require, "ffi") then 1927 | ffi = require("ffi") 1928 | if not ffi.lcpp_cdef_backup then 1929 | if not ffi.lcpp_defs then ffi.lcpp_defs = {} end -- defs are stored and reused 1930 | ffi.lcpp = function(input) 1931 | local output, state = lcpp.compile(input, ffi.lcpp_defs, ffi.lcpp_macro_sources) 1932 | ffi.lcpp_defs = state.defines 1933 | ffi.lcpp_macro_sources = state.macro_sources 1934 | return output 1935 | end 1936 | ffi.lcpp_cdef_backup = ffi.cdef 1937 | ffi.cdef = function(input) 1938 | if true then 1939 | return ffi.lcpp_cdef_backup(ffi.lcpp(input)) 1940 | else 1941 | local fn,cnt = input:gsub('#include ["<].-([^/]+%.h)[">]', '%1') 1942 | input = ffi.lcpp(input) 1943 | if cnt > 0 then 1944 | local f = io.open("./tmp/"..fn, 'w') 1945 | if f then 1946 | f:write(input) 1947 | f:close() 1948 | else 1949 | assert(fn:find('/'), 'cannot open: ./tmp/'..fn) 1950 | end 1951 | end 1952 | return ffi.lcpp_cdef_backup(input) 1953 | end 1954 | end 1955 | end 1956 | end 1957 | end 1958 | 1959 | lcpp.enable() 1960 | return lcpp 1961 | 1962 | -------------------------------------------------------------------------------- /resources/lua-logo-label.ps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-2.0 EPSF-2.0 2 | %%Title: Lua logo 3 | %%Creator: lua@tecgraf.puc-rio.br 4 | %%CreationDate: Wed Nov 29 19:04:04 EDT 2000 5 | %%BoundingBox: -45 0 1035 1080 6 | %%Pages: 1 7 | %%EndComments 8 | %%EndProlog 9 | 10 | %------------------------------------------------------------------------------ 11 | % 12 | % Graphic design by Alexandre Nakonechnyj. 13 | % PostScript programming by the Lua team. 14 | % This code is hereby placed in the public domain. 15 | % 16 | % Permission is hereby granted, without written agreement and without license 17 | % or royalty fees, to use, copy, and distribute this logo for any purpose, 18 | % including commercial applications, subject to the following conditions: 19 | % 20 | % * The origin of this logo must not be misrepresented; you must not 21 | % claim that you drew the original logo. We recommend that you give credit 22 | % to the graphics designer in all printed matter that includes the logo. 23 | % 24 | % * The only modification you can make is to adapt the orbiting text to 25 | % your product name. 26 | % 27 | % * The logo can be used in any scale as long as the relative proportions 28 | % of its elements are maintained. 29 | % 30 | %------------------------------------------------------------------------------ 31 | 32 | /LABEL (lcpp) def 33 | 34 | %-- DO NOT CHANGE ANYTHING BELOW THIS LINE ------------------------------------ 35 | 36 | /PLANETCOLOR {0 0 0.5 setrgbcolor} bind def 37 | /HOLECOLOR {1.0 setgray} bind def 38 | /ORBITCOLOR {0.5 setgray} bind def 39 | /LOGOFONT {/Helvetica 0.90} def 40 | /LABELFONT {/Helvetica 0.36} def 41 | 42 | %------------------------------------------------------------------------------ 43 | 44 | /MOONCOLOR {PLANETCOLOR} bind def 45 | /LOGOCOLOR {HOLECOLOR} bind def 46 | /LABELCOLOR {ORBITCOLOR} bind def 47 | 48 | /LABELANGLE 125 def 49 | /LOGO (Lua) def 50 | 51 | /DASHANGLE 10 def 52 | /HALFDASHANGLE DASHANGLE 2 div def 53 | 54 | % moon radius. planet radius is 1. 55 | /r 1 2 sqrt 2 div sub def 56 | 57 | /D {0 360 arc fill} bind def 58 | /F {exch findfont exch scalefont setfont} bind def 59 | 60 | % place it nicely on the paper 61 | /RESOLUTION 1024 def 62 | RESOLUTION 2 div dup translate 63 | RESOLUTION 2 div 2 sqrt div dup scale 64 | 65 | %-------------------------------------------------------------------- planet -- 66 | PLANETCOLOR 67 | 0 0 1 D 68 | 69 | %---------------------------------------------------------------------- hole -- 70 | HOLECOLOR 71 | 1 2 r mul sub dup r D 72 | 73 | %---------------------------------------------------------------------- moon -- 74 | MOONCOLOR 75 | 1 1 r D 76 | 77 | %---------------------------------------------------------------------- logo -- 78 | LOGOCOLOR 79 | LOGOFONT 80 | F 81 | LOGO stringwidth pop 2 div neg 82 | -0.5 moveto 83 | LOGO show 84 | 85 | %------------------------------------------------------------------------------ 86 | % based on code from Blue Book Program 10, on pages 167--169 87 | % available at ftp://ftp.adobe.com/pub/adobe/displaypostscript/bluebook.shar 88 | 89 | % str ptsize centerangle radius outsidecircletext -- 90 | /outsidecircletext { 91 | circtextdict begin 92 | /radius exch def 93 | /centerangle exch def 94 | /ptsize exch def 95 | /str exch def 96 | 97 | gsave 98 | str radius ptsize findhalfangle 99 | centerangle 100 | add rotate 101 | str 102 | { /charcode exch def 103 | ( ) dup 0 charcode put outsideplacechar 104 | } forall 105 | 106 | grestore 107 | end 108 | } def 109 | 110 | % string radius ptsize findhalfangle halfangle 111 | /findhalfangle { 112 | 4 div add 113 | exch 114 | stringwidth pop 2 div 115 | exch 116 | 2 mul 3.1415926535 mul div 360 mul 117 | } def 118 | 119 | /circtextdict 16 dict def 120 | circtextdict begin 121 | 122 | /outsideplacechar { 123 | /char exch def 124 | /halfangle char radius ptsize findhalfangle def 125 | gsave 126 | halfangle neg rotate 127 | radius 0 translate 128 | -90 rotate 129 | char stringwidth pop 2 div neg 0 moveto 130 | char show 131 | grestore 132 | halfangle 2 mul neg rotate 133 | } def 134 | 135 | end 136 | 137 | %--------------------------------------------------------------------- label -- 138 | LABELFONT 139 | F 140 | 141 | /LABELSIZE LABELFONT exch pop def 142 | /LABELRADIUS LABELSIZE 3 div 1 r add sub neg 1.02 mul def 143 | 144 | 145 | /HALFANGLE 146 | LABEL LABELRADIUS LABELSIZE findhalfangle 147 | HALFDASHANGLE div ceiling HALFDASHANGLE mul 148 | def 149 | 150 | /LABELANGLE 151 | 60 LABELANGLE HALFANGLE sub 152 | lt 153 | { 154 | HALFANGLE 155 | HALFANGLE DASHANGLE div floor DASHANGLE mul 156 | eq 157 | {LABELANGLE DASHANGLE div ceiling DASHANGLE mul} 158 | {LABELANGLE HALFDASHANGLE sub DASHANGLE div round DASHANGLE mul HALFDASHANGLE add} 159 | ifelse 160 | } 161 | {HALFANGLE 60 add} 162 | ifelse 163 | def 164 | 165 | LABELCOLOR 166 | LABEL 167 | LABELSIZE 168 | LABELANGLE 169 | LABELRADIUS 170 | outsidecircletext 171 | 172 | %--------------------------------------------------------------------- orbit -- 173 | ORBITCOLOR 174 | 0.03 setlinewidth 175 | [1 r add 3.1415926535 180 div HALFDASHANGLE mul mul] 0 setdash 176 | newpath 177 | 0 0 178 | 1 r add 179 | 3 copy 180 | 30 181 | LABELANGLE HALFANGLE add 182 | arcn 183 | stroke 184 | 60 185 | LABELANGLE HALFANGLE sub 186 | 2 copy 187 | lt {arc stroke} {4 {pop} repeat} ifelse 188 | 189 | %------------------------------------------------------------------ copyright -- 190 | /COPYRIGHT 191 | (Graphic design by A. Nakonechnyj. Copyright (c) 1998, All rights reserved.) 192 | def 193 | 194 | LABELCOLOR 195 | LOGOFONT 196 | 32 div 197 | F 198 | 2 sqrt 0.99 mul 199 | dup 200 | neg 201 | moveto 202 | COPYRIGHT 203 | 90 rotate 204 | %show 205 | 206 | %---------------------------------------------------------------------- done -- 207 | showpage 208 | 209 | %%Trailer 210 | %%EOF 211 | -------------------------------------------------------------------------------- /resources/lua-logo-lcpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-schmoock/lcpp/4a6987071ec74b083a9e88954d0e66c8d6ba2ba7/resources/lua-logo-lcpp.png --------------------------------------------------------------------------------