nativegate 0.1.8__tar.gz → 0.1.9__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.
- {nativegate-0.1.8 → nativegate-0.1.9}/PKG-INFO +8 -1
- {nativegate-0.1.8 → nativegate-0.1.9}/README.md +7 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/fortran.py +42 -0
- nativegate-0.1.9/nativegate/parsers/hollerith.py +93 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/PKG-INFO +8 -1
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/SOURCES.txt +2 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/pyproject.toml +1 -1
- nativegate-0.1.9/tests/test_hollerith.py +84 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/__init__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/__main__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/buildinfo.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/cli.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/config.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/declared_invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/discovery.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/driverbuild.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/drivers/__init__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/drivers/cpp.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/drivers/fortran.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/__init__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/cmake_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/docker_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/error_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/f2py_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/gateway_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/golden_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/k8s_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/mcp_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/middleware_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/pybind_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/pyproject_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/python_pkg_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/generators/test_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/golden.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/ir.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/lattice.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/locking.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/oracle.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/__init__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/cpp.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/cpp_ast.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/cpp_regex.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/dialect.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/fixed_form.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/fortran_fparser.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/parsers/fortran_regex.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/preprocess.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/structural_invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/suggest.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/templates/__init__.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/templates/golden_test_template.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate/wire.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/dependency_links.txt +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/entry_points.txt +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/requires.txt +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/nativegate.egg-info/top_level.txt +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/setup.cfg +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_buildinfo.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cli_serve_verify.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cli_suggest.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cli_verify_aggregate.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cmake_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cmake_missing_input.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_config_unknown_keys.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cpp_ast.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cpp_driver.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_cpp_pipeline.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_declared_invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_derived_types.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_dialect.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_docker_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_driverbuild.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_endpoint_docstrings.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_external_callbacks.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fixed_form.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fortran_doc_comments.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fortran_driver.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fortran_fparser.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fortran_libraries.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_fortran_pipeline.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_gateway_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_generated_python_is_valid.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_golden.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_inspect_dialect.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_ir_contract.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_k8s_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_lattice.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_legacy_cpp.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_legacy_fortran.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_locking.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_mcp_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_middleware_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_multifile_cpp.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_native_oracle.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_native_state_contract.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_oracle.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_preprocess_and_config.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_preprocessed_fortran.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_pybind_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_pyproject_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_quickstart.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_scalar_ref.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_service_gen.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_service_name_validation.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_shared_libraries.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_structural_invariants.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_suggest.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_verification_config.py +0 -0
- {nativegate-0.1.8 → nativegate-0.1.9}/tests/test_wire.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nativegate
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Make legacy C++/C/Fortran AI-ready and microservice-ready: expose native code as an MCP server, REST API and container
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -552,3 +552,10 @@ mapping. Each refusal is printed at `generate` time with its reason, and the
|
|
|
552
552
|
generated service publishes them at `GET /_unexposed` so they stay visible to
|
|
553
553
|
whoever is calling the API. An empty mapping there means this build refused
|
|
554
554
|
nothing; a 404 means the service predates the route and cannot tell you.
|
|
555
|
+
|
|
556
|
+
## Community
|
|
557
|
+
|
|
558
|
+
Contributing: see the repository's [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
559
|
+
Participation in this project's spaces is covered by the
|
|
560
|
+
[Code of Conduct](../../CODE_OF_CONDUCT.md); reports go to
|
|
561
|
+
sr.rabiu@gmail.com.
|
|
@@ -519,3 +519,10 @@ mapping. Each refusal is printed at `generate` time with its reason, and the
|
|
|
519
519
|
generated service publishes them at `GET /_unexposed` so they stay visible to
|
|
520
520
|
whoever is calling the API. An empty mapping there means this build refused
|
|
521
521
|
nothing; a 404 means the service predates the route and cannot tell you.
|
|
522
|
+
|
|
523
|
+
## Community
|
|
524
|
+
|
|
525
|
+
Contributing: see the repository's [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
526
|
+
Participation in this project's spaces is covered by the
|
|
527
|
+
[Code of Conduct](../../CODE_OF_CONDUCT.md); reports go to
|
|
528
|
+
sr.rabiu@gmail.com.
|
|
@@ -65,10 +65,12 @@ from __future__ import annotations
|
|
|
65
65
|
|
|
66
66
|
import os
|
|
67
67
|
from pathlib import Path
|
|
68
|
+
import tempfile
|
|
68
69
|
|
|
69
70
|
from ..config import ExposeConfig
|
|
70
71
|
from ..ir import ModuleIR
|
|
71
72
|
from . import fortran_fparser, fortran_regex
|
|
73
|
+
from . import hollerith
|
|
72
74
|
|
|
73
75
|
# Re-exported so callers that reach for the free-form continuation joiner (the
|
|
74
76
|
# CLI's source-rewriting paths, and a good number of tests) keep working after
|
|
@@ -137,6 +139,31 @@ def backend_description(backend: str | None = None) -> str:
|
|
|
137
139
|
)
|
|
138
140
|
|
|
139
141
|
|
|
142
|
+
def _hollerith_resolved(path: Path) -> Path | None:
|
|
143
|
+
"""A temp copy of `path` with Hollerith constants resolved, or None.
|
|
144
|
+
|
|
145
|
+
fparser2 refuses Hollerith constants gfortran compiles with a warning —
|
|
146
|
+
the one measured counterexample to the parity rule that made fparser2
|
|
147
|
+
the default (DEFECTS D15, netlib quadpack). The file gfortran accepts
|
|
148
|
+
is the file that should parse, so the front door resolves the constants
|
|
149
|
+
into their standard quoted spelling and reads that copy instead. The
|
|
150
|
+
copy is attributed back to the original path, so source-file references
|
|
151
|
+
and doc-comment line spans keep pointing at the real file.
|
|
152
|
+
"""
|
|
153
|
+
raw = path.read_text()
|
|
154
|
+
if not hollerith.is_hollerith(raw):
|
|
155
|
+
return None
|
|
156
|
+
resolved, _ = hollerith.resolve_hollerith(raw)
|
|
157
|
+
handle, name = tempfile.mkstemp(suffix=path.suffix)
|
|
158
|
+
Path(name).write_text(resolved)
|
|
159
|
+
# mkstemp exposes an int fd; close it — Path.write_text already used
|
|
160
|
+
# a second open, and the raw descriptor must not leak.
|
|
161
|
+
import os
|
|
162
|
+
|
|
163
|
+
os.close(handle)
|
|
164
|
+
return Path(name)
|
|
165
|
+
|
|
166
|
+
|
|
140
167
|
def parse_source(
|
|
141
168
|
path: Path,
|
|
142
169
|
expose: ExposeConfig,
|
|
@@ -146,6 +173,15 @@ def parse_source(
|
|
|
146
173
|
) -> ModuleIR:
|
|
147
174
|
"""Parse one Fortran source into a ModuleIR using the selected backend."""
|
|
148
175
|
if resolve_backend(backend) == "fparser2":
|
|
176
|
+
resolved = _hollerith_resolved(path)
|
|
177
|
+
if resolved is not None:
|
|
178
|
+
module = fortran_fparser.parse_source(resolved, expose, include_paths)
|
|
179
|
+
# The temp copy must not leak into user-visible IR: same name,
|
|
180
|
+
# same source file, only the text differs (D15).
|
|
181
|
+
module.name = path.stem
|
|
182
|
+
module.source_file = str(path)
|
|
183
|
+
resolved.unlink(missing_ok=True)
|
|
184
|
+
return module
|
|
149
185
|
return fortran_fparser.parse_source(path, expose, include_paths)
|
|
150
186
|
return fortran_regex.parse_source(path, expose, include_paths)
|
|
151
187
|
|
|
@@ -153,5 +189,11 @@ def parse_source(
|
|
|
153
189
|
def list_routine_names(path: Path, *, backend: str | None = None) -> list[str]:
|
|
154
190
|
"""Every function/subroutine name in the file, in source order."""
|
|
155
191
|
if resolve_backend(backend) == "fparser2":
|
|
192
|
+
resolved = _hollerith_resolved(path)
|
|
193
|
+
if resolved is not None:
|
|
194
|
+
try:
|
|
195
|
+
return fortran_fparser.list_routine_names(resolved)
|
|
196
|
+
finally:
|
|
197
|
+
resolved.unlink(missing_ok=True)
|
|
156
198
|
return fortran_fparser.list_routine_names(path)
|
|
157
199
|
return fortran_regex.list_routine_names(path)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Hollerith constants in fixed-form Fortran — rewrite to CHARACTER literals.
|
|
2
|
+
|
|
3
|
+
The netlib quadpack contact (DEFECTS D15) is the canonical case: 1970s-era
|
|
4
|
+
sources pass error text as Hollerith constants,
|
|
5
|
+
|
|
6
|
+
call xerror(26habnormal return from qng ,26,ier,0)
|
|
7
|
+
|
|
8
|
+
gfortran compiles that (legacy extension, warning only), but fparser2
|
|
9
|
+
refuses the whole file — the first measured counterexample to the
|
|
10
|
+
"fparser2 is never stricter than gfortran" rule that justifies fparser2
|
|
11
|
+
being the default front end. Since nativegate must compile with gfortran
|
|
12
|
+
anyway, the file gfortran accepts is exactly the file that should reach
|
|
13
|
+
the parser; the quoted CHARACTER constant is the standard F77 spelling of
|
|
14
|
+
the same argument, which f2py's own reader accepts too.
|
|
15
|
+
|
|
16
|
+
Deliberately conservative:
|
|
17
|
+
|
|
18
|
+
* Fixed-form text only; F90+ sources do not carry Hollerith.
|
|
19
|
+
* One constant per physical line: an `N*H` whose count runs past the line
|
|
20
|
+
end (it continues onto the next) is left untouched — rewriting it would
|
|
21
|
+
move every following line number and shift the doc-comment spans both
|
|
22
|
+
backends position by line.
|
|
23
|
+
* The rewritten line must fit the punch-card margin (72 columns). One
|
|
24
|
+
that cannot is left alone. What survives un-resolved is the caller's
|
|
25
|
+
decision (see resolver_hollerith in parsers/fortran.py).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
|
|
32
|
+
_HOLLERITH_START_RE = re.compile(r"(?<!\w)(\d+)\s*[hH]", re.ASCII)
|
|
33
|
+
|
|
34
|
+
_FIXED_FORM_LINE_LIMIT = 72
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def is_hollerith(text: str) -> bool:
|
|
38
|
+
return bool(_HOLLERITH_START_RE.search(text))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_hollerith(text: str) -> tuple[str, int]:
|
|
42
|
+
"""Replace resolvable Hollerith constants with quoted literals, in line.
|
|
43
|
+
|
|
44
|
+
Returns (text, resolved). Line count is always preserved, so line-based
|
|
45
|
+
spans (doc comments, fparser2 item spans) stay put — the same invariant
|
|
46
|
+
the dialect pass keeps.
|
|
47
|
+
"""
|
|
48
|
+
if not is_hollerith(text):
|
|
49
|
+
return text, 0
|
|
50
|
+
|
|
51
|
+
out_lines = []
|
|
52
|
+
resolved_total = 0
|
|
53
|
+
for line in text.splitlines(keepends=True):
|
|
54
|
+
resolved_line, n = _resolve_line(line)
|
|
55
|
+
out_lines.append(resolved_line)
|
|
56
|
+
resolved_total += n
|
|
57
|
+
return "".join(out_lines), resolved_total
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _resolve_line(line: str) -> tuple[str, int]:
|
|
61
|
+
"""Substitute every fitting Hollerith constant on one physical line."""
|
|
62
|
+
end_of_line = len(line.rstrip("\r\n"))
|
|
63
|
+
eol = line[end_of_line:]
|
|
64
|
+
body = line[:end_of_line]
|
|
65
|
+
|
|
66
|
+
resolved = 0
|
|
67
|
+
position = 0
|
|
68
|
+
while position <= len(body):
|
|
69
|
+
match = _HOLLERITH_START_RE.search(body, position)
|
|
70
|
+
if match is None:
|
|
71
|
+
break
|
|
72
|
+
count = int(match.group(1))
|
|
73
|
+
start = match.end()
|
|
74
|
+
if start + count > len(body):
|
|
75
|
+
# the constant counts past this physical line — it continues on
|
|
76
|
+
# the next; rewriting it would move following line numbers
|
|
77
|
+
break
|
|
78
|
+
literal = body[start : start + count]
|
|
79
|
+
quoted = _quote(literal)
|
|
80
|
+
new_line = f"{body[: match.start()]}{quoted}{body[start + count :]}"
|
|
81
|
+
if len(new_line) > _FIXED_FORM_LINE_LIMIT:
|
|
82
|
+
# never widen the punch-card margin; leave this one alone
|
|
83
|
+
break
|
|
84
|
+
body = new_line
|
|
85
|
+
resolved += 1
|
|
86
|
+
position = match.start() + len(quoted)
|
|
87
|
+
return body + eol, resolved
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _quote(literal: str) -> str:
|
|
91
|
+
if "'" in literal:
|
|
92
|
+
return '"' + literal.replace('"', '""') + '"'
|
|
93
|
+
return "'" + literal + "'"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nativegate
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Make legacy C++/C/Fortran AI-ready and microservice-ready: expose native code as an MCP server, REST API and container
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -552,3 +552,10 @@ mapping. Each refusal is printed at `generate` time with its reason, and the
|
|
|
552
552
|
generated service publishes them at `GET /_unexposed` so they stay visible to
|
|
553
553
|
whoever is calling the API. An empty mapping there means this build refused
|
|
554
554
|
nothing; a 404 means the service predates the route and cannot tell you.
|
|
555
|
+
|
|
556
|
+
## Community
|
|
557
|
+
|
|
558
|
+
Contributing: see the repository's [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
559
|
+
Participation in this project's spaces is covered by the
|
|
560
|
+
[Code of Conduct](../../CODE_OF_CONDUCT.md); reports go to
|
|
561
|
+
sr.rabiu@gmail.com.
|
|
@@ -50,6 +50,7 @@ nativegate/parsers/fixed_form.py
|
|
|
50
50
|
nativegate/parsers/fortran.py
|
|
51
51
|
nativegate/parsers/fortran_fparser.py
|
|
52
52
|
nativegate/parsers/fortran_regex.py
|
|
53
|
+
nativegate/parsers/hollerith.py
|
|
53
54
|
nativegate/templates/__init__.py
|
|
54
55
|
nativegate/templates/golden_test_template.py
|
|
55
56
|
tests/test_buildinfo.py
|
|
@@ -78,6 +79,7 @@ tests/test_fortran_pipeline.py
|
|
|
78
79
|
tests/test_gateway_gen.py
|
|
79
80
|
tests/test_generated_python_is_valid.py
|
|
80
81
|
tests/test_golden.py
|
|
82
|
+
tests/test_hollerith.py
|
|
81
83
|
tests/test_inspect_dialect.py
|
|
82
84
|
tests/test_invariants.py
|
|
83
85
|
tests/test_ir_contract.py
|
|
@@ -13,7 +13,7 @@ build-backend = "setuptools.build_meta"
|
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
15
|
name = "nativegate"
|
|
16
|
-
version = "0.1.
|
|
16
|
+
version = "0.1.9"
|
|
17
17
|
description = "Make legacy C++/C/Fortran AI-ready and microservice-ready: expose native code as an MCP server, REST API and container"
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""D15: Hollerith constants resolve so fparser2 reads gfortran-acceptable files.
|
|
2
|
+
|
|
3
|
+
`call xerror(26habnormal return from qng ,26,ier,0)` compiles under
|
|
4
|
+
gfortran with a legacy-extension warning but made fparser2 refuse the
|
|
5
|
+
whole file — QUADPACK's QNG, un-inspectable at the default backend. The
|
|
6
|
+
front door now resolves these into the standard quoted spelling in a temp
|
|
7
|
+
copy attributed back to the original path (same module name, same source
|
|
8
|
+
file reference, same line count), so both `inspect` and `generate` see
|
|
9
|
+
the file the compiler sees.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import textwrap
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from click.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
from nativegate import cli
|
|
18
|
+
from nativegate.parsers import fortran as fortran_parser
|
|
19
|
+
from nativegate.parsers import hollerith
|
|
20
|
+
from nativegate.config import ExposeConfig
|
|
21
|
+
|
|
22
|
+
QNG_WITH_HOLLERITH = (
|
|
23
|
+
" SUBROUTINE QNG(F, A, B)\n"
|
|
24
|
+
" EXTERNAL F\n"
|
|
25
|
+
" REAL A, B\n"
|
|
26
|
+
" IF (IER.NE.0) THEN\n"
|
|
27
|
+
" 80 CALL XERROR(26HABNORMAL RETURN FROM QNG ,26,IER,0)\n"
|
|
28
|
+
" END IF\n"
|
|
29
|
+
" RETURN\n"
|
|
30
|
+
" END\n"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_resolver_quotes_and_preserves_columns():
|
|
35
|
+
source = QNG_WITH_HOLLERITH
|
|
36
|
+
out, resolved = hollerith.resolve_hollerith(source)
|
|
37
|
+
assert resolved == 1
|
|
38
|
+
assert "26HABNORMAL" not in out
|
|
39
|
+
assert "'ABNORMAL RETURN FROM QNG '" in out
|
|
40
|
+
# line count and margin are invariants: every physical line stays
|
|
41
|
+
assert out.count("\n") == source.count("\n")
|
|
42
|
+
for original, rewritten in zip(source.splitlines(), out.splitlines()):
|
|
43
|
+
assert len(rewritten) <= 72
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_resolver_leaves_continuation_spanning_constants():
|
|
47
|
+
# the constant counts past the physical line: untouched, so line
|
|
48
|
+
# numbers never shift
|
|
49
|
+
text = " CALL XERROR(6HABCDE\n -F,6,IER,0)\n"
|
|
50
|
+
out, resolved = hollerith.resolve_hollerith(text)
|
|
51
|
+
assert resolved == 0
|
|
52
|
+
assert out == text
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_resolver_is_a_noop_without_hollerith():
|
|
56
|
+
text = " REAL A\n A = 2.0\n"
|
|
57
|
+
out, resolved = hollerith.resolve_hollerith(text)
|
|
58
|
+
assert resolved == 0 and out == text
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_quote_escapes_are_balanced():
|
|
62
|
+
assert hollerith._quote("it's") == '"it\'s"'
|
|
63
|
+
assert hollerith._quote('say "hi"') == '\'say "hi"\''
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_inspect_parses_hollerith_deck_by_default(tmp_path):
|
|
67
|
+
src = tmp_path / "qng.f"
|
|
68
|
+
src.write_text(QNG_WITH_HOLLERITH)
|
|
69
|
+
# the ONE check that matters: no explicit backend flag, the default
|
|
70
|
+
# (fparser2 where importable) reads the file the compiler reads
|
|
71
|
+
result = CliRunner().invoke(
|
|
72
|
+
cli.main, ["inspect", str(src), "--function", "QNG"]
|
|
73
|
+
)
|
|
74
|
+
assert result.exit_code == 0, result.output
|
|
75
|
+
# the callback is refused with its reason, never mis-typed (D14)
|
|
76
|
+
assert "EXTERNAL" in result.output
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_module_name_attributed_to_real_file(tmp_path):
|
|
80
|
+
src = tmp_path / "qng.f"
|
|
81
|
+
src.write_text(QNG_WITH_HOLLERITH)
|
|
82
|
+
module = fortran_parser.parse_source(src, ExposeConfig(functions=["QNG"]))
|
|
83
|
+
assert module.name == "qng"
|
|
84
|
+
assert module.source_file == str(src)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|