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