@aryacode/dsh-engineering 0.0.0-stage → 0.3.0
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.
- package/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +228 -2
- package/cordis.patch.yml +122 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +77 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""Conservative, source-linked programming review; never mutates a project."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .variables import parse_declarations
|
|
8
|
+
from .knowledge import reference, signature
|
|
9
|
+
|
|
10
|
+
IDENT = r'[A-Za-z_][A-Za-z_0-9]*'
|
|
11
|
+
INTEGER_RANGES = {
|
|
12
|
+
'SINT': (-128, 127), 'USINT': (0, 255), 'INT': (-32768, 32767), 'UINT': (0, 65535),
|
|
13
|
+
'DINT': (-2147483648, 2147483647), 'UDINT': (0, 4294967295),
|
|
14
|
+
'LINT': (-2**63, 2**63-1), 'ULINT': (0, 2**64-1),
|
|
15
|
+
'BYTE': (0, 255), 'WORD': (0, 65535), 'DWORD': (0, 2**32-1), 'LWORD': (0, 2**64-1),
|
|
16
|
+
}
|
|
17
|
+
ELEMENTARY = set(INTEGER_RANGES) | {'BOOL', 'REAL', 'LREAL', 'TIME', 'STRING', 'WSTRING', 'DATE', 'TOD', 'DT'}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def mask(text):
|
|
21
|
+
"""Mask nested comments and IEC strings while preserving offsets and newlines."""
|
|
22
|
+
out = list(text); i = 0; depth = 0; quote = None
|
|
23
|
+
def blank(pos, count=1):
|
|
24
|
+
for j in range(pos, min(pos+count, len(text))):
|
|
25
|
+
if text[j] not in '\r\n': out[j] = ' '
|
|
26
|
+
while i < len(text):
|
|
27
|
+
pair = text[i:i+2]
|
|
28
|
+
if depth:
|
|
29
|
+
if pair == '(*': depth += 1; blank(i, 2); i += 2
|
|
30
|
+
elif pair == '*)': depth -= 1; blank(i, 2); i += 2
|
|
31
|
+
else: blank(i); i += 1
|
|
32
|
+
elif quote:
|
|
33
|
+
if text[i] == '$': blank(i, 2); i += 2
|
|
34
|
+
elif text[i] == quote:
|
|
35
|
+
if i+1 < len(text) and text[i+1] == quote: blank(i, 2); i += 2
|
|
36
|
+
else: blank(i); quote = None; i += 1
|
|
37
|
+
else: blank(i); i += 1
|
|
38
|
+
elif pair == '(*': depth = 1; blank(i, 2); i += 2
|
|
39
|
+
elif pair == '//':
|
|
40
|
+
end = text.find('\n', i)
|
|
41
|
+
end = len(text) if end < 0 else end
|
|
42
|
+
blank(i, end-i); i = end
|
|
43
|
+
elif text[i] in "'\"": quote = text[i]; blank(i); i += 1
|
|
44
|
+
else: i += 1
|
|
45
|
+
return ''.join(out)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def calls(body):
|
|
49
|
+
clean = mask(body)
|
|
50
|
+
for match in re.finditer(r'(?<![.\w])('+IDENT+r')\s*\(', clean):
|
|
51
|
+
depth = 1; i = match.end(); start = i; parts = []
|
|
52
|
+
while i < len(clean) and depth:
|
|
53
|
+
char = clean[i]
|
|
54
|
+
if char in '([': depth += 1
|
|
55
|
+
elif char in ')]': depth -= 1
|
|
56
|
+
if depth == 1 and char == ',': parts.append((start, i)); start = i+1
|
|
57
|
+
if depth == 0: parts.append((start, i))
|
|
58
|
+
i += 1
|
|
59
|
+
if depth: continue
|
|
60
|
+
args = []
|
|
61
|
+
for first, last in parts:
|
|
62
|
+
# Masked strings look like whitespace. Stop at the operator so its
|
|
63
|
+
# original literal is not consumed as trailing whitespace.
|
|
64
|
+
param = re.match(r'\s*('+IDENT+r')\s*(:=|=>)', clean[first:last])
|
|
65
|
+
if param:
|
|
66
|
+
args.append((param[1], param[2], body[first+param.end():last].strip()))
|
|
67
|
+
yield match[1], args, clean.count('\n', 0, match.start())+1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def norm(type_name):
|
|
71
|
+
return re.sub(r'\s+', '', type_name).upper()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def expression_type(expr, types):
|
|
75
|
+
expr = expr.strip()
|
|
76
|
+
if re.fullmatch(IDENT, expr):
|
|
77
|
+
if expr.upper() in ('TRUE', 'FALSE'): return 'BOOL'
|
|
78
|
+
return types.get(expr.upper())
|
|
79
|
+
typed = re.fullmatch(r'('+IDENT+r')#[A-Za-z_0-9.+-]+', expr)
|
|
80
|
+
if typed:
|
|
81
|
+
literal_type = typed[1].upper()
|
|
82
|
+
return {'T': 'TIME', 'D': 'DATE', 'TIME_OF_DAY': 'TOD', 'DATE_AND_TIME': 'DT'}.get(literal_type, literal_type)
|
|
83
|
+
return None # Untyped literals, conversions, arrays and compound expressions need compiler context.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def integer_literal(expr):
|
|
87
|
+
value = expr.strip().replace('_', '')
|
|
88
|
+
if re.fullmatch(r'[+-]?\d+', value): return int(value)
|
|
89
|
+
match = re.fullmatch(r'(?:(?:SINT|USINT|INT|UINT|DINT|UDINT|LINT|ULINT|BYTE|WORD|DWORD|LWORD)#)?(?:(2|8|16)#)?([+-]?[0-9A-F]+)', value, re.I)
|
|
90
|
+
if not match: return None
|
|
91
|
+
try: return int(match[2], int(match[1] or 10))
|
|
92
|
+
except ValueError: return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def constant_expression(expr):
|
|
96
|
+
"""Recognize complete IEC literals, without treating members/indexes as constants."""
|
|
97
|
+
value = expr.strip()
|
|
98
|
+
if value.upper() in ('TRUE', 'FALSE') or integer_literal(value) is not None:
|
|
99
|
+
return True
|
|
100
|
+
if re.fullmatch(r'[+-]?\d[\d_]*(?:\.[\d_]+(?:[Ee][+-]?\d[\d_]*)?|[Ee][+-]?\d[\d_]*)', value):
|
|
101
|
+
return True
|
|
102
|
+
# A typed literal cannot designate writable storage. Its validity is still
|
|
103
|
+
# the compiler's job; DATE/TIME spelling must not hide a non-variable binding.
|
|
104
|
+
if re.fullmatch(r'(?:'+IDENT+r')#[^;\r\n]+', value):
|
|
105
|
+
return True
|
|
106
|
+
return bool(re.fullmatch(r"'(?:\$[^\r\n]|''|[^'$])*'|\"(?:\$[^\r\n]|\"\"|[^\"$])*\"", value))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def review(body, declarations, globals_text=None, *, signatures=None):
|
|
110
|
+
table = parse_declarations(declarations)
|
|
111
|
+
globals_table = parse_declarations(globals_text) if globals_text is not None else None
|
|
112
|
+
variables = {v.name.upper(): v for v in table.variables}
|
|
113
|
+
types = {n: norm(v.type_name) for n, v in variables.items()}
|
|
114
|
+
globals_ = {v.name.upper(): v for v in globals_table.variables} if globals_table else {}
|
|
115
|
+
findings, unresolved = [], set()
|
|
116
|
+
def add(code, message, topic, severity='warning', line=None, evidence=None):
|
|
117
|
+
findings.append({'code': code, 'severity': severity, 'message': message, 'line': line,
|
|
118
|
+
'reference': evidence or reference(topic), 'automatic_fix': False})
|
|
119
|
+
for warning in table.warnings:
|
|
120
|
+
add('declarations-incomplete', warning, 'scope')
|
|
121
|
+
for name, variable in variables.items():
|
|
122
|
+
if variable.section == 'VAR_EXTERNAL' and globals_table is not None and not globals_table.warnings:
|
|
123
|
+
if name not in globals_:
|
|
124
|
+
add('external-missing-global', f'{variable.name} has no resource global declaration.', 'scope', 'error', variable.line)
|
|
125
|
+
elif norm(variable.type_name) != norm(globals_[name].type_name):
|
|
126
|
+
severity = 'error' if types[name] in ELEMENTARY and norm(globals_[name].type_name) in ELEMENTARY else 'warning'
|
|
127
|
+
add('external-type-mismatch', f'{variable.name}: POU type {variable.type_name}, resource type {globals_[name].type_name}.', 'scope', severity, variable.line)
|
|
128
|
+
elif name in globals_ and variable.section != 'VAR_EXTERNAL':
|
|
129
|
+
add('local-shadows-global', f'{variable.name} is local and also names a resource global; confirm the intended scope.', 'scope', line=variable.line)
|
|
130
|
+
if variable.initial_value and types[name] in INTEGER_RANGES:
|
|
131
|
+
value = integer_literal(variable.initial_value)
|
|
132
|
+
low, high = INTEGER_RANGES[types[name]]
|
|
133
|
+
if value is not None and not low <= value <= high:
|
|
134
|
+
add('initializer-out-of-range', f'{variable.name} initializer {value} exceeds {variable.type_name} [{low}, {high}].', 'strict-types', 'error', variable.line)
|
|
135
|
+
clean = mask(body)
|
|
136
|
+
# Check direct variable-to-variable assignments only; complex expressions stay unresolved.
|
|
137
|
+
for match in re.finditer(r'(?<![.\w])('+IDENT+r')\s*:=\s*([^;\n]+);', clean):
|
|
138
|
+
target, expression = match[1].upper(), match[2].strip()
|
|
139
|
+
if target not in types: continue
|
|
140
|
+
# Named call arguments must not be mistaken for assignments to homonymous locals.
|
|
141
|
+
prefix = clean[:match.start()]
|
|
142
|
+
if prefix.count('(') != prefix.count(')'): continue
|
|
143
|
+
line = clean.count('\n', 0, match.start())+1
|
|
144
|
+
actual = expression_type(expression, types)
|
|
145
|
+
expected = types[target]
|
|
146
|
+
if actual and expected in ELEMENTARY and actual in ELEMENTARY and expected != actual:
|
|
147
|
+
add('explicit-conversion-review', f'{match[1]} is {expected}; assigned expression is {actual}. Check an explicit conversion.', 'strict-types', line=line)
|
|
148
|
+
if expected in INTEGER_RANGES:
|
|
149
|
+
value = integer_literal(expression)
|
|
150
|
+
low, high = INTEGER_RANGES[expected]
|
|
151
|
+
if value is not None and not low <= value <= high:
|
|
152
|
+
add('assignment-out-of-range', f'{match[1]} value {value} exceeds {expected} [{low}, {high}].', 'strict-types', 'error', line)
|
|
153
|
+
call_counts = Counter()
|
|
154
|
+
for instance, args, line in calls(body):
|
|
155
|
+
variable = variables.get(instance.upper())
|
|
156
|
+
if not variable: continue
|
|
157
|
+
type_name = variable.type_name
|
|
158
|
+
# An explicit unresolved installed interface must not silently fall back
|
|
159
|
+
# to a historical signature for a different library revision.
|
|
160
|
+
key = type_name.upper()
|
|
161
|
+
spec = signatures[key] if signatures is not None and key in signatures else signature(type_name)
|
|
162
|
+
if spec is None:
|
|
163
|
+
unresolved.add(type_name); continue
|
|
164
|
+
call_counts[instance.upper()] += 1
|
|
165
|
+
local = spec.get('authority') in ('project_declaration', 'bound_installed_declaration', 'fresh_bound_compiled_contract')
|
|
166
|
+
severity = 'error' if local else 'warning'
|
|
167
|
+
evidence = spec.get('citation') or reference('execute')
|
|
168
|
+
pins = {n.upper(): (direction, norm(t)) for direction in ('inputs', 'outputs', 'inouts') for n, t in spec[direction].items()}
|
|
169
|
+
seen = set()
|
|
170
|
+
for name, operator, expr in args:
|
|
171
|
+
pin = name.upper()
|
|
172
|
+
if pin in seen: add('duplicate-fb-parameter', f'{instance}: parameter {name} is supplied twice.', 'execute', 'error', line, evidence)
|
|
173
|
+
seen.add(pin)
|
|
174
|
+
if pin not in pins:
|
|
175
|
+
if spec.get('complete'): add('unknown-fb-parameter', f'{instance} ({type_name}) has no {name} pin in the referenced interface.', 'execute', severity, line, evidence)
|
|
176
|
+
continue
|
|
177
|
+
direction, expected = pins[pin]
|
|
178
|
+
unsupported = {n.upper(): note for n, note in spec.get('unsupported_pins', {}).items()}
|
|
179
|
+
if pin in unsupported:
|
|
180
|
+
add('historical-unsupported-pin', f'{instance}.{name}: {unsupported[pin]} Confirm installed library support.', 'execute', 'warning', line, evidence)
|
|
181
|
+
if (direction == 'outputs' and operator == ':=') or (direction != 'outputs' and operator == '=>'):
|
|
182
|
+
add('fb-parameter-direction', f'{instance}.{name} is {direction}; review {operator} binding.', 'execute', severity, line, evidence)
|
|
183
|
+
actual = expression_type(expr, types)
|
|
184
|
+
if actual and actual != expected and not expected.startswith('ANY'):
|
|
185
|
+
add('fb-parameter-type', f'{instance}.{name}: expected {expected}, found {actual}.', 'strict-types', severity, line, evidence)
|
|
186
|
+
if direction in ('inouts', 'outputs'):
|
|
187
|
+
if constant_expression(expr):
|
|
188
|
+
code = 'fb-inout-needs-variable' if direction == 'inouts' else 'fb-output-needs-variable'
|
|
189
|
+
add(code, f'{instance}.{name} needs writable variable storage, not a constant.', 'execute', severity, line, evidence)
|
|
190
|
+
elif not (re.fullmatch(IDENT, expr) and expr.upper() in variables):
|
|
191
|
+
add('fb-writable-binding-unresolved', f'{instance}.{name}: writable storage for {expr} is not resolved; inspect member/index declarations and compiler diagnostics.', 'execute', 'warning', line, evidence)
|
|
192
|
+
for name in spec['inouts']:
|
|
193
|
+
if name.upper() not in seen:
|
|
194
|
+
add('fb-inout-binding-review', f'{instance}.{name} is not bound in this call; confirm its existing binding.', 'execute', line=line, evidence=evidence)
|
|
195
|
+
if any(n.upper() == 'EXECUTE' for n in spec['inputs']):
|
|
196
|
+
if any(n.upper() == 'EXECUTE' and e.upper() == 'TRUE' for n, _, e in args):
|
|
197
|
+
add('execute-held-true', f'{instance}: constant TRUE provides no recurring rising edge; verify intentional one-shot operation and rearming.', 'execute', line=line)
|
|
198
|
+
# Explicit outputs mapped with => count as handled, as do member accesses.
|
|
199
|
+
if any(n.upper() == 'ERROR' for n in spec['outputs']):
|
|
200
|
+
handles_error = any(n.upper() == 'ERROR' and op == '=>' for n, op, _ in args) or bool(re.search(r'\b'+re.escape(instance)+r'\s*\.\s*Error\b', clean, re.I))
|
|
201
|
+
if not handles_error: add('fb-error-not-observed', f'{instance}: no Error output binding or member reference was found.', 'fb-results', line=line)
|
|
202
|
+
for instance, count in call_counts.items():
|
|
203
|
+
if count > 1: add('multiple-fb-call-sites', f'{instance} has {count} call sites; check mutually exclusive branches and scan order.', 'execute')
|
|
204
|
+
return {'findings': findings, 'unresolved_signatures': sorted(unresolved),
|
|
205
|
+
'global_scope': 'unresolved' if globals_table is None or globals_table.warnings else 'reviewed',
|
|
206
|
+
'coverage': 'Direct ST assignments and resolved named FB calls; not a full IEC compiler or control-flow proof.'}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def task_review(programs, assignments, *, exact_bindings=False):
|
|
210
|
+
findings = []
|
|
211
|
+
assigned = {p.casefold() for entries in assignments.values() for p in entries}
|
|
212
|
+
for program in programs:
|
|
213
|
+
if program.casefold() not in assigned:
|
|
214
|
+
findings.append({'code': 'program-task-binding-review', 'severity': 'warning',
|
|
215
|
+
'message': (f'{program} has no direct PROGRAM instance in the saved task tree; inspect indirect calls and current IDE state.' if exact_bindings else f'{program} has no matching task entry; confirm program type versus instance name and indirect calls.'),
|
|
216
|
+
'reference': reference('task-order'), 'binding_resolution': 'exact' if exact_bindings else 'instance_names_only'})
|
|
217
|
+
return findings
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def interface_signature(interface):
|
|
221
|
+
"""Keep the exact bound interface and source hashes in review citations."""
|
|
222
|
+
if interface.get('kind') != 'FUNCTION_BLOCK' or interface.get('hidden') or interface.get('evidence_kind') != 'installed-declared-block-interface' or interface.get('insertion_eligible') is False:
|
|
223
|
+
raise ValueError('Requires a visible declared function-block interface')
|
|
224
|
+
spec = {key: {} for key in ('inputs', 'outputs', 'inouts')}
|
|
225
|
+
directions = {'input': 'inputs', 'output': 'outputs', 'in_out': 'inouts'}
|
|
226
|
+
seen = set()
|
|
227
|
+
for pin in interface['pins']:
|
|
228
|
+
key = pin['name'].upper()
|
|
229
|
+
if key in seen or pin['direction'] not in directions or not pin['type']:
|
|
230
|
+
raise ValueError('Ambiguous interface pin')
|
|
231
|
+
seen.add(key); spec[directions[pin['direction']]][pin['name']] = pin['type']
|
|
232
|
+
spec.update(complete=True, authority='bound_installed_declaration', citation={
|
|
233
|
+
key: interface[key] for key in ('library', 'binding', 'origin', 'source_file', 'source_stream',
|
|
234
|
+
'source_sha256', 'reference_registry', 'registry_sha256')})
|
|
235
|
+
return spec
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def compiled_interface_signature(interface, current):
|
|
239
|
+
"""Accept only the freshly verified native contract matching current files."""
|
|
240
|
+
if interface.get('evidence_kind') != 'fresh-bound-compiled-block-interface':
|
|
241
|
+
raise ValueError('Fresh native compiled contract required')
|
|
242
|
+
for key in ('project_compiler_freshness_verified', 'compiler_library_binding_verified',
|
|
243
|
+
'compiler_pin_types_verified', 'compile_acceptance_only', 'insertion_eligible'):
|
|
244
|
+
if interface.get(key) is not True:
|
|
245
|
+
raise ValueError('Incomplete compiled contract: ' + key)
|
|
246
|
+
if interface.get('compiler_source_binding_verified') is not False or interface.get('compiler_cache_freshness_verified') is not False:
|
|
247
|
+
raise ValueError('Protected-source/cache freshness is not proved')
|
|
248
|
+
age = time.time() * 1000 - interface.get('verified_at_ms', 0)
|
|
249
|
+
if age < -2000 or age > 300000:
|
|
250
|
+
raise ValueError('Expired native compiled contract')
|
|
251
|
+
for key in ('source_baseline_digest', 'library_manifest_digest'):
|
|
252
|
+
if not re.fullmatch(r'[a-f0-9]{64}', str(interface.get(key, ''))):
|
|
253
|
+
raise ValueError('Missing compiled provenance: ' + key)
|
|
254
|
+
if current.get('evidence_kind') != 'installed-compiled-block-interface':
|
|
255
|
+
raise ValueError('Current diagnostic interface required')
|
|
256
|
+
for key in ('name', 'library', 'kind', 'hidden', 'pins', 'source_file', 'source_sha256',
|
|
257
|
+
'reference_registry', 'registry_sha256', 'worksheet_file', 'worksheet_sha256',
|
|
258
|
+
'cache_file', 'cache_sha256', 'compiler_dependency', 'compiler_dependency_sha256',
|
|
259
|
+
'compiler_type_table', 'compiler_type_table_sha256', 'source_declaration_count',
|
|
260
|
+
'compiler_declaration_count'):
|
|
261
|
+
if key not in current or interface.get(key) != current[key]:
|
|
262
|
+
raise ValueError('Compiled contract changed: ' + key)
|
|
263
|
+
# Reuse pin direction/type validation without claiming source declarations.
|
|
264
|
+
spec = interface_signature({**current, 'evidence_kind': 'installed-declared-block-interface',
|
|
265
|
+
'insertion_eligible': True})
|
|
266
|
+
spec['authority'] = 'fresh_bound_compiled_contract'
|
|
267
|
+
spec['citation'] = {key: interface[key] for key in (
|
|
268
|
+
'library', 'compiler_dependency', 'compiler_dependency_sha256',
|
|
269
|
+
'compiler_type_table', 'compiler_type_table_sha256', 'verified_at_ms',
|
|
270
|
+
'source_baseline_digest', 'library_manifest_digest')}
|
|
271
|
+
spec['citation'].update(compile_acceptance_only=True, protected_source_decoded=False,
|
|
272
|
+
compiler_cache_freshness_verified=False)
|
|
273
|
+
return spec
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def check_project(root, *, pou=None, body=None, native_libraries=None, interface_libraries=None,
|
|
277
|
+
native_compiled_interfaces=None):
|
|
278
|
+
from .project import Project
|
|
279
|
+
root = Path(root)
|
|
280
|
+
if root.suffix.lower() == '.mwt': root = root.with_suffix('')
|
|
281
|
+
project = Project(root=root)
|
|
282
|
+
selected = {}
|
|
283
|
+
for name, library in (interface_libraries or {}).items():
|
|
284
|
+
if not re.fullmatch(IDENT, name) or not isinstance(library, str) or not re.fullmatch(IDENT, library) or name.upper() in selected:
|
|
285
|
+
raise ValueError('Invalid or duplicate interface library selector')
|
|
286
|
+
selected[name.upper()] = library
|
|
287
|
+
if selected and native_libraries is None:
|
|
288
|
+
raise ValueError('Interface library selection requires bound installed review')
|
|
289
|
+
compiled = {}
|
|
290
|
+
for interface in native_compiled_interfaces or []:
|
|
291
|
+
name = interface.get('name', '')
|
|
292
|
+
if not re.fullmatch(IDENT, name) or name.upper() in compiled or selected.get(name.upper()) != interface.get('library') or native_libraries is None:
|
|
293
|
+
raise ValueError('Exact unique bound compiled interface selection required')
|
|
294
|
+
compiled[name.upper()] = interface
|
|
295
|
+
findings, coverage = [], []
|
|
296
|
+
globals_text = None
|
|
297
|
+
try:
|
|
298
|
+
resources = [p for p in root.glob('C/**/src.st1') if p.parent.name.casefold() == 'resource']
|
|
299
|
+
if len(resources) > 1:
|
|
300
|
+
raise ValueError('Multiple resources: POU-to-resource scope cannot yet be resolved')
|
|
301
|
+
table = project.global_variables()
|
|
302
|
+
if not table.warnings:
|
|
303
|
+
globals_text = '\n'.join(f'VAR_GLOBAL\n{v.name}:{v.type_name};\nEND_VAR' for v in table.variables)
|
|
304
|
+
else: coverage.append({'globals': 'unresolved', 'warnings': table.warnings})
|
|
305
|
+
except Exception as exc: coverage.append({'globals': 'unresolved', 'error': str(exc)})
|
|
306
|
+
signatures = {}
|
|
307
|
+
for item in project.pous():
|
|
308
|
+
try:
|
|
309
|
+
table = item.declarations()
|
|
310
|
+
pins = {section: {v.name: v.type_name for v in table.variables if v.section == section}
|
|
311
|
+
for section in ('VAR_INPUT', 'VAR_OUTPUT', 'VAR_IN_OUT')}
|
|
312
|
+
if not table.warnings and any(pins.values()):
|
|
313
|
+
signatures[item.name.upper()] = {'inputs': pins['VAR_INPUT'], 'outputs': pins['VAR_OUTPUT'], 'inouts': pins['VAR_IN_OUT'],
|
|
314
|
+
'complete': True, 'authority': 'project_declaration', 'citation': {'path': str(item.source_path), 'section': 'native POU declaration', 'version_match': 'project_source'}}
|
|
315
|
+
except Exception: continue
|
|
316
|
+
interface_coverage = {}
|
|
317
|
+
for item in ([project.pou(pou)] if pou else project.pous()):
|
|
318
|
+
try:
|
|
319
|
+
if item.language() == 'IL':
|
|
320
|
+
coverage.append({'pou': item.name, 'language': 'IL', 'status': 'IL_static_review_not_supported'})
|
|
321
|
+
continue
|
|
322
|
+
text = body if body is not None and pou else item.st_body()
|
|
323
|
+
if text is None:
|
|
324
|
+
coverage.append({'pou': item.name, 'status': 'graphical_body_not_analyzed'}); continue
|
|
325
|
+
cfb = item.source()
|
|
326
|
+
declaration = next(n for n in cfb.stream_names() if n.upper().endswith('.VB'))
|
|
327
|
+
if native_libraries is not None:
|
|
328
|
+
from .block_interfaces import inspect
|
|
329
|
+
variables = {v.name.upper(): v for v in item.declarations().variables}
|
|
330
|
+
for instance, _, _ in calls(text):
|
|
331
|
+
variable = variables.get(instance.upper())
|
|
332
|
+
if not variable or variable.type_name.upper() in signatures: continue
|
|
333
|
+
type_name = variable.type_name
|
|
334
|
+
try:
|
|
335
|
+
current = inspect(root, native_libraries, type_name, selected.get(type_name.upper()))
|
|
336
|
+
evidence = compiled.get(type_name.upper())
|
|
337
|
+
spec = compiled_interface_signature(evidence, current) if evidence else interface_signature(current)
|
|
338
|
+
signatures[type_name.upper()] = spec
|
|
339
|
+
interface_coverage[type_name.upper()] = {'type': type_name, 'status': 'fresh_bound_compiled_contract' if evidence else 'bound_installed_interface', 'citation': spec['citation']}
|
|
340
|
+
except Exception as exc:
|
|
341
|
+
signatures[type_name.upper()] = None
|
|
342
|
+
interface_coverage[type_name.upper()] = {'type': type_name, 'status': 'unresolved', 'error': str(exc)}
|
|
343
|
+
report = review(text, cfb.read_stream(declaration).decode('latin1'), globals_text, signatures=signatures)
|
|
344
|
+
findings.extend({**f, 'pou': item.name} for f in report['findings'])
|
|
345
|
+
coverage.append({'pou': item.name, 'status': 'ST_reviewed', 'unresolved_signatures': report['unresolved_signatures']})
|
|
346
|
+
except Exception as exc: coverage.append({'pou': item.name, 'status': 'unresolved', 'error': str(exc)})
|
|
347
|
+
try:
|
|
348
|
+
from .task_bindings import inspect
|
|
349
|
+
task_bindings = inspect(root)
|
|
350
|
+
assignments = {task['path']: [x['type'] for x in task['instances']] for task in task_bindings['bindings']}
|
|
351
|
+
findings.extend(task_review(task_bindings['program_types'], assignments, exact_bindings=True))
|
|
352
|
+
coverage.append({'tasks': 'exact_saved_tree', 'bindings': task_bindings['bindings'], 'source_hashes': task_bindings['source_hashes']})
|
|
353
|
+
except Exception as exc: coverage.append({'tasks': 'unresolved', 'error': str(exc)})
|
|
354
|
+
return {'project': str(root), 'findings': findings, 'coverage': coverage,
|
|
355
|
+
'interface_resolution': 'bound_compiled_requested' if compiled else 'bound_installed_requested' if native_libraries is not None else 'project_and_historical',
|
|
356
|
+
'installed_interfaces': list(interface_coverage.values()),
|
|
357
|
+
'errors': sum(f['severity'] == 'error' for f in findings),
|
|
358
|
+
'warnings': sum(f['severity'] == 'warning' for f in findings),
|
|
359
|
+
'verification': 'static_review_only', 'automatic_changes': False,
|
|
360
|
+
'limitations': ['No arbitrary LD/FBD analysis', 'No IL static syntax/semantic review; require native compilation', 'No complete IEC type inference', 'Saved task ownership is not runtime execution proof', 'Historical vendor signatures require installed-version confirmation']}
|