MacroDefinition.java
package com.varnernet.gerb4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Represents a Gerber aperture macro definition (AM command). Stores the macro name, its primitive
* definitions, and evaluated variables.
*
* <p>Example: %AMBOXR* 0 Rectangle with Rounded corners* $5=$1/2-$3* $6=-$3+$2/2*
* 21,1,$1,$2-$7,0,0,$4* 1,1,$7,$5,$6,$4* *%
*
* <p>When instantiated: %ADD10BOXR,0.1X0.05X0.02X0.0*% Parameters: [0.1, 0.05, 0.02, 0.0]
* Variables: $5, $6, $7 are evaluated with these parameters
*/
public class MacroDefinition {
private final String name;
private final List<String> primitives;
private final Map<Integer, String> variableExpressions;
private final MacroExpressionEvaluator expressionEvaluator;
/**
* Constructor with macro name.
*
* @param name the macro name
*/
public MacroDefinition(final String name) {
this(name, MacroExpressionEvaluator.DEFAULT);
}
/**
* Constructor with dependency injection for expression evaluator. Allows testing with custom evaluators.
*
* @param name the macro name
* @param evaluator the expression evaluator
*/
public MacroDefinition(final String name, final MacroExpressionEvaluator evaluator) {
this.name = name;
this.primitives = new ArrayList<>();
this.variableExpressions = new HashMap<>();
this.expressionEvaluator = evaluator;
}
/**
* Gets the macro name (e.g., "BOXR").
*
* @return the macro name
*/
public String getName() {
return name;
}
/**
* Gets all primitive definitions (raw strings from macro body).
*
* @return the list of primitive definitions
*/
public List<String> getPrimitives() {
return primitives;
}
/**
* Adds a primitive definition to this macro. Examples: "21,1,$1,$2-$7,0,0,$4*", "1,1,$7,$5,$6,$4*".
*
* @param primitiveDefinition the primitive definition string
*/
public void addPrimitive(final String primitiveDefinition) {
if (primitiveDefinition != null && !primitiveDefinition.isEmpty()) {
primitives.add(primitiveDefinition);
}
}
/**
* Registers a variable expression (e.g., $5=$1/2-$3).
*
* @param variableNumber the variable number (5 for $5)
* @param expression the expression (e.g., "$1/2-$3")
*/
public void addVariableExpression(final int variableNumber, final String expression) {
variableExpressions.put(variableNumber, expression);
}
/**
* Gets the expression for a variable (e.g., get expression for $5).
*
* @param variableNumber the variable number
* @return the expression string
*/
public String getVariableExpression(final int variableNumber) {
return variableExpressions.get(variableNumber);
}
/**
* Gets all defined variables.
*
* @return map of variable expressions
*/
public Map<Integer, String> getVariableExpressions() {
return new HashMap<>(variableExpressions);
}
/**
* Evaluates all variables with the given parameters, processing them in ascending variable-number
* order so that a variable ($7) may safely reference an already-computed variable ($6).
*
* @param parameters the parameters provided in the AD command
* @return map of variable number → evaluated value (includes all computed vars)
*/
public Map<Integer, Double> evaluateVariables(final List<String> parameters) {
Map<Integer, Double> evaluatedVariables = new HashMap<>();
// Sort by variable number to ensure dependencies are resolved in order
new TreeMap<>(variableExpressions)
.forEach(
(varNumber, expression) -> {
try {
double value =
expressionEvaluator.evaluateWithVariables(
expression, parameters, evaluatedVariables);
evaluatedVariables.put(varNumber, value);
} catch (Exception e) {
throw new IllegalArgumentException(
String.format(
"Failed to evaluate $%d in macro %s: %s", varNumber, name, e.getMessage()),
e);
}
});
return evaluatedVariables;
}
}