Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions compiler/propagate/labels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include "description.hh"
#include "global.hh"

#include <vector>

using namespace std;

//=========================== PATHNAME ===============================
Expand Down Expand Up @@ -288,3 +290,111 @@ bool matchGroup(Tree gpath, Tree lpath, Tree& rpath)
return false;
}
}

/**
* @brief Normalize a group label with type prefix against current path.
* This function properly handles relative path components (.., .) in group labels.
*
* @param groupType 0=vertical, 1=horizontal, 2=tab
* @param label The group label (may contain relative path components like "../name")
* @param currentPath The current path context (bottom-up list)
* @return The normalized path to use for contents of this group
*/
Tree normalizeGroupPath(int groupType, Tree label, Tree currentPath)
{
// Safety check
if (!label) {
return cons(cons(tree(groupType), tree("")), currentPath);
}

// Convert the label tree to a string
Sym s;
if (!isSym(label->node(), &s)) {
// If it's not a symbol, treat it as pre-parsed
return cons(cons(tree(groupType), label), currentPath);
}

// Get the label string
const char* labelStr = name(s);
if (!labelStr || labelStr[0] == 0) {
// Empty label
return cons(cons(tree(groupType), tree("")), currentPath);
}

// Parse the label to extract path components
Tree parsedPath = label2path(labelStr);

if (!parsedPath || isNil(parsedPath)) {
// Empty parsed path, shouldn't happen but be safe
return cons(cons(tree(groupType), label), currentPath);
}

// If the parsed path is just a single name (no / separators),
// encode it with the group type and add to current path
if (isNil(tl(parsedPath))) {
Tree name = hd(parsedPath);
// Check if it's a special path component
if (isPathRoot(name) || isPathParent(name) || isPathCurrent(name)) {
// Just apply the path operation
return concatPath(parsedPath, currentPath);
} else {
// It's a simple name, encode it with group type
return cons(cons(tree(groupType), name), currentPath);
}
}

// The parsed path has multiple components: [elem1, elem2, ..., finalName]
// We need to split off the final name and apply the rest (prefix) to currentPath.
// Strategy: Build a list of all elements except the last, then process them.

// Collect all elements except the last into a vector for easier processing
std::vector<Tree> elements;
Tree temp = parsedPath;
while (!isNil(temp)) {
elements.push_back(hd(temp));
temp = tl(temp);
}

if (elements.empty()) {
// Should not happen, but be safe
return cons(cons(tree(groupType), label), currentPath);
}

// The last element is the final name
Tree finalName = elements.back();

// Build the prefix path (all elements except the last) in correct order
Tree pathPrefix = gGlobal->nil;
for (size_t i = 0; i + 1 < elements.size(); i++) {
pathPrefix = cons(elements[i], pathPrefix);
}

// Reverse pathPrefix to get correct top-down order
Tree reversedPrefix = gGlobal->nil;
while (!isNil(pathPrefix)) {
reversedPrefix = cons(hd(pathPrefix), reversedPrefix);
pathPrefix = tl(pathPrefix);
}

// Apply the prefix to current path (this handles .. and .)
// concatPath resolves relative path elements (.., .) against the current path
Tree adjustedPath = concatPath(reversedPrefix, currentPath);

// Check if adjustedPath is a bare path marker (not a list)
// This happens when ".." tries to go beyond the root of an empty path.
// In this case, concatPath returns a single pathParent marker.
// We wrap it in a list to maintain the path structure invariant.
if (adjustedPath && !isNil(adjustedPath) && !isList(adjustedPath)) {
// adjustedPath is a single marker (like pathParent), wrap it in a list
adjustedPath = cons(adjustedPath, gGlobal->nil);
}

// Now add the final name with group type encoding
if (isPathRoot(finalName) || isPathParent(finalName) || isPathCurrent(finalName)) {
// This shouldn't normally happen (path ending in .. or .), but handle it
return concatPath(cons(finalName, gGlobal->nil), adjustedPath);
} else {
// Encode the final name with the group type
return cons(cons(tree(groupType), finalName), adjustedPath);
}
}
4 changes: 4 additions & 0 deletions compiler/propagate/labels.hh
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ Tree normalizePath(Tree path);
Tree superNormalizePath(Tree path);
bool matchGroup(Tree gpath, Tree lpath, Tree& rpath);

// Normalize a group label with type prefix against current path
// groupType: 0=vertical, 1=horizontal, 2=tab
Tree normalizeGroupPath(int groupType, Tree label, Tree currentPath);

#endif
6 changes: 3 additions & 3 deletions compiler/propagate/propagate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,15 +431,15 @@ static siglist realPropagate(Tree slotenv, Tree path, Tree box, const siglist& l
// User Interface Groups

else if (isBoxVGroup(box, label, t1)) {
return propagate(slotenv, cons(cons(tree(0), label), path), t1, lsig);
return propagate(slotenv, normalizeGroupPath(0, label, path), t1, lsig);
}

else if (isBoxHGroup(box, label, t1)) {
return propagate(slotenv, cons(cons(tree(1), label), path), t1, lsig);
return propagate(slotenv, normalizeGroupPath(1, label, path), t1, lsig);
}

else if (isBoxTGroup(box, label, t1)) {
return propagate(slotenv, cons(cons(tree(2), label), path), t1, lsig);
return propagate(slotenv, normalizeGroupPath(2, label, path), t1, lsig);
}

// Block Diagram Composition Algebra
Expand Down
11 changes: 11 additions & 0 deletions tests/codegen-tests/relative-group-paths-string.dsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Companion test for relative-group-paths.dsp
// This uses the string literal form that was already working
// Both tests should produce identical UI structure

import("stdfaust.lib");

// Using explicit path in slider string (already working)
freq = vslider("../h:FREQ/Freq",200,200,1000,0.01);

// The freq slider should be shared between V1 and V2
process = hgroup("V1", os.osc(freq)), hgroup("V2", os.osc(freq));
20 changes: 20 additions & 0 deletions tests/codegen-tests/relative-group-paths.dsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Regression test for issue #1063: relative paths in group labels
// https://github.com/grame-cncm/faust/issues/1063
//
// This test verifies that relative path components (.., .) in group labels
// are resolved correctly against the parent path context.
//
// Both forms should produce the same resolved label for the slider:
// 1. Using explicit path in slider: vslider("../h:FREQ/Freq", ...)
// 2. Using hgroup with relative path: hgroup("../FREQ", vslider("Freq", ...))
//
// Expected behavior: In both cases, the slider should be shared between
// V1 and V2 groups, appearing in a FREQ group at the same level as V1/V2.

import("stdfaust.lib");

// Using hgroup with relative path (previously broken, now fixed)
freq = hgroup("../FREQ", vslider("Freq",200,200,1000,0.01));

// The freq slider should be shared between V1 and V2
process = hgroup("V1", os.osc(freq)), hgroup("V2", os.osc(freq));
5 changes: 5 additions & 0 deletions tests/codegen-tests/relative_group_paths_explicit.dsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import("stdfaust.lib");

// Explicit path form that previously worked
freq_explicit = vslider("../h:FREQ/Freq", 200, 200, 1000, 0.01);
process = hgroup("V1", os.osc(freq_explicit)), hgroup("V2", os.osc(freq_explicit));
5 changes: 5 additions & 0 deletions tests/codegen-tests/relative_group_paths_grouped.dsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import("stdfaust.lib");

// Relative-group form that previously failed: group uses relative path segment
freq_grouped = hgroup("../FREQ", vslider("Freq", 200, 200, 1000, 0.01));
process = hgroup("V1", os.osc(freq_grouped)), hgroup("V2", os.osc(freq_grouped));