LINE_CONTAINS, LINE_STARTSWITH, LINE_ENDSWITH = map(CaselessKeyword,
    'LINE_CONTAINS LINE_STARTSWITH LINE_ENDSWITH'.split())
NOT, AND, OR = map(CaselessKeyword, 'NOT AND OR'.split())
BEFORE, AFTER, JOIN = map(CaselessKeyword, 'BEFORE AFTER JOIN'.split())
keyword = MatchFirst([LINE_CONTAINS, LINE_STARTSWITH, LINE_ENDSWITH, NOT,
    AND, OR, BEFORE, AFTER, JOIN])
phrase_word = ~keyword + Word(alphas + '_')
phrase_term = phrase_word | quotedString
phrase_expr = infixNotation(phrase_term, [(BEFORE | AFTER | JOIN, 2,
    opAssoc.LEFT), (NOT, 1, opAssoc.RIGHT), (AND, 2, opAssoc.LEFT), (OR, 2,
    opAssoc.LEFT)], lpar=Suppress('{'), rpar=Suppress('}'))
line_term = Group((LINE_CONTAINS | LINE_STARTSWITH | LINE_ENDSWITH)(
    'line_directive') + Group(phrase_expr)('phrase'))
line_contents_expr = infixNotation(line_term, [(NOT, 1, opAssoc.RIGHT), (
    AND, 2, opAssoc.LEFT), (OR, 2, opAssoc.LEFT)])
sample = """
LINE_CONTAINS transfected BEFORE {sirna} AND gene AND LINE_STARTSWITH Therefore
"""
line_contents_expr.runTests(sample)
Writing grammar rules for context sensitive elements using Pyparsing