Created
November 26, 2013 10:39
-
-
Save julik/7656373 to your computer and use it in GitHub Desktop.
Yo dawg, I heard you like Nuke so I wrote this so that you can parse your Nuke TCL while you tickle yo' Python. Ya dig?
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
TERMINATORS = ["\n", ";"] | |
ESC = "\\" | |
QUOTES = ['"', "'"] | |
class c: | |
""" | |
Will be at the start of a list representing an expression | |
in curly braces. Every c() is equal to any other c() | |
""" | |
def __repr__(self): | |
return ':c' # Ruby says "hi" | |
def __eq__(self, other): | |
return self.__class__ == other.__class__ | |
class b: | |
""" | |
Will be at the start of a list representing an expression | |
in square braces. Every b() is equal to any other b() | |
""" | |
def __repr__(self): | |
return ':b' | |
def __eq__(self, other): | |
return self.__class__ == other.__class__ | |
class Parser: | |
""" | |
Simplistic, incomplete and most likely incorrect TCL parser | |
""" | |
def parse(self, io): | |
""" | |
Parses a piece of TCL and returns it converted into internal expression | |
structures. A basic TCL expression is just an array of Strings. An expression | |
in curly braces will have the symbol :c tacked onto the beginning of the array. | |
An expression in square braces will have the symbol :b tacked onto the beginning. | |
This method always returns a Array of expressions. If you only fed it one expression, | |
this expression will be the only element of the array. | |
>>> p = tickly.Parser() | |
>>> p.parse("{2 + 2}") | |
[[:c, "2", "+", "2"]] | |
""" | |
return self.parse_expr(io, stop_char = None, stack_depth = 0, multiple_expressions = True) | |
def compact_subexpr(self, expr, at_depth): | |
""" | |
Override this to remove any unneeded subexpressions. | |
Return the modified expression. If you return None, the result | |
will not be added to the expression list. You can also use this | |
method for bottom-up expression evaluation, returning the result | |
of the expression being evaluated. This method will be first called | |
for the innermost expressions and then proceed up the call stack. | |
""" | |
return expr | |
def wrap_up(self, expressions, stack, buf, stack_depth, multiple_expressions): | |
""" | |
Package the expressions, stack and buffer. | |
We use a special flag to tell us whether we need multuple expressions. | |
If we do, the expressions will be returned. If not, just the stack. | |
Also, anything that remains on the stack will be put on the expressions | |
list if multiple_expressions is true. | |
""" | |
self.consume_remaining_buffer(stack, buf) | |
if not multiple_expressions: | |
return stack | |
if len(stack) > 0: | |
expressions.append(stack) | |
return expressions | |
def consume_remaining_buffer(self, stack, buf): | |
""" | |
If the passed buf contains any bytes, put them on the stack and | |
empty the buffer | |
""" | |
if len(buf) is 0: | |
return | |
else: | |
stack.append(buf) | |
buf = '' | |
def parse_expr(self, io, stop_char = None, stack_depth = 0, multiple_expressions = False): | |
""" | |
Parse a subexpression from a passed IO object either until an unescaped stop_char is reached | |
or until the IO is exhausted. The last argument is the class used to | |
compose the subexpression being parsed. The subparser is reentrant and not | |
destructive for the object containing it. | |
""" | |
expressions = [] # Already parsed exprs | |
stack = [] # The current stack of barewords | |
buf = '' # The current charbuf for the bareword being accumulated | |
while True: | |
char = io.read(1) | |
if char == "\r": # Ignore Windows carriage returns | |
pass | |
elif stop_char and char is "": | |
raise Exception("IO ran out when parsing a subexpression (expected to end on %s)" % stop_char) | |
elif char in ('', stop_char): # Bail out of a subexpr or bail out when the buffer is at EOF | |
# Handle any remaining subexpressions | |
return self.wrap_up(expressions, stack, buf, stack_depth, multiple_expressions) | |
elif char in [" ", "\n"]: # Space | |
if len(buf) > 0: | |
stack.append(buf) | |
buf = '' | |
if char in TERMINATORS and len(stack) > 0: # Introduce a stack separator! This is a new line | |
# First get rid of the remaining buffer data | |
self.consume_remaining_buffer(stack, buf) | |
# Since we now finished an expression and it is on the stack, | |
# we can run this expression through the filter | |
filtered_expr = self.compact_subexpr(stack, stack_depth + 1) | |
# Only preserve the parsed expression if it's not None | |
if filtered_expr: | |
expressions.append(filtered_expr) | |
# Reset the stack for the next expression | |
stack = [] | |
# Note that we will return multiple expressions instead of one | |
multiple_expressions = True | |
elif char == '[': # Opens a new string expression | |
self.consume_remaining_buffer(stack, buf) | |
# Start a subexpression with a bracket | |
stack.append([b()] + self.parse_expr(io, ']', stack_depth + 1)) | |
elif char == '{': # Opens a new literal expression | |
self.consume_remaining_buffer(stack, buf) | |
# Start a subexpression with a curly | |
stack.append([c()] + self.parse_expr(io, '}', stack_depth + 1)) | |
elif char in QUOTES: # String | |
self.consume_remaining_buffer(stack, buf) | |
stack.append(self.parse_str(io, char)) | |
else: | |
buf += char | |
raise Exception("parse_expr should always return from the parse loop explicitly") | |
def parse_str(self, io, stop_quote): | |
""" | |
Parse a string literal, in single or double quotes, honoring | |
the backslash-escape | |
""" | |
buf = '' | |
while True: | |
c = io.read(1) | |
if len(c) == 0: | |
raise Exception("The IO ran out before the end of a literal string, with %d bytes on the buffer" % len(buf)) | |
elif len(buf) > 0 and buf[-1] is ESC: # If this char was escaped | |
buf = buf[0:-1] # Trim the escape character at the end of the buffer | |
buf += c | |
elif c == stop_quote: | |
return buf | |
else: | |
buf += c | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment