Regex vs. AST: Lessons from SystemVerilog Coverage Linting
Why Regex Is the Wrong Abstraction for SystemVerilog Coverage Linting
When building a lint rule for SystemVerilog functional coverage, it is tempting to start with a regular expression.
The problem can initially appear simple: find array-based coverpoint bins, calculate how many bins they expand into, and report a violation if the count exceeds the supported limit.
A first implementation might read the source file, remove comments, and search for patterns such as bins foo[] = {[0:2048]};.
As a first-cut implementation, this is perfectly reasonable. It is small, easy to prototype, and useful for validating the basic rule logic.
The problem is that regular expressions operate on source text, while a lint rule needs to reason about language structure.
Consider:
bins values[] =
{
[0:2048],
[3000:4000]
};
The rule now needs to understand that this is one array-bin declaration containing multiple ranges, and calculate the expansion represented by those ranges. A regular expression that matches one particular textual form quickly becomes a collection of special cases attempting to reconstruct information that the SystemVerilog parser already understands.
This leads to the first major problem: false negatives. A regex may correctly recognize the examples it was written for, while silently missing valid SystemVerilog expressed using a different but legal syntactic form.
Whitespace, line breaks, multiple ranges, expressions, and other valid language constructs can all cause a source-text pattern to stop matching.
Comment handling illustrates another issue. A first implementation might use something like:
text = re.sub(r"//.*", "", text)
This looks convenient, but source code is not simply text with comments removed. Strings and other lexical constructs can contain characters that resemble comment syntax. Once a lint rule starts implementing its own lexical processing, it is effectively beginning to duplicate functionality that belongs to the language lexer and parser.
The deeper issue is that the rule is fundamentally a semantic and structural problem.
Conceptually, what the rule wants to process is:
array bin declaration
├── range
│ ├── lower bound
│ └── upper bound
└── range
├── lower bound
└── upper bound
That is precisely the type of information a parser and syntax tree provide.
If the lint infrastructure is already based on Verible, the natural implementation is therefore:
SystemVerilog source
↓
Verible parser
↓
syntax tree / AST
↓
identify array-bin declaration
↓
inspect ranges
↓
calculate expansion
↓
report violation
This approach allows the rule to work with the structure of the language rather than attempting to rediscover that structure from raw text.
This does not mean regular expressions have no place in lint tooling. Regex is useful for genuinely lexical tasks, simple textual conventions, filtering already-isolated text, and rapid prototypes.
The important engineering distinction is:
Use regular expressions to recognize text. Use a parser to understand code.
A regex-based implementation can therefore be a useful first step. It helps validate the rule concept, threshold, diagnostic, and initial test cases. But as the rule moves toward production quality, the implementation should make use of the parser and AST APIs already available in the linting infrastructure.
For SystemVerilog functional coverage linting, the parser has already done the difficult work of understanding the language. The lint rule should take advantage of that work rather than trying to build a small, incomplete parser of its own.
Comments
Post a Comment