diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9911557..0f5bb1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,15 +4,14 @@ on: [push, pull_request] jobs: test: - runs-on: ubuntu-latest - + timeout-minutes: 10 steps: - uses: actions/checkout@v2 - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '20' - name: Run npm steps run: | npm install diff --git a/.gitignore b/.gitignore index 401f578..7ef6d44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules output -downport \ No newline at end of file +downport +output.proto2 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index c66c47d..8d462af 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,7 @@ "configurations": [ { "name": "All Unit Tests", - "type": "pwa-node", + "type": "node", "request": "launch", "pauseForSourceMap": true, "trace": false, diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..06cfc1d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "proto", + "protobuf" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 993c072..6c69f76 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # abap-protobuf -work in progress, ABAP protobuf + +Currently only `proto2` supported + +https://protobuf.dev/reference/protobuf/proto2-spec/ + diff --git a/abaplint.json b/abaplint.json index c06c15a..da2c1ca 100644 --- a/abaplint.json +++ b/abaplint.json @@ -14,12 +14,40 @@ ], "syntax": { "version": "v740sp02", - "errorNamespace": ".", - "globalConstants": [], - "globalMacros": [] + "errorNamespace": "." }, "rules": { "call_transaction_authority_check": true, + "align_parameters": true, + "cds_comment_style": true, + "cds_legacy_view": true, + "cds_parser_error": true, + "change_if_to_case": true, + "classic_exceptions_overlap": true, + "constant_classes": true, + "easy_to_find_messages": true, + "expand_macros": true, + "fully_type_itabs": true, + "local_testclass_consistency": true, + "no_aliases": true, + "no_external_form_calls": true, + "no_inline_in_optional_branches": false, + "nrob_consistency": true, + "select_single_full_key": true, + "slow_parameter_passing": true, + "smim_consistency": true, + "sql_value_conversion": true, + "static_call_via_instance": true, + "strict_sql": false, + "superfluous_value": true, + "unnecessary_chaining": true, + "unnecessary_pragma": true, + "unnecessary_return": true, + "omit_preceding_zeros": true, + "pragma_style": true, + "prefer_corresponding": true, + "prefer_pragmas": true, + "no_chained_assignment": true, "check_subrc": true, "cyclomatic_complexity": true, "dangerous_statement": true, @@ -43,7 +71,6 @@ "line_break_style": false, "many_parentheses": true, "cyclic_oo": true, - "pragma_placement": true, "max_one_method_parameter_per_line": true, "method_implemented_twice": true, "method_overwrites_builtin": true, @@ -78,7 +105,6 @@ "forbidden_void_type": true, "fully_type_constants": true, "allowed_object_naming": true, - "check_no_handler_pragma": true, "newline_between_methods": true, "chain_mainly_declarations": true, "check_abstract": true, @@ -97,7 +123,7 @@ "class_attribute_names": true, "cloud_types": true, "colon_missing_space": true, - "commented_code": true, + "commented_code": false, "constructor_visibility_public": true, "contains_tab": true, "definitions_top": true, @@ -121,7 +147,6 @@ "line_length": true, "line_only_punc": true, "local_class_naming": true, - "local_testclass_location": true, "local_variable_names": true, "main_file_contents": true, "max_one_statement": true, diff --git a/package-lock.json b/package-lock.json index 0a90f12..ca28f46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,92 +9,101 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@abaplint/cli": "^2.93.31", - "@abaplint/runtime": "^2.1.78", - "@abaplint/transpiler-cli": "^2.1.78" + "@abaplint/cli": "^2.102.22", + "@abaplint/runtime": "^2.7.74", + "@abaplint/transpiler-cli": "^2.7.74" } }, "node_modules/@abaplint/cli": { - "version": "2.93.31", - "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.93.31.tgz", - "integrity": "sha512-s4Qjo/zIyPMSczdeyY5RWaJyNFSiTdrNBKHsmVkK2KtIvKRfY9OjR9b0vbXWaXfZ+EGYRymaU5dT6CoK71e2Hw==", + "version": "2.102.22", + "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.102.22.tgz", + "integrity": "sha512-BNVvb6Fk1/h1HvfsRL0tph28/MN1hdRLZRQwu0Rn2y0VPtUFj/yS+b7qiLEhNF8XJSFsY+Kv6IwBU9efDCyt3A==", "dev": true, "bin": { "abaplint": "abaplint" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/larshp" } }, "node_modules/@abaplint/runtime": { - "version": "2.1.78", - "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.1.78.tgz", - "integrity": "sha512-zcpnjODQh/OHtoEQISOjW/dyQq6Lq1FwaifVlRb0R5l+YMk2L4FpGwJvc9nz7KuP+iMY2mW7V3n5G+/PeWYuLA==", + "version": "2.7.74", + "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.7.74.tgz", + "integrity": "sha512-+IECl1aLnLPwCNu8Kkcup2a5BTwEPKHAANVveGA76yxReuCtqCMaw/2hfDNQipO7iLN5WpEmL831JUcSuvR+ew==", "dev": true, "dependencies": { - "temporal-polyfill": "^0.0.8" + "temporal-polyfill": "^0.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/larshp" } }, "node_modules/@abaplint/transpiler-cli": { - "version": "2.1.78", - "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-2.1.78.tgz", - "integrity": "sha512-bVOMPlINpkjBwSc/6tWciULIaq6moYms8QDyp+9RQY+uVdWja+euUXR9Gn6Cr9SUojSiypTFJykiGEMSatHpGA==", + "version": "2.7.74", + "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-2.7.74.tgz", + "integrity": "sha512-0YU6TylvOW/hP1XGDKxCEGyg/pe7wn66+8sgyDJbpi7cZyoWNrlsVvvIX803m7BjYwpawPLmkqdyPUkoUhXuFw==", "dev": true, "bin": { "abap_transpile": "abap_transpile" + }, + "funding": { + "url": "https://github.com/sponsors/larshp" } }, "node_modules/temporal-polyfill": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.0.8.tgz", - "integrity": "sha512-IuA8GhS1PRC04H/zVNAIxJvCZQum6V5HjqFj7gz1a3SMUf/Kf1xIXILNYtxrWYnGqIU/RrDRxlCKCm/vmqnBvw==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.1.1.tgz", + "integrity": "sha512-/5e4EVRA0wBI/bEhWLirSjwUg1lELhQyTXxw9zNbVhqjKvI9BLczs+3wtsoD9sn3HN2ImAMW5XJQwAiXgWT+GA==", "dev": true, "dependencies": { - "temporal-spec": "0.0.3" + "temporal-spec": "~0.1.0" } }, "node_modules/temporal-spec": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.0.3.tgz", - "integrity": "sha512-gJu7QRqn5c2vTSkYWGC4qz1i+FZ9C+Cz16UIBMRcjgXOsHfXeSIgaWUKeq/2rz1iNfFxvmF/ywqbfC6ggTpjkA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.1.0.tgz", + "integrity": "sha512-sMNggMeS6trCgMQuudgFHhX1gtBK3e+AT1zGrMsFYG1wlqtRT5E9rcvm3I1iNlvHpJX/3DO6L4qtWAuEl/T04Q==", "dev": true } }, "dependencies": { "@abaplint/cli": { - "version": "2.93.31", - "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.93.31.tgz", - "integrity": "sha512-s4Qjo/zIyPMSczdeyY5RWaJyNFSiTdrNBKHsmVkK2KtIvKRfY9OjR9b0vbXWaXfZ+EGYRymaU5dT6CoK71e2Hw==", + "version": "2.102.22", + "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.102.22.tgz", + "integrity": "sha512-BNVvb6Fk1/h1HvfsRL0tph28/MN1hdRLZRQwu0Rn2y0VPtUFj/yS+b7qiLEhNF8XJSFsY+Kv6IwBU9efDCyt3A==", "dev": true }, "@abaplint/runtime": { - "version": "2.1.78", - "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.1.78.tgz", - "integrity": "sha512-zcpnjODQh/OHtoEQISOjW/dyQq6Lq1FwaifVlRb0R5l+YMk2L4FpGwJvc9nz7KuP+iMY2mW7V3n5G+/PeWYuLA==", + "version": "2.7.74", + "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.7.74.tgz", + "integrity": "sha512-+IECl1aLnLPwCNu8Kkcup2a5BTwEPKHAANVveGA76yxReuCtqCMaw/2hfDNQipO7iLN5WpEmL831JUcSuvR+ew==", "dev": true, "requires": { - "temporal-polyfill": "^0.0.8" + "temporal-polyfill": "^0.1.1" } }, "@abaplint/transpiler-cli": { - "version": "2.1.78", - "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-2.1.78.tgz", - "integrity": "sha512-bVOMPlINpkjBwSc/6tWciULIaq6moYms8QDyp+9RQY+uVdWja+euUXR9Gn6Cr9SUojSiypTFJykiGEMSatHpGA==", + "version": "2.7.74", + "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-2.7.74.tgz", + "integrity": "sha512-0YU6TylvOW/hP1XGDKxCEGyg/pe7wn66+8sgyDJbpi7cZyoWNrlsVvvIX803m7BjYwpawPLmkqdyPUkoUhXuFw==", "dev": true }, "temporal-polyfill": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.0.8.tgz", - "integrity": "sha512-IuA8GhS1PRC04H/zVNAIxJvCZQum6V5HjqFj7gz1a3SMUf/Kf1xIXILNYtxrWYnGqIU/RrDRxlCKCm/vmqnBvw==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.1.1.tgz", + "integrity": "sha512-/5e4EVRA0wBI/bEhWLirSjwUg1lELhQyTXxw9zNbVhqjKvI9BLczs+3wtsoD9sn3HN2ImAMW5XJQwAiXgWT+GA==", "dev": true, "requires": { - "temporal-spec": "0.0.3" + "temporal-spec": "~0.1.0" } }, "temporal-spec": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.0.3.tgz", - "integrity": "sha512-gJu7QRqn5c2vTSkYWGC4qz1i+FZ9C+Cz16UIBMRcjgXOsHfXeSIgaWUKeq/2rz1iNfFxvmF/ywqbfC6ggTpjkA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.1.0.tgz", + "integrity": "sha512-sMNggMeS6trCgMQuudgFHhX1gtBK3e+AT1zGrMsFYG1wlqtRT5E9rcvm3I1iNlvHpJX/3DO6L4qtWAuEl/T04Q==", "dev": true } } diff --git a/package.json b/package.json index af4c736..0e1224c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "lint": "abaplint", "unit": "rm -rf output && abap_transpile && echo RUNNING && node output/index.mjs", - "test": "npm run lint && npm run downport && npm run unit", + "integration": "node test/test.mjs", + "test": "npm run lint && npm run downport && npm run unit && npm run integration", "downport": "rm -rf downport && cp src -r downport && abaplint --fix abaplint_downport.json" }, "repository": { @@ -21,8 +22,8 @@ }, "homepage": "https://github.com/heliconialabs/abap-protobuf#readme", "devDependencies": { - "@abaplint/cli": "^2.93.31", - "@abaplint/runtime": "^2.1.78", - "@abaplint/transpiler-cli": "^2.1.78" + "@abaplint/cli": "^2.102.22", + "@abaplint/runtime": "^2.7.74", + "@abaplint/transpiler-cli": "^2.7.74" } } diff --git a/src/model/zcl_protobuf2_enum.clas.abap b/src/model/zcl_protobuf2_enum.clas.abap new file mode 100644 index 0000000..48f4101 --- /dev/null +++ b/src/model/zcl_protobuf2_enum.clas.abap @@ -0,0 +1,34 @@ +CLASS zcl_protobuf2_enum DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES zif_protobuf2_artefact. + METHODS constructor IMPORTING iv_name TYPE string. + DATA mv_name TYPE string. + + TYPES: BEGIN OF ty_enum, + name TYPE string, + value TYPE string, + END OF ty_enum. + DATA mt_fields TYPE STANDARD TABLE OF ty_enum WITH EMPTY KEY. +ENDCLASS. + +CLASS zcl_protobuf2_enum IMPLEMENTATION. + + METHOD constructor. + mv_name = iv_name. + ENDMETHOD. + + METHOD zif_protobuf2_artefact~serialize. + rv_string = |enum { mv_name } \{\n|. + DATA(lv_spaces) = repeat( + val = | | + occ = iv_nesting + 1 ). + LOOP AT mt_fields INTO DATA(ls_field). + rv_string = rv_string && lv_spaces && ls_field-name && | = { ls_field-value };\n|. + ENDLOOP. + lv_spaces = repeat( + val = | | + occ = iv_nesting ). + rv_string = rv_string && lv_spaces && |}|. + ENDMETHOD. + +ENDCLASS. diff --git a/src/model/zcl_protobuf2_enum.clas.xml b/src/model/zcl_protobuf2_enum.clas.xml new file mode 100644 index 0000000..fcb88b0 --- /dev/null +++ b/src/model/zcl_protobuf2_enum.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_PROTOBUF2_ENUM + E + ZCL_PROTOBUF2_ENUM + 1 + X + X + X + + + + \ No newline at end of file diff --git a/src/model/zcl_protobuf2_field.clas.abap b/src/model/zcl_protobuf2_field.clas.abap new file mode 100644 index 0000000..edc0138 --- /dev/null +++ b/src/model/zcl_protobuf2_field.clas.abap @@ -0,0 +1,22 @@ +CLASS zcl_protobuf2_field DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES zif_protobuf2_artefact. + + DATA mv_label TYPE string. + DATA mv_type TYPE string. + DATA mv_field_name TYPE string. + DATA mv_options TYPE string. + DATA mv_field_number TYPE i. +ENDCLASS. + +CLASS zcl_protobuf2_field IMPLEMENTATION. + + METHOD zif_protobuf2_artefact~serialize. + DATA(lv_options) = mv_options. + IF lv_options IS NOT INITIAL. + lv_options = | { mv_options }|. + ENDIF. + rv_string = |{ mv_label } { mv_type } { mv_field_name } = { mv_field_number }{ lv_options };|. + ENDMETHOD. + +ENDCLASS. diff --git a/src/model/zcl_protobuf2_field.clas.xml b/src/model/zcl_protobuf2_field.clas.xml new file mode 100644 index 0000000..303817b --- /dev/null +++ b/src/model/zcl_protobuf2_field.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_PROTOBUF2_FIELD + E + ZCL_PROTOBUF2_FIELD + 1 + X + X + X + + + + \ No newline at end of file diff --git a/src/model/zcl_protobuf2_file.clas.abap b/src/model/zcl_protobuf2_file.clas.abap index bff7ae5..2e745f7 100644 --- a/src/model/zcl_protobuf2_file.clas.abap +++ b/src/model/zcl_protobuf2_file.clas.abap @@ -1,8 +1,19 @@ CLASS zcl_protobuf2_file DEFINITION PUBLIC. PUBLIC SECTION. - DATA mt_messages TYPE STANDARD TABLE OF REF TO zcl_protobuf2_message WITH EMPTY KEY. +* https://protobuf.dev/reference/protobuf/proto2-spec/#proto_file + + INTERFACES zif_protobuf2_artefact. + + DATA mt_artefacts TYPE STANDARD TABLE OF REF TO zif_protobuf2_artefact WITH EMPTY KEY. ENDCLASS. CLASS zcl_protobuf2_file IMPLEMENTATION. + METHOD zif_protobuf2_artefact~serialize. + rv_string = |syntax = "proto2";|. + LOOP AT mt_artefacts INTO DATA(lo_artefact). + rv_string = rv_string && |\n| && lo_artefact->serialize( ). + ENDLOOP. + ENDMETHOD. + ENDCLASS. diff --git a/src/model/zcl_protobuf2_message.clas.abap b/src/model/zcl_protobuf2_message.clas.abap index b3e4ef2..8c60364 100644 --- a/src/model/zcl_protobuf2_message.clas.abap +++ b/src/model/zcl_protobuf2_message.clas.abap @@ -1,7 +1,11 @@ CLASS zcl_protobuf2_message DEFINITION PUBLIC. PUBLIC SECTION. +* https://protobuf.dev/reference/protobuf/proto2-spec/#message_definition + INTERFACES zif_protobuf2_artefact. METHODS constructor IMPORTING iv_name TYPE string. DATA mv_name TYPE string. + + DATA mt_artefacts TYPE STANDARD TABLE OF REF TO zif_protobuf2_artefact WITH EMPTY KEY. ENDCLASS. CLASS zcl_protobuf2_message IMPLEMENTATION. @@ -10,4 +14,18 @@ CLASS zcl_protobuf2_message IMPLEMENTATION. mv_name = iv_name. ENDMETHOD. + METHOD zif_protobuf2_artefact~serialize. + rv_string = |message { mv_name } \{\n|. + DATA(lv_spaces) = repeat( + val = | | + occ = iv_nesting + 1 ). + LOOP AT mt_artefacts INTO DATA(lo_artefact). + rv_string = rv_string && lv_spaces && lo_artefact->serialize( iv_nesting + 1 ) && |\n|. + ENDLOOP. + lv_spaces = repeat( + val = | | + occ = iv_nesting ). + rv_string = rv_string && lv_spaces && |}|. + ENDMETHOD. + ENDCLASS. diff --git a/src/model/zif_protobuf2_artefact.intf.abap b/src/model/zif_protobuf2_artefact.intf.abap new file mode 100644 index 0000000..0440045 --- /dev/null +++ b/src/model/zif_protobuf2_artefact.intf.abap @@ -0,0 +1,5 @@ +INTERFACE zif_protobuf2_artefact PUBLIC. + METHODS serialize + IMPORTING iv_nesting TYPE i OPTIONAL + RETURNING VALUE(rv_string) TYPE string. +ENDINTERFACE. diff --git a/src/model/zif_protobuf2_artefact.intf.xml b/src/model/zif_protobuf2_artefact.intf.xml new file mode 100644 index 0000000..8a76c23 --- /dev/null +++ b/src/model/zif_protobuf2_artefact.intf.xml @@ -0,0 +1,15 @@ + + + + + + ZIF_PROTOBUF2_ARTEFACT + E + ZIF_PROTOBUF2_SERIALIZABLE + 2 + 1 + X + + + + diff --git a/src/parser/zcl_protobuf2_parser.clas.abap b/src/parser/zcl_protobuf2_parser.clas.abap index ef3f457..2560477 100644 --- a/src/parser/zcl_protobuf2_parser.clas.abap +++ b/src/parser/zcl_protobuf2_parser.clas.abap @@ -1,59 +1,167 @@ CLASS zcl_protobuf2_parser DEFINITION PUBLIC. PUBLIC SECTION. CLASS-METHODS parse - IMPORTING iv_proto TYPE string - RETURNING VALUE(ro_file) TYPE REF TO zcl_protobuf2_file. + IMPORTING + iv_proto TYPE string + RETURNING + VALUE(ro_file) TYPE REF TO zcl_protobuf2_file. PROTECTED SECTION. PRIVATE SECTION. CLASS-METHODS traverse IMPORTING - io_file TYPE REF TO zcl_protobuf2_file - io_stream TYPE REF TO lcl_stream. - CLASS-METHODS message_body IMPORTING io_stream TYPE REF TO lcl_stream. + io_stream TYPE REF TO lcl_stream + RETURNING + VALUE(ro_file) TYPE REF TO zcl_protobuf2_file. + + CLASS-METHODS message + IMPORTING + io_stream TYPE REF TO lcl_stream + RETURNING + VALUE(ro_message) TYPE REF TO zcl_protobuf2_message. + + CLASS-METHODS field + IMPORTING + io_stream TYPE REF TO lcl_stream + RETURNING + VALUE(ro_field) TYPE REF TO zcl_protobuf2_field. + + CLASS-METHODS enum + IMPORTING + io_stream TYPE REF TO lcl_stream + RETURNING + VALUE(ro_enum) TYPE REF TO zcl_protobuf2_enum. + + CLASS-METHODS remove_comments + IMPORTING + iv_input TYPE string + RETURNING + VALUE(rv_output) TYPE string. ENDCLASS. CLASS zcl_protobuf2_parser IMPLEMENTATION. + METHOD enum. +* https://protobuf.dev/reference/protobuf/proto2-spec/#enum_definition + ro_enum = NEW #( io_stream->take_token( ) ). - METHOD message_body. + DATA(lo_stream) = io_stream->take_matching_squiggly( ). + WHILE lo_stream->is_empty( ) = abap_false. + DATA(lo_statement) = lo_stream->take_statement( ). + DATA(lv_name) = lo_statement->take_token( ). + lo_statement->take_token( ). + DATA(lv_value) = lo_statement->take_token( ). + APPEND VALUE #( + name = lv_name + value = lv_value ) TO ro_enum->mt_fields. + ENDWHILE. + + ENDMETHOD. + + METHOD field. +* https://protobuf.dev/reference/protobuf/proto2-spec/#fields + + ro_field = NEW #( ). + ro_field->mv_label = io_stream->take_token( ). + ro_field->mv_type = io_stream->take_token( ). + ro_field->mv_field_name = io_stream->take_token( ). + ASSERT io_stream->take_token( ) = '='. + ro_field->mv_field_number = io_stream->take_token( ). + + ro_field->mv_options = io_stream->get( ). + ENDMETHOD. + + METHOD message. * https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#message_definition - ASSERT io_stream IS NOT INITIAL. - RETURN. " todo + ro_message = NEW #( io_stream->take_token( ) ). + + DATA(lo_stream) = io_stream->take_matching_squiggly( ). + + WHILE lo_stream->is_empty( ) = abap_false. + DATA(lv_token) = lo_stream->peek_token( ). + CASE lv_token. + WHEN 'message'. + lo_stream->take_token( ). + APPEND message( lo_stream ) TO ro_message->mt_artefacts. + WHEN 'enum'. + lo_stream->take_token( ). + APPEND enum( lo_stream ) TO ro_message->mt_artefacts. + WHEN OTHERS. + APPEND field( lo_stream->take_statement( ) ) TO ro_message->mt_artefacts. + ENDCASE. + ENDWHILE. ENDMETHOD. METHOD parse. ASSERT iv_proto IS NOT INITIAL. - DATA(lv_proto) = condense( iv_proto ). + DATA(lv_proto) = remove_comments( iv_proto ). + + WHILE lv_proto(1) = |\n|. + lv_proto = lv_proto+1. + ENDWHILE. + ASSERT lv_proto CP |syntax = "proto2";*|. REPLACE FIRST OCCURRENCE OF |syntax = "proto2";| IN lv_proto WITH ''. - REPLACE ALL OCCURRENCES OF |\n| IN lv_proto WITH | |. - ro_file = NEW #( ). + REPLACE ALL OCCURRENCES OF |\n| IN lv_proto WITH | |. + REPLACE ALL OCCURRENCES OF |\t| IN lv_proto WITH | |. - traverse( - io_file = ro_file - io_stream = NEW lcl_stream( lv_proto ) ). + ro_file = traverse( NEW lcl_stream( lv_proto ) ). ENDMETHOD. + METHOD remove_comments. + DATA lv_start TYPE i. + DATA lv_end TYPE i. + + rv_output = condense( iv_input ). + WHILE 1 = 1. + FIND FIRST OCCURRENCE OF '/*' IN rv_output MATCH OFFSET lv_start. + IF sy-subrc <> 0. + EXIT. + ENDIF. + FIND FIRST OCCURRENCE OF '*/' IN rv_output MATCH OFFSET lv_end. + IF sy-subrc <> 0. + EXIT. + ENDIF. + + lv_end = lv_end + 2. + rv_output = rv_output(lv_start) && rv_output+lv_end. + ENDWHILE. + + SPLIT rv_output AT |\n| INTO TABLE DATA(lt_lines). + LOOP AT lt_lines ASSIGNING FIELD-SYMBOL(). + FIND FIRST OCCURRENCE OF '//' IN MATCH OFFSET lv_start. + IF sy-subrc = 0. + = (lv_start). + ENDIF. + ENDLOOP. + CONCATENATE LINES OF lt_lines INTO rv_output SEPARATED BY |\n|. + + CONDENSE rv_output. + ENDMETHOD. METHOD traverse. * https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#proto_file + ro_file = NEW #( ). + WHILE io_stream->is_empty( ) = abap_false. DATA(lv_token) = io_stream->take_token( ). CASE lv_token. + WHEN 'package'. + io_stream->take_statement( ). + WHEN 'option'. + io_stream->take_statement( ). WHEN 'message'. - DATA(lo_message) = NEW zcl_protobuf2_message( io_stream->take_token( ) ). - APPEND lo_message TO io_file->mt_messages. - WRITE: / 'Message:', lo_message->mv_name. - message_body( io_stream->take_matching( ) ). + APPEND message( io_stream ) TO ro_file->mt_artefacts. + WHEN 'enum'. + APPEND enum( io_stream ) TO ro_file->mt_artefacts. WHEN OTHERS. WRITE: / 'todo, handle token:', lv_token. - ASSERT 1 = 2. + ASSERT 1 = 'todo'. ENDCASE. ENDWHILE. ENDMETHOD. diff --git a/src/parser/zcl_protobuf2_parser.clas.locals_def.abap b/src/parser/zcl_protobuf2_parser.clas.locals_def.abap index 8d0de40..a9ef931 100644 --- a/src/parser/zcl_protobuf2_parser.clas.locals_def.abap +++ b/src/parser/zcl_protobuf2_parser.clas.locals_def.abap @@ -8,7 +8,9 @@ CLASS lcl_stream DEFINITION. METHODS take_token RETURNING VALUE(rv_token) TYPE string. METHODS peek_token RETURNING VALUE(rv_token) TYPE string. METHODS is_empty RETURNING VALUE(rv_empty) TYPE abap_bool. - METHODS take_matching RETURNING VALUE(ro_stream) TYPE REF TO lcl_stream. + METHODS get RETURNING VALUE(rv_str) TYPE string. + METHODS take_statement RETURNING VALUE(ro_stream) TYPE REF TO lcl_stream. + METHODS take_matching_squiggly RETURNING VALUE(ro_stream) TYPE REF TO lcl_stream. PRIVATE SECTION. DATA mv_str TYPE string. ENDCLASS. diff --git a/src/parser/zcl_protobuf2_parser.clas.locals_imp.abap b/src/parser/zcl_protobuf2_parser.clas.locals_imp.abap index b09ce9f..74edb64 100644 --- a/src/parser/zcl_protobuf2_parser.clas.locals_imp.abap +++ b/src/parser/zcl_protobuf2_parser.clas.locals_imp.abap @@ -4,17 +4,39 @@ CLASS lcl_stream IMPLEMENTATION. CONDENSE mv_str. ENDMETHOD. + METHOD get. + rv_str = mv_str. + ENDMETHOD. + METHOD is_empty. - rv_empty = boolc( strlen( mv_str ) = 0 ). + rv_empty = boolc( strlen( condense( mv_str ) ) = 0 ). ENDMETHOD. METHOD take_token. DATA lv_offset TYPE i. + + IF mv_str(1) = '='. + " its a special character, so ignore spaces + rv_token = mv_str(1). + mv_str = mv_str+1. + CONDENSE mv_str. + RETURN. + ENDIF. + FIND FIRST OCCURRENCE OF | | IN mv_str MATCH OFFSET lv_offset. IF sy-subrc <> 0. + rv_token = mv_str. + mv_str = ''. RETURN. ENDIF. + rv_token = mv_str(lv_offset). + + IF strlen( rv_token ) > 1 AND rv_token CP '*='. + REPLACE FIRST OCCURRENCE OF '=' IN rv_token WITH ||. + lv_offset = lv_offset - 1. + ENDIF. + mv_str = mv_str+lv_offset. CONDENSE mv_str. ENDMETHOD. @@ -28,7 +50,20 @@ CLASS lcl_stream IMPLEMENTATION. rv_token = mv_str(lv_offset). ENDMETHOD. - METHOD take_matching. + METHOD take_statement. + DATA lv_offset TYPE i. + + FIND FIRST OCCURRENCE OF |;| IN mv_str MATCH OFFSET lv_offset. + ASSERT sy-subrc = 0. + + ro_stream = NEW #( mv_str(lv_offset) ). + + lv_offset = lv_offset + 1. + mv_str = mv_str+lv_offset. + CONDENSE mv_str. + ENDMETHOD. + + METHOD take_matching_squiggly. DATA lt_open TYPE match_result_tab. DATA lt_close TYPE match_result_tab. DATA lt_all TYPE match_result_tab. @@ -59,6 +94,11 @@ CLASS lcl_stream IMPLEMENTATION. ENDLOOP. DATA(lv_tmp) = mv_str(ls_all-offset). + " remove the squirly brackets, + lv_tmp = lv_tmp+1. + lv_count = strlen( lv_tmp ) - 1. + lv_tmp = lv_tmp(lv_count). + ro_stream = NEW #( lv_tmp ). mv_str = mv_str+ls_all-offset. CONDENSE mv_str. diff --git a/src/parser/zcl_protobuf2_parser.clas.testclasses.abap b/src/parser/zcl_protobuf2_parser.clas.testclasses.abap index ae853ac..79ccedc 100644 --- a/src/parser/zcl_protobuf2_parser.clas.testclasses.abap +++ b/src/parser/zcl_protobuf2_parser.clas.testclasses.abap @@ -1,17 +1,18 @@ CLASS ltcl_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS FINAL. PRIVATE SECTION. - METHODS parse IMPORTING iv_spec TYPE string RAISING cx_static_check. - METHODS test1 FOR TESTING RAISING cx_static_check. - METHODS test2 FOR TESTING RAISING cx_static_check. + METHODS identity1 FOR TESTING RAISING cx_static_check. + METHODS identity2 FOR TESTING RAISING cx_static_check. + + METHODS without_space FOR TESTING RAISING cx_static_check. + METHODS without_space2 FOR TESTING RAISING cx_static_check. + METHODS test_tabs FOR TESTING RAISING cx_static_check. + + METHODS remove_comments1 FOR TESTING RAISING cx_static_check. ENDCLASS. CLASS ltcl_test IMPLEMENTATION. - METHOD parse. - zcl_protobuf2_parser=>parse( iv_spec ). - ENDMETHOD. - - METHOD test1. + METHOD identity1. DATA(lv_proto) = |syntax = "proto2";\n| && @@ -30,11 +31,70 @@ CLASS ltcl_test IMPLEMENTATION. | optional string label = 2;\n| && |\}|. - parse( lv_proto ). + DATA(lo_file) = zcl_protobuf2_parser=>parse( lv_proto ). + + cl_abap_unit_assert=>assert_equals( + exp = 3 + act = lines( lo_file->mt_artefacts ) ). + + cl_abap_unit_assert=>assert_equals( + exp = lv_proto + act = lo_file->zif_protobuf2_artefact~serialize( ) ). ENDMETHOD. - METHOD test2. + METHOD without_space. + + DATA(lv_proto) = + |syntax = "proto2";\n| && + |message Polyline \{\n| && + | optional string label =2;\n| && + |\}|. + + DATA(lo_file) = zcl_protobuf2_parser=>parse( lv_proto ). + + cl_abap_unit_assert=>assert_equals( + exp = 1 + act = lines( lo_file->mt_artefacts ) ). + + ENDMETHOD. + + METHOD without_space2. + + DATA(lv_proto) = + |syntax = "proto2";\n| && + |message Polyline \{\n| && + | optional string subscription= 4;\n| && + |\}|. + + DATA(lo_file) = zcl_protobuf2_parser=>parse( lv_proto ). + + cl_abap_unit_assert=>assert_equals( + exp = 1 + act = lines( lo_file->mt_artefacts ) ). + + ENDMETHOD. + + METHOD test_tabs. + + DATA(lv_proto) = + |syntax = "proto2";\n| && + |message Polyline \{\n| && + | \t enum ResourceType \{\n| && + | Producer = 0;\n| && + | \}\n| && + | optional string label =2;\n| && + |\}|. + + DATA(lo_file) = zcl_protobuf2_parser=>parse( lv_proto ). + + cl_abap_unit_assert=>assert_equals( + exp = 1 + act = lines( lo_file->mt_artefacts ) ). + + ENDMETHOD. + + METHOD identity2. DATA(lv_proto) = |syntax = "proto2";\n| && |message Person \{\n| && @@ -55,8 +115,26 @@ CLASS ltcl_test IMPLEMENTATION. |message AddressBook \{\n| && | repeated Person people = 1;\n| && |\}|. -* todo, parse( lv_proto ). - ASSERT lv_proto IS NOT INITIAL. + + DATA(lo_file) = zcl_protobuf2_parser=>parse( lv_proto ). + + cl_abap_unit_assert=>assert_equals( + exp = lv_proto + act = lo_file->zif_protobuf2_artefact~serialize( ) ). + + ENDMETHOD. + + METHOD remove_comments1. + + DATA(lv_proto) = + |syntax = "proto2";\n| && + |/* hello world */| && + |message AddressBook \{ // hello world \n| && + | repeated Person people = 1;\n| && + |\}|. + + zcl_protobuf2_parser=>parse( lv_proto ). + ENDMETHOD. ENDCLASS. diff --git a/test/links.txt b/test/links.txt new file mode 100644 index 0000000..8b2ac74 --- /dev/null +++ b/test/links.txt @@ -0,0 +1,3 @@ +https://github.com/apache/pulsar/blob/master/pulsar-common/src/main/proto/PulsarApi.proto + +https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto \ No newline at end of file diff --git a/test/test.mjs b/test/test.mjs new file mode 100644 index 0000000..b9b4541 --- /dev/null +++ b/test/test.mjs @@ -0,0 +1,17 @@ +import * as fs from "node:fs"; + +await import("../output/init.mjs"); + +async function run() { + const response = await fetch('https://raw.githubusercontent.com/apache/pulsar/master/pulsar-common/src/main/proto/PulsarApi.proto'); + const proto = await response.text(); + + const result = await abap.Classes["ZCL_PROTOBUF2_PARSER"].parse({iv_proto: proto}); + + const serialized = await result.get().zif_protobuf2_artefact$serialize(); + + fs.writeFileSync("output.proto2", serialized.get()); +} + +console.log("Integration testing"); +await run(); \ No newline at end of file