Skip to content

Instantly share code, notes, and snippets.

@dreamlayers
Created February 1, 2014 16:40
Show Gist options
  • Save dreamlayers/8754844 to your computer and use it in GitHub Desktop.
Save dreamlayers/8754844 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
# decatchify.py : Script for removing exception catching from MESS
# *** Configuration ***
# List of filenames to not process
WHITELIST = (
'machine.c' # Contains running_machine::start_all_devices()
)
# Try needs to become nothing, so the try block is just a normal block.
# A preprocessor define can eliminate try, but replacing allows more
# selective removal. Use of a string that can be defined to try or nothing
# would allow switching.
TRY_REPLACE = '/* try */'
# Catch blocks need to be entirely disabled, because they can refer to
# the catch parameter. A preprocessor condition is needed here, because
# "#endif" is added afterwards.
CATCH_COND = '#if 0'
# This just ensures that the program is being run in the right place.
# It is a safety measure to avoid accidentally processing unintended source.
MUSTEXIST = ('src/emu/emu.h', 'src/emu/mame.c', 'docs/mame.txt')
# *** Program ***
import os
import re
import sys
OKBESIDE = re.compile('[^A-Za-z_0-9]')
def validate_keyword(match):
b = match.start()
if b == 0 or OKBESIDE.match(match.string[b-1]):
e = match.end()
if e == len(match.string) or OKBESIDE.match(match.string[e]):
return True
return False
# Each parser state has a regular expression, with 1 or more alternatives
# that can be found. Each alternative has an associated parser function.
# The match object is passed to that function.
#
# Parser functions return a tuple: (current line string, current column,
# parser state). This allows functions to alter the line, including adding
# newlines inside to create more lines.
TRY_REPLACE_LEN = len(TRY_REPLACE)
def remove_try(match):
if validate_keyword(match):
return (match.string[:match.start()] + TRY_REPLACE
+ match.string[match.end():],
match.start() + TRY_REPLACE_LEN, 0)
else:
return (match.string, match.end(), 0)
BADC = re.compile(',|\s*\)')
INDENT = re.compile('\s*')
def begin_catch(match):
global bracelevel
e = match.string[match.end():]
if validate_keyword(match) and not BADC.match(e):
before = match.string[:match.start()]
if before.strip() == '':
# Preserve indent for catch.
prefix = before
before = ''
else:
# There is some code before the catch.
# Split line and use its indent.
before += '\n'
m = INDENT.match(before)
if m:
prefix = m.group()
else:
prefix = ''
before += CATCH_COND + '\n' + prefix + 'catch'
bracelevel = 0;
return (before + e, len(before), 3)
else:
return (match.string, match.end(), 0)
def brace_in(match):
global bracelevel
bracelevel += 1
return (match.string, match.end(), 3)
def brace_out(match):
global bracelevel
bracelevel -= 1
if bracelevel > 0:
return (match.string, match.end(), 3)
else:
# End of catch statement
e = match.string[match.end():]
if e.strip() == '':
e = ''
return (match.string[:match.end()] + '\n#endif\n' + e,
match.end() + 8, 0)
def unquote(match):
b = match.start()
if b == 0 or match.string[b] != '\\':
return (match.string, match.end(), 0)
else:
return (match.string, match.end(), 2)
# This defines parser states, their regular expressions and associated
# functions. Simple functions are lambdas below.
RE = (
# Initial state
(re.compile('(/\*)|(//)|(try)|(catch)|(")'),
{
1 : lambda match : (match.string, match.end(), 1),
2 : lambda match : (match.string, len(match.string), 0),
3 : remove_try,
4 : begin_catch,
5 : lambda match : (match.string, match.end(), 2)
}),
# Searching for end of comment
(re.compile('(\*/)'),
{
1 : lambda match : (match.string, match.end(), 0),
}),
# Searching for end of quote
(re.compile('(")'),
{
1 : unquote,
}),
# In catch statement, using braces to find its end
(re.compile('({)|(})'),
{
1 : brace_in,
2 : brace_out
}),
)
# This processes one file.
def decatchify(fn):
with open(fn, 'r+') as f:
lines = f.readlines()
f.seek(0)
f.truncate()
# Exiting the script here would lose file contents. That is assumed
# to be okay because git can re-create the file.
reidx = 0 # Which regular expression to use now
for l in lines:
loc = 0 # Current column in l
while True:
match = RE[reidx][0].search(l, loc)
if match:
(l, loc, reidx) = RE[reidx][1][match.lastindex](match)
else:
break
f.write(l)
if len(sys.argv) != 2:
print '''Give top level MESS (mame) directory as argument. C++ source files
in it and all subdirectories will be altered to remove exception catching.
Do not run this a second time if files have already been altered.'''
sys.exit(1)
for fn in MUSTEXIST:
fntest = os.path.join(sys.argv[1], fn)
if not os.path.isfile(fntest):
print 'Failed to find', fntest
print 'Are you running this in the right place?'
sys.exit(1)
# This traverses the tree and removes exception catching
for subdir, dirs, files in os.walk(sys.argv[1]):
for fn in files:
if fn.endswith('.c') or fn.endswith('.cpp'):
if not fn in WHITELIST:
decatchify(os.path.join(subdir, fn))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment