Macros & Preprocessor

Zym includes a built-in preprocessor that runs before compilation. It supports #define macros, function-like macros, multi-line block macros, conditional compilation, and expression evaluation — giving you compile-time code generation and configuration without runtime overhead.

Overview

The preprocessor processes your source code before the compiler sees it. Directives start with # at the beginning of a line (leading whitespace is allowed). The preprocessor strips comments, evaluates directives, expands macros, and passes the result to the compiler.

DirectivePurpose
#defineDefine an object-like or function-like macro (single line)
##define / ##enddefineDefine a multi-line block macro
#undefRemove a macro definition
#if / #elifConditional compilation with expression evaluation
#ifdef / #ifndefConditional compilation based on macro existence
#else / #endifAlternative branch / end conditional block
#errorAbort compilation

Object-like Macros

The simplest form of macro: a name that expands to a value. Anywhere the macro name appears in code, it is replaced with the defined value before compilation.

basic #define
#define MAX_SIZE 100
#define GREETING "Hello, World"
#define PI 3.14159

var arr = [null] * MAX_SIZE   // expands to: [null] * 100
print(GREETING)                // expands to: print("Hello, World")
var circumference = 2 * PI * r

Flag macros

A macro can be defined without a value — it simply “exists” and can be tested with #ifdef or defined().

#define DEBUG
#define VERBOSE

#ifdef DEBUG
    print("Debug mode is on")
#endif

Redefining macros

Defining a macro with the same name replaces the previous definition. Use #undef to remove a macro entirely.

#define MODE 1
// MODE expands to 1

#define MODE 2
// MODE now expands to 2

#undef MODE
// MODE is no longer defined

Function-like Macros

Macros can take parameters, making them act like inline templates. The parameter list follows the macro name immediately (no space before the opening parenthesis).

function-like macro
#define SQUARE(x) x * x
#define ADD(a, b) a + b
#define MAX(a, b) if (a > b) { a } else { b }

var result = SQUARE(5)     // expands to: 5 * 5
var sum = ADD(3, 4)        // expands to: 3 + 4

Argument expansion

Arguments are expanded before substitution into the macro body. This means you can pass macro names or expressions as arguments and they will be resolved.

#define VALUE 10
#define DOUBLE(x) x + x

DOUBLE(VALUE)              // VALUE expands to 10, then: 10 + 10
DOUBLE(3 + 2)              // expands to: 3 + 2 + 3 + 2

Multiple parameters

Function-like macros can have any number of parameters, separated by commas.

#define CLAMP(val, lo, hi) if (val < lo) { lo } else if (val > hi) { hi } else { val }
#define LERP(a, b, t) a + (b - a) * t

var clamped = CLAMP(x, 0, 100)
var mid = LERP(0, 10, 0.5)

Block Macros (Multi-line)

For macros that span multiple lines, use the ##define / ##enddefine syntax (double hash). Everything between the opening directive and ##enddefine becomes the macro body, preserving line breaks.

block macro
##define SETUP_PLAYER
var health = 100
var mana = 50
var level = 1
func heal(amount) {
    health = health + amount
    if (health > 100) health = 100
}
##enddefine

// Use it — expands to the full block
SETUP_PLAYER
heal(30)

Block macros with parameters

Block macros can also take parameters, just like single-line function macros.

parameterized block macro
##define MAKE_COUNTER(name, start)
var name = start
func increment_##name() {
    name = name + 1
}
func get_##name() {
    return name
}
##enddefine

MAKE_COUNTER(score, 0)
MAKE_COUNTER(lives, 3)

Line Continuation

A backslash (\) at the end of a line joins it with the next line. This lets you split long single-line #define directives across multiple lines for readability.

line continuation
#define LONG_MACRO(a, b, c) \
    a + b + c

// Equivalent to: #define LONG_MACRO(a, b, c) a + b + c
Tip: For anything beyond a simple one-liner, prefer block macros (##define / ##enddefine) over line continuations. They are easier to read and maintain.

Conditional Compilation

Conditional directives let you include or exclude code based on whether macros are defined and what values they hold. The preprocessor evaluates conditions and only passes the active branch to the compiler — excluded code is completely removed.

#ifdef / #ifndef

Test whether a macro is defined (or not defined).

#ifdef / #ifndef
#define DEBUG

#ifdef DEBUG
    print("Debug: initializing...")
#endif

#ifndef RELEASE
    print("Not a release build")
#endif

#if / #elif / #else

Evaluate expressions to decide which code to include. Supports integer comparisons, logical operators, and the defined() function.

#if / #elif / #else
#define VERSION 3

#if VERSION == 1
    print("Version 1")
#elif VERSION == 2
    print("Version 2")
#elif VERSION >= 3
    print("Version 3 or later")
#else
    print("Unknown version")
#endif

Expression operators

The following operators are available inside #if and #elif expressions:

OperatorMeaningExample
==Equal#if VERSION == 2
!=Not equal#if MODE != 0
&&Logical AND#if DEBUG && VERBOSE
||Logical OR#if A || B
!Logical NOT#if !RELEASE
defined()Check if macro exists#if defined(FEATURE_X)
( )Grouping#if (A || B) && C

defined() function

The defined() function returns 1 if a macro is defined, 0 otherwise. It can be used with or without parentheses.

#define FEATURE_A

#if defined(FEATURE_A) && !defined(FEATURE_B)
    // Only FEATURE_A is enabled
    print("Feature A only")
#endif

// Without parentheses
#if defined FEATURE_A
    print("Feature A is defined")
#endif

Nesting conditionals

Conditional blocks can be nested. Inner conditions are only evaluated if all outer conditions are active.

nested conditionals
#define PLATFORM 1
#define DEBUG

#if PLATFORM == 1
    print("Platform 1")
    #ifdef DEBUG
        print("Platform 1, debug mode")
    #endif
#elif PLATFORM == 2
    print("Platform 2")
#endif

Undefining Macros

#undef removes a previously defined macro. After undefining, the name is no longer expanded and #ifdef will be false.

#undef
#define TEMP 42

var x = TEMP   // expands to: var x = 42

#undef TEMP

var y = TEMP   // TEMP is no longer a macro — treated as an identifier

#ifdef TEMP
    // This block is skipped — TEMP is no longer defined
#endif

#error

The #error directive immediately aborts compilation. Use it inside conditional blocks to enforce configuration requirements.

#error
#ifndef PLATFORM
    #error
#endif

// Compilation only reaches here if PLATFORM is defined

Macro Expansion

Macros are expanded recursively — if a macro body contains another macro name, that name is expanded too. The preprocessor includes an infinite-recursion guard: if a macro references itself (directly or through a chain), the self-reference is left unexpanded.

recursive expansion
#define A B + 1
#define B 10

var x = A
// A → B + 1 → 10 + 1
// x is 11
self-reference guard
#define FOO FOO + 1

var x = FOO
// FOO expands once, but the inner FOO is NOT expanded again
// Result: FOO + 1 (FOO treated as identifier in expansion)

Expansion in conditionals

Macros inside #if and #elif expressions are expanded before the expression is evaluated. This means you can use macros in condition expressions.

#define MAJOR 2
#define MINOR 5
#define VERSION MAJOR

#if VERSION == 2
    print("Major version 2")   // this branch is taken
#endif

Comment Handling

The preprocessor strips comments before processing directives and expanding macros. Both line comments (//) and block comments (/* */) are removed, while preserving line numbers for accurate error reporting.

#define VALUE 42  // this comment is stripped before the macro is stored

/* Block comments are also
   stripped before preprocessing */
var x = VALUE    // expands to 42

Comments inside strings are not stripped — string literals are preserved exactly as written.

Common Patterns

Feature flags

Use macros as compile-time feature toggles to include or exclude functionality.

feature flags
#define ENABLE_LOGGING
#define ENABLE_METRICS

func processRequest(req) {
    #ifdef ENABLE_LOGGING
        log("Processing: " + str(req))
    #endif

    var result = handle(req)

    #ifdef ENABLE_METRICS
        recordMetric("request_processed")
    #endif

    return result
}

Platform-specific code

platform branching
#define PLATFORM 1  // 1 = desktop, 2 = mobile, 3 = web

#if PLATFORM == 1
    func getInput() { return readKeyboard() }
#elif PLATFORM == 2
    func getInput() { return readTouch() }
#else
    func getInput() { return readEvent() }
#endif

Code generation with block macros

Block macros are powerful for generating repetitive boilerplate code.

code generation
##define MAKE_ACCESSOR(field)
func get_##field(obj) {
    return obj.field
}
func set_##field(obj, value) {
    obj.field = value
}
##enddefine

struct Player { name; health; score }

MAKE_ACCESSOR(name)
MAKE_ACCESSOR(health)
MAKE_ACCESSOR(score)

Configuration constants

configuration
#define MAX_PLAYERS 16
#define TICK_RATE 60
#define MAP_WIDTH 1024
#define MAP_HEIGHT 768

var players = []
for (var i = 0; i < MAX_PLAYERS; i = i + 1) {
    push(players, null)
}

Guard patterns

Use #ifndef to prevent double-definition, or #error to enforce requirements.

guard patterns
// Ensure a required config is set
#ifndef API_VERSION
    #error
#endif

// Default values
#ifndef MAX_RETRIES
    #define MAX_RETRIES 3
#endif

Directive Reference

DirectiveSyntaxDescription
#define #define NAME value Define an object-like macro that expands to value
#define #define NAME(a, b) body Define a function-like macro with parameters
##define ##define NAME
...
##enddefine
Define a multi-line block macro (with optional parameters)
#undef #undef NAME Remove a macro definition
#if #if expression Include following code if expression is non-zero
#ifdef #ifdef NAME Include following code if NAME is defined
#ifndef #ifndef NAME Include following code if NAME is not defined
#elif #elif expression Alternative branch with expression check
#else #else Alternative branch (no condition)
#endif #endif End a conditional block
#error #error Abort compilation immediately

See also: Language GuideEmbedding GuideContinuations