Fortran · 45123 bytes Raw Blame History
1 module syntax_highlighter_module
2 use iso_fortran_env, only: int32
3 implicit none
4 private
5
6 public :: syntax_highlighter_t
7 public :: token_t
8 public :: init_highlighter, cleanup_highlighter
9 public :: tokenize_line, get_token_color
10 public :: detect_language
11 public :: TOKEN_PLAIN, TOKEN_KEYWORD, TOKEN_STRING, TOKEN_NUMBER
12 public :: TOKEN_COMMENT, TOKEN_OPERATOR, TOKEN_TYPE, TOKEN_FUNCTION
13 public :: TOKEN_PREPROCESSOR
14
15 ! Token types as integer parameters
16 integer, parameter :: TOKEN_PLAIN = 0
17 integer, parameter :: TOKEN_KEYWORD = 1
18 integer, parameter :: TOKEN_STRING = 2
19 integer, parameter :: TOKEN_NUMBER = 3
20 integer, parameter :: TOKEN_COMMENT = 4
21 integer, parameter :: TOKEN_OPERATOR = 5
22 integer, parameter :: TOKEN_TYPE = 6
23 integer, parameter :: TOKEN_FUNCTION = 7
24 integer, parameter :: TOKEN_PREPROCESSOR = 8
25
26 ! Token structure
27 type :: token_t
28 integer :: type = TOKEN_PLAIN
29 integer :: start_col
30 integer :: end_col
31 end type token_t
32
33 ! Language definition
34 type :: language_def_t
35 character(len=32) :: name = ""
36 character(len=16), allocatable :: extensions(:)
37 character(len=64), allocatable :: keywords(:)
38 character(len=64), allocatable :: types(:)
39 character(len=8) :: comment_single = ""
40 character(len=8) :: comment_start = ""
41 character(len=8) :: comment_end = ""
42 character(len=4), allocatable :: string_delimiters(:)
43 character(len=4), allocatable :: operators(:)
44 logical :: case_sensitive = .true.
45 end type language_def_t
46
47 ! Main highlighter type
48 type :: syntax_highlighter_t
49 type(language_def_t) :: current_lang
50 logical :: enabled = .false.
51 logical :: in_multiline_comment = .false.
52 logical :: in_multiline_string = .false.
53 character(len=4) :: string_delimiter = ""
54 end type syntax_highlighter_t
55
56 ! Color mapping (ANSI escape codes)
57 character(len=*), parameter :: COLOR_KEYWORD = char(27) // '[1;34m' ! Bold Blue
58 character(len=*), parameter :: COLOR_STRING = char(27) // '[32m' ! Green
59 character(len=*), parameter :: COLOR_NUMBER = char(27) // '[35m' ! Magenta
60 character(len=*), parameter :: COLOR_COMMENT = char(27) // '[90m' ! Gray
61 character(len=*), parameter :: COLOR_OPERATOR = char(27) // '[33m' ! Yellow
62 character(len=*), parameter :: COLOR_TYPE = char(27) // '[36m' ! Cyan
63 character(len=*), parameter :: COLOR_FUNCTION = char(27) // '[1;36m' ! Bold Cyan
64 character(len=*), parameter :: COLOR_PREPROC = char(27) // '[95m' ! Light Magenta
65 character(len=*), parameter :: COLOR_RESET = char(27) // '[0m'
66
67 contains
68
69 subroutine init_highlighter(highlighter, filename)
70 type(syntax_highlighter_t), intent(out) :: highlighter
71 character(len=*), intent(in), optional :: filename
72
73 highlighter%enabled = .false.
74 highlighter%in_multiline_comment = .false.
75 highlighter%in_multiline_string = .false.
76
77 if (present(filename)) then
78 call detect_language(highlighter, filename)
79 end if
80 end subroutine init_highlighter
81
82 subroutine cleanup_highlighter(highlighter)
83 type(syntax_highlighter_t), intent(inout) :: highlighter
84
85 if (allocated(highlighter%current_lang%extensions)) &
86 deallocate(highlighter%current_lang%extensions)
87 if (allocated(highlighter%current_lang%keywords)) &
88 deallocate(highlighter%current_lang%keywords)
89 if (allocated(highlighter%current_lang%types)) &
90 deallocate(highlighter%current_lang%types)
91 if (allocated(highlighter%current_lang%string_delimiters)) &
92 deallocate(highlighter%current_lang%string_delimiters)
93 if (allocated(highlighter%current_lang%operators)) &
94 deallocate(highlighter%current_lang%operators)
95 end subroutine cleanup_highlighter
96
97 subroutine detect_language(highlighter, filename)
98 type(syntax_highlighter_t), intent(inout) :: highlighter
99 character(len=*), intent(in) :: filename
100 character(len=:), allocatable :: extension
101 integer :: dot_pos
102
103 ! Find file extension
104 dot_pos = index(filename, '.', back=.true.)
105 if (dot_pos > 0) then
106 extension = filename(dot_pos:)
107
108 select case(extension)
109 case('.f90', '.f95', '.f03', '.f08', '.f18')
110 call load_fortran_syntax(highlighter)
111 case('.py', '.pyw')
112 call load_python_syntax(highlighter)
113 case('.c', '.h')
114 call load_c_syntax(highlighter)
115 case('.cpp', '.cc', '.cxx', '.hpp', '.hxx')
116 call load_cpp_syntax(highlighter)
117 case('.rs')
118 call load_rust_syntax(highlighter)
119 case('.go')
120 call load_go_syntax(highlighter)
121 case('.js', '.jsx', '.mjs')
122 call load_javascript_syntax(highlighter)
123 case('.ts', '.tsx')
124 call load_typescript_syntax(highlighter)
125 case('.sh', '.bash')
126 call load_bash_syntax(highlighter)
127 case('.md', '.markdown')
128 call load_markdown_syntax(highlighter)
129 case default
130 highlighter%enabled = .false.
131 end select
132 end if
133 end subroutine detect_language
134
135 subroutine tokenize_line(highlighter, line, tokens)
136 type(syntax_highlighter_t), intent(inout) :: highlighter
137 character(len=*), intent(in) :: line
138 type(token_t), allocatable, intent(out) :: tokens(:)
139 integer :: i, line_len, token_count
140 character :: ch
141
142 if (.not. highlighter%enabled) then
143 allocate(tokens(1))
144 tokens(1)%type = TOKEN_PLAIN
145 tokens(1)%start_col = 1
146 tokens(1)%end_col = len(line)
147 return
148 end if
149
150 line_len = len(line)
151 allocate(tokens(line_len)) ! Worst case: each char is a token
152 token_count = 0
153 i = 1
154
155 ! Handle multiline comment continuation
156 if (highlighter%in_multiline_comment) then
157 call process_multiline_comment(highlighter, line, tokens, token_count, i)
158 end if
159
160 ! Handle multiline string continuation
161 if (highlighter%in_multiline_string) then
162 call process_multiline_string(highlighter, line, tokens, token_count, i)
163 end if
164
165 ! Process rest of line
166 do while (i <= line_len)
167 ch = line(i:i)
168
169 ! Check for multiline comment start
170 if (check_multiline_comment_start(highlighter, line, i)) then
171 call process_multiline_comment_start(highlighter, line, tokens, token_count, i)
172
173 ! Check for single-line comment
174 else if (check_comment_start(highlighter, line, i)) then
175 token_count = token_count + 1
176 tokens(token_count)%type = TOKEN_COMMENT
177 tokens(token_count)%start_col = i
178 tokens(token_count)%end_col = line_len
179 exit
180 end if
181
182 ! Check for string
183 if (check_string_start(highlighter, line, i)) then
184 call process_string(highlighter, line, tokens, token_count, i)
185
186 ! Check for number
187 else if (is_digit(ch) .or. (ch == '.' .and. i + 1 <= line_len .and. is_digit(line(i+1:i+1)))) then
188 call process_number(line, tokens, token_count, i)
189
190 ! Check for word (keyword, type, identifier)
191 else if (is_alpha(ch) .or. ch == '_') then
192 call process_word(highlighter, line, tokens, token_count, i)
193
194 ! Check for operator
195 else if (is_operator_char(highlighter, ch)) then
196 token_count = token_count + 1
197 tokens(token_count)%type = TOKEN_OPERATOR
198 tokens(token_count)%start_col = i
199 tokens(token_count)%end_col = i
200 i = i + 1
201
202 ! Plain character (space, etc.)
203 else
204 token_count = token_count + 1
205 tokens(token_count)%type = TOKEN_PLAIN
206 tokens(token_count)%start_col = i
207 tokens(token_count)%end_col = i
208 i = i + 1
209 end if
210 end do
211
212 ! Resize tokens array
213 if (token_count > 0) then
214 tokens = tokens(1:token_count)
215 else
216 ! tokens is already allocated, just resize to 1 element
217 deallocate(tokens)
218 allocate(tokens(1))
219 tokens(1)%type = TOKEN_PLAIN
220 tokens(1)%start_col = 1
221 tokens(1)%end_col = max(1, line_len)
222 end if
223 end subroutine tokenize_line
224
225 function get_token_color(tok_type) result(color)
226 integer, intent(in) :: tok_type
227 character(len=:), allocatable :: color
228
229 select case(tok_type)
230 case(TOKEN_KEYWORD)
231 color = COLOR_KEYWORD
232 case(TOKEN_STRING)
233 color = COLOR_STRING
234 case(TOKEN_NUMBER)
235 color = COLOR_NUMBER
236 case(TOKEN_COMMENT)
237 color = COLOR_COMMENT
238 case(TOKEN_OPERATOR)
239 color = COLOR_OPERATOR
240 case(TOKEN_TYPE)
241 color = COLOR_TYPE
242 case(TOKEN_FUNCTION)
243 color = COLOR_FUNCTION
244 case(TOKEN_PREPROCESSOR)
245 color = COLOR_PREPROC
246 case(TOKEN_PLAIN)
247 color = ""
248 case default
249 color = ""
250 end select
251 end function get_token_color
252
253 ! Language-specific loading routines
254 subroutine load_fortran_syntax(highlighter)
255 type(syntax_highlighter_t), intent(inout) :: highlighter
256
257 highlighter%current_lang%name = "fortran"
258 highlighter%current_lang%case_sensitive = .false.
259
260 ! Keywords
261 allocate(highlighter%current_lang%keywords(58))
262 highlighter%current_lang%keywords = [ &
263 "program ", "end ", "subroutine ", "function ", &
264 "module ", "use ", "implicit ", "none ", &
265 "if ", "then ", "else ", "elseif ", &
266 "endif ", "do ", "while ", "enddo ", &
267 "select ", "case ", "default ", "endselect ", &
268 "where ", "elsewhere ", "endwhere ", "forall ", &
269 "call ", "return ", "contains ", "interface ", &
270 "abstract ", "allocate ", "deallocate ", "allocatable ", &
271 "intent ", "in ", "out ", "inout ", &
272 "optional ", "parameter ", "save ", "pointer ", &
273 "target ", "public ", "private ", "protected ", &
274 "bind ", "import ", "only ", "operator ", &
275 "assignment ", "generic ", "final ", "extends ", &
276 "class ", "type ", "endtype ", "enum ", &
277 "enumerator ", "namelist " &
278 ]
279
280 ! Types
281 allocate(highlighter%current_lang%types(10))
282 highlighter%current_lang%types = [ &
283 "integer ", "real ", "complex ", "logical ", &
284 "character ", "double ", "precision ", "int32 ", &
285 "int64 ", "real64 " &
286 ]
287
288 ! Comments
289 highlighter%current_lang%comment_single = "!"
290
291 ! String delimiters
292 allocate(highlighter%current_lang%string_delimiters(2))
293 highlighter%current_lang%string_delimiters = ['"', "'"]
294
295 ! Operators
296 allocate(highlighter%current_lang%operators(20))
297 highlighter%current_lang%operators = [ &
298 "+ ", "- ", "* ", "/ ", "** ", "= ", &
299 "== ", "/= ", "< ", "> ", "<= ", ">= ", &
300 ".and", ".or.", ".not", ".eq.", ".ne.", ".lt.", &
301 ".gt.", ".le." &
302 ]
303
304 highlighter%enabled = .true.
305 end subroutine load_fortran_syntax
306
307 subroutine load_python_syntax(highlighter)
308 type(syntax_highlighter_t), intent(inout) :: highlighter
309
310 highlighter%current_lang%name = "python"
311 highlighter%current_lang%case_sensitive = .true.
312
313 ! Keywords
314 allocate(highlighter%current_lang%keywords(35))
315 highlighter%current_lang%keywords = [ &
316 "def ", "class ", "if ", "elif ", &
317 "else ", "for ", "while ", "break ", &
318 "continue ", "return ", "yield ", "import ", &
319 "from ", "as ", "try ", "except ", &
320 "finally ", "raise ", "with ", "assert ", &
321 "lambda ", "pass ", "del ", "global ", &
322 "nonlocal ", "in ", "is ", "and ", &
323 "or ", "not ", "True ", "False ", &
324 "None ", "async ", "await " &
325 ]
326
327 ! Types
328 allocate(highlighter%current_lang%types(8))
329 highlighter%current_lang%types = [ &
330 "int ", "float ", "str ", "bool ", &
331 "list ", "dict ", "tuple ", "set " &
332 ]
333
334 highlighter%current_lang%comment_single = "#"
335
336 ! String delimiters
337 allocate(highlighter%current_lang%string_delimiters(4))
338 highlighter%current_lang%string_delimiters = ['" ', "' ", '""" ', "''' "]
339
340 ! Operators
341 allocate(highlighter%current_lang%operators(15))
342 highlighter%current_lang%operators = [ &
343 "+ ", "- ", "* ", "/ ", "// ", "% ", "** ", &
344 "= ", "== ", "!= ", "< ", "> ", "<= ", ">= ", &
345 "& " &
346 ]
347
348 highlighter%enabled = .true.
349 end subroutine load_python_syntax
350
351 ! Helper functions
352 function is_alpha(ch) result(res)
353 character(len=1), intent(in) :: ch
354 logical :: res
355 res = (ch >= 'a' .and. ch <= 'z') .or. (ch >= 'A' .and. ch <= 'Z')
356 end function is_alpha
357
358 function is_digit(ch) result(res)
359 character(len=1), intent(in) :: ch
360 logical :: res
361 res = (ch >= '0' .and. ch <= '9')
362 end function is_digit
363
364 function is_alnum(ch) result(res)
365 character(len=1), intent(in) :: ch
366 logical :: res
367 res = is_alpha(ch) .or. is_digit(ch) .or. ch == '_'
368 end function is_alnum
369
370 function is_operator_char(highlighter, ch) result(res)
371 type(syntax_highlighter_t), intent(in) :: highlighter
372 character(len=1), intent(in) :: ch
373 logical :: res
374 integer :: i
375
376 res = .false.
377 if (.not. allocated(highlighter%current_lang%operators)) return
378
379 do i = 1, size(highlighter%current_lang%operators)
380 if (index(trim(highlighter%current_lang%operators(i)), ch) > 0) then
381 res = .true.
382 exit
383 end if
384 end do
385 end function is_operator_char
386
387 function check_comment_start(highlighter, line, pos) result(res)
388 type(syntax_highlighter_t), intent(in) :: highlighter
389 character(len=*), intent(in) :: line
390 integer, intent(in) :: pos
391 logical :: res
392 integer :: comment_len
393
394 res = .false.
395 if (highlighter%current_lang%comment_single /= "") then
396 comment_len = len_trim(highlighter%current_lang%comment_single)
397 if (pos + comment_len - 1 <= len(line)) then
398 res = line(pos:pos+comment_len-1) == trim(highlighter%current_lang%comment_single)
399 end if
400 end if
401 end function check_comment_start
402
403 function check_multiline_comment_start(highlighter, line, pos) result(res)
404 type(syntax_highlighter_t), intent(in) :: highlighter
405 character(len=*), intent(in) :: line
406 integer, intent(in) :: pos
407 logical :: res
408 integer :: comment_len
409
410 res = .false.
411 if (highlighter%current_lang%comment_start /= "") then
412 comment_len = len_trim(highlighter%current_lang%comment_start)
413 if (pos + comment_len - 1 <= len(line)) then
414 res = line(pos:pos+comment_len-1) == trim(highlighter%current_lang%comment_start)
415 end if
416 end if
417 end function check_multiline_comment_start
418
419 function check_string_start(highlighter, line, pos) result(res)
420 type(syntax_highlighter_t), intent(in) :: highlighter
421 character(len=*), intent(in) :: line
422 integer, intent(in) :: pos
423 logical :: res
424 integer :: i, delim_len
425
426 res = .false.
427 if (.not. allocated(highlighter%current_lang%string_delimiters)) return
428
429 do i = 1, size(highlighter%current_lang%string_delimiters)
430 delim_len = len_trim(highlighter%current_lang%string_delimiters(i))
431 if (pos + delim_len - 1 <= len(line)) then
432 if (line(pos:pos+delim_len-1) == trim(highlighter%current_lang%string_delimiters(i))) then
433 res = .true.
434 exit
435 end if
436 end if
437 end do
438 end function check_string_start
439
440 subroutine process_string(highlighter, line, tokens, token_count, pos)
441 type(syntax_highlighter_t), intent(inout) :: highlighter
442 character(len=*), intent(in) :: line
443 type(token_t), intent(inout) :: tokens(:)
444 integer, intent(inout) :: token_count, pos
445 integer :: i, start_pos, delim_len, line_len
446 character(len=:), allocatable :: delimiter
447 logical :: found_end, is_multiline
448
449 line_len = len(line)
450 start_pos = pos
451
452 ! Initialize delimiter (defensive programming - should always be set in loop below)
453 delimiter = '"' ! Default fallback
454
455 ! Find which delimiter matches
456 do i = 1, size(highlighter%current_lang%string_delimiters)
457 delim_len = len_trim(highlighter%current_lang%string_delimiters(i))
458 if (pos + delim_len - 1 <= line_len) then
459 if (line(pos:pos+delim_len-1) == trim(highlighter%current_lang%string_delimiters(i))) then
460 delimiter = trim(highlighter%current_lang%string_delimiters(i))
461 exit
462 end if
463 end if
464 end do
465
466 ! Check if this is a multiline-capable delimiter
467 ! Python: """ or ''' (length 3)
468 ! JavaScript/TypeScript template literals (```) would need length 3
469 ! Single backticks should NOT be multiline (breaks markdown with Ctrl+\`)
470 is_multiline = (len(delimiter) >= 3)
471
472 ! Move past opening delimiter
473 pos = pos + len(delimiter)
474
475 ! Find closing delimiter
476 found_end = .false.
477 do while (pos <= line_len)
478 ! Skip escaped character (but NOT for backticks - markdown inline code is literal)
479 if (line(pos:pos) == '\' .and. pos < line_len .and. delimiter /= '`') then
480 pos = pos + 2
481 else if (pos + len(delimiter) - 1 <= line_len) then
482 if (line(pos:pos+len(delimiter)-1) == delimiter) then
483 pos = pos + len(delimiter)
484 found_end = .true.
485 exit
486 else
487 pos = pos + 1
488 end if
489 else
490 pos = pos + 1
491 end if
492 end do
493
494 ! Add string token
495 token_count = token_count + 1
496 tokens(token_count)%type = TOKEN_STRING
497 tokens(token_count)%start_col = start_pos
498 tokens(token_count)%end_col = min(pos - 1, line_len)
499
500 ! Handle unclosed string
501 if (.not. found_end) then
502 if (is_multiline) then
503 ! Enter multiline string mode
504 highlighter%in_multiline_string = .true.
505 highlighter%string_delimiter = delimiter
506 end if
507 pos = line_len + 1
508 end if
509 end subroutine process_string
510
511 subroutine process_number(line, tokens, token_count, pos)
512 character(len=*), intent(in) :: line
513 type(token_t), intent(inout) :: tokens(:)
514 integer, intent(inout) :: token_count, pos
515 integer :: start_pos
516 logical :: has_dot, has_e
517
518 start_pos = pos
519 has_dot = .false.
520 has_e = .false.
521
522 do while (pos <= len(line))
523 if (is_digit(line(pos:pos))) then
524 pos = pos + 1
525 else if (line(pos:pos) == '.' .and. .not. has_dot .and. .not. has_e) then
526 has_dot = .true.
527 pos = pos + 1
528 else if ((line(pos:pos) == 'e' .or. line(pos:pos) == 'E') .and. .not. has_e) then
529 has_e = .true.
530 pos = pos + 1
531 if (pos <= len(line) .and. (line(pos:pos) == '+' .or. line(pos:pos) == '-')) then
532 pos = pos + 1
533 end if
534 else
535 exit
536 end if
537 end do
538
539 token_count = token_count + 1
540 tokens(token_count)%type = TOKEN_NUMBER
541 tokens(token_count)%start_col = start_pos
542 tokens(token_count)%end_col = pos - 1
543 end subroutine process_number
544
545 subroutine process_word(highlighter, line, tokens, token_count, pos)
546 type(syntax_highlighter_t), intent(in) :: highlighter
547 character(len=*), intent(in) :: line
548 type(token_t), intent(inout) :: tokens(:)
549 integer, intent(inout) :: token_count, pos
550 integer :: start_pos, end_pos, i
551 character(len=:), allocatable :: word
552 logical :: is_keyword, is_type
553
554 start_pos = pos
555
556 ! Find end of word
557 do while (pos <= len(line) .and. is_alnum(line(pos:pos)))
558 pos = pos + 1
559 end do
560 end_pos = pos - 1
561
562 word = line(start_pos:end_pos)
563
564 ! Check if it's a keyword
565 is_keyword = .false.
566 if (allocated(highlighter%current_lang%keywords)) then
567 do i = 1, size(highlighter%current_lang%keywords)
568 if (compare_word(word, highlighter%current_lang%keywords(i), &
569 highlighter%current_lang%case_sensitive)) then
570 is_keyword = .true.
571 exit
572 end if
573 end do
574 end if
575
576 ! Check if it's a type
577 is_type = .false.
578 if (.not. is_keyword .and. allocated(highlighter%current_lang%types)) then
579 do i = 1, size(highlighter%current_lang%types)
580 if (compare_word(word, highlighter%current_lang%types(i), &
581 highlighter%current_lang%case_sensitive)) then
582 is_type = .true.
583 exit
584 end if
585 end do
586 end if
587
588 token_count = token_count + 1
589 tokens(token_count)%start_col = start_pos
590 tokens(token_count)%end_col = end_pos
591
592 if (is_keyword) then
593 tokens(token_count)%type = TOKEN_KEYWORD
594 else if (is_type) then
595 tokens(token_count)%type = TOKEN_TYPE
596 else
597 tokens(token_count)%type = TOKEN_PLAIN
598 end if
599 end subroutine process_word
600
601 function compare_word(word1, word2, case_sensitive) result(match)
602 character(len=*), intent(in) :: word1, word2
603 logical, intent(in) :: case_sensitive
604 logical :: match
605
606 if (case_sensitive) then
607 match = trim(word1) == trim(word2)
608 else
609 match = to_lower(trim(word1)) == to_lower(trim(word2))
610 end if
611 end function compare_word
612
613 function to_lower(str) result(lower_str)
614 character(len=*), intent(in) :: str
615 character(len=len(str)) :: lower_str
616 integer :: i
617
618 lower_str = str
619 do i = 1, len(str)
620 if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then
621 lower_str(i:i) = char(ichar(str(i:i)) + 32)
622 end if
623 end do
624 end function to_lower
625
626 ! C language support
627 subroutine load_c_syntax(highlighter)
628 type(syntax_highlighter_t), intent(inout) :: highlighter
629
630 highlighter%current_lang%name = "c"
631 highlighter%current_lang%case_sensitive = .true.
632
633 ! Keywords
634 allocate(highlighter%current_lang%keywords(32))
635 highlighter%current_lang%keywords = [ &
636 "auto ", "break ", "case ", "char ", &
637 "const ", "continue ", "default ", "do ", &
638 "double ", "else ", "enum ", "extern ", &
639 "float ", "for ", "goto ", "if ", &
640 "inline ", "int ", "long ", "register ", &
641 "return ", "short ", "signed ", "sizeof ", &
642 "static ", "struct ", "switch ", "typedef ", &
643 "union ", "unsigned ", "void ", "while " &
644 ]
645
646 ! Types
647 allocate(highlighter%current_lang%types(8))
648 highlighter%current_lang%types = [ &
649 "size_t ", "uint32_t ", "int32_t ", "uint64_t ", &
650 "int64_t ", "bool ", "FILE ", "NULL " &
651 ]
652
653 highlighter%current_lang%comment_single = "//"
654 highlighter%current_lang%comment_start = "/*"
655 highlighter%current_lang%comment_end = "*/"
656
657 allocate(highlighter%current_lang%string_delimiters(2))
658 highlighter%current_lang%string_delimiters = ['"', "'"]
659
660 allocate(highlighter%current_lang%operators(20))
661 highlighter%current_lang%operators = [ &
662 "+ ", "- ", "* ", "/ ", "% ", "= ", &
663 "== ", "!= ", "< ", "> ", "<= ", ">= ", &
664 "&& ", "|| ", "! ", "& ", "| ", "^ ", &
665 "<< ", ">> " &
666 ]
667
668 highlighter%enabled = .true.
669 end subroutine load_c_syntax
670
671 ! C++ language support
672 subroutine load_cpp_syntax(highlighter)
673 type(syntax_highlighter_t), intent(inout) :: highlighter
674
675 highlighter%current_lang%name = "cpp"
676 highlighter%current_lang%case_sensitive = .true.
677
678 ! Keywords (C++ specific + C keywords)
679 allocate(highlighter%current_lang%keywords(48))
680 highlighter%current_lang%keywords = [ &
681 "auto ", "break ", "case ", "char ", &
682 "const ", "continue ", "default ", "do ", &
683 "double ", "else ", "enum ", "extern ", &
684 "float ", "for ", "goto ", "if ", &
685 "inline ", "int ", "long ", "register ", &
686 "return ", "short ", "signed ", "sizeof ", &
687 "static ", "struct ", "switch ", "typedef ", &
688 "union ", "unsigned ", "void ", "while ", &
689 "class ", "namespace ", "template ", "typename ", &
690 "new ", "delete ", "this ", "friend ", &
691 "virtual ", "override ", "final ", "public ", &
692 "private ", "protected ", "try ", "catch " &
693 ]
694
695 ! Types
696 allocate(highlighter%current_lang%types(12))
697 highlighter%current_lang%types = [ &
698 "std ", "string ", "vector ", "map ", &
699 "set ", "pair ", "unique_ptr ", "shared_ptr ", &
700 "nullptr ", "true ", "false ", "bool " &
701 ]
702
703 highlighter%current_lang%comment_single = "//"
704 highlighter%current_lang%comment_start = "/*"
705 highlighter%current_lang%comment_end = "*/"
706
707 allocate(highlighter%current_lang%string_delimiters(2))
708 highlighter%current_lang%string_delimiters = ['"', "'"]
709
710 allocate(highlighter%current_lang%operators(22))
711 highlighter%current_lang%operators = [ &
712 "+ ", "- ", "* ", "/ ", "% ", "= ", &
713 "== ", "!= ", "< ", "> ", "<= ", ">= ", &
714 "&& ", "|| ", "! ", "& ", "| ", "^ ", &
715 "<< ", ">> ", ":: ", "-> " &
716 ]
717
718 highlighter%enabled = .true.
719 end subroutine load_cpp_syntax
720
721 ! Rust language support
722 subroutine load_rust_syntax(highlighter)
723 type(syntax_highlighter_t), intent(inout) :: highlighter
724
725 highlighter%current_lang%name = "rust"
726 highlighter%current_lang%case_sensitive = .true.
727
728 ! Keywords
729 allocate(highlighter%current_lang%keywords(40))
730 highlighter%current_lang%keywords = [ &
731 "as ", "async ", "await ", "break ", &
732 "const ", "continue ", "crate ", "dyn ", &
733 "else ", "enum ", "extern ", "false ", &
734 "fn ", "for ", "if ", "impl ", &
735 "in ", "let ", "loop ", "match ", &
736 "mod ", "move ", "mut ", "pub ", &
737 "ref ", "return ", "self ", "Self ", &
738 "static ", "struct ", "super ", "trait ", &
739 "true ", "type ", "unsafe ", "use ", &
740 "where ", "while ", "async ", "await " &
741 ]
742
743 ! Types
744 allocate(highlighter%current_lang%types(16))
745 highlighter%current_lang%types = [ &
746 "i8 ", "i16 ", "i32 ", "i64 ", &
747 "i128 ", "u8 ", "u16 ", "u32 ", &
748 "u64 ", "u128 ", "f32 ", "f64 ", &
749 "bool ", "char ", "str ", "String " &
750 ]
751
752 highlighter%current_lang%comment_single = "//"
753 highlighter%current_lang%comment_start = "/*"
754 highlighter%current_lang%comment_end = "*/"
755
756 allocate(highlighter%current_lang%string_delimiters(2))
757 highlighter%current_lang%string_delimiters = ['"', "'"]
758
759 allocate(highlighter%current_lang%operators(20))
760 highlighter%current_lang%operators = [ &
761 "+ ", "- ", "* ", "/ ", "% ", "= ", &
762 "== ", "!= ", "< ", "> ", "<= ", ">= ", &
763 "&& ", "|| ", "! ", "& ", "| ", "^ ", &
764 ":: ", "-> " &
765 ]
766
767 highlighter%enabled = .true.
768 end subroutine load_rust_syntax
769
770 ! Go language support
771 subroutine load_go_syntax(highlighter)
772 type(syntax_highlighter_t), intent(inout) :: highlighter
773
774 highlighter%current_lang%name = "go"
775 highlighter%current_lang%case_sensitive = .true.
776
777 ! Keywords
778 allocate(highlighter%current_lang%keywords(25))
779 highlighter%current_lang%keywords = [ &
780 "break ", "case ", "chan ", "const ", &
781 "continue ", "default ", "defer ", "else ", &
782 "fallthrough ", "for ", "func ", "go ", &
783 "goto ", "if ", "import ", "interface ", &
784 "map ", "package ", "range ", "return ", &
785 "select ", "struct ", "switch ", "type ", &
786 "var " &
787 ]
788
789 ! Types
790 allocate(highlighter%current_lang%types(15))
791 highlighter%current_lang%types = [ &
792 "bool ", "byte ", "complex64 ", "complex128 ", &
793 "error ", "float32 ", "float64 ", "int ", &
794 "int8 ", "int16 ", "int32 ", "int64 ", &
795 "string ", "uint ", "nil " &
796 ]
797
798 highlighter%current_lang%comment_single = "//"
799 highlighter%current_lang%comment_start = "/*"
800 highlighter%current_lang%comment_end = "*/"
801
802 allocate(highlighter%current_lang%string_delimiters(3))
803 highlighter%current_lang%string_delimiters = ['"', "'", '`']
804
805 allocate(highlighter%current_lang%operators(18))
806 highlighter%current_lang%operators = [ &
807 "+ ", "- ", "* ", "/ ", "% ", "= ", &
808 "== ", "!= ", "< ", "> ", "<= ", ">= ", &
809 "&& ", "|| ", "! ", "& ", "| ", ":= " &
810 ]
811
812 highlighter%enabled = .true.
813 end subroutine load_go_syntax
814
815 ! JavaScript language support
816 subroutine load_javascript_syntax(highlighter)
817 type(syntax_highlighter_t), intent(inout) :: highlighter
818
819 highlighter%current_lang%name = "javascript"
820 highlighter%current_lang%case_sensitive = .true.
821
822 ! Keywords
823 allocate(highlighter%current_lang%keywords(38))
824 highlighter%current_lang%keywords = [ &
825 "async ", "await ", "break ", "case ", &
826 "catch ", "class ", "const ", "continue ", &
827 "debugger ", "default ", "delete ", "do ", &
828 "else ", "export ", "extends ", "finally ", &
829 "for ", "function ", "if ", "import ", &
830 "in ", "instanceof ", "let ", "new ", &
831 "return ", "super ", "switch ", "this ", &
832 "throw ", "try ", "typeof ", "var ", &
833 "void ", "while ", "with ", "yield ", &
834 "true ", "false " &
835 ]
836
837 ! Types
838 allocate(highlighter%current_lang%types(10))
839 highlighter%current_lang%types = [ &
840 "null ", "undefined ", "Boolean ", "Number ", &
841 "String ", "Symbol ", "Object ", "Array ", &
842 "Function ", "Promise " &
843 ]
844
845 highlighter%current_lang%comment_single = "//"
846 highlighter%current_lang%comment_start = "/*"
847 highlighter%current_lang%comment_end = "*/"
848
849 allocate(highlighter%current_lang%string_delimiters(3))
850 highlighter%current_lang%string_delimiters = ['"', "'", '`']
851
852 allocate(highlighter%current_lang%operators(20))
853 highlighter%current_lang%operators = [ &
854 "+ ", "- ", "* ", "/ ", "% ", "= ", &
855 "== ", "=== ", "!= ", "!== ", "< ", "> ", &
856 "<= ", ">= ", "&& ", "|| ", "! ", "? ", &
857 ": ", "=> " &
858 ]
859
860 highlighter%enabled = .true.
861 end subroutine load_javascript_syntax
862
863 ! TypeScript language support (extends JavaScript)
864 subroutine load_typescript_syntax(highlighter)
865 type(syntax_highlighter_t), intent(inout) :: highlighter
866
867 ! Start with JavaScript syntax
868 call load_javascript_syntax(highlighter)
869 highlighter%current_lang%name = "typescript"
870
871 ! Add TypeScript-specific keywords
872 deallocate(highlighter%current_lang%keywords)
873 allocate(highlighter%current_lang%keywords(45))
874 highlighter%current_lang%keywords = [ &
875 "async ", "await ", "break ", "case ", &
876 "catch ", "class ", "const ", "continue ", &
877 "debugger ", "default ", "delete ", "do ", &
878 "else ", "export ", "extends ", "finally ", &
879 "for ", "function ", "if ", "import ", &
880 "in ", "instanceof ", "let ", "new ", &
881 "return ", "super ", "switch ", "this ", &
882 "throw ", "try ", "typeof ", "var ", &
883 "void ", "while ", "with ", "yield ", &
884 "true ", "false ", "enum ", "interface ", &
885 "type ", "namespace ", "module ", "declare ", &
886 "abstract " &
887 ]
888
889 ! Add TypeScript types
890 deallocate(highlighter%current_lang%types)
891 allocate(highlighter%current_lang%types(15))
892 highlighter%current_lang%types = [ &
893 "null ", "undefined ", "boolean ", "number ", &
894 "string ", "symbol ", "object ", "any ", &
895 "unknown ", "never ", "void ", "Array ", &
896 "Function ", "Promise ", "ReadonlyArra" &
897 ]
898
899 highlighter%enabled = .true.
900 end subroutine load_typescript_syntax
901
902 ! Bash/Shell script support
903 subroutine load_bash_syntax(highlighter)
904 type(syntax_highlighter_t), intent(inout) :: highlighter
905
906 highlighter%current_lang%name = "bash"
907 highlighter%current_lang%case_sensitive = .true.
908
909 ! Keywords
910 allocate(highlighter%current_lang%keywords(30))
911 highlighter%current_lang%keywords = [ &
912 "if ", "then ", "else ", "elif ", &
913 "fi ", "for ", "while ", "do ", &
914 "done ", "case ", "esac ", "function ", &
915 "return ", "break ", "continue ", "exit ", &
916 "export ", "source ", "alias ", "unset ", &
917 "shift ", "local ", "declare ", "readonly ", &
918 "echo ", "printf ", "read ", "cd ", &
919 "pwd ", "ls " &
920 ]
921
922 ! Built-in variables/types
923 allocate(highlighter%current_lang%types(10))
924 highlighter%current_lang%types = [ &
925 "$0 ", "$1 ", "$@ ", "$* ", &
926 "$# ", "$? ", "$$ ", "$! ", &
927 "true ", "false " &
928 ]
929
930 highlighter%current_lang%comment_single = "#"
931
932 allocate(highlighter%current_lang%string_delimiters(3))
933 highlighter%current_lang%string_delimiters = ['"', "'", '`']
934
935 allocate(highlighter%current_lang%operators(15))
936 highlighter%current_lang%operators = [ &
937 "= ", "== ", "!= ", "< ", "> ", &
938 "-eq ", "-ne ", "-lt ", "-gt ", "-le ", &
939 "-ge ", "&& ", "|| ", "| ", "& " &
940 ]
941
942 highlighter%enabled = .true.
943 end subroutine load_bash_syntax
944
945 ! Markdown support (special handling needed)
946 subroutine load_markdown_syntax(highlighter)
947 type(syntax_highlighter_t), intent(inout) :: highlighter
948
949 highlighter%current_lang%name = "markdown"
950 highlighter%current_lang%case_sensitive = .true.
951
952 ! Headers and emphasis markers as "keywords"
953 allocate(highlighter%current_lang%keywords(8))
954 highlighter%current_lang%keywords = [ &
955 "# ", "## ", "### ", "#### ", &
956 "##### ", "###### ", "* ", "_ " &
957 ]
958
959 ! Code block languages as "types"
960 allocate(highlighter%current_lang%types(10))
961 highlighter%current_lang%types = [ &
962 "``` ", "```python ", "```bash ", "```fortran ", &
963 "```c ", "```cpp ", "```rust ", "```go ", &
964 "```javascri ", "```typescri " &
965 ]
966
967 ! No traditional comments in markdown
968 highlighter%current_lang%comment_single = ""
969
970 ! Links and code spans
971 allocate(highlighter%current_lang%string_delimiters(2))
972 highlighter%current_lang%string_delimiters = ['`', '[']
973
974 ! List markers and special chars
975 allocate(highlighter%current_lang%operators(6))
976 highlighter%current_lang%operators = [ &
977 "- ", "+ ", "* ", "> ", "| ", "! " &
978 ]
979
980 highlighter%enabled = .true.
981 end subroutine load_markdown_syntax
982
983 ! Handle multiline comment start (when we encounter /* on a line)
984 subroutine process_multiline_comment_start(highlighter, line, tokens, token_count, pos)
985 type(syntax_highlighter_t), intent(inout) :: highlighter
986 character(len=*), intent(in) :: line
987 type(token_t), intent(inout) :: tokens(:)
988 integer, intent(inout) :: token_count, pos
989 integer :: start_pos, end_pos, comment_start_len, comment_end_len, line_len
990
991 line_len = len(line)
992 start_pos = pos
993 comment_start_len = len_trim(highlighter%current_lang%comment_start)
994 comment_end_len = len_trim(highlighter%current_lang%comment_end)
995
996 ! Skip past the opening delimiter
997 pos = pos + comment_start_len
998
999 ! Look for closing delimiter on the same line
1000 end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end))
1001
1002 if (end_pos > 0) then
1003 ! Found closing delimiter on same line - this is a complete comment
1004 end_pos = pos + end_pos - 1 + comment_end_len - 1
1005
1006 token_count = token_count + 1
1007 tokens(token_count)%type = TOKEN_COMMENT
1008 tokens(token_count)%start_col = start_pos
1009 tokens(token_count)%end_col = end_pos
1010
1011 pos = end_pos + 1
1012 else
1013 ! Closing delimiter not found - rest of line is comment, enter multiline mode
1014 token_count = token_count + 1
1015 tokens(token_count)%type = TOKEN_COMMENT
1016 tokens(token_count)%start_col = start_pos
1017 tokens(token_count)%end_col = line_len
1018
1019 highlighter%in_multiline_comment = .true.
1020 pos = line_len + 1
1021 end if
1022 end subroutine process_multiline_comment_start
1023
1024 ! Handle multiline comment continuation (when we start a line in comment mode)
1025 subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos)
1026 type(syntax_highlighter_t), intent(inout) :: highlighter
1027 character(len=*), intent(in) :: line
1028 type(token_t), intent(inout) :: tokens(:)
1029 integer, intent(inout) :: token_count, pos
1030 integer :: end_pos, comment_end_len, line_len
1031
1032 line_len = len(line)
1033 comment_end_len = len_trim(highlighter%current_lang%comment_end)
1034
1035 ! Look for closing delimiter
1036 end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end))
1037
1038 if (end_pos > 0) then
1039 ! Found closing delimiter on this line
1040 end_pos = pos + end_pos - 1 + comment_end_len - 1
1041
1042 token_count = token_count + 1
1043 tokens(token_count)%type = TOKEN_COMMENT
1044 tokens(token_count)%start_col = pos
1045 tokens(token_count)%end_col = end_pos
1046
1047 ! Exit multiline comment mode
1048 highlighter%in_multiline_comment = .false.
1049 pos = end_pos + 1
1050 else
1051 ! Closing delimiter not found - entire rest of line is comment
1052 token_count = token_count + 1
1053 tokens(token_count)%type = TOKEN_COMMENT
1054 tokens(token_count)%start_col = pos
1055 tokens(token_count)%end_col = line_len
1056
1057 ! Stay in multiline comment mode
1058 pos = line_len + 1
1059 end if
1060 end subroutine process_multiline_comment
1061
1062 subroutine process_multiline_string(highlighter, line, tokens, token_count, pos)
1063 type(syntax_highlighter_t), intent(inout) :: highlighter
1064 character(len=*), intent(in) :: line
1065 type(token_t), intent(inout) :: tokens(:)
1066 integer, intent(inout) :: token_count, pos
1067 integer :: end_pos, delim_len, line_len
1068
1069 line_len = len(line)
1070 delim_len = len(highlighter%string_delimiter)
1071
1072 ! Look for closing delimiter
1073 end_pos = index(line(pos:), highlighter%string_delimiter)
1074
1075 if (end_pos > 0) then
1076 ! Found closing delimiter on this line
1077 end_pos = pos + end_pos - 1 + delim_len - 1
1078
1079 token_count = token_count + 1
1080 tokens(token_count)%type = TOKEN_STRING
1081 tokens(token_count)%start_col = pos
1082 tokens(token_count)%end_col = end_pos
1083
1084 ! Exit multiline string mode
1085 highlighter%in_multiline_string = .false.
1086 highlighter%string_delimiter = ""
1087 pos = end_pos + 1
1088 else
1089 ! Closing delimiter not found - entire rest of line is string
1090 token_count = token_count + 1
1091 tokens(token_count)%type = TOKEN_STRING
1092 tokens(token_count)%start_col = pos
1093 tokens(token_count)%end_col = line_len
1094
1095 ! Stay in multiline string mode
1096 pos = line_len + 1
1097 end if
1098 end subroutine process_multiline_string
1099
1100 end module syntax_highlighter_module