oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,6 @@
3
3
  Built-in command implementations for DSL.
4
4
  """
5
5
 
6
- import sys
7
6
  from pathlib import Path
8
7
  from typing import Any
9
8
 
@@ -27,33 +26,14 @@ def cmd_load(filename: str) -> Any:
27
26
  if not path.exists():
28
27
  raise OscuraError(f"File not found: {filename}")
29
28
 
30
- # Determine loader based on extension
31
- ext = path.suffix.lower()
32
-
29
+ # Use the unified loader
33
30
  try:
34
- if ext == ".csv":
35
- from oscura.loaders.csv import ( # type: ignore[import-not-found]
36
- load_csv, # type: ignore[import-not-found]
37
- )
38
-
39
- return load_csv(str(path))
40
- elif ext == ".bin":
41
- from oscura.loaders.binary import ( # type: ignore[import-not-found]
42
- load_binary, # type: ignore[import-not-found]
43
- )
44
-
45
- return load_binary(str(path))
46
- elif ext in (".h5", ".hdf5"):
47
- from oscura.loaders.hdf5 import ( # type: ignore[import-not-found]
48
- load_hdf5, # type: ignore[import-not-found]
49
- )
50
-
51
- return load_hdf5(str(path))
52
- else:
53
- raise OscuraError(f"Unsupported file format: {ext}")
31
+ from oscura.loaders import load
32
+
33
+ return load(str(path))
54
34
 
55
35
  except ImportError as e:
56
- raise OscuraError(f"Loader not available for {ext}: {e}") # noqa: B904
36
+ raise OscuraError(f"Loader not available: {e}")
57
37
 
58
38
 
59
39
  def cmd_filter(trace: Any, filter_type: str, *args: Any, **kwargs: Any) -> Any:
@@ -72,41 +52,41 @@ def cmd_filter(trace: Any, filter_type: str, *args: Any, **kwargs: Any) -> Any:
72
52
  OscuraError: If filter cannot be applied
73
53
  """
74
54
  try:
75
- from oscura.filtering import filters # type: ignore[attr-defined]
55
+ from oscura.utils import filtering
76
56
 
77
57
  if filter_type.lower() == "lowpass":
78
58
  if len(args) < 1:
79
59
  raise OscuraError("lowpass filter requires cutoff frequency")
80
- return filters.low_pass(trace, cutoff=args[0], **kwargs)
60
+ return filtering.low_pass(trace, cutoff=args[0], **kwargs)
81
61
 
82
62
  elif filter_type.lower() == "highpass":
83
63
  if len(args) < 1:
84
64
  raise OscuraError("highpass filter requires cutoff frequency")
85
- return filters.high_pass(trace, cutoff=args[0], **kwargs)
65
+ return filtering.high_pass(trace, cutoff=args[0], **kwargs)
86
66
 
87
67
  elif filter_type.lower() == "bandpass":
88
68
  if len(args) < 2:
89
69
  raise OscuraError("bandpass filter requires low and high cutoff frequencies")
90
- return filters.band_pass(trace, low=args[0], high=args[1], **kwargs)
70
+ return filtering.band_pass(trace, low=args[0], high=args[1], **kwargs)
91
71
 
92
72
  elif filter_type.lower() == "bandstop":
93
73
  if len(args) < 2:
94
74
  raise OscuraError("bandstop filter requires low and high cutoff frequencies")
95
- return filters.band_stop(trace, low=args[0], high=args[1], **kwargs)
75
+ return filtering.band_stop(trace, low=args[0], high=args[1], **kwargs)
96
76
 
97
77
  else:
98
78
  raise OscuraError(f"Unknown filter type: {filter_type}")
99
79
 
100
80
  except ImportError:
101
- raise OscuraError("Filtering module not available") # noqa: B904
81
+ raise OscuraError("Filtering module not available")
102
82
 
103
83
 
104
- def cmd_measure(trace: Any, *measurements: str) -> Any:
84
+ def cmd_measure(trace: Any, *measurement_names: str) -> Any:
105
85
  """Measure properties of trace.
106
86
 
107
87
  Args:
108
88
  trace: Input trace
109
- *measurements: Measurement names (rise_time, fall_time, etc.)
89
+ *measurement_names: Measurement names (rise_time, fall_time, etc.)
110
90
 
111
91
  Returns:
112
92
  Measurement results (single value or dict)
@@ -115,38 +95,36 @@ def cmd_measure(trace: Any, *measurements: str) -> Any:
115
95
  OscuraError: If measurement cannot be performed
116
96
  """
117
97
  try:
118
- from oscura.analyzers import ( # type: ignore[attr-defined]
119
- measurements as meas, # type: ignore[attr-defined]
120
- )
98
+ from oscura.analyzers import measurements
121
99
 
122
- if len(measurements) == 0:
100
+ if len(measurement_names) == 0:
123
101
  raise OscuraError("measure command requires at least one measurement name")
124
102
 
125
103
  results = {}
126
104
 
127
- for measurement in measurements:
128
- meas_name = measurement.lower()
105
+ for measurement_name in measurement_names:
106
+ meas_name = measurement_name.lower()
129
107
 
130
108
  if meas_name == "rise_time":
131
- results["rise_time"] = meas.rise_time(trace)
109
+ results["rise_time"] = measurements.rise_time(trace)
132
110
  elif meas_name == "fall_time":
133
- results["fall_time"] = meas.fall_time(trace)
111
+ results["fall_time"] = measurements.fall_time(trace)
134
112
  elif meas_name == "period":
135
- results["period"] = meas.period(trace)
113
+ results["period"] = measurements.period(trace)
136
114
  elif meas_name == "frequency":
137
- results["frequency"] = meas.frequency(trace)
115
+ results["frequency"] = measurements.frequency(trace)
138
116
  elif meas_name == "amplitude":
139
- results["amplitude"] = meas.amplitude(trace)
117
+ results["amplitude"] = measurements.amplitude(trace)
140
118
  elif meas_name == "mean":
141
- results["mean"] = meas.mean(trace)
119
+ results["mean"] = measurements.mean(trace)
142
120
  elif meas_name == "rms":
143
- results["rms"] = meas.rms(trace)
121
+ results["rms"] = measurements.rms(trace)
144
122
  elif meas_name == "all":
145
123
  # Measure all available measurements
146
- results = meas.measure_all(trace)
124
+ results = measurements.measure(trace, parameters=None)
147
125
  break
148
126
  else:
149
- raise OscuraError(f"Unknown measurement: {measurement}")
127
+ raise OscuraError(f"Unknown measurement: {measurement_name}")
150
128
 
151
129
  # Return single value if only one measurement
152
130
  if len(results) == 1:
@@ -155,7 +133,7 @@ def cmd_measure(trace: Any, *measurements: str) -> Any:
155
133
  return results
156
134
 
157
135
  except ImportError:
158
- raise OscuraError("Measurements module not available") # noqa: B904
136
+ raise OscuraError("Measurements module not available")
159
137
 
160
138
 
161
139
  def cmd_plot(trace: Any, **options: Any) -> None:
@@ -169,22 +147,23 @@ def cmd_plot(trace: Any, **options: Any) -> None:
169
147
  OscuraError: If plotting fails
170
148
  """
171
149
  try:
172
- from oscura.visualization import ( # type: ignore[attr-defined]
173
- plot as plot_module, # type: ignore[attr-defined]
174
- )
150
+ from oscura.visualization import plot
175
151
 
176
152
  title = options.get("title", "Trace Plot")
177
153
  annotate = options.get("annotate")
178
154
 
179
- plot_module.plot_trace(trace, title=title)
155
+ plot.plot_trace(trace, title=title)
180
156
 
181
157
  if annotate:
182
- plot_module.add_annotation(annotate)
158
+ plot.add_annotation(annotate)
159
+
160
+ # Import matplotlib.pyplot for show()
161
+ import matplotlib.pyplot as plt
183
162
 
184
- plot_module.show()
163
+ plt.show()
185
164
 
186
165
  except ImportError:
187
- raise OscuraError("Visualization module not available") # noqa: B904
166
+ raise OscuraError("Visualization module not available")
188
167
 
189
168
 
190
169
  def cmd_export(data: Any, format_type: str, filename: str | None = None) -> None:
@@ -199,26 +178,14 @@ def cmd_export(data: Any, format_type: str, filename: str | None = None) -> None
199
178
  OscuraError: If export fails
200
179
  """
201
180
  try:
202
- from oscura.exporters import exporters # type: ignore[attr-defined]
203
-
204
- if filename is None:
205
- filename = f"export.{format_type}"
206
-
207
- fmt = format_type.lower()
208
-
209
- if fmt == "json":
210
- exporters.json(data, filename)
211
- elif fmt == "csv":
212
- exporters.csv(data, filename)
213
- elif fmt in ("h5", "hdf5"):
214
- exporters.hdf5(data, filename)
215
- else:
216
- raise OscuraError(f"Unknown export format: {format_type}")
217
-
218
- print(f"Exported to {filename}", file=sys.stderr)
219
-
181
+ # Export functionality has been redesigned
182
+ # Use oscura.export.* modules for protocol export
183
+ raise NotImplementedError(
184
+ "Data export has been redesigned. Use oscura.export.wireshark, "
185
+ "oscura.export.kaitai_struct, or oscura.export.scapy_layer for protocol exports."
186
+ )
220
187
  except ImportError:
221
- raise OscuraError("Export module not available") # noqa: B904
188
+ raise OscuraError("Export module not available")
222
189
 
223
190
 
224
191
  def cmd_glob(pattern: str) -> list[str]:
@@ -232,7 +199,7 @@ def cmd_glob(pattern: str) -> list[str]:
232
199
  """
233
200
  from glob import glob as glob_func
234
201
 
235
- return list(glob_func(pattern)) # noqa: PTH207
202
+ return list(glob_func(pattern))
236
203
 
237
204
 
238
205
  # Command registry
@@ -9,7 +9,7 @@ from collections.abc import Callable
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
- from oscura.dsl.parser import (
12
+ from oscura.api.dsl.parser import (
13
13
  Assignment,
14
14
  Command,
15
15
  Expression,
@@ -75,9 +75,9 @@ class Interpreter:
75
75
  return load(str(path))
76
76
 
77
77
  except ImportError as e:
78
- raise InterpreterError(f"oscura.loaders not available: {e}") # noqa: B904
78
+ raise InterpreterError(f"oscura.loaders not available: {e}")
79
79
  except Exception as e:
80
- raise InterpreterError(f"Failed to load {filename}: {e}") # noqa: B904
80
+ raise InterpreterError(f"Failed to load {filename}: {e}")
81
81
 
82
82
  def _cmd_filter(self, trace: Any, *args: Any) -> Any:
83
83
  """Filter command: filter lowpass 1000."""
@@ -90,7 +90,7 @@ class Interpreter:
90
90
 
91
91
  # Import filter functions
92
92
  try:
93
- from oscura import filtering
93
+ from oscura.utils import filtering
94
94
 
95
95
  # Note: bandpass and bandstop have different signatures (low, high)
96
96
  # but we treat them as Any -> Any for DSL simplicity
@@ -119,9 +119,9 @@ class Interpreter:
119
119
  return filter_func(trace, cutoff)
120
120
 
121
121
  except ImportError as e:
122
- raise InterpreterError(f"oscura.filtering not available: {e}") # noqa: B904
122
+ raise InterpreterError(f"oscura.filtering not available: {e}")
123
123
  except Exception as e:
124
- raise InterpreterError(f"Filter failed: {e}") # noqa: B904
124
+ raise InterpreterError(f"Filter failed: {e}")
125
125
 
126
126
  def _cmd_measure(self, trace: Any, *args: Any) -> Any:
127
127
  """Measure command: measure rise_time."""
@@ -161,11 +161,11 @@ class Interpreter:
161
161
  return measure_func(trace)
162
162
 
163
163
  except ImportError as e:
164
- raise InterpreterError(f"oscura measurements not available: {e}") # noqa: B904
164
+ raise InterpreterError(f"oscura measurements not available: {e}")
165
165
  except AttributeError as e:
166
- raise InterpreterError(f"Measurement function not found: {e}") # noqa: B904
166
+ raise InterpreterError(f"Measurement function not found: {e}")
167
167
  except Exception as e:
168
- raise InterpreterError(f"Measurement failed: {e}") # noqa: B904
168
+ raise InterpreterError(f"Measurement failed: {e}")
169
169
 
170
170
  def _cmd_plot(self, trace: Any, *args: Any) -> Any:
171
171
  """Plot command: plot."""
@@ -191,9 +191,9 @@ class Interpreter:
191
191
  raise InterpreterError(f"Unknown plot type: {plot_type}. Available: waveform")
192
192
 
193
193
  except ImportError as e:
194
- raise InterpreterError(f"oscura.visualization not available: {e}") # noqa: B904
194
+ raise InterpreterError(f"oscura.visualization not available: {e}")
195
195
  except Exception as e:
196
- raise InterpreterError(f"Plot failed: {e}") # noqa: B904
196
+ raise InterpreterError(f"Plot failed: {e}")
197
197
 
198
198
  def _cmd_export(self, data: Any, *args: Any) -> Any:
199
199
  """Export command: export json "output.json"."""
@@ -211,37 +211,11 @@ class Interpreter:
211
211
  if not isinstance(filename, str):
212
212
  raise InterpreterError("export filename must be a string")
213
213
 
214
- # Import export functions
215
- try:
216
- from oscura.exporters import csv as csv_exporter
217
- from oscura.exporters import json_export
218
-
219
- export_map: dict[str, Any] = {
220
- "csv": csv_exporter.export_csv,
221
- "json": json_export.export_json,
222
- }
223
-
224
- if format_type not in export_map:
225
- raise InterpreterError(
226
- f"Unknown export format: {format_type}. "
227
- f"Available: {', '.join(export_map.keys())}"
228
- )
229
-
230
- # Export data
231
- export_func = export_map[format_type]
232
- if filename:
233
- export_func(data, filename)
234
- return filename
235
- else:
236
- # Generate default filename
237
- default_name = f"export.{format_type}"
238
- export_func(data, default_name)
239
- return default_name
240
-
241
- except ImportError as e:
242
- raise InterpreterError(f"oscura.export not available: {e}") # noqa: B904
243
- except Exception as e:
244
- raise InterpreterError(f"Export failed: {e}") # noqa: B904
214
+ # Export functionality has been redesigned
215
+ raise InterpreterError(
216
+ "Data export has been redesigned. Use oscura.export.wireshark, "
217
+ "oscura.export.kaitai_struct, or oscura.export.scapy_layer for protocol exports."
218
+ )
245
219
 
246
220
  def _cmd_glob(self, pattern: str) -> list[str]:
247
221
  """Glob command: glob("*.csv")."""
@@ -250,7 +224,7 @@ class Interpreter:
250
224
 
251
225
  from glob import glob as glob_func
252
226
 
253
- return list(glob_func(pattern)) # noqa: PTH207
227
+ return list(glob_func(pattern))
254
228
 
255
229
  def eval_expression(self, expr: Expression) -> Any:
256
230
  """Evaluate an expression.
@@ -356,12 +330,15 @@ class Interpreter:
356
330
 
357
331
  return result
358
332
 
359
- def eval_statement(self, stmt: Statement) -> None:
333
+ def eval_statement(self, stmt: Statement) -> Any:
360
334
  """Execute a statement.
361
335
 
362
336
  Args:
363
337
  stmt: Statement AST node
364
338
 
339
+ Returns:
340
+ Result of statement execution (None for Assignment and ForLoop, value for expressions)
341
+
365
342
  Raises:
366
343
  InterpreterError: On execution errors
367
344
  """
@@ -369,26 +346,24 @@ class Interpreter:
369
346
  if isinstance(stmt, Assignment):
370
347
  value = self.eval_expression(stmt.expression)
371
348
  self.variables[stmt.variable] = value
372
- return
349
+ return None
373
350
 
374
351
  # For loop
375
352
  if isinstance(stmt, ForLoop):
376
353
  self.eval_for_loop(stmt)
377
- return
354
+ return None
378
355
 
379
356
  # Expression statement (pipeline)
380
357
  if isinstance(stmt, Pipeline):
381
- self.eval_pipeline(stmt)
382
- return
358
+ return self.eval_pipeline(stmt)
383
359
 
384
360
  # Function call as statement (e.g., in for loop body: process($f))
385
361
  if isinstance(stmt, FunctionCall):
386
- self.eval_function_call(stmt)
387
- return
362
+ return self.eval_function_call(stmt)
388
363
 
389
364
  # All Statement types covered above (Assignment, ForLoop, Pipeline, FunctionCall)
390
365
  # This line is unreachable if type system is correct, but kept for runtime safety
391
- raise InterpreterError(f"Unknown statement type: {type(stmt).__name__}") # type: ignore[unreachable]
366
+ raise InterpreterError(f"Unknown statement type: {type(stmt).__name__}")
392
367
 
393
368
  def eval_for_loop(self, loop: ForLoop) -> None:
394
369
  """Execute for loop.
@@ -61,7 +61,7 @@ class Lexer:
61
61
  Supports indentation-based block structure (Python-style).
62
62
  """
63
63
 
64
- KEYWORDS = { # noqa: RUF012
64
+ KEYWORDS = {
65
65
  "load": TokenType.LOAD,
66
66
  "filter": TokenType.FILTER,
67
67
  "measure": TokenType.MEASURE,
@@ -198,10 +198,12 @@ class Lexer:
198
198
  def read_identifier(self) -> str:
199
199
  """Read identifier or keyword."""
200
200
  chars = []
201
- while self.current_char() and (self.current_char().isalnum() or self.current_char() in "_"): # type: ignore[union-attr, operator, syntax, operator]
202
- chars.append(self.current_char())
201
+ current = self.current_char()
202
+ while current and (current.isalnum() or current in "_"):
203
+ chars.append(current)
203
204
  self.advance()
204
- return "".join(chars) # type: ignore[arg-type]
205
+ current = self.current_char()
206
+ return "".join(chars)
205
207
 
206
208
  def read_variable(self) -> str:
207
209
  """Read variable name ($varname)."""
@@ -246,22 +248,8 @@ class Lexer:
246
248
  SyntaxError: On lexical errors
247
249
  """
248
250
  while self.pos < len(self.text):
249
- # Handle indentation at line start
250
- if self.at_line_start:
251
- self.at_line_start = False
252
- indent = self.measure_indent()
253
-
254
- # Skip blank/comment lines
255
- if indent == -1:
256
- self.skip_whitespace()
257
- self.skip_comment()
258
- if self.current_char() == "\n":
259
- self.advance()
260
- continue
261
- elif self.current_char() is None:
262
- break
263
- else:
264
- self.emit_indent_tokens(indent)
251
+ if not self._process_line_start():
252
+ break
265
253
 
266
254
  self.skip_whitespace()
267
255
  self.skip_comment()
@@ -269,76 +257,118 @@ class Lexer:
269
257
  if not self.current_char():
270
258
  break
271
259
 
272
- line, col = self.line, self.column
273
- char = self.current_char()
274
-
275
- # Newline
276
- if char == "\n":
277
- self.tokens.append(Token(TokenType.NEWLINE, "\n", line, col))
278
- self.advance()
279
-
280
- # String
281
- elif char in "\"'": # type: ignore[operator]
282
- value = self.read_string()
283
- self.tokens.append(Token(TokenType.STRING, value, line, col))
260
+ self._process_token()
284
261
 
285
- # Number
286
- elif char.isdigit() or ( # type: ignore[union-attr]
287
- char == "." and self.peek_char() and self.peek_char().isdigit() # type: ignore[union-attr]
288
- ):
289
- value = self.read_number() # type: ignore[assignment]
290
- self.tokens.append(Token(TokenType.NUMBER, value, line, col))
291
-
292
- # Variable
293
- elif char == "$":
294
- value = self.read_variable()
295
- self.tokens.append(Token(TokenType.VARIABLE, value, line, col))
262
+ self._finalize_tokens()
263
+ return self.tokens
296
264
 
297
- # Pipe
298
- elif char == "|":
299
- self.tokens.append(Token(TokenType.PIPE, "|", line, col))
300
- self.advance()
265
+ def _process_line_start(self) -> bool:
266
+ """Process indentation at line start.
301
267
 
302
- # Assignment
303
- elif char == "=":
304
- self.tokens.append(Token(TokenType.ASSIGN, "=", line, col))
305
- self.advance()
268
+ Returns:
269
+ False if EOF reached, True otherwise.
270
+ """
271
+ if self.at_line_start:
272
+ self.at_line_start = False
273
+ indent = self.measure_indent()
306
274
 
307
- # Comma
308
- elif char == ",":
309
- self.tokens.append(Token(TokenType.COMMA, ",", line, col))
310
- self.advance()
275
+ if indent == -1:
276
+ return self._skip_blank_line()
277
+ else:
278
+ self.emit_indent_tokens(indent)
279
+ return True
311
280
 
312
- # Colon
313
- elif char == ":":
314
- self.tokens.append(Token(TokenType.COLON, ":", line, col))
315
- self.advance()
281
+ def _skip_blank_line(self) -> bool:
282
+ """Skip blank or comment-only line.
316
283
 
317
- # Parentheses
318
- elif char == "(":
319
- self.tokens.append(Token(TokenType.LPAREN, "(", line, col))
320
- self.advance()
321
- elif char == ")":
322
- self.tokens.append(Token(TokenType.RPAREN, ")", line, col))
323
- self.advance()
284
+ Returns:
285
+ False if EOF, True otherwise.
286
+ """
287
+ self.skip_whitespace()
288
+ self.skip_comment()
289
+ if self.current_char() == "\n":
290
+ self.advance()
291
+ return True
292
+ return self.current_char() is not None
293
+
294
+ def _process_token(self) -> None:
295
+ """Process and emit next token."""
296
+ line, col = self.line, self.column
297
+ char = self.current_char()
298
+
299
+ if char == "\n":
300
+ self._emit_newline(line, col)
301
+ elif char in "\"'": # type: ignore[operator]
302
+ self._emit_string(line, col)
303
+ elif self._is_number_start(char):
304
+ self._emit_number(line, col)
305
+ elif char == "$":
306
+ self._emit_variable(line, col)
307
+ elif self._is_single_char_token(char):
308
+ self._emit_single_char(char, line, col)
309
+ elif char.isalpha() or char == "_": # type: ignore[union-attr]
310
+ self._emit_identifier(line, col)
311
+ else:
312
+ raise SyntaxError(f"Unexpected character '{char}' at line {line}, column {col}")
313
+
314
+ def _is_number_start(self, char: str | None) -> bool:
315
+ """Check if character starts a number."""
316
+ if not char:
317
+ return False
318
+ next_char = self.peek_char()
319
+ return char.isdigit() or (char == "." and next_char is not None and next_char.isdigit())
320
+
321
+ def _is_single_char_token(self, char: str | None) -> bool:
322
+ """Check if character is a single-character token."""
323
+ if char is None:
324
+ return False
325
+ return char in "|=,:()"
326
+
327
+ def _emit_newline(self, line: int, col: int) -> None:
328
+ """Emit newline token."""
329
+ self.tokens.append(Token(TokenType.NEWLINE, "\n", line, col))
330
+ self.advance()
324
331
 
325
- # Identifier or keyword
326
- elif char.isalpha() or char == "_": # type: ignore[union-attr]
327
- ident = self.read_identifier()
328
- token_type = self.KEYWORDS.get(ident.lower(), TokenType.IDENTIFIER)
329
- self.tokens.append(Token(token_type, ident, line, col))
332
+ def _emit_string(self, line: int, col: int) -> None:
333
+ """Emit string token."""
334
+ value = self.read_string()
335
+ self.tokens.append(Token(TokenType.STRING, value, line, col))
336
+
337
+ def _emit_number(self, line: int, col: int) -> None:
338
+ """Emit number token."""
339
+ value = self.read_number()
340
+ self.tokens.append(Token(TokenType.NUMBER, value, line, col))
341
+
342
+ def _emit_variable(self, line: int, col: int) -> None:
343
+ """Emit variable token."""
344
+ value = self.read_variable()
345
+ self.tokens.append(Token(TokenType.VARIABLE, value, line, col))
346
+
347
+ def _emit_single_char(self, char: str | None, line: int, col: int) -> None:
348
+ """Emit single-character token."""
349
+ token_map = {
350
+ "|": TokenType.PIPE,
351
+ "=": TokenType.ASSIGN,
352
+ ",": TokenType.COMMA,
353
+ ":": TokenType.COLON,
354
+ "(": TokenType.LPAREN,
355
+ ")": TokenType.RPAREN,
356
+ }
357
+ self.tokens.append(Token(token_map[char], char, line, col)) # type: ignore[index]
358
+ self.advance()
330
359
 
331
- else:
332
- raise SyntaxError(f"Unexpected character '{char}' at line {line}, column {col}")
360
+ def _emit_identifier(self, line: int, col: int) -> None:
361
+ """Emit identifier or keyword token."""
362
+ ident = self.read_identifier()
363
+ token_type = self.KEYWORDS.get(ident.lower(), TokenType.IDENTIFIER)
364
+ self.tokens.append(Token(token_type, ident, line, col))
333
365
 
334
- # Emit remaining DEDENTs at end of file
366
+ def _finalize_tokens(self) -> None:
367
+ """Emit remaining DEDENT and EOF tokens."""
335
368
  while len(self.indent_stack) > 1:
336
369
  self.indent_stack.pop()
337
370
  self.tokens.append(Token(TokenType.DEDENT, 0, self.line, self.column))
338
-
339
- # Add EOF token
340
371
  self.tokens.append(Token(TokenType.EOF, None, self.line, self.column))
341
- return self.tokens
342
372
 
343
373
 
344
374
  @dataclass
@@ -5,8 +5,8 @@ Interactive shell for Oscura DSL.
5
5
 
6
6
  import sys
7
7
 
8
- from oscura.dsl.interpreter import Interpreter, InterpreterError
9
- from oscura.dsl.parser import parse_dsl
8
+ from oscura.api.dsl.interpreter import Interpreter, InterpreterError
9
+ from oscura.api.dsl.parser import parse_dsl
10
10
 
11
11
 
12
12
  class REPL:
oscura/api/dsl.py CHANGED
@@ -78,7 +78,7 @@ class DSLParser:
78
78
  API-010: Domain-Specific Language (DSL)
79
79
  """
80
80
 
81
- OPERATIONS = { # noqa: RUF012
81
+ OPERATIONS = {
82
82
  "load",
83
83
  "save",
84
84
  "export",
@@ -3,7 +3,7 @@
3
3
  Third-party and external system integrations.
4
4
  """
5
5
 
6
- from oscura.integrations.llm import (
6
+ from oscura.api.integrations.llm import (
7
7
  AnalysisHook,
8
8
  CostTracker,
9
9
  FailoverLLMClient,