#!/usr/bin/env python """Uses abnfgen to stress test the grammar. Make sure abnfgen is installed:: brew install abnfgen # On Mac """ import argparse import tempfile import subprocess import jmespath from jmespath import exceptions # Add any additional args you want to control # abnfgen. #ARGS = ['-y', '10'] ARGS = [] GRAMMAR = r""" expression = sub-expression / index-expression / or-expression / identifier / "*" expression =/ multi-select-list / multi-select-hash / literal / function-expression / pipe-expression sub-expression = expression "." ( identifier / multi-select-list / multi-select-hash / function-expression / "*" ) or-expression = expression "||" expression index-expression = expression bracket-specifier / bracket-specifier multi-select-list = "[" ( expression *( "," expression ) ) "]" multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" pipe-expression = expression "|" expression keyval-expr = identifier ":" expression bracket-specifier = "[" (number / "*") "]" / "[]" bracket-specifier =/ "[?" list-filter-expr "]" list-filter-expr = expression comparator expression comparator = "<" / "<=" / "==" / ">=" / ">" / "!=" function-expression = unquoted-string ( no-args / one-or-more-args ) no-args = "(" ")" one-or-more-args = "(" ( function-arg *( "," function-arg ) ) ")" function-arg = expression / current-node / expression-type current-node = "@" expression-type = "&" expression literal = "`" json-value "`" literal =/ "`" 1*(unescaped-literal / escaped-literal) "`" unescaped-literal = %x20-21 / ; space ! %x23-5A / ; # - [ %x5D-5F / ; ] ^ _ %x61-7A ; a-z %x7C-10FFFF ; |}~ ... escaped-literal = escaped-char / (escape %x60) number = ["-"]1*digit digit = %x30-39 identifier = unquoted-string / quoted-string unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; a-zA-Z_ %x30-39 / ; 0-9 %x41-5A / ; A-Z %x5F / ; _ %x61-7A) ; a-z quoted-string = quote 1*(unescaped-char / escaped-char) quote unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF escape = %x5C ; Back slash: \ quote = %x22 ; Double quote: '"' escaped-char = escape ( %x22 / ; " quotation mark U+0022 %x5C / ; \ reverse solidus U+005C %x2F / ; / solidus U+002F %x62 / ; b backspace U+0008 %x66 / ; f form feed U+000C %x6E / ; n line feed U+000A %x72 / ; r carriage return U+000D %x74 / ; t tab U+0009 %x75 4HEXDIG ) ; uXXXX U+XXXX ; The ``json-value`` is any valid JSON value with the one exception that the ; ``%x60`` character must be escaped. While it's encouraged that implementations ; use any existing JSON parser for this grammar rule (after handling the escaped ; literal characters), the grammar rule is shown below for completeness:: json-value = false / null / true / json-object / json-array / json-number / json-quoted-string false = %x66.61.6c.73.65 ; false null = %x6e.75.6c.6c ; null true = %x74.72.75.65 ; true json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22 begin-array = ws %x5B ws ; [ left square bracket begin-object = ws %x7B ws ; { left curly bracket end-array = ws %x5D ws ; ] right square bracket end-object = ws %x7D ws ; } right curly bracket name-separator = ws %x3A ws ; : colon value-separator = ws %x2C ws ; , comma ws = *(%x20 / ; Space %x09 / ; Horizontal tab %x0A / ; Line feed or New line %x0D ; Carriage return ) json-object = begin-object [ member *( value-separator member ) ] end-object member = quoted-string name-separator json-value json-array = begin-array [ json-value *( value-separator json-value ) ] end-array json-number = [ minus ] int [ frac ] [ exp ] decimal-point = %x2E ; . digit1-9 = %x31-39 ; 1-9 e = %x65 / %x45 ; e E exp = e [ minus / plus ] 1*DIGIT frac = decimal-point 1*DIGIT int = zero / ( digit1-9 *DIGIT ) minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 """ def stress(args): with tempfile.NamedTemporaryFile('w') as f: f.write(GRAMMAR) f.flush() i = 0 while True: output = subprocess.check_output(['abnfgen'] + ARGS + [f.name]) output = output.decode('utf-8') try: parsed = jmespath.compile(output) # abnfgen can generate expressions that contain # unknown functions. This is ok, because the # grammar doesn't enforce the known functions. except exceptions.UnknownFunctionError: pass except Exception as e: print(e) print(output) import pdb; pdb.set_trace() i += 1 if i % 1000 == 0: print("num_expressions: %s" % i) def main(): parser = argparse.ArgumentParser() args = parser.parse_args() stress(args) if __name__ == '__main__': main()