11use itertools:: Itertools ;
2- use ndc_lexer:: { Lexer , Token } ;
2+ use ndc_lexer:: { Lexer , Token , TokenLocation } ;
3+ use ndc_parser:: { Expression , ExpressionLocation , ForBody , ForIteration } ;
4+ use std:: collections:: HashSet ;
35use yansi:: { Paint , Painted } ;
46
57pub ( crate ) struct AndycppHighlighter ;
68
79impl AndycppHighlighter {
8- pub fn highlight_line ( line : & str ) -> Vec < Painted < & str > > {
10+ /// Parser-enhanced highlighting that correctly identifies function names
11+ /// even in method-call syntax like `foo.len`.
12+ pub fn highlight_parsed ( line : & str ) -> Vec < Painted < & str > > {
13+ let mut function_spans = HashSet :: new ( ) ;
14+
15+ let expressions = Lexer :: new ( line)
16+ . collect :: < Result < Vec < TokenLocation > , _ > > ( )
17+ . ok ( )
18+ . and_then ( |tokens| ndc_parser:: Parser :: from_tokens ( tokens) . parse ( ) . ok ( ) ) ;
19+
20+ if let Some ( expressions) = expressions {
21+ for expr in & expressions {
22+ collect_function_spans ( expr, & mut function_spans) ;
23+ }
24+ }
25+
26+ Self :: highlight_tokens ( line, & function_spans)
27+ }
28+
29+ fn highlight_tokens < ' a > (
30+ line : & ' a str ,
31+ function_spans : & HashSet < usize > ,
32+ ) -> Vec < Painted < & ' a str > > {
933 let Ok ( tokens) = Lexer :: new ( line) . collect :: < Result < Vec < _ > , _ > > ( ) else {
1034 return vec ! [ line. red( ) ] ;
1135 } ;
@@ -22,26 +46,67 @@ impl AndycppHighlighter {
2246 }
2347
2448 let mut out = Vec :: new ( ) ;
25- for ( range, token) in ranges. into_iter ( ) . zip ( tokens. into_iter ( ) ) {
49+ let pairs: Vec < _ > = ranges. into_iter ( ) . zip ( tokens) . collect ( ) ;
50+ for ( i, ( range, token) ) in pairs. iter ( ) . enumerate ( ) {
2651 let substring = & line[ range. start ..( range. start + range. len ( ) ) ] ;
52+ let next_token = pairs. get ( i + 1 ) . map ( |( _, t) | & t. token ) ;
2753
2854 let colored = match & token. token {
29- Token :: String ( _) => substring. rgb ( 70 , 200 , 128 ) ,
55+ // Strings — green
56+ Token :: String ( _) => substring. rgb ( 152 , 195 , 121 ) ,
57+ // Numeric literals and booleans — orange
3058 Token :: BigInt ( _)
3159 | Token :: Int64 ( _)
3260 | Token :: Float64 ( _)
3361 | Token :: Complex ( _)
62+ | Token :: Infinity
3463 | Token :: True
35- | Token :: False => substring. rgb ( 253 , 151 , 31 ) ,
64+ | Token :: False => substring. rgb ( 209 , 154 , 102 ) ,
65+ // Keywords — coral red
66+ Token :: Let
67+ | Token :: Fn
68+ | Token :: If
69+ | Token :: Else
70+ | Token :: Return
71+ | Token :: Break
72+ | Token :: Continue
73+ | Token :: For
74+ | Token :: In
75+ | Token :: While
76+ | Token :: Pure
77+ | Token :: LogicAnd
78+ | Token :: LogicOr
79+ | Token :: LogicNot => substring. rgb ( 224 , 108 , 117 ) ,
80+ // Function identifiers — yellow/gold
81+ // Detected by parser (dot-calls, etc.) or by token heuristics as fallback
82+ Token :: Identifier ( _) if function_spans. contains ( & token. span . offset ( ) ) => {
83+ substring. rgb ( 229 , 192 , 123 )
84+ }
85+ Token :: Identifier ( _) if matches ! ( next_token, Some ( Token :: LeftParentheses ) ) => {
86+ substring. rgb ( 229 , 192 , 123 )
87+ }
88+ Token :: Identifier ( _) if i > 0 && matches ! ( pairs[ i - 1 ] . 1 . token, Token :: Fn ) => {
89+ substring. rgb ( 229 , 192 , 123 )
90+ }
91+ // Variable identifiers — blue
92+ Token :: Identifier ( _) => substring. rgb ( 97 , 175 , 239 ) ,
93+ // Arrows, fat arrows, and assignment — cyan
94+ Token :: RightArrow | Token :: FatArrow | Token :: EqualsSign | Token :: OpAssign ( _) => {
95+ substring. rgb ( 86 , 182 , 194 )
96+ }
97+ // Brackets and delimiters — light gray (neutral)
3698 Token :: LeftSquareBracket
3799 | Token :: RightSquareBracket
38100 | Token :: LeftCurlyBracket
39101 | Token :: RightCurlyBracket
40102 | Token :: LeftParentheses
41103 | Token :: RightParentheses
42- | Token :: MapOpen => substring. rgb ( 229 , 181 , 103 ) ,
43- Token :: Identifier ( _) => substring. rgb ( 51 , 177 , 255 ) ,
44- _ => substring. rgb ( 140 , 182 , 255 ) . bold ( ) ,
104+ | Token :: MapOpen
105+ | Token :: Semicolon
106+ | Token :: Comma
107+ | Token :: Colon => substring. rgb ( 171 , 178 , 191 ) ,
108+ // Operators — purple
109+ _ => substring. rgb ( 198 , 120 , 221 ) ,
45110 } ;
46111
47112 out. push ( colored) ;
@@ -50,3 +115,107 @@ impl AndycppHighlighter {
50115 out
51116 }
52117}
118+
119+ /// Walk the parsed AST and collect the byte offsets of identifiers used as function names.
120+ fn collect_function_spans ( expr : & ExpressionLocation , spans : & mut HashSet < usize > ) {
121+ match & expr. expression {
122+ Expression :: Call {
123+ function,
124+ arguments,
125+ } => {
126+ if let Expression :: Identifier { .. } = & function. expression {
127+ spans. insert ( function. span . offset ( ) ) ;
128+ }
129+ collect_function_spans ( function, spans) ;
130+ for arg in arguments {
131+ collect_function_spans ( arg, spans) ;
132+ }
133+ }
134+ Expression :: FunctionDeclaration { body, .. } => {
135+ collect_function_spans ( body, spans) ;
136+ }
137+ Expression :: VariableDeclaration { value, .. }
138+ | Expression :: Assignment { r_value : value, .. }
139+ | Expression :: OpAssignment { r_value : value, .. }
140+ | Expression :: Return { value } => {
141+ collect_function_spans ( value, spans) ;
142+ }
143+ Expression :: Statement ( inner) | Expression :: Grouping ( inner) => {
144+ collect_function_spans ( inner, spans) ;
145+ }
146+ Expression :: Block { statements } => {
147+ for s in statements {
148+ collect_function_spans ( s, spans) ;
149+ }
150+ }
151+ Expression :: If {
152+ condition,
153+ on_true,
154+ on_false,
155+ } => {
156+ collect_function_spans ( condition, spans) ;
157+ collect_function_spans ( on_true, spans) ;
158+ if let Some ( f) = on_false {
159+ collect_function_spans ( f, spans) ;
160+ }
161+ }
162+ Expression :: While {
163+ expression,
164+ loop_body,
165+ } => {
166+ collect_function_spans ( expression, spans) ;
167+ collect_function_spans ( loop_body, spans) ;
168+ }
169+ Expression :: For { iterations, body } => {
170+ for iteration in iterations {
171+ match iteration {
172+ ForIteration :: Iteration { sequence, .. } => {
173+ collect_function_spans ( sequence, spans) ;
174+ }
175+ ForIteration :: Guard ( expr) => collect_function_spans ( expr, spans) ,
176+ }
177+ }
178+ match body. as_ref ( ) {
179+ ForBody :: Block ( e) | ForBody :: List { expr : e, .. } => {
180+ collect_function_spans ( e, spans) ;
181+ }
182+ ForBody :: Map {
183+ key,
184+ value,
185+ default,
186+ ..
187+ } => {
188+ collect_function_spans ( key, spans) ;
189+ if let Some ( v) = value {
190+ collect_function_spans ( v, spans) ;
191+ }
192+ if let Some ( d) = default {
193+ collect_function_spans ( d, spans) ;
194+ }
195+ }
196+ }
197+ }
198+ Expression :: Logical { left, right, .. } => {
199+ collect_function_spans ( left, spans) ;
200+ collect_function_spans ( right, spans) ;
201+ }
202+ Expression :: Tuple { values } | Expression :: List { values } => {
203+ for v in values {
204+ collect_function_spans ( v, spans) ;
205+ }
206+ }
207+ Expression :: Map { values, default } => {
208+ for ( k, v) in values {
209+ collect_function_spans ( k, spans) ;
210+ if let Some ( v) = v {
211+ collect_function_spans ( v, spans) ;
212+ }
213+ }
214+ if let Some ( d) = default {
215+ collect_function_spans ( d, spans) ;
216+ }
217+ }
218+ // Literals, identifiers (non-call), etc. — nothing to collect
219+ _ => { }
220+ }
221+ }
0 commit comments