Simulate Macros to Simplify Scripts

  • Scripts need to access existing data in variables.

  • The particular fields to access correspond to long/complex expressions (e.g. long indexing and/or property chains foo[x][y].bar[z].baz).

  • Usage is prevalent inside the scripts, requiring extensive duplications of code that are prone to typos and errors.

  • There are a few such variables to modify at the same time – otherwise, it would be simpler to bind the this pointer to the variable.

  • Pick a macro syntax that is unlikely to conflict with content in literal strings.

  • Before script evaluation/compilation, globally replace macros with their corresponding expansions.

Pick a Macro Syntax

The technique described here is to simulate macros. They are not REAL macros.

Pick a syntax that is intuitive for the domain but unlikely to occur naturally inside string literals.

Sample SyntaxSample usage
#FOO#FOO = 42;
$Bar$Bar.work();
<Baz>print(<Baz>);
#HELLO#let x = #HELLO#;
%HEY%%HEY% += 1;

Avoid normal syntax that may show up inside a string literal.

For example, if using Target as a macro:

// This script...
Target.do_damage(10);

if Target.hp <= 0 {
    print("Target is destroyed!");
}

// Will turn to this...
entities["monster"].do_damage(10);

if entities["monster"].hp <= 0 {
    // Text in string literal erroneously replaced!
    print("entities["monster"] is destroyed!");
}

Global Search/Replace

// Replace macros with expansions
let script = script.replace("#FOO", "foo[x][y].bar[z].baz");

let mut scope = Scope::new();

// Add global variables
scope.push("foo", ...);
scope.push_constant("x", ...);
scope.push_constant("y", ...);
scope.push_constant("z", ...);

// Run the script as normal
engine.run_with_scope(&mut scope, script)?;
print(`Found entity FOO at (${x},${y},${z})`);

let speed = #FOO.speed;

if speed < 42 {
    #FOO.speed *= 2;
} else {
    #FOO.teleport(#FOO.home());
}

print(`FOO is now at (${ #FOO.current_location() })`);

After macro expansion, the character positions of different script elements will be shifted based on the length of the expanded text.

Therefore, error positions may no longer point to the correct locations in the original, unexpanded scripts.

Line numbers are not affected.