Repository: apple/pkl
File: pkl-core/src/main/java/org/pkl/core/runtime/StackTraceRenderer.java (Java)

Code before:
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pkl.core.runtime;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.pkl.core.StackFrame;
import org.pkl.core.util.Nullable;

public final class StackTraceRenderer {
  private final Function<StackFrame, StackFrame> frameTransformer;

  public StackTraceRenderer(Function<StackFrame, StackFrame> frameTransformer) {
    this.frameTransformer = frameTransformer;
  }

  public void render(List<StackFrame> frames, @Nullable String hint, StringBuilder builder) {
    var compressed = compressFrames(frames);
    doRender(compressed, hint, builder, "", true);
  }

  // non-private for testing
  void doRender(
      List<Object /*StackFrame|StackFrameLoop*/> frames,
      @Nullable String hint,
      StringBuilder builder,
      String leftMargin,
      boolean isFirstElement) {
    for (var frame : frames) {
      if (frame instanceof StackFrameLoop loop) {
        // ensure a cycle of length 1 doesn't get rendered as a loop
        if (loop.count == 1) {
          doRender(loop.frames, null, builder, leftMargin, isFirstElement);
        } else {
          if (!isFirstElement) {
            builder.append(leftMargin).append("\n");
          }
          builder.append(leftMargin).append("┌─ ").append(loop.count).append(" repetitions of:\n");
          var newLeftMargin = leftMargin + "│ ";
          doRender(loop.frames, null, builder, newLeftMargin, isFirstElement);
          if (isFirstElement) {
            renderHint(hint, builder, newLeftMargin);
            isFirstElement = false;
          }

Review comment (suggestion):
Happy to see you're choosing colours that don't wobble to us deuteranomalous folks; that said... should these be configurable? More generally, I feel it's probably wise to put all the colour definitions in one place.

Besides that: I'm not _entirely_ certain what `rep` is the abbreviation of... guessing here;
```suggestion
  private static final Ansi.Color frameColor = Color.YELLOW;
  private static final Ansi.Color lineNumColor = Color.BLUE;
  private static final Ansi.Color repetitionColor = Color.MAGENTA;
```

Code after:
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pkl.core.runtime;

import static org.fusesource.jansi.Ansi.ansi;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.pkl.core.StackFrame;
import org.pkl.core.util.Nullable;

public final class StackTraceRenderer {
  private final Function<StackFrame, StackFrame> frameTransformer;

  private static final Ansi.Color frameColor = Color.YELLOW;
  private static final Ansi.Color lineNumColor = Color.BLUE;
  private static final Ansi.Color repetitionColor = Color.MAGENTA;

  public StackTraceRenderer(Function<StackFrame, StackFrame> frameTransformer) {
    this.frameTransformer = frameTransformer;
  }

  public void render(List<StackFrame> frames, @Nullable String hint, StringBuilder builder) {
    var compressed = compressFrames(frames);
    doRender(compressed, hint, builder, "", true);
  }

  // non-private for testing
  void doRender(
      List<Object /*StackFrame|StackFrameLoop*/> frames,
      @Nullable String hint,
      StringBuilder builder,
      String leftMargin,
      boolean isFirstElement) {
    var out = ansi(builder);
    for (var frame : frames) {
      if (frame instanceof StackFrameLoop loop) {
        // ensure a cycle of length 1 doesn't get rendered as a loop
        if (loop.count == 1) {
          doRender(loop.frames, null, builder, leftMargin, isFirstElement);
        } else {
          if (!isFirstElement) {