def test_combined_expression_with_single_primitive_expr(self): # [ [ [ s A s ] B s B s ] A s ] s = ast.PrimitiveSansArg() op_sequence_1 = ast.InfixOpA([s, s]) op_sequence_2 = ast.InfixOpB([op_sequence_1, s, s]) expected = ast.InfixOpA([op_sequence_2, s]) arguments = '{s} {op_a} {s} {op_b} {s} {op_b} {s} {op_a} {s}'.format( op_a=ast.INFIX_OP_A, op_b=ast.INFIX_OP_B_THAT_IS_NOT_A_VALID_SYMBOL_NAME, s=ast.PRIMITIVE_SANS_ARG, ) check( self, PARSER_MAKER_OF_FULL_EXPR_PARSER, Arrangement( grammar= ast.GRAMMAR_WITH_ALL_COMPONENTS, source= remaining_source(arguments)), Expectation( expression= expected, source= asrt_source.is_at_end_of_line(1), ) )
def test_successful_parse_of_expr_sans_argument(self): infix_op_and_expr_after = ' '.join([ast.INFIX_OP_A, ast.PRIMITIVE_SANS_ARG]) format_map = { 'primitive_expr': ast.PRIMITIVE_SANS_ARG, 'infix_op_and_expr_after': infix_op_and_expr_after, } def source(s: str) -> str: return s.format_map(format_map) cases = [ SourceCase( 'primitive followed by INFIX-OP EXPR', source('{primitive_expr} {infix_op_and_expr_after}'), SourceExpectation.source_is_not_at_end(current_line_number=1, remaining_part_of_current_line=infix_op_and_expr_after), ), SourceCase( '( primitive ) followed by INFIX-OP EXPR', source('( {primitive_expr} ) {infix_op_and_expr_after}'), SourceExpectation.source_is_not_at_end(current_line_number=1, remaining_part_of_current_line=infix_op_and_expr_after), ), ] # ACT & ASSERT # parse_check.check_with_must_be_on_current_line_variants( self, parse_check.PARSER_MAKER_OF_SIMPLE_EXPR_PARSER, ast.PrimitiveSansArg(), test_grammars.GRAMMARS, cases, )
def test__primitive_recursive_followed_by_binary_op_on_same_line(self): s = ast.PrimitiveSansArg() expected = ast.InfixOpA([ast.PrimitiveRecursive(s), s]) arguments = '{r} {s} {op_a} {s}'.format( r=ast.PRIMITIVE_RECURSIVE, s=ast.PRIMITIVE_SANS_ARG, op_a=ast.INFIX_OP_A, ) check( self, PARSER_MAKER_OF_FULL_EXPR_PARSER, Arrangement( grammar= ast.GRAMMAR_WITH_ALL_COMPONENTS, source= remaining_source(arguments)), Expectation( expression= expected, source= asrt_source.is_at_end_of_line(1), ) )
def test_successful_parse_of_expr_sans_argument(self): space_after = ' ' token_after = str(surrounded_by_hard_quotes('not an expression')) format_map = { 'primitive_expr': ast.PRIMITIVE_SANS_ARG, 'space_after': space_after, 'token_after': token_after, } def source(s: str) -> str: return s.format_map(format_map) cases = [ SourceCase('first line is only primitive expr', source('{primitive_expr}'), SourceExpectation.is_at_end_of_line(1)), SourceCase( 'first line is primitive expr with space around', source(' {primitive_expr}{space_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=space_after[1:])), SourceCase( 'expression is followed by non-expression', source('{primitive_expr} {token_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=token_after)), SourceCase( '( primitive )', source('( {primitive_expr} )'), SourceExpectation.is_at_end_of_line(1), ), SourceCase( '( primitive ) followed by non-expression', source('( {primitive_expr} ) {token_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=token_after), ), SourceCase( '( ( primitive ) )', source('( ( {primitive_expr} ) )'), SourceExpectation.is_at_end_of_line(1), ), ] # ACT & ASSERT # parse_check.check_with_must_be_on_current_line_variants( self, self.parser_maker, ast.PrimitiveSansArg(), GRAMMARS, cases, )
def test_combined_expression_sans_parentheses(self): # [ [ [ ref1 OPA s ] OPB s OPB ref2 ] OPA s_x OPA ref3 ] ref_1 = ast.RefExpr('symbol_1') ref_2 = ast.RefExpr('symbol_2') ref_3 = ast.RefExpr('symbol_3') s = ast.PrimitiveSansArg() s_x = ast.PrimitiveWithArg('X') e1 = ast.InfixOpA([ ref_1, s, ]) e2 = ast.InfixOpB([ e1, s, ref_2, ]) expected = ast.InfixOpA([ e2, s_x, ref_3, ]) argument_string = '{ref_1} {op_a} {s} {op_b} {s} {op_b} {ref_2} {op_a} {s_w_arg} {x} {op_a} {ref_3}'.format( s=ast.PRIMITIVE_SANS_ARG, ref_1=ref_1.symbol_name, ref_2=ref_2.symbol_name, ref_3=ref_3.symbol_name, op_a=ast.INFIX_OP_A, op_b=ast.INFIX_OP_B_THAT_IS_NOT_A_VALID_SYMBOL_NAME, s_w_arg=ast.PRIMITIVE_WITH_ARG, x=s_x.argument, ) check( self, PARSER_MAKER_OF_FULL_EXPR_PARSER, Arrangement( grammar= ast.GRAMMAR_WITH_ALL_COMPONENTS, source= remaining_source(argument_string)), Expectation( expression= expected, source= asrt_source.is_at_end_of_line(1), ) )
def test__inside_parentheses__primitive_recursive_followed_by_binary_op(self): s = ast.PrimitiveSansArg() expected = ast.InfixOpA([ast.PrimitiveRecursive(s), s]) cases = [ NameAndValue( 'bin op on same line', '( \n {r} {s} \n {op_a} \n {s} \n )'.format( r=ast.PRIMITIVE_RECURSIVE, s=ast.PRIMITIVE_SANS_ARG, op_a=ast.INFIX_OP_A, ), ), NameAndValue( 'bin op on following line', '( \n {r} {s} {op_a} \n {s} \n )'.format( r=ast.PRIMITIVE_RECURSIVE, s=ast.PRIMITIVE_SANS_ARG, op_a=ast.INFIX_OP_A, ), ), ] for case in cases: expected_source = asrt_source.is_at_end_of_line(len(case.value.splitlines())) with self.subTest(case.name): check( self, PARSER_MAKER_OF_FULL_EXPR_PARSER, Arrangement( grammar= ast.GRAMMAR_WITH_ALL_COMPONENTS, source= remaining_source(case.value)), Expectation( expression= expected, source= expected_source, ) )
def test_successful_parse_with_infix_op_expressions(self): s = ast.PrimitiveSansArg() cases = [ NArrEx( 'prefix operator binds to following primitive expression (single infix ops)', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('{p_op} {s} {bin_op} {s} {bin_op} {p_op} {s}'.format( s=ast.PRIMITIVE_SANS_ARG, p_op=ast.PREFIX_P, bin_op=ast.INFIX_OP_A, )), ), Expectation( expression=InfixOpA([PrefixOpExprP(s), s, PrefixOpExprP(s)]), source=asrt_source.is_at_end_of_line(1), ), ), NArrEx( 'prefix operator binds to following primitive expression (different infix ops)', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('{p_op} {s} {bin_op_a} {s} {bin_op_b} {p_op} {s}'.format( s=ast.PRIMITIVE_SANS_ARG, p_op=ast.PREFIX_P, bin_op_a=ast.INFIX_OP_A, bin_op_b=ast.INFIX_OP_B_THAT_IS_NOT_A_VALID_SYMBOL_NAME, )), ), Expectation( expression=InfixOpB([InfixOpA([PrefixOpExprP(s), s]), PrefixOpExprP(s)]), source=asrt_source.is_at_end_of_line(1), ), ), ] # ACT & ASSERT # parse_check.check__multi(self, PARSER_MAKER_OF_FULL_EXPR_PARSER, cases)
def test_successful_parse_with_primitive_expr(self): space_after = ' ' token_after = str(surrounded_by_hard_quotes('not an expression')) primitive_expr = ast.PrimitiveSansArg() primitive_expr_src = ast.PRIMITIVE_SANS_ARG def cases_for_operator(the_prefix_operator: str) -> List[SourceCase]: sf = StringFormatter({ 'op': the_prefix_operator, 'primitive_expr': primitive_expr_src, 'space_after': space_after, 'token_after': token_after, }) return [ SourceCase('first line is only primitive expr', sf.format('{op} {primitive_expr}'), SourceExpectation.is_at_end_of_line(1)), SourceCase( 'first line is primitive expr with space around', sf.format(' {op} {primitive_expr}{space_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=space_after[1:])), SourceCase( 'expression is followed by non-expression', sf.format('{op} {primitive_expr} {token_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=token_after)), SourceCase( '( op primitive )', sf.format('( {op} {primitive_expr} )'), SourceExpectation.is_at_end_of_line(1), ), SourceCase( 'op ( primitive )', sf.format('{op} ( {primitive_expr} )'), SourceExpectation.is_at_end_of_line(1), ), SourceCase( 'no source after operator, but expr on following line', sf.format('{op}\n{primitive_expr}'), SourceExpectation.is_at_end_of_line(2), ), ] operator_cases = [ (prefix_operator, mk_prefix_expr(primitive_expr), cases_for_operator(prefix_operator)) for prefix_operator, mk_prefix_expr in self.PREFIX_OPERATORS ] for grammar in GRAMMARS: for operator_case in operator_cases: cases = current_line_case_variants_for_grammar( operator_case[1], grammar.value, operator_case[2]) for case in cases: with self.subTest(grammar=grammar.name, prefix_operator=operator_case[0], name=case.name): parse_check.check( self, self.parser_maker, case.arrangement, case.expectation, )
def test_success_of_expression_within_parentheses_spanning_several_lines(self): s = ast.PrimitiveSansArg() cases = [ NArrEx( 'primitive expr and ) on following line', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('(', ['{s} )'.format(s=ast.PRIMITIVE_SANS_ARG)]), ), Expectation( expression=s, source=asrt_source.is_at_end_of_line(2), ), ), NArrEx( 'primitive expr and ) on following line, followed by non-expr', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('(', ['{s} ) non-expr'.format(s=ast.PRIMITIVE_SANS_ARG)]), ), Expectation( expression=s, source=asrt_source.source_is_not_at_end(current_line_number=asrt.equals(2), remaining_part_of_current_line=asrt.equals('non-expr')), ), ), NArrEx( 'primitive expr with ) on following line', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('( {s}'.format(s=ast.PRIMITIVE_SANS_ARG), [' )']), ), Expectation( expression=s, source=asrt_source.is_at_end_of_line(2), ), ), NArrEx( 'primitive expr with ) on following line, and non-expr on line after that', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('( {s}'.format(s=ast.PRIMITIVE_SANS_ARG), [' )', 'non-expr']), ), Expectation( expression=s, source=asrt_source.is_at_end_of_line(2), ), ), NArrEx( 'binary op with only ( on first line', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('(', [' {s} {op} {s} )'.format(s=ast.PRIMITIVE_SANS_ARG, op=ast.INFIX_OP_A) ]), ), Expectation( expression=InfixOpA([s, s]), source=asrt_source.is_at_end_of_line(2), ), ), NArrEx( 'binary op with ) on following line', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('( {s} {op} {s}'.format(s=ast.PRIMITIVE_SANS_ARG, op=ast.INFIX_OP_A), [' ) ']), ), Expectation( expression=InfixOpA([s, s]), source=asrt_source.is_at_end_of_line(2), ), ), ] for case in cases: with self.subTest(name=case.name): check(self, PARSER_MAKER_OF_FULL_EXPR_PARSER, case.arrangement, case.expectation )
def test_success_of_expression_within_parentheses(self): s = ast.PrimitiveSansArg() cases = [ NArrEx( 'parentheses around first expr to make nested expr instead of "linear" args to op', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('( {s} {op} {s} ) {op} {s}'.format( s=ast.PRIMITIVE_SANS_ARG, op=ast.INFIX_OP_A, )), ), Expectation( expression=InfixOpA([InfixOpA([s, s]), s]), source=asrt_source.is_at_end_of_line(1), ), ), NArrEx( 'parentheses around final (second) expr to make first op have precedence', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('{s} {op} ( {s} {op} {s} )'.format( s=ast.PRIMITIVE_SANS_ARG, op=ast.INFIX_OP_A, )), ), Expectation( expression=InfixOpA([s, InfixOpA([s, s])]), source=asrt_source.is_at_end_of_line(1), ), ), NArrEx( '"linear" (sequence) of OPA, by embedding OPB inside parentheses', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=remaining_source('{s} {op_a} ( {s} {op_b} {s} ) {op_a} {s}'.format( s=ast.PRIMITIVE_SANS_ARG, op_a=ast.INFIX_OP_A, op_b=ast.INFIX_OP_B_THAT_IS_NOT_A_VALID_SYMBOL_NAME, )), ), Expectation( expression=InfixOpA([s, InfixOpB([s, s]), s]), source=asrt_source.is_at_end_of_line(1), ), ), NArrEx( 'parentheses around expr should allow binary operator to be on separate lines', Arrangement( grammar=ast.GRAMMAR_WITH_ALL_COMPONENTS, source=source_of_lines([ '(', ast.PRIMITIVE_SANS_ARG, ast.INFIX_OP_A, ast.PRIMITIVE_SANS_ARG, ')', ]), ), Expectation( expression=InfixOpA([s, s]), source=asrt_source.is_at_end_of_line(5), ), ), ] for case in cases: with self.subTest(name=case.name): check(self, PARSER_MAKER_OF_FULL_EXPR_PARSER, case.arrangement, case.expectation )
def test_success_of_single_operator(self): valid_primitive_expressions = [ ( '{primitive_expression}'.format(primitive_expression=ast.PRIMITIVE_SANS_ARG), ast.PrimitiveSansArg() ), ( '{primitive_expression_name} {argument}'.format( primitive_expression_name=ast.PRIMITIVE_WITH_ARG, argument='primitive-expr-argument'), ast.PrimitiveWithArg('primitive-expr-argument') ), ] operators = [ ( ast.INFIX_OP_A, ast.InfixOpA, ), ( ast.INFIX_OP_B_THAT_IS_NOT_A_VALID_SYMBOL_NAME, ast.InfixOpB, ), ] def source_cases_for_expressions(primitive_expr: str, operator: str) -> List[SourceCase]: sf = StringFormatter({ 'primitive_expr': primitive_expr, 'operator': operator, 'quoted_operator': surrounded_by_soft_quotes(operator_source), 'space_after': ' ', 'quoted_string': surrounded_by_hard_quotes('quoted string'), }) def source(template: str) -> str: return sf.format(template) return [ SourceCase( 'first line is just infix op expr', source('{primitive_expr} {operator} {primitive_expr}'), SourceExpectation.is_at_end_of_line(1) ), SourceCase( 'first line is infix op expr, followed by space', source('{primitive_expr} {operator} {primitive_expr}{space_after}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=sf.format('{space_after}')[1:]) ), SourceCase( 'infix op expr followed by non-operator', source('{primitive_expr} {operator} {primitive_expr} {quoted_string}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=sf.format('{quoted_string}')) ), SourceCase( 'infix op expr followed by primitive expression', source('{primitive_expr} {operator} {primitive_expr} {primitive_expr}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=sf.format('{primitive_expr}')) ), SourceCase( 'infix op expr followed by quoted operator', source('{primitive_expr} {operator} {primitive_expr} {quoted_operator}'), SourceExpectation.source_is_not_at_end( current_line_number=1, remaining_part_of_current_line=sf.format('{quoted_operator}')) ), SourceCase( 'first line is just infix op expr: inside ()', source('( {primitive_expr} {operator} {primitive_expr} )'), SourceExpectation.is_at_end_of_line(1) ), SourceCase( 'first primitive expr inside ()', source('( {primitive_expr} ) {operator} {primitive_expr}'), SourceExpectation.is_at_end_of_line(1) ), SourceCase( 'second primitive expr inside ()', source('{primitive_expr} {operator} ( {primitive_expr} )'), SourceExpectation.is_at_end_of_line(1) ), SourceCase( 'second expr on following line', source('{primitive_expr} {operator}\n{primitive_expr}'), SourceExpectation.is_at_end_of_line(2) ), ] for valid_primitive_expr_source, expected_primitive_expr in valid_primitive_expressions: for operator_source, operator_constructor in operators: expected_expression = operator_constructor([expected_primitive_expr, expected_primitive_expr]) source_cases = source_cases_for_expressions(valid_primitive_expr_source, operator_source) cases = current_line_case_variants_for_grammar(expected_expression, ast.GRAMMAR_WITH_ALL_COMPONENTS, source_cases) for case in cases: with self.subTest(name=case.name, operator_source=operator_source, valid_primitive_expr_source=valid_primitive_expr_source): check(self, PARSER_MAKER_OF_FULL_EXPR_PARSER, case.arrangement, case.expectation)