Skip to content

Instantly share code, notes, and snippets.

@julik
Created November 26, 2013 10:39
Show Gist options
  • Save julik/7656373 to your computer and use it in GitHub Desktop.
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?
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