670 lines
23 KiB
Python
670 lines
23 KiB
Python
"""Error-driven фиксы по логу javac: волна boolean-кастов и private→package-private."""
|
||
|
||
import re, sys, os, collections
|
||
|
||
LOG = sys.argv[1]
|
||
WAVES = sys.argv[2] if len(sys.argv) > 2 else "ab"
|
||
|
||
HDR = re.compile(r"^(\S+\.java):(\d+): error: (.+)$")
|
||
|
||
|
||
def load_errors(path):
|
||
errs = []
|
||
lines = open(path, encoding="utf-8", errors="replace").read().splitlines()
|
||
i = 0
|
||
while i < len(lines):
|
||
m = HDR.match(lines[i])
|
||
if m:
|
||
col = None
|
||
srcline = None
|
||
if i + 2 < len(lines) and lines[i + 2].lstrip().startswith("^"):
|
||
srcline = lines[i + 1]
|
||
col = len(lines[i + 2]) - len(lines[i + 2].lstrip()) + 1
|
||
extra = []
|
||
j = i + 1
|
||
while j < len(lines) and j < i + 8 and lines[j].strip():
|
||
if (
|
||
lines[j]
|
||
.lstrip()
|
||
.startswith(("symbol:", "location:", "required:", "found:"))
|
||
):
|
||
extra.append(lines[j].strip())
|
||
j += 1
|
||
errs.append(
|
||
(
|
||
m.group(1),
|
||
int(m.group(2)),
|
||
col,
|
||
srcline,
|
||
m.group(3) + (" | " + " | ".join(extra) if extra else ""),
|
||
)
|
||
)
|
||
i += 3
|
||
else:
|
||
i += 1
|
||
return errs
|
||
|
||
|
||
OPS_STOP = set("+-*/%&|^<>!=?:,")
|
||
|
||
|
||
def scan_end(line, start):
|
||
"""Конец primary-выражения, начиная с start (0-based). None = не найден."""
|
||
depth = 0
|
||
i = start
|
||
n = len(line)
|
||
first = True
|
||
while i < n:
|
||
c = line[i]
|
||
if c in "([{":
|
||
depth += 1
|
||
elif c in ")]}":
|
||
if depth == 0:
|
||
break
|
||
depth -= 1
|
||
elif depth == 0 and not first and (c in OPS_STOP or c == ";"):
|
||
break
|
||
elif depth == 0 and c == ")":
|
||
break
|
||
first = False
|
||
i += 1
|
||
return i
|
||
|
||
|
||
def scan_multiline(lines, ln, start):
|
||
"""Возвращает (li, idx, ok): конец operand, начиная с lines[ln-1][start]."""
|
||
depth = 0
|
||
first = True
|
||
li = ln - 1
|
||
i = start
|
||
guard = 0
|
||
while li < len(lines) and guard < 400:
|
||
guard += 1
|
||
line = lines[li]
|
||
while i < len(line):
|
||
c = line[i]
|
||
if c in "([{":
|
||
depth += 1
|
||
elif c in ")]}":
|
||
if depth == 0:
|
||
return li, i, True
|
||
depth -= 1
|
||
elif depth == 0 and not first and (c in OPS_STOP or c == ";"):
|
||
return li, i, True
|
||
first = False
|
||
i += 1
|
||
li += 1
|
||
i = 0
|
||
return li, i, False
|
||
|
||
|
||
def wave_a(errs, stats, i2b=True, b2i=True):
|
||
per_file = collections.defaultdict(list)
|
||
for f, ln, col, srcline, msg in errs:
|
||
if i2b and "int cannot be converted to boolean" in msg and srcline and col:
|
||
per_file[f].append((ln, col, srcline, "i2b"))
|
||
elif b2i and "boolean cannot be converted to int" in msg and srcline and col:
|
||
per_file[f].append((ln, col, srcline, "b2i"))
|
||
for f, items in per_file.items():
|
||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||
by_line = collections.defaultdict(list)
|
||
for ln, col, srcline, kind in items:
|
||
by_line[ln].append((col, kind))
|
||
touched = False
|
||
for ln, cols in sorted(by_line.items(), reverse=True):
|
||
if ln - 1 >= len(lines):
|
||
continue
|
||
i2b_done = False
|
||
for col, kind in sorted(cols, reverse=True):
|
||
if kind != "i2b":
|
||
continue
|
||
if i2b_done:
|
||
stats["i2b_dup_line"] += 1
|
||
continue
|
||
pos = col - 1
|
||
# найти (boolean): на этой строке перед колонкой, иначе выше до 3 строк
|
||
cast_line, cast_pos = None, None
|
||
for back in range(0, 4):
|
||
bl = ln - 1 - back
|
||
if bl < 0:
|
||
break
|
||
hay = lines[bl]
|
||
limit = pos + 1 if back == 0 else len(hay)
|
||
cp = hay.rfind("(boolean)", 0, limit)
|
||
if cp != -1:
|
||
cast_line, cast_pos = bl, cp
|
||
break
|
||
if cast_line is None:
|
||
stats["i2b_no_cast"] += 1
|
||
continue
|
||
if cast_line == ln - 1:
|
||
op_start = cast_pos + len("(boolean)")
|
||
while (
|
||
op_start < len(lines[cast_line])
|
||
and lines[cast_line][op_start] == " "
|
||
):
|
||
op_start += 1
|
||
else:
|
||
# cast на предыдущей строке: operand начинается в начале строки ln
|
||
op_start = 0
|
||
cast_line = ln - 1
|
||
while (
|
||
op_start < len(lines[cast_line])
|
||
and lines[cast_line][op_start] == " "
|
||
):
|
||
op_start += 1
|
||
end_li, end_i, ok = scan_multiline(lines, ln, op_start)
|
||
if not ok:
|
||
stats["i2b_no_end"] += 1
|
||
continue
|
||
# собрать span
|
||
if end_li == ln - 1:
|
||
span = lines[ln - 1][op_start:end_i]
|
||
new_line = (
|
||
lines[ln - 1][:op_start]
|
||
+ "(("
|
||
+ span
|
||
+ ") != 0)"
|
||
+ lines[ln - 1][end_i:]
|
||
)
|
||
lines[ln - 1] = new_line
|
||
else:
|
||
span = (
|
||
lines[ln - 1][op_start:]
|
||
+ "".join(lines[ln:end_li])
|
||
+ lines[end_li][:end_i]
|
||
)
|
||
tail = lines[end_li][end_i:]
|
||
head = lines[ln - 1][:op_start]
|
||
merged = head + "((" + span + ") != 0)" + tail
|
||
if not merged.endswith("\n"):
|
||
merged += "\n"
|
||
del lines[ln - 1 : end_li + 1]
|
||
lines.insert(ln - 1, merged)
|
||
stats["i2b"] += 1
|
||
touched = True
|
||
i2b_done = True
|
||
if touched:
|
||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||
# b2i оставлен на потом
|
||
if b2i:
|
||
stats["b2i_skipped"] = sum(
|
||
1 for x in errs if "boolean cannot be converted to int" in x[4]
|
||
)
|
||
|
||
|
||
def _scan_stmt_end(lines, li, start):
|
||
"""Конец statement: ';' на depth 0, начиная с lines[li][start]. (end_li, end_i, ok)."""
|
||
depth = 0
|
||
i = start
|
||
guard = 0
|
||
while li < len(lines) and guard < 500:
|
||
guard += 1
|
||
line = lines[li]
|
||
while i < len(line):
|
||
c = line[i]
|
||
if c in "([{":
|
||
depth += 1
|
||
elif c in ")]}":
|
||
if depth == 0 and c == "}":
|
||
return li, i, False
|
||
if depth > 0:
|
||
depth -= 1
|
||
elif c == ";" and depth == 0:
|
||
return li, i, True
|
||
i += 1
|
||
li += 1
|
||
i = 0
|
||
return li, i, False
|
||
|
||
|
||
def wave_b2i(errs, stats):
|
||
"""boolean-выражение в int-контексте: обернуть RHS в (EXPR) ? 1 : 0."""
|
||
per_file = collections.defaultdict(list)
|
||
for f, ln, col, srcline, msg in errs:
|
||
if "boolean cannot be converted to int" in msg and srcline and col:
|
||
per_file[f].append((ln, col))
|
||
for f, items in per_file.items():
|
||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||
for ln, col in sorted(set(items), reverse=True):
|
||
if ln - 1 >= len(lines):
|
||
continue
|
||
eq = None
|
||
for back in range(0, 8):
|
||
li = ln - 1 - back
|
||
if li < 0:
|
||
break
|
||
hay = lines[li]
|
||
limit = col if back == 0 else len(hay)
|
||
for m in re.finditer(r"(?<![=!<>+\-*/&|^])=(?!=)", hay[:limit]):
|
||
idx = m.start()
|
||
e_li, e_i, ok = _scan_stmt_end(lines, li, idx + 1)
|
||
if not ok:
|
||
continue
|
||
if (e_li, e_i) >= (ln - 1, col):
|
||
eq = (li, idx)
|
||
break
|
||
if eq:
|
||
break
|
||
if not eq:
|
||
stats["b2i_no_eq"] += 1
|
||
continue
|
||
li, idx = eq
|
||
rs = idx + 1
|
||
while rs < len(lines[li]) and lines[li][rs] in " \t":
|
||
rs += 1
|
||
end_li, end_i, ok = _scan_stmt_end(lines, li, rs)
|
||
if not ok:
|
||
stats["b2i_no_end"] += 1
|
||
continue
|
||
if end_li == li:
|
||
span = lines[li][rs:end_i]
|
||
else:
|
||
span = (
|
||
lines[li][rs:]
|
||
+ "".join(lines[li + 1 : end_li])
|
||
+ lines[end_li][:end_i]
|
||
)
|
||
if span.rstrip().endswith("? 1 : 0"):
|
||
stats["b2i_dup"] += 1
|
||
continue
|
||
if end_li == li:
|
||
lines[li] = (
|
||
lines[li][:rs] + "(" + span + ") ? 1 : 0" + lines[li][end_i:]
|
||
)
|
||
else:
|
||
head = lines[li][:rs]
|
||
tail = lines[end_li][end_i:]
|
||
merged = head + "(" + span + ") ? 1 : 0" + tail
|
||
if not merged.endswith("\n"):
|
||
merged += "\n"
|
||
del lines[li : end_li + 1]
|
||
lines.insert(li, merged)
|
||
stats["b2i"] += 1
|
||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||
|
||
|
||
PRIV = re.compile(r"has private access in (\w+)$")
|
||
|
||
|
||
def wave_b(errs, stats):
|
||
members = collections.defaultdict(set)
|
||
for f, ln, col, srcline, msg in errs:
|
||
m = PRIV.search(msg)
|
||
if not m:
|
||
continue
|
||
owner = m.group(1)
|
||
sig = msg[: m.start()].strip()
|
||
name = sig.split("(")[0].strip()
|
||
# убрать квалификаторы вида SomeClass.member -> member
|
||
name = name.split(".")[-1]
|
||
if re.fullmatch(r"[A-Za-z_$][\w$]*", name):
|
||
members[owner].add(name)
|
||
for owner, names in members.items():
|
||
path = None
|
||
for cand in (
|
||
"/storage/aboba/evovis-client/src/main/java/defpackage/%s.java" % owner,
|
||
"/storage/aboba/evovis-client/src/main/java/a/%s.java" % owner,
|
||
"/storage/aboba/evovis-client/src/main/java/b/%s.java" % owner,
|
||
"/storage/aboba/evovis-client/src/main/java/evo/%s.java" % owner,
|
||
):
|
||
import os
|
||
|
||
if os.path.exists(cand):
|
||
path = cand
|
||
break
|
||
if not path:
|
||
stats["b_noowner"] += 1
|
||
continue
|
||
text = open(path, encoding="utf-8").read()
|
||
orig = text
|
||
for name in names:
|
||
rx = re.compile(
|
||
r"^(\s*)private\s+((?:static\s+|final\s+|volatile\s+|transient\s+|abstract\s+|native\s+|default\s+|synchronized\s+)*"
|
||
r"[\w.$<>\[\], ?]+\s+(?<![\w$])"
|
||
+ re.escape(name)
|
||
+ r"(?![\w$])\s*[;=(])",
|
||
re.M,
|
||
)
|
||
text, n = rx.subn(r"\1\2", text)
|
||
stats["b_priv"] += n
|
||
if text != orig:
|
||
open(path, "w", encoding="utf-8").write(text)
|
||
|
||
|
||
RECORD_RX = re.compile(
|
||
r"^(?P<mods>(?:public\s+|final\s+|abstract\s+)*)record\s+(?P<name>[A-Za-z_$][\w$]*)\s*\(",
|
||
re.M,
|
||
)
|
||
|
||
|
||
def _split_top(s, sep=","):
|
||
out, depth, cur = [], 0, ""
|
||
for ch in s:
|
||
if ch in "<([":
|
||
depth += 1
|
||
elif ch in ">)]":
|
||
depth -= 1
|
||
if ch == sep and depth == 0:
|
||
out.append(cur)
|
||
cur = ""
|
||
else:
|
||
cur += ch
|
||
if cur.strip():
|
||
out.append(cur)
|
||
return out
|
||
|
||
|
||
def wave_records(errs, stats):
|
||
owners = set()
|
||
for f, ln, col, srcline, msg in errs:
|
||
m = PRIV.search(msg.split(" | ")[0])
|
||
if m:
|
||
owners.add(m.group(1))
|
||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||
for owner in sorted(owners):
|
||
path = None
|
||
for pkg in ("defpackage/", "a/", "b/", "evo/"):
|
||
cand = base + pkg + owner + ".java"
|
||
if os.path.exists(cand):
|
||
path = cand
|
||
break
|
||
if not path:
|
||
continue
|
||
text = open(path, encoding="utf-8").read()
|
||
head = text[:2000]
|
||
m = RECORD_RX.search(text)
|
||
if not m or m.group("name") != owner:
|
||
continue
|
||
# найти компоненты: от '(' до matching ')' с балансом
|
||
start = m.end() - 1
|
||
depth = 0
|
||
i = start
|
||
while i < len(text):
|
||
if text[i] == "(":
|
||
depth += 1
|
||
elif text[i] == ")":
|
||
depth -= 1
|
||
if depth == 0:
|
||
break
|
||
i += 1
|
||
comps_raw = text[start + 1 : i]
|
||
comps = []
|
||
for piece in _split_top(comps_raw):
|
||
piece = " ".join(piece.split())
|
||
if not piece:
|
||
continue
|
||
parts = piece.rsplit(None, 1)
|
||
if len(parts) != 2:
|
||
continue
|
||
typ, name = parts
|
||
typ = re.sub(r"\s*\bfinal\b\s*", " ", typ).strip()
|
||
comps.append((typ, name))
|
||
if not comps:
|
||
stats["rec_skip"] += 1
|
||
continue
|
||
# тело: от ')' до конца класса — сохранить как есть
|
||
rest = text[i + 1 :]
|
||
brace = rest.find("{")
|
||
if brace == -1:
|
||
stats["rec_skip"] += 1
|
||
continue
|
||
body = rest[brace + 1 :]
|
||
header = text[: m.start()]
|
||
mods = (m.group("mods") or "").replace("record", "").strip()
|
||
is_public = "public" in mods
|
||
lines_cls = []
|
||
vis = "public " if is_public else ""
|
||
lines_cls.append(f"{vis}final class {owner} {{")
|
||
for typ, name in comps:
|
||
lines_cls.append(f" {typ} {name};")
|
||
lines_cls.append("")
|
||
ctor_args = ", ".join(f"{typ} {name}" for typ, name in comps)
|
||
lines_cls.append(f" {vis}{owner}({ctor_args}) {{")
|
||
for typ, name in comps:
|
||
lines_cls.append(f" this.{name} = {name};")
|
||
lines_cls.append(" }")
|
||
lines_cls.append("")
|
||
for typ, name in comps:
|
||
lines_cls.append(f" public {typ} {name}() {{")
|
||
lines_cls.append(f" return this.{name};")
|
||
lines_cls.append(" }")
|
||
lines_cls.append("")
|
||
# equals
|
||
lines_cls.append(" @Override")
|
||
lines_cls.append(" public boolean equals(Object o) {")
|
||
lines_cls.append(f" if (!(o instanceof {owner} that)) {{")
|
||
lines_cls.append(" return false;")
|
||
lines_cls.append(" }")
|
||
for typ, name in comps:
|
||
if "[]" in typ:
|
||
if typ.endswith("[]") and typ[:-2] in (
|
||
"int",
|
||
"long",
|
||
"double",
|
||
"float",
|
||
"boolean",
|
||
"byte",
|
||
"short",
|
||
"char",
|
||
):
|
||
lines_cls.append(
|
||
f" if (!java.util.Arrays.equals(this.{name}, that.{name})) {{ return false; }}"
|
||
)
|
||
else:
|
||
lines_cls.append(
|
||
f" if (!java.util.Objects.deepEquals(this.{name}, that.{name})) {{ return false; }}"
|
||
)
|
||
elif typ in (
|
||
"int",
|
||
"long",
|
||
"double",
|
||
"float",
|
||
"boolean",
|
||
"byte",
|
||
"short",
|
||
"char",
|
||
):
|
||
if typ == "float":
|
||
lines_cls.append(
|
||
f" if (Float.compare(this.{name}, that.{name}) != 0) {{ return false; }}"
|
||
)
|
||
elif typ == "double":
|
||
lines_cls.append(
|
||
f" if (Double.compare(this.{name}, that.{name}) != 0) {{ return false; }}"
|
||
)
|
||
else:
|
||
lines_cls.append(
|
||
f" if (this.{name} != that.{name}) {{ return false; }}"
|
||
)
|
||
else:
|
||
lines_cls.append(
|
||
f" if (!java.util.Objects.equals(this.{name}, that.{name})) {{ return false; }}"
|
||
)
|
||
lines_cls.append(" return true;")
|
||
lines_cls.append(" }")
|
||
lines_cls.append("")
|
||
lines_cls.append(" @Override")
|
||
lines_cls.append(" public int hashCode() {")
|
||
args = ", ".join(
|
||
(
|
||
"java.util.Arrays.deepHashCode(this.%s)" % n
|
||
if "[]" in ty
|
||
else "this.%s" % n
|
||
)
|
||
for ty, n in comps
|
||
)
|
||
lines_cls.append(f" return java.util.Objects.hash({args});")
|
||
lines_cls.append(" }")
|
||
lines_cls.append("")
|
||
lines_cls.append(" @Override")
|
||
lines_cls.append(" public String toString() {")
|
||
sb = 'return "%s[" + ' % owner
|
||
parts = []
|
||
for ty, n in comps:
|
||
parts.append('"%s=" + this.%s' % (n, n))
|
||
lines_cls.append(" " + sb + ' + ", " + '.join(parts) + ' + "]";')
|
||
lines_cls.append(" }")
|
||
lines_cls.append(" ")
|
||
new_text = header + "\n".join(lines_cls) + body
|
||
open(path, "w", encoding="utf-8").write(new_text)
|
||
stats["records"] += 1
|
||
|
||
|
||
def wave_ctors(errs, stats):
|
||
pairs = set()
|
||
for f, ln, col, srcline, msg in errs:
|
||
m = PRIV.search(msg.split(" | ")[0])
|
||
if not m:
|
||
continue
|
||
owner = m.group(1)
|
||
sig = msg.split(" | ")[0][: m.start()].strip()
|
||
if sig.startswith(owner + "("):
|
||
pairs.add(owner)
|
||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||
for owner in pairs:
|
||
path = None
|
||
for pkg in ("defpackage/", "a/", "b/", "evo/"):
|
||
cand = base + pkg + owner + ".java"
|
||
if os.path.exists(cand):
|
||
path = cand
|
||
break
|
||
if not path:
|
||
continue
|
||
text = open(path, encoding="utf-8").read()
|
||
new = re.sub(
|
||
r"(?m)^(\s*)private(\s+)" + re.escape(owner) + r"\s*\(",
|
||
r"\1\2" + owner + "(",
|
||
text,
|
||
)
|
||
if new != text:
|
||
open(path, "w", encoding="utf-8").write(new)
|
||
stats["ctors"] += 1
|
||
|
||
|
||
def wave_pkgqual(errs, stats):
|
||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||
per_file = collections.defaultdict(list)
|
||
for f, ln, col, srcline, msg in errs:
|
||
if not f.startswith(base + "b/"):
|
||
continue
|
||
if "cannot find symbol" not in msg:
|
||
continue
|
||
parts = msg.split(" | ")
|
||
symbol = location = None
|
||
for p in parts:
|
||
if p.startswith("symbol:"):
|
||
symbol = p[len("symbol:") :].strip()
|
||
elif p.startswith("location:"):
|
||
location = p[len("location:") :].strip()
|
||
if not location:
|
||
continue
|
||
lm = re.match(r"(?:class|interface|enum|record)\s+([\w$]+)", location)
|
||
if not lm:
|
||
continue
|
||
loc = lm.group(1)
|
||
if symbol and symbol.startswith("method "):
|
||
member = symbol[len("method ") :].split("(")[0]
|
||
elif symbol and symbol.startswith("class "):
|
||
member = None
|
||
else:
|
||
member = None
|
||
per_file[f].append((ln, col, loc, member))
|
||
for f, items in per_file.items():
|
||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||
for ln, col, loc, member in sorted(set(items), reverse=True):
|
||
if ln - 1 >= len(lines):
|
||
continue
|
||
line = lines[ln - 1]
|
||
if member:
|
||
needle = loc + "." + member
|
||
idx = line.find(needle)
|
||
if idx == -1:
|
||
stats["qual_miss"] += 1
|
||
continue
|
||
lines[ln - 1] = (
|
||
line[:idx] + "defpackage." + needle + line[idx + len(needle) :]
|
||
)
|
||
stats["qual"] += 1
|
||
else:
|
||
# класс: заменить идентификатор на позиции колонки
|
||
pos = col - 1
|
||
m = re.match(r"[A-Za-z_$][\w$]*", line[pos:])
|
||
if not m or m.group() != loc:
|
||
# найти loc как целый идентификатор
|
||
m2 = re.search(r"(?<![\w.$])" + re.escape(loc) + r"(?![\w$])", line)
|
||
if not m2:
|
||
stats["qual_miss"] += 1
|
||
continue
|
||
idx = m2.start()
|
||
else:
|
||
idx = pos
|
||
lines[ln - 1] = (
|
||
line[:idx] + "defpackage." + loc + line[idx + len(loc) :]
|
||
)
|
||
stats["qual"] += 1
|
||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||
|
||
|
||
def wave_vec(errs, stats):
|
||
per_file = collections.defaultdict(list)
|
||
for f, ln, col, srcline, msg in errs:
|
||
if "cannot find symbol" not in msg or not col:
|
||
continue
|
||
parts = msg.split(" | ")
|
||
symbol = location = None
|
||
for p in parts:
|
||
if p.startswith("symbol:"):
|
||
symbol = p[len("symbol:") :].strip()
|
||
elif p.startswith("location:"):
|
||
location = p[len("location:") :].strip()
|
||
if not symbol or not location or "Vector3fc" not in location:
|
||
continue
|
||
vm = re.match(r"variable\s+([\w$]+)", location)
|
||
sm = re.match(r"variable\s+([\w$]+)", symbol)
|
||
if not vm or not sm:
|
||
continue
|
||
per_file[f].append((ln, col, vm.group(1), sm.group(1)))
|
||
for f, items in per_file.items():
|
||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||
for ln, col, var, field in sorted(items, reverse=True):
|
||
if ln - 1 >= len(lines):
|
||
continue
|
||
line = lines[ln - 1]
|
||
needle = var + "." + field
|
||
idx = line.rfind(needle, 0, col + 10)
|
||
if idx == -1:
|
||
idx = line.find(needle)
|
||
if idx == -1:
|
||
stats["vec_miss"] += 1
|
||
continue
|
||
lines[ln - 1] = (
|
||
line[:idx] + var + "." + field + "()" + line[idx + len(needle) :]
|
||
)
|
||
stats["vec"] += 1
|
||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||
|
||
|
||
def main():
|
||
errs = load_errors(LOG)
|
||
stats = collections.Counter()
|
||
if "a" in WAVES:
|
||
wave_a(errs, stats, i2b=("1" in WAVES or WAVES == "a"), b2i=False)
|
||
elif "1" in WAVES or "2" in WAVES:
|
||
wave_a(errs, stats, i2b="1" in WAVES, b2i=False)
|
||
if "2" in WAVES:
|
||
wave_b2i(errs, stats)
|
||
if "b" in WAVES:
|
||
wave_b(errs, stats)
|
||
if "c" in WAVES:
|
||
wave_records(errs, stats)
|
||
wave_ctors(errs, stats)
|
||
if "e" in WAVES:
|
||
wave_pkgqual(errs, stats)
|
||
if "f" in WAVES:
|
||
wave_vec(errs, stats)
|
||
print(dict(stats))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|