Roblox Lua Preprocessors and Macros

The Problem

As a Roblox developer, it is currently impossible to quickly change how my scripts are formatted and/or run specific operations without overly relying on function calls (which can be expensive at times and not produce the result I want).

The Examples

By this I mean the usage of things such as:

local function Operate(A, B)
    return A + B;
end;

Where I would use Operate as a function if I wanted a portion of my code to be synced and rely on the same operator. This wouldn’t be such an issue if say I just need to do something quick like add to a player’s stat, but it becomes more impacting performance-wise when attempting to make expensive calls and equations which I might change from time to time, but want to be synced with the same operator.

If this isn’t a good example then maybe something like quick script operations could be better.
Say I wanted to do x = x + y multiple times with different variables around and don’t want to write this.That.Something.OtherThing.Value = this.That.Something.OtherThing.Value + y over and over again and it being a single line means using locals wouldn’t help out much, or maybe when you just want to inline or predefine a bunch of numbers without taking up more LOADK or MOVE opcodes. (Such as by doing This + 5 > 10 vs This + localVariable5)

The Proposal

The solution I propose is Lua preprocessors (like the macros and such we have in C, C++, etc) which there was syntax highlighting in for quite a while but no actual usage. I’m aware of more threads about pre-processors but they all seem to just ask “what happened to them?” rather than “can we have them?”

These could be implemented with the $ symbol or something like that.
For example:

$define plusEqual (a, b)(a.Something.Value = a.Something.Value + b)

plusEqual(ReplicatedStorage.Stuff, 5)

And $define wouldn’t need to be the only preprocessor, as maybe $ifndef and such can be used for disabling debug code to not be compiled when releasing a new update for your game. The idea is to keep code clean, efficient, and be able to debug with ease.

Final Thoughts

See here for a C++ explanation.
I see no issue with this as we wouldn’t be changing Lua’s opcodes, bytecode format or overall functionality and it would be more of a word-replacement prior to compile time. Any thoughts on this subject?

6 Likes

omg this would be AMAZING

I’m gonna make a plugin to do this :heart_eyes:

also @Osyris doing this is a programming style, it should be left up to the developers whether or not they want to make their code cleaner at the cost of portability.
With that being said, it isn’t just about efficiency - although code that is slightly more efficient AND clean is ultra cool.

A solution might be prefixing with --$ as to trigger a Lua comment and not create one massive syntax error as opposed to knowing which macros/functions you’ll have to re-do.

EDIT: I would like to add that “code portability” doesn’t seem like a good argument when referring to real Lua -> Roblox Lua because it’ll always be compatible even with this.
The issue is with Roblox Lua -> real Lua but even then you shouldn’t do that because Roblox runs on a very modified environment.

3 Likes

What you’re proposing is a change to Lua, not Roblox. The differences between Roblox lua and vanilla lua are some security-based io/os/debug library details, some differences with coroutines because of the thread scheduler, modified loadstring, and __namecall. You’re proposing new syntax that would completely change the language into a new one.

14 Likes

Yeah. Following on this, ROBLOX doesn’t change any syntax regarding lua intentionally. Everything in that respect has stayed pretty much vanilla. If every syntax request was completed, it really wouldn’t even be able to be called Lua.

1 Like

Regarding a syntax change, it isn’t really changing Lua as the way preprocessors work is by basically replacing words before compile time as opposed to creating new functionality. Behind the scenes there wouldn’t be any real changes to opcodes or bytecode or anything, it’d just be simple find+replace.

Although even if that wouldn’t be a possibility, a better way to format scripts should at least make an appearance.

1 Like

You can argue semantics, but ultimately the effect is the same. You should just get used to using another programming language instead of trying to make it what you’re used to. ROBLOX won’t be implementing this – sorry. As others have mentioned, its outside the scope of ROBLOX.

I’m not sure if by out of scope you mean too complex or too un-Lua-like. But I would also like to mention Lua had preprocessor support once in a blue moon a few versions ago and they aren’t hard to implement as seen here. The issue though being a manual user implementation wouldn’t be reversible or flexible.

1 Like

Lua removed preprocessors in 4.0.


ROBLOX is reluctant to change how Lua works in constructive ways, so we’re certainly not going to change Lua and re-add a feature that was deemed harmful. Lua isn’t C, so get used to programming differently than you do in another language. Again, ROBLOX won’t be implementing this – sorry.

6 Likes

Boy, oh boy. I definitely wouldn’t want macros in my Lua code (nor even the possibility of having one). Lua is not C and therefore should not work like C:

  • We don’t share variables across multiple files, so we don’t need headers and header guards (as long as you are not using getfenv/setfenv, which you shouldn’t be!). Lua gets around this problem through modules, storing useful functions/variables in a table you can reference.
  • Using function macros is bad practice anyways since it can lead to unreadable, spaghetti code and depending on the use case can seriously screw your whole game/app over. This could of course be fixed by using naming conventions, but the hassle could be entirely avoided just by not using function macros. Take for example the assert macro in C++'s <cassert> STL header. Looking at the language’s ISO standards, assert() is a macro purely because of the language’s restrictions and one of its premises is that you can “disable” the macro entirely by doing a replacement #define. It was made to be disabled when releasing code.
  • Even the Lua team removed preprocessors from the language 4 versions ago, under the reason that “preprocessor macros make the code bulkier”, and it makes perfect sense under the language’s philosophy of simplicity and meta-mechanisms: it doesn’t do that out of the package, but you can make it do it.

Before I end this, I want to remark that, regarding macros, I have been educated around not using them, under the same arguments I used here and therefore my opinion is really biased. I know there are definitely a lot of valid use cases for macros but, in my opinion, the cons just outweigh the pros.

This would force devs to fork validation tools like LuaCheck and luac, or else just stop using them.

Adding globals is one thing. Any self-respecting linter lets you configure those. Adding syntax is something else entirely.

There are implementations that wouldn’t require forking Lua sysntax. For example,

function _INL.Operate(A, B)
    return A + B
end

local a, b = 1, 2
local c = _INL.Operate(a, b)

would compile to something like

local a, b = 1, 2
local c = a + b

(You’d need to take some care in your implementation for order of operations and conditionals, etc.)

I need something like this mainly for globally defined constants. Right now I have a dictionary full of voxel types, “DIRT = 2, GRASS = 3,” etc.

--I would rather make this check
if target == DIRT then
--than what I have to do now
if target == Voxel.DIRT then

My dictionary is used in multiple scripts, so I actually create a bunch of local variables at the head of each script that is performance intensive so that I can emulate the former. Not only is it a hassle and source of bugs to manually update this local variable list across scripts, it is also subject to the Lua 200 local variable limit.

That being said, this use case is pretty rare among Roblox developers. Regardless of any drawbacks of having a system like this, my guess is that it’s probably not worth the engineers’ time to implement right now. It is worth my time, however. It’s even more worthwhile if there are other developers interested in this feature. If no one has done it already, I’ll probably write a plugin in a few months.

1 Like

The issue is that the compiler and lexer don’t work like that.

Something like that not compile down to a bunch of LOADKs of which the latter would.

My suggestion involved less editing Lua and how it works and more like an “edit” before compile-time.
The _INL method’s opcodes:
image

The local method’s opcodes:
image

The idea is that we pre-process the code before we actually make the opcodes/compile. But I guess that isn’t going to be made.

2 Likes

For this plugin, I’m imagining a step that converts a script Source string into a preprocessed Source string. So I mean that _INL call would “compile” to the string “local c = a + b”, which would then produce the same bytecode. The _INL.Operate function wouldn’t appear in the generated script at all.

After this request I actually did start working on something like that but using --$ syntax and might be making it into a plugin at a later date. So I guess in a way we’ll be getting preprocessors?

1 Like

Sweet :slight_smile: Is there some greater functionality you want that we can’t get through a plugin?

Not really, it’s just text formatting.

EDIT: Actually it might be hard to have it switch back and forth between formats but we’ll see.

1 Like

I don’t think that’s true - we will not change Lua unless there’s a good reason, but we don’t have a rule “never change Lua’s syntax/semantics/implementation details”.

8 Likes