abap-parser 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {abap_parser-0.1.0 → abap_parser-0.2.0}/Cargo.lock +9 -9
- {abap_parser-0.1.0 → abap_parser-0.2.0}/Cargo.toml +1 -1
- {abap_parser-0.1.0 → abap_parser-0.2.0}/PKG-INFO +8 -3
- {abap_parser-0.1.0 → abap_parser-0.2.0}/README.md +7 -2
- {abap_parser-0.1.0 → abap_parser-0.2.0}/bindings/python/README.md +7 -2
- {abap_parser-0.1.0 → abap_parser-0.2.0}/bindings/python/src/lib.rs +23 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/bindings/python/tests/test_symbols.py +17 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/calls.rs +188 -46
- abap_parser-0.2.0/crates/abap-analysis/src/ddic.rs +202 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/lib.rs +65 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/logic.rs +382 -13
- abap_parser-0.2.0/crates/abap-analysis/src/stores.rs +212 -0
- abap_parser-0.2.0/crates/abap-analysis/src/text.rs +805 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/workspace.rs +395 -8
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/logic_corpus.rs +16 -2
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-model/src/lib.rs +128 -4
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/adt.rs +186 -8
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/cache.rs +14 -2
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/fs.rs +130 -34
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/lib.rs +72 -2
- abap_parser-0.2.0/crates/abap-sql/src/cds.rs +995 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-sql/src/lib.rs +1 -0
- abap_parser-0.2.0/crates/abap-sql/tests/cds_corpus.rs +59 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/python/abap_parser/__init__.py +40 -1
- {abap_parser-0.1.0 → abap_parser-0.2.0}/python/abap_parser/types.py +90 -5
- {abap_parser-0.1.0 → abap_parser-0.2.0}/LICENSE +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/bindings/python/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/bindings/python/LICENSE +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/args.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/context.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/db.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/src/symbols.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/adt_mock.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/amdp_corpus.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/call_graph_corpus.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/corpus_smoke.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-analysis/tests/symbols_diff.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-lexer/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-lexer/src/lib.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-model/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/mock.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-source/src/options.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-sql/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-sql/src/hana.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-sql/src/tests.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-sql/tests/sql_diff.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/Cargo.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/classify.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/decl.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/expr.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/kind.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/lib.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/operands.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/src/structure.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/tests/abaplint_statements.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/tests/common/mod.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/tests/oracle_diff.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/crates/abap-syntax/tests/refs_diff.rs +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/pyproject.toml +0 -0
- {abap_parser-0.1.0 → abap_parser-0.2.0}/python/abap_parser/py.typed +0 -0
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "abap-analysis"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"abap-model",
|
|
10
10
|
"abap-source",
|
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
|
16
16
|
|
|
17
17
|
[[package]]
|
|
18
18
|
name = "abap-cli"
|
|
19
|
-
version = "0.
|
|
19
|
+
version = "0.2.0"
|
|
20
20
|
dependencies = [
|
|
21
21
|
"abap-analysis",
|
|
22
22
|
"abap-lexer",
|
|
@@ -28,14 +28,14 @@ dependencies = [
|
|
|
28
28
|
|
|
29
29
|
[[package]]
|
|
30
30
|
name = "abap-lexer"
|
|
31
|
-
version = "0.
|
|
31
|
+
version = "0.2.0"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"serde",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "abap-model"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.2.0"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"schemars",
|
|
41
41
|
"serde",
|
|
@@ -44,7 +44,7 @@ dependencies = [
|
|
|
44
44
|
|
|
45
45
|
[[package]]
|
|
46
46
|
name = "abap-node"
|
|
47
|
-
version = "0.
|
|
47
|
+
version = "0.2.0"
|
|
48
48
|
dependencies = [
|
|
49
49
|
"abap-analysis",
|
|
50
50
|
"napi",
|
|
@@ -55,7 +55,7 @@ dependencies = [
|
|
|
55
55
|
|
|
56
56
|
[[package]]
|
|
57
57
|
name = "abap-python"
|
|
58
|
-
version = "0.
|
|
58
|
+
version = "0.2.0"
|
|
59
59
|
dependencies = [
|
|
60
60
|
"abap-analysis",
|
|
61
61
|
"pyo3",
|
|
@@ -63,7 +63,7 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "abap-source"
|
|
66
|
-
version = "0.
|
|
66
|
+
version = "0.2.0"
|
|
67
67
|
dependencies = [
|
|
68
68
|
"base64 0.22.1",
|
|
69
69
|
"serde",
|
|
@@ -72,7 +72,7 @@ dependencies = [
|
|
|
72
72
|
|
|
73
73
|
[[package]]
|
|
74
74
|
name = "abap-sql"
|
|
75
|
-
version = "0.
|
|
75
|
+
version = "0.2.0"
|
|
76
76
|
dependencies = [
|
|
77
77
|
"abap-model",
|
|
78
78
|
"abap-syntax",
|
|
@@ -82,7 +82,7 @@ dependencies = [
|
|
|
82
82
|
|
|
83
83
|
[[package]]
|
|
84
84
|
name = "abap-syntax"
|
|
85
|
-
version = "0.
|
|
85
|
+
version = "0.2.0"
|
|
86
86
|
dependencies = [
|
|
87
87
|
"abap-lexer",
|
|
88
88
|
"serde",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/abap-lexer", "crates/abap-syntax", "crates/abap-model", "crates/abap-sql", "crates/abap-source", "crates/abap-analysis", "bindings/python"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.2.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
repository = "https://github.com/albertlast/abapPaser"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abap-parser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -17,7 +17,7 @@ Project-URL: Repository, https://github.com/albertlast/abapPaser
|
|
|
17
17
|
|
|
18
18
|
# abap-parser
|
|
19
19
|
|
|
20
|
-
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API.
|
|
20
|
+
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end, also through SUBMIT, ABAP memory and files. CDS views tell their data sources and the fields of each element. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API, SAP standard code and dictionary types included.
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
pip install abap-parser
|
|
@@ -45,7 +45,12 @@ options = {
|
|
|
45
45
|
}
|
|
46
46
|
graph = abap_parser.call_graph(["CLAS:ZCL_ORDERS"], options, depth=2)
|
|
47
47
|
logic = await abap_parser.logic_object_async("PROG:ZREPORT", options)
|
|
48
|
+
# compact text, e.g. for a language model
|
|
49
|
+
prompt = abap_parser.text("logic", source)
|
|
50
|
+
outline = await abap_parser.object_text_async("symbols", ["CLAS:ZCL_ORDERS"], options)
|
|
51
|
+
# a CDS view: sources, joins, associations, the fields of each element
|
|
52
|
+
view = abap_parser.cds(open("zi_orders.ddls.asddls", encoding="utf-8").read())
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
55
|
+
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. `text` and `object_text` give a compact form instead: definitions written like ABAP, one line per call or database access, per procedure its effects with statements and conditions. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
51
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# abap-parser
|
|
2
2
|
|
|
3
|
-
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API.
|
|
3
|
+
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end, also through SUBMIT, ABAP memory and files. CDS views tell their data sources and the fields of each element. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API, SAP standard code and dictionary types included.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
pip install abap-parser
|
|
@@ -28,6 +28,11 @@ options = {
|
|
|
28
28
|
}
|
|
29
29
|
graph = abap_parser.call_graph(["CLAS:ZCL_ORDERS"], options, depth=2)
|
|
30
30
|
logic = await abap_parser.logic_object_async("PROG:ZREPORT", options)
|
|
31
|
+
# compact text, e.g. for a language model
|
|
32
|
+
prompt = abap_parser.text("logic", source)
|
|
33
|
+
outline = await abap_parser.object_text_async("symbols", ["CLAS:ZCL_ORDERS"], options)
|
|
34
|
+
# a CDS view: sources, joins, associations, the fields of each element
|
|
35
|
+
view = abap_parser.cds(open("zi_orders.ddls.asddls", encoding="utf-8").read())
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
38
|
+
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. `text` and `object_text` give a compact form instead: definitions written like ABAP, one line per call or database access, per procedure its effects with statements and conditions. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# abap-parser
|
|
2
2
|
|
|
3
|
-
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API.
|
|
3
|
+
Parses ABAP code and tells what it does: classes, methods, forms and declarations; which database tables a statement reads or writes; which methods, function modules and forms it calls; and the logic of each procedure, from conditions and data flow to what it writes in the end, also through SUBMIT, ABAP memory and files. CDS views tell their data sources and the fields of each element. Sources come from a folder (abapGit layout or plain `.abap` files) or from an SAP system through the ADT REST API, SAP standard code and dictionary types included.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
pip install abap-parser
|
|
@@ -28,6 +28,11 @@ options = {
|
|
|
28
28
|
}
|
|
29
29
|
graph = abap_parser.call_graph(["CLAS:ZCL_ORDERS"], options, depth=2)
|
|
30
30
|
logic = await abap_parser.logic_object_async("PROG:ZREPORT", options)
|
|
31
|
+
# compact text, e.g. for a language model
|
|
32
|
+
prompt = abap_parser.text("logic", source)
|
|
33
|
+
outline = await abap_parser.object_text_async("symbols", ["CLAS:ZCL_ORDERS"], options)
|
|
34
|
+
# a CDS view: sources, joins, associations, the fields of each element
|
|
35
|
+
view = abap_parser.cds(open("zi_orders.ddls.asddls", encoding="utf-8").read())
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
38
|
+
Every function has an `..._async` variant for asyncio; the native code releases the GIL. The results are dictionaries typed by the TypedDicts in `abap_parser.types`. `text` and `object_text` give a compact form instead: definitions written like ABAP, one line per call or database access, per procedure its effects with statements and conditions. See the [repository](https://github.com/albertlast/abapPaser) for what each result contains.
|
|
@@ -61,6 +61,29 @@ mod _native {
|
|
|
61
61
|
py.detach(|| abap_analysis::json::statements(source))
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
#[pyfunction]
|
|
65
|
+
fn cds_json(py: Python<'_>, source: &str) -> String {
|
|
66
|
+
py.detach(|| abap_analysis::json::cds(source))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[pyfunction]
|
|
70
|
+
fn text(py: Python<'_>, analysis: &str, source: &str) -> PyResult<String> {
|
|
71
|
+
py.detach(|| abap_analysis::json::text(analysis, source))
|
|
72
|
+
.map_err(pyo3::exceptions::PyValueError::new_err)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[pyfunction]
|
|
76
|
+
fn object_text(
|
|
77
|
+
py: Python<'_>,
|
|
78
|
+
analysis: &str,
|
|
79
|
+
objects: Vec<String>,
|
|
80
|
+
options: &str,
|
|
81
|
+
depth: u32,
|
|
82
|
+
) -> PyResult<String> {
|
|
83
|
+
py.detach(|| abap_analysis::json::object_text(analysis, &objects, options, depth))
|
|
84
|
+
.map_err(pyo3::exceptions::PyRuntimeError::new_err)
|
|
85
|
+
}
|
|
86
|
+
|
|
64
87
|
#[pyfunction]
|
|
65
88
|
fn version() -> &'static str {
|
|
66
89
|
env!("CARGO_PKG_VERSION")
|
|
@@ -102,6 +102,22 @@ class SymbolsTest(unittest.TestCase):
|
|
|
102
102
|
db = [e for e in fm["effects"] if e["scope"] == "database"]
|
|
103
103
|
self.assertTrue(db and all(e["via"] for e in db))
|
|
104
104
|
|
|
105
|
+
def test_cds(self):
|
|
106
|
+
d = abap_parser.cds("define view entity zi_c as select from t005 { key land1 as Country }")
|
|
107
|
+
self.assertEqual(d["kind"], "view entity")
|
|
108
|
+
self.assertEqual(d["elements"][0]["fields"], ["T005.LAND1"])
|
|
109
|
+
self.assertIn("key Country = T005.LAND1", abap_parser.text("cds", "define view entity zi_c as select from t005 { key land1 as Country }"))
|
|
110
|
+
|
|
111
|
+
def test_text(self):
|
|
112
|
+
t = abap_parser.text("symbols", SOURCE)
|
|
113
|
+
self.assertTrue(t.startswith('CLASS zcl_demo DEFINITION PUBLIC FINAL. " 2-7\n'), t)
|
|
114
|
+
self.assertIn(' METHODS run IMPORTING iv_id TYPE i RETURNING VALUE(rv_ok) TYPE abap_bool. " 4\n', t)
|
|
115
|
+
self.assertIn(" writes rv_ok (parameter) at 10", abap_parser.text("logic", SOURCE))
|
|
116
|
+
with self.assertRaises(ValueError):
|
|
117
|
+
abap_parser.text("nope", SOURCE)
|
|
118
|
+
with self.assertRaises(RuntimeError):
|
|
119
|
+
abap_parser.object_text("graph", ["CLAS:X"], {})
|
|
120
|
+
|
|
105
121
|
@unittest.skipUnless((CORPUS / "abapgit" / "src").exists(), "corpus submodule missing")
|
|
106
122
|
def test_real_class(self):
|
|
107
123
|
path = CORPUS / "abapgit" / "src" / "apack" / "zcl_abapgit_apack_migration.clas.abap"
|
|
@@ -119,6 +135,7 @@ class AsyncTest(unittest.IsolatedAsyncioTestCase):
|
|
|
119
135
|
self.assertEqual(await abap_parser.symbols_async(SOURCE), abap_parser.symbols(SOURCE))
|
|
120
136
|
self.assertEqual(await abap_parser.logic_async(SOURCE), abap_parser.logic(SOURCE))
|
|
121
137
|
self.assertEqual(await abap_parser.statements_async(SOURCE), abap_parser.statements(SOURCE))
|
|
138
|
+
self.assertEqual(await abap_parser.text_async("logic", SOURCE), abap_parser.text("logic", SOURCE))
|
|
122
139
|
with self.assertRaises(RuntimeError):
|
|
123
140
|
await abap_parser.call_graph_async(["CLAS:X"], {})
|
|
124
141
|
|
|
@@ -8,17 +8,32 @@ use abap_syntax::{File, Statement, StatementKind as K, Token, TokenKind};
|
|
|
8
8
|
|
|
9
9
|
use crate::context::statement_blocks;
|
|
10
10
|
|
|
11
|
-
///
|
|
11
|
+
/// The type of a value as far as calls need it.
|
|
12
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
13
|
+
pub enum ValueType {
|
|
14
|
+
/// `TYPE REF TO name`: a class or interface, or a data reference
|
|
15
|
+
Ref(String),
|
|
16
|
+
/// `TYPE name`: e.g. a structure or a data element, which may be typed
|
|
17
|
+
/// as a reference itself
|
|
18
|
+
Data(String),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Type information about classes, interfaces and other types, possibly
|
|
22
|
+
/// outside the file.
|
|
12
23
|
pub trait TypeResolver {
|
|
13
|
-
///
|
|
14
|
-
fn
|
|
15
|
-
///
|
|
16
|
-
fn
|
|
24
|
+
/// Type of the RETURNING parameter of a method.
|
|
25
|
+
fn returning_type(&self, class: &str, method: &str) -> Option<ValueType>;
|
|
26
|
+
/// Type of an attribute.
|
|
27
|
+
fn attribute_type(&self, class: &str, attribute: &str) -> Option<ValueType>;
|
|
17
28
|
fn superclass(&self, class: &str) -> Option<String>;
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
/// Typed parameters of a method; `None` when the method is unknown.
|
|
30
|
+
/// Follows interfaces, aliases and, for redefinitions, the superclass.
|
|
31
|
+
fn method_parameters(&self, class: &str, method: &str) -> Option<Vec<(String, ValueType)>>;
|
|
32
|
+
/// Type of a component of a structure type, local or from the dictionary.
|
|
33
|
+
fn component_type(&self, structure: &str, component: &str) -> Option<ValueType>;
|
|
34
|
+
/// Class or interface a data element or type refers to, when it is
|
|
35
|
+
/// typed as a reference.
|
|
36
|
+
fn data_element_reference(&self, name: &str) -> Option<String>;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
/// Resolves within one set of symbols, e.g. one file.
|
|
@@ -64,17 +79,47 @@ pub(crate) fn type_info<'a>(symbols: &'a m::FileSymbols, name: &str) -> Option<T
|
|
|
64
79
|
})
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
fn
|
|
82
|
+
fn value_type(t: &Option<m::Typing>) -> Option<ValueType> {
|
|
68
83
|
match t {
|
|
69
|
-
Some(t) if t.kind == m::TypingKind::TypeRefTo => t.name.clone(),
|
|
84
|
+
Some(t) if t.kind == m::TypingKind::TypeRefTo => t.name.clone().map(ValueType::Ref),
|
|
85
|
+
Some(t) if t.kind == m::TypingKind::Type => t.name.clone().map(ValueType::Data),
|
|
70
86
|
_ => None,
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
|
|
90
|
+
/// A type declared with TYPES anywhere in the symbols.
|
|
91
|
+
fn local_type<'a>(s: &'a m::FileSymbols, name: &str) -> Option<&'a m::Declaration> {
|
|
92
|
+
let is = |d: &&m::Declaration| {
|
|
93
|
+
d.kind == m::DeclarationKind::Type && d.name.eq_ignore_ascii_case(name)
|
|
94
|
+
};
|
|
95
|
+
let classes = s.classes.iter().flat_map(|c| {
|
|
96
|
+
c.types.iter().chain(&c.attributes).chain(
|
|
97
|
+
c.implementation
|
|
98
|
+
.iter()
|
|
99
|
+
.flat_map(|i| i.methods.iter().flat_map(|m| m.locals.iter())),
|
|
100
|
+
)
|
|
101
|
+
});
|
|
102
|
+
s.declarations
|
|
103
|
+
.iter()
|
|
104
|
+
.chain(classes)
|
|
105
|
+
.chain(
|
|
106
|
+
s.interfaces
|
|
107
|
+
.iter()
|
|
108
|
+
.flat_map(|i| i.types.iter().chain(&i.attributes)),
|
|
109
|
+
)
|
|
110
|
+
.chain(
|
|
111
|
+
s.forms
|
|
112
|
+
.iter()
|
|
113
|
+
.chain(&s.function_modules)
|
|
114
|
+
.flat_map(|f| f.locals.iter()),
|
|
115
|
+
)
|
|
116
|
+
.find(is)
|
|
117
|
+
}
|
|
118
|
+
|
|
74
119
|
impl TypeResolver for SymbolsResolver<'_> {
|
|
75
|
-
fn
|
|
120
|
+
fn returning_type(&self, class: &str, method: &str) -> Option<ValueType> {
|
|
76
121
|
let Some(info) = type_info(self.symbols, class) else {
|
|
77
|
-
return self.fallback.and_then(|f| f.
|
|
122
|
+
return self.fallback.and_then(|f| f.returning_type(class, method));
|
|
78
123
|
};
|
|
79
124
|
let (intf, meth) = match method.split_once('~') {
|
|
80
125
|
Some((i, mth)) => (Some(i), mth),
|
|
@@ -90,7 +135,7 @@ impl TypeResolver for SymbolsResolver<'_> {
|
|
|
90
135
|
.parameters
|
|
91
136
|
.iter()
|
|
92
137
|
.find(|p| p.direction == m::Direction::Returning)
|
|
93
|
-
.and_then(|p|
|
|
138
|
+
.and_then(|p| value_type(&p.typing));
|
|
94
139
|
}
|
|
95
140
|
if let Some(a) = info
|
|
96
141
|
.aliases
|
|
@@ -98,36 +143,40 @@ impl TypeResolver for SymbolsResolver<'_> {
|
|
|
98
143
|
.find(|a| a.name.eq_ignore_ascii_case(meth))
|
|
99
144
|
{
|
|
100
145
|
if let Some((i, mth)) = a.component.split_once('~') {
|
|
101
|
-
return self.
|
|
146
|
+
return self.returning_type(i, mth);
|
|
102
147
|
}
|
|
103
148
|
}
|
|
104
149
|
}
|
|
105
150
|
for i in &info.interfaces {
|
|
106
151
|
if intf.is_none_or(|x| x.eq_ignore_ascii_case(i)) {
|
|
107
|
-
if let Some(c) = self.
|
|
152
|
+
if let Some(c) = self.returning_type(i, meth) {
|
|
108
153
|
return Some(c);
|
|
109
154
|
}
|
|
110
155
|
}
|
|
111
156
|
}
|
|
112
157
|
let superclass = info.superclass.map(str::to_string);
|
|
113
|
-
superclass.and_then(|s| self.
|
|
158
|
+
superclass.and_then(|s| self.returning_type(&s, method))
|
|
114
159
|
}
|
|
115
160
|
|
|
116
|
-
fn
|
|
161
|
+
fn attribute_type(&self, class: &str, attribute: &str) -> Option<ValueType> {
|
|
117
162
|
let Some(info) = type_info(self.symbols, class) else {
|
|
163
|
+
// a reference to a structure: lr->component
|
|
164
|
+
if local_type(self.symbols, class).is_some() {
|
|
165
|
+
return self.component_type(class, attribute);
|
|
166
|
+
}
|
|
118
167
|
return self
|
|
119
168
|
.fallback
|
|
120
|
-
.and_then(|f| f.
|
|
169
|
+
.and_then(|f| f.attribute_type(class, attribute));
|
|
121
170
|
};
|
|
122
171
|
if let Some(a) = info
|
|
123
172
|
.attributes
|
|
124
173
|
.iter()
|
|
125
174
|
.find(|a| a.name.eq_ignore_ascii_case(attribute))
|
|
126
175
|
{
|
|
127
|
-
return
|
|
176
|
+
return value_type(&a.typing);
|
|
128
177
|
}
|
|
129
178
|
let superclass = info.superclass.map(str::to_string);
|
|
130
|
-
superclass.and_then(|s| self.
|
|
179
|
+
superclass.and_then(|s| self.attribute_type(&s, attribute))
|
|
131
180
|
}
|
|
132
181
|
|
|
133
182
|
fn superclass(&self, class: &str) -> Option<String> {
|
|
@@ -137,7 +186,7 @@ impl TypeResolver for SymbolsResolver<'_> {
|
|
|
137
186
|
}
|
|
138
187
|
}
|
|
139
188
|
|
|
140
|
-
fn method_parameters(&self, class: &str, method: &str) -> Option<Vec<(String,
|
|
189
|
+
fn method_parameters(&self, class: &str, method: &str) -> Option<Vec<(String, ValueType)>> {
|
|
141
190
|
let Some(info) = type_info(self.symbols, class) else {
|
|
142
191
|
return self
|
|
143
192
|
.fallback
|
|
@@ -155,7 +204,7 @@ impl TypeResolver for SymbolsResolver<'_> {
|
|
|
155
204
|
return Some(
|
|
156
205
|
md.parameters
|
|
157
206
|
.iter()
|
|
158
|
-
.filter_map(|p|
|
|
207
|
+
.filter_map(|p| value_type(&p.typing).map(|c| (p.name.clone(), c)))
|
|
159
208
|
.collect(),
|
|
160
209
|
);
|
|
161
210
|
}
|
|
@@ -177,6 +226,32 @@ impl TypeResolver for SymbolsResolver<'_> {
|
|
|
177
226
|
let superclass = info.superclass.map(str::to_string);
|
|
178
227
|
superclass.and_then(|sc| self.method_parameters(&sc, method))
|
|
179
228
|
}
|
|
229
|
+
|
|
230
|
+
fn component_type(&self, structure: &str, component: &str) -> Option<ValueType> {
|
|
231
|
+
match local_type(self.symbols, structure) {
|
|
232
|
+
Some(d) => d
|
|
233
|
+
.components
|
|
234
|
+
.iter()
|
|
235
|
+
.find(|c| c.name.eq_ignore_ascii_case(component))
|
|
236
|
+
.and_then(|c| value_type(&c.typing)),
|
|
237
|
+
None => self
|
|
238
|
+
.fallback
|
|
239
|
+
.and_then(|f| f.component_type(structure, component)),
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
fn data_element_reference(&self, name: &str) -> Option<String> {
|
|
244
|
+
match local_type(self.symbols, name) {
|
|
245
|
+
Some(d) => match value_type(&d.typing)? {
|
|
246
|
+
ValueType::Ref(c) => Some(c),
|
|
247
|
+
ValueType::Data(other) if !other.eq_ignore_ascii_case(name) => {
|
|
248
|
+
self.data_element_reference(&other)
|
|
249
|
+
}
|
|
250
|
+
ValueType::Data(_) => None,
|
|
251
|
+
},
|
|
252
|
+
None => self.fallback.and_then(|f| f.data_element_reference(name)),
|
|
253
|
+
}
|
|
254
|
+
}
|
|
180
255
|
}
|
|
181
256
|
|
|
182
257
|
/// Built-in functions; a bare call is one unless the class has a method of
|
|
@@ -308,9 +383,9 @@ fn is_declaration(kind: K) -> bool {
|
|
|
308
383
|
)
|
|
309
384
|
}
|
|
310
385
|
|
|
311
|
-
/// Variables with a known
|
|
386
|
+
/// Variables with a known type, per processing block.
|
|
312
387
|
struct Scope {
|
|
313
|
-
vars: HashMap<String,
|
|
388
|
+
vars: HashMap<String, ValueType>,
|
|
314
389
|
class: Option<String>,
|
|
315
390
|
}
|
|
316
391
|
|
|
@@ -443,7 +518,7 @@ impl<'a> CallExtractor<'a> {
|
|
|
443
518
|
|
|
444
519
|
/// Declared variable types visible in a processing block.
|
|
445
520
|
fn scope(&self, block: Option<&m::BlockRef>, blocks: &[Option<m::BlockRef>]) -> Scope {
|
|
446
|
-
let mut vars: HashMap<String,
|
|
521
|
+
let mut vars: HashMap<String, ValueType> = HashMap::new();
|
|
447
522
|
add_declarations(&mut vars, &self.symbols.declarations);
|
|
448
523
|
let mut class = None;
|
|
449
524
|
if let Some(b) = block {
|
|
@@ -521,9 +596,10 @@ impl<'a> CallExtractor<'a> {
|
|
|
521
596
|
let declares = t.get(k + 1).is_some_and(|x| x.is("DATA") || x.is("FINAL"));
|
|
522
597
|
if declares && t.get(k + 2).is_some_and(|x| x.kind == TokenKind::ParenLeft) {
|
|
523
598
|
if let Some(var) = t.get(k + 3) {
|
|
524
|
-
scope
|
|
525
|
-
.
|
|
526
|
-
|
|
599
|
+
scope.vars.insert(
|
|
600
|
+
var.text.to_ascii_lowercase(),
|
|
601
|
+
ValueType::Ref(class.text.clone()),
|
|
602
|
+
);
|
|
527
603
|
}
|
|
528
604
|
}
|
|
529
605
|
}
|
|
@@ -550,11 +626,11 @@ impl<'a> CallExtractor<'a> {
|
|
|
550
626
|
let rhs = &t[5..t.len() - 1];
|
|
551
627
|
let class =
|
|
552
628
|
if (rhs[0].is("NEW") || rhs[0].is("CAST")) && rhs.len() > 1 && rhs[1].text != "#" {
|
|
553
|
-
Some(rhs[1].text.clone())
|
|
629
|
+
Some(ValueType::Ref(rhs[1].text.clone()))
|
|
554
630
|
} else {
|
|
555
|
-
// a call chain: the type of its result
|
|
556
|
-
let end = rhs.len();
|
|
557
|
-
self.
|
|
631
|
+
// a call chain: the type of its result, resolved when used
|
|
632
|
+
let end = operand_end(rhs, 0).min(rhs.len());
|
|
633
|
+
self.units_type(&units(rhs, 0, end), &scope, None)
|
|
558
634
|
};
|
|
559
635
|
if let Some(c) = class {
|
|
560
636
|
scope.vars.insert(var, c);
|
|
@@ -816,6 +892,13 @@ impl<'a> CallExtractor<'a> {
|
|
|
816
892
|
);
|
|
817
893
|
if name != "#" {
|
|
818
894
|
c.class = Some(name);
|
|
895
|
+
} else if let Some(eq) = start
|
|
896
|
+
.checked_sub(2)
|
|
897
|
+
.filter(|e| *e > 0 && matches!(t[*e].text.as_str(), "=" | "?="))
|
|
898
|
+
{
|
|
899
|
+
// x = NEW #( ): the type of the target
|
|
900
|
+
c.class = self.receiver_class(t, 0, eq, scope);
|
|
901
|
+
c.receiver = Some(join(t, 0, eq, &self.file.lexed.source));
|
|
819
902
|
}
|
|
820
903
|
out.push(c);
|
|
821
904
|
continue;
|
|
@@ -933,57 +1016,114 @@ impl<'a> CallExtractor<'a> {
|
|
|
933
1016
|
scope: &Scope,
|
|
934
1017
|
next: Option<TokenKind>,
|
|
935
1018
|
) -> Option<String> {
|
|
1019
|
+
self.class_of(self.units_type(units, scope, next)?)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/// Type of the value of chain elements; `next` is the arrow after them.
|
|
1023
|
+
fn units_type(
|
|
1024
|
+
&self,
|
|
1025
|
+
units: &[Unit],
|
|
1026
|
+
scope: &Scope,
|
|
1027
|
+
next: Option<TokenKind>,
|
|
1028
|
+
) -> Option<ValueType> {
|
|
936
1029
|
let first = units.first()?;
|
|
937
|
-
|
|
1030
|
+
// ls_x-ref: a component of a structure
|
|
1031
|
+
let (head, path) = split_components(&first.name);
|
|
1032
|
+
let name = head.to_ascii_lowercase();
|
|
938
1033
|
let first_is_class = match units.get(1) {
|
|
939
1034
|
Some(u) => u.arrow == Some(TokenKind::StaticArrow),
|
|
940
1035
|
None => next == Some(TokenKind::StaticArrow),
|
|
941
1036
|
};
|
|
942
|
-
let mut current: Option<
|
|
943
|
-
Some(first.name.clone())
|
|
1037
|
+
let mut current: Option<ValueType> = if first_is_class && !first.is_call {
|
|
1038
|
+
Some(ValueType::Ref(first.name.clone()))
|
|
944
1039
|
} else if name == "me" {
|
|
945
|
-
scope.class.clone()
|
|
1040
|
+
scope.class.clone().map(ValueType::Ref)
|
|
946
1041
|
} else if name == "super" {
|
|
947
1042
|
scope
|
|
948
1043
|
.class
|
|
949
1044
|
.as_deref()
|
|
950
1045
|
.and_then(|c| self.resolver.superclass(c))
|
|
1046
|
+
.map(ValueType::Ref)
|
|
951
1047
|
} else if first.is_call {
|
|
952
1048
|
scope
|
|
953
1049
|
.class
|
|
954
1050
|
.as_deref()
|
|
955
|
-
.and_then(|c| self.resolver.
|
|
1051
|
+
.and_then(|c| self.resolver.returning_type(c, head))
|
|
956
1052
|
} else {
|
|
957
1053
|
scope.vars.get(&name).cloned().or_else(|| {
|
|
958
1054
|
scope
|
|
959
1055
|
.class
|
|
960
1056
|
.as_deref()
|
|
961
|
-
.and_then(|c| self.resolver.
|
|
1057
|
+
.and_then(|c| self.resolver.attribute_type(c, head))
|
|
962
1058
|
})
|
|
963
1059
|
};
|
|
1060
|
+
if let Some(p) = path {
|
|
1061
|
+
current = self.components(current?, p);
|
|
1062
|
+
}
|
|
964
1063
|
for u in &units[1..] {
|
|
965
|
-
let cls = current?;
|
|
1064
|
+
let cls = self.class_of(current?)?;
|
|
1065
|
+
let (head, path) = split_components(&u.name);
|
|
966
1066
|
current = if u.is_call {
|
|
967
|
-
self.resolver.
|
|
1067
|
+
self.resolver.returning_type(&cls, head)
|
|
968
1068
|
} else {
|
|
969
|
-
self.resolver.
|
|
1069
|
+
self.resolver.attribute_type(&cls, head)
|
|
970
1070
|
};
|
|
1071
|
+
if let Some(p) = path {
|
|
1072
|
+
current = self.components(current?, p);
|
|
1073
|
+
}
|
|
971
1074
|
}
|
|
972
1075
|
current
|
|
973
1076
|
}
|
|
1077
|
+
|
|
1078
|
+
/// Class or interface a value of the type refers to.
|
|
1079
|
+
fn class_of(&self, ty: ValueType) -> Option<String> {
|
|
1080
|
+
match ty {
|
|
1081
|
+
ValueType::Ref(c) => Some(c),
|
|
1082
|
+
ValueType::Data(d) => match d.split_once('-') {
|
|
1083
|
+
// TYPE vbak-field
|
|
1084
|
+
Some((s, path)) => self.class_of(self.components(ValueType::Data(s.into()), path)?),
|
|
1085
|
+
None => self.resolver.data_element_reference(&d),
|
|
1086
|
+
},
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/// Type of the components `a-b` of a structure.
|
|
1091
|
+
fn components(&self, mut ty: ValueType, path: &str) -> Option<ValueType> {
|
|
1092
|
+
for component in path.split('-') {
|
|
1093
|
+
let ValueType::Data(s) = ty else {
|
|
1094
|
+
return None;
|
|
1095
|
+
};
|
|
1096
|
+
ty = match s.split_once('-') {
|
|
1097
|
+
Some((head, rest)) => {
|
|
1098
|
+
let inner = self.components(ValueType::Data(head.into()), rest)?;
|
|
1099
|
+
self.components(inner, component)?
|
|
1100
|
+
}
|
|
1101
|
+
None => self.resolver.component_type(&s, component)?,
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
Some(ty)
|
|
1105
|
+
}
|
|
974
1106
|
}
|
|
975
1107
|
|
|
976
|
-
|
|
1108
|
+
/// `ls_x-a-b` into `ls_x` and the components `a-b`.
|
|
1109
|
+
fn split_components(name: &str) -> (&str, Option<&str>) {
|
|
1110
|
+
match name.split_once('-') {
|
|
1111
|
+
Some((head, path)) if !head.is_empty() && !path.is_empty() => (head, Some(path)),
|
|
1112
|
+
_ => (name, None),
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
fn add_declarations(vars: &mut HashMap<String, ValueType>, decls: &[m::Declaration]) {
|
|
977
1117
|
for d in decls {
|
|
978
|
-
if let Some(c) =
|
|
1118
|
+
if let Some(c) = value_type(&d.typing) {
|
|
979
1119
|
vars.insert(d.name.to_ascii_lowercase(), c);
|
|
980
1120
|
}
|
|
981
1121
|
}
|
|
982
1122
|
}
|
|
983
1123
|
|
|
984
|
-
fn params(vars: &mut HashMap<String,
|
|
1124
|
+
fn params(vars: &mut HashMap<String, ValueType>, parameters: &[m::Parameter]) {
|
|
985
1125
|
for p in parameters {
|
|
986
|
-
if let Some(c) =
|
|
1126
|
+
if let Some(c) = value_type(&p.typing) {
|
|
987
1127
|
vars.insert(p.name.to_ascii_lowercase(), c);
|
|
988
1128
|
}
|
|
989
1129
|
}
|
|
@@ -1284,6 +1424,7 @@ CLASS lcl_main IMPLEMENTATION.
|
|
|
1284
1424
|
PERFORM write_log IN PROGRAM zlog_prog IF FOUND.
|
|
1285
1425
|
SUBMIT zreport AND RETURN.
|
|
1286
1426
|
CALL TRANSACTION 'VA01'.
|
|
1427
|
+
lo_item = NEW #( ).
|
|
1287
1428
|
ENDMETHOD.
|
|
1288
1429
|
METHOD helper.
|
|
1289
1430
|
ENDMETHOD.
|
|
@@ -1330,6 +1471,7 @@ ENDCLASS.
|
|
|
1330
1471
|
"Form write_log",
|
|
1331
1472
|
"Submit ZREPORT",
|
|
1332
1473
|
"Transaction VA01",
|
|
1474
|
+
"Constructor lcl_item.constructor resolved",
|
|
1333
1475
|
]
|
|
1334
1476
|
);
|
|
1335
1477
|
assert_eq!(c[13].options, ["DESTINATION 'NONE'"]);
|