import re
import subprocess
import sys
import tempfile
from pathlib import Path
GRAMMAR = "src/main/resources/com/puppycrawl/tools/checkstyle/grammar/java.g"
def generate(checkout: Path, jar: str, out: Path) -> None:
cmd = ["java", "-cp", jar, "antlr.Tool", "-o", str(out), str(checkout / GRAMMAR)]
subprocess.run(cmd, check=True, capture_output=True)
def token_types(out: Path) -> list[tuple[str, int]]:
pairs = []
for line in (out / "GeneratedJavaTokenTypes.txt").read_text().splitlines():
m = re.fullmatch(r'(\w+)(?:="[^"]*")?=(\d+)', line.strip())
if m:
pairs.append((m.group(1), int(m.group(2))))
return pairs
def token_names(java: str) -> list[str]:
body = java[java.index("_tokenNames = {") :]
body = body[: body.index("};")]
return [bytes(s, "ascii").decode("unicode_escape") for s in re.findall(r'"((?:[^"\\]|\\.)*)"', body)]
def long_arrays(java: str) -> dict[int, list[int]]:
sets = {}
for m in re.finditer(r"mk_tokenSet_(\d+)\(\) \{(.*?)return data;", java, re.S):
body = m.group(2)
size_m = re.search(r"new long\[(\d+)\]", body)
if size_m:
data = [0] * int(size_m.group(1))
for i, v in re.findall(r"data\[(\d+)\]=(-?\d+)L;", body):
data[int(i)] = int(v) & (2**64 - 1)
for lo, hi, v in re.findall(r"for \(int i = (\d+); i<=(\d+); i\+\+\) \{ data\[i\]=(-?\d+)L; \}", body):
for i in range(int(lo), int(hi) + 1):
data[i] = int(v) & (2**64 - 1)
else:
init = re.search(r"long\[\] data = \{([^}]*)\}", body).group(1)
data = [int(v.strip().rstrip("L")) & (2**64 - 1) for v in init.split(",")]
sets[int(m.group(1))] = data
return sets
def char_ranges(words: list[int]) -> list[tuple[int, int]]:
ranges, start = [], None
for c in range(0x10000):
member = c // 64 < len(words) and (words[c // 64] >> (c % 64)) & 1
if member and start is None:
start = c
if not member and start is not None:
ranges.append((start, c - 1))
start = None
if start is not None:
ranges.append((start, 0xFFFF))
return ranges
def main() -> None:
checkout = Path(sys.argv[1])
jar = sys.argv[2] if len(sys.argv) > 2 else "/usr/share/java/antlr.jar"
with tempfile.TemporaryDirectory() as tmp:
out = Path(tmp)
generate(checkout, jar, out)
parser = (out / "GeneratedJavaRecognizer.java").read_text()
lexer = (out / "GeneratedJavaLexer.java").read_text()
types = token_types(out)
names = token_names(parser)
literals = sorted(
(word, int(t)) for word, t in re.findall(r'literals\.put\(new ANTLRHashString\("(\w+)", this\), new Integer\((\d+)\)\);', lexer)
)
parser_sets = long_arrays(parser)
lexer_sets = long_arrays(lexer)
w = sys.stdout.write
w("//! Generated by scripts/gen-icheckstyle-tables.py from checkstyle 8.36.1's\n")
w("//! `java.g` (ANTLR 2.7.7 output): token types and names, the keyword\n")
w("//! table, and the lookahead sets of the recognizer and the lexer. Do not edit.\n\n")
w("use super::lexer::CharSet;\nuse super::parser::TokenSet;\n\n")
for name, value in types:
w(f"pub const {name}: u16 = {value};\n")
w(f"\n/// The recognizer's `_tokenNames`, as its error messages print them.\npub const TOKEN_NAMES: [&str; {len(names)}] = [\n")
for n in names:
escaped = n.replace("\\", "\\\\").replace('"', '\\"')
w(f' "{escaped}",\n')
w("];\n\n/// The lexer's literals table: identifiers that are keywords.\n")
w(f"pub const LITERALS: [(&str, u16); {len(literals)}] = [\n")
for word, t in literals:
w(f' ("{word}", {t}),\n')
w("];\n\n")
for i in sorted(parser_sets):
words = (parser_sets[i] + [0] * 4)[:4]
if any(parser_sets[i][4:]):
raise ValueError(f"parser token set {i} has types above 255")
hex = [f"0x{x >> 48:04x}_{(x >> 32) & 0xFFFF:04x}_{(x >> 16) & 0xFFFF:04x}_{x & 0xFFFF:04x}" for x in words]
w(f"pub const SET_{i}: TokenSet = TokenSet([{', '.join(hex)}]);\n")
w("\n")
for i in sorted(lexer_sets):
ranges = char_ranges(lexer_sets[i])
body = ", ".join(f"(0x{lo:04x}, 0x{hi:04x})" for lo, hi in ranges)
w(f"pub const CHARS_{i}: CharSet = CharSet(&[{body}]);\n")
if __name__ == "__main__":
main()