Created
January 4, 2024 19:49
-
-
Save tlively/85ae7f01f92f772241ec994c840ccbb1 to your computer and use it in GitHub Desktop.
Automatically translate Binaryen's if expressions to standard format
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python3 | |
"""Translate from the legacy if-else text syntax to the standard syntax | |
""" | |
import argparse | |
import glob | |
import os | |
import subprocess | |
import sys | |
def warn(msg): | |
print(f'WARNING: {msg}', file=sys.stderr) | |
def takeComment(text, i): | |
start = i | |
if text[i:].startswith(';;'): | |
while i < len(text) and text[i] != '\n': | |
i += 1 | |
return text[start:i], i | |
def takeUntilParen(text, i): | |
start = i | |
while i < len(text): | |
comment, i = takeComment(text, i) | |
if comment: | |
continue | |
if text[i] == '"': | |
i += 1 | |
while i < len(text) and text[i] != '"': | |
i += 1 | |
if i < len(text): | |
i += 1 | |
continue | |
if text[i] == '(' or text[i] == ')': | |
break; | |
i += 1 | |
return text[start:i], i | |
def getIndent(text, i): | |
start = i | |
i -= 1 | |
while i >= 0 and text[i].isspace() and text[i] != '\n': | |
i -= 1 | |
if text[i] == '\n': | |
return start - i - 1 | |
return 0 | |
def applyIndent(text, indent): | |
out = [] | |
for c in text: | |
if c == '\n': | |
out.append('\n' + ' ' * indent) | |
else: | |
out.append(c) | |
return ''.join(out) | |
def wrap(text, i, word, baseIndent, marginalIndent): | |
out = [] | |
if text[i:].startswith('(' + word): | |
wrapped, i = takeParen(text, i) | |
out.append(wrapped) | |
else: | |
wrappee, i = takeParen(text, i) | |
while i < len(text) and text[i].isspace() and text[i] != '\n': | |
wrappee += text[i] | |
i += 1 | |
comment, i = takeComment(text, i) | |
if comment: | |
wrappee += comment | |
out.append('(') | |
out.append(word) | |
if baseIndent and marginalIndent: | |
out.append('\n') | |
out.append(' ' * (baseIndent + marginalIndent)) | |
out.append(applyIndent(wrappee, marginalIndent)) | |
out.append('\n') | |
out.append(' ' * baseIndent) | |
out.append(')') | |
else: | |
out.append(' ') | |
out.append(wrappee) | |
out.append(')') | |
return ''.join(out), i | |
def takeParen(text, i): | |
assert text[i] == '(' | |
out = [] | |
# ;; (if ... | |
if text[i:].startswith('(if'): | |
ifIndent = getIndent(text, i) | |
out.append(text[i:i+3]) | |
i += 3 | |
space, i = takeUntilParen(text, i) | |
out.append(space) | |
if text[i:].startswith('(result'): | |
result, i = takeParen(text, i) | |
out.append(result) | |
space, i = takeUntilParen(text, i) | |
out.append(space) | |
cond, i = takeParen(text, i) | |
out.append(cond) | |
space, i = takeUntilParen(text, i) | |
out.append(space) | |
thenIndent = getIndent(text, i) | |
if thenIndent > ifIndent: | |
indent = thenIndent - ifIndent | |
else: | |
indent = 0 | |
then, i = wrap(text, i, 'then', thenIndent, indent) | |
out.append(then) | |
space, i = takeUntilParen(text, i) | |
out.append(space) | |
if i < len(text) and text[i] == '(': | |
else_, i = wrap(text, i, 'else', thenIndent, indent) | |
out.append(else_) | |
space, i = takeUntilParen(text, i) | |
out.append(space) | |
else: | |
out.append('(') | |
i += 1 | |
while i < len(text): | |
space, i = takeUntilParen(text, i) | |
if space: | |
out.append(space) | |
continue | |
if text[i] == '(': | |
paren, i = takeParen(text, i) | |
out.append(paren) | |
continue | |
assert text[i] == ')' | |
out.append(')') | |
i += 1 | |
break | |
return ''.join(out), i | |
def port_test(args, test): | |
if not test.endswith('.wast') and not test.endswith('.wat'): | |
warn(f'Skipping {test} because only .wat and .wast files are supported') | |
return | |
forbidden = { | |
'test/passes/limit-segments_disable-bulk-memory.wast', | |
'test/spec/block.wast', | |
'test/spec/br_if.wast', | |
'test/spec/br_table.wast', | |
'test/spec/call_indirect.wast', | |
'test/spec/globals.wast', | |
'test/spec/i32.wast', | |
'test/spec/if.wast', | |
'test/spec/load64.wast', | |
'test/spec/load.wast', | |
'test/spec/local_tee.wast', | |
'test/spec/loop.wast', | |
'test/spec/memory_grow.wast', | |
'test/spec/names.wast', | |
'test/spec/nop.wast', | |
'test/spec/return.wast', | |
'test/spec/select.wast', | |
'test/spec/store.wast', | |
'test/spec/typecheck.wast', | |
'test/spec/comments.wast', | |
'test/lit/wat-kitchen-sink.wast', | |
} | |
for f in forbidden: | |
if test.endswith(f): | |
return | |
print("porting", test) | |
with open(test) as file: | |
text = file.read() | |
i = 0 | |
out = [] | |
while i < len(text): | |
intro, i = takeUntilParen(text, i) | |
if intro: | |
out.append(intro) | |
continue | |
paren, i = takeParen(text, i) | |
out.append(paren) | |
updated = ''.join(out) | |
if args.dry_run: | |
print(updated) | |
else: | |
with open(test, 'w') as file: | |
file.write(updated) | |
def main(): | |
parser = argparse.ArgumentParser(description=__doc__) | |
parser.add_argument( | |
'--binaryen-bin', dest='binaryen_bin', default='bin', | |
help=('Specifies the path to the Binaryen executables in the CMake build' | |
' directory. Default: bin/ of current directory (i.e. assume an' | |
' in-tree build).')) | |
parser.add_argument('--dry', dest='dry_run', action='store_true', | |
help=('Print the updated tests instead of updating' | |
' files in-place.')) | |
parser.add_argument('tests', nargs='+', help='The test files to port') | |
args = parser.parse_args() | |
args.binaryen_bin = os.path.abspath(args.binaryen_bin) | |
for pattern in args.tests: | |
for test in glob.glob(pattern, recursive=True): | |
port_test(args, test) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment