pycforge 0.15.2__py3-none-any.whl
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.
- pycforge/__init__.py +8 -0
- pycforge/__main__.py +2 -0
- pycforge/_version.py +1 -0
- pycforge/converter/__init__.py +0 -0
- pycforge/converter/analysis/__init__.py +5 -0
- pycforge/converter/analysis/functions.py +723 -0
- pycforge/converter/analysis/model.py +158 -0
- pycforge/converter/analysis/planning.py +464 -0
- pycforge/converter/analysis/stage.py +1976 -0
- pycforge/converter/analysis/symbols.py +237 -0
- pycforge/converter/analysis/validation.py +2166 -0
- pycforge/converter/c_output/__init__.py +2 -0
- pycforge/converter/c_output/conformance.py +297 -0
- pycforge/converter/c_output/renderer.py +220 -0
- pycforge/converter/conditional_regions/__init__.py +71 -0
- pycforge/converter/conditional_regions/analysis.py +481 -0
- pycforge/converter/conditional_regions/lowering.py +579 -0
- pycforge/converter/conditional_regions/model.py +243 -0
- pycforge/converter/conditional_regions/validation.py +690 -0
- pycforge/converter/containers/__init__.py +19 -0
- pycforge/converter/containers/analysis.py +542 -0
- pycforge/converter/containers/lowering.py +290 -0
- pycforge/converter/containers/model.py +146 -0
- pycforge/converter/contracts/__init__.py +0 -0
- pycforge/converter/contracts/configuration.py +178 -0
- pycforge/converter/contracts/identifiers.py +68 -0
- pycforge/converter/contracts/versions.py +71 -0
- pycforge/converter/core/__init__.py +0 -0
- pycforge/converter/core/artifact_io.py +87 -0
- pycforge/converter/core/cancellation.py +7 -0
- pycforge/converter/core/canonicalization.py +254 -0
- pycforge/converter/core/context.py +13 -0
- pycforge/converter/core/diagnostics.py +95 -0
- pycforge/converter/core/enums.py +23 -0
- pycforge/converter/core/fingerprint.py +22 -0
- pycforge/converter/core/pipeline.py +8 -0
- pycforge/converter/core/progress.py +25 -0
- pycforge/converter/core/request.py +50 -0
- pycforge/converter/core/resource_policy.py +31 -0
- pycforge/converter/core/result.py +23 -0
- pycforge/converter/core/serialization.py +36 -0
- pycforge/converter/core/stage.py +10 -0
- pycforge/converter/core/stage_artifact.py +80 -0
- pycforge/converter/core/stage_outcome.py +15 -0
- pycforge/converter/decision_trace/__init__.py +0 -0
- pycforge/converter/decision_trace/recorder.py +52 -0
- pycforge/converter/facade.py +459 -0
- pycforge/converter/frontend/__init__.py +4 -0
- pycforge/converter/frontend/normalizer.py +59 -0
- pycforge/converter/frontend/parser.py +20 -0
- pycforge/converter/frontend/source_document.py +93 -0
- pycforge/converter/frontend/stages.py +518 -0
- pycforge/converter/frontend/validation.py +76 -0
- pycforge/converter/io/__init__.py +0 -0
- pycforge/converter/io/atomic_writer.py +33 -0
- pycforge/converter/ir/__init__.py +1 -0
- pycforge/converter/ir/c_ir/__init__.py +4 -0
- pycforge/converter/ir/c_ir/builder.py +26 -0
- pycforge/converter/ir/c_ir/model.py +318 -0
- pycforge/converter/ir/c_ir/serialization.py +88 -0
- pycforge/converter/ir/c_ir/validation.py +834 -0
- pycforge/converter/ir/python_ir/__init__.py +17 -0
- pycforge/converter/ir/python_ir/nodes.py +86 -0
- pycforge/converter/keyword_calls/__init__.py +68 -0
- pycforge/converter/keyword_calls/analysis.py +458 -0
- pycforge/converter/keyword_calls/lowering.py +271 -0
- pycforge/converter/keyword_calls/model.py +304 -0
- pycforge/converter/keyword_calls/validation.py +802 -0
- pycforge/converter/keyword_only_calls/__init__.py +68 -0
- pycforge/converter/keyword_only_calls/analysis.py +640 -0
- pycforge/converter/keyword_only_calls/lowering.py +376 -0
- pycforge/converter/keyword_only_calls/model.py +328 -0
- pycforge/converter/keyword_only_calls/validation.py +1357 -0
- pycforge/converter/lowering.py +1000 -0
- pycforge/converter/modules/__init__.py +13 -0
- pycforge/converter/modules/analysis.py +1250 -0
- pycforge/converter/modules/lowering.py +200 -0
- pycforge/converter/modules/model.py +73 -0
- pycforge/converter/modules/stage.py +192 -0
- pycforge/converter/numeric_semantics/__init__.py +23 -0
- pycforge/converter/numeric_semantics/analysis.py +420 -0
- pycforge/converter/numeric_semantics/lowering.py +153 -0
- pycforge/converter/numeric_semantics/model.py +123 -0
- pycforge/converter/records/__init__.py +34 -0
- pycforge/converter/records/analysis.py +1394 -0
- pycforge/converter/records/lowering.py +393 -0
- pycforge/converter/records/model.py +319 -0
- pycforge/converter/rules/__init__.py +3 -0
- pycforge/converter/support_templates/__init__.py +49 -0
- pycforge/converter/support_templates/assembly.py +106 -0
- pycforge/converter/support_templates/factories.py +348 -0
- pycforge/converter/support_templates/model.py +159 -0
- pycforge/converter/support_templates/registry.py +388 -0
- pycforge/converter/telemetry/__init__.py +0 -0
- pycforge/converter/telemetry/sink.py +39 -0
- pycforge/ide/__init__.py +19 -0
- pycforge/ide/__main__.py +5 -0
- pycforge/ide/_worker_event_codec.py +276 -0
- pycforge/ide/_worker_protocol_json.py +197 -0
- pycforge/ide/_worker_protocol_types.py +90 -0
- pycforge/ide/_worker_request_codec.py +200 -0
- pycforge/ide/_worker_request_data.py +260 -0
- pycforge/ide/_worker_result_data.py +343 -0
- pycforge/ide/action_contract.py +469 -0
- pycforge/ide/action_validation.py +229 -0
- pycforge/ide/command_palette.py +223 -0
- pycforge/ide/controller.py +904 -0
- pycforge/ide/controller_conversion.py +546 -0
- pycforge/ide/controller_io.py +205 -0
- pycforge/ide/editor.py +951 -0
- pycforge/ide/editor_commands_qt.py +274 -0
- pycforge/ide/editor_lexical.py +43 -0
- pycforge/ide/editor_sidebars.py +236 -0
- pycforge/ide/editor_syntax.py +243 -0
- pycforge/ide/find_replace.py +535 -0
- pycforge/ide/icons.py +91 -0
- pycforge/ide/io_service.py +196 -0
- pycforge/ide/model.py +164 -0
- pycforge/ide/panels.py +903 -0
- pycforge/ide/positions.py +220 -0
- pycforge/ide/process_worker.py +130 -0
- pycforge/ide/qt.py +230 -0
- pycforge/ide/qt_actions.py +418 -0
- pycforge/ide/qt_close.py +169 -0
- pycforge/ide/qt_command_palette.py +297 -0
- pycforge/ide/qt_contract.py +220 -0
- pycforge/ide/qt_documents.py +590 -0
- pycforge/ide/qt_editor_buffers.py +113 -0
- pycforge/ide/qt_editor_surfaces.py +112 -0
- pycforge/ide/qt_menus.py +324 -0
- pycforge/ide/qt_projection.py +500 -0
- pycforge/ide/qt_shell.py +348 -0
- pycforge/ide/qt_shell_interactions.py +320 -0
- pycforge/ide/qt_state.py +393 -0
- pycforge/ide/qt_workspace_features.py +398 -0
- pycforge/ide/qt_workspace_navigation.py +208 -0
- pycforge/ide/qt_workspace_observers.py +173 -0
- pycforge/ide/qt_workspace_panels.py +545 -0
- pycforge/ide/qt_workspace_widgets.py +228 -0
- pycforge/ide/resources/icons/about.svg +1 -0
- pycforge/ide/resources/icons/add-document.svg +1 -0
- pycforge/ide/resources/icons/brand-mark.svg +1 -0
- pycforge/ide/resources/icons/bundle-search.svg +1 -0
- pycforge/ide/resources/icons/cancel.svg +1 -0
- pycforge/ide/resources/icons/check.svg +1 -0
- pycforge/ide/resources/icons/chevron-right.svg +1 -0
- pycforge/ide/resources/icons/close.svg +1 -0
- pycforge/ide/resources/icons/collapse-all.svg +1 -0
- pycforge/ide/resources/icons/command-palette.svg +1 -0
- pycforge/ide/resources/icons/convert.svg +1 -0
- pycforge/ide/resources/icons/copy.svg +1 -0
- pycforge/ide/resources/icons/cut.svg +1 -0
- pycforge/ide/resources/icons/decision-trace.svg +1 -0
- pycforge/ide/resources/icons/details.svg +1 -0
- pycforge/ide/resources/icons/diagnostics.svg +1 -0
- pycforge/ide/resources/icons/duplicate-line.svg +1 -0
- pycforge/ide/resources/icons/exit.svg +1 -0
- pycforge/ide/resources/icons/expand-all.svg +1 -0
- pycforge/ide/resources/icons/export.svg +1 -0
- pycforge/ide/resources/icons/find.svg +1 -0
- pycforge/ide/resources/icons/go-to-line.svg +1 -0
- pycforge/ide/resources/icons/go-to-output.svg +1 -0
- pycforge/ide/resources/icons/go-to-source.svg +1 -0
- pycforge/ide/resources/icons/history.svg +1 -0
- pycforge/ide/resources/icons/indent.svg +1 -0
- pycforge/ide/resources/icons/link-c.svg +1 -0
- pycforge/ide/resources/icons/mappings.svg +1 -0
- pycforge/ide/resources/icons/module.svg +1 -0
- pycforge/ide/resources/icons/move-down.svg +1 -0
- pycforge/ide/resources/icons/move-line-down.svg +1 -0
- pycforge/ide/resources/icons/move-line-up.svg +1 -0
- pycforge/ide/resources/icons/move-up.svg +1 -0
- pycforge/ide/resources/icons/next.svg +1 -0
- pycforge/ide/resources/icons/open.svg +1 -0
- pycforge/ide/resources/icons/outdent.svg +1 -0
- pycforge/ide/resources/icons/outline.svg +1 -0
- pycforge/ide/resources/icons/paste.svg +1 -0
- pycforge/ide/resources/icons/previous.svg +1 -0
- pycforge/ide/resources/icons/primary-module.svg +1 -0
- pycforge/ide/resources/icons/redo.svg +1 -0
- pycforge/ide/resources/icons/remove-document.svg +1 -0
- pycforge/ide/resources/icons/replace.svg +1 -0
- pycforge/ide/resources/icons/save-as.svg +1 -0
- pycforge/ide/resources/icons/save.svg +1 -0
- pycforge/ide/resources/icons/select-all.svg +1 -0
- pycforge/ide/resources/icons/settings.svg +1 -0
- pycforge/ide/resources/icons/show-c.svg +1 -0
- pycforge/ide/resources/icons/split-view.svg +1 -0
- pycforge/ide/resources/icons/summary.svg +1 -0
- pycforge/ide/resources/icons/telemetry.svg +1 -0
- pycforge/ide/resources/icons/toggle-comment.svg +1 -0
- pycforge/ide/resources/icons/toggle-fold.svg +1 -0
- pycforge/ide/resources/icons/undo.svg +1 -0
- pycforge/ide/resources/icons/whitespace.svg +1 -0
- pycforge/ide/revisions.py +400 -0
- pycforge/ide/search_service.py +280 -0
- pycforge/ide/session_history.py +150 -0
- pycforge/ide/source_editing.py +417 -0
- pycforge/ide/source_structure.py +558 -0
- pycforge/ide/source_structure_async.py +191 -0
- pycforge/ide/supervisor.py +503 -0
- pycforge/ide/theme.py +119 -0
- pycforge/ide/theme_stylesheet.py +598 -0
- pycforge/ide/theme_workspace_stylesheet.py +155 -0
- pycforge/ide/visual_tokens.py +142 -0
- pycforge/ide/worker_protocol.py +80 -0
- pycforge/ide/workspace_search.py +462 -0
- pycforge/ide/workspace_session.py +256 -0
- pycforge/laboratory/__init__.py +6 -0
- pycforge/laboratory/audits.py +2244 -0
- pycforge/laboratory/checkpoint_e.py +2685 -0
- pycforge/laboratory/cli.py +119 -0
- pycforge/laboratory/exit_codes.py +11 -0
- pycforge/laboratory/keyword_audit.py +661 -0
- pycforge/laboratory/keyword_only_audit.py +481 -0
- pycforge-0.15.2.dist-info/METADATA +209 -0
- pycforge-0.15.2.dist-info/RECORD +221 -0
- pycforge-0.15.2.dist-info/WHEEL +5 -0
- pycforge-0.15.2.dist-info/entry_points.txt +5 -0
- pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
- pycforge-0.15.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""Authenticated laboratory audit for required keyword-only direct calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .keyword_audit import _facts, _lowering_proof, _table
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def audit_keyword_only(root: Path) -> dict[str, Any]:
|
|
16
|
+
"""Gate the bounded Phase 14D profile without invoking a C toolchain."""
|
|
17
|
+
|
|
18
|
+
from pycforge import (
|
|
19
|
+
ConversionRequest,
|
|
20
|
+
PythonToCConverter,
|
|
21
|
+
ResultStatus,
|
|
22
|
+
SourceBundle,
|
|
23
|
+
SourceDocumentInput,
|
|
24
|
+
)
|
|
25
|
+
from pycforge.converter.c_output import validate_c_text
|
|
26
|
+
from pycforge.converter.contracts.configuration import (
|
|
27
|
+
DEFAULT_RENDERER,
|
|
28
|
+
DEFAULT_RULE_SET,
|
|
29
|
+
PHASE14C_RENDERER,
|
|
30
|
+
PHASE14C_RULE_SET,
|
|
31
|
+
)
|
|
32
|
+
from pycforge.converter.contracts.versions import (
|
|
33
|
+
C_IR_SCHEMA,
|
|
34
|
+
CONVERSION_PLAN_SCHEMA,
|
|
35
|
+
CONVERSION_SUMMARY_SCHEMA,
|
|
36
|
+
DECISION_TRACE_SCHEMA,
|
|
37
|
+
GENERATED_C_SCHEMA,
|
|
38
|
+
KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
39
|
+
PHASE14C_CONVERSION_PLAN_SCHEMA,
|
|
40
|
+
PHASE14C_CONVERSION_SUMMARY_SCHEMA,
|
|
41
|
+
PHASE14C_DECISION_TRACE_SCHEMA,
|
|
42
|
+
)
|
|
43
|
+
from pycforge.converter.core.request import ObservationOptions
|
|
44
|
+
from pycforge.converter.core.serialization import result_to_json
|
|
45
|
+
from pycforge.converter.keyword_only_calls import (
|
|
46
|
+
KEYWORD_ONLY_CALL_KEY_DOMAIN,
|
|
47
|
+
KEYWORD_ONLY_CALL_LOWERING_SHAPE,
|
|
48
|
+
KEYWORD_ONLY_CALL_OBLIGATIONS,
|
|
49
|
+
KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE,
|
|
50
|
+
KEYWORD_ONLY_CALL_RULE_ID,
|
|
51
|
+
KEYWORD_ONLY_CALL_RULE_VERSION,
|
|
52
|
+
KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES,
|
|
53
|
+
KEYWORD_ONLY_CALL_TABLE_ID,
|
|
54
|
+
validate_keyword_only_call_binding_facts,
|
|
55
|
+
)
|
|
56
|
+
from pycforge.converter.support_templates import default_helper_registry
|
|
57
|
+
|
|
58
|
+
source = (
|
|
59
|
+
"def mark_int(value: int) -> int:\n return value\n\n"
|
|
60
|
+
"def mark_bool(value: bool) -> bool:\n return value\n\n"
|
|
61
|
+
"def mark_float(value: float) -> float:\n return value\n\n"
|
|
62
|
+
"def choose(left: int, *, flag: bool, ratio: float) -> int:\n"
|
|
63
|
+
" return left\n\n"
|
|
64
|
+
"def run(x: int, y: bool, z: float) -> int:\n"
|
|
65
|
+
" return choose(ratio=mark_float(z), left=mark_int(x), flag=mark_bool(y))\n"
|
|
66
|
+
)
|
|
67
|
+
cross_primary = (
|
|
68
|
+
"from lib import choose\n\n"
|
|
69
|
+
"def run(value: int, flag: bool, ratio: float) -> int:\n"
|
|
70
|
+
" return choose(ratio=ratio, flag=flag, left=value)\n"
|
|
71
|
+
)
|
|
72
|
+
cross_companion = (
|
|
73
|
+
"def choose(left: int, *, flag: bool, ratio: float) -> int:\n"
|
|
74
|
+
" return left\n"
|
|
75
|
+
)
|
|
76
|
+
required_sink = (
|
|
77
|
+
"def sink(value: int, *, flag: bool) -> int:\n"
|
|
78
|
+
" return value\n\n"
|
|
79
|
+
)
|
|
80
|
+
rejections = {
|
|
81
|
+
"missing-keyword": (
|
|
82
|
+
"PYC2904",
|
|
83
|
+
required_sink
|
|
84
|
+
+ "def run(value: int) -> int:\n return sink(value)\n",
|
|
85
|
+
),
|
|
86
|
+
"keyword-only-positional": (
|
|
87
|
+
"PYC2904",
|
|
88
|
+
required_sink
|
|
89
|
+
+ "def run(value: int, flag: bool) -> int:\n"
|
|
90
|
+
" return sink(value, flag)\n",
|
|
91
|
+
),
|
|
92
|
+
"unknown-keyword": (
|
|
93
|
+
"PYC2912",
|
|
94
|
+
required_sink
|
|
95
|
+
+ "def run(value: int, flag: bool) -> int:\n"
|
|
96
|
+
" return sink(value=value, missing=flag)\n",
|
|
97
|
+
),
|
|
98
|
+
"duplicate-binding": (
|
|
99
|
+
"PYC2912",
|
|
100
|
+
required_sink
|
|
101
|
+
+ "def run(value: int, flag: bool) -> int:\n"
|
|
102
|
+
" return sink(value, value=value, flag=flag)\n",
|
|
103
|
+
),
|
|
104
|
+
"keyword-unpacking": (
|
|
105
|
+
"PYC2910",
|
|
106
|
+
required_sink
|
|
107
|
+
+ "def run(value: int, flag: bool) -> int:\n"
|
|
108
|
+
" return sink(value=value, **flag)\n",
|
|
109
|
+
),
|
|
110
|
+
"mapped-category": (
|
|
111
|
+
"PYC2905",
|
|
112
|
+
required_sink
|
|
113
|
+
+ "def run(value: int, flag: bool) -> int:\n"
|
|
114
|
+
" return sink(value=flag, flag=value)\n",
|
|
115
|
+
),
|
|
116
|
+
"defaulted-keyword-only": (
|
|
117
|
+
"PYC2911",
|
|
118
|
+
"def sink(value: int, *, flag: bool = True) -> int:\n"
|
|
119
|
+
" return value\n\n"
|
|
120
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
121
|
+
" return sink(value=value, flag=flag)\n",
|
|
122
|
+
),
|
|
123
|
+
"variadic-target": (
|
|
124
|
+
"PYC2911",
|
|
125
|
+
"def sink(value: int, *rest: int, flag: bool) -> int:\n"
|
|
126
|
+
" return value\n\n"
|
|
127
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
128
|
+
" return sink(value=value, flag=flag)\n",
|
|
129
|
+
),
|
|
130
|
+
"recursive-target": (
|
|
131
|
+
"PYC2920",
|
|
132
|
+
"def run(value: int, *, flag: bool) -> int:\n"
|
|
133
|
+
" return run(value=value, flag=flag)\n",
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
expected_contracts = {
|
|
137
|
+
"rule_set": "phase14-required-keyword-only-calls-v0.14.3",
|
|
138
|
+
"renderer": "c-renderer-v0.14.3",
|
|
139
|
+
"keyword_only_call_facts": "fact-table/0.14.3",
|
|
140
|
+
"conversion_plan": "conversion-plan/0.14.3",
|
|
141
|
+
"c_ir": "c-ir/0.14.3",
|
|
142
|
+
"generated_c": "generated-c/0.14.3",
|
|
143
|
+
"conversion_summary": "pycforge.conversion-summary/0.14.3",
|
|
144
|
+
"decision_trace": "pycforge.decision-trace/0.14.3",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
converter = PythonToCConverter()
|
|
149
|
+
observation = ObservationOptions("Full", False)
|
|
150
|
+
accepted = converter.convert(
|
|
151
|
+
ConversionRequest.from_source(source),
|
|
152
|
+
observation=observation,
|
|
153
|
+
)
|
|
154
|
+
repeated = converter.convert(
|
|
155
|
+
ConversionRequest.from_source(source),
|
|
156
|
+
observation=observation,
|
|
157
|
+
)
|
|
158
|
+
if accepted.stage_artifact is None:
|
|
159
|
+
raise ValueError("keyword-only conversion omitted its final artifact")
|
|
160
|
+
payload = accepted.stage_artifact.payload
|
|
161
|
+
summary = accepted.conversion_summary or {}
|
|
162
|
+
trace = accepted.decision_trace or {}
|
|
163
|
+
contracts = {
|
|
164
|
+
"rule_set": DEFAULT_RULE_SET,
|
|
165
|
+
"renderer": DEFAULT_RENDERER,
|
|
166
|
+
"keyword_only_call_facts": KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
167
|
+
"conversion_plan": CONVERSION_PLAN_SCHEMA,
|
|
168
|
+
"c_ir": C_IR_SCHEMA,
|
|
169
|
+
"generated_c": GENERATED_C_SCHEMA,
|
|
170
|
+
"conversion_summary": CONVERSION_SUMMARY_SCHEMA,
|
|
171
|
+
"decision_trace": DECISION_TRACE_SCHEMA,
|
|
172
|
+
}
|
|
173
|
+
identity_valid = (
|
|
174
|
+
contracts == expected_contracts
|
|
175
|
+
and accepted.status is ResultStatus.CONVERTED
|
|
176
|
+
and accepted.stage_artifact.kind == "generated_c"
|
|
177
|
+
and accepted.stage_artifact.schema_version == "0.14.3"
|
|
178
|
+
and payload.get("schema_version") == GENERATED_C_SCHEMA
|
|
179
|
+
and payload.get("c_ir_schema") == C_IR_SCHEMA
|
|
180
|
+
and payload.get("rule_set_version") == DEFAULT_RULE_SET
|
|
181
|
+
and payload.get("renderer_version") == DEFAULT_RENDERER
|
|
182
|
+
and summary.get("schema_version") == CONVERSION_SUMMARY_SCHEMA
|
|
183
|
+
and trace.get("schema_version") == DECISION_TRACE_SCHEMA
|
|
184
|
+
)
|
|
185
|
+
table = _table(payload, KEYWORD_ONLY_CALL_TABLE_ID)
|
|
186
|
+
records = list(table.get("records", ()))
|
|
187
|
+
facts = _facts(payload, KEYWORD_ONLY_CALL_TABLE_ID)
|
|
188
|
+
fact = facts[0] if len(facts) == 1 else {}
|
|
189
|
+
constants_valid = (
|
|
190
|
+
KEYWORD_ONLY_CALL_TABLE_ID == "keyword-only-call-binding-facts"
|
|
191
|
+
and KEYWORD_ONLY_CALL_KEY_DOMAIN == "keyword-only-call-node-id"
|
|
192
|
+
and KEYWORD_ONLY_CALL_RULE_ID
|
|
193
|
+
== "phase14.keyword_only_call.exact_binding"
|
|
194
|
+
and KEYWORD_ONLY_CALL_RULE_VERSION == "0.14.3"
|
|
195
|
+
and KEYWORD_ONLY_CALL_LOWERING_SHAPE
|
|
196
|
+
== "source-order-actual-temporaries-formal-order-references-v1"
|
|
197
|
+
)
|
|
198
|
+
table_valid = (
|
|
199
|
+
table.get("schema_version") == KEYWORD_ONLY_CALL_FACT_SCHEMA
|
|
200
|
+
and table.get("producer_stage") == "analysis.plan"
|
|
201
|
+
and table.get("key_domain") == KEYWORD_ONLY_CALL_KEY_DOMAIN
|
|
202
|
+
and table.get("completeness") == "complete"
|
|
203
|
+
and tuple(table.get("invalidation_dependencies", ()))
|
|
204
|
+
== KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES
|
|
205
|
+
and len(records) == 1
|
|
206
|
+
and records[0].get("key") == fact.get("call_node_id")
|
|
207
|
+
and tuple(records[0].get("provenance", {}).get("evidence", ()))
|
|
208
|
+
== KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE
|
|
209
|
+
)
|
|
210
|
+
fact_valid = (
|
|
211
|
+
fact.get("supported") is True
|
|
212
|
+
and fact.get("parameter_names") == ["left", "flag", "ratio"]
|
|
213
|
+
and fact.get("parameter_kinds")
|
|
214
|
+
== ["positional-or-keyword", "keyword-only", "keyword-only"]
|
|
215
|
+
and fact.get("keyword_names") == ["ratio", "left", "flag"]
|
|
216
|
+
and fact.get("source_to_parameter_ordinals") == [2, 0, 1]
|
|
217
|
+
and fact.get("parameter_to_source_ordinals") == [1, 2, 0]
|
|
218
|
+
and fact.get("evaluation_order")
|
|
219
|
+
== fact.get("source_argument_node_ids")
|
|
220
|
+
and fact.get("arguments_evaluated_once") is True
|
|
221
|
+
and fact.get("parameter_coverage_exact") is True
|
|
222
|
+
and fact.get("keyword_only_coverage_exact") is True
|
|
223
|
+
and fact.get("lowering_shape") == KEYWORD_ONLY_CALL_LOWERING_SHAPE
|
|
224
|
+
and fact.get("allocation_model") == "none"
|
|
225
|
+
and fact.get("cleanup_model") == "none"
|
|
226
|
+
and fact.get("runtime_binding_failure") == "proved-absent"
|
|
227
|
+
)
|
|
228
|
+
independent_valid, independent_reason = (
|
|
229
|
+
validate_keyword_only_call_binding_facts(payload)
|
|
230
|
+
)
|
|
231
|
+
plans = [
|
|
232
|
+
item
|
|
233
|
+
for item in payload.get("rule_plans", ())
|
|
234
|
+
if item.get("rule_id") == KEYWORD_ONLY_CALL_RULE_ID
|
|
235
|
+
]
|
|
236
|
+
plan_valid = (
|
|
237
|
+
len(plans) == 1
|
|
238
|
+
and plans[0].get("source_node_id") == fact.get("call_node_id")
|
|
239
|
+
and plans[0].get("rule_version") == KEYWORD_ONLY_CALL_RULE_VERSION
|
|
240
|
+
and plans[0].get("support_state") == "SupportedDirect"
|
|
241
|
+
and plans[0].get("helper_requirements") == []
|
|
242
|
+
and plans[0].get("semantic_obligations")[: len(KEYWORD_ONLY_CALL_OBLIGATIONS)]
|
|
243
|
+
== list(KEYWORD_ONLY_CALL_OBLIGATIONS)
|
|
244
|
+
and plans[0].get("resolved_obligations")
|
|
245
|
+
== plans[0].get("semantic_obligations")
|
|
246
|
+
and plans[0].get("unresolved_obligations") == []
|
|
247
|
+
)
|
|
248
|
+
lowering_valid, lowering_evidence = _lowering_proof(payload, facts)
|
|
249
|
+
observer_valid = (
|
|
250
|
+
list(summary.get("keyword_only_calls", ())) == facts
|
|
251
|
+
and [
|
|
252
|
+
item
|
|
253
|
+
for item in trace.get("rule_decisions", ())
|
|
254
|
+
if item.get("rule_id") == KEYWORD_ONLY_CALL_RULE_ID
|
|
255
|
+
]
|
|
256
|
+
== plans
|
|
257
|
+
and trace.get("trace_level") == "Full"
|
|
258
|
+
and trace.get("completeness") == "complete"
|
|
259
|
+
and trace.get("truncated") is False
|
|
260
|
+
and trace.get("observer_failed") is False
|
|
261
|
+
)
|
|
262
|
+
helper_registry = default_helper_registry()
|
|
263
|
+
helper_valid = (
|
|
264
|
+
payload.get("helper_manifest") == []
|
|
265
|
+
and payload.get("helper_requirements") == []
|
|
266
|
+
and helper_registry.fingerprint
|
|
267
|
+
== "fcb719f07984f3b79e17262e90f93823a9a0139a60529f8486ab09f6c3663d98"
|
|
268
|
+
)
|
|
269
|
+
deterministic = (
|
|
270
|
+
result_to_json(accepted) == result_to_json(repeated)
|
|
271
|
+
and accepted.request_fingerprint == repeated.request_fingerprint
|
|
272
|
+
and accepted.output_fingerprint == repeated.output_fingerprint
|
|
273
|
+
and accepted.stage_artifact.artifact_fingerprint
|
|
274
|
+
== repeated.stage_artifact.artifact_fingerprint
|
|
275
|
+
)
|
|
276
|
+
fresh_code = (
|
|
277
|
+
"import hashlib,json; "
|
|
278
|
+
"from pycforge import ConversionRequest,PythonToCConverter; "
|
|
279
|
+
"from pycforge.converter.core.request import ObservationOptions; "
|
|
280
|
+
"from pycforge.converter.core.serialization import result_to_json; "
|
|
281
|
+
f"s={source!r}; "
|
|
282
|
+
"r=PythonToCConverter().convert(ConversionRequest.from_source(s),"
|
|
283
|
+
"observation=ObservationOptions('Full',False)); "
|
|
284
|
+
"print(json.dumps({'serialized_sha256':hashlib.sha256(result_to_json(r).encode()).hexdigest(),"
|
|
285
|
+
"'generated_sha256':hashlib.sha256((r.generated_c or '').encode()).hexdigest(),"
|
|
286
|
+
"'artifact_fingerprint':r.stage_artifact.artifact_fingerprint.value},sort_keys=True))"
|
|
287
|
+
)
|
|
288
|
+
fresh = json.loads(
|
|
289
|
+
subprocess.check_output(
|
|
290
|
+
[sys.executable, "-c", fresh_code],
|
|
291
|
+
cwd=root,
|
|
292
|
+
env={**__import__("os").environ, "PYTHONPATH": str(root)},
|
|
293
|
+
text=True,
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
fresh_expected = {
|
|
297
|
+
"serialized_sha256": hashlib.sha256(
|
|
298
|
+
result_to_json(accepted).encode()
|
|
299
|
+
).hexdigest(),
|
|
300
|
+
"generated_sha256": hashlib.sha256(
|
|
301
|
+
(accepted.generated_c or "").encode()
|
|
302
|
+
).hexdigest(),
|
|
303
|
+
"artifact_fingerprint": accepted.stage_artifact.artifact_fingerprint.value,
|
|
304
|
+
}
|
|
305
|
+
fresh_valid = fresh == fresh_expected
|
|
306
|
+
|
|
307
|
+
cross_request = ConversionRequest(
|
|
308
|
+
SourceBundle(
|
|
309
|
+
SourceDocumentInput("app.py", cross_primary, "app"),
|
|
310
|
+
(SourceDocumentInput("lib.py", cross_companion, "lib"),),
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
cross = converter.convert(cross_request, observation=observation)
|
|
314
|
+
cross_repeated = converter.convert(cross_request, observation=observation)
|
|
315
|
+
cross_payload = (
|
|
316
|
+
cross.stage_artifact.payload if cross.stage_artifact is not None else {}
|
|
317
|
+
)
|
|
318
|
+
cross_facts = (
|
|
319
|
+
_facts(cross_payload, KEYWORD_ONLY_CALL_TABLE_ID)
|
|
320
|
+
if cross_payload
|
|
321
|
+
else []
|
|
322
|
+
)
|
|
323
|
+
cross_fact = cross_facts[0] if len(cross_facts) == 1 else {}
|
|
324
|
+
cross_independent, cross_reason = (
|
|
325
|
+
validate_keyword_only_call_binding_facts(cross_payload)
|
|
326
|
+
if cross_payload
|
|
327
|
+
else (False, "cross-module conversion omitted its payload")
|
|
328
|
+
)
|
|
329
|
+
cross_lowering, cross_lowering_evidence = _lowering_proof(
|
|
330
|
+
cross_payload,
|
|
331
|
+
cross_facts,
|
|
332
|
+
)
|
|
333
|
+
cross_valid = (
|
|
334
|
+
cross.status is ResultStatus.CONVERTED
|
|
335
|
+
and cross.stage_artifact is not None
|
|
336
|
+
and cross.stage_artifact.schema_version == "0.14.3"
|
|
337
|
+
and cross_fact.get("source_to_parameter_ordinals") == [2, 1, 0]
|
|
338
|
+
and cross_fact.get("parameter_to_source_ordinals") == [2, 1, 0]
|
|
339
|
+
and (cross.conversion_summary or {})
|
|
340
|
+
.get("module_initialization", {})
|
|
341
|
+
.get("module_order")
|
|
342
|
+
== ["lib", "app"]
|
|
343
|
+
and cross_independent
|
|
344
|
+
and cross_lowering
|
|
345
|
+
and result_to_json(cross) == result_to_json(cross_repeated)
|
|
346
|
+
and validate_c_text(cross.generated_c or "").accepted
|
|
347
|
+
and cross_payload.get("helper_manifest") == []
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
rejected = {
|
|
351
|
+
label: converter.convert(ConversionRequest.from_source(text))
|
|
352
|
+
for label, (_, text) in rejections.items()
|
|
353
|
+
}
|
|
354
|
+
rejection_codes = {
|
|
355
|
+
label: [item.code for item in result.diagnostics]
|
|
356
|
+
for label, result in rejected.items()
|
|
357
|
+
}
|
|
358
|
+
rejections_valid = all(
|
|
359
|
+
result.status is ResultStatus.REJECTED
|
|
360
|
+
and rejection_codes[label] == [code]
|
|
361
|
+
and result.generated_c is None
|
|
362
|
+
and result.output_fingerprint is None
|
|
363
|
+
and result.stage_artifact is not None
|
|
364
|
+
and "c_ir" not in result.stage_artifact.payload
|
|
365
|
+
and "helper_manifest" not in result.stage_artifact.payload
|
|
366
|
+
for label, (code, _) in rejections.items()
|
|
367
|
+
for result in (rejected[label],)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
historical = converter.convert(
|
|
371
|
+
ConversionRequest.from_source(
|
|
372
|
+
source,
|
|
373
|
+
rule_set_version=PHASE14C_RULE_SET,
|
|
374
|
+
renderer_version=PHASE14C_RENDERER,
|
|
375
|
+
),
|
|
376
|
+
observation=observation,
|
|
377
|
+
)
|
|
378
|
+
historical_payload = (
|
|
379
|
+
historical.stage_artifact.payload
|
|
380
|
+
if historical.stage_artifact is not None
|
|
381
|
+
else {}
|
|
382
|
+
)
|
|
383
|
+
historical_exact = (
|
|
384
|
+
historical.status is ResultStatus.REJECTED
|
|
385
|
+
and [item.code for item in historical.diagnostics] == ["PYC2911"]
|
|
386
|
+
and historical.stage_artifact is not None
|
|
387
|
+
and historical.stage_artifact.kind == "conversion_plan"
|
|
388
|
+
and historical.stage_artifact.schema_version == "0.14.2"
|
|
389
|
+
and historical_payload.get("schema_version")
|
|
390
|
+
== PHASE14C_CONVERSION_PLAN_SCHEMA
|
|
391
|
+
and (historical.conversion_summary or {}).get("schema_version")
|
|
392
|
+
== PHASE14C_CONVERSION_SUMMARY_SCHEMA
|
|
393
|
+
and (historical.decision_trace or {}).get("schema_version")
|
|
394
|
+
== PHASE14C_DECISION_TRACE_SCHEMA
|
|
395
|
+
and KEYWORD_ONLY_CALL_TABLE_ID
|
|
396
|
+
not in {
|
|
397
|
+
item.get("table_id")
|
|
398
|
+
for item in historical_payload.get("fact_tables", ())
|
|
399
|
+
}
|
|
400
|
+
and historical.generated_c is None
|
|
401
|
+
and historical.output_fingerprint is None
|
|
402
|
+
)
|
|
403
|
+
generated_conformance = validate_c_text(accepted.generated_c or "").accepted
|
|
404
|
+
passed = all(
|
|
405
|
+
(
|
|
406
|
+
identity_valid,
|
|
407
|
+
constants_valid,
|
|
408
|
+
table_valid,
|
|
409
|
+
fact_valid,
|
|
410
|
+
independent_valid,
|
|
411
|
+
plan_valid,
|
|
412
|
+
lowering_valid,
|
|
413
|
+
observer_valid,
|
|
414
|
+
helper_valid,
|
|
415
|
+
generated_conformance,
|
|
416
|
+
deterministic,
|
|
417
|
+
fresh_valid,
|
|
418
|
+
cross_valid,
|
|
419
|
+
rejections_valid,
|
|
420
|
+
historical_exact,
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
return {
|
|
424
|
+
"audit": "keyword-only",
|
|
425
|
+
"passed": passed,
|
|
426
|
+
"active_contracts": contracts,
|
|
427
|
+
"active_contract_identities_valid": identity_valid,
|
|
428
|
+
"keyword_only_rule_id": KEYWORD_ONLY_CALL_RULE_ID,
|
|
429
|
+
"keyword_only_rule_version": KEYWORD_ONLY_CALL_RULE_VERSION,
|
|
430
|
+
"keyword_only_fact_schema": KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
431
|
+
"keyword_only_contract_constants_valid": constants_valid,
|
|
432
|
+
"keyword_only_fact_count": len(facts),
|
|
433
|
+
"keyword_only_fact_table_valid": table_valid,
|
|
434
|
+
"keyword_only_facts_valid": fact_valid,
|
|
435
|
+
"keyword_only_rule_plan_count": len(plans),
|
|
436
|
+
"keyword_only_rule_plans_valid": plan_valid,
|
|
437
|
+
"independent_fact_and_plan_validation": independent_valid,
|
|
438
|
+
"independent_validation_message": independent_reason,
|
|
439
|
+
"source_to_parameter_ordinals": fact.get(
|
|
440
|
+
"source_to_parameter_ordinals"
|
|
441
|
+
),
|
|
442
|
+
"parameter_to_source_ordinals": fact.get(
|
|
443
|
+
"parameter_to_source_ordinals"
|
|
444
|
+
),
|
|
445
|
+
"source_order_staging_and_formal_reference_permutation_valid": lowering_valid,
|
|
446
|
+
"lowering_evidence": lowering_evidence,
|
|
447
|
+
"observer_evidence_valid": observer_valid,
|
|
448
|
+
"cross_module_valid": cross_valid,
|
|
449
|
+
"cross_module_independent_validation": cross_independent,
|
|
450
|
+
"cross_module_independent_validation_message": cross_reason,
|
|
451
|
+
"cross_module_lowering_evidence": cross_lowering_evidence,
|
|
452
|
+
"rejection_codes": rejection_codes,
|
|
453
|
+
"rejections_exact_and_publish_no_c": rejections_valid,
|
|
454
|
+
"historical_phase14c_exact_rejection": historical_exact,
|
|
455
|
+
"helper_manifest_empty": payload.get("helper_manifest") == [],
|
|
456
|
+
"helper_registry_fingerprint": helper_registry.fingerprint,
|
|
457
|
+
"helper_contract_unchanged": helper_valid,
|
|
458
|
+
"allocation": "none",
|
|
459
|
+
"cleanup": "none",
|
|
460
|
+
"runtime_binding_failure": "proved-absent",
|
|
461
|
+
"generated_c_conformance": generated_conformance,
|
|
462
|
+
"deterministic": deterministic,
|
|
463
|
+
"fresh_process_deterministic": fresh_valid,
|
|
464
|
+
"serialized_sha256": fresh_expected["serialized_sha256"],
|
|
465
|
+
"generated_c_sha256": fresh_expected["generated_sha256"],
|
|
466
|
+
"python_subprocess_used_for_determinism_only": True,
|
|
467
|
+
"c_toolchain_invoked": False,
|
|
468
|
+
"generated_c_compiled_or_executed": False,
|
|
469
|
+
}
|
|
470
|
+
except Exception as exc:
|
|
471
|
+
return {
|
|
472
|
+
"audit": "keyword-only",
|
|
473
|
+
"passed": False,
|
|
474
|
+
"error": f"{type(exc).__name__}: {exc}",
|
|
475
|
+
"python_subprocess_used_for_determinism_only": True,
|
|
476
|
+
"c_toolchain_invoked": False,
|
|
477
|
+
"generated_c_compiled_or_executed": False,
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
__all__ = ["audit_keyword_only"]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycforge
|
|
3
|
+
Version: 0.15.2
|
|
4
|
+
Summary: Deterministic bounded Python-to-C source transpiler
|
|
5
|
+
License-Expression: GPL-3.0-only
|
|
6
|
+
Project-URL: Homepage, https://github.com/lastforkbender/pycforge
|
|
7
|
+
Project-URL: Repository, https://github.com/lastforkbender/pycforge
|
|
8
|
+
Project-URL: Issues, https://github.com/lastforkbender/pycforge/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/lastforkbender/pycforge/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: python,c,transpiler,pyqt5,desktop
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: PyQt5<6,>=5.15.11
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# PyCForge
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/pycforge/)
|
|
28
|
+
[](https://pypi.org/project/pycforge/)
|
|
29
|
+
[](https://github.com/lastforkbender/pycforge/actions/workflows/ci.yml)
|
|
30
|
+
[](https://github.com/lastforkbender/pycforge/blob/main/LICENSE)
|
|
31
|
+
|
|
32
|
+
PyCForge is a deterministic Python-to-C source transpiler with a full PyQt5
|
|
33
|
+
desktop workspace, a command-line interface, and a Python API. It converts a
|
|
34
|
+
documented, deliberately bounded Python subset into readable C11 source while
|
|
35
|
+
producing diagnostics, source mappings, decision traces, and reproducible
|
|
36
|
+
fingerprints.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
**[Open the 32-page PyCForge Programmer's Conversion Guide (PDF)](https://raw.githubusercontent.com/lastforkbender/pycforge/6ad94a7963720772b60ae16b46da46396b1e616d/docs/PyCForge_v0_15_2_Conversion_Examples_Reference_Edition.pdf)**
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
PyCForge requires Python 3.11 or newer. Install it from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install pycforge
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That one command installs **PyQt5 and the desktop application as required
|
|
51
|
+
dependencies**. There is no GUI extra and no separate desktop package.
|
|
52
|
+
|
|
53
|
+
Launch the desktop workspace:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pycforge-workspace
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The equivalent module command is:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m pycforge.ide
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## A 60-second conversion
|
|
66
|
+
|
|
67
|
+
Save this as `example.py`:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
def add(left: int, right: int) -> int:
|
|
71
|
+
return left + right
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Convert it:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pycforge convert example.py --output example.c
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
PyCForge produces:
|
|
81
|
+
|
|
82
|
+
```c
|
|
83
|
+
#include <stdint.h>
|
|
84
|
+
|
|
85
|
+
int64_t add(int64_t left, int64_t right);
|
|
86
|
+
|
|
87
|
+
int64_t add(int64_t left, int64_t right)
|
|
88
|
+
{
|
|
89
|
+
return left + right;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The generated C is deterministic for the same authenticated source bundle and
|
|
94
|
+
converter configuration.
|
|
95
|
+
|
|
96
|
+
## What PyCForge provides
|
|
97
|
+
|
|
98
|
+
- A Python-first PyQt5 workspace with document tabs, source splitting,
|
|
99
|
+
navigation, search, outline, command palette, and conversion history.
|
|
100
|
+
- Explicit source bundles containing 1 to 64 Python documents, including
|
|
101
|
+
bounded cross-module function imports.
|
|
102
|
+
- Read-only generated C with diagnostics, source mappings, conversion summary,
|
|
103
|
+
decision trace, and telemetry inspectors.
|
|
104
|
+
- Isolated, cancellable desktop conversion so the converter does not run on
|
|
105
|
+
the GUI thread.
|
|
106
|
+
- A headless CLI for scripts and build pipelines.
|
|
107
|
+
- A Python API for applications that need structured conversion results.
|
|
108
|
+
- Stable diagnostics and fail-closed rejection of unsupported Python.
|
|
109
|
+
|
|
110
|
+
## Command-line interface
|
|
111
|
+
|
|
112
|
+
Write generated C to a file:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pycforge convert input.py --output generated.c
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Emit the structured result as JSON:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pycforge --format json convert input.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See all commands and options:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pycforge --help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Python API
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from pycforge import ConversionRequest, PythonToCConverter
|
|
134
|
+
|
|
135
|
+
source = """\
|
|
136
|
+
def add(left: int, right: int) -> int:
|
|
137
|
+
return left + right
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
result = PythonToCConverter().convert(
|
|
141
|
+
ConversionRequest.from_source(source)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if result.generated_c is not None:
|
|
145
|
+
print(result.generated_c)
|
|
146
|
+
else:
|
|
147
|
+
for diagnostic in result.diagnostics:
|
|
148
|
+
print(diagnostic)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The desktop workspace, CLI, and Python API use the same converter and result
|
|
152
|
+
contracts.
|
|
153
|
+
|
|
154
|
+
## Supported Python
|
|
155
|
+
|
|
156
|
+
PyCForge is intentionally not a general Python runtime. Its current subset
|
|
157
|
+
includes strictly annotated top-level functions using selected scalar values,
|
|
158
|
+
arithmetic and comparisons, `if`/`elif`/`else`, bounded `while` and `range`
|
|
159
|
+
loops, direct eligible function calls, fixed homogeneous containers, and a
|
|
160
|
+
bounded static-record profile.
|
|
161
|
+
|
|
162
|
+
Anything outside the documented subset is unsupported by default and is
|
|
163
|
+
rejected with diagnostics rather than silently approximated. Read the exact
|
|
164
|
+
[supported-Python specification](https://github.com/lastforkbender/pycforge/blob/main/specifications/supported_python.md)
|
|
165
|
+
before adopting PyCForge for production input.
|
|
166
|
+
|
|
167
|
+
## Safety boundary
|
|
168
|
+
|
|
169
|
+
PyCForge parses supplied source as data. It does not import or execute the
|
|
170
|
+
input Python, scan the host environment for modules, or resolve undeclared
|
|
171
|
+
files. It stops after C source generation and does not compile, assemble, link,
|
|
172
|
+
load, or execute the generated C.
|
|
173
|
+
|
|
174
|
+
Python `int` values map to the documented signed 64-bit representation domain;
|
|
175
|
+
other supported Python values likewise follow explicit target-C contracts.
|
|
176
|
+
Review the
|
|
177
|
+
[product boundary](https://github.com/lastforkbender/pycforge/blob/main/specifications/product_boundary.md)
|
|
178
|
+
and the Programmer's Conversion Guide for the complete limitations.
|
|
179
|
+
|
|
180
|
+
## Documentation
|
|
181
|
+
|
|
182
|
+
- **[Programmer's Conversion Guide — 0.15.2 Reference Edition (PDF)](https://raw.githubusercontent.com/lastforkbender/pycforge/6ad94a7963720772b60ae16b46da46396b1e616d/docs/PyCForge_v0_15_2_Conversion_Examples_Reference_Edition.pdf)**
|
|
183
|
+
- [Supported Python](https://github.com/lastforkbender/pycforge/blob/main/specifications/supported_python.md)
|
|
184
|
+
- [Workspace specification](https://github.com/lastforkbender/pycforge/blob/main/specifications/pycforge_workspace.md)
|
|
185
|
+
- [Current state](https://github.com/lastforkbender/pycforge/blob/main/CURRENT_STATE.md)
|
|
186
|
+
- [Release notes](https://github.com/lastforkbender/pycforge/blob/main/RELEASE_NOTES.md)
|
|
187
|
+
- [Changelog](https://github.com/lastforkbender/pycforge/blob/main/CHANGELOG.md)
|
|
188
|
+
- [Security policy](https://github.com/lastforkbender/pycforge/blob/main/SECURITY.md)
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
git clone https://github.com/lastforkbender/pycforge.git
|
|
194
|
+
cd pycforge
|
|
195
|
+
python -m venv .venv
|
|
196
|
+
python -m pip install --upgrade pip
|
|
197
|
+
python -m pip install -e .
|
|
198
|
+
python -m pip install pytest
|
|
199
|
+
QT_QPA_PLATFORM=offscreen python -m pytest -q
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The normal editable installation includes PyQt5, matching the package users
|
|
203
|
+
receive from PyPI. Automated release checks cover Python 3.11 and 3.12 on
|
|
204
|
+
Linux with real PyQt5 widgets using Qt's offscreen platform.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
PyCForge is free software released under the
|
|
209
|
+
[GNU General Public License v3.0 only](https://github.com/lastforkbender/pycforge/blob/main/LICENSE).
|