Skip to content

Simplify MapToolLineParser and related code - #6071

Open
kwvanderlinde wants to merge 10 commits into
RPTools:developfrom
kwvanderlinde:refactor/6070-MapToolLineParser
Open

kwvanderlinde wants to merge 10 commits into
RPTools:developfrom
kwvanderlinde:refactor/6070-MapToolLineParser

Conversation

@kwvanderlinde

@kwvanderlinde kwvanderlinde commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Identify the Bug or Feature request

Resolves #6070

Description of the Change

There's a few semi-independent things going on. Mostly, it's that cleanup in MapToolLineParser motivated clean up elsewhere. The bulk of the changes outside of MapToolLineParser, MapToolVariableResolver, and Token are just callers being updated to reflect the method changes.

MapToolLineParser

The bulk of the change is in here. Most method overloads have been removed since they did not do anything interesting beyond saving callers a few characters here and there. This applies to:

  • #runMacro()
  • #rollString()
  • #expandRoll()
  • #parseExpression()
  • #enterContext() (overload that was only used in tests)
  • #parseLine()

#parseLine() also has a couple other simplifications:

  1. It requires the caller to pass a MapToolVariableResolver. Passing null is no longer an option.
  2. With the changes in (1), it became clear that the tokenInContext parameter would always be the same as resolver.getTokenInContext(). So the parameter (which had to be plumbed around in many places) has been removed.

The class was generally cleaned up as well:

  • Fields (static or otherwise) are all at the top instead of interleaved with other parts of the class.
  • Unnecessary code was removed (unnecessary generics, length checks, initializers, etc)
  • Unused code was removed
  • Methods are made private if they aren't needed outside MapToolLineParser.

While updating dependent code, some other unnecessary or unused code was identified and removed as well.

Token

The Token class no longer has anything to do with property evaluation. It only does property lookup. The evaluation portions were moved into MapToolVariableResolver that only knows how to evaluate properties of its tokenInContext. Additionaly, MapToolVariableResolver does not permit arbitrary evaluations (as Token#evaluateProperty() did), instead only permitted properties to be looked up and evaluated by the property name.

The main thrust of this change is to avoid any possibility of one token being asked to evaluate its own properties with a resolver that was set up for a different token. And many callers already have their resolver + token available, and so it's simpler for them with the new approach.

MapToolVariableResolver

A quality of life change in MapToolVariableResolver is the new #spawn() method that creates a new MapToolVariableResolver with the same tokenInContext. This simplifies a handful of places that create resolvers, and allows these call sites to continue to be correct into the future if we ever add to what can be "in context".

Also, the delayed actions were never used, so they have been removed. That also means we no longer need to call MapToolVariableResolver#initialize() or MapToolVariableResolver#flush().

MapToolLineParser contexts

#enterContext() has been simplified so it no longer handles the context == null case. This case was only ever hit inside parseLine(), and it is clearer to create the new context there. All other callers provided a non-null context.

Since #enterContext() no longer has to return a MapToolMacroContext, it (and related methods) instead returns an AutoCloseable that can be used to exit the context. Most callers were already using try/finally (or at least try/catch) to consistently exit their contexts, and this allows to avoid the explicit finally logic. It also fixes some "bugs", though these aren't hit in practice:

  • EventMacroUtil#callEventHandlerOld() could exit the wrong context if an error was thrown while setting variables. Not actually hit since it is only used for onCampaignLoad which does not set variables.
  • ExecFunction#runExecFunction() would not exit its context if an unhandled exception was thrown, though it did at least catch ParserException.

Possible Drawbacks

The change to MapToolLineParser#enterContext() had one change of behaviour, but I'm not sure right now how to expose it. I'm 99% sure the original behaviour is unintended.

If #enterContext() received a null parameter with an empty context stack, it would always set #macroPathTrusted to false, even if the generated context would have been trusted (i.e., if the player is a GM). This is specific to being called the first time from #parseLine() as calls from #enterTrustedContext() and #enterUntrustedContext() do not hit this case even if they are run on an empty stack and would add an identical context to the stack. This had resulted in an inconsistency where entering a top-level trusted context on an empty stack might set #macroPathTrusted to true, but it might not. Along with the comment in #enterContext(), I'm pretty confident the new behaviour is the intended behaviour:

// First time through set our trusted path to same as first context.

Documentation Notes

N/A

Release Notes

  • Cleaned up macro evaluation code.

This change is Reviewable

@kwvanderlinde kwvanderlinde self-assigned this Oct 15, 2026
@kwvanderlinde kwvanderlinde moved this from Todo to Awaiting Review in MapTool 1.20 Oct 15, 2026
@github-actions github-actions Bot added the code-maintenance Adding/editing javadocs, unit tests, formatting. label Oct 15, 2026
@kwvanderlinde
kwvanderlinde force-pushed the refactor/6070-MapToolLineParser branch 2 times, most recently from e0eaddc to 5cca691 Compare October 18, 2026 07:12
@kwvanderlinde
kwvanderlinde force-pushed the refactor/6070-MapToolLineParser branch from 5cca691 to ccd951d Compare November 7, 2026 17:13
These were simply unused and thus have been removed:
- `#UNKNOWN_LIB_MACRO`
- `#getTokenMacroLib(String)`
- `#getTokenMacroLibZone(String)`
- `InlineRollMatch(int, int, String)` (previously suppressed)
- `InlineRollMatch#getOptEnd()` (previously suppressed)

Additionally, the `#runMacroBlock()` overloads have been removed since they are either unused or don't do anything
meaningful. There is only one surviving `#runMacroBlock()` method, and it is `private` now.
Static fields are now all at the top of the class, separated by services and constants.

In general, avoid writing things that don't need to exist (generics, initializers, non-zero length checks, etc).

Some improvements as well to how the context stack is interacted with (no more `#contextStackEmpty()`, and
`#getContext()` is explicitly nullable now).
For most of the methods, this just removes the uninteresting overloads that tack on a `null` or `true` to the argument
list. This applies to:
- `#runMacro()`
- `#rollString()`
- `#expandRoll()`
- `#parseExpression()`
- `#enterContext()` (overload only used in tests)

The `#parseLine()` change is similar, but with a bit more going on. It now requires the variable resolver to be provided
by the caller (`null` is no longer allowed). It is the caller's responsibility to ensure the variable resolver and
`tokenInContext` agree as needed. Otherwise, like the other methods, the other overloads have been removed.
Many methods that accepted a `Token tokenInContext` parameter did not ultimately use it. Others only used when creating
a new `MapToolVariableResolver` in case a `null` was passed, but this was avoided by requiring callers to always pass a
real `MapToolVariableResolver`.

Once the above was done, it became clear that there was no need to pass around `tokenInContext` separately from
`MapToolVariableResolver` at all. Overloads of methods like `MapToolLineParser#parseLine()` are now more focused on
providing the option to provide a `MapToolVariableResolver` directly, or for convenience a `Token` that should be
wrapped in a `MapToolVariableResolver`.
This also means we don't need to "initialize" or "flush" the resolver anymore.
This avoids any possibility of callers providing a resolver for a different token, and just better separates
responsibilities. It also means `Token` isn't responsible for evaluating arbitrary objects either, as it had to
with `evaluateProperty()`.

In contrast with the old methods from `Token`, the new methods on `MapToolVariableResolver` only allows evaluating a
named property's value or default value. There is no exposed way to evaluate an arbitrary object.
`#enterContext()` no longer handles the `context == null` case, instead requiring the caller to always provide a real
context to use. This case was only ever used by `parseLine()` anyway, and is much clearer when done there.

This fixes a bug where entering a new **trusted** top-level context on an empty context stack would set
`macroPathTrusted` to `false`, whereas in every other case (including calls through `#enterTrustedContext())` this would
result in `true`. The surrounding comments also clarify that this is the intent.

Now that `#enterContext()` no longer creates a context, it also has no reason to return one. Similarly, `#exitContext()`
no longer returns a context either.
`#enterContext()`, `#enterTrustedContext()`, and `#enterUntrustedContext()` now all return an `AutoCloseable` that can
be used to exit the context. All callers now use this, and so `#exitContext()` has been removed as unnecessary.

All callers now use try-with-resources to guarantee proper exiting of the context. Most sites were already well-behaved,
but a couple had issues:
- `ExecFunction#runExecFunction()` has not wrapped `#exitContext()` in a `finally {...}` block, though it did at least
  catch `ParseException` first. So this was unlikely to be seen in a reproducible way.
- `EventMacroUtil#callEventHandlerOld()` wrapped `#exitContext()` in the wrong `finally {...}` block. If an error were
  throw while setting up the variables (including a handled `ParserException`), it would end up exiting a context it
  never entered. In practice, however, this case could not be hit since the function is only used for `onCampaignLoad`
  which sets no variables.
@kwvanderlinde
kwvanderlinde force-pushed the refactor/6070-MapToolLineParser branch from ccd951d to 211feeb Compare November 7, 2026 17:14
@github-project-automation github-project-automation Bot moved this from Awaiting Review to To Be Merged in MapTool 1.20 Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-maintenance Adding/editing javadocs, unit tests, formatting.

Projects

Status: To Be Merged

Development

Successfully merging this pull request may close these issues.

[Maintenance]: Simplify MapToolLineParser

2 participants