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 Syntax | Sample 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() })`);