Skip to content
ReFreezed edited this page Jul 12, 2021 · 7 revisions

Note: The documentation has moved to the LuaPreprocess website. Information here may be out of date!

LuaPreprocess Wiki

See the sidebar for all pages.


How to Metaprogram

The exclamation mark (!) is used to indicate what code is part of the metaprogram. There are 4 main ways to write metaprogram code:

  • !... - One exclamation mark.
  • !!... - Two exclamation marks.
  • !( ... ) - One exclamation mark with a parenthesis.
  • !!( ... ) - Two exclamation marks with a parenthesis.

!...

The line will simply run during preprocessing. The line can span multiple physical lines if it contains brackets.

-- Simple preprocessor lines.
!if not isDeveloper then
	sendTelemetry()
!end

-- Span multiple lines.
!newClass{
	name  = "Entity",
	props = {x=0, y=0},
}

-- There can be stuff before a preprocessor line.
local foo = 10  !print("We've reached foo.")


-- Example output:
sendTelemetry()

function newEntity()
	return {__classname="Entity", x=0, y=0}
end

local foo = 10

!!...

The line will appear in both the metaprogram and the final program. The line must be an assignment.

-- The expression will be evaluated in the metaprogram and the
-- result will appear in the final program as a literal value.
!!local tau     = 2*math.pi
!!local aAndTau = ("a"):rep(20)..tau

-- Output:
local tau     = 6.283185
local aAndTau = "aaaaaaaaaaaaaaaaaaaa6.283185"

!( ... )

If the parenthesis contains an expression the result of the expression will be outputted as a literal value, otherwise the code will just run as-is.

-- Output values.
local bigNumber = !( 5^10 )
local manyAbcs  = !( ("abc"):rep(10) )

-- Run a block of code.
!(
local dogWord = "Woof "
function getDogText()
	return dogWord:rep(3)
end
outputLua("dog = ")
outputValue(getDogText())
)

-- Output:
local bigNumber = 9765625
local manyAbcs  = "abcabcabcabcabcabcabcabcabcabc"

dog = "Woof Woof Woof "

!!( ... )

The expression in the parenthesis will be outputted as Lua code. The expression must result in a string value.

local font = !!( isDeveloper and "loadDevFont()" or "loadUserFont()" )

!local globals = {pi=math.pi, tau=2*math.pi}
!for k, v in pairs(globals) do
	_G.!!(k) = !(v)
!end

-- Example output:
local font = loadUserFont()

_G.pi  = 3.14159265358979323846
_G.tau = 6.28318530717958647693

(Also see @insert.)

Two Ways of Generating Code

These examples show two ways of doing the same thing using inline code and a code block.

Populate a Table

-- Using inline code.
local oddNumbers = {
	!for v = 1, 5, 2 do
		!( v ),
	!end
}

-- Using a code block.
!(
outputLua("local oddNumbers = {\n")
for v = 1, 5, 2 do
	outputLua("\t")
	outputValue(v)
	outputLua(",\n")
end
outputLua("}")
)

-- Output:
local oddNumbers = {
	1,
	3,
	5,
}

Generate a Pattern

-- Using inline code.
!local alpha        = "[%a_]"
!local alphaNumeric = "[%w_]"
local identifierPattern = !( "^"..alpha..alphaNumeric.."*$" )

-- Using a code block.
!(
local alpha        = "[%a_]"
local alphaNumeric = "[%w_]"
outputLua("local identifierPattern = ")
outputValue("^"..alpha..alphaNumeric.."*$")
)

-- Output:
local identifierPattern = "^[%a_][%w_]*$"

Potential Gotchas

Beware in code blocks that only call a single function:

-- This will bee seen as an inline block and output whatever value
-- func() returns as a literal.
!( func() )

-- If that's not wanted then a trailing ";" will prevent that.
-- This line won't output anything (unless func() calls outputLua()
-- or outputValue()).
!( func(); )

-- When the full metaprogram is generated, `!(func())` translates
-- into `outputValue(func())` while `!(func();)` simply translates
-- into `func();` (because `outputValue(func();)` would be invalid
-- Lua code).

-- Anyway, in this specific case a preprocessor line (without the
-- parenthesis) would be nicer:
!func()