Files
GaCy-GO/tools/compare_cw_reference_protocol.py
2026-10-28 18:54:43 +07:00

72 lines
3.7 KiB
Python

"""Compare reference CW descriptors with the repository's compiled descriptor set."""
import argparse
import json
import re
from pathlib import Path
from google.protobuf import descriptor_pb2 as d
def messages(files):
result = {}
def visit(rows, prefix):
for row in rows:
full = prefix+'.'+row.name
result[full] = row
visit(row.nested_type, full)
for file in files:
visit(file.message_type, '.'+file.package)
return result
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--reference', type=Path, required=True)
p.add_argument('--current', type=Path, required=True)
p.add_argument('--commands', type=Path, required=True)
p.add_argument('--output', type=Path, required=True)
args = p.parse_args()
reference = d.FileDescriptorProto.FromString(args.reference.read_bytes())
current = d.FileDescriptorSet.FromString(args.current.read_bytes())
fm, cm = messages([reference]), messages(current.file)
current_names = {int(v):k for k,v in re.findall(r'"([^"]+)":\s*(\d+)',args.commands.read_text())}
commands = [{'id':v.number,'ffgo_name':v.name[3:],'current_name':current_names.get(v.number)} for enum in reference.enum_type if enum.name == 'CmdGridFightType' for v in enum.value if v.number]
mapping, conflicts = {}, []
def match(a,b,seen):
if (a,b) in seen:
return True
seen.add((a,b))
if a not in fm or b not in cm:
return False
xf, yf = ({v.number:v for v in rows.field} for rows in (fm[a],cm[b]))
if xf.keys()!=yf.keys():
return False
for num,u in xf.items():
v=yf[num]
if u.type!=v.type or u.label!=v.label or u.HasField('oneof_index')!=v.HasField('oneof_index'):
return False
if u.type==u.TYPE_MESSAGE and not match(u.type_name,v.type_name,seen):
return False
# Preserve the partition of fields into oneofs, independent of names.
groups = lambda fields: sorted(sorted(v.number for v in fields if v.HasField('oneof_index') and v.oneof_index==i) for i in set(v.oneof_index for v in fields if v.HasField('oneof_index')))
return groups(fm[a].field)==groups(cm[b].field)
for row in commands:
row['same_name']=row['ffgo_name']==row['current_name']
a='.'+reference.package+'.'+row['ffgo_name']
candidates=[k for k in cm if k.rsplit('.',1)[-1]==row['current_name']]
seen=set()
row['wire_shape_equal']=len(candidates)==1 and match(a,candidates[0],seen)
if row['wire_shape_equal']:
for k,v in seen:
if k in mapping and mapping[k]!=v:
conflicts.append([k,mapping[k],v])
mapping[k]=v
args.output.mkdir(parents=True,exist_ok=True)
provenance={'reference_descriptor_sha256':__import__('hashlib').sha256(args.reference.read_bytes()).hexdigest(),'current_descriptor_sha256':__import__('hashlib').sha256(args.current.read_bytes()).hexdigest(),'limitations':['Equal wire shapes do not establish enum meanings, field semantics, runtime success, or permission to register numeric aliases.']}
(args.output/'cw-command-comparison.json').write_text(json.dumps({'provenance':provenance,'commands':commands},indent=2))
(args.output/'cw-message-mapping.json').write_text(json.dumps({'provenance':provenance,'messages':mapping,'conflicts':conflicts},indent=2))
print(json.dumps({'commands':len(commands),'same_names':sum(x['same_name'] for x in commands),'equal_wire_shapes':sum(x['wire_shape_equal'] for x in commands),'mapped_messages':len(mapping),'conflicts':len(conflicts),'shape_differences':[x for x in commands if not x['wire_shape_equal']]}))
if __name__=='__main__':
main()