|
3 | 3 | import warnings |
4 | 4 | import linecache |
5 | 5 | import re |
| 6 | +import unicodedata |
6 | 7 | from collections import OrderedDict |
7 | 8 | from collections.abc import Iterable |
8 | 9 |
|
9 | 10 |
|
10 | 11 | __all__ = ["flatten", "union", "final", "deprecated", "get_linter_options", |
11 | | - "get_linter_option"] |
| 12 | + "get_linter_option", "validate_name"] |
12 | 13 |
|
13 | 14 |
|
14 | 15 | def flatten(i): |
@@ -101,3 +102,25 @@ def get_linter_option(filename, name, type, default): |
101 | 102 | except ValueError: |
102 | 103 | return default |
103 | 104 | assert False |
| 105 | + |
| 106 | +_invalid_categories = { |
| 107 | + "Zs", # space separators |
| 108 | + "Zl", # line separators |
| 109 | + "Zp", # paragraph separators |
| 110 | + "Cc", # control codepoints (e.g. \0) |
| 111 | + "Cs", # UTF-16 surrogate pair codepoints (no thanks WTF-8) |
| 112 | + "Cn", # unassigned codepoints |
| 113 | +} |
| 114 | + |
| 115 | +def validate_name(name, what, none_ok=False, empty_ok=False): |
| 116 | + if not isinstance(name, str): |
| 117 | + if name is None and none_ok: return |
| 118 | + raise TypeError(f"{what} must be a string, not {name!r}") |
| 119 | + if name == "" and not empty_ok: |
| 120 | + raise NameError(f"{what} must be a non-empty string") |
| 121 | + |
| 122 | + # RTLIL allows bytes >= 33. In the same spirit we allow all characters that |
| 123 | + # Unicode does not declare as separator or control. |
| 124 | + for c in name: |
| 125 | + if unicodedata.category(c) in _invalid_categories: |
| 126 | + raise NameError(f"{what} {name!r} contains whitespace/control character {c!r}") |
0 commit comments