Hog v9.78.0
hdl_parser.tcl
Go to the documentation of this file.
1 
2 proc token_value {token} { return [dict get $token value ] }
3 proc token_type {token} { return [dict get $token type ] }
4 proc token_line {token} { return [dict get $token line ] }
5 proc token_col {token} { return [dict get $token col ] }
6 
7 proc tokenize {code token_patterns keywords {case_sensitive 1}} {
8  set tokens {}
9  set line 1
10  set line_start 0
11  set cursor 0
12 
13  set keyword_dict [dict create]
14  foreach kw $keywords {
15  dict set keyword_dict $kw 1
16  }
17 
18  set code_len [string length $code]
19  while {$cursor < $code_len} {
20  set found_match 0
21 
22 
23  # this is an attempt at "optimizing" the regex matching by only checking a window of the code
24  # we double the window size if no match is found and try again
25  set window_size 200
26  while {!$found_match && $cursor < $code_len} {
27  set window_end [expr {min($cursor + $window_size - 1, $code_len - 1)}]
28  set window [string range $code $cursor $window_end]
29 
30  foreach item $token_patterns {
31  set type [lindex $item 0]
32  set pattern [lindex $item 1]
33  # set re [string cat {^(} $pattern {)}]
34  set re "^($pattern)"
35 
36  if {[regexp -- $re $window allmatch submatch]} {
37  set value $submatch
38  set match_len [string length $allmatch]
39 
40  set column [expr {$cursor - $line_start + 1}]
41 
42  if {$type == "NEWLINE"} {
43  incr line
44  set line_start [expr {$cursor + $match_len}]
45  } elseif {$type != "WHITESPACE" && $type != "COMMENT"} {
46 
47  if {!$case_sensitive} {
48  set value [string tolower $value]
49  }
50 
51  if {$type == "IDENTIFIER" && [dict exists $keyword_dict $value]} {
52  set type "KEYWORD"
53  }
54  lappend tokens [dict create type $type value $value line $line column $column]
55  }
56 
57  set cursor [expr {$cursor + $match_len}]
58  set found_match 1
59  break
60  }
61  }
62 
63  if {!$found_match && $window_end >= $code_len - 1} {
64  break
65  }
66 
67  if {!$found_match} {
68  set window_size [expr {$window_size * 2}]
69  Msg Warning "No match found at index $cursor, increasing window size to $window_size"
70  }
71  }
72 
73  if {!$found_match} {
74  puts stderr "Error: Tokenizer stuck at index $cursor on char '[string index $code $cursor]'"
75  incr cursor
76  }
77  }
78 
79  return $tokens
80 }
81 
82 
83 # hdl_node
84 # type: "vhdl_entity", "vhdl_architecture", "verilog_module", etc.
85 # name: module name
86 # file_path: path to the source file
87 # line: line number of declaration
88 # entity: name of the parent entity, for arch/body
89 # libraries: [
90 # library_node dict {
91 # name: library name, e.g., "ieee"
92 # uses: list of usesg, e.g., "IEEE.STD_LOGIC_1164.all"
93 # }
94 # ]
95 # components_declared: [
96 # component_node dict {
97 # name: component name
98 # line: line number
99 # }
100 # ]
101 # instantiations: [
102 # instantiation_node dict {
103 # mod_name: name of the instantiated module
104 # type: "entity_inst", "component_inst"
105 # inst_name: instance label
106 # line: line number
107 # }
108 # ]
109 
110 proc create_hdl_node {type name file_path line {libraries ""} {components_declared ""} {instantiations ""} {entity ""}} {
111  return [dict create type $type name $name file_path $file_path line $line \
112  libraries $libraries components_declared $components_declared instantiations $instantiations entity $entity]
113 }
114 
115 proc create_instantiation_node {mod_name type inst_name line} {
116  return [dict create mod_name $mod_name type $type inst_name $inst_name line $line]
117 }
118 
119 proc hdl_node_string {hdl_node} {
120 
121  set node_info "Node Type: [dict get $hdl_node type]"
122  append node_info "\n Name: [dict get $hdl_node name]"
123  append node_info "\n File Path: [dict get $hdl_node file_path]"
124  append node_info "\n Declared on line: [dict get $hdl_node line]"
125  if {[dict exists $hdl_node entity] && [dict get $hdl_node entity] ne ""} {
126  append node_info "\n Entity: [dict get $hdl_node entity]"
127  }
128 
129  if {[dict get $hdl_node type] eq "vhdl_entity" || [dict get $hdl_node type] eq "vhdl_package" \
130  || [dict get $hdl_node type] eq "vhdl_architecture" || [dict get $hdl_node type] eq "vhdl_package_body"} {
131  append node_info "\n Libraries:"
132  set libraries [dict get $hdl_node libraries]
133  if {[llength $libraries] == 0} {
134  append node_info " (None)"
135  } else {
136  foreach lib $libraries {
137  append node_info "\n - Library: [dict get $lib name]"
138  set uses [dict get $lib uses]
139  if {[llength $uses] > 0} {
140  append node_info "\n Uses:"
141  foreach use $uses {
142  append node_info "\n - $use"
143  }
144  }
145  }
146  }
147 
148  if {[dict get $hdl_node type] eq "vhdl_architecture"} {
149  append node_info "\n Components Declared:"
150  set components_declared [dict get $hdl_node components_declared]
151  if {[llength $components_declared] == 0} {
152  append node_info " (None)"
153  } else {
154  foreach comp $components_declared {
155  append node_info "\n - [dict get $comp name] (line [dict get $comp line])"
156  }
157  }
158  append node_info "\n Instantiations:"
159  set instantiations [dict get $hdl_node instantiations]
160  if {[llength $instantiations] == 0} {
161  append node_info " (None)"
162  } else {
163  foreach inst $instantiations {
164  # tclint-disable-next-line line-length
165  append node_info "\n - Name: [dict get $inst mod_name], Instance: [dict get $inst inst_name] type: [dict get $inst type] (line [dict get $inst line])"
166  }
167  }
168  }
169  } elseif {[dict get $hdl_node type] eq "verilog_module"} {
170  append node_info "\n Instantiations:"
171  set instantiations [dict get $hdl_node instantiations]
172  if {[llength $instantiations] == 0} {
173  append node_info " (None)"
174  } else {
175  foreach inst $instantiations {
176  # tclint-disable-next-line line-length
177  append node_info "\n - Name: [dict get $inst mod_name], Instance: [dict get $inst inst_name] type: [dict get $inst type] (line [dict get $inst line])"
178  }
179  }
180  }
181 
182  return $node_info
183 }
184 
185 
186 
187 
188 
189 
190 ########## VERILOG #########
191 
192 set verilog_keywords {
193  always and assign automatic
194  begin buf bufif0 bufif1 case casex
195  casez cell cmos config
196  deassign default defparam design disable
197  edge else end endcase endconfig endfunction endgenerate
198  endmodule endprimitive endspecify endtable endtask event
199  for force forever fork function generate genvar highz0
200  highz1 if ifnone incdir include initial inout input instance integer
201  join large liblist library localparam macromodule medium module nand negedge
202  nmos nor noshowcancelled not notif0 notif1 or output parameter pmos
203  posedge primitive pull0 pull1 pulldown pullup pulsestyle_onevent pulsestyle_ondetect rcmos real
204  realtime reg release repeat rnmos rpmos rtran rtranif0 rtranif1 scalared
205  showcancelled signed small specify specparam strong0 strong1 supply0 supply1 table
206  task time tran tranif0 tranif1 tri tri0 tri1 triand trior
207  trireg unsigned1 use uwire vectored wait wand weak0 weak1 while
208  wire wor xnor xor
209 }
210 
211 set verilog_token_patterns {
212  {COMMENT {//[^\n]*|/\*.*?\*/}}
213  {NEWLINE {\n}}
214  {WHITESPACE {[ \t\r]+}}
215  {STRING {"[^\"]*"}}
216  {NUMBER {\d['\d_hHbsodxza-fA-F]*}}
217  {IDENTIFIER {[a-zA-Z_][a-zA-Z0-9_$]*}}
218  {OPERATOR {[+\-*/%<>=!&|~^?@:]+}}
219  {LPAREN {\(}}
220  {RPAREN {\)}}
221  {LBRACE {\{}}
222  {RBRACE {\}}}
223  {LBRACK {\[}}
224  {RBRACK {\]}}
225  {COMMA {,}}
226  {SEMICOLON {;}}
227  {DOT {\.}}
228  {POUND {#}}
229  {DIRECTIVE {\`[a-zA-Z_][a-zA-Z0-9_]*}}
230  {MISMATCH {.}}
231 }
232 
233 proc tokenize_verilog {code} {
234  global verilog_token_patterns verilog_keywords
235  return [tokenize $code $verilog_token_patterns $verilog_keywords 1]
236 }
237 
238 proc find_verilog_constructs {tokens filename} {
239  set results [list]
240  set state "TOP_LEVEL"
241  set current_module ""
242  set current_module_insts [list]
243 
244  for {set i 0} {$i < [llength $tokens]} {incr i} {
245  set token [lindex $tokens $i]
246  set type [token_type $token]
247  set value [token_value $token]
248 
249  if {$state == "TOP_LEVEL"} {
250  if {$type == "KEYWORD" && $value == "module"} {
251  if {$i + 1 < [llength $tokens]} {
252  set next_token [lindex $tokens [expr {$i + 1}]]
253  if {[token_type $next_token] == "IDENTIFIER"} {
254  set current_module [token_value $next_token]
255  set state "IN_MODULE_HEADER"
256  set current_module_insts [list];
257  }
258  }
259  }
260  } elseif {$state == "IN_MODULE_HEADER"} {
261  if {$type == "SEMICOLON"} {
262  set state "IN_MODULE_BODY"
263  }
264  } elseif {$state == "IN_MODULE_BODY"} {
265  if {$type == "KEYWORD" && $value == "endmodule"} {
266 
267  set decl_node [create_hdl_node "verilog_module" $current_module $filename [dict get $token line] "" "" $current_module_insts]
268  lappend results $decl_node
269 
270  set state "TOP_LEVEL"
271  set current_module ""
272  continue
273  }
274 
275  if {$type == "IDENTIFIER"} {
276  if {$i + 2 < [llength $tokens]} {
277  set token2 [lindex $tokens [expr {$i + 1}]]
278  set token3 [lindex $tokens [expr {$i + 2}]]
279 
280  if {[token_type $token2 ] == "IDENTIFIER" && [token_type $token3] == "LPAREN"} {
281  set inst_dict [create_instantiation_node $value "inst" [token_value $token2] [token_line $token]]
282  lappend current_module_insts $inst_dict
283  } elseif {[token_type $token2] == "POUND" && [token_type $token3] == "LPAREN"} {
284  # find last )
285  set old_token $token
286  incr i 3
287  set depth 1
288  while {$i < [llength $tokens]} {
289  set token [lindex $tokens $i]
290  if {[token_type $token] == "LPAREN" } {
291  incr depth
292  }
293 
294  if {[token_type $token] == "RPAREN" } {
295  incr depth -1
296  if {$depth == 0} {
297  incr i
298  break
299  }
300  }
301  incr i
302  }
303 
304  set token [lindex $tokens $i]
305  if {[token_type $token] == "IDENTIFIER"} {
306  set inst_dict [create_instantiation_node [token_value $old_token] "inst" [token_value $token] [token_line $token]]
307  lappend current_module_insts $inst_dict
308  }
309  }
310  }
311  }
312  }
313  }
314  return $results
315 }
316 
317 
318 ########## VHDL ##########
319 
320 set vhdl_keywords {
321  abs access after alias all and architecture array assert attribute
322  begin block body buffer bus case component configuration constant
323  disconnect downto else elsif end entity exit file for function
324  generate generic group guarded if impure in inertial inout is
325  label library linkage literal loop map mod nand new next nor not
326  null of on open or others out package port postponed procedure
327  process pure range record register reject rem report return rol
328  ror select severity signal shared sla sll sra srl subtype then
329  to transport type unaffected units until use variable wait when
330  while with xnor xor
331 }
332 
333 set vhdl_token_patterns {
334  {COMMENT {--[^\n]*}}
335  {NEWLINE {\n}}
336  {WHITESPACE {[ \t\r]+}}
337  {STRING {"[^"]*"}}
338  {CHAR_LITERAL {'[^']'}}
339  {NUMBER {\d['\d_.]*}}
340  {IDENTIFIER {[a-zA-Z][a-zA-Z0-9_]*}}
341  {OPERATOR {[:=<>|/*&.+-]}}
342  {LPAREN {\(}}
343  {RPAREN {\)}}
344  {COMMA {,}}
345  {SEMICOLON {;}}
346  {MISMATCH {.}}
347 }
348 
349 proc tokenize_vhdl {code} {
350  global vhdl_token_patterns vhdl_keywords
351  return [tokenize $code $vhdl_token_patterns $vhdl_keywords 0]
352 }
353 
354 
355 proc parse_vhdl_architecture_header {tokens index} {
356  set architecture_components [list]
357  set i $index
358  for {set i $index} {$i < [llength $tokens]} {incr i} {
359  set token [lindex $tokens $i]
360  set type [token_type $token]
361  set value [token_value $token]
362 
363  if {$type == "KEYWORD" && $value == "begin"} {
364  break
365  }
366 
367  # Look for component declarations
368  if {$type == "KEYWORD" && $value == "component"} {
369  if {[expr {$i + 2}] < [llength $tokens]} {
370  set comp_name_tok [lindex $tokens [expr {$i + 1}]]
371  set is_tok [lindex $tokens [expr {$i + 2}]]
372 
373  if {[token_type $comp_name_tok] == "IDENTIFIER" &&
374  [token_type $is_tok] == "KEYWORD" && [token_value $is_tok] == "is"} {
375  lappend architecture_components [dict create name [token_value $comp_name_tok] line [token_line $comp_name_tok]]
376  }
377  }
378  }
379  }
380  return [dict create index $i components $architecture_components]
381 }
382 
383 proc parse_vhdl_architecture_body {tokens index arch_name} {
384  set architecture_insts [list]
385  set i $index
386  for {set i $index} {$i < [llength $tokens]} {incr i} {
387  set token [lindex $tokens $i]
388  set type [token_type $token]
389  set value [token_value $token]
390 
391  if {$type == "KEYWORD" && $value == "end"} {
392  if {$i + 2 < [llength $tokens]} {
393  set token2 [lindex $tokens [expr {$i + 1}]]
394  set token3 [lindex $tokens [expr {$i + 2}]]
395  if { [token_value $token2] == "$arch_name" && [token_type $token3] == "SEMICOLON"} {
396  break
397  }
398  }
399  }
400 
401  # Look for instantiations in the concurrent part
402  if {$type == "IDENTIFIER"} {
403  if {$i + 2 < [llength $tokens]} {
404  set inst_tok $token
405  set token2 [lindex $tokens [expr {$i + 1}]]
406  set token3 [lindex $tokens [expr {$i + 2}]]
407  # Entity instantiation: INST_NAME : entity ENTITY_NAME ...
408  if {[token_value $token2] == ":" && [token_value $token3] == "entity"} {
409  incr i 3
410  set entity_inst_name ""
411  while {$i < [llength $tokens]} {
412  set token [lindex $tokens $i]
413  set type [token_type $token]
414  set value [token_value $token]
415 
416  if {$value == "generic" || $value == "port" || $type == "SEMICOLON"} {
417  break
418  }
419  set entity_inst_name "${entity_inst_name}${value}"
420  incr i
421  }
422  if {$entity_inst_name != ""} {
423  lappend architecture_insts [create_instantiation_node $entity_inst_name "entity_inst" [token_value $inst_tok] [token_line $inst_tok]]
424  }
425  # Component instantiation: INST_NAME : COMPONENT COMPONENT_NAME ...
426  } elseif {[token_value $token2] == ":" && [token_value $token3] == "component"} {
427  set token4 [lindex $tokens [expr {$i + 3}]]
428  set comp_inst_name [token_value $token4]
429  lappend architecture_insts [create_instantiation_node $comp_inst_name "component_inst" [token_value $token] [token_line $token]]
430 
431  # Component instantiation: INST_NAME : COMPONENT_NAME ...
432  } elseif {[token_value $token2] == ":" && [token_type $token3] == "IDENTIFIER"} {
433  set is_instantiation 0
434  # look for port or generic followed by map after token3
435  for {set k [expr {$i + 3}]} {$k < [llength $tokens]} {incr k} {
436  set check_token [lindex $tokens $k]
437  set check_value [token_value $check_token]
438  set check_type [token_type $check_token]
439 
440  if {$check_type == "SEMICOLON"} {
441  break
442  }
443  if {$check_type == "KEYWORD" && ($check_value eq "port" || $check_value eq "generic")} {
444  if {[expr {$k + 1}] < [llength $tokens]} {
445  set next_token [lindex $tokens [expr {$k + 1}]]
446  if {[token_value $next_token] eq "map"} {
447  set is_instantiation 1
448  break
449  }
450  }
451  }
452  }
453 
454  if {$is_instantiation} {
455  set comp_inst_name [token_value $token3]
456  lappend architecture_insts [create_instantiation_node $comp_inst_name "component_inst" [token_value $inst_tok] [token_line $inst_tok]]
457  }
458  }
459  }
460  }
461  }
462  return [dict create index $i insts $architecture_insts]
463 }
464 
465 proc parse_vhdl_architecture_content {arch tokens index} {
466  set i $index
467 
468  set header_info [parse_vhdl_architecture_header $tokens $i]
469  set i [dict get $header_info index]
470  set architecture_components [dict get $header_info components]
471 
472  if {$i < [llength $tokens] && [token_value [lindex $tokens $i]] eq "begin"} {
473  incr i
474  }
475  set body_info [parse_vhdl_architecture_body $tokens $i $arch]
476  set i [dict get $body_info index]
477  set architecture_insts [dict get $body_info insts]
478 
479  return [dict create index $i components $architecture_components insts $architecture_insts]
480 }
481 
482 proc skip_vhdl_package_spec {tokens index} {
483  set i $index
484  while {$i < [llength $tokens]} {
485  set current_tok [lindex $tokens $i]
486  set current_val [token_value $current_tok]
487  if {$current_val eq "end"} {
488  if {[expr {$i + 1} < [llength $tokens]]} {
489  set next_token [lindex $tokens [expr {$i + 1}]]
490  if {[token_value $next_token] eq "package"} {
491  set i [expr {$i + 1}]
492  break
493  }
494  }
495  }
496  incr i
497  }
498  return $i
499 }
500 
501 proc skip_vhdl_package_body {tokens index} {
502  set i $index
503  while {$i < [llength $tokens]} {
504  set current_tok [lindex $tokens $i]
505  set current_val [token_value $current_tok]
506  if {$current_val eq "end"} {
507  if {[expr {$i + 2} < [llength $tokens]]} {
508  set next_token [lindex $tokens [expr {$i + 1}]]
509  set next_next_token [lindex $tokens [expr {$i + 2}]]
510  if {[token_value $next_token] eq "package" && [token_value $next_next_token] eq "body"} {
511  set i [expr {$i + 2}]
512  break
513  }
514  }
515  }
516  incr i
517  }
518  return $i
519 }
520 
521 proc find_vhdl_constructs {tokens filename} {
522  set results [list]
523  set libraries_map [dict create]
524 
525  for {set i 0} {$i < [llength $tokens]} {incr i} {
526  set token [lindex $tokens $i]
527  set type [token_type $token]
528  set value [token_value $token]
529 
530  if {$type == "KEYWORD" && $value == "library"} {
531  set i [expr {$i + 1}]
532  while {$i < [llength $tokens]} {
533  set token [lindex $tokens $i]
534  set type [token_type $token]
535  set value [token_value $token]
536 
537  if {$type == "IDENTIFIER"} {
538  set lib_name [string tolower $value]
539  if {![dict exists $libraries_map $lib_name]} {
540  dict set libraries_map $lib_name [list]
541  }
542  } elseif {$type == "SEMICOLON"} {
543  break
544  }
545  incr i
546  }
547  } elseif {$type == "KEYWORD" && $value == "use"} {
548  if {$i + 1 < [llength $tokens]} {
549  set use_path_start_idx [expr {$i + 1}]
550  set use_path ""
551  for {set j $use_path_start_idx} {$j < [llength $tokens]} {incr j} {
552  set use_token [lindex $tokens $j]
553  if {[token_type $use_token] == "SEMICOLON"} {
554  set i $j
555  break
556  }
557  append use_path [token_value $use_token]
558  }
559  if {$use_path != ""} {
560  set use_path_parts [split $use_path .]
561  set lib_name [string tolower [lindex $use_path_parts 0]]
562  if {![dict exists $libraries_map $lib_name]} {
563  dict set libraries_map $lib_name [list]
564  }
565  dict lappend libraries_map $lib_name $use_path
566  }
567  }
568  } elseif {$type == "KEYWORD" && $value == "entity"} {
569  if {$i + 2 < [llength $tokens]} {
570  set name_tok [lindex $tokens [expr {$i + 1}]]
571  set is_tok [lindex $tokens [expr {$i + 2}]]
572  if {[token_type $name_tok] == "IDENTIFIER" && [token_value $is_tok] == "is"} {
573  set entity_name [token_value $name_tok]
574  set final_libraries [list]
575  dict for {lib_name use_paths} $libraries_map {
576  lappend final_libraries [dict create name $lib_name uses $use_paths]
577  }
578  set entity_node [create_hdl_node "vhdl_entity" $entity_name $filename [token_line $name_tok] $final_libraries]
579  lappend results $entity_node
580  }
581  }
582  } elseif {$type == "KEYWORD" && $value == "package"} {
583  if {[expr {$i + 1} < [llength $tokens]]} {
584  set next_token [lindex $tokens [expr {$i + 1}]]
585 
586  if {[token_type $next_token] == "IDENTIFIER"} {
587  set package_name [token_value $next_token]
588  set package_line [token_line $next_token]
589  set i [expr {$i + 2}]
590 
591  if {$i < [llength $tokens]} {
592  set current_tok [lindex $tokens $i]
593  set current_val [token_value $current_tok]
594  if {[token_type $current_tok] == "KEYWORD" && $current_val eq "is"} {
595  # package declaration
596  set i [skip_vhdl_package_spec $tokens $i]
597  set final_libraries [list]
598  dict for {lib_name use_paths} $libraries_map {
599  lappend final_libraries [dict create name $lib_name uses $use_paths]
600  }
601  set package_node [create_hdl_node "vhdl_package" $package_name $filename $package_line $final_libraries]
602  lappend results $package_node
603  continue
604  }
605  }
606  } elseif {[token_value $next_token] == "body" } {
607  # package body
608  set i [skip_vhdl_package_body $tokens $i]
609  set final_libraries [list]
610  dict for {lib_name use_paths} $libraries_map {
611  lappend final_libraries [dict create name $lib_name uses $use_paths]
612  }
613  set package_body_node [create_hdl_node "vhdl_package_body" $package_name $filename $package_line $final_libraries]
614  lappend results $package_body_node
615  continue
616  }
617  }
618  } elseif {$type == "KEYWORD" && $value == "architecture"} {
619  if {$i + 4 < [llength $tokens]} {
620  set arch_name_tok [lindex $tokens [expr {$i + 1}]]
621  set of_tok [lindex $tokens [expr {$i + 2}]]
622  set entity_name_tok [lindex $tokens [expr {$i + 3}]]
623  set is_tok [lindex $tokens [expr {$i + 4}]]
624 
625  if {[token_type $arch_name_tok] == "IDENTIFIER" && [token_value $of_tok] == "of" \
626  && [token_type $entity_name_tok] == "IDENTIFIER" && [token_value $is_tok] == "is"} {
627  set arch_name [token_value $arch_name_tok]
628  set entity_name [token_value $entity_name_tok]
629 
630  set arch_info [parse_vhdl_architecture_content $arch_name $tokens [expr $i + 1]]
631  set i [dict get $arch_info index]
632  set components [dict get $arch_info components]
633  set insts [dict get $arch_info insts]
634  set final_libraries [list]
635  dict for {lib_name use_paths} $libraries_map {
636  lappend final_libraries [dict create name $lib_name uses $use_paths]
637  }
638 
639  set arch_node [create_hdl_node "vhdl_architecture" $arch_name $filename [token_line $arch_name_tok] $final_libraries $components $insts $entity_name]
640  lappend results $arch_node
641  }
642  }
643  }
644  }
645  return $results
646 }
647 
648 
649 proc parse_hdl_file {filename} {
650 
651  if {![file exists $filename]} {
652  puts "Error: file not found: $filename"
653  return ""
654  }
655 
656  set fp [open $filename r]
657  set code [read $fp]
658  close $fp
659 
660 
661  # skip protected files
662  set first_line [lindex [split $code "\n"] 0]
663  set first_line_trimmed [string trim $first_line]
664  if {$first_line_trimmed eq "`pragma protect begin_protected" ||
665  $first_line_trimmed eq "`protect begin_protected"} {
666  return {}
667  }
668 
669  set extension [string tolower [file extension $filename]]
670 
671  set tokens {}
672  set constructs {}
673 
674  switch -- $extension {
675  ".v" -
676  ".vh" -
677  ".sv" {
678  set t_tokenize [time {set tokens [tokenize_verilog $code]} 1]
679  set t_constructs [time {set constructs [find_verilog_constructs $tokens $filename]} 1]
680  }
681  ".vhd" -
682  ".vhdl" {
683  set t_tokenize [time {set tokens [tokenize_vhdl $code]} 1]
684  set t_constructs [time {set constructs [find_vhdl_constructs $tokens $filename]} 1]
685  }
686  default {
687  return {}
688  }
689  }
690 
691  # Extract microseconds and convert to milliseconds
692  set tokenize_us [lindex $t_tokenize 0]
693  set constructs_us [lindex $t_constructs 0]
694  set tokenize_ms [expr {$tokenize_us / 1000.0}]
695  set constructs_ms [expr {$constructs_us / 1000.0}]
696 
697  #puts "\[PERFORMACE\] Tokenization: $tokenize_ms ms, Construct discovery: $constructs_ms ms for file $filename"
698 
699  return $constructs
700 }