Skip to content

test: InfoVarsLstTest for INFOVARS token (WIP — for review with LK) - #7720

Draft
Vest wants to merge 5 commits into
PCGen:masterfrom
Vest:infovars-scope-prefix
Draft

test: InfoVarsLstTest for INFOVARS token (WIP — for review with LK)#7720
Vest wants to merge 5 commits into
PCGen:masterfrom
Vest:infovars-scope-prefix

Conversation

@Vest

@Vest Vest commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Status: draft / WIP — to be analyzed together by @Vest and LegacyKing before this is ready.

Test-only PR adding coverage for the INFOVARS token. No production change: investigation confirmed what is (and isn't) implemented relative to the 2018 spec.

What matches the spec

  • INFO:x|y — info name + java.text.MessageFormat. ✅
  • The three load-error rules — INFOVARS name with no INFO; INFO needs vars but none provided; var count mismatch (all in InfoLst.process). ✅
  • FreeMarker *.info accessor (e.g. pc.race.info.wildness). ✅
  • "Global variables only" is enforced at parse timeInfoVarsLst.parseTokenWithSeparator validates each name against the global active scope via isLegalVariableID(getActiveScope(), name).

Spec divergence (the unbuilt feature)

The 2018 spec describes a scoped grammar INFOVARS:x|SCOPE=var (scope optional for default VAR). This is not implemented — INFOVARS parses bare names only; a SCOPE=var token is treated as one illegal name and rejected (verified, including the default VAR=x form). The implementation is a subset: bare global names, no scope prefix. Local-scope support needs a per-instance selector INFOVARS lacks (LK flagged getOther() as the fitting model). Deferred.

Can INFOVARS replace existing Pathfinder LST?

Conceptually yes (INFO/INFOVARS is meant to replace DESC/ASPECT/BENEFIT/SPROP), but almost none is replaceable as-is today:

Token PF uses with % substitution
DESC 59,808 9,770
ASPECT 9,024 2,879
BENEFIT 4,131 489
  • Static (no %) DESC/ASPECT/SPROP → replaceable by plain INFO: now (no INFOVARS needed); the large majority, low-risk, but doesn't exercise INFOVARS.
  • %-substitution tokens (~13k) are the real INFOVARS targets, but their variables are old-system DEFINE: (23,458 in PF) not new-system GLOBAL: (14) — INFOVARS cannot read a DEFINE: var, so these need a DEFINE:GLOBAL: migration first (the data-team "Phase 2"). Many also embed PRExxx (INFO is unconditional by design) or math, or hit the local-scope wall — needing the deferred features.

Smallest genuine conversion (e.g. ASPECT:Ability Benefit|(%1 ft.)|JetSpeed): declare GLOBAL:NUMBER=JetSpeed, then INFO:AbilityBenefit|({0} ft.) + INFOVARS:AbilityBenefit|JetSpeed. testAspectMigrationPatternToInfoVars demonstrates exactly this end-to-end. (No shipped-data change here — migration is a separate decision.)

Data usage

INFOVARS: appears zero times in shipped data (plain INFO: ~580 in PF core, ~1332 project-wide). The feature is greenfield — no data-regression risk, no existing INFOVARS example to copy.

Tests added

  • InfoVarsLstTest (unit): round-robin + invalid-input cases.
  • GlobalInfoVarsTest (integration) — INFOVARS against the real formula system:
    • testInfoVarsResolvesGlobalVariable: GLOBAL-declared var referenced by INFO+INFOVARS, loads end-to-end.
    • testInfoVarsRejectsUndeclaredVariable: undeclared var rejected at parse.
    • testInfoVarsScopedFormNotYetSupported: the spec's SCOPE=var form is currently rejected (flip if implemented).
    • testInfoVarsCannotReachLocalHeadScopeVariable: LegacyKing's example — a crit multiplier in the weapon head's local PC.EQUIPMENT.PART scope is still rejected, because INFOVARS resolves against the global scope with no per-instance selector.
    • testAspectMigrationPatternToInfoVars: the ASPECT→INFOVARS conversion (GLOBAL + INFO/INFOVARS) loading end-to-end.

How to use INFOVARS in LST today

# declare a global variable (in a *__variables.lst datacontrol file)
GLOBAL:NUMBER=NumFiles

# reference it (bare name — no scope prefix)
Dodge   INFO:FileCount|There are {0,number,integer} files.   INFOVARS:FileCount|NumFiles

Wiki reference: https://vest.github.io/pcgen-wiki/formula-system/INFO%20and%20INFOVARS.html

Vest added 5 commits August 26, 2026 01:34
INFOVARS parses bare variable names validated in the active scope; this
adds the previously-missing test coverage (round-trip for single and
multiple vars, plus load-error cases). No production change: investigation
confirmed the documented scope/format prefix grammar is not resolvable
(local scopes need an instance selector INFOVARS lacks; the engine already
enforces one format per variable name), so bare names are the correct and
complete surface.
Adds GlobalInfoVarsTest, an itest that exercises INFOVARS against the real
formula system rather than a programmatically-asserted variable:

- testInfoVarsResolvesGlobalVariable: declare a global via the GLOBAL token,
  then INFO + INFOVARS reference it and load end-to-end (finishLoad runs the
  deferred tokens and resolveReferences).
- testInfoVarsRejectsUndeclaredVariable: an undeclared variable is rejected at
  INFOVARS parse time.

Confirms the "INFOVARS can only reference global variables" rule already
enforced in InfoVarsLst.parseTokenWithSeparator. Test-only.
The 2018 INFO/INFOVARS spec describes a scoped form INFOVARS:x|SCOPE=var
(scope prefix optional for the default VAR scope). That grammar is not
implemented: INFOVARS accepts only bare global variable names, so a
"SCOPE=var" token is treated as one illegal variable name and rejected.

Add testInfoVarsScopedFormNotYetSupported pinning that boundary (even
VAR=x, the spec's default scope, is rejected today). If the scoped form is
implemented later, this test flips to expect success. Test-only.
Adds testInfoVarsCannotReachLocalHeadScopeVariable, the executable form of
LegacyKing's example: a weapon crit multiplier lives in the local
PC.EQUIPMENT.PART (head) scope, and an INFO/INFOVARS on another object (a
weapon-enchantment ability) cannot pull it.

The variable IS declared (via the real LOCAL token in the head scope), yet
INFOVARS still rejects it — proving the limitation is the scope (INFOVARS
resolves against the global scope and has no per-instance selector for "this
weapon's head"), not a missing declaration. This is the concrete case the
deferred getOther()-style feature would need to address. Test-only.
Adds testAspectMigrationPatternToInfoVars, an executable template for
converting a substitution-bearing ASPECT/DESC to INFO/INFOVARS. Pathfinder
has ~13k such tokens (e.g. "ASPECT:Ability Benefit|(%1 ft.)|JetSpeed"), but
their %-vars are old-system DEFINE: variables that INFOVARS cannot read.
The conversion is: migrate the variable to a new-system GLOBAL declaration,
then express the text as a MessageFormat:

    GLOBAL:NUMBER=JetSpeed
    INFO:AbilityBenefit|({0} ft.)   INFOVARS:AbilityBenefit|JetSpeed

The test loads that migrated form end-to-end. It does not change shipped
data; the DEFINE->GLOBAL migration is a separate data-team decision. Test-only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant