JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Environment

Jun 19, 2025 • [designluamoverstesting]


I have been doing all my Luau development locally on my Mac for a week or so now. Let me describe my personal development environment, and work a bit on improving it.

Present Setup

I edit in Sublime Text 4, with a Lua plugin for syntax highlighting. It works pretty well, although it doesn’t understand backtick strings.

I have luau installed on my Mac, via homebrew. I just followed the instructions. I did have to set up a “build system”. It looks like this:

{
  "cmd": ["luau", "$file"],
  "selector": "source.luau",
  "file_regex": "^(...*?):([0-9]*):?([0-9]*)",
  "line_regex": "^(?:stdin|(...*?)):([0-9]*):?([0-9]*)",
  "working_dir": "$file_path"
}

If I recall, I just took an existing one and substituted “luau” into the “cmd” line.

With that file in place and a luau file open in Sublime, and that file in the active tab, if I type Command+B, the build system will compile and run the file. F7 will also run it. I get my results in a pop-up panel in Sublime. If all goes well, it looks like this:

All Tests: Pass 867 Fail 0.  Tests:run_tests
[Finished in 32ms]

Naturally, if the tests encounter any errors, they will be listed there, as will compile errors, in the frequent event that I have a syntax error in the file.

Needs Improvement

Presently, I have a file test.lua that contains everything I’m working on, including the class function, the Tests object, and all my tests and all my code. As of this morning, that file is 783 lines long. It contains essentially everything I’ve worked on so far, including classes Interpolator, Bezier, L_Bezier, D_Bezier, Waypoint, and Multipath, as well as a vector class that I’ve built to support my work.

With a file that large, work isn’t as smooth as I’d like. I have to keep scrolling between the tests and the code, and it’s hundreds of lines between them. I’ve tried using a 4-panel editor layout and multiple tabs open on the same file, but so far it’s just not convenient.

If I were using a real IDE, like PyCharm or IDEA, I would have a separate file for every class, another file for its tests, and an editor hierarchy open in the left column that would let me quickly select the file I wanted to read or change.

I’d like to get to a similar situation with Luau.

Existing Downloads

As presently set up, our download above are not what I need for my own work. They are somewhat suitable for inclusion in SLua code and less suitable for my home setup. As part of this effort, I’ll try to set them up to be useful for both situations. Since I have essentially zero users just now, I can probably work freely as I see fit. Incremental improvement, my main life strategy.

Require

Luau has a language feature require that allows a file to refer to another and bring in that file’s definitions. It’s not an include like the one in Python. A file used in require must define a table, and return that table as the result of the require call.

We can make that work for Tests right now, except that the current Tests file includes both the framework and the tests that test and demonstrate the framework. When we physically include that file, as we would with SLua, we can just remove those sample tests and replace with our own. So one thing I need to do is split out the sample tests from the Tests object.

The class function is a bit more of a problem if I want to use require locally—and I do. Since require returns a table, I need to wrap class in a table one way or another.

Round Tuit

Enough warmup chatter, let’s do something. I’ll start by splitting tests into a file named tests and a file named tests-samples, and then see if I can require them.

That was quickly done. I now have two additional files in my repo folder, tests.lua and test-samples.lua I don’t think that the latter will work yet, as it does not return a table.

Let’s see if we can make that work. I add a require at the top to provide access to Tests, and then return Tests as the return from that require. When I do this in my main file:

local Tests = require('./tests')
require('./test-samples')

All my existing tests run, plus the samples, some of which fail as intended, to show how the framework works.

So far so good.

Next Steps?

I can see two main paths ahead. One will be modifying the class function so that it can be in a require file. The other path is to break out the objects I’ve created into fewer files: my ‘test.lua’ working file is still 689 lines after removing the initial Tests class.

I think I’ll get greater benefit by breaking out separate files. The class function is only about 45 lines and I could make it shorter by removing is_a, which I never use.

Let’s remove vector. I am briefly thwarted, because my vector is a class and therefore needs my class function. I’ll include it for now. OK, vector is now separate and required in. My 867 tests are still running.

Even though further separation is easier and more effective at reducing my file size, I think I’d better figure out how to make the class work. Here’s the class function now:

function class(base)
   local c = {}    -- a new class instance
   if type(base) == 'table' then
       -- our new class is a shallow copy of the base class!
       for i,v in pairs(base) do
           c[i] = v
       end
       c._base = base
   end

   -- the class will be the metatable for all its objects,
   -- and they will look up their methods in it.
   c.__index = c

   -- expose a constructor which can be called by <classname>( <args> )
   local mt = {}
   mt.__call = function(class_tbl, ...)
       local obj = {}
       setmetatable(obj,c)
       if class_tbl.init then
           class_tbl.init(obj,...)
       else
           -- make sure that any stuff from the base class is initialized!
           if base and base.init then
               base.init(obj, ...)
           end
       end

       return obj
   end

   c.is_a = function(self, klass)
       local m = getmetatable(self)
       while m do
           if m == klass then return true end
           m = m._base
       end
       return false
   end

   setmetatable(c, mt)
   return c
end

We need to rig up a table, and what we would like to have work is this:

class = require('./class')

So we need a table to return, and that table needs to be callable.

I’ll try to do this in line and then move it.

local class = {}
local meta = {}
meta.__index = meta
meta.__call = function(table, base)
    return table.class(base)
end
setmetatable(class, meta)

function class.class(base)
   ...

My tests all run. Try moving this out and requiring it as intended.

Seems to work. Change the vector file to use it. Works. Here’s the top of my test.lua file now:

local class = require('./class')
local Tests = require('./tests')
-- require('./test-samples')
local vector = require('./vector')

It’s still pretty long, 598 lines. Of those, About 300 are tests and 300 are actual code. Not an unreasonable ratio, I guess.

There are classes that I’d like to preserve but get out of sight, like Interpolator or D_Bezier, which are either ideas that didn’t pan out or that have been superseded.

The Bezier class itself is just about 100 lines, and includes a lot of methods that we will probably no longer want, such as compute_length and estimate_length and certainly not create_waypoints, which is now replaced with the L_Bezier’s partitioning.

I think I’ll let this ride for now. The test.lua file is down 183 lines from its maximum, and we have our two key components, class and Tests broken out. More will come.

For now, it’s chai time. Safe paths!