def define_grammar(): global grammar one_expr = Forward() one_or_more_expr = Forward() zero_or_more_expr = Forward() anum = Word( alphas+"_", alphanums+"_" ) # Starts with a letter or underscore, can continue with numbers too nametype_expr = Group(anum + ":" + anum).setParseAction(actionNTP) comment_expr = Group(Literal("#") + restOfLine).setParseAction(actionComment) list_expr = Group(Suppress("[") + zero_or_more_expr + Optional(Suppress(",") + one_or_more_expr) + Suppress("]")).setParseAction(actionList) # Putting it all together. # Any earlier Forward-defined expressions must be assigned to using "<<" rather than "=" # REMEMBER: You MUST put () around the whole RHS of << assignment, or the wrong thing happens one_expr << (list_expr | nametype_expr | comment_expr) # "|" is pyparsing's MatchFirst operator, so put most general matches at the end so they only gets matched if nothing else does # "^" is pyparsing's Or operator, which finds the longest match one_or_more_expr << (OneOrMore(one_expr)) zero_or_more_expr << (ZeroOrMore(one_expr)) zero_or_more_expr.validate() # Check for recursive loops (NOTE: Highly compute-intensive) final_expr = zero_or_more_expr + StringEnd() # StringEnd() to force consumption of entire string (no trailing stuff) grammar = final_expr
def define_grammar(): global grammar def removechar(s, removechar): # Returns string <s> having removed all occurrences of character <removechar> return ''.join(s.split(removechar)) def removechars(s, removechars): for c in removechars: s = removechar(s, c) return s # pyparsing's setResultsName() seems to have a bug: it names both a Group AND its children with the tag, which confuses us later when we traverse the tree # So instead, we use setParseAction() to manually create a special tag attribute # This can then be read from any x=ParseResults with x.storyelement (which will be the empty string if it doesn't exist) def setattr_choice(this): this.__setattr__("storyelement","choice") def setattr_varlookup(this): this.__setattr__("storyelement","var") def setattr_varassign(this): this[0].insert(0,SETVARINDICATOR) def setattr_constassign(this): this[0].insert(0,SETCONSTINDICATOR) # Our Grammar... # Returns a pyparsing object ParserElement.setDefaultWhitespaceChars("") one_expr = Forward() # Exactly one expression (of any sort) one_or_more_expr = Forward() # Compound expression (must have at least one expression) zero_or_more_expr = Forward() # Compound expression (can have zero expressions) # Any expression which has to be forward-declared MUST then be assigned-to later using "<<" not "=" !!! # WARNING: You must put brackets after << otherwise the wrong thing happens. e.g. A << (B | C) without the brackets fails # Define allchars as all characters allchars = srange("[!-~]") # All ASCII characters (same as "printables"?) allchars = "\t\n " + allchars # And all whitespace chars # Bodytext is all characters which don't have special meaning bodychars = removechars(allchars, "[]{|}=<>$") # Bodytext cannot contain special characters # Bodytext (meaning text we want to leave alone) is # anything consisting entirely of non-special characters bodytext = OneOrMore(Word(bodychars)).leaveWhitespace() # and don't try to strip off any leading whitespace htmlchars = allchars htmlchars = removechars(htmlchars, ">") # HTML tags need to be left alone. htmltags = Literal("<") - ZeroOrMore(Word(htmlchars)).leaveWhitespace() - Literal(">") # A valid variable name starts with a letter varname_expr = Word(alphas,alphanums + '_') # A use of a variable looks like [varname] # varname can be computed, e.g. [{A|B}] is fine # but it can't be empty, e.g. [] var_expr = Group(Literal("[") - one_or_more_expr - Literal("]")).setParseAction(setattr_varlookup) # A choice looks like {} or {A} or {A|B} etc. # (and of course A & B can be any expression at all, i.e. recursive) # and can include null entries e.g. {A|} choice_expr = (Group(Literal("{") - Group(Optional(one_or_more_expr,default=""))("firstargs") - ZeroOrMore(Literal("|").suppress() - Group(Optional(one_or_more_expr, default="")("subsargs"))) - Literal("}")).setParseAction(setattr_choice)) # We use Optional(one_or_more_expr) rather than zero_or_more_expr because that way we can explicitly capture null expressions as choices # We use Group() around the subexpressions to ensure that if they are complex they don't get flattened (otherwise we'll end-up choosing from things that are just sub-lists) # There seems to be a but with SetResultsName() which causes both the Group and its children to both be named the same (meaning you can't tell when traversing the tree whether you are a choice group, or an element within a choice group). # So instead we just leave the {} brackets unsuppressed and use them as a label # Setting a variable looks like [varname]={blah} setvar_expr = Group(var_expr + Literal("=") - choice_expr).setParseAction(setattr_varassign) # We use "+" (not "-") before Literal("=") because it IS legal for there to be a var_expr on it's own (i.e. when a var is used not set) # Using a constant looks like $const const_expr = Group(Literal("$") - varname_expr) # Setting a constant looks like $const={choice} or $const=[var] setconst_expr = Group(const_expr + Literal("=") - (choice_expr ^ var_expr)).setParseAction(setattr_constassign) # We use "+" (not "-") before Literal("=") because it IS legal when using rather than assigning a constant # Putting it all together. # Any earlier Forward-defined expressions must be assigned to using "<<" rather than "=" # REMEMBER: You MUST put () around the whole RHS of << assignment, or the wrong thing happens one_expr << (setconst_expr | const_expr | setvar_expr | var_expr | choice_expr | htmltags | bodytext) # "|" is pyparsing's MatchFirst operator, so put bodytext at the end so it only gets matched if nothing else does # "^" is pyparsing's Or operator, which finds the longest match one_or_more_expr << (OneOrMore(one_expr)) zero_or_more_expr << (ZeroOrMore(one_expr)) zero_or_more_expr.validate() # Check for recursive loops (NOTE: Highly compute-intensive) final_expr = zero_or_more_expr + StringEnd() grammar = final_expr
def Grammar(home): # Productions cfoperator = equalsOp ^ notequalsOp pathElement= seperator + identifier RelSpec = OneOrMore(pathElement) AbsSpec = identifier + RelSpec ExprField = AbsSpec ^ RelSpec ExprText = Forward() BoolExpr = ExprField + cfoperator + ExprText QueryExpr = BoolExpr + queryOp + ExprText + "/" +ExprText ExprText << ( ExprField ^ \ QueryExpr ^ \ LiteralVal ) ## Functions for parsing. def doBool(s,loc,toks): modlogger.debug( "getbol\n") sense=(toks[1]=="!=") return sense ^ ( str(toks[0]) == toks[2]) def doQuery(s,loc,toks): modlogger.debug( "doing query\n") if toks[0]: return toks[2] else: return toks[4] def ExprFieldAction(s,loc,toks): #In this case our walk will fail so # just return None as an invalid thang. if home is None: return None #Determine whether abs or rel and # find our origin. # We use the home objct - or # derefernce the object if it is an absolute reference. # - we can't just get the category because there is (currently) # no object which represents those , but we can get an object # and the grammar is specified such that an object must be spcified # not just a category` if toks[0] == ":": origin = home path = toks[1:] else: origin = home.get_root().get_object(toks[0],toks[2]) path = toks[4:] #Walk along the attributes field = origin canonpath = origin.get_nodeid() for ele in path: if ele != ":": #Skip ':' as grammar noise. if field is None: raise NullReference(canonpath) canonpath ="%s:%s"%(field.get_nodeid(),ele) try: field = field[ele] except KeyError: raise NoAttribute("%s has no attribute `%s'"%(field.get_nodeid(),ele)) if hasattr(field,"getSelf"): field = field.getSelf() return field ## Bind functions to parse actions ExprField.setParseAction(ExprFieldAction) BoolExpr.setParseAction(doBool) QueryExpr.setParseAction(doQuery) ExprText.enablePackrat() ExprText.validate() return ExprText + stringEnd