[ANN] lcm -- Lua Code Melder, 2026-04

108 views
Skip to first unread message

Martin Eden

unread,
Apr 23, 2026, 1:22:37 PM (10 days ago) Apr 23
to lu...@googlegroups.com
Hello list,

I've updated lcm, Lua Code Melder.

It's command-line tool to merge all .lua files in provided directory
into one Lua code block.

  https://github.com/martin-eden/lua_code_melder

Now it prints usage help! But more importantly, now it gets working
directory name from command-line. Before you had to chdir to working
directory and call it remotely.

Ability to call any module from package from command-line is removed. No
uses.

Ah, and code casing in implementation and generated code is changed from
CamelCase to snake_case. (Except for tables and their fields.) Thanks to
Scott Morgan for hint.

All feedback is welcome!

-- Martin

Marc Balmer

unread,
Apr 23, 2026, 1:51:01 PM (10 days ago) Apr 23
to lu...@googlegroups.com, lu...@googlegroups.com


> Am 23.04.2026 um 19:22 schrieb 'Martin Eden' via lua-l <lu...@googlegroups.com>:
>
> Hello list,
>
> I've updated lcm, Lua Code Melder.
>
> It's command-line tool to merge all .lua files in provided directory into one Lua code block.

How does it differ from $ luac -o myprog.luac *.lua ?

>
> https://github.com/martin-eden/lua_code_melder
>
> Now it prints usage help! But more importantly, now it gets working directory name from command-line. Before you had to chdir to working directory and call it remotely.
>
> Ability to call any module from package from command-line is removed. No uses.
>
> Ah, and code casing in implementation and generated code is changed from CamelCase to snake_case. (Except for tables and their fields.) Thanks to Scott Morgan for hint.
>
> All feedback is welcome!
>
> -- Martin
>
> --
> You received this message because you are subscribed to the Google Groups "lua-l" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/lua-l/2c3ac1e3-77aa-4b88-b94c-b11f70683ba7%40disroot.org.

Martin Eden

unread,
Apr 23, 2026, 2:08:37 PM (10 days ago) Apr 23
to lu...@googlegroups.com
On 2026-04-23 19:50, Marc Balmer wrote:
>> Am 23.04.2026 um 19:22 schrieb 'Martin Eden' via lua-l<lu...@googlegroups.com>:
>>
>> Hello list,
>>
>> I've updated lcm, Lua Code Melder.
>>
>> It's command-line tool to merge all .lua files in provided directory into one Lua code block.
> How does it differ from $ luac -o myprog.luac *.lua ?
Guten Abend, Marc.

It's directory tree scanner which embeds literal file contents
(with quoting) into generated text code. When you run Lua on that
generated code file -- only one module will run (which was specified
at pack time).

As I understood from "man luac", "luac *.lua" compiles .lua files
from current directory and places their bytecodes in another file.
Running Lua on that file will run all that files in some sequence.

So I don't think "luac" is designed for bundling.

-- Martin

mjmouse9999

unread,
Apr 23, 2026, 7:16:50 PM (10 days ago) Apr 23
to lua-l
On Friday, April 24, 2026 at 3:22 am UTC+10 Martin Eden wrote:
> Hello list,
>
> I've updated lcm, Lua Code Melder.
>
> It's command-line tool to merge all .lua files in provided directory
> into one Lua code block.
>
>   https://github.com/martin-eden/lua_code_melder

This bit of the implementation is interesting:

  local add_module =
    function(module_name, module_code_str)
      local compiled_code = assert(load(module_code_str, module_name, 't'))

      _G.package.preload[module_name] =
        function(...)
          return compiled_code(...)
        end
    end

  for module_name, module_code_str in pairs(Modules) do
    add_module(module_name, module_code_str)
  end


I did make a combiner for the main Teal module to generate tl.lua, but it
instead just fills out package.preload directly, rather than doing separate
load(...) calls. It also scans the dependencies by looking for require statements
(This is helped by the fact that Teal always includes the brackets)

It instead just does the following for each module in the output file:

  -- module this from this.lua
  package.preload["this"] = function(...)
    -- contents of the this.lua file
  end


https://github.com/teal-language/tl/blob/main/extras/combine.tl

However it looks like your implementation will give correct line numbers for
errors and tracebacks, which is a very nice thing to have.

> [...]

Martin Eden

unread,
Apr 24, 2026, 2:54:32 AM (9 days ago) Apr 24
to lu...@googlegroups.com
Hello Mjmouse,

I've tested your approach and like it. Going to use it too.

Thanks for the feedback!

--

Below is my two test scripts. We are testing line numbers
using debug.traceback(). In both cases line numbers are relevant.

But with direct code insertion you have syntax highlighting and
less code lines.

( -- test_direct.lua

_G.package.preload['modules.a.Represent'] =
  function(...)
    return
      function(self)
        print(debug.traceback())
        print(self.Data)
      end
  end

_G.package.preload['modules.a'] =
  function(...)
    local Represent = require('modules.a.Represent')

    return
      {
        Represent = Represent,
        Data = '[Test representation.]',
      }
  end

_G.package.preload.test =
  function(...)
    local ModuleA = require('modules.a')

    ModuleA:Represent()
  end

require('test')

)

( -- test_wrapped.lua

local Modules = {
  ['modules.a'] = [[
local Represent = require('modules.a.Represent')

return
  {
    Represent = Represent,
    Data = '[Test representation.]',
  }
]],
  ['modules.a.Represent'] = [[
return
  function(self)
    print(debug.traceback())
    print(self.Data)
  end
]],
  test = [[
local ModuleA = require('modules.a')

ModuleA:Represent()
]],
}

do
  local add_module =
    function(module_name, module_code_str)
      local compiled_code = assert(load(module_code_str, module_name, 't'))

      _G.package.preload[module_name] =
        function(...)
          return compiled_code(...)
        end
    end

  for module_name, module_code_str in pairs(Modules) do
    add_module(module_name, module_code_str)
  end
end

require('test')

)

-- Martin

Martin Eden

unread,
Apr 24, 2026, 3:30:49 AM (9 days ago) Apr 24
to lu...@googlegroups.com
On 2026-04-24 01:16, mjmouse9999 wrote:
> It also scans the dependencies by looking for require
> statements
> (This is helped by the fact that Teal always includes the brackets)
>
> [...]
>
> https://github.com/teal-language/tl/blob/main/extras/combine.tl
Scanning for deps in dynamic languages via parsing source generally
doesn't work:

  function request(str)
    return require(str)
  end

  request('debug')

Before deploy I'm actually running main code. My strain of "require"
wrapper tracks run-time dependencies in global table. Which is later
used to go and copy those files.

-- Martin

Martin Eden

unread,
Apr 24, 2026, 6:21:25 AM (9 days ago) Apr 24
to lu...@googlegroups.com
Following the suggestion of mjmouse9999 I've changed generated code layout.

And added command-line option "--indent".

With it produces code like

_G.package.preload['modules.a.Represent'] =
  function(...)
    return
      function(self)
        print(self.Data)
      end
  end

Sadly, we can't apply indent if there are multi-line strings.
And we can't detect them without fully-blown parser.

So without "--indent" embedded code is not modified.

But we are still trying to remain nice and produce comments around block:

-- ( module modules.a.Represent
_G.package.preload['modules.a.Represent'] =
function(...)
return
  function(self)
    print(self.Data)
  end
end
-- ) module modules.a.Represent

-- Martin

mjmouse9999

unread,
Apr 24, 2026, 8:28:18 AM (9 days ago) Apr 24
to lua-l
On Friday, April 24, 2026 at 5:30pm Martin Eden wrote:
> On 2026-04-24 01:16, mjmouse9999 wrote:
> > It also scans the dependencies by looking for require
> > statements
> > (This is helped by the fact that Teal always includes the brackets)
> >
> > [...]
> >
> > https://github.com/teal-language/tl/blob/main/extras/combine.tl
> Scanning for deps in dynamic languages via parsing source generally
> doesn't work:
>
>   function request(str)
>     return require(str)
>   end
>
>   request('debug')

Yes. Teal also requires all the calls that actually use the module
to be in the format require("code") or pcall(require, "code").

Once you wrap the function in Teal, it loses its special typechecking
functionality:

  local function readmodule(s: string): any
    return require(s)
  end
  local res = readmodule("some.module")
  res.this() -- This fails since res is 'any'

But just assigning to a variable keeps the functionality:

  local ha = require
  local result = ha("ha") -- this call is still recognised as a special one
  result:dostuff() -- result does actually get the type from the module

> Before deploy I'm actually running main code. My strain of "require"
> wrapper tracks run-time dependencies in global table. Which is later
> used to go and copy those files.

That makes sense.

For module tracking you could also wrap the three file-searching functions
under package.searchers and track that if you still wanted to use require.

Martin Eden

unread,
Apr 25, 2026, 12:13:12 PM (8 days ago) Apr 25
to lu...@googlegroups.com
Additions and changes of tool repo:

* Added compiled binary (shebang + luac) for Lua v5.3

* Code base is switched from Lua v5.5 to v5.3

* Added deploy scripts to generate compiled binary

  I think it's kinda cool: tool uses self code to pack itself.

  68 KB source goes to 27 KB binary.

  (If you want to repeat this feat you'll need to clone my main
  "workshop" repo into "src/workshop" (replacing existing one)).

All feedback is still welcome!

-- Martin

Reply all
Reply to author
Forward
0 new messages