Skip to content

Commit

Permalink
(wip) finish expression parsing, de Morgan's laws
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoern-jueliger-sap committed Mar 6, 2024
1 parent e62f475 commit 6f58ad4
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 79 deletions.
38 changes: 37 additions & 1 deletion src/checks/#cc4a#proper_bool_expression.clas.abap
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ class /cc4a/proper_bool_expression definition
procedure type if_ci_atc_source_code_provider=>ty_procedure
statement_idx type i
returning value(findings) type if_ci_atc_check=>ty_findings.
methods analyze_initial_bool_condition
importing
procedure type if_ci_atc_source_code_provider=>ty_procedure
statement_idx type i
returning value(findings) type if_ci_atc_check=>ty_findings.
methods are_inverse_booleans
importing
bool_1 type ty_boolean
Expand Down Expand Up @@ -148,6 +153,9 @@ class /cc4a/proper_bool_expression implementation.
pseudo_comment = 'BOOL_VAL' )
( code = finding_codes-transform_to_xsd
text = 'IF...ENDIF block can be replaced by inline XSDBOOL( )'(xsd)
pseudo_comment = 'BOOL_VAL' )
( code = finding_codes-initial
text = 'IS (NOT) INITIAL can be replaced by comparison with ABAP_BOOL'(ini)
pseudo_comment = 'BOOL_VAL' ) )
quickfix_codes = value #(
( code = quickfix_codes-if_else short_text = 'Replace with xsdbool'(qie) )
Expand Down Expand Up @@ -213,6 +221,8 @@ class /cc4a/proper_bool_expression implementation.
insert lines of
analyze_pot_xsd_transform( procedure = procedure statement_idx = statement_idx ) into table findings.
endif.
insert lines of
analyze_initial_bool_condition( procedure = procedure statement_idx = statement_idx ) into table findings.
endif.
if <statement>-keyword = 'COMPUTE'.
if <statement>-tokens[ lines( <statement>-tokens ) - 1 ]-lexeme = '='.
Expand Down Expand Up @@ -373,7 +383,10 @@ class /cc4a/proper_bool_expression implementation.
assign declarations[ declared_identifier = <accessed_component> ]
to field-symbol(<declaration>).
if sy-subrc = 0.
return xsdbool( <declaration>-type-name = '\TY:ABAP_BOOL' or <declaration>-kind = declaration_kind-inline ).
return xsdbool(
<declaration>-type-name = '\TY:ABAP_BOOL'
or ( <declaration>-kind = declaration_kind-inline
and to_boolean( value #( lexeme = <declaration>-value ) ) ) ).
else.
if lines( token-references ) > 1.
assign token-references[ 1 ]-full_name to field-symbol(<main_object>).
Expand Down Expand Up @@ -477,4 +490,27 @@ class /cc4a/proper_bool_expression implementation.
code = analyzer->break_into_lines( xsd_statement ) ).
ENDMETHOD.

method analyze_initial_bool_condition.
assign procedure-statements[ statement_idx ] to field-symbol(<statement>).
loop at <statement>-tokens assigning field-symbol(<token>).
data(token_idx) = sy-tabix.
if analyzer->is_token_keyword( token = <token> keyword = 'INITIAL' ).
assign <statement>-tokens[ token_idx - 1 ] to field-symbol(<previous_token>).
if ( analyzer->is_token_keyword( token = <previous_token> keyword = 'IS' )
and is_boolean_typed( <statement>-tokens[ token_idx - 2 ] ) )
or ( analyzer->is_token_keyword( token = <previous_token> keyword = 'NOT' )
and analyzer->is_token_keyword( token = <statement>-tokens[ token_idx - 2 ] keyword = 'IS' )
and is_boolean_typed( <statement>-tokens[ token_idx - 3 ] ) ).
insert value #(
code = finding_codes-initial
location = code_provider->get_statement_location( <statement> )
checksum = code_provider->get_statement_checksum( <statement> )
has_pseudo_comment = meta_data->has_valid_pseudo_comment(
statement = <statement>
finding_code = finding_codes-initial ) ) into table findings.
endif.
endif.
endloop.
endmethod.

endclass.
100 changes: 49 additions & 51 deletions src/checks/#cc4a#proper_bool_expression.clas.testclasses.abap
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,19 @@ class test definition final
methods if_then_else
importing position type if_ci_atc_check=>ty_position
returning value(location) type if_ci_atc_check=>ty_location.
methods bool_usage
importing position type if_ci_atc_check=>ty_position
returning value(location) type if_ci_atc_check=>ty_location.
methods bool_initial
importing position type if_ci_atc_check=>ty_position
returning value(location) type if_ci_atc_check=>ty_location.
methods public_section
importing position type if_ci_atc_check=>ty_position
returning value(location) type if_ci_atc_check=>ty_location.
endclass.

class test implementation.
method execute_test_class.
data(finding13) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_correct_bool_usage ) )
position = value #( line = 3 column = 4 ) ).
data(finding14) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_correct_bool_usage ) )
position = value #( line = 4 column = 4 ) ).
data(finding15) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_correct_bool_usage ) )
position = value #( line = 5 column = 4 ) ).
data(finding16) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_correct_bool_usage ) )
position = value #( line = 6 column = 4 ) ).
data(finding17) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_class_section_object(
value #( class = test_class kind = cl_ci_atc_unit_driver=>class_section_kind-public ) )
position = value #( line = 7 column = 4 ) ).
data(finding18) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_bool_initial ) )
position = value #( line = 4 column = 4 ) ).
data(finding19) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_bool_initial ) )
position = value #( line = 2 column = 4 ) ).
data(finding20) = value if_ci_atc_check=>ty_location(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_bool_initial ) )
position = value #( line = 6 column = 4 ) ).

data(transform_to_xsd_findings) = value if_ci_atc_unit_asserter=>ty_expected_findings(
code = /cc4a/proper_bool_expression=>finding_codes-transform_to_xsd
( location = if_then_else( value #( line = 5 column = 4 ) )
Expand Down Expand Up @@ -130,22 +106,22 @@ class test implementation.
( `DATA(C) = xsdbool( TABLE2[ 4 ]-TABLE[ 1 ]-BOOLEAN = ABAP_FALSE ).` ) ( ` ` ) ( ` ` ) ( ` ` ) ( ` ` ) ) ) ) ) ).
data(bool_value_findings) = value if_ci_atc_unit_asserter=>ty_expected_findings(
code = /cc4a/proper_bool_expression=>finding_codes-boolean_value
( location = finding13
( location = bool_usage( value #( line = 3 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-character_equivalence
location = finding13
location = bool_usage( value #( line = 3 column = 4 ) )
code = value #(
( `T = ABAP_TRUE .` ) ) ) ) )
( location = finding14
( location = bool_usage( value #( line = 4 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-character_equivalence
location = finding14
location = bool_usage( value #( line = 4 column = 4 ) )
code = value #(
( `NUMBER_BOOL_STRUCTURE-BOOLEAN = ABAP_FALSE .` ) ) ) ) )
( location = finding15
( location = bool_usage( value #( line = 5 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-character_equivalence
location = finding15
location = bool_usage( value #( line = 5 column = 4 ) )
code = value #(
( `A = ABAP_FALSE .` ) ) ) ) )
( location = if_then_else( value #( line = 49 column = 6 ) )
Expand All @@ -160,36 +136,36 @@ class test implementation.
location = if_then_else( value #( line = 51 column = 6 ) )
code = value #(
( `B = ABAP_TRUE.` ) ) ) ) )
( location = finding16
( location = bool_usage( value #( line = 6 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-character_equivalence
location = finding16
location = bool_usage( value #( line = 6 column = 4 ) )
code = value #(
( `TEST_STRUC_NAB-NAB-BOOLEAN = ABAP_TRUE .` ) ) ) ) )
( location = finding17
( location = public_section( value #( line = 7 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-character_equivalence
location = finding17
location = public_section( value #( line = 7 column = 4 ) )
code = value #(
( `CONSTANTS: BOOL TYPE abap_bool VALUE ABAP_TRUE.` ) ) ) ) ) ).
data(initial) = value if_ci_atc_unit_asserter=>ty_expected_findings(
data(initial_findings) = value if_ci_atc_unit_asserter=>ty_expected_findings(
code = /cc4a/proper_bool_expression=>finding_codes-initial
( location = finding18
( location = bool_initial( value #( line = 4 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-initial_boolean
location = finding18
location = bool_initial( value #( line = 4 column = 4 ) )
code = value #(
( `IF TABLE2[ 4 ]-TABLE[ 1 ]-BOOLEAN = ABAP_FALSE .` ) ) ) ) )
( location = finding19
( location = bool_initial( value #( line = 2 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-initial_boolean
location = finding19
location = bool_initial( value #( line = 2 column = 4 ) )
code = value #(
( `IF A = ABAP_FALSE .` ) ) ) ) )
( location = finding20
( location = bool_initial( value #( line = 6 column = 4 ) )
quickfixes = value #( (
quickfix_code = /cc4a/proper_bool_expression=>quickfix_codes-initial_boolean
location = finding20
location = bool_initial( value #( line = 6 column = 4 ) )
code = value #(
( `IF TEST_STRUC_NAB-NAB-BOOLEAN = ABAP_FALSE .` ) ) ) ) ) ).

Expand All @@ -198,7 +174,8 @@ class test implementation.
object = value #( type = 'CLASS' name = test_class )
expected_findings = value #(
( lines of transform_to_xsd_findings )
( lines of bool_value_findings ) )
( lines of bool_value_findings )
( lines of initial_findings ) )
asserter_config = value #( quickfixes = abap_false ) ).
endmethod.
method if_then_else.
Expand All @@ -208,4 +185,25 @@ class test implementation.
position = position ).
endmethod.

method bool_usage.
return value #(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_correct_bool_usage ) )
position = position ).
endmethod.

method bool_initial.
return value #(
object = cl_ci_atc_unit_driver=>get_method_object(
value #( class = test_class method = test_class_methods-test_bool_initial ) )
position = position ).
endmethod.

method public_section.
return value #(
object = cl_ci_atc_unit_driver=>get_class_section_object(
value #( class = test_class kind = cl_ci_atc_unit_driver=>class_section_kind-public ) )
position = position ).
endmethod.

endclass.
88 changes: 72 additions & 16 deletions src/core/#cc4a#abap_analyzer.clas.abap
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class /cc4a/abap_analyzer definition
importing tokens type if_ci_atc_source_code_provider=>ty_tokens
returning value(expression) type ty_logical_expression.
methods _parse_logical_expression
importing offsets type ty_offsets
importing value(offsets) type ty_offsets
changing tokens type if_ci_atc_source_code_provider=>ty_tokens
returning value(expression) type ty_logical_expression.
endclass.
Expand Down Expand Up @@ -454,8 +454,45 @@ class /cc4a/abap_analyzer implementation.
method /cc4a/if_abap_analyzer~negate_logical_expression.
data(new_tokens) = tokens.
data(expression) = parse_logical_expression( tokens ).
insert value #( lexeme = 'NOT (' ) into new_tokens index 1.
insert value #( lexeme = ')' ) into table new_tokens.
" If true, this is a complex expression we probably don't want to negate "in a clever way".
if lines( expression ) > 3.
insert value #( lexeme = 'NOT (' ) into new_tokens index 1.
insert value #( lexeme = ')' ) into table new_tokens.
elseif lines( expression ) > 1.
assign expression[ 1 ] to field-symbol(<connective>).
case <connective>-connective.
when /cc4a/if_abap_analyzer=>logical_connective-and or /cc4a/if_abap_analyzer=>logical_connective-or.
data(left) = /cc4a/if_abap_analyzer~negate_logical_expression(
value #( for <t> in new_tokens from expression[ 2 ]-tokens-from to expression[ 2 ]-tokens-to ( <t> ) ) ).
data(right) = /cc4a/if_abap_analyzer~negate_logical_expression(
value #( for <t> in new_tokens from expression[ 3 ]-tokens-from to expression[ 3 ]-tokens-to ( <t> ) ) ).
case <connective>-connective.
when /cc4a/if_abap_analyzer=>logical_connective-and.
return |{ left } OR { right }|.
when /cc4a/if_abap_analyzer=>logical_connective-or.
return |{ left } AND { right }|.

endcase.
endcase.
else.
data(tokens_end) = lines( new_tokens ).
if is_token_keyword( token = new_tokens[ tokens_end ] keyword = 'INITIAL' ).
if is_token_keyword( token = new_tokens[ tokens_end - 1 ] keyword = 'NOT' ).
delete new_tokens index tokens_end - 1.
else.
insert value #( lexeme = 'NOT' ) into new_tokens index tokens_end.
endif.
elseif is_token_keyword( token = new_tokens[ 2 ] keyword = 'IN' ).
insert value #( lexeme = 'NOT' ) into new_tokens index 2.
elseif is_token_keyword( token = new_tokens[ 2 ] keyword = 'NOT' ).
delete new_tokens index 2.
elseif /cc4a/if_abap_analyzer~token_is_comparison_operator( token = new_tokens[ 2 ] ).
new_tokens[ 2 ]-lexeme = /cc4a/if_abap_analyzer~negate_comparison_operator( new_tokens[ 2 ]-lexeme ).
else.
insert value #( lexeme = 'NOT (' ) into new_tokens index 1.
insert value #( lexeme = ')' ) into table new_tokens.
endif.
endif.
return /cc4a/if_abap_analyzer~flatten_tokens( new_tokens ).
endmethod.

Expand All @@ -475,25 +512,37 @@ class /cc4a/abap_analyzer implementation.
if /cc4a/if_abap_analyzer~is_logical_connective( tokens[ 1 ] ) = /cc4a/if_abap_analyzer=>logical_connective-not.
delete tokens index 1.
return value #(
( connective = /cc4a/if_abap_analyzer=>logical_connective-not left = offsets-table + 1 )
( connective = /cc4a/if_abap_analyzer=>logical_connective-not left = offsets-table + 1
tokens = value #( from = offsets-token to = offsets-token ) )
( lines of _parse_logical_expression(
exporting offsets = value #( token = offsets-token + 1 table = offsets-table )
changing tokens = tokens ) ) ).
endif.
data(look_for_closing_paren) = xsdbool( tokens[ 1 ]-lexeme = '(' ).
if look_for_closing_paren = abap_true.
delete tokens index 1.
offsets-token += 1.
open_brackets = 1.
endif.
while tokens is not initial.
assign tokens[ 1 ] to field-symbol(<token>).
if open_brackets = 0.
if look_for_closing_paren = abap_true and open_brackets = 1 and <token>-lexeme = ')'.
look_for_closing_paren = abap_false.
delete tokens index 1.
open_brackets = 0.
continue.
if look_for_closing_paren = abap_true and open_brackets = 1 and <token>-lexeme = ')'.
look_for_closing_paren = abap_false.
delete tokens index 1.
assign tokens[ 1 ] to <token>.
open_brackets = 0.
if tokens is initial.
data(left_bracketed) = _parse_logical_expression(
exporting offsets = value #( token = offsets-token table = offsets-table )
changing tokens = left_tokens ).
return left_bracketed.
else.
left_bracketed = _parse_logical_expression(
exporting offsets = value #( token = offsets-token table = offsets-table + 1 )
changing tokens = left_tokens ).
endif.
endif.
if open_brackets = 0.
data(connective) = /cc4a/if_abap_analyzer~is_logical_connective( <token> ).
case connective.
when /cc4a/if_abap_analyzer=>logical_connective-none or
Expand All @@ -506,16 +555,23 @@ class /cc4a/abap_analyzer implementation.
endcase.

when others.
data(left_token_len) = lines( left_tokens ).
data(left) = _parse_logical_expression(
exporting offsets = value #( token = offsets-token table = offsets-table + 1 )
changing tokens = left_tokens ).
if left_bracketed is not initial.
data(left_token_len) = left_bracketed[ lines( left_bracketed ) ]-tokens-to - offsets-token + 2.
data(left) = left_bracketed.
else.
left_token_len = lines( left_tokens ).
left = _parse_logical_expression(
exporting offsets = value #( token = offsets-token table = offsets-table + 1 )
changing tokens = left_tokens ).
endif.
delete tokens index 1.
data(connective_token_offset) = offsets-token + left_token_len.
data(right) = _parse_logical_expression(
exporting offsets = value #( token = offsets-token + left_token_len + 1 table = offsets-table + 1 + lines( left ) )
exporting offsets = value #( token = connective_token_offset + 1 table = offsets-table + 1 + lines( left ) )
changing tokens = tokens ).
return value #(
( connective = connective left = offsets-table + 1 right = offsets-table + lines( left ) + 1 )
( connective = connective left = offsets-table + 1 right = offsets-table + lines( left ) + 1
tokens = value #( from = connective_token_offset to = connective_token_offset ) )
( lines of left )
( lines of right ) ).
endcase.
Expand Down
Loading

0 comments on commit 6f58ad4

Please sign in to comment.