Skip to content

Instantly share code, notes, and snippets.

@tlively
Created January 4, 2024 19:49
Show Gist options
  • Save tlively/85ae7f01f92f772241ec994c840ccbb1 to your computer and use it in GitHub Desktop.
Save tlively/85ae7f01f92f772241ec994c840ccbb1 to your computer and use it in GitHub Desktop.
Automatically translate Binaryen's if expressions to standard format
#! /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