summaryrefslogtreecommitdiff
path: root/Tools/LuaMacro/macro/module.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/LuaMacro/macro/module.lua')
-rw-r--r--Tools/LuaMacro/macro/module.lua132
1 files changed, 132 insertions, 0 deletions
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
+
+