laddercodec 0.1.8__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (569) hide show
  1. laddercodec-0.2.0/CHANGELOG.md +134 -0
  2. {laddercodec-0.1.8 → laddercodec-0.2.0}/CLAUDE.md +6 -3
  3. {laddercodec-0.1.8 → laddercodec-0.2.0}/PKG-INFO +1 -1
  4. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/compare_csv.py +13 -2
  5. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/dump_scr_tags.py +7 -13
  6. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/guides/csv-format.md +7 -4
  7. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/internals/binary-format.md +40 -10
  8. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/internals/wire-rendering.md +1 -1
  9. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/cell.py +24 -27
  10. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/contract.py +12 -1
  11. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/parser.py +5 -3
  12. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/writer.py +51 -10
  13. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/decode.py +23 -0
  14. laddercodec-0.2.0/src/laddercodec/decode_program.py +911 -0
  15. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/drum.py +35 -7
  16. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/math.py +15 -6
  17. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_contract.py +31 -0
  18. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_converter.py +22 -1
  19. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_parser.py +36 -0
  20. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_writer.py +98 -0
  21. laddercodec-0.2.0/tests/fixtures/ladder_captures/drum_weird_branch.bin +0 -0
  22. laddercodec-0.2.0/tests/fixtures/scr_captures/time_drums.bin +0 -0
  23. laddercodec-0.2.0/tests/fixtures/scr_captures/time_drums.csv +42 -0
  24. laddercodec-0.2.0/tests/fixtures/scr_captures/time_drums.scr +0 -0
  25. laddercodec-0.2.0/tests/fixtures/tumbler/main.clipboard.bin +0 -0
  26. laddercodec-0.2.0/tests/fixtures/tumbler/main.clipboard.csv +217 -0
  27. laddercodec-0.2.0/tests/fixtures/tumbler/main.csv +217 -0
  28. laddercodec-0.2.0/tests/fixtures/tumbler/main.scr +0 -0
  29. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAlarm.clipboard.bin +0 -0
  30. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAlarm.clipboard.csv +5 -0
  31. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAlarm.csv +5 -0
  32. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAlarm.scr +0 -0
  33. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAndClearAllAlarms.clipboard.bin +0 -0
  34. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAndClearAllAlarms.clipboard.csv +8 -0
  35. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAndClearAllAlarms.csv +8 -0
  36. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckAndClearAllAlarms.scr +0 -0
  37. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckWarning.clipboard.bin +0 -0
  38. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckWarning.clipboard.csv +5 -0
  39. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckWarning.csv +5 -0
  40. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AckWarning.scr +0 -0
  41. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AlmHistorian.clipboard.bin +0 -0
  42. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AlmHistorian.clipboard.csv +50 -0
  43. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AlmHistorian.csv +50 -0
  44. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/AlmHistorian.scr +0 -0
  45. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Blower.clipboard.bin +0 -0
  46. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Blower.clipboard.csv +86 -0
  47. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Blower.csv +86 -0
  48. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Blower.scr +0 -0
  49. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/DisableOutputs.clipboard.bin +0 -0
  50. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/DisableOutputs.clipboard.csv +6 -0
  51. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/DisableOutputs.csv +6 -0
  52. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/DisableOutputs.scr +0 -0
  53. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Heat.clipboard.bin +0 -0
  54. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Heat.clipboard.csv +100 -0
  55. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Heat.csv +100 -0
  56. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Heat.scr +0 -0
  57. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Init.clipboard.bin +0 -0
  58. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Init.clipboard.csv +19 -0
  59. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Init.csv +19 -0
  60. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Init.scr +0 -0
  61. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Maint.clipboard.bin +0 -0
  62. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Maint.clipboard.csv +14 -0
  63. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Maint.csv +14 -0
  64. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Maint.scr +0 -0
  65. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Manual.clipboard.bin +0 -0
  66. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Manual.clipboard.csv +11 -0
  67. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Manual.csv +11 -0
  68. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Manual.scr +0 -0
  69. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ModeChange.clipboard.bin +0 -0
  70. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ModeChange.clipboard.csv +17 -0
  71. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ModeChange.csv +17 -0
  72. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ModeChange.scr +0 -0
  73. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCDateTime.clipboard.bin +0 -0
  74. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCDateTime.clipboard.csv +26 -0
  75. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCDateTime.csv +26 -0
  76. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCDateTime.scr +0 -0
  77. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCErrors.clipboard.bin +0 -0
  78. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCErrors.clipboard.csv +15 -0
  79. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCErrors.csv +15 -0
  80. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/PLCErrors.scr +0 -0
  81. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionBoilerplate.clipboard.bin +0 -0
  82. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionBoilerplate.clipboard.csv +11 -0
  83. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionBoilerplate.csv +11 -0
  84. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionBoilerplate.scr +0 -0
  85. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionErrors.clipboard.bin +0 -0
  86. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionErrors.clipboard.csv +45 -0
  87. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionErrors.csv +45 -0
  88. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionErrors.scr +0 -0
  89. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionExecuteSteps.clipboard.bin +0 -0
  90. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionExecuteSteps.clipboard.csv +48 -0
  91. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionExecuteSteps.csv +48 -0
  92. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionExecuteSteps.scr +0 -0
  93. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionHMI.clipboard.bin +0 -0
  94. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionHMI.clipboard.csv +15 -0
  95. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionHMI.csv +15 -0
  96. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionHMI.scr +0 -0
  97. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionStates.clipboard.bin +0 -0
  98. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionStates.clipboard.csv +39 -0
  99. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionStates.csv +39 -0
  100. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ProductionStates.scr +0 -0
  101. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ReadInputs.clipboard.bin +0 -0
  102. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ReadInputs.clipboard.csv +8 -0
  103. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ReadInputs.csv +8 -0
  104. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ReadInputs.scr +0 -0
  105. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/RecipeChange.clipboard.bin +0 -0
  106. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/RecipeChange.clipboard.csv +14 -0
  107. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/RecipeChange.csv +14 -0
  108. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/RecipeChange.scr +0 -0
  109. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Rotate.clipboard.bin +0 -0
  110. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Rotate.clipboard.csv +95 -0
  111. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Rotate.csv +95 -0
  112. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Rotate.scr +0 -0
  113. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SFCExample.clipboard.bin +0 -0
  114. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SFCExample.clipboard.csv +144 -0
  115. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SFCExample.csv +144 -0
  116. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SFCExample.scr +0 -0
  117. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputs.clipboard.bin +0 -0
  118. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputs.clipboard.csv +8 -0
  119. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputs.csv +8 -0
  120. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputs.scr +0 -0
  121. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputsManual.clipboard.bin +0 -0
  122. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputsManual.clipboard.csv +6 -0
  123. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputsManual.csv +6 -0
  124. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/SetOutputsManual.scr +0 -0
  125. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ThermErrors.clipboard.bin +0 -0
  126. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ThermErrors.clipboard.csv +7 -0
  127. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ThermErrors.csv +7 -0
  128. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/ThermErrors.scr +0 -0
  129. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Validation.clipboard.bin +0 -0
  130. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Validation.clipboard.csv +11 -0
  131. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Validation.csv +11 -0
  132. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/Validation.scr +0 -0
  133. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/WarnTimestamp.clipboard.bin +0 -0
  134. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/WarnTimestamp.clipboard.csv +28 -0
  135. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/WarnTimestamp.csv +28 -0
  136. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/WarnTimestamp.scr +0 -0
  137. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CopyOrJumpState.clipboard.bin +0 -0
  138. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CopyOrJumpState.clipboard.csv +34 -0
  139. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CopyOrJumpState.csv +34 -0
  140. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CopyOrJumpState.scr +0 -0
  141. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CtrlCmd2StateRequest.clipboard.bin +0 -0
  142. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CtrlCmd2StateRequest.clipboard.csv +72 -0
  143. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CtrlCmd2StateRequest.csv +72 -0
  144. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_CtrlCmd2StateRequest.scr +0 -0
  145. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_IsCmdValid.clipboard.bin +0 -0
  146. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_IsCmdValid.clipboard.csv +29 -0
  147. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_IsCmdValid.csv +29 -0
  148. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_IsCmdValid.scr +0 -0
  149. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapCmd2Val.clipboard.bin +0 -0
  150. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapCmd2Val.clipboard.csv +35 -0
  151. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapCmd2Val.csv +35 -0
  152. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapCmd2Val.scr +0 -0
  153. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapVal2State.clipboard.bin +0 -0
  154. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapVal2State.clipboard.csv +19 -0
  155. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapVal2State.csv +19 -0
  156. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_MapVal2State.scr +0 -0
  157. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_StateComplete2Request.clipboard.bin +0 -0
  158. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_StateComplete2Request.clipboard.csv +14 -0
  159. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_StateComplete2Request.csv +14 -0
  160. laddercodec-0.2.0/tests/fixtures/tumbler/subroutines/sm_StateComplete2Request.scr +0 -0
  161. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_decode.py +73 -0
  162. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_decode_program.py +196 -296
  163. laddercodec-0.2.0/tests/ladder/test_decode_program_tumbler.py +33 -0
  164. laddercodec-0.1.8/CHANGELOG.md +0 -96
  165. laddercodec-0.1.8/src/laddercodec/decode_program.py +0 -1154
  166. {laddercodec-0.1.8 → laddercodec-0.2.0}/.claude/settings.local.json +0 -0
  167. {laddercodec-0.1.8 → laddercodec-0.2.0}/.github/workflows/ci.yml +0 -0
  168. {laddercodec-0.1.8 → laddercodec-0.2.0}/.github/workflows/docs.yml +0 -0
  169. {laddercodec-0.1.8 → laddercodec-0.2.0}/.github/workflows/publish.yml +0 -0
  170. {laddercodec-0.1.8 → laddercodec-0.2.0}/.gitignore +0 -0
  171. {laddercodec-0.1.8 → laddercodec-0.2.0}/LICENSE +0 -0
  172. {laddercodec-0.1.8 → laddercodec-0.2.0}/Makefile +0 -0
  173. {laddercodec-0.1.8 → laddercodec-0.2.0}/README.md +0 -0
  174. {laddercodec-0.1.8 → laddercodec-0.2.0}/add-features-workflow.md +0 -0
  175. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/analyze_scr.py +0 -0
  176. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/combine_coverage.py +0 -0
  177. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/compare_blobs.py +0 -0
  178. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/coverage_golden.py +0 -0
  179. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/create_mr_max_csv.py +0 -0
  180. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/diag_blob.py +0 -0
  181. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/gen_modbus_coverage.py +0 -0
  182. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/golden.py +0 -0
  183. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/inspect_bin.py +0 -0
  184. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/inspect_scr.py +0 -0
  185. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/lint.py +0 -0
  186. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/pdf_to_png.py +0 -0
  187. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/test_decode_scr.py +0 -0
  188. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/validate_scr.py +0 -0
  189. {laddercodec-0.1.8 → laddercodec-0.2.0}/devtools/walk_scr.py +0 -0
  190. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/CLAUDE.md +0 -0
  191. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/gen_reference.py +0 -0
  192. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/guides/adding-instructions.md +0 -0
  193. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/guides/decoding.md +0 -0
  194. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/guides/encoding.md +0 -0
  195. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/guides/troubleshooting.md +0 -0
  196. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/index.md +0 -0
  197. {laddercodec-0.1.8 → laddercodec-0.2.0}/docs/internals/instruction-blobs.md +0 -0
  198. {laddercodec-0.1.8 → laddercodec-0.2.0}/mkdocs.yml +0 -0
  199. {laddercodec-0.1.8 → laddercodec-0.2.0}/pyproject.toml +0 -0
  200. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/__init__.py +0 -0
  201. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/_grid.py +0 -0
  202. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/binary_helpers.py +0 -0
  203. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/__init__.py +0 -0
  204. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/ast.py +0 -0
  205. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/bundle.py +0 -0
  206. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/converter.py +0 -0
  207. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/csv/token_parser.py +0 -0
  208. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/empty_multirow.py +0 -0
  209. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/encode.py +0 -0
  210. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/encode_multi.py +0 -0
  211. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/__init__.py +0 -0
  212. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/call.py +0 -0
  213. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/coil.py +0 -0
  214. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/comparison.py +0 -0
  215. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/contact.py +0 -0
  216. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/copy.py +0 -0
  217. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/counter.py +0 -0
  218. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/email.py +0 -0
  219. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/end.py +0 -0
  220. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/family.py +0 -0
  221. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/forloop.py +0 -0
  222. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/home.py +0 -0
  223. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/position.py +0 -0
  224. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/raw.py +0 -0
  225. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/return_.py +0 -0
  226. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/search.py +0 -0
  227. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/send_receive.py +0 -0
  228. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/shift.py +0 -0
  229. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/timer.py +0 -0
  230. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/instructions/velocity.py +0 -0
  231. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/model.py +0 -0
  232. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/resources/empty_multirow_rule_minimal.scaffold.bin +0 -0
  233. {laddercodec-0.1.8 → laddercodec-0.2.0}/src/laddercodec/topology.py +0 -0
  234. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/__init__.py +0 -0
  235. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_bundle.py +0 -0
  236. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_token_parser.py +0 -0
  237. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/csv/test_writer_raw_validator.py +0 -0
  238. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/coverage.ckp +0 -0
  239. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__block.bin +0 -0
  240. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__block.csv +0 -0
  241. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__oneshot.bin +0 -0
  242. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__oneshot.csv +0 -0
  243. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__to_ascii.bin +0 -0
  244. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__to_ascii.csv +0 -0
  245. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__to_value.bin +0 -0
  246. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/blockcopy__to_value.csv +0 -0
  247. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/call__basic.bin +0 -0
  248. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/call__basic.csv +0 -0
  249. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__and.bin +0 -0
  250. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__and.csv +0 -0
  251. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__eq.bin +0 -0
  252. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__eq.csv +0 -0
  253. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__fall.bin +0 -0
  254. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__fall.csv +0 -0
  255. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__ge.bin +0 -0
  256. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__ge.csv +0 -0
  257. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__gt.bin +0 -0
  258. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__gt.csv +0 -0
  259. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__immediate.bin +0 -0
  260. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__immediate.csv +0 -0
  261. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__le.bin +0 -0
  262. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__le.csv +0 -0
  263. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__lt.bin +0 -0
  264. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__lt.csv +0 -0
  265. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__nc.bin +0 -0
  266. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__nc.csv +0 -0
  267. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__ne.bin +0 -0
  268. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__ne.csv +0 -0
  269. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__no.bin +0 -0
  270. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__no.csv +0 -0
  271. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__or.bin +0 -0
  272. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__or.csv +0 -0
  273. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__rise.bin +0 -0
  274. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/cond__rise.csv +0 -0
  275. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__literal_float.bin +0 -0
  276. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__literal_float.csv +0 -0
  277. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__literal_int.bin +0 -0
  278. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__literal_int.csv +0 -0
  279. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__oneshot.bin +0 -0
  280. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__oneshot.csv +0 -0
  281. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__tag.bin +0 -0
  282. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__tag.csv +0 -0
  283. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_ascii.bin +0 -0
  284. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_ascii.csv +0 -0
  285. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_binary.bin +0 -0
  286. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_binary.csv +0 -0
  287. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text.bin +0 -0
  288. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text.csv +0 -0
  289. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_exp.bin +0 -0
  290. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_exp.csv +0 -0
  291. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_sz.bin +0 -0
  292. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_sz.csv +0 -0
  293. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_term.bin +0 -0
  294. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_text_term.csv +0 -0
  295. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_value.bin +0 -0
  296. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/copy__to_value.csv +0 -0
  297. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_down.bin +0 -0
  298. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_down.csv +0 -0
  299. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_up__down.bin +0 -0
  300. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_up__down.csv +0 -0
  301. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_up__reset.bin +0 -0
  302. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/count_up__reset.csv +0 -0
  303. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/end.bin +0 -0
  304. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/end.csv +0 -0
  305. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__basic.bin +0 -0
  306. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__basic.csv +0 -0
  307. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__full.bin +0 -0
  308. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__full.csv +0 -0
  309. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__jog.bin +0 -0
  310. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__jog.csv +0 -0
  311. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__jump.bin +0 -0
  312. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__jump.csv +0 -0
  313. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__max.bin +0 -0
  314. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/event_drum__max.csv +0 -0
  315. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__literal.bin +0 -0
  316. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__literal.csv +0 -0
  317. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__oneshot.bin +0 -0
  318. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__oneshot.csv +0 -0
  319. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__tag.bin +0 -0
  320. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/fill__tag.csv +0 -0
  321. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/forloop__basic.bin +0 -0
  322. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/forloop__basic.csv +0 -0
  323. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/forloop__oneshot.bin +0 -0
  324. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/forloop__oneshot.csv +0 -0
  325. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__block.bin +0 -0
  326. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__block.csv +0 -0
  327. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__immediate.bin +0 -0
  328. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__immediate.csv +0 -0
  329. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__tag.bin +0 -0
  330. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/latch__tag.csv +0 -0
  331. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__decimal.bin +0 -0
  332. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__decimal.csv +0 -0
  333. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__hex.bin +0 -0
  334. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__hex.csv +0 -0
  335. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__oneshot.bin +0 -0
  336. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/math__oneshot.csv +0 -0
  337. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/off_delay__tof.bin +0 -0
  338. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/off_delay__tof.csv +0 -0
  339. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/on_delay__rton.bin +0 -0
  340. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/on_delay__rton.csv +0 -0
  341. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/on_delay__ton.bin +0 -0
  342. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/on_delay__ton.csv +0 -0
  343. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__block.bin +0 -0
  344. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__block.csv +0 -0
  345. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__immediate.bin +0 -0
  346. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__immediate.csv +0 -0
  347. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__oneshot.bin +0 -0
  348. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__oneshot.csv +0 -0
  349. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__tag.bin +0 -0
  350. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/out__tag.csv +0 -0
  351. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_bits.bin +0 -0
  352. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_bits.csv +0 -0
  353. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_bits__oneshot.bin +0 -0
  354. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_bits__oneshot.csv +0 -0
  355. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text.bin +0 -0
  356. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text.csv +0 -0
  357. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text__allow_whitespace.bin +0 -0
  358. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text__allow_whitespace.csv +0 -0
  359. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text__oneshot.bin +0 -0
  360. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_text__oneshot.csv +0 -0
  361. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_words.bin +0 -0
  362. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_words.csv +0 -0
  363. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_words__oneshot.bin +0 -0
  364. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/pack_words__oneshot.csv +0 -0
  365. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__email.bin +0 -0
  366. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__email.csv +0 -0
  367. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__home.bin +0 -0
  368. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__home.csv +0 -0
  369. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__position.bin +0 -0
  370. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__position.csv +0 -0
  371. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__velocity.bin +0 -0
  372. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/raw_instructions__velocity.csv +0 -0
  373. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_click_block.bin +0 -0
  374. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_click_block.csv +0 -0
  375. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_click_single.bin +0 -0
  376. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_click_single.csv +0 -0
  377. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_modbus_block.bin +0 -0
  378. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_modbus_block.csv +0 -0
  379. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_modbus_single.bin +0 -0
  380. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__rtu_modbus_single.csv +0 -0
  381. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_coil.bin +0 -0
  382. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_coil.csv +0 -0
  383. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_input.bin +0 -0
  384. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_input.csv +0 -0
  385. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_input_reg.bin +0 -0
  386. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_input_reg.csv +0 -0
  387. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_wordswap.bin +0 -0
  388. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/receive__tcp_click_wordswap.csv +0 -0
  389. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__block.bin +0 -0
  390. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__block.csv +0 -0
  391. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__immediate.bin +0 -0
  392. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__immediate.csv +0 -0
  393. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__tag.bin +0 -0
  394. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/reset__tag.csv +0 -0
  395. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/return_early__basic.bin +0 -0
  396. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/return_early__basic.csv +0 -0
  397. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__continuous.bin +0 -0
  398. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__continuous.csv +0 -0
  399. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__eq.bin +0 -0
  400. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__eq.csv +0 -0
  401. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__ge.bin +0 -0
  402. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__ge.csv +0 -0
  403. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__gt.bin +0 -0
  404. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__gt.csv +0 -0
  405. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__le.bin +0 -0
  406. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__le.csv +0 -0
  407. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__lt.bin +0 -0
  408. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__lt.csv +0 -0
  409. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__ne.bin +0 -0
  410. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__ne.csv +0 -0
  411. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__oneshot.bin +0 -0
  412. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/search__oneshot.csv +0 -0
  413. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__error_off.bin +0 -0
  414. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__error_off.csv +0 -0
  415. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__errorcode_off.bin +0 -0
  416. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__errorcode_off.csv +0 -0
  417. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_click_block.bin +0 -0
  418. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_click_block.csv +0 -0
  419. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_click_single.bin +0 -0
  420. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_click_single.csv +0 -0
  421. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_modbus_block.bin +0 -0
  422. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_modbus_block.csv +0 -0
  423. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_modbus_single.bin +0 -0
  424. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__rtu_modbus_single.csv +0 -0
  425. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__sending_off.bin +0 -0
  426. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__sending_off.csv +0 -0
  427. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__success_off.bin +0 -0
  428. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__success_off.csv +0 -0
  429. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_coil.bin +0 -0
  430. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_coil.csv +0 -0
  431. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_coils.bin +0 -0
  432. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_coils.csv +0 -0
  433. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_wordswap.bin +0 -0
  434. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/send__tcp_click_wordswap.csv +0 -0
  435. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/shift__basic.bin +0 -0
  436. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/shift__basic.csv +0 -0
  437. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/time_drum__basic.bin +0 -0
  438. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/time_drum__basic.csv +0 -0
  439. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/time_drum__basic_td.bin +0 -0
  440. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/time_drum__basic_td.csv +0 -0
  441. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_bits.bin +0 -0
  442. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_bits.csv +0 -0
  443. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_bits__oneshot.bin +0 -0
  444. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_bits__oneshot.csv +0 -0
  445. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_words.bin +0 -0
  446. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_words.csv +0 -0
  447. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_words__oneshot.bin +0 -0
  448. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/unpack_to_words__oneshot.csv +0 -0
  449. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/golden/verify_progress.log +0 -0
  450. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/coverage/subroutines/coverage_sub.csv +0 -0
  451. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-fullwire-nop.bin +0 -0
  452. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-fullwire-nop.csv +0 -0
  453. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-max1400.bin +0 -0
  454. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-max1400.csv +0 -0
  455. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-styled-mixed.bin +0 -0
  456. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-1row-styled-mixed.csv +0 -0
  457. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-2row-max1324.bin +0 -0
  458. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-2row-max1324.csv +0 -0
  459. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-2row-nop-wire.bin +0 -0
  460. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-2row-nop-wire.csv +0 -0
  461. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-32row-sparse.bin +0 -0
  462. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-32row-sparse.csv +0 -0
  463. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-3row-mixed.bin +0 -0
  464. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/cmt-3row-mixed.csv +0 -0
  465. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-cond__and.bin +0 -0
  466. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-cond__and.csv +0 -0
  467. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-r0only.bin +0 -0
  468. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-r0only.csv +0 -0
  469. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-spread.bin +0 -0
  470. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-2row-spread.csv +0 -0
  471. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-branch.bin +0 -0
  472. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-branch.csv +0 -0
  473. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-or-colA.bin +0 -0
  474. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-or-colA.csv +0 -0
  475. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-or.bin +0 -0
  476. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-or.csv +0 -0
  477. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-scatter.bin +0 -0
  478. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-3row-scatter.csv +0 -0
  479. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-6row-multi-output.bin +0 -0
  480. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-6row-multi-output.csv +0 -0
  481. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-coil-timer-ret.bin +0 -0
  482. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-coil-timer-ret.csv +0 -0
  483. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-ge.bin +0 -0
  484. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-ge.csv +0 -0
  485. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-series.bin +0 -0
  486. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-series.csv +0 -0
  487. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-timer.bin +0 -0
  488. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-compare-timer.csv +0 -0
  489. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-cond__and.bin +0 -0
  490. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-cond__and.csv +0 -0
  491. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-copy-multirow-2af-T.bin +0 -0
  492. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-copy-multirow-2af-T.csv +0 -0
  493. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-copy-multirow-2af.bin +0 -0
  494. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-copy-multirow-2af.csv +0 -0
  495. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-count_down-row0-data.bin +0 -0
  496. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-count_down-row0-data.csv +0 -0
  497. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-no-out.bin +0 -0
  498. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-no-out.csv +0 -0
  499. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-series.bin +0 -0
  500. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-series.csv +0 -0
  501. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-off.bin +0 -0
  502. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-off.csv +0 -0
  503. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-on.bin +0 -0
  504. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-on.csv +0 -0
  505. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-ret.bin +0 -0
  506. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/instr-timer-ret.csv +0 -0
  507. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-2rung-2row.bin +0 -0
  508. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-2rung-2row.csv +0 -0
  509. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-2rung-wire.bin +0 -0
  510. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-2rung-wire.csv +0 -0
  511. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-3rung-wire-nop.bin +0 -0
  512. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-3rung-wire-nop.csv +0 -0
  513. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-2row.bin +0 -0
  514. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-2row.csv +0 -0
  515. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-both.bin +0 -0
  516. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-both.csv +0 -0
  517. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-r1-max1400.bin +0 -0
  518. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-2rung-r1-max1400.csv +0 -0
  519. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-all.bin +0 -0
  520. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-all.csv +0 -0
  521. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow-multiinstructions.bin +0 -0
  522. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow-multiinstructions.csv +0 -0
  523. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow-nopaddedrows.bin +0 -0
  524. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow-nopaddedrows.csv +0 -0
  525. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow.bin +0 -0
  526. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-cmt-3rung-multirow.csv +0 -0
  527. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-instr-2rung.bin +0 -0
  528. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-instr-2rung.csv +0 -0
  529. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-instr-cond__and.bin +0 -0
  530. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/mr-instr-cond__and.csv +0 -0
  531. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-1row-empty.bin +0 -0
  532. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-1row-empty.csv +0 -0
  533. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-1row-fullwire-nop.bin +0 -0
  534. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-1row-fullwire-nop.csv +0 -0
  535. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-32row-empty.bin +0 -0
  536. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-32row-empty.csv +0 -0
  537. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-3row-t-junction.bin +0 -0
  538. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-3row-t-junction.csv +0 -0
  539. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-4row-vertical.bin +0 -0
  540. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/nc-4row-vertical.csv +0 -0
  541. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/ladder_captures/golden/verify_progress.log +0 -0
  542. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/counter_scr.bin +0 -0
  543. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/counter_scr.csv +0 -0
  544. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/counter_scr.scr +0 -0
  545. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/coverage.bin +0 -0
  546. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/coverage.csv +0 -0
  547. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/coverage.scr +0 -0
  548. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/or_topology.bin +0 -0
  549. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/or_topology.csv +0 -0
  550. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/or_topology.scr +0 -0
  551. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/shift_scr.bin +0 -0
  552. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/shift_scr.csv +0 -0
  553. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/fixtures/scr_captures/shift_scr.scr +0 -0
  554. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/golden_io.py +0 -0
  555. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_counter.py +0 -0
  556. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_decode_multi.py +0 -0
  557. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_decode_rtf_oracle.py +0 -0
  558. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_empty_multirow.py +0 -0
  559. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_encode.py +0 -0
  560. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_encode_multi.py +0 -0
  561. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_forloop.py +0 -0
  562. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_math.py +0 -0
  563. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_model.py +0 -0
  564. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_shift.py +0 -0
  565. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/ladder/test_verify_status.py +0 -0
  566. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/test_coverage.py +0 -0
  567. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/test_public_api.py +0 -0
  568. {laddercodec-0.1.8 → laddercodec-0.2.0}/tests/test_smoke.py +0 -0
  569. {laddercodec-0.1.8 → laddercodec-0.2.0}/uv.lock +0 -0
@@ -0,0 +1,134 @@
1
+ # Changelog
2
+
3
+ <!-- Style guide: one sentence per entry. Describe the user-visible effect, not the
4
+ implementation. Group related fixes/features into a single entry when they share
5
+ a theme. Breaking changes and migration steps can be longer — users need the
6
+ specifics. Detail belongs in commit messages and PR descriptions, not here.
7
+
8
+ Review and condense before release — entries accumulate during development and
9
+ should be edited into shape before moving from Unreleased to a version heading. -->
10
+
11
+ ## 0.2.0
12
+
13
+ ### Fixed
14
+ - **SCR decode**: correct the row-topology block framing — every row (including row 0) uses one uniform block format, fixing branch rungs whose wires (`T`/`|`/`-`) were silently dropped whenever a continuation row's first right-wired cell carried segment flag 1.
15
+ - **SCR decode**: accept placement-ordered flag-table entries — native captures store columns out of order (e.g. `[6,1,0,3,2,4,5]`), which the old sorted-order validation rejected.
16
+ - **SCR decode**: drop orphaned editor-debris wire rows stored above a rung's true row 0 (observed under drum boxes), matching what Click's own clipboard copy produces.
17
+
18
+ ### Changed
19
+ - **SCR decode**: replace the heuristic topology parser (leading-row guessing, count_down special cases, end-marker scanning) with a deterministic parser derived from the verified format — see `docs/internals/binary-format.md` §"SCR row-topology blocks" for the spec.
20
+ - **SCR decode**: `decode_program()` now parses rung records with a single forward cursor — comments, topology, and instructions are read from explicit lengths and counts instead of scanning for signatures, so comments attach to their own rung by construction and malformed files fail loudly with the byte offset (see §"SCR rung records" in the binary format doc).
21
+
22
+ ### Added
23
+ - **Tumbler fixture**: 34 real Click programs (`tests/fixtures/tumbler/`), each pairing the internal `.scr` program file with a clipboard capture of the same program; `decode_program()` output is regression-tested byte-identical against the clipboard CSVs.
24
+ - **Devtools**: `compare_csv.py` normalizes `R` vs `R1`/`R2` rung markers before comparing, so scr-derived and clipboard-saved CSVs diff cleanly.
25
+
26
+ ## 0.1.9
27
+
28
+ ### Added
29
+ - **Time drums**: encode and decode all five time bases (`Tms`/`Ts`/`Tm`/`Th`/`Td`), plus jog and jump, matching event drums.
30
+ - **CSV writer**: `write_csv(path, rungs, index=True)` emits 1-based sequential rung markers (`R1`, `R2`, `R3`, …) instead of plain `R`; readers accept `R` optionally followed by digits, so indexed files still round-trip.
31
+
32
+ ### Fixed
33
+ - **Drum decode**: single-step event and time drums now decode from `Scr*.tmp` files instead of falling back to `RawInstruction`.
34
+ - **Drum decode**: a stray NOP stranded on a drum's pin row is dropped instead of surfacing as a spurious `NOP` that broke `write_csv`.
35
+ - **CSV writer**: a tall instruction (drum, timer, counter, …) with a wire branch or content running below its body keeps its rows positional instead of collapsing the following rows up, fixing `WriterError`/lost geometry on those rungs.
36
+
37
+ ### Performance
38
+ - `decode_program()` is ~48% faster — topology block scanning uses `bytes.find()` instead of byte-by-byte iteration, and hot loops cache `len(data)` and precomputed constants.
39
+ - `encode()` is ~35% faster — cell headers use a single pre-compiled `struct.Struct.pack` instead of 8 separate calls, and Math blobs use pre-computed empty slot bytes instead of 1000 per-instruction loop iterations.
40
+
41
+ ## 0.1.8
42
+
43
+ ### Fixed
44
+ - **CSV writer**: downgrade trailing blank row mismatch from error to warning
45
+ when a tall instruction's stripped continuation absorbs extra blank rows on
46
+ read-back — fixes `WriterError` on rungs with blank rows beyond an
47
+ instruction's visual height
48
+
49
+ ## 0.1.7
50
+
51
+ ### Fixed
52
+ - **SCR decode**: skip pure vertical pass-through rows in implied modifier
53
+ row fallback, preventing `|` → `T` and blank → `-` corruption on
54
+ continuation rows
55
+ - **CSV converter**: recognize `Contact`/`CompareContact` objects with
56
+ `wire_down=True` during wire hydration, not just bare string tokens
57
+
58
+ ### Changed
59
+ - **SCR decode**: remove `_implied_modifier_row_offsets` fallback — SCR
60
+ continuation row topology already encodes per-row horizontal connectivity
61
+ explicitly, verified across 883 rungs in 47 SCR files
62
+
63
+ ## 0.1.6
64
+
65
+ ### Fixed
66
+ - **SCR decode**: replace backward/brute-force topology block scanning with a
67
+ single forward pass, fixing false positive matches from flag-table tail bytes
68
+ that caused spurious empty rungs with inflated row counts
69
+
70
+ ## 0.1.5
71
+
72
+ ### Changed
73
+ - **Encode**: remove AF summary block — Click accepts multi-AF rungs without
74
+ it, eliminating complex gating logic
75
+ - **CSV**: consolidate tall-instruction padding into shared hydrate/dehydrate
76
+ helpers in converter and writer
77
+
78
+ ### Added
79
+ - **Devtools**: `combine_coverage.py` — combine random coverage fixtures into
80
+ a single multi-row rung for paste smoke-testing
81
+ - **Devtools**: `coverage_golden.py` now regenerates all bins unconditionally
82
+
83
+ ## 0.1.4
84
+
85
+ ### Fixed
86
+ - **CSV**: preserve multi-row AF round-trips, including pinned/tall AF blocks
87
+ away from row 0, multiple tall blocks in one rung, and generic tall
88
+ continuation rows
89
+ - **SCR decode**: restore omitted wiring for generic tall AF continuation rows
90
+ - **CSV writer**: fail loudly when emitted CSV rows lose decoded rung
91
+ semantics by reparsing writer output and checking for row, condition, or AF
92
+ mismatches before writing to disk
93
+
94
+ ## 0.1.3
95
+
96
+ ### Fixed
97
+ - **RTF decode**: parse RTF comments structurally instead of regex-based
98
+ stripping, handling nested groups, font/color tables, and style toggles
99
+ correctly
100
+ - **RTF decode**: handle `\uN` unicode escape sequences (signed 16-bit) and
101
+ validate `ansicpg1252` before decoding
102
+ - **CSV**: preserve quoted string literals in AF token positional args — e.g.
103
+ `copy("d",TXT1)` no longer loses its quotes during parse
104
+ - **Search instruction**: store text-search operands with quotes end-to-end,
105
+ removing the strip-on-decode/re-add-on-encode anti-pattern
106
+
107
+ ### Added
108
+ - **Encode**: auto-sanitize non-ASCII characters (curly quotes, em-dashes,
109
+ arrows, etc.) in rung comments before RTF conversion via NFKD normalization
110
+
111
+
112
+ ## 0.1.2
113
+
114
+ ### Fixed
115
+ - **Math**: preserve duplicate operands and normalize parenthesis spacing
116
+
117
+ ## 0.1.1
118
+
119
+ ### Fixed
120
+ - **Encode**: correct `row_span` for tall AF instructions and single-rung list
121
+ routing
122
+
123
+ ## 0.1.0
124
+
125
+ Initial release. Originally prototyped in
126
+ [clicknick](https://github.com/ssweber/clicknick), then developed into a
127
+ standalone library here.
128
+
129
+ - Encode `Rung` objects or CSV into Click clipboard binary
130
+ - Decode clipboard binary back into structured Python objects
131
+ - Decode `Scr*.tmp` program files into `Program` objects
132
+ - All standard Click instruction types supported natively
133
+ - `RawInstruction` passthrough for unknown blob types
134
+ - Zero runtime dependencies
@@ -98,6 +98,7 @@ tests/
98
98
  │ ├── test_decode.py # decode() tests
99
99
  │ ├── test_decode_multi.py # decode() multi-rung tests
100
100
  │ ├── test_decode_program.py # decode_program() program file tests
101
+ │ ├── test_decode_program_tumbler.py # 34 real programs: scr decode == clipboard CSV
101
102
  │ ├── test_verify_status.py # Golden verification status check
102
103
  │ ├── test_model.py # InstructionType, operand validation
103
104
  │ └── test_empty_multirow.py # Payload synthesis for rows 1..32
@@ -109,10 +110,12 @@ tests/
109
110
  │ ├── test_token_parser.py # Condition + AF token parsing
110
111
  │ └── test_writer.py # CSV writer tests
111
112
  └── fixtures/
112
- └── ladder_captures/golden/ # Golden CSV/BIN fixtures
113
+ ├── ladder_captures/golden/ # Golden CSV/BIN fixtures
114
+ ├── scr_captures/ # Scr program files + clipboard bins (decode_program)
115
+ └── tumbler/ # 34 real programs: <name>.scr + <name>.clipboard.{bin,csv}
113
116
  ```
114
117
 
115
- Golden fixtures verified through Click paste round-trip.
118
+ Golden fixtures verified through Click paste round-trip. The tumbler fixture pairs each Click internal program file (`.scr`) with a clipboard capture of the same program; `devtools/compare_csv.py <dir>` compares `<name>.csv` (scr decode) against `<name>.clipboard.csv` ground truth.
116
119
 
117
120
  ## Binary Format
118
121
 
@@ -157,7 +160,7 @@ All tested shapes pass Click round-trip (verified via paste → copy-back):
157
160
 
158
161
  The clipboard decoder (`decode.py`) reads Click clipboard binaries back into structured data. Validated against a 37-rung native capture covering all basic instruction types.
159
162
 
160
- The program file decoder (`decode_program.py`) reads Click's internal `Scr*.tmp` files. Returns a `Program` with name, index, and all rungs. Validated against 114-rung coverage fixture plus shift and counter programs.
163
+ The program file decoder (`decode_program.py`) reads Click's internal `Scr*.tmp` files. Returns a `Program` with name, index, and all rungs. Validated against the 114-rung coverage fixture, shift/counter/drum programs, and the 34-program tumbler fixture (byte-identical CSV vs clipboard captures). Row topology uses the uniform row-block framing documented in `docs/internals/binary-format.md` §"SCR row-topology blocks".
161
164
 
162
165
  **All standard Click instruction types decoded natively:**
163
166
  - Contacts: NO, NC, edge (rise/fall), immediate (NO/NC)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: laddercodec
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: Binary codec for AutomationDirect CLICK PLC ladder clipboard format
5
5
  Project-URL: Repository, https://github.com/ssweber/laddercodec
6
6
  Project-URL: Documentation, https://ssweber.github.io/laddercodec/
@@ -1,6 +1,9 @@
1
1
  """Compare {slug}.csv against {slug}.clipboard.csv in a directory.
2
2
 
3
- Reports which pairs are identical and which differ.
3
+ Reports which pairs are identical and which differ. Rung markers are
4
+ normalized before comparison (indexed ``R1``/``R2`` vs plain ``R``), since
5
+ scr-derived bundles are written with ``index=True`` while clipboard saves
6
+ are not.
4
7
 
5
8
  Usage:
6
9
  uv run devtools/compare_csv.py <directory>
@@ -8,9 +11,17 @@ Usage:
8
11
 
9
12
  from __future__ import annotations
10
13
 
14
+ import re
11
15
  import sys
12
16
  from pathlib import Path
13
17
 
18
+ _INDEXED_MARKER = re.compile(r"^R\d+(?=,)")
19
+
20
+
21
+ def _normalized_lines(path: Path) -> list[str]:
22
+ text = path.read_text(encoding="utf-8")
23
+ return [_INDEXED_MARKER.sub("R", line) for line in text.splitlines()]
24
+
14
25
 
15
26
  def main() -> None:
16
27
  if len(sys.argv) != 2:
@@ -42,7 +53,7 @@ def main() -> None:
42
53
  missing += 1
43
54
  continue
44
55
 
45
- if base_path.read_bytes() == clip_path.read_bytes():
56
+ if _normalized_lines(base_path) == _normalized_lines(clip_path):
46
57
  print(f" IDENTICAL {slug}")
47
58
  identical += 1
48
59
  else:
@@ -25,20 +25,14 @@ def main() -> None:
25
25
  print(f"\nRung {si}:")
26
26
  cursor = sec_off + 6
27
27
  for ii in range(count):
28
- blob8 = _parse_blob(data, cursor + 8)
29
- blob9 = _parse_blob(data, cursor + 9)
30
- if blob8:
31
- row = data[cursor]
32
- col = data[cursor + 1]
33
- blob_start = cursor + 8
34
- cls, typ, end_off, next_pos, m1 = blob8
35
- elif blob9:
36
- row = data[cursor + 1]
37
- col = data[cursor + 2]
38
- blob_start = cursor + 9
39
- cls, typ, end_off, next_pos, m1 = blob9
40
- else:
28
+ blob = _parse_blob(data, cursor + 8)
29
+ if not blob:
41
30
  break
31
+ row = data[cursor]
32
+ col = data[cursor + 1]
33
+ blob_start = cursor + 8
34
+ cls, typ, end_off, m1 = blob
35
+ next_pos = end_off + 2 + data[end_off] # trailer-aware advance
42
36
 
43
37
  # Parse tagged fields
44
38
  pos = blob_start
@@ -6,7 +6,7 @@ used by `laddercodec` CSV tooling and by `pyrung.click.to_ladder()`.
6
6
  Relevant entry points:
7
7
 
8
8
  - `laddercodec.read_csv(path)`
9
- - `laddercodec.write_csv(path, rungs)`
9
+ - `laddercodec.write_csv(path, rungs, *, index=False)`
10
10
  - `laddercodec.csv.bundle.parse_bundle(directory)`
11
11
  - `pyrung.click.to_ladder(program, tag_map)`
12
12
  - `pyrung.click.LadderBundle.write(directory)`
@@ -58,6 +58,9 @@ marker,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF
58
58
 
59
59
  - `marker`:
60
60
  - `R` => first row of a rung
61
+ - `R<n>` (e.g. `R1`, `R2`) => first row of a rung, with an optional 1-based
62
+ index. `write_csv(path, rungs, index=True)` emits these; the index is
63
+ decorative and ignored on read.
61
64
  - `""` (blank) => continuation row of current rung
62
65
  - `#` => comment row
63
66
  - `AF`:
@@ -66,8 +69,8 @@ marker,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF
66
69
 
67
70
  Rung segmentation rule for consumers:
68
71
 
69
- - A rung starts at each row with `marker == "R"` and continues until the next
70
- `R` or EOF.
72
+ - A rung starts at each row whose `marker` is `R` optionally followed by digits
73
+ (`R`, `R1`, `R2`, ...) and continues until the next such marker or EOF.
71
74
 
72
75
  ## Comment rows
73
76
 
@@ -464,4 +467,4 @@ On any issue:
464
467
  3. Treat `AF` as an opaque canonical token string unless your decoder
465
468
  intentionally parses token grammar.
466
469
  4. Treat unknown future token names as extension points (fail closed if strict).
467
- 5. Segment rungs by `marker == "R"`.
470
+ 5. Segment rungs by a `marker` of `R` optionally followed by digits (`R`, `R1`, `R2`, ...).
@@ -198,24 +198,54 @@ The instruction blob content (tag IDs, operand values, wire types) is **identica
198
198
 
199
199
  The embedded fields at offsets +0 through +6 after the type code correspond to clipboard cell header offsets +0x09 through +0x10. The `end_offset` is an absolute file pointer to the blob boundary — the same boundary that clipboard's `find_blob_boundary()` derives by scanning tags. Tags use length-prefixed strings (no `FFFFFFFF` sentinel).
200
200
 
201
+ The byte at `end_offset` is a 1-byte trailer length (observed 0 or 1): the next structure starts at `end_offset + 2 + data[end_offset]`. Certain instruction encodings (`RD`/`SD` always; `Copy`/`Math`/`Out`/`Drum` sometimes) emit a 1-byte trailer whose value is opaque.
202
+
203
+ ## SCR rung records
204
+
205
+ An SCR file is a header followed by one rung record per rung, in order, parseable with a single forward cursor:
206
+
207
+ ```
208
+ RUNG = [2B rung_index] -- 1-based ordinal; asserted sequential
209
+ [4B rtf_len][rtf body] -- comment; rtf_len = 0 means none
210
+ TOPOLOGY -- row-topology block (below)
211
+ [2B instr_count] -- 0 = empty rung (nothing follows)
212
+ [4B section_marker] -- only when instr_count > 0
213
+ instr_count x ENTRY -- 8-byte header + blob (see framing above)
214
+ ENTRY = [1B row_1based][1B col][1B b2][1B b3][1B entry_seq][3B 00] + blob
215
+ ```
216
+
217
+ Rung 0 carries a 7-byte file prelude instead of `rung_index`: `[2B file_marker][0d][4B total_rung_records]`. `file_marker` equals the per-file `section_marker` constant (opaque; validation magic only), and `total_rung_records` counts every rung record in the file.
218
+
219
+ Programs end with 1–4 ordinary content-less rungs (the empty editor rungs below the last programmed rung; some carry a fully-wired row) — the decoder drops trailing records with no instructions and no comment. Files with `prog_idx == 1` (the main program) carry a 2-byte tail after the last record; subroutines end exactly at the last record.
220
+
201
221
  ## SCR row-topology blocks
202
222
 
203
223
  SCR files store per-rung wire topology in structured blocks that precede each rung's instruction section. Each block encodes the right-wire and segment-flag data that clipboard stores per-cell in the grid.
204
224
 
205
225
  ```
206
- [2B row_word][03 00 00] -- 5-byte header
207
- [leading-row wire blocks...] -- rows before row 0 (count_down bridge)
208
- [1B af_segment][1B entry_count][00] -- 3-byte trailer
209
- [entry_count x (1B seg_flag, 1B col_idx)] -- row 0 flag table
210
- [continuation-row wire blocks...] -- rows 1..N-1
211
- [20 00] -- end marker
212
- [wire_down data] -- per-column vertical wire indices
226
+ [2B row_word][03 00 00] -- 5-byte header; row_word = stored rows + 1
227
+ { row block } x (row_word - 1) -- one per grid row, in row order (row 0 first)
228
+ [20 00] -- end marker
229
+ [wire-down table] -- exactly 32 column entries (see below)
230
+ ```
231
+
232
+ Every row block — including row 0's — has the same uniform format:
233
+
213
234
  ```
235
+ [1B flag][1B count][00] + count x ([1B seg][1B col])
236
+ ```
237
+
238
+ - `flag` — the +0x19 segment flag of the row's AF (col 31) cell. 1 for normal output/coil/NOP rows, 0 for tall segment-0 instructions (timer/counter/drum).
239
+ - `count` — number of right-wired cells on the row (condition cells plus AF when present). `count = 0` is an empty row: the 3-byte block `[flag][00][00]`.
240
+ - Each entry pairs a cell's +0x19 segment flag with its column index (0–31, 31 = AF). Entries are **placement-ordered** — the column sequence is usually ascending but not guaranteed (native captures include orders like `[6,1,0,3,2,4,5]`), so columns must be treated as a set.
241
+
242
+ A row block whose entries include col 0 with every condition-cell seg = 1 carries the grid-row-0 signature (row 0 is exempt from the per-row segment boundary). SCR can retain orphaned wire rows *above* the true row 0 — editor debris under a tall instruction box — which Click's own clipboard copy omits; the decoder drops non-empty stored rows preceding the first row with the row-0 signature.
214
243
 
215
- Each continuation-row wire block:
244
+ The wire-down table after the `20 00` marker has exactly one entry per column 0–31:
216
245
 
217
246
  ```
218
- [1B seg][1B right_count][00 00][pairs of (col_idx, next_seg)...][final_col]
247
+ no down wires: [00 00]
248
+ down wires: [1B count][00][count x 1-based row index]
219
249
  ```
220
250
 
221
- The block is parsed forward: leading-row blocks are consumed until the 3-byte trailer is found, then the row-0 flag table, then continuation rows, then the `0x0020` marker, then wire-down data.
251
+ This table is the SCR serialization of the per-cell down flag (+0x21) that clipboard stores on each cell — it lists every down wire, including those carried by contact cells (rendered as a `T:` prefix, see [wire rendering](wire-rendering.md)). It is therefore a superset of the visible `T`/`|` wire tokens.
@@ -21,7 +21,7 @@ Wire tokens are classified by (right, down) only — the segment flag is indepen
21
21
  | 1 | 1 | `T` | Branch junction (right + down) |
22
22
  | 0 | 0 | *(blank)* | No wire |
23
23
 
24
- Instruction cells (contacts, coils) also set right=1 — they behave like horizontal wires with instruction data layered on top.
24
+ Instruction cells (contacts, coils) also set right=1 — they behave like horizontal wires with instruction data layered on top. A contact cell can additionally set down=1 (a branch rooted directly under the contact): since right is already 1, that's the T shape, and the CSV renders it as a `T:` prefix on the contact token (e.g. `T:C1204`), mapping to `wire_down=True` on `Contact`/`CompareContact`.
25
25
 
26
26
  ## Left-edge rendering
27
27
 
@@ -21,6 +21,11 @@ from .topology import CELL_SIZE, COLS_PER_ROW
21
21
  # Instruction data starts at this offset in the cell header.
22
22
  _INSTR_DATA_OFFSET = 0x25
23
23
 
24
+ # Pre-compiled struct for the 37-byte cell header (single pack replaces 8 calls).
25
+ # Layout: pad(1) col(u32) row(u32) span(u8) vis(u8) 0(u8) extra(u8)
26
+ # instr_idx(i32) 1(u32) contact(u32) seg(u32) right(u32) down(u32)
27
+ _HEADER_STRUCT = struct.Struct("<xIIBBBBiIIIII")
28
+
24
29
 
25
30
  # ---------------------------------------------------------------------------
26
31
  # Unified cell builder
@@ -67,37 +72,21 @@ class ClickCell:
67
72
  row_span: int = 1
68
73
  visual_rows: int = 1
69
74
 
70
- def build_header(self) -> bytearray:
75
+ def build_header(self) -> bytes:
71
76
  """Build the 0x25 (37-byte) structured header."""
72
- header = bytearray(_INSTR_DATA_OFFSET)
73
-
74
- # 32-bit LE Column Index and Row Index
75
- header[0x01:0x05] = struct.pack("<I", self.col)
76
- header[0x05:0x09] = struct.pack("<I", self.global_row + 1)
77
-
78
- # 8-bit structural bytes
77
+ # 8-bit structural bytes at +0x09..+0x0C
79
78
  if self.row_span > 1:
80
79
  # Multi-row AF instruction (timer): +0x09 = row span,
81
80
  # +0x0A = visual sub-rows, +0x0B stays 0x00 (no 0xFD marker).
82
- header[0x09] = self.row_span
83
- header[0x0A] = self.visual_rows
81
+ b09, b0a, b0c = self.row_span, self.visual_rows, 0
84
82
  elif self.blob or self.rung_has_instructions:
85
83
  # Instruction cell or wire cell in an instruction-bearing rung.
86
84
  # Native Click writes +0x0B=0x00, +0x0C=0x00 for these cells
87
85
  # (confirmed via native captures: 1rung_2rows, instr-cond__and).
88
- header[0x09] = 0x01
89
- header[0x0A] = 0x01
86
+ b09, b0a, b0c = 1, 1, 0
90
87
  else:
91
88
  # Pure wire/data cell in a wire-only rung: +0x0C = 0x01
92
- header[0x09] = 0x01
93
- header[0x0A] = 0x01
94
- header[0x0C] = 0x01
95
-
96
- # 32-bit LE Instruction Index (-1 packs to 0xFFFFFFFF for data cells)
97
- header[0x0D:0x11] = struct.pack("<i", self.instr_index)
98
-
99
- # 32-bit LE Structural Flag (always 1)
100
- header[0x11:0x15] = struct.pack("<I", 1)
89
+ b09, b0a, b0c = 1, 1, 1
101
90
 
102
91
  # 32-bit LE Wire & Logic Flags
103
92
  if self.blob:
@@ -106,12 +95,20 @@ class ClickCell:
106
95
  contact_flag = self.nop_enable
107
96
  right_flag = self.af_nop if self.af_nop else self.wire_right
108
97
 
109
- header[0x15:0x19] = struct.pack("<I", contact_flag)
110
- header[0x19:0x1D] = struct.pack("<I", self.segment)
111
- header[0x1D:0x21] = struct.pack("<I", right_flag)
112
- header[0x21:0x25] = struct.pack("<I", self.wire_down)
113
-
114
- return header
98
+ return _HEADER_STRUCT.pack(
99
+ self.col,
100
+ self.global_row + 1,
101
+ b09,
102
+ b0a,
103
+ 0,
104
+ b0c,
105
+ self.instr_index,
106
+ 1,
107
+ contact_flag,
108
+ self.segment,
109
+ right_flag,
110
+ self.wire_down,
111
+ )
115
112
 
116
113
  def build_tail(self) -> bytearray:
117
114
  """Build the 16-byte tail with dynamic rung index."""
@@ -2,10 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
6
+
5
7
  MARKER_COLUMN = "marker"
6
8
  OUTPUT_COLUMN = "AF"
7
9
  COMMENT_MARKER = "#"
8
10
 
11
+ # Rung-start marker: ``R`` optionally followed by a 1-based decimal index
12
+ # (``R``, ``R1``, ``R2``, ...). The index is decorative and ignored on read.
13
+ _RUNG_MARKER_RE = re.compile(r"R\d*")
14
+
9
15
 
10
16
  def _excel_column_name(index_1_based: int) -> str:
11
17
  value = index_1_based
@@ -21,8 +27,13 @@ CSV_HEADER = (MARKER_COLUMN, *CONDITION_COLUMNS, OUTPUT_COLUMN)
21
27
  TOTAL_COLUMNS = len(CSV_HEADER)
22
28
 
23
29
 
30
+ def is_rung_marker(value: str) -> bool:
31
+ """Return True for a rung-start marker: ``R`` with optional 1-based index."""
32
+ return _RUNG_MARKER_RE.fullmatch(value) is not None
33
+
34
+
24
35
  def is_valid_marker(value: str) -> bool:
25
- return value in {"R", "", COMMENT_MARKER}
36
+ return value in {"", COMMENT_MARKER} or is_rung_marker(value)
26
37
 
27
38
 
28
39
  def validate_header(fields: list[str]) -> None:
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  from typing import Literal
9
9
 
10
10
  from .ast import CanonicalRow, ParsedCsvFileAst, RowAst, RungAst
11
- from .contract import TOTAL_COLUMNS, is_valid_marker, validate_header
11
+ from .contract import TOTAL_COLUMNS, is_rung_marker, is_valid_marker, validate_header
12
12
  from .token_parser import parse_af_token, parse_condition_token
13
13
 
14
14
 
@@ -24,7 +24,9 @@ def _detect_file_role(path: Path) -> tuple[Literal["main", "subroutine"], str |
24
24
  def _canonical_row_from_fields(fields: list[str]) -> CanonicalRow:
25
25
  marker = fields[0].strip() if fields else ""
26
26
  if not is_valid_marker(marker):
27
- raise ValueError(f"Invalid marker {marker!r}; expected 'R', '#', or blank")
27
+ raise ValueError(
28
+ f"Invalid marker {marker!r}; expected 'R' (optionally indexed, e.g. R1), '#', or blank"
29
+ )
28
30
 
29
31
  # Comment rows may be short (just marker + text); pad to full width.
30
32
  # Preserve whitespace in column A for code-style comments (indentation).
@@ -67,7 +69,7 @@ def _segment_rungs(rows: tuple[RowAst, ...]) -> tuple[RungAst, ...]:
67
69
  pending_comments.append(row)
68
70
  continue
69
71
 
70
- if row.canonical.marker == "R":
72
+ if is_rung_marker(row.canonical.marker):
71
73
  if current:
72
74
  rungs.append(RungAst(comment_rows=tuple(current_comments), rows=tuple(current)))
73
75
  current = [row]
@@ -104,13 +104,32 @@ def _append_data_row(
104
104
  return data_row_count + 1
105
105
 
106
106
 
107
+ def _followed_by_floating_row(af_tokens: Sequence[object], span_end: int) -> bool:
108
+ """True when a blank-AF row sits just below a tall instruction's span.
109
+
110
+ Such a row would be absorbed into the instruction's continuation slots on
111
+ reparse, so the instruction's trailing padding must stay positional rather
112
+ than dehydrated — otherwise the following content floats up. A real AF
113
+ instruction (or the end of the rung) anchors the position, so dehydration
114
+ is safe there.
115
+ """
116
+ return span_end < len(af_tokens) and af_tokens[span_end] == ""
117
+
118
+
107
119
  def _emit_blank_continuation(
108
120
  rows: list[list[str]],
109
121
  data_row_count: int,
110
122
  conditions: Sequence[object],
123
+ keep_positional: bool = False,
111
124
  ) -> int:
112
- """Emit a blank-AF continuation row only when it carries geometry."""
113
- if _is_padding_row(conditions):
125
+ """Emit a blank-AF continuation row.
126
+
127
+ Trailing padding (blank cells and ``|`` pass-through wires) is dehydrated —
128
+ the reparser rebuilds it from the instruction's visual height. But when a
129
+ blank-AF row follows the instruction's span (``keep_positional``), the row
130
+ is emitted so following content keeps its position on reparse.
131
+ """
132
+ if not keep_positional and _is_padding_row(conditions):
114
133
  return data_row_count
115
134
  return _append_data_row(rows, data_row_count, conditions, "")
116
135
 
@@ -139,13 +158,16 @@ def _emit_timer_block(
139
158
  message="timer continuation row must be blank AF",
140
159
  )
141
160
 
161
+ keep = _followed_by_floating_row(af_tokens, start + 2)
142
162
  data_row_count = _append_data_row(rows, data_row_count, condition_rows[start], timer)
143
163
  if timer.retained:
144
164
  data_row_count = _append_data_row(
145
165
  rows, data_row_count, condition_rows[start + 1], ".reset()"
146
166
  )
147
167
  else:
148
- data_row_count = _emit_blank_continuation(rows, data_row_count, condition_rows[start + 1])
168
+ data_row_count = _emit_blank_continuation(
169
+ rows, data_row_count, condition_rows[start + 1], keep
170
+ )
149
171
  return data_row_count, 2
150
172
 
151
173
 
@@ -164,6 +186,7 @@ def _emit_counter_block(
164
186
  f"{counter.counter_type} requires {needed} decoded rows; got {len(condition_rows)}"
165
187
  )
166
188
 
189
+ keep = _followed_by_floating_row(af_tokens, start + 3)
167
190
  if counter.counter_type == "count_up":
168
191
  _require_blank_af(
169
192
  af_tokens[start + 1],
@@ -180,7 +203,7 @@ def _emit_counter_block(
180
203
  )
181
204
  else:
182
205
  data_row_count = _emit_blank_continuation(
183
- rows, data_row_count, condition_rows[start + 1]
206
+ rows, data_row_count, condition_rows[start + 1], keep
184
207
  )
185
208
  if counter.reset_enabled:
186
209
  data_row_count = _append_data_row(
@@ -188,7 +211,7 @@ def _emit_counter_block(
188
211
  )
189
212
  else:
190
213
  data_row_count = _emit_blank_continuation(
191
- rows, data_row_count, condition_rows[start + 2]
214
+ rows, data_row_count, condition_rows[start + 2], keep
192
215
  )
193
216
  return data_row_count, 3
194
217
 
@@ -212,7 +235,9 @@ def _emit_counter_block(
212
235
  rows, data_row_count, condition_rows[start + 2], ".reset()"
213
236
  )
214
237
  else:
215
- data_row_count = _emit_blank_continuation(rows, data_row_count, condition_rows[start + 2])
238
+ data_row_count = _emit_blank_continuation(
239
+ rows, data_row_count, condition_rows[start + 2], keep
240
+ )
216
241
  return data_row_count, 3
217
242
 
218
243
 
@@ -270,6 +295,7 @@ def _emit_drum_block(
270
295
  message="drum .jog() row must be blank AF",
271
296
  )
272
297
 
298
+ keep = _followed_by_floating_row(af_tokens, start + 4)
273
299
  data_row_count = _append_data_row(rows, data_row_count, condition_rows[start], drum)
274
300
  data_row_count = _append_data_row(rows, data_row_count, condition_rows[start + 1], ".reset()")
275
301
  if drum.jump_enabled:
@@ -280,11 +306,15 @@ def _emit_drum_block(
280
306
  f".jump({drum.jump_target})",
281
307
  )
282
308
  else:
283
- data_row_count = _emit_blank_continuation(rows, data_row_count, condition_rows[start + 2])
309
+ data_row_count = _emit_blank_continuation(
310
+ rows, data_row_count, condition_rows[start + 2], keep
311
+ )
284
312
  if drum.jog_enabled:
285
313
  data_row_count = _append_data_row(rows, data_row_count, condition_rows[start + 3], ".jog()")
286
314
  else:
287
- data_row_count = _emit_blank_continuation(rows, data_row_count, condition_rows[start + 3])
315
+ data_row_count = _emit_blank_continuation(
316
+ rows, data_row_count, condition_rows[start + 3], keep
317
+ )
288
318
  return data_row_count, 4
289
319
 
290
320
 
@@ -304,6 +334,7 @@ def _emit_generic_tall_block(
304
334
  f"{type(af).__name__} requires {needed} decoded rows; got {len(condition_rows)}"
305
335
  )
306
336
 
337
+ keep = _followed_by_floating_row(af_tokens, start + visual_rows)
307
338
  data_row_count = _append_data_row(rows, data_row_count, condition_rows[start], af)
308
339
  for offset in range(1, visual_rows):
309
340
  _require_blank_af(
@@ -311,7 +342,7 @@ def _emit_generic_tall_block(
311
342
  message=f"{type(af).__name__} continuation row must be blank AF",
312
343
  )
313
344
  data_row_count = _emit_blank_continuation(
314
- rows, data_row_count, condition_rows[start + offset]
345
+ rows, data_row_count, condition_rows[start + offset], keep
315
346
  )
316
347
  return data_row_count, visual_rows
317
348
 
@@ -498,6 +529,8 @@ def decoded_rung_to_rows(rung: Rung) -> list[list[str]]:
498
529
  def write_csv(
499
530
  path: Path | str,
500
531
  rungs: list[Rung],
532
+ *,
533
+ index: bool = False,
501
534
  ) -> None:
502
535
  """Write decoded rungs to a canonical CSV file.
503
536
 
@@ -508,6 +541,12 @@ def write_csv(
508
541
  rungs:
509
542
  One or more decoded rungs (from ``decode_rung()`` or
510
543
  ``decode_rungs()``).
544
+ index:
545
+ When ``True``, the per-rung ``R`` marker is replaced with a 1-based
546
+ sequential index (``R1``, ``R2``, ``R3``, ...) counting across the
547
+ whole file. The default (``False``) emits a plain ``R``. Indexed
548
+ files still round-trip through ``read_csv()`` — the digits are
549
+ decorative and ignored on read.
511
550
 
512
551
  Raises
513
552
  ------
@@ -518,6 +557,8 @@ def write_csv(
518
557
  with open(path, "w", newline="", encoding="utf-8") as f:
519
558
  writer = csv_mod.writer(f)
520
559
  writer.writerow(CSV_HEADER)
521
- for rung in rungs:
560
+ for rung_number, rung in enumerate(rungs, start=1): # start=1 → first rung is R1
522
561
  for row in decoded_rung_to_rows(rung):
562
+ if index and row and row[0] == "R":
563
+ row = [f"R{rung_number}", *row[1:]]
523
564
  writer.writerow(row)