@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.
Files changed (281) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -2
  4. package/cordis.patch.yml +122 -0
  5. package/locale/en.json +6 -0
  6. package/locale/zh.json +6 -0
  7. package/package.json +77 -3
  8. package/vendor/ftview/README.md +55 -0
  9. package/vendor/ftview/SKILL.md +74 -0
  10. package/vendor/ftview/lib/checks.d.ts +27 -0
  11. package/vendor/ftview/lib/checks.js +39 -0
  12. package/vendor/ftview/lib/core.d.ts +208 -0
  13. package/vendor/ftview/lib/core.js +160 -0
  14. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  15. package/vendor/ftview/lib/edit/drafts.js +84 -0
  16. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  17. package/vendor/ftview/lib/edit/operations.js +126 -0
  18. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  19. package/vendor/ftview/lib/edit/prepare.js +133 -0
  20. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  21. package/vendor/ftview/lib/edit/tools.js +198 -0
  22. package/vendor/ftview/lib/edit.d.ts +30 -0
  23. package/vendor/ftview/lib/edit.js +38 -0
  24. package/vendor/ftview/lib/index.d.ts +31 -0
  25. package/vendor/ftview/lib/index.js +36 -0
  26. package/vendor/ftview/lib/model.d.ts +80 -0
  27. package/vendor/ftview/lib/model.js +267 -0
  28. package/vendor/ftview/lib/session.d.ts +48 -0
  29. package/vendor/ftview/lib/session.js +98 -0
  30. package/vendor/ftview/lib/skill.d.ts +29 -0
  31. package/vendor/ftview/lib/skill.js +43 -0
  32. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  33. package/vendor/ftview/lib/studio/discover.js +87 -0
  34. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  35. package/vendor/ftview/lib/studio/preflight.js +64 -0
  36. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  37. package/vendor/ftview/lib/studio/tools.js +83 -0
  38. package/vendor/ftview/lib/studio.d.ts +30 -0
  39. package/vendor/ftview/lib/studio.js +37 -0
  40. package/vendor/ftview/lib/tools.d.ts +14 -0
  41. package/vendor/ftview/lib/tools.js +202 -0
  42. package/vendor/l5x/README.md +62 -0
  43. package/vendor/l5x/SKILL.md +81 -0
  44. package/vendor/l5x/lib/approvals.d.ts +50 -0
  45. package/vendor/l5x/lib/approvals.js +93 -0
  46. package/vendor/l5x/lib/client.js +412 -0
  47. package/vendor/l5x/lib/client.js.map +1 -0
  48. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  49. package/vendor/l5x/lib/core/errors.js +48 -0
  50. package/vendor/l5x/lib/core/params.d.ts +49 -0
  51. package/vendor/l5x/lib/core/params.js +39 -0
  52. package/vendor/l5x/lib/core/result.d.ts +46 -0
  53. package/vendor/l5x/lib/core/result.js +21 -0
  54. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  55. package/vendor/l5x/lib/core/tool.js +19 -0
  56. package/vendor/l5x/lib/drafts.d.ts +66 -0
  57. package/vendor/l5x/lib/drafts.js +98 -0
  58. package/vendor/l5x/lib/editing.d.ts +30 -0
  59. package/vendor/l5x/lib/editing.js +42 -0
  60. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  61. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  62. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  63. package/vendor/l5x/lib/engine/analysis.js +258 -0
  64. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  65. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  66. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  67. package/vendor/l5x/lib/engine/change-set.js +446 -0
  68. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  69. package/vendor/l5x/lib/engine/configuration.js +42 -0
  70. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  71. package/vendor/l5x/lib/engine/details.js +173 -0
  72. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  73. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  74. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  75. package/vendor/l5x/lib/engine/download-review.js +63 -0
  76. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  77. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  78. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  79. package/vendor/l5x/lib/engine/observations.js +67 -0
  80. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  81. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  82. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  83. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  84. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  85. package/vendor/l5x/lib/engine/review.js +123 -0
  86. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  87. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  88. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  89. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  90. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  91. package/vendor/l5x/lib/engine/usage.js +92 -0
  92. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  93. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  94. package/vendor/l5x/lib/fs.d.ts +17 -0
  95. package/vendor/l5x/lib/fs.js +36 -0
  96. package/vendor/l5x/lib/index.d.ts +36 -0
  97. package/vendor/l5x/lib/index.js +42 -0
  98. package/vendor/l5x/lib/live.d.ts +48 -0
  99. package/vendor/l5x/lib/live.js +48 -0
  100. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  101. package/vendor/l5x/lib/model/aois.js +112 -0
  102. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  103. package/vendor/l5x/lib/model/errors.js +18 -0
  104. package/vendor/l5x/lib/model/index.d.ts +20 -0
  105. package/vendor/l5x/lib/model/index.js +19 -0
  106. package/vendor/l5x/lib/model/module.d.ts +37 -0
  107. package/vendor/l5x/lib/model/module.js +54 -0
  108. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  109. package/vendor/l5x/lib/model/modules.js +55 -0
  110. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  111. package/vendor/l5x/lib/model/parse.js +116 -0
  112. package/vendor/l5x/lib/model/project.d.ts +15 -0
  113. package/vendor/l5x/lib/model/project.js +31 -0
  114. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  115. package/vendor/l5x/lib/model/routines.js +183 -0
  116. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  117. package/vendor/l5x/lib/model/tags.js +119 -0
  118. package/vendor/l5x/lib/model/types.d.ts +304 -0
  119. package/vendor/l5x/lib/model/types.js +12 -0
  120. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  121. package/vendor/l5x/lib/model/udts.js +81 -0
  122. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  123. package/vendor/l5x/lib/model/xml.js +291 -0
  124. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  125. package/vendor/l5x/lib/model/xref.js +154 -0
  126. package/vendor/l5x/lib/session.d.ts +119 -0
  127. package/vendor/l5x/lib/session.js +236 -0
  128. package/vendor/l5x/lib/skill.d.ts +30 -0
  129. package/vendor/l5x/lib/skill.js +44 -0
  130. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  131. package/vendor/l5x/lib/studio-state.js +79 -0
  132. package/vendor/l5x/lib/studio.d.ts +29 -0
  133. package/vendor/l5x/lib/studio.js +40 -0
  134. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  135. package/vendor/l5x/lib/tools/checks.js +140 -0
  136. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  137. package/vendor/l5x/lib/tools/common.js +84 -0
  138. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  139. package/vendor/l5x/lib/tools/controller.js +170 -0
  140. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  141. package/vendor/l5x/lib/tools/drafts.js +270 -0
  142. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  143. package/vendor/l5x/lib/tools/live.js +175 -0
  144. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  145. package/vendor/l5x/lib/tools/read.js +245 -0
  146. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  147. package/vendor/l5x/lib/tools/references.js +269 -0
  148. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  149. package/vendor/l5x/lib/tools/studio.js +193 -0
  150. package/vendor/l5x/poll-worker.mjs +77 -0
  151. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  152. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  153. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  154. package/vendor/mwi/README.md +60 -0
  155. package/vendor/mwi/SKILL.md +87 -0
  156. package/vendor/mwi/bridge/README.md +8 -0
  157. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  158. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  159. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  160. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  161. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  162. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  163. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  164. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  165. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  229. package/vendor/mwi/code/mw_code.py +1258 -0
  230. package/vendor/mwi/legacy/edit-session.js +50 -0
  231. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  232. package/vendor/mwi/legacy/native-code.js +83 -0
  233. package/vendor/mwi/legacy/native-groups.js +75 -0
  234. package/vendor/mwi/legacy/native-structure.js +143 -0
  235. package/vendor/mwi/legacy/native-variables.js +77 -0
  236. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  237. package/vendor/mwi/legacy/pou-package.js +66 -0
  238. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  239. package/vendor/mwi/lib/core/bridge.js +53 -0
  240. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  241. package/vendor/mwi/lib/core/code-engine.js +109 -0
  242. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  243. package/vendor/mwi/lib/core/errors.js +47 -0
  244. package/vendor/mwi/lib/core/params.d.ts +52 -0
  245. package/vendor/mwi/lib/core/params.js +46 -0
  246. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  247. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  248. package/vendor/mwi/lib/core/result.d.ts +45 -0
  249. package/vendor/mwi/lib/core/result.js +20 -0
  250. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  251. package/vendor/mwi/lib/core/tool.js +55 -0
  252. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  253. package/vendor/mwi/lib/edit/compare.js +70 -0
  254. package/vendor/mwi/lib/edit.d.ts +20 -0
  255. package/vendor/mwi/lib/edit.js +32 -0
  256. package/vendor/mwi/lib/index.d.ts +21 -0
  257. package/vendor/mwi/lib/index.js +33 -0
  258. package/vendor/mwi/lib/read/service.d.ts +117 -0
  259. package/vendor/mwi/lib/read/service.js +229 -0
  260. package/vendor/mwi/lib/read.d.ts +21 -0
  261. package/vendor/mwi/lib/read.js +33 -0
  262. package/vendor/mwi/lib/session/project.d.ts +34 -0
  263. package/vendor/mwi/lib/session/project.js +54 -0
  264. package/vendor/mwi/lib/session/service.d.ts +167 -0
  265. package/vendor/mwi/lib/session/service.js +566 -0
  266. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  267. package/vendor/mwi/lib/session/stage.js +187 -0
  268. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  269. package/vendor/mwi/lib/session/workspace.js +90 -0
  270. package/vendor/mwi/lib/skill.d.ts +36 -0
  271. package/vendor/mwi/lib/skill.js +56 -0
  272. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  273. package/vendor/mwi/lib/tools/edit.js +286 -0
  274. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  275. package/vendor/mwi/lib/tools/read.js +161 -0
  276. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  277. package/vendor/mwi/lib/tools/session.js +166 -0
  278. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  279. package/vendor/mwi/lib/tools/verify.js +85 -0
  280. package/vendor/mwi/lib/verify.d.ts +17 -0
  281. 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']}