summaryrefslogtreecommitdiff
path: root/Tools/LuaMacro/macro
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/LuaMacro/macro')
-rw-r--r--Tools/LuaMacro/macro/Getter.lua320
-rw-r--r--Tools/LuaMacro/macro/TokenList.lua201
-rw-r--r--Tools/LuaMacro/macro/all.lua5
-rw-r--r--Tools/LuaMacro/macro/assert.lua74
-rw-r--r--Tools/LuaMacro/macro/builtin.lua161
-rw-r--r--Tools/LuaMacro/macro/clexer.lua169
-rw-r--r--Tools/LuaMacro/macro/do.lua75
-rw-r--r--Tools/LuaMacro/macro/forall.lua70
-rw-r--r--Tools/LuaMacro/macro/ifelse.lua90
-rw-r--r--Tools/LuaMacro/macro/lambda.lua22
-rw-r--r--Tools/LuaMacro/macro/lc.lua343
-rw-r--r--Tools/LuaMacro/macro/lexer.lua179
-rw-r--r--Tools/LuaMacro/macro/lib/class.lua35
-rw-r--r--Tools/LuaMacro/macro/lib/test.lua144
-rw-r--r--Tools/LuaMacro/macro/module.lua132
-rw-r--r--Tools/LuaMacro/macro/try.lua47
-rw-r--r--Tools/LuaMacro/macro/with.lua31
17 files changed, 2098 insertions, 0 deletions
diff --git a/Tools/LuaMacro/macro/Getter.lua b/Tools/LuaMacro/macro/Getter.lua
new file mode 100644
index 0000000..af58b3c
--- /dev/null
+++ b/Tools/LuaMacro/macro/Getter.lua
@@ -0,0 +1,320 @@
+--- Getter class. Used to get values from the token stream. The first
+-- argument `get` of a macro substitution function is of this type.
+--
+-- M.define ('\\',function(get,put)
+-- local args, body = get:idens('('), get:list()
+-- return put:keyword 'function' '(' : idens(args) ')' :
+-- keyword 'return' : list(body) : space() : keyword 'end'
+-- end)
+--
+-- The second argument `put` is a `TokenList` object.
+-- @see macro.TokenList
+-- @module macro.Getter
+
+local TokenList = require 'macro.TokenList'
+local append = table.insert
+local setmetatable = setmetatable
+
+local Getter = {
+ __call = function(self)
+ return self.fun()
+ end
+}
+local M = Getter
+
+Getter.__index = Getter;
+
+local scan_iter
+
+function Getter.new (get)
+ return setmetatable({fun=get},Getter)
+end
+
+function Getter.from_tl(tl)
+ return Getter.new(scan_iter(tl))
+end
+
+local Tok = {
+ __tostring = function(self)
+ return self[2]
+ end
+}
+
+local function tok_new (t)
+ return setmetatable(t,Tok)
+end
+
+-- create a token iterator out of a token list
+function Getter.scan_iter (tlist)
+ local i,n = 1,#tlist
+ return function(k)
+ if k ~= nil then
+ k = i + k
+ if k < 1 or k > n then return nil end
+ return tlist[k]
+ end
+ local tv = tlist[i]
+ if tv == nil then return nil end
+ i = i + 1
+ return tv[1],tv[2]
+ end
+end
+
+scan_iter = Getter.scan_iter
+
+--- get the next non-whitespace token.
+-- @return token type
+-- @return token value
+-- @function Getter.next
+function Getter.next(get)
+ local t,v = get()
+ while t == 'space' or t == 'comment' do
+ t,v = get()
+ end
+ return t,v
+end
+
+local TL,LTL = TokenList.new, TokenList.new_list
+
+
+local function tappend (tl,t,val)
+ val = val or t
+ append(tl,{t,val})
+end
+
+--- get a balanced block.
+-- Typically for grabbing up to an `end` keyword including any nested
+-- `if`, `do` or `function` blocks with their own `end` keywords.
+-- @param tok the token stream
+-- @param begintokens set of tokens requiring their own nested *endtokens*
+-- (default: `{['do']=true,['function']=true,['if']=true}`)
+-- @param endtokens set of tokens ending a block (default:`{['end']=true}`)
+-- @return list of tokens
+-- @return block end token in form `{type,value}`
+-- @usage
+-- -- copy a balanced table constructor
+-- get:expecting '{'
+-- put '{':tokens (get:block ({['{']=true}, {['}']=true}) '}')
+function Getter.block(tok,begintokens,endtokens)
+ begintokens = begintokens or {['do']=true,['function']=true,['if']=true}
+ endtokens = endtokens or {['end']=true}
+ local level = 1 -- used to count expected matching `endtokens`
+ local tl = TL()
+ local token,value
+ repeat
+ token,value = tok()
+ if not token then return nil,'unexpected end of block' end
+ if begintokens[value] then
+ level = level + 1
+ elseif endtokens[value] then
+ level = level - 1
+ end
+ if level > 0 then -- final end token is returned separately
+ tappend(tl,token,value)
+ end
+ until level == 0
+ return tl,tok_new{token,value}
+end
+
+--- get a delimited list of token lists.
+-- Typically used for grabbing argument lists like ('hello',a+1,fred(c,d)); will count parens
+-- so that the delimiter (usually a comma) is ignored inside sub-expressions. You must have
+-- already read the start token of the list, e.g. open parentheses. It will eat the end token
+-- and return the list of TLs, plus the end token. Based on similar code in Penlight's
+-- `pl.lexer` module.
+-- @param tok the token stream
+-- @param endt the end token (default ')')
+-- @param delim the delimiter (default ',')
+-- @return list of token lists
+-- @return end token in form {type,value}
+function Getter.list(tok,endtoken,delim)
+ endtoken = endtoken or ')'
+ delim = delim or ','
+ local parm_values = LTL()
+ local level = 1 -- used to count ( and )
+ local tl = TL()
+ local is_end
+ if type(endtoken) == 'function' then
+ is_end = endtoken
+ elseif endtoken == '\n' then
+ is_end = function(t,val)
+ return t == 'space' and val:find '\n'
+ end
+ else
+ is_end = function (t)
+ return t == endtoken
+ end
+ end
+ local token,value = tok()
+ if is_end(token,value) then return parm_values end
+ if token == 'space' then
+ token,value = tok()
+ end
+ while true do
+ if not token then return nil,'unexpected end of list' end -- end of stream is an error!
+ if is_end(token,value) and level == 1 then
+ append(parm_values,tl)
+ break
+ elseif token == '(' then
+ level = level + 1
+ tappend(tl,'(')
+ elseif token == ')' then
+ level = level - 1
+ if level == 0 then -- finished with parm list
+ append(parm_values,tl)
+ break
+ else
+ tappend(tl,')')
+ end
+ elseif token == '{' then
+ level = level + 1
+ tappend(tl,'{')
+ elseif token == '}' then
+ level = level - 1
+ tappend(tl,'}')
+ elseif token == delim and level == 1 then
+ append(parm_values,tl) -- a new parm
+ tl = TL()
+ else
+ tappend(tl,token,value)
+ end
+ token,value=tok()
+ end
+ return parm_values,tok_new{token,value}
+end
+
+function Getter.upto_keywords (k1,k2)
+ return function(t,v)
+ return t == 'keyword' and (v == k1 or v == k2)
+ end,''
+end
+
+local tnext = Getter.next
+
+
+function Getter.upto(tok,k1,k2)
+ local endt = k1
+ if type(k1) == 'string' and k1:match '^%a+$' then
+ endt = Getter.upto_keywords(k1,k2)
+ end
+ local ltl,tok = tok:list(endt,'')
+ M.assert(ltl ~= nil and #ltl > 0,'failed to grab tokens')
+ return ltl[1],tok
+end
+
+function Getter.line(tok)
+ return tok:upto(function(t,v)
+ return (t=='space' and v:match '\n') or t == 'comment'
+ end)
+end
+
+
+local function prettyprint (t, v)
+ v = v:gsub ("\n", "\\n")
+ if t == "string" then
+ if #v > 16 then v = v:sub(1,12).."..."..v:sub(1,1) end
+ return t.." "..v
+ end
+ if #v > 16 then v = v:sub(1,12).."..." end
+ if t == "space" or t == "comment" or t == "keyword" then
+ return t.." '"..v.."'"
+ elseif t == v then
+ return "'"..v.."'"
+ else
+ return t.." "..v
+ end
+end
+
+--- get the next identifier token.
+-- (will be an error if the token has wrong type)
+-- @return identifier name
+function Getter.iden(tok)
+ local t,v = tnext(tok)
+ M.assert(t == 'iden','expecting identifier, got '..prettyprint(t,v))
+ return v
+end
+
+Getter.name = Getter.iden -- backwards compatibility!
+
+--- get the next number token.
+-- (will be an error if the token has wrong type)
+-- @return converted number
+function Getter.number(tok)
+ local t,v = tnext(tok)
+ M.assert(t == 'number','expecting number, got '..prettyprint(t,v))
+ return tonumber(v)
+end
+
+--- get a delimited list of identifiers.
+-- works like list.
+-- @param tok the token stream
+-- @param endt the end token (default ')')
+-- @param delim the delimiter (default ',')
+-- @see list
+function Getter.idens(tok,endt,delim)
+ local ltl,err = tok:list(endt,delim)
+ if not ltl then error('idens: '..err) end
+ local names = {}
+ -- list() will return {{}} for an empty list of tlists
+ for i = 1,#ltl do
+ local tl = ltl[i]
+ local tv = tl[1]
+ if tv then
+ if tv[1] == 'space' then tv = tl[2] end
+ names[i] = tv[2]
+ end
+ end
+ return names, err
+end
+
+Getter.names = Getter.idens -- backwards compatibility!
+
+--- get the next string token.
+-- (will be an error if the token has wrong type)
+-- @return string value (without quotes)
+function Getter.string(tok)
+ local t,v = tok:expecting("string")
+ return v:sub(2,-2)
+end
+
+--- assert that the next token has the given type. This will throw an
+-- error if the next non-whitespace token does not match.
+-- @param type a token type ('iden','string',etc)
+-- @param value a token value (optional)
+-- @usage get:expecting '('
+-- @usage get:expecting ('iden','bonzo')
+function Getter.expecting (tok,type,value)
+ local t,v = tnext(tok)
+ if t ~= type then M.error ("expected "..type.." got "..prettyprint(t,v)) end
+ if value then
+ if v ~= value then M.error("expected "..value.." got "..prettyprint(t,v)) end
+ end
+ return t,v
+end
+
+--- peek ahead or before in the token stream.
+-- @param k positive delta for looking ahead, negative for looking behind.
+-- @param dont_skip true if you want to check for whitespace
+-- @return the token type
+-- @return the token value
+-- @return the token offset
+-- @function Getter.peek
+
+--- peek ahead two tokens.
+-- @return first token type
+-- @return first token value
+-- @return second token type
+-- @return second token value
+-- @function Getter.peek2
+
+--- patch the token stream at the end.
+-- @param idx index in output table
+-- @param text to replace value at that index
+-- @function Getter.patch
+
+--- put out a placeholder for later patching.
+-- @param put a putter object
+-- @return an index into the output table
+-- @function Getter.placeholder
+
+return Getter
diff --git a/Tools/LuaMacro/macro/TokenList.lua b/Tools/LuaMacro/macro/TokenList.lua
new file mode 100644
index 0000000..a18ac67
--- /dev/null
+++ b/Tools/LuaMacro/macro/TokenList.lua
@@ -0,0 +1,201 @@
+---------------
+-- A TokenList class for generating token lists.
+--
+-- There are also useful `get_` methods for extracting values from
+-- the first token.
+--
+-- @module macro.TokenList
+
+local TokenList = {}
+local M = TokenList
+TokenList.__index = TokenList
+
+local append = table.insert
+
+function TokenList.new (tl)
+ return setmetatable(tl or {},TokenList)
+end
+
+local TokenListList = {}
+
+function TokenList.new_list (ltl)
+ return setmetatable(ltl or {},TokenListList)
+end
+
+TokenListList.__index = function(self,key)
+ local m = TokenList[key]
+ return function(self,...)
+ local res = {}
+ for i = 1,#self do res[i] = m(self[i],...) end
+ return TokenList.new_list(res)
+ end
+end
+
+-- token-getting helpers
+
+
+local function extract (tl)
+ local tk = tl[1]
+ if tk[1] == 'space' then
+ tk = tl[2]
+ end
+ return tk
+end
+
+--- get an identifier from front of a token list.
+-- @return identifier name
+function TokenList.get_iden (tl)
+ local tk = extract(tl)
+ M.assert(tk[1]=='iden','expecting identifier')
+ return tk[2]
+end
+
+--- get an number from front of a token list.
+-- @return number
+function TokenList.get_number(tl)
+ local tk = extract(tl)
+ M.assert(tk[1]=='number','expecting number')
+ return tonumber(tk[2])
+end
+
+--- get a string from front of a token list.
+-- @return string value (without quotes)
+function TokenList.get_string(tl)
+ local tk = extract(tl)
+ M.assert(tk[1]=='string')
+ return tk[2]:sub(2,-2) -- watch out! what about long string literals??
+end
+
+--- takes a token list and strips spaces and comments.
+-- @return new tokenlist
+function TokenList.strip_spaces (tl)
+ local out = TokenList.new()
+ for _,t in ipairs(tl) do
+ if t[1] ~= 'comment' and t[1] ~= 'space' then
+ append(out,t)
+ end
+ end
+ return out
+end
+
+--- pick the n-th token from this tokenlist.
+-- Note that it returns the value and type, not the type and value.
+-- @param n (1 to #self)
+-- @return token value
+-- @return token type
+function TokenList.pick (tl,n)
+ local t = tl[n]
+ return t[2],t[1]
+end
+
+-- token-putting helpers
+local comma,space = {',',','},{'space',' '}
+
+--- append an identifier.
+-- @param name the identifier
+-- @param no_space true if you don't want a space after the iden
+-- @return self
+function TokenList.iden(res,name,no_space)
+ append(res,{'iden',name})
+ if not no_space then
+ append(res,space)
+ end
+ return res
+end
+
+TokenList.name = TokenList.iden -- backwards compatibility!
+
+--- append a string.
+-- @param s the string
+-- @return self
+function TokenList.string(res,s)
+ append(res,{'string','"'..s..'"'})
+ return res
+end
+
+--- append a number.
+-- @param val the number
+-- @return self
+function TokenList.number(res,val)
+ append(res,{'number',val})
+ return res
+end
+
+--- put out a list of identifiers, separated by commas.
+-- @param res output token list
+-- @param names a list of identifiers
+-- @return self
+function TokenList.idens(res,names)
+ for i = 1,#names do
+ res:iden(names[i],true)
+ if i ~= #names then append(res,comma) end
+ end
+ return res
+end
+
+TokenList.names = TokenList.idens -- backwards compatibility!
+
+--- put out a token list.
+-- @param res output token list
+-- @param tl a token list
+-- @return self
+function TokenList.tokens(res,tl)
+ for j = 1,#tl do
+ append(res,tl[j])
+ end
+ return res
+end
+
+--- put out a list of token lists, separated by commas.
+-- @param res output token list
+-- @param ltl a list of token lists
+-- @return self
+function TokenList.list(res,ltl)
+ for i = 1,#ltl do
+ res:tokens(ltl[i])
+ if i ~= #ltl then append(res,comma) end
+ end
+ return res
+end
+
+--- put out a space token.
+-- @param res output token list
+-- @param space a string containing only whitespace (default ' ')
+-- @return self
+function TokenList.space(res,space)
+ append(res,{'space',space or ' '})
+ return res
+end
+
+--- put out a keyword token.
+-- @param res output token list
+-- @param keyw a Lua keyword
+-- @param no_space true if you don't want a space after the iden
+-- @return self
+function TokenList.keyword(res,keyw,no_space)
+ append(res,{'keyword',keyw})
+ if not no_space then
+ append(res,space)
+ end
+ return res
+end
+
+--- convert this tokenlist into a string.
+function TokenList.__tostring(tl)
+ local res = {}
+ for j = 1,#tl do
+ append(res,tl[j][2])
+ end
+ return table.concat(res)
+end
+
+--- put out a operator token. This is the overloaded call operator
+-- for token lists.
+-- @param res output token list
+-- @param keyw an operator string
+function TokenList.__call(res,t,v)
+ append(res,{t,v or t})
+ return res
+end
+
+return TokenList
diff --git a/Tools/LuaMacro/macro/all.lua b/Tools/LuaMacro/macro/all.lua
new file mode 100644
index 0000000..0d7c098
--- /dev/null
+++ b/Tools/LuaMacro/macro/all.lua
@@ -0,0 +1,5 @@
+require 'macro.forall'
+require 'macro.lambda'
+require 'macro.try'
+require 'macro.do'
+
diff --git a/Tools/LuaMacro/macro/assert.lua b/Tools/LuaMacro/macro/assert.lua
new file mode 100644
index 0000000..b25daaf
--- /dev/null
+++ b/Tools/LuaMacro/macro/assert.lua
@@ -0,0 +1,74 @@
+--- a simple testing framework.
+-- Defines a single statment macro assert_ which has the following syntax:
+--
+-- - assert_ val1 == val2
+-- - assert_ val1 > val2
+-- - assert_ val1 < val2
+-- - assert_ val1 matches val2 (using string matching)
+-- - assert_ val1 throws val2 (ditto, on exception string)
+--
+-- The `==` case has some special forms. If `val2` is `(v1,v2,..)` then
+-- it's assumed that the expression `val1` returns multiple values. `==` will
+-- also do value equality for plain tables. If `val2` is a number given in
+-- %f format (such as 3.14) then it will match `vall` up to that specified
+-- number of digits.
+--
+-- assert_ {one=1,two=2} == {two=2,one=1}
+-- assert_ 'hello' matches '^hell'
+-- assert_ 2 > 1
+-- assert_ ('hello'):find 'll' == (3,4)
+-- assert_ a.x throws 'attempt to index global'
+-- @module macro.assert
+
+local M = require 'macro'
+local relop = {
+ ['=='] = 'eq',
+ ['<'] = 'lt',
+ ['>'] = 'gt'
+}
+
+local function numfmt (x)
+ local int,frac = x:match('(%d+)%.(%d+)')
+ if not frac then return nil end
+ return '%'..#x..'.'..#frac..'f', x
+end
+
+--- assert that two values match the desired relation.
+-- @macro assert_
+M.define('assert_',function(get,put)
+ local testx,tok = get:upto(function(t,v)
+ return relop[t] or (t == 'iden' and (v == 'matches' or v == 'throws'))
+ end)
+ local testy,eos = get:line()
+ local otesty = testy
+ testx = tostring(testx)
+ testy = tostring(testy)
+ local t,v,op = tok[1],tok[2]
+ if relop[t] then
+ op = relop[t]
+ if t == '==' then
+ if testy:match '^%(.+%)$' then
+ testx = 'T_.tuple('..testx..')'
+ testy = 'T_.tuple'..testy
+ elseif #otesty == 1 and otesty[1][1] == 'number' then
+ local num = otesty[1][2]
+ local fmt,num = numfmt(num)
+ if fmt then -- explicit floating-point literal
+ testy = '"'..num..'"'
+ testx = '("'..fmt..'"):format('..testx..')'
+ op = 'match'
+ end
+ end
+ end
+ elseif v == 'matches' then
+ op = 'match'
+ elseif v == 'throws' then
+ op = 'match'
+ testx = 'T_.pcall_no(function() return '..testx..' end)'
+ end
+ return ('T_.assert_%s(%s,%s)%s'):format(op,testx,testy,tostring(eos))
+end)
+
+return function()
+ return "T_ = require 'macro.lib.test'"
+end
diff --git a/Tools/LuaMacro/macro/builtin.lua b/Tools/LuaMacro/macro/builtin.lua
new file mode 100644
index 0000000..12c8b38
--- /dev/null
+++ b/Tools/LuaMacro/macro/builtin.lua
@@ -0,0 +1,161 @@
+-------
+-- LuaMacro built-in macros.
+-- @module macro.builtin
+
+local M = require 'macro'
+
+local function macro_def (scoped)
+ return function (get)
+ local t,name,parms,openp
+ local t,name = get:next()
+ local upto,ret
+ if t == '(' then
+ t,name = get:next()
+ upto = function(t,v) return t == ')' end
+ else
+ upto = function(t,v)
+ return t == 'space' and v:find '\n'
+ end
+ -- return space following (returned by copy_tokens)
+ ret = true
+ end
+ -- might be immediately followed by a parm list
+ t,openp = get()
+ if openp == '(' then
+ parms = get:names()
+ end
+ -- the actual substitution is up to the end of the line
+ local args, space = M.copy_tokens(get,upto)
+ if scoped then
+ M.define_scoped(name,args,parms)
+ else
+ M.set_macro(name,args,parms)
+ end
+ return ret and space[2]
+ end
+end
+
+--- a macro for defining lexically scoped simple macros.
+-- def_ may be followed by an arglist, and the substitution is the
+-- rest of the line.
+-- @usage def_ block (function() _END_CLOSE_
+-- @usage def_ sqr(x) ((x)*(x))
+-- @macro def_
+M.define ('def_',macro_def(true))
+
+--- a global version of `def_`.
+-- @see def_
+-- @macro define_
+M.define ('define_',macro_def(false))
+
+--- set the value of an existing macro.
+-- the name and value follows immediately, and the value must be
+-- a single token
+-- @usage set_ T 'string'
+-- @usage set_ F function
+-- @macro set_
+M.define('set_',function(get)
+ local name = get:name()
+ local t,v = get:next()
+ M.set_macro(name,{{t,v}})
+end)
+
+--- undefining identifier macros.
+-- @macro undef_
+M.define('undef_',function(get)
+ M.set_macro(get:name())
+end)
+
+--- Insert text after current block end. `_END_` is followed by a quoted string
+-- and is used to insert that string after the current block closes.
+-- @macro _END_
+M.define ('_END_',function(get)
+ local str = get:string()
+ M.block_handler(-1,function(get,word)
+ if word ~= 'end' then return nil,true end
+ return str
+ end)
+end)
+
+--- insert an end after the next closing block.
+-- @macro _END_END_
+-- @see _END_
+M.define '_END_END_ _END_ " end"'
+
+--- insert a closing parens after next closing block.
+-- @usage def_ begin (function() _END_CLOSE_
+-- fun begin ... end --> fun (function() ... end)
+-- @macro _END_CLOSE_
+-- @see _END_
+M.define '_END_CLOSE_ _END_ ")"'
+
+--- 'stringizing' macro.
+-- Will convert its argument into a string.
+-- @usage def_ _assert(x) assert(x,_STR_(x))
+-- @macro _STR_
+M.define('_STR_(x)',function(x)
+ x = tostring(x)
+ local put = M.Putter()
+ return put '"':name(x) '"'
+end)
+
+-- macro stack manipulation
+
+
+--- push a value onto a given macro' stack.
+-- @macro _PUSH_
+-- @param mac existing macro name
+-- @param V a string
+M.define('_PUSH_(mac,V)',function(mac,V)
+ M.push_macro_stack(mac:get_string(),V:get_string())
+end)
+
+--- pop a value from a macro's stack.
+-- @macro _POP_
+-- @param mac existing macro name
+-- @return a string
+-- @see _PUSH_
+M.define('_POP_',function(get,put)
+ local val = M.pop_macro_stack(get:string())
+ if val then
+ return put(val)
+ end
+end)
+
+--- drop the top of a macro's stack.
+-- Like `_POP_`, except that it does not return the value
+-- @macro _DROP_
+-- @return existing macro name
+-- @see _POP_
+M.define('_DROP_',function(get)
+ M.pop_macro_stack(get:string())
+end)
+
+--- Load a Lua module immediately. This allows macro definitions to
+-- to be loaded before the rest of the file is parsed.
+-- If the module returns a function, then this is assumed to be a
+-- substitution function, allowing macro modules to insert code
+-- at this point.
+-- @macro require_
+M.define('require_',function(get,put)
+ local name = get:string()
+ local ok,fn = pcall(require,name)
+ if not ok then
+ fn = require('macro.'..name)
+ end
+ if type(fn) == 'function' then
+ return fn(get,put)
+ end
+end)
+
+--- Include the contents of a file. This inserts the file directly
+-- into the token stream, and is equivalent to cpp's `#include` directive.
+-- @macro include_
+M.define('include_',function(get)
+ local str = get:string()
+ local f = M.assert(io.open(str))
+ local txt = f:read '*a'
+ f:close()
+ M.push_substitution(txt)
+end)
+
diff --git a/Tools/LuaMacro/macro/clexer.lua b/Tools/LuaMacro/macro/clexer.lua
new file mode 100644
index 0000000..fd859a8
--- /dev/null
+++ b/Tools/LuaMacro/macro/clexer.lua
@@ -0,0 +1,169 @@
+--[[--- A C lexical scanner using LPeg.
+= CREDITS
+= based on the C lexer in Peter Odding's lua-lxsh
+@module macro.clexer
+--]]
+
+local clexer = {}
+local lpeg = require 'lpeg'
+local P, R, S, C, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Ct
+
+-- create a pattern which captures the lua value [id] and the input matching
+-- [patt] in a table
+local function token(id, patt) return Ct(Cc(id) * C(patt)) end
+
+-- private interface
+local table_of_tokens
+local extra_tokens
+
+function clexer.add_extra_tokens(extra)
+ extra_tokens = extra_tokens or {}
+ for _,t in ipairs(extra) do
+ table.insert(extra_tokens,t)
+ end
+ table_of_tokens = nil -- re-initialize
+end
+
+function clexer.init ()
+ local digit = R('09')
+
+ local upp, low = R'AZ', R'az'
+ local oct, dec = R'07', R'09'
+ local hex = dec + R'AF' + R'af'
+ local letter = upp + low
+ local alnum = letter + dec + '_'
+ local endline = S'\r\n\f'
+ local newline = '\r\n' + endline
+ local escape = '\\' * ( newline
+ + S'\\"\'?abfnrtv'
+ + (#oct * oct^-3)
+ + ('x' * #hex * hex^-2))
+
+
+ -- range of valid characters after first character of identifier
+ local idsafe = R('AZ', 'az', '\127\255') + P '_'
+
+ -- operators
+ local OT = P '=='
+ if extra_tokens then
+ for _,ex in ipairs(extra_tokens) do
+ OT = OT + P(ex)
+ end
+ end
+ local operator = token('operator', OT + P '.' + P'>>=' + '<<=' + '--' + '>>' + '>=' + '/=' + '==' + '<='
+ + '+=' + '<<' + '*=' + '++' + '&&' + '|=' + '||' + '!=' + '&=' + '-='
+ + '^=' + '%=' + '->' + S',)*%+&(-~/^]{}|.[>!?:=<;')
+ -- identifiers
+ local ident = token('iden', idsafe * (idsafe + digit) ^ 0)
+
+ -- keywords
+ local keyword = token('keyword', (P 'auto' + P 'break' + P 'case' + P'char' +
+ P 'const' + P 'continue' + P 'default' +
+ P 'do' + P 'double' + P 'else' + P 'enum' + P 'extern' + P 'float' +
+ P 'for' + P 'goto' + P 'if' + P 'int' + P 'long' + P 'register' +
+ P 'return' + P 'short' + P 'signed' + P 'sizeof' + P 'static' +
+ P 'struct' + P 'switch' + P 'typedef' + P 'union' + P 'void' +
+ P 'volatile' + P 'while') * -(idsafe + digit))
+
+ -- numbers
+ local number_sign = S'+-'^-1
+ local number_decimal = digit ^ 1
+ local number_hexadecimal = P '0' * S 'xX' * R('09', 'AF', 'af') ^ 1
+ local number_float = (digit^1 * P'.' * digit^0 + P'.' * digit^1) *
+ (S'eE' * number_sign * digit^1)^-1
+ local number = token('number', number_hexadecimal +
+ number_float +
+ number_decimal)
+
+
+ local string = token('string', '"' * ((1 - S'\\\r\n\f"') + escape)^0 * '"')
+ local char = token('char',"'" * ((1 - S"\\\r\n\f'") + escape) * "'")
+
+ -- comments
+ local singleline_comment = P '//' * (1 - S '\r\n\f') ^ 0
+ local multiline_comment = '/*' * (1 - P'*/')^0 * '*/'
+ local comment = token('comment', multiline_comment + singleline_comment)
+ local prepro = token('prepro',P '#' * (1 - S '\r\n\f') ^ 0)
+
+ -- whitespace
+ local whitespace = token('space', S('\r\n\f\t ')^1)
+
+ -- ordered choice of all tokens and last-resort error which consumes one character
+ local any_token = whitespace + number + keyword + ident +
+ string + char + comment + prepro + operator + token('error', 1)
+
+
+ table_of_tokens = Ct(any_token ^ 0)
+end
+
+-- increment [line] by the number of line-ends in [text]
+local function sync(line, text)
+ local index, limit = 1, #text
+ while index <= limit do
+ local start, stop = text:find('\r\n', index, true)
+ if not start then
+ start, stop = text:find('[\r\n\f]', index)
+ if not start then break end
+ end
+ index = stop + 1
+ line = line + 1
+ end
+ return line
+end
+clexer.sync = sync
+
+clexer.line = 0
+
+-- we only need to synchronize the line-counter for these token types
+local multiline_tokens = { comment = true, space = true }
+clexer.multiline_tokens = multiline_tokens
+
+function clexer.scan_c_tokenlist(input)
+ if not table_of_tokens then
+ clexer.init()
+ end
+ assert(type(input) == 'string', 'bad argument #1 (expected string)')
+ local line = 1
+ local tokens = lpeg.match(table_of_tokens, input)
+ for i, token in pairs(tokens) do
+ local t = token[1]
+ if t == 'operator' or t == 'error' then
+ token[1] = token[2]
+ end
+ token[3] = line
+ if multiline_tokens[t] then
+ line = sync(line, token[2])
+ end
+ end
+ return tokens
+end
+
+--- get a token iterator from a source containing Lua code.
+-- S is the source - can be a string or a file-like object (i.e. read() returns line)
+-- Note that this token iterator includes spaces and comments, and does not convert
+-- string and number tokens - so e.g. a string token is quoted and a number token is
+-- an unconverted string.
+function clexer.scan_c(input,name)
+ if type(input) ~= 'string' and input.read then
+ input = input:read('*a')
+ end
+ local tokens = clexer.scan_c_tokenlist(input)
+ local i, n = 1, #tokens
+ return function(k)
+ if k ~= nil then
+ k = i + k
+ if k < 1 or k > n then return nil end
+ return tokens[k]
+ end
+ local tok = tokens[i]
+ i = i + 1
+ if tok then
+ clexer.line = tok[3]
+ clexer.name = name
+ return tok[1],tok[2]
+ end
+ end
+
+end
+
+return clexer
diff --git a/Tools/LuaMacro/macro/do.lua b/Tools/LuaMacro/macro/do.lua
new file mode 100644
index 0000000..45cf84c
--- /dev/null
+++ b/Tools/LuaMacro/macro/do.lua
@@ -0,0 +1,75 @@
+--- An intelligent 'loop-unrolling' macro.
+-- `do_` defines a named scoped macro `var` which is the loop iterator.
+--
+-- For example,
+--
+-- y = 0
+-- do_(i,1,10
+-- y = y + i
+-- )
+-- assert(y == 55)
+--
+-- `tuple` is an example of how the expansion of a macro can be
+-- controlled by its context. Normally a tuple `A` expands to
+-- `A_1,A_2,A_3` but inside `do_` it works element-wise:
+--
+-- tuple(3) A,B
+-- def_ do3(stmt) do_(k,1,3,stmt)
+-- do3(A = B/2)
+--
+-- This expands as
+--
+-- A_1 = B_1/2
+-- A_2 = B_2/2
+-- A_3 = B_3/2
+--
+-- @module macro.do
+local M = require 'macro'
+
+--- Expand a loop inline.
+-- @p var the loop variable
+-- @p start initial value of `var`
+-- @p finish final value of `var`
+-- @p stat the statement containing `var`
+-- @macro do_
+M.define('do_(v,s,f,stat)',function(var,start,finish,statements)
+ -- macros with specified formal args have to make their own putter,
+ -- and convert the actual arguments to the type they expect.
+ local put = M.Putter()
+ var,start,finish = var:get_iden(),start:get_number(),finish:get_number()
+ M.push_macro_stack('do_',var)
+ -- 'do_' works by setting the variable macro for each value
+ for i = start, finish do
+ put:name 'set_':name(var):number(i):space()
+ put:tokens(statements)
+ end
+ put:name 'undef_':name(var)
+ put:name '_DROP_':string 'do_':space()
+ return put
+end)
+
+--- an example of conditional expansion.
+-- `tuple` takes a list of variable names, like a declaration list except that it
+-- must end with a line end.
+-- @macro tuple
+M.define('tuple',function(get)
+ get:expecting '('
+ local N = get:number()
+ get:expecting ')'
+ local names = get:names '\n'
+ for _,name in ipairs(names) do
+ M.define(name,function(get,put)
+ local loop_var = M.value_of_macro_stack 'do_'
+ if loop_var then
+ local loop_idx = tonumber(M.get_macro_value(loop_var))
+ return put:name (name..'_'..loop_idx)
+ else
+ local out = {}
+ for i = 1,N do
+ out[i] = name..'_'..i
+ end
+ return put:names(out)
+ end
+ end)
+ end
+end)
diff --git a/Tools/LuaMacro/macro/forall.lua b/Tools/LuaMacro/macro/forall.lua
new file mode 100644
index 0000000..8ec5c68
--- /dev/null
+++ b/Tools/LuaMacro/macro/forall.lua
@@ -0,0 +1,70 @@
+--------------------
+-- `forall` statement.
+-- The syntax is `forall VAR SELECT [if CONDN] do` where
+-- `SELECT` is either `in TBL` or `= START,FINISH`
+--
+-- For example,
+--
+-- forall name in {'one','two'} do print(name) end
+--
+-- forall obj in get_objects() if obj:alive() then
+-- obj:action()
+-- end
+--
+-- Using `forall`, we also define _list comprehensions_ like
+-- `L{s:upper() | s in names if s:match '%S+'}`
+--
+-- @module macro.forall
+
+local M = require 'macro'
+
+--- extended for statement.
+-- @macro forall
+M.define('forall',function(get,put)
+ local var = get:iden()
+ local t,v = get:next()
+ local rest,endt = get:list(M.upto_keywords('do','if'))
+ put:keyword 'for'
+ if v == 'in' then
+ put:iden '_' ',' :iden(var):keyword 'in'
+ put:iden 'ipairs' '(' :list(rest) ')'
+ elseif v == '=' then
+ put:iden(var) '=' :list(rest)
+ else
+ M.error("expecting in or =")
+ end
+ put:keyword 'do'
+ if endt[2] == 'if' then
+ rest,endt = get:list(M.upto_keywords('do'))
+ put:keyword 'if':list(rest):keyword 'then':iden '_END_END_'
+ end
+ return put
+end)
+
+--- list comprehension.
+-- Syntax is `L{expr | select}` where `select` is as in `forall`,
+-- or `L{expr for select}` where `select` is as in the regular `for` statement.
+-- @macro L
+-- @return a list of values
+-- @usage L{2*x | x in {1,2,3}} == {1,4,9}
+-- @usage L{2*x|x = 1,3} == {1,4,9}
+-- @usage L{{k,v} for k,v in pairs(t)}
+-- @see forall
+M.define('L',function(get,put)
+ local t,v = get:next() -- must be '{'
+ local expr,endt = get:list(function(t,v)
+ return t == '|' or t == 'keyword' and v == 'for'
+ end,'')
+ local select = get:list('}','')
+ put '(' : keyword 'function' '(' ')' :keyword 'local':iden 'res' '=' '{' '}'
+ if endt[2] == '|' then
+ put:iden'forall'
+ else
+ put:keyword 'for'
+ end
+ put:list(select):space():keyword'do'
+ put:iden'res' '[' '#' :iden'res' '+' :number(1) ']' '=' :list(expr):space()
+ put:keyword 'end' :keyword 'return' : iden 'res' :keyword 'end' ')' '(' ')'
+ put:iden '_POP_':string'L'
+ return put
+end)
diff --git a/Tools/LuaMacro/macro/ifelse.lua b/Tools/LuaMacro/macro/ifelse.lua
new file mode 100644
index 0000000..3d5a0df
--- /dev/null
+++ b/Tools/LuaMacro/macro/ifelse.lua
@@ -0,0 +1,90 @@
+local M = require 'macro'
+
+local function eval (expr,was_expr)
+ expr = tostring(expr)
+ if was_expr then expr = "return "..expr end
+ local chunk = M.assert(loadstring(expr))
+ local ok, res = pcall(chunk)
+ if not ok then M.error("error evaluating "..res) end
+ return res
+end
+
+local function eval_line (get,was_expr)
+ local args = get:line()
+ return eval(args,was_expr)
+end
+
+local function grab (get)
+ local ilevel = 0
+ while true do
+ local t,v = get()
+ while t ~= '@' do t = get() end
+ t,v = get()
+ if v == 'if' then
+ ilevel = ilevel + 1
+ else -- 'end','elseif','else'
+ if ilevel > 0 and v == 'end' then
+ ilevel = ilevel - 1
+ elseif ilevel == 0 then return '@'..v end
+ end
+ end
+end
+
+M.define('@',function(get,put)
+ local t,v = get()
+--~ print('got',t,v)
+ return put:iden(v..'_')
+end)
+
+local ifstack,push,pop = {},table.insert,table.remove
+
+local function push_if (res)
+--~ print 'push'
+ push(ifstack, not (res==false or res==nil))
+end
+
+local function pop_if ()
+--~ print 'pop'
+ pop(ifstack)
+end
+
+M.define('if_',function(get)
+ local res = eval_line(get,true)
+ push_if(res)
+ if not res then
+ return grab(get)
+ end
+end)
+
+M.define('elseif_',function(get)
+ local res
+ if ifstack[#ifstack] then
+ res = false
+ else
+ res = eval_line(get,true)
+ pop_if()
+ push_if(res)
+ end
+ if not res then
+ return grab(get)
+ end
+end)
+
+M.define('else_',function(get)
+ if #ifstack == 0 then M.error("mismatched else") end
+ if ifstack[#ifstack] then
+ return grab(get)
+ end
+end)
+
+M.define('end_',function(get)
+ pop_if()
+end)
+
+M.define('let_',function(get)
+ eval_line(get)
+end)
+
+M.define('eval_(X)',function(X)
+ return tostring(eval(X,true))
+end)
diff --git a/Tools/LuaMacro/macro/lambda.lua b/Tools/LuaMacro/macro/lambda.lua
new file mode 100644
index 0000000..677f997
--- /dev/null
+++ b/Tools/LuaMacro/macro/lambda.lua
@@ -0,0 +1,22 @@
+--- Short anonymous functions (lambdas).
+-- This syntax is suited
+-- to any naive token-processor because the payload is always inside parens.
+-- It is an example of a macro associated with a 'operator' character.
+--
+-- Syntax is `\<args>(<expr>)`
+--
+-- `\x(x+10)` is short for
+-- `function(x) return x+10 end`. There may be a number of formal argumets,
+-- e.g. `\x,y(x+y)` or there may be none, e.g. `\(somefun())`. Such functions
+-- may return multiple values, e.g `\x(x+1,x-1)`.
+--
+-- @module macro.lambda
+
+local M = require 'macro'
+
+M.define ('\\',function(get,put)
+ local args, body = get:idens('('), get:list()
+ return put:keyword 'function' '(' : idens(args) ')' :
+ keyword 'return' : list(body) : space() : keyword 'end'
+end)
+
diff --git a/Tools/LuaMacro/macro/lc.lua b/Tools/LuaMacro/macro/lc.lua
new file mode 100644
index 0000000..0d2968d
--- /dev/null
+++ b/Tools/LuaMacro/macro/lc.lua
@@ -0,0 +1,343 @@
+-- Simplifying writing C extensions for Lua
+-- Adds new module and class constructs;
+-- see class1.lc and str.lc for examples.
+local M = require 'macro'
+
+function dollar_subst(s,tbl)
+ return (s:gsub('%$%((%a+)%)',tbl))
+end
+
+-- reuse some machinery from the C-skin experiments
+local push,pop = table.insert,table.remove
+local bstack,btop = {},{}
+
+local function push_brace_stack (newv)
+ newv = newv or {}
+ newv.lev = 0
+ push(bstack,btop)
+ btop = newv
+end
+
+M.define('{',function()
+ if btop.lev then
+ btop.lev = btop.lev + 1
+ end
+ return nil,true --> pass-through macro
+end)
+
+M.define('}',function(get,put)
+ if not btop.lev then
+ return nil,true
+ elseif btop.lev == 0 then
+ local res
+ if btop.handler then res = btop.handler(get,put) end
+ if not res then res = put:space() '}' end
+ btop = pop(bstack)
+ return res
+ else
+ btop.lev = btop.lev - 1
+ return nil,true --> pass-through macro
+ end
+end)
+
+--------- actual implementation begins -------
+
+local append = table.insert
+local module
+
+local function register_functions (names,cnames)
+ local out = {}
+ for i = 1,#names do
+ append(out,(' {"%s",l_%s},'):format(names[i],cnames[i]))
+ end
+ return table.concat(out,'\n')
+end
+
+local function finalizers (names)
+ local out = {}
+ for i = 1,#names do
+ append(out,names[i].."(L);")
+ end
+ return table.concat(out,'\n')
+end
+
+local typedefs
+
+local preamble = [[
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#ifdef WIN32
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+#if LUA_VERSION_NUM > 501
+#define lua_objlen lua_rawlen
+#endif
+]]
+
+local finis = [[
+static const luaL_Reg $(cname)_funs[] = {
+ $(funs)
+ {NULL,NULL}
+};
+
+EXPORT int luaopen_$(cname) (lua_State *L) {
+#if LUA_VERSION_NUM > 501
+ lua_newtable(L);
+ luaL_setfuncs (L,$(cname)_funs,0);
+ lua_pushvalue(L,-1);
+ lua_setglobal(L,"$(cname)");
+#else
+ luaL_register(L,"$(cname)",$(cname)_funs);
+#endif
+ $(finalizers)
+ return 1;
+}
+]]
+
+M.define('module',function(get)
+ local name = get:string()
+ local cname = name:gsub('%.','_')
+ get:expecting '{'
+ local out = preamble .. typedefs
+ push_brace_stack{
+ name = name, cname = cname,
+ names = {}, cnames = {}, finalizers = {},
+ handler = function()
+ local out = {}
+ local funs = register_functions(btop.names,btop.cnames)
+ local final = finalizers(btop.finalizers)
+ append(out,dollar_subst(finis, {
+ cname = cname,
+ name = name,
+ funs = funs,
+ finalizers = final
+ }))
+ return table.concat(out,'\n')
+ end }
+ module = btop
+ return out
+end)
+
+
+M.define('def',function(get)
+ local fname = get:name()
+ local cname = (btop.ns and btop.ns..'_' or '')..fname
+ append(btop.names,fname)
+ append(btop.cnames,cname)
+ get:expecting '('
+ local args = get:list():strip_spaces()
+ get:expecting '{'
+ local t,space = get()
+ indent = space:gsub('^%s*[\n\r]',''):gsub('%s$','')
+ local out = {"static int l_"..cname.."(lua_State *L) {"}
+ if btop.massage_arg then
+ btop.massage_arg(args)
+ end
+ for i,arg in ipairs(args) do
+ local mac = arg[1][2]..'_init'
+ if arg[3] and arg[3][1] == '=' then
+ mac = mac .. 'o'
+ i = i .. ',' .. arg[4][2]
+ end
+ if not arg[2] then M.error("parameter must be TYPE NAME [= VALUE]") end
+ append(out,indent..mac..'('..arg[2][2]..','..i..');')
+ end
+ --append(out,space)
+ return table.concat(out,'\n')..space
+end)
+
+M.define('constants',function(get,put)
+ get:expecting '{'
+ local consts = get:list '}' :strip_spaces()
+ --for k,v in pairs(btop) do io.stderr:write(k,'=',tostring(v),'\n') end
+ -- os.exit()
+ local fname = 'set_'..btop.cname..'_constants'
+ local out = { 'static void '..fname..'(lua_State *L) {'}
+ if not btop.finalizers then M.error("not inside a module") end
+ append(btop.finalizers,fname)
+ for _,c in ipairs(consts) do
+ local type,value,name
+ if #c == 1 then -- a simple int constant: CONST
+ name = c:pick(1)
+ type = 'Int'
+ value = name
+ else -- Type CONST [ = VALUE ]
+ type = c:pick(1)
+ name = c:pick(2)
+ if #c == 2 then
+ value = name
+ else
+ value = c:pick(4)
+ end
+ end
+ append(out,('%s_set("%s",%s);'):format(type,name,value ))
+ end
+ append(out,'}')
+ return table.concat(out,'\n')
+end)
+
+M.define('assign',function(get)
+ get:expecting '{'
+ local asses = get:list '}' :strip_spaces()
+ local out = {}
+ for _,c in ipairs(asses) do
+ append(out,('%s_set("%s",%s);\n'):format(c:pick(1),c:pick(2),c:pick(4)) )
+ end
+ return table.concat(out,'\n')
+end)
+
+local load_lua = [[
+static void load_lua_code (lua_State *L) {
+ luaL_dostring(L,lua_code_block);
+}
+]]
+
+M.define('lua',function(get)
+ get:expecting '{'
+ local block = tostring(get:upto '}')
+ local code_name = 'lua_code_block'
+ local out = {'static const char *'.. code_name .. ' = ""\\'}
+ for line in block:gmatch('([^\r\n]+)') do
+ line = line:gsub('\\','\\\\'):gsub('"','\\"')
+ append(out,' "'..line..'\\n"\\')
+ end
+ append(out,';')
+ append(out,load_lua)
+ out = table.concat(out,'\n')
+ append(module.finalizers,'load_lua_code')
+ return out
+end)
+
+typedefs = [[
+typedef const char *Str;
+typedef const char *StrNil;
+typedef int Int;
+typedef double Number;
+typedef int Boolean;
+]]
+
+
+M.define 'Str_init(var,idx) const char *var = luaL_checklstring(L,idx,NULL)'
+M.define 'Str_inito(var,idx,val) const char *var = luaL_optlstring(L,idx,val,NULL)'
+M.define 'Str_set(name,value) lua_pushstring(L,value); lua_setfield(L,-2,name)'
+M.define 'Str_get(var,name) lua_getfield(L,-1,name); var=lua_tostring(L,-1); lua_pop(L,1)'
+M.define 'Str_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tostring(L,-1); lua_pop(L,1)'
+
+M.define 'StrNil_init(var,idx) const char *var = lua_tostring(L,idx)'
+
+M.define 'Int_init(var,idx) int var = luaL_checkinteger(L,idx)'
+M.define 'Int_inito(var,idx,val) int var = luaL_optinteger(L,idx,val)'
+M.define 'Int_set(name,value) lua_pushinteger(L,value); lua_setfield(L,-2,name)'
+M.define 'Int_get(var,name) lua_getfield(L,-1,name); var=lua_tointeger(L,-1); lua_pop(L,1)'
+M.define 'Int_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tointeger(L,-1); lua_pop(L,1)'
+
+M.define 'Number_init(var,idx) double var = luaL_checknumber(L,idx)'
+M.define 'Number_inito(var,idx,val) double var = luaL_optnumber(L,idx,val)'
+M.define 'NUmber_set(name,value) lua_pushnumber(L,value); lua_setfield(L,-2,name)'
+M.define 'Number_get(var,name) lua_getfield(L,-1,name); var=lua_tonumber(L,-1); lua_pop(L,1)'
+M.define 'Number_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tonumber(L,-1); lua_pop(L,1)'
+
+M.define 'Boolean_init(var,idx) int var = lua_toboolean(L,idx)'
+M.define 'Boolean_set(name,value) lua_pushboolean(L,value); lua_setfield(L,-2,name)'
+M.define 'Boolean_get(var,name) lua_getfield(L,-1,name); var=lua_toboolean(L,-1); lua_pop(L,1)'
+M.define 'Boolean_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_toboolean(L,-1); lua_pop(L,1)'
+
+M.define 'Value_init(var,idx) int var = idx'
+
+M.define('lua_tests',function(get)
+ get:expecting '{'
+ local body = get:upto '}'
+ local f = io.open(M.filename..'.lua','w')
+ f:write(tostring(body))
+ f:close()
+end)
+
+------ class support ----------------------
+
+local klass_ctor = "static void $(klass)_ctor(lua_State *L, $(klass) *this, $(fargs))"
+
+local begin_klass = [[
+
+typedef struct {
+ $(fields)
+} $(klass);
+
+define_ $(klass)_init(var,idx) $(klass) *var = $(klass)_arg(L,idx)
+
+#define $(klass)_MT "$(klass)"
+
+$(klass) * $(klass)_arg(lua_State *L,int idx) {
+ $(klass) *this = ($(klass) *)luaL_checkudata(L,idx,$(klass)_MT);
+ luaL_argcheck(L, this != NULL, idx, "$(klass) expected");
+ return this;
+}
+
+$(ctor);
+
+static int push_new_$(klass)(lua_State *L,$(fargs)) {
+ $(klass) *this = ($(klass) *)lua_newuserdata(L,sizeof($(klass)));
+ luaL_getmetatable(L,$(klass)_MT);
+ lua_setmetatable(L,-2);
+ $(klass)_ctor(L,this,$(aargs));
+ return 1;
+}
+
+]]
+
+local end_klass = [[
+
+static const struct luaL_Reg $(klass)_methods [] = {
+ $(methods)
+ {NULL, NULL} /* sentinel */
+};
+
+static void $(klass)_register (lua_State *L) {
+ luaL_newmetatable(L,$(klass)_MT);
+#if LUA_VERSION_NUM > 501
+ luaL_setfuncs(L,$(klass)_methods,0);
+#else
+ luaL_register(L,NULL,$(klass)_methods);
+#endif
+ lua_pushvalue(L,-1);
+ lua_setfield(L,-2,"__index");
+ lua_pop(L,1);
+}
+]]
+
+M.define('class',function(get)
+ local name = get:iden()
+ get:expecting '{'
+ local fields = get:upto (function(t,v)
+ return t == 'iden' and v == 'constructor'
+ end)
+ fields = tostring(fields):gsub('%s+$','\n')
+ get:expecting '('
+ local out = {}
+ local args = get:list()
+ local f_args = args:strip_spaces()
+ local a_args = f_args:pick(2)
+ f_args = table.concat(args:__tostring(),',')
+ a_args = table.concat(a_args,',')
+ local subst = {klass=name,fields=fields,fargs=f_args,aargs=a_args }
+ local proto = dollar_subst(klass_ctor,subst)
+ subst.ctor = proto
+ append(out,dollar_subst(begin_klass,subst))
+ append(out,proto)
+ local pp = {{'iden',name},{'iden','this'}}
+ push_brace_stack{
+ names = {}, cnames = {}, ns = name, cname = name,
+ massage_arg = function(args)
+ table.insert(args,1,pp)
+ end,
+ handler = function(get,put)
+ append(module.finalizers,name.."_register")
+ local methods = register_functions(btop.names,btop.cnames)
+ return dollar_subst(end_klass,{methods=methods,klass=name,fargs=f_args,aargs=a_args})
+ end
+ }
+ return table.concat(out,'\n')
+end)
+
diff --git a/Tools/LuaMacro/macro/lexer.lua b/Tools/LuaMacro/macro/lexer.lua
new file mode 100644
index 0000000..58ab53a
--- /dev/null
+++ b/Tools/LuaMacro/macro/lexer.lua
@@ -0,0 +1,179 @@
+--[[--- A Lua lexical scanner using LPeg.
+= CREDITS
+Written by Peter Odding, 2007/04/04
+
+= THANKS TO
+- the Lua authors for a wonderful language;
+- Roberto for LPeg;
+- caffeine for keeping me awake :)
+
+= LICENSE
+Shamelessly ripped from the SQLite[3] project:
+
+ The author disclaims copyright to this source code. In place of a legal
+ notice, here is a blessing:
+
+ May you do good and not evil.
+ May you find forgiveness for yourself and forgive others.
+ May you share freely, never taking more than you give.
+
+@module macro.lexer
+--]]
+
+local lexer = {}
+local lpeg = require 'lpeg'
+local P, R, S, C, Cb, Cc, Cg, Cmt, Ct =
+ lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cb, lpeg.Cc, lpeg.Cg, lpeg.Cmt, lpeg.Ct
+
+-- create a pattern which captures the lua value [id] and the input matching
+-- [patt] in a table
+local function token(id, patt) return Ct(Cc(id) * C(patt)) end
+
+-- private interface
+local table_of_tokens
+local extra_tokens
+
+function lexer.add_extra_tokens(extra)
+ extra_tokens = extra_tokens or {}
+ for _,t in ipairs(extra) do
+ table.insert(extra_tokens,t)
+ end
+ table_of_tokens = nil -- re-initialize
+end
+
+function lexer.init ()
+ local digit = R('09')
+
+ -- range of valid characters after first character of identifier
+ --local idsafe = R('AZ', 'az', '\127\255') + P '_'
+ local idsafe = R('AZ', 'az') + P '_' + R '\206\223' * R '\128\255'
+ -- operators
+ local OT = P '=='
+ if extra_tokens then
+ for _,ex in ipairs(extra_tokens) do
+ OT = OT + P(ex)
+ end
+ end
+ local operator = token('operator', OT + P '.' + P '~=' + P '<=' + P '>=' + P '...'
+ + P '..' + S '+-*/%^#=<>;:,.{}[]()')
+ -- identifiers
+ local ident = token('iden', idsafe * (idsafe + digit) ^ 0)
+
+ -- keywords
+ local keyword = token('keyword', (P 'and' + P 'break' + P 'do' + P 'elseif' +
+ P 'else' + P 'end' + P 'false' + P 'for' + P 'function' + P 'if' +
+ P 'in' + P 'local' + P 'nil' + P 'not' + P 'or' + P 'repeat' + P 'return' +
+ P 'then' + P 'true' + P 'until' + P 'while') * -(idsafe + digit))
+
+ -- numbers
+ local number_sign = S'+-'^-1
+ local number_decimal = digit ^ 1
+ local number_hexadecimal = P '0' * S 'xX' * R('09', 'AF', 'af') ^ 1
+ local number_float = (digit^1 * P'.' * digit^0 + P'.' * digit^1) *
+ (S'eE' * number_sign * digit^1)^-1
+ local number = token('number', number_hexadecimal +
+ number_float +
+ number_decimal)
+
+ -- callback for [=[ long strings ]=]
+ -- ps. LPeg is for Lua what regex is for Perl, which makes me smile :)
+ local equals = P '=' ^ 0
+ local open = P '[' * Cg(equals, "init") * P '[' * P '\n' ^ -1
+ local close = P ']' * C(equals) * P ']'
+ local closeeq = Cmt(close * Cb "init", function (s, i, a, b) return a == b end)
+ local longstring = open * C((P(1) - closeeq)^0) * close --/ 1
+
+ -- strings
+ local singlequoted_string = P "'" * ((1 - S "'\r\n\f\\") + (P '\\' * 1)) ^ 0 * "'"
+ local doublequoted_string = P '"' * ((1 - S '"\r\n\f\\') + (P '\\' * 1)) ^ 0 * '"'
+ local string = token('string', singlequoted_string +
+ doublequoted_string +
+ longstring)
+
+ -- comments
+ local singleline_comment = P '--' * (1 - S '\r\n\f') ^ 0
+ local multiline_comment = P '--' * longstring
+ local comment = token('comment', multiline_comment + singleline_comment)
+
+ -- whitespace
+ local whitespace = token('space', S('\r\n\f\t ')^1)
+
+ -- ordered choice of all tokens and last-resort error which consumes one character
+ local any_token = whitespace + number + keyword + ident +
+ string + comment + operator + token('error', 1)
+
+
+ table_of_tokens = Ct(any_token ^ 0)
+end
+
+-- increment [line] by the number of line-ends in [text]
+local function sync(line, text)
+ local index, limit = 1, #text
+ while index <= limit do
+ local start, stop = text:find('\r\n', index, true)
+ if not start then
+ start, stop = text:find('[\r\n\f]', index)
+ if not start then break end
+ end
+ index = stop + 1
+ line = line + 1
+ end
+ return line
+end
+lexer.sync = sync
+
+lexer.line = 0
+
+-- we only need to synchronize the line-counter for these token types
+local multiline_tokens = { comment = true, string = true, space = true }
+lexer.multiline_tokens = multiline_tokens
+
+function lexer.scan_lua_tokenlist(input)
+ if not table_of_tokens then
+ lexer.init()
+ end
+ assert(type(input) == 'string', 'bad argument #1 (expected string)')
+ local line = 1
+ local tokens = lpeg.match(table_of_tokens, input)
+ for i, token in pairs(tokens) do
+ local t = token[1]
+ if t == 'operator' or t == 'error' then
+ token[1] = token[2]
+ end
+ token[3] = line
+ if multiline_tokens[t] then
+ line = sync(line, token[2])
+ end
+ end
+ return tokens
+end
+
+--- get a token iterator from a source containing Lua code.
+-- Note that this token iterator includes spaces and comments, and does not convert
+-- string and number tokens - so e.g. a string token is quoted and a number token is
+-- an unconverted string.
+-- @param input the source - can be a string or a file-like object (i.e. read() returns line)
+-- @param name for the source
+function lexer.scan_lua(input,name)
+ if type(input) ~= 'string' and input.read then
+ input = input:read('*a')
+ end
+ local tokens = lexer.scan_lua_tokenlist(input)
+ local i, n = 1, #tokens
+ return function(k)
+ if k ~= nil then
+ k = i + k
+ if k < 1 or k > n then return nil end
+ return tokens[k]
+ end
+ local tok = tokens[i]
+ i = i + 1
+ if tok then
+ lexer.line = tok[3]
+ lexer.name = name
+ return tok[1],tok[2]
+ end
+ end
+end
+
+return lexer
diff --git a/Tools/LuaMacro/macro/lib/class.lua b/Tools/LuaMacro/macro/lib/class.lua
new file mode 100644
index 0000000..f762f36
--- /dev/null
+++ b/Tools/LuaMacro/macro/lib/class.lua
@@ -0,0 +1,35 @@
+----
+-- a basic class mechanism.
+-- Used for some of the demonstrations; the `class` macro in the `module`
+-- package uses it. It provides a single function which returns a new 'class'.
+-- The resulting object can be called to generate an instance of the class.
+-- You may provide a base class for single inheritance; in this case, the functions
+-- of the base class will be copied into the new class' metatable (so-called 'fat metatable')
+--
+-- Example:
+--
+-- local class = require 'macro.lib.class'
+-- A = class()
+-- function A._init(name) self.name = name end
+-- a = A("hello")
+-- assert(a.name == "hello")
+--
+-- @module macro.lib.class
+
+return function (base)
+ -- OOP with single inheritance
+ local klass,cmt = {},{}
+ if base then -- 'fat metatable' inheritance
+ for k,v in pairs(base) do klass[k] = v end
+ end
+ klass.__index = klass
+ -- provide a callable constructor that invokes user-supplied ctor
+ function cmt:__call(...)
+ local obj = setmetatable({},klass)
+ if klass._init then klass._init(obj,...)
+ elseif base and base._init then base._init(base,...) end
+ return obj
+ end
+ setmetatable(klass,cmt)
+ return klass
+end
diff --git a/Tools/LuaMacro/macro/lib/test.lua b/Tools/LuaMacro/macro/lib/test.lua
new file mode 100644
index 0000000..5fff39e
--- /dev/null
+++ b/Tools/LuaMacro/macro/lib/test.lua
@@ -0,0 +1,144 @@
+--- `assert_` macro library support.
+-- This module may of course be used on its own; `assert_` merely provides
+-- some syntactical sugar for its functionality. It is based on Penlight's
+-- `pl.test` module.
+-- @module macro.libs.test
+
+local test = {}
+
+local _eq,_tostring
+
+-- very much like tablex.deepcompare from Penlight
+function _eq (v1,v2)
+ if type(v1) ~= type(v2) then return false end
+ -- if the value isn't a table, or it has defined the equality operator..
+ local mt = getmetatable(v1)
+ if (mt and mt.__eq) or type(v1) ~= 'table' then
+ return v1 == v2
+ end
+ -- both values are plain tables
+ if v1 == v2 then return true end -- they were the same table...
+ for k1,x1 in pairs(v1) do
+ local x2 = v2[k1]
+ if x2 == nil or not _eq(x1,x2) then return false end
+ end
+ for k2,x2 in pairs(v2) do
+ local x1 = v1[k2]
+ if x1 == nil or not _eq(x1,x2) then return false end
+ end
+ return true
+end
+
+local function keyv (k)
+ if type(k) ~= 'string' then
+ k = '['..k..']'
+ end
+ return k
+end
+
+function _tostring (val)
+ local mt = getmetatable(val)
+ if (mt and mt.__tostring) or type(val) ~= 'table' then
+ if type(val) == 'string' then
+ return '"'..tostring(val)..'"'
+ else
+ return tostring(val)
+ end
+ end
+ -- dump the table; doesn't need to be pretty!
+ local res = {}
+ local function put(s) res[#res+1] = s end
+ put '{'
+ for k,v in pairs(val) do
+ put(keyv(k)..'=')
+ put(_tostring(v))
+ put ','
+ end
+ table.remove(res) -- remove last ','
+ put '}'
+ return table.concat(res)
+end
+
+local function _lt (v1,v2) return v1 < v2 end
+local function _gt (v1,v2) return v1 > v2 end
+local function _match (v1,v2) return v1:match(v2) end
+
+local function _assert (v1,v2,cmp,msg)
+ if not cmp(v1,v2) then
+ print('first:',_tostring(v1))
+ print(msg)
+ print('second:',_tostring(v2))
+ error('assertion failed',3)
+ end
+end
+
+--- assert if parameters are not equal. If the values are tables,
+-- they will be compared by value.
+-- @param v1 given value
+-- @param v2 test value
+function test.assert_eq (v1,v2)
+ _assert(v1,v2,_eq,"is not equal to");
+end
+
+--- assert if first parameter is not less than second.
+-- @param v1 given value
+-- @param v2 test value
+function test.assert_lt (v1,v2)
+ _assert(v1,v2,_lt,"is not less than")
+end
+
+--- assert if first parameter is not greater than second.
+-- @param v1 given value
+-- @param v2 test value
+function test.assert_gt (v1,v2)
+ _assert(v1,v2,_gt,"is not greater than")
+end
+
+--- assert if first parameter string does not match the second.
+-- The condition is `v1:match(v2)`.
+-- @param v1 given value
+-- @param v2 test value
+function test.assert_match (v1,v2)
+ _assert(v1,v2,_match,"does not match")
+end
+
+-- return the error message from a function that raises an error.
+-- Will raise an error if the function did not raise an error.
+-- @param fun the function
+-- @param ... any arguments to the function
+-- @return the error message
+function test.pcall_no(fun,...)
+ local ok,err = pcall(fun,...)
+ if ok then error('expression did not throw error',3) end
+ return err
+end
+
+local tuple = {}
+
+function tuple.__eq (a,b)
+ if a.n ~= b.n then return false end
+ for i=1, a.n do
+ if not _eq(a[i],b[i]) then return false end
+ end
+ return true
+end
+
+function tuple.__tostring (self)
+ local ts = {}
+ for i = 1,self.n do
+ ts[i] = _tostring(self[i])
+ end
+ return '('..table.concat(ts,',')..')'
+end
+
+--- create a tuple capturing multiple return values.
+-- Equality between tuples means that all of their values are equal;
+-- values may be `nil`
+-- @param ... any values
+-- @return a tuple object
+function test.tuple(...)
+ return setmetatable({n=select('#',...),...},tuple)
+end
+
+return test
+
diff --git a/Tools/LuaMacro/macro/module.lua b/Tools/LuaMacro/macro/module.lua
new file mode 100644
index 0000000..a8e1b8d
--- /dev/null
+++ b/Tools/LuaMacro/macro/module.lua
@@ -0,0 +1,132 @@
+--[[---
+Easy no-fuss modules.
+
+Any function inside the module will be exported, unless it is explicitly
+local. The functions are declared up front using patching, leading to efficient
+calls between module functions.
+
+ require_ 'module'
+
+ function one ()
+ return two()
+ end
+
+ function two ()
+ return 42
+ end
+
+Classes can also be declared inside modules:
+
+ require_ 'module'
+
+ class A
+ function set(self,val) @val = val end
+ function get(self) return @val end
+ end
+
+Within class definitions, the macro `@` expands to either `self.` or `self:` depending
+on context, and provides a Ruby-like shortcut.
+
+If you give these modules names with `m.lua` extension like `mod.m.lua`, then you can
+simply use `require()` to use them with LuaMacro.
+
+@module macro.module
+]]
+local M = require 'macro'
+
+local locals, locals_with_value = {}, {}
+local ref
+
+local function module_add_new_local (name)
+ locals[#locals+1] = name
+end
+
+local function module_add_new_local_with_value (name,value)
+ locals_with_value[name] = value
+end
+
+
+local function was_local_function (get)
+ local tl,keyw = get:peek(-1)
+ return tl=='keyword' and keyw=='local'
+end
+
+-- exclude explicitly local functions and anonymous functions.
+M.keyword_handler('function',function(get)
+ local tn,name = get:peek(1)
+ local was_local = was_local_function(get)
+ if not was_local and tn == 'iden' then
+ module_add_new_local(name)
+ end
+end)
+
+-- when the module is closed, this will patch the locals and
+-- output the module table.
+M.keyword_handler('END',function(get)
+ local concat = table.concat
+ local patch = ''
+ if next(locals_with_value) then
+ local lnames,lvalues = {},{}
+ local i = 1
+ for k,v in pairs(locals_with_value) do
+ lnames[i] = k
+ lvalues[i] = v
+ i = i + 1
+ end
+ patch = patch..'local '..concat(lnames,',')..'='..concat(lvalues,',')..'; '
+ end
+ if #locals > 0 then
+ patch = patch .. 'local '..concat(locals,',')
+ end
+ get:patch(ref,patch)
+ local dcl = {}
+ for i,name in ipairs(locals) do
+ dcl[i] = name..'='..name
+ end
+ dcl = table.concat(dcl,', ')
+ return 'return {' .. dcl .. '}'
+end)
+
+local no_class_require
+
+-- the meaning of @f is either 'self.f' for fields, or 'self:f' for methods.
+local function at_handler (get,put)
+ local tn,name,tp = get:peek2(1)
+ M.assert(tn == 'iden','identifier must follow @')
+ return put:iden ('self',true) (tp=='(' and ':' or '.')
+end
+
+local function method_handler (get,put)
+ local tn,name,tp = get:peek2()
+ if not was_local_function(get) and tn == 'iden' and tp == '(' then
+ return put ' ' :iden ('_C',true) '.'
+ end
+end
+
+M.define ('_C_',function()
+ M.define_scoped('@',at_handler)
+ if not no_class_require then
+ module_add_new_local_with_value('_class','require "macro.lib.class"')
+ no_class_require = true
+ end
+ M.scoped_keyword_handler('function',method_handler)
+end)
+
+M.define('class',function(get)
+ local base = ''
+ local name = get:iden()
+ if get:peek(1) == ':' then
+ get:next()
+ base = get:iden()
+ end
+ module_add_new_local(name)
+ return ('do local _C = _class(%s); %s = _C; _C_\n'):format(base,name)
+end)
+
+-- the result of the macro is just a placeholder for the locals
+return function(get,put)
+ ref = get:placeholder(put)
+ return put
+end
+
+
diff --git a/Tools/LuaMacro/macro/try.lua b/Tools/LuaMacro/macro/try.lua
new file mode 100644
index 0000000..8e49eb0
--- /dev/null
+++ b/Tools/LuaMacro/macro/try.lua
@@ -0,0 +1,47 @@
+--- A try/except block.
+-- This generates syntactical sugar around `pcal`l, and correctly
+-- distinguishes between the try block finishing naturally and
+-- explicitly using 'return' with no value. This is handled by
+-- converting any no value `return` to `return nil`.
+--
+-- Apart from the usual creation of a closure, this uses a table
+-- to capture all the results. Not likely to win speed contests,
+-- but intended to be correct.
+-- @module macro.try
+
+local M = require 'macro'
+
+local function pack (...)
+ local args = {...}
+ args.n = select('#',...)
+ return args
+end
+
+function pcall_(fn,...)
+ return pack(pcall(fn,...))
+end
+
+local function check_return_value(get,put)
+ local t,v = get:next()
+ put:space()
+ if t=='keyword' and (v=='end' or v=='else' or v=='until') then
+ put:keyword 'nil'
+ end
+ return put(t,v)
+end
+
+
+M.define('RR_',M.make_scoped_handler('return',check_return_value))
+
+
+--- A try macro, paired with except.
+--
+-- try
+-- maybe_something_bad()
+-- except (e)
+-- print(e)
+-- end
+-- @macro try
+M.define 'try do local r_ = pcall_(function() RR_ '
+M.define 'except(e) end); if r_[1] then if r_.n > 1 then return unpack(r_,2,r_.n) end else local e = r_[2] _END_END_ '
+
diff --git a/Tools/LuaMacro/macro/with.lua b/Tools/LuaMacro/macro/with.lua
new file mode 100644
index 0000000..de51590
--- /dev/null
+++ b/Tools/LuaMacro/macro/with.lua
@@ -0,0 +1,31 @@
+--[[--
+A `with` statement. This works more like the Visual Basic statement than the
+Pascal one; fields have an explicit period to indicate that they are special.
+This makes variable scoping explcit.
+
+ aLongTableName = {}
+ with aLongTableName do
+ .a = 1
+ .b = {{x=1},{x=2}}
+ .c = {f = 2}
+ print(.a,.c.f,.b[1].x)
+ end
+
+Fields that follow an identifier or a `}` are passed as-is.
+
+@module macro.with
+]]
+local M = require 'macro'
+
+M.define('with',function(get,put)
+ M.define_scoped('.',function()
+ local lt,lv = get:peek(-1,true) -- peek before the period...
+ if lt ~= 'iden' and lt ~= ']' then
+ return '_var.'
+ else
+ return nil,true -- pass through
+ end
+ end)
+ local expr = get:upto 'do'
+ return 'do local _var = '..tostring(expr)..'; '
+end)