FancyMenu [Fabric] [MOVED TO NEW PROJECT]

FancyMenu [Fabric] [MOVED TO NEW PROJECT]

17M Downloads

PlaceholderParser failing to replace placeholders in some edge case strings with lots of curly brackets outside placeholders

Keksuccino opened this issue · 0 comments

commented

Description

Description:
When attempting to execute an /item replace command via a FancyMenu action, a Javscript Error: expected "}" syntax error is encountered. This error specifically arises when a nested json placeholder is used to define an NBT tag's value, and the json_path parameter within this json placeholder itself contains a getvariable placeholder.

Problematic Command Snippet (FancyMenu Action Value):
/item replace entity @p hotbar.3 with tacz:ammo{AmmoId:"tacz:{"placeholder":"json","values":{"source":"bst_data/loadout/gun_ammo_types","json_path":"{"placeholder":"getvariable","values":{"name":"sel_pmy_gun"}}"}}"} 20

Steps to Reproduce (Conceptual based on user interaction):

  1. Define a FancyMenu variable, e.g., sel_pmy_gun, with a value representing a valid JSON path (e.g., field_name).
  2. Create an action (e.g., a button click action) that executes a sendmessage or similar action containing the /item replace command string constructed as shown above.
  3. Trigger the action.

Expected Behavior:
FancyMenu should correctly evaluate all nested placeholders, resulting in a valid string (e.g., tacz:ammo{AmmoId:"tacz:some_final_ammo_id"}) which is then passed to the Minecraft command system, and the command should execute successfully.

Actual Behavior:
A Javscript Error: expected "}" is reported, indicating a syntax error originating from the placeholder evaluation or command string construction process within FancyMenu, before Minecraft itself processes the command.
Additional Context:

  • A simpler /item replace command using a json placeholder (without the getvariable nested in json_path) works without issues: /item replace entity @s weapon.offhand with tacz:modern_kinetic_gun{HasBulletInBarrel: 1b, GunFireMode: "SEMI", GunId: "tacz:{"placeholder":"json","values":{"source":"bst_data/loadout/scd_gun.json","json_path":"scd_gun"}}", GunCurrentAmmoCount: 10}.
  • The raw command (with manually evaluated placeholder results) functions correctly when entered directly into Minecraft chat.
  • The issue seems specific to the deep nesting combination of json_path being dynamically set by getvariable within the json placeholder, coupled with being embedded inside NBT string quotes.

This seems like a very specific parsing anomaly that warrants further investigation into the placeholder evaluation logic for such complex nesting scenarios.

Potential Fix

Looking at this bug report and the Java code, I can identify the issue. The problem is in the findPlaceholderEndIndex method, which doesn’t properly handle brackets that appear inside quoted strings.

The Bug

When parsing nested placeholders where the inner placeholder is within a JSON string value (like "json_path":"{"placeholder":"getvariable"...}"), the parser incorrectly counts the brackets inside the quoted string as part of the bracket depth calculation. This causes it to match the wrong closing bracket, resulting in malformed JSON and the “expected }” error.

The Issue in findPlaceholderEndIndex

The current implementation:

private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
    int currentIndex = startIndex;
    int depth = 0;
    boolean backslash = false;
    for (char c : placeholderStartSubString.toCharArray()) {
        if (currentIndex != startIndex) { //skip first char
            if ((c == OPEN_CURLY_BRACKETS_CHAR) && !backslash) {
                depth++;  // Counts ALL brackets, even those inside quotes!
            } else if ((c == CLOSE_CURLY_BRACKETS_CHAR) && !backslash) {
                if (depth <= 0) {
                    return currentIndex;
                } else {
                    depth--;
                }
            }
            backslash = (c == BACKSLASH_CHAR);
        }
        currentIndex++;
    }
    return -1;
}

This method tracks bracket depth but doesn’t account for quotes. When brackets appear inside a quoted string (like in your nested placeholder), they shouldn’t affect the depth count.

The Fix

The method needs to track whether it’s currently inside a quoted string, similar to how normalizePlaceholderString already does:

private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
    int currentIndex = startIndex;
    int depth = 0;
    boolean backslash = false;
    boolean inQuotes = false;  // Add quote tracking
    
    for (char c : placeholderStartSubString.toCharArray()) {
        if (currentIndex != startIndex) { //skip first char
            // Handle quote state changes
            if ((c == APOSTROPHE_CHAR) && !backslash) {
                inQuotes = !inQuotes;
            }
            // Only count brackets when NOT inside quotes
            else if (!inQuotes) {
                if ((c == OPEN_CURLY_BRACKETS_CHAR) && !backslash) {
                    depth++;
                } else if ((c == CLOSE_CURLY_BRACKETS_CHAR) && !backslash) {
                    if (depth <= 0) {
                        return currentIndex;
                    } else {
                        depth--;
                    }
                }
            }
            backslash = (c == BACKSLASH_CHAR);
        }
        currentIndex++;
    }
    return -1;
}

Key Changes:

  1. Added boolean inQuotes = false to track whether we’re inside a quoted string
  2. When encountering an unescaped quote ("), toggle the inQuotes state
  3. Only count brackets ({ and }) when !inQuotes - brackets inside quoted strings are just part of the string value and shouldn’t affect depth

This fix ensures that the parser correctly identifies the boundaries of the outer placeholder without being confused by brackets that appear within nested placeholders inside quoted JSON values.​​​​​​​​​​​​​​​​


Yes, my conclusion remains the same. The additional context actually confirms the issue I identified. Let me explain why:

Why the Additional Context Confirms the Bug

  1. The working example (without nested getvariable) has NO brackets inside the quoted json_path value:

    "json_path":"scd_gun"  // Just a simple string, no brackets
  2. The failing example has brackets INSIDE the quoted json_path value:

    "json_path":"{"placeholder":"getvariable","values":{"name":"sel_pmy_gun"}}"
    //            ^-- These brackets are inside quotes but counted by findPlaceholderEndIndex

The Root Cause

When findPlaceholderEndIndex encounters the nested placeholder structure, it:

  1. Starts at the opening { of the outer json placeholder
  2. Increments depth for every { it finds (including those inside the quoted json_path)
  3. Decrements depth for every } it finds
  4. Incorrectly matches a closing } from inside the quoted string as the end of the outer placeholder

This explains why:

  • Simple placeholders work (no brackets in quoted values)
  • The raw command works when manually evaluated (no placeholder parsing needed)
  • The specific nesting pattern fails (brackets inside quoted values confuse the parser)

The Fix Remains the Same

The findPlaceholderEndIndex method needs to track whether it’s inside quotes:

private static int findPlaceholderEndIndex(@NotNull String placeholderStartSubString, int startIndex) {
    int currentIndex = startIndex;
    int depth = 0;
    boolean backslash = false;
    boolean inQuotes = false;  // Track quote state
    
    for (char c : placeholderStartSubString.toCharArray()) {
        if (currentIndex != startIndex) {
            // Handle escape sequences
            if (backslash) {
                backslash = false;
                currentIndex++;
                continue;
            }
            
            // Check for escape character
            if (c == BACKSLASH_CHAR) {
                backslash = true;
                currentIndex++;
                continue;
            }
            
            // Toggle quote state
            if (c == APOSTROPHE_CHAR) {
                inQuotes = !inQuotes;
            }
            // Only count brackets when NOT inside quotes
            else if (!inQuotes) {
                if (c == OPEN_CURLY_BRACKETS_CHAR) {
                    depth++;
                } else if (c == CLOSE_CURLY_BRACKETS_CHAR) {
                    if (depth <= 0) {
                        return currentIndex;
                    } else {
                        depth--;
                    }
                }
            }
        }
        currentIndex++;
    }
    return -1;
}

Note: I’ve also improved the escape handling to be cleaner - the previous version had a subtle bug where backslash was set at the end of the loop, which could miss some edge cases.

This fix ensures that brackets inside quoted strings (like your nested getvariable placeholder) are treated as literal string content rather than structural JSON brackets, allowing the parser to correctly identify where the outer placeholder ends.​​​​​​​​​​​​​​​​