// lesson: syntax-highlighting
Syntax Highlighting
The last subsystem before assembly. Syntax highlighting sounds like "parse the language" and would be a terrible idea if it were: real parsers demand valid programs, and an editor's buffer is invalid almost always โ you're typing, half the file is mid-keystroke. What editors actually run (until you get to tree-sitter-class machinery) is a lexer-shaped state machine: classify every character into a handful of buckets โ keyword, string, comment, number, normal โ with just enough state to get the annoying cases right.
Per character, because that's what rendering needs: the render loop
walks the line emitting SGR color codes (ESC [ 3x m โ the same SGR
family as your status bar's inverted video) whenever the class changes
from one char to the next; a std::vector<Hl> parallel to the line is
exactly the right output shape. (Hl is an enum class, and the color
mapping lives elsewhere โ classification and presentation separated,
so the tests can check classification without caring about colors.)
The interesting design decision comes from a nasty fact: /* ... */
comments cross lines. Whether line 500 starts inside a comment
depends, in principle, on every line above it. Rescanning the file per
keystroke is O(file) per frame; the editor answer is beautiful:
per-line highlighting with one bit of carried state. Each line's
highlight function takes "did we start inside a comment?" and returns
"did we end inside one?" โ line n's output feeding line n+1's
input, like carry propagation in an adder. Your editor caches per-line
results and, after an edit to line n, re-highlights from n
downward only while the carry-out changes โ type /* at the top of
the file and the repaint cascades; fix a typo inside a line and
re-highlighting stops after that one line. (Strings, in our dialect as
in C's grammar, do not cross lines โ an unterminated string dies at
the newline, so the string flag is line-local and doesn't join the
carry.)
The classification rules, in precedence order โ comment state beats
string state beats everything, because inside /* */ a quote is just
punctuation, and inside "..." a // is just two slashes:
- Carrying a comment: chars are Comment until
*/(both chars Comment), then back to normal scanning. - In a string: chars are String; backslash escapes the next char (both
String โ
"a\"b"doesn't end at the escaped quote); the closing"is String; end of line terminates the string without carry. //opens a comment to end of line;/*opens the carrying kind;"opens a string.- Numbers: a digit run is Number if it starts after a separator
(or at line start); a
.between digits stays Number (3.14). A digit glued to a word (x1,return42) is Normal โ highlighting1inx1as a number is the kind of half-right that's worse than wrong. - Keywords: matched as whole words โ separator (or line edge) on
both sides.
intinprintfmust not light up. - A separator for these purposes: any char that's not a letter, digit, or underscore. (The C-identifier class again โ the same definition as the word-motion classes, not by coincidence: both are asking "where do tokens end?")
โบ The Highlight State Machine
20 ptsImplement highlight_line(line, keywords, starts_in_comment) โ
HlLine{spans, ends_in_comment} per the rules above. spans has
exactly one entry per character. An empty line passes the carry
through untouched.
This function is your render loop's per-line preprocessor: cache the results, feed each carry-out forward, and re-highlight below an edit only while carry-outs change.
Log in to submit a solution and earn points.