Fix DSN tokenizer to accept KiCad net names like /*52
Specctra's SpecCharASCII includes / and *, and an Identifier may start with /, so KiCad emits hierarchical net names such as /*52 and /53. The tokenizer treated any /* as a block-comment start and raised 'unterminated comment' when no */ followed — rejecting real KiCad DSN. Match FreeRouting's JFlex rule-order resolution: /* is a comment only when a closing */ exists; otherwise it is an ordinary name run. Validated against a KiCad 10.0.4 pcbnew-exported DSN (78 nets incl. /*52, /53).
This commit is contained in:
parent
07a9433f19
commit
c905032a39
@ -126,13 +126,19 @@ def tokenize(text: str, quote_chars: str = _QUOTES) -> list[Token]:
|
|||||||
eol = text.find("\n", i)
|
eol = text.find("\n", i)
|
||||||
i = n if eol == -1 else eol
|
i = n if eol == -1 else eol
|
||||||
continue
|
continue
|
||||||
if ch == "/" and i + 1 < n and text[i + 1] == "*": # block comment
|
# `/* ... */` is a block comment ONLY when a closing `*/` exists.
|
||||||
|
# SpecCharASCII includes `/` and `*`, and an Identifier may start with
|
||||||
|
# `/`, so KiCad emits net names like `/*52`. FreeRouting's JFlex scanner
|
||||||
|
# resolves the ambiguity by longest-match / rule-order: with no closing
|
||||||
|
# `*/`, the comment rule fails and the run is read as a name. Mirror
|
||||||
|
# that — an unclosed `/*` is an ordinary token, not an error.
|
||||||
|
if ch == "/" and i + 1 < n and text[i + 1] == "*":
|
||||||
end = text.find("*/", i + 2)
|
end = text.find("*/", i + 2)
|
||||||
if end == -1:
|
if end != -1:
|
||||||
raise DsnSyntaxError(f"unterminated /* */ comment at line {line}")
|
|
||||||
line += text.count("\n", i, end)
|
line += text.count("\n", i, end)
|
||||||
i = end + 2
|
i = end + 2
|
||||||
continue
|
continue
|
||||||
|
# no closing `*/` — fall through and read `/*...` as a normal token
|
||||||
|
|
||||||
# Brackets -----------------------------------------------------------
|
# Brackets -----------------------------------------------------------
|
||||||
if ch == "(":
|
if ch == "(":
|
||||||
|
|||||||
@ -117,9 +117,16 @@ def test_unterminated_string_raises():
|
|||||||
tokenize('(a "no end')
|
tokenize('(a "no end')
|
||||||
|
|
||||||
|
|
||||||
def test_unterminated_block_comment_raises():
|
def test_unclosed_block_comment_is_name_token():
|
||||||
with pytest.raises(DsnSyntaxError):
|
# KiCad emits hierarchical net names like `/*52` (SpecCharASCII includes
|
||||||
tokenize("(a /* no end")
|
# `/` and `*`). With no closing `*/`, `/*...` is a name run, not an
|
||||||
|
# unterminated comment — matching FreeRouting's JFlex rule-order resolution.
|
||||||
|
assert texts("(net /*52)") == ["(", "net", "/*52", ")"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_closed_block_comment_is_stripped():
|
||||||
|
# A properly closed `/* ... */` is still a comment and gets dropped.
|
||||||
|
assert texts("(a /* c */ b)") == ["(", "a", "b", ")"]
|
||||||
|
|
||||||
|
|
||||||
def test_hyphenated_pin_ref_is_single_atom():
|
def test_hyphenated_pin_ref_is_single_atom():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user