Skip to content

Commit 2c2c232

Browse files
committed
Add diff.py to check definitions.py against CanonicalABI.md
1 parent f858bd3 commit 2c2c232

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

design/mvp/canonical-abi/definitions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class StreamType(ValType):
176176
class FutureType(ValType):
177177
t: Optional[ValType]
178178

179+
# START
180+
179181
### Lifting and Lowering Context
180182

181183
class LiftLowerContext:

design/mvp/canonical-abi/diff.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import re
2+
import sys
3+
from pathlib import Path
4+
5+
def extract_code_from_definitions(file_path):
6+
with open(file_path, 'r') as f:
7+
content = f.read()
8+
9+
start_pos = content.find("# START")
10+
if start_pos == -1:
11+
print("Error: Could not find '# START' comment in definitions.py")
12+
sys.exit(1)
13+
14+
code_after_start = content[start_pos:].split('\n', 1)[1].strip()
15+
return code_after_start.split('\n')
16+
17+
def extract_code_blocks_from_md(file_path):
18+
with open(file_path, 'r') as f:
19+
content = f.read()
20+
21+
code_blocks_text = re.findall(r'```python\n(.*?)```', content, re.DOTALL)
22+
return [block.split('\n') for block in code_blocks_text]
23+
24+
def normalize_line(line):
25+
return line.strip()
26+
27+
def is_comment_or_empty(line):
28+
normalized = normalize_line(line)
29+
return normalized == '' or normalized.startswith('#')
30+
31+
def is_canon_thread_function(line):
32+
normalized = normalize_line(line)
33+
return (normalized.startswith('async def canon_thread') or
34+
normalized.startswith('def canon_thread'))
35+
36+
def filter_canon_thread_functions(code_blocks):
37+
filtered_blocks = []
38+
39+
for block in code_blocks:
40+
filtered_block = []
41+
i = 0
42+
43+
while i < len(block):
44+
if is_canon_thread_function(block[i]):
45+
i += 1
46+
while i < len(block) and (not block[i].strip() or block[i].startswith(' ') or block[i].startswith('\t')):
47+
i += 1
48+
else:
49+
filtered_block.append(block[i])
50+
i += 1
51+
52+
if filtered_block:
53+
filtered_blocks.append(filtered_block)
54+
55+
return filtered_blocks
56+
57+
def find_spurious_newlines_in_definitions(def_lines, md_lines):
58+
empty_lines = []
59+
for i, line in enumerate(def_lines):
60+
if is_comment_or_empty(line):
61+
empty_lines.append(i)
62+
63+
spurious_lines = []
64+
for i in empty_lines:
65+
prev_line = def_lines[i-1] if i > 0 else ""
66+
next_line = def_lines[i+1] if i+1 < len(def_lines) else ""
67+
68+
if not is_comment_or_empty(prev_line) and not is_comment_or_empty(next_line):
69+
prev_normalized = normalize_line(prev_line)
70+
next_normalized = normalize_line(next_line)
71+
72+
prev_indices = [j for j, line in enumerate(md_lines) if normalize_line(line) == prev_normalized]
73+
next_indices = [j for j, line in enumerate(md_lines) if normalize_line(line) == next_normalized]
74+
75+
for prev_idx in prev_indices:
76+
for next_idx in next_indices:
77+
if next_idx == prev_idx + 1:
78+
spurious_lines.append(i)
79+
break
80+
if i in spurious_lines:
81+
break
82+
83+
return spurious_lines
84+
85+
def find_spurious_newlines_in_md(def_lines, md_blocks):
86+
spurious_lines = []
87+
88+
for block_idx, block in enumerate(md_blocks):
89+
for line_idx, line in enumerate(block):
90+
if is_comment_or_empty(line):
91+
prev_line = block[line_idx-1] if line_idx > 0 else ""
92+
next_line = block[line_idx+1] if line_idx+1 < len(block) else ""
93+
94+
if not is_comment_or_empty(prev_line) and not is_comment_or_empty(next_line):
95+
prev_normalized = normalize_line(prev_line)
96+
next_normalized = normalize_line(next_line)
97+
98+
prev_in_def = False
99+
next_in_def = False
100+
adjacent_in_def = False
101+
102+
for i in range(len(def_lines) - 1):
103+
if normalize_line(def_lines[i]) == prev_normalized and normalize_line(def_lines[i+1]) == next_normalized:
104+
adjacent_in_def = True
105+
break
106+
107+
if adjacent_in_def:
108+
spurious_lines.append((block_idx, line_idx))
109+
110+
return spurious_lines
111+
112+
def check_content_differences(def_lines, md_lines):
113+
def_content = [(i, normalize_line(line)) for i, line in enumerate(def_lines) if not is_comment_or_empty(line)]
114+
md_content = [(i, normalize_line(line)) for i, line in enumerate(md_lines) if not is_comment_or_empty(line)]
115+
116+
differences = []
117+
i, j = 0, 0
118+
119+
while i < len(def_content) and j < len(md_content):
120+
def_idx, def_line = def_content[i]
121+
md_idx, md_line = md_content[j]
122+
123+
if def_line != md_line:
124+
found_match = False
125+
for k in range(i+1, min(i+10, len(def_content))):
126+
if def_content[k][1] == md_line:
127+
for l in range(i, k):
128+
differences.append((def_content[l][0], def_content[l][1], md_idx, md_line))
129+
i = k
130+
found_match = True
131+
break
132+
133+
if not found_match:
134+
for k in range(j+1, min(j+10, len(md_content))):
135+
if md_content[k][1] == def_line:
136+
for l in range(j, k):
137+
differences.append((def_idx, def_line, md_content[l][0], md_content[l][1]))
138+
j = k
139+
found_match = True
140+
break
141+
142+
if not found_match:
143+
differences.append((def_idx, def_line, md_idx, md_line))
144+
i += 1
145+
j += 1
146+
else:
147+
i += 1
148+
j += 1
149+
150+
while i < len(def_content):
151+
def_idx, def_line = def_content[i]
152+
differences.append((def_idx, def_line, None, None))
153+
i += 1
154+
155+
while j < len(md_content):
156+
md_idx, md_line = md_content[j]
157+
differences.append((None, None, md_idx, md_line))
158+
j += 1
159+
160+
return differences
161+
162+
def main():
163+
script_dir = Path(__file__).parent.absolute()
164+
definitions_path = script_dir / 'definitions.py'
165+
canonical_abi_path = script_dir.parent / 'CanonicalABI.md'
166+
167+
try:
168+
def_lines = extract_code_from_definitions(definitions_path)
169+
md_blocks = extract_code_blocks_from_md(canonical_abi_path)
170+
filtered_blocks = filter_canon_thread_functions(md_blocks)
171+
md_lines = []
172+
for block in filtered_blocks:
173+
md_lines.extend(block)
174+
175+
spurious_in_def = find_spurious_newlines_in_definitions(def_lines, md_lines)
176+
spurious_in_md = find_spurious_newlines_in_md(def_lines, filtered_blocks)
177+
content_differences = check_content_differences(def_lines, md_lines)
178+
179+
has_errors = False
180+
if spurious_in_def:
181+
has_errors = True
182+
print(f"\nFound {len(spurious_in_def)} spurious newline(s) in definitions.py:")
183+
for i in spurious_in_def:
184+
print(f" Line {i+1}")
185+
start = max(0, i - 2)
186+
end = min(len(def_lines), i + 3)
187+
print("\n Context:")
188+
for j in range(start, end):
189+
prefix = ">" if j == i else " "
190+
print(f" {prefix} {j+1}: '{def_lines[j]}'")
191+
192+
if spurious_in_md:
193+
has_errors = True
194+
print(f"\nFound {len(spurious_in_md)} spurious newline(s) in CanonicalABI.md code blocks:")
195+
for block_idx, line_idx in spurious_in_md:
196+
block = filtered_blocks[block_idx]
197+
print(f" Block {block_idx+1}, Line {line_idx+1}")
198+
start = max(0, line_idx - 2)
199+
end = min(len(block), line_idx + 3)
200+
print("\n Context:")
201+
for j in range(start, end):
202+
prefix = ">" if j == line_idx else " "
203+
print(f" {prefix} Line {j+1}: '{block[j]}'")
204+
205+
if content_differences:
206+
has_errors = True
207+
print(f"\nFound {len(content_differences)} content difference(s) between the files:")
208+
for def_idx, def_line, md_idx, md_line in content_differences:
209+
if def_idx is not None and md_idx is not None:
210+
print(f"\n Difference at definitions.py line {def_idx+1} and CanonicalABI.md line {md_idx+1}:")
211+
print(f" definitions.py: '{def_line}'")
212+
print(f" CanonicalABI.md: '{md_line}'")
213+
elif def_idx is not None:
214+
print(f"\n Extra line in definitions.py at line {def_idx+1}:")
215+
print(f" '{def_line}'")
216+
else:
217+
print(f"\n Extra line in CanonicalABI.md at line {md_idx+1}:")
218+
print(f" '{md_line}'")
219+
220+
if has_errors:
221+
print("\nError: Differences found between definitions.py and CanonicalABI.md.")
222+
sys.exit(1)
223+
except Exception as e:
224+
print(f"\nError: {str(e)}")
225+
import traceback
226+
traceback.print_exc()
227+
sys.exit(1)
228+
229+
if __name__ == "__main__":
230+
main()

0 commit comments

Comments
 (0)