Skip to content

Conversation

@mcourteaux
Copy link
Contributor

@mcourteaux mcourteaux commented May 28, 2025

📖 Overview

  • IRPrinter supports ANSI escape codes for coloring when printing to std::cout or std::cerr.
  • Priority on parenthesis and braces matching (rainbow) to make depth of Exprs clear.
  • Control flow keywords color by depth.
  • Only enables if your TERM environment variable indicates color support, and if isatty() passes for the output stream.
  • Can be disabled with HL_COLORS=0, and forcefully enabled with HL_COLORS=1.
  • Rely on standard VGA colors to make sure it works with dark and light terminal color schemes.
  • Resets the depth on RHS of LetStmt, Store, and Provide, to have have reliable depth to color mapping on the Stmt level.

👀 Preview

Screenshot 2025-05-28 at 16 12 46

Screenshot 2025-05-28 at 16 14 06

Screenshot 2025-05-28 at 16 15 23

Screenshot 2025-05-28 at 16 22 08

Screenshot 2025-05-28 at 16 24 20

Also useful to simply print Exprs for debugging:
Screenshot 2025-05-28 at 16 25 30

@mcourteaux mcourteaux requested a review from alexreinking May 28, 2025 14:26
@alexreinking
Copy link
Member

Love this idea. Will take a while to review.

@mcourteaux
Copy link
Contributor Author

I was looking into whether this is going to to work on Windows. I saw that you can enable Virtual Terminal processing, by doing:

// Set output mode to handle virtual terminal sequences
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode)
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode)

I have not added the code above right now, and rely on detecting color support through the TERM environment variable. I don't know if the code I have for detecting color support is adequate on Windows right now. Maybe @slomp can test this? I wanna be sure this doesn't produce gibberish on Windows.

@mcourteaux mcourteaux requested a review from slomp May 28, 2025 15:11
@alexreinking
Copy link
Member

I was looking into whether this is going to work on Windows.

On Posix-y systems at least, I think you need to check isatty somewhere, in case stdout or stderr are redirected to a file or pipe. On such systems, a function like this should work:

bool supports_ansi(std::ostream &os) {
    if (&os == &std::cout) {
        return isatty(fileno(stdout));
    } else if (&os == &std::cerr) {
        return isatty(fileno(stderr));
    }
    return false;
}

This should be more robust than checking TERM. In this case, you can also check on Windows (using appropriate #ifdefs):

bool supports_ansi(std::ostream &os) {
    HANDLE h;
    if (&os == &std::cout) {
        h = GetStdHandle(STD_OUTPUT_HANDLE);
    } else if (&os == &std::cerr) {
        h = GetStdHandle(STD_ERROR_HANDLE);
    } else {
        return false;
    }

    DWORD mode;
    return GetConsoleMode(h, &mode) &&
           SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}

Should be something like that.

@alexreinking
Copy link
Member

I also wonder whether the other ad-hoc if (ansi) checks can be replaced with more dedicated helpers? You could instantiate IRPrinter with one of two tables for the ANSI codes: one has all empty strings and the other has the real ones.

@mcourteaux
Copy link
Contributor Author

I also wonder whether the other ad-hoc if (ansi) checks can be replaced with more dedicated helpers? You could instantiate IRPrinter with one of two tables for the ANSI codes: one has all empty strings and the other has the real ones.

I thought about alternatives, but decided for this, as this will be 100% correctly branch predicted.

@alexreinking
Copy link
Member

I thought about alternatives, but decided for this, as this will be 100% correctly branch predicted.

Sure, but performance doesn't really matter for the IRPrinter (it's all IO bound anyway) and I'm thinking about saving future headaches if we need to add another case.

You could also template-ize the whole thing and have no branches at all. 🤷

@mcourteaux
Copy link
Contributor Author

Implemented your suggestion for the tabular constants, and added the Windows-specific code. Thanks for those!

@slomp
Copy link
Contributor

slomp commented May 28, 2025

A few things to keep in mind:
If, say, stdout is redirected, and another stream (say, myout) is attached to where the stdout pipe was originally connected to the terminal, and myout is passed to supports_ansi(myout) then the if (&os == &std::cout) will fail, even though isatty(fileno(myout)) is true.

That said, this is catastrophizing to the next level! (but I have seen super weird stream rebindings in production over the years...)

@alexreinking
Copy link
Member

Implemented your suggestion for the tabular constants, and added the Windows-specific code. Thanks for those!

That looks great!

@alexreinking
Copy link
Member

alexreinking commented May 28, 2025

A few things to keep in mind: If, say, stdout is redirected, and another stream (say, myout) is attached to where the stdout pipe was originally connected to the terminal, and myout is passed to supports_ansi(myout) then the if (&os == &std::cout) will fail, even though isatty(fileno(myout)) is true.

That said, this is catastrophizing to the next level! (but I have seen super weird stream rebindings in production over the years...)

In this case, the failure mode would be to not emit ANSI codes, which is always safe. I suppose HL_NO_COLORS could be replaced with a ternary HL_COLORS policy variable where:

  • (not defined) means "auto-detect"
  • 0 means "do not emit ANSI codes", and
  • 1 means "always emit ANSI codes"

Of course, the real failure is of the C++ standards committee to make the std::ostream interface so anemic. 🙄

… is used as undefined (auto-detect), 0 or 1.
@mcourteaux
Copy link
Contributor Author

Thanks @alexreinking and @slomp for the feedback. I implemented this:

In this case, the failure mode would be to not emit ANSI codes, which is always safe. I suppose HL_NO_COLORS could be replaced with a ternary HL_COLORS policy variable where:

  • (not defined) means "auto-detect"
  • 0 means "do not emit ANSI codes", and
  • 1 means "always emit ANSI codes"

I also validated that piping to a file does not produce colors. Overriding with HL_COLORS works.

@mcourteaux
Copy link
Contributor Author

the if (&os == &std::cout) will fail, even though isatty(fileno(myout)) is true.

There seems to be no way to get the file descriptor from an std::ostream. I guess because it's not guaranteed to go to a file (such as stringstream).

@slomp
Copy link
Contributor

slomp commented May 28, 2025

the if (&os == &std::cout) will fail, even though isatty(fileno(myout)) is true.

There seems to be no way to get the file descriptor from an std::ostream. I guess because it's not guaranteed to go to a file (such as stringstream).

Yup, no standard way of doing so, and as you pointed, a stream object does not need to be backed by a file stream or (file-descriptor). It's one of the reasons I tend to steer away from C++ io abstractions.
Anyway, not much we can do about it, I just wanted to point this very rare edge-case, in case you experience that some day.

Copy link
Member

@alexreinking alexreinking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Excited to try it out.

@mcourteaux
Copy link
Contributor Author

Dang I switched to another branch to continue work there, and I'm missing this already badly 😝

@mcourteaux
Copy link
Contributor Author

Does anybody want to add something to this, or can we merge this? @steven-johnson @abadams ?

@alexreinking
Copy link
Member

I think it's safe to merge this. We can always revert if something goes wrong and both @slomp and I have taken a look.

@mcourteaux mcourteaux merged commit 8bf7827 into halide:main May 29, 2025
18 of 19 checks passed
@mcourteaux
Copy link
Contributor Author

Btw, if you liked this, you might also wanna be aware of the neovim/vim syntax highlighting file I contributed to the Wiki: https://github.com/halide/Halide/wiki/Syntax-highlighting-for-Stmt-files

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.

3 participants