oscura 0.0.1__py3-none-any.whl → 0.1.1__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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/dsl/parser.py ADDED
@@ -0,0 +1,689 @@
1
+ """Oscura DSL Parser.
2
+
3
+ Implements simple domain-specific language for trace analysis workflows.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from enum import Enum, auto
8
+ from typing import Any, Union
9
+
10
+
11
+ class TokenType(Enum):
12
+ """Token types for DSL lexer."""
13
+
14
+ # Literals
15
+ STRING = auto()
16
+ NUMBER = auto()
17
+ VARIABLE = auto()
18
+ IDENTIFIER = auto()
19
+
20
+ # Operators
21
+ PIPE = auto()
22
+ ASSIGN = auto()
23
+ COMMA = auto()
24
+
25
+ # Keywords
26
+ LOAD = auto()
27
+ FILTER = auto()
28
+ MEASURE = auto()
29
+ PLOT = auto()
30
+ EXPORT = auto()
31
+ FOR = auto()
32
+ IN = auto()
33
+ GLOB = auto()
34
+
35
+ # Structural
36
+ LPAREN = auto()
37
+ RPAREN = auto()
38
+ COLON = auto()
39
+ NEWLINE = auto()
40
+ INDENT = auto()
41
+ DEDENT = auto()
42
+ EOF = auto()
43
+
44
+
45
+ @dataclass
46
+ class Token:
47
+ """Lexical token."""
48
+
49
+ type: TokenType
50
+ value: Any
51
+ line: int
52
+ column: int
53
+
54
+
55
+ class Lexer:
56
+ """Tokenizer for Oscura DSL.
57
+
58
+ Breaks input text into tokens for parsing.
59
+ Supports indentation-based block structure (Python-style).
60
+ """
61
+
62
+ KEYWORDS = { # noqa: RUF012
63
+ "load": TokenType.LOAD,
64
+ "filter": TokenType.FILTER,
65
+ "measure": TokenType.MEASURE,
66
+ "plot": TokenType.PLOT,
67
+ "export": TokenType.EXPORT,
68
+ "for": TokenType.FOR,
69
+ "in": TokenType.IN,
70
+ "glob": TokenType.GLOB,
71
+ }
72
+
73
+ def __init__(self, text: str):
74
+ """Initialize lexer with input text.
75
+
76
+ Args:
77
+ text: DSL source code
78
+ """
79
+ self.text = text
80
+ self.pos = 0
81
+ self.line = 1
82
+ self.column = 1
83
+ self.tokens: list[Token] = []
84
+ # Indentation tracking
85
+ self.indent_stack: list[int] = [0]
86
+ self.at_line_start = True
87
+
88
+ def current_char(self) -> str | None:
89
+ """Get current character without advancing."""
90
+ if self.pos >= len(self.text):
91
+ return None
92
+ return self.text[self.pos]
93
+
94
+ def peek_char(self, offset: int = 1) -> str | None:
95
+ """Peek ahead at character."""
96
+ pos = self.pos + offset
97
+ if pos >= len(self.text):
98
+ return None
99
+ return self.text[pos]
100
+
101
+ def advance(self) -> None:
102
+ """Advance position and update line/column."""
103
+ if self.pos < len(self.text) and self.text[self.pos] == "\n":
104
+ self.line += 1
105
+ self.column = 1
106
+ self.at_line_start = True
107
+ else:
108
+ self.column += 1
109
+ self.pos += 1
110
+
111
+ def skip_whitespace(self) -> None:
112
+ """Skip whitespace except newlines."""
113
+ while self.current_char() and self.current_char() in " \t\r": # type: ignore[operator]
114
+ self.advance()
115
+
116
+ def skip_comment(self) -> None:
117
+ """Skip # comment to end of line."""
118
+ if self.current_char() == "#":
119
+ while self.current_char() and self.current_char() != "\n":
120
+ self.advance()
121
+
122
+ def measure_indent(self) -> int:
123
+ """Measure indentation at current position (after newline).
124
+
125
+ Returns:
126
+ Number of spaces of indentation (tabs count as 4 spaces)
127
+ """
128
+ indent = 0
129
+ start_pos = self.pos
130
+
131
+ while self.current_char() and self.current_char() in " \t": # type: ignore[operator]
132
+ if self.current_char() == " ":
133
+ indent += 1
134
+ elif self.current_char() == "\t":
135
+ indent += 4 # Tab = 4 spaces
136
+ self.pos += 1
137
+ self.column += 1
138
+
139
+ # Check if rest of line is blank or comment
140
+ if self.current_char() == "#" or self.current_char() == "\n" or self.current_char() is None:
141
+ # Blank line or comment-only line - reset position and return -1
142
+ self.pos = start_pos
143
+ self.column = 1
144
+ return -1 # Signal to ignore this line for indentation
145
+
146
+ return indent
147
+
148
+ def read_string(self) -> str:
149
+ """Read quoted string literal."""
150
+ quote_char = self.current_char()
151
+ self.advance() # Skip opening quote
152
+
153
+ chars = []
154
+ while self.current_char() and self.current_char() != quote_char:
155
+ if self.current_char() == "\\":
156
+ self.advance()
157
+ # Simple escape sequences
158
+ escape_map = {"n": "\n", "t": "\t", "r": "\r", "\\": "\\", '"': '"', "'": "'"}
159
+ if self.current_char() in escape_map:
160
+ chars.append(escape_map[self.current_char()]) # type: ignore[index]
161
+ else:
162
+ chars.append(self.current_char() or "")
163
+ else:
164
+ chars.append(self.current_char() or "")
165
+ self.advance()
166
+
167
+ if not self.current_char():
168
+ raise SyntaxError(f"Unterminated string at line {self.line}")
169
+
170
+ self.advance() # Skip closing quote
171
+ return "".join(chars)
172
+
173
+ def read_number(self) -> int | float:
174
+ """Read numeric literal."""
175
+ chars = []
176
+ has_dot = False
177
+ has_exp = False
178
+
179
+ while self.current_char() and (
180
+ self.current_char().isdigit() or self.current_char() in ".eE+-" # type: ignore[union-attr, operator]
181
+ ):
182
+ if self.current_char() == ".":
183
+ if has_dot:
184
+ break
185
+ has_dot = True
186
+ elif self.current_char() in "eE": # type: ignore[operator]
187
+ if has_exp:
188
+ break
189
+ has_exp = True
190
+ chars.append(self.current_char())
191
+ self.advance()
192
+
193
+ num_str = "".join(chars) # type: ignore[arg-type]
194
+ return float(num_str) if has_dot or has_exp else int(num_str)
195
+
196
+ def read_identifier(self) -> str:
197
+ """Read identifier or keyword."""
198
+ chars = []
199
+ while self.current_char() and (self.current_char().isalnum() or self.current_char() in "_"): # type: ignore[union-attr, operator, syntax, operator]
200
+ chars.append(self.current_char())
201
+ self.advance()
202
+ return "".join(chars) # type: ignore[arg-type]
203
+
204
+ def read_variable(self) -> str:
205
+ """Read variable name ($varname)."""
206
+ self.advance() # Skip $
207
+ return "$" + self.read_identifier()
208
+
209
+ def emit_indent_tokens(self, indent: int) -> None:
210
+ """Emit INDENT/DEDENT tokens based on indentation change.
211
+
212
+ Args:
213
+ indent: Current line's indentation level
214
+
215
+ Raises:
216
+ SyntaxError: If indentation is inconsistent.
217
+ """
218
+ current_indent = self.indent_stack[-1]
219
+
220
+ if indent > current_indent:
221
+ # Increased indentation
222
+ self.indent_stack.append(indent)
223
+ self.tokens.append(Token(TokenType.INDENT, indent, self.line, 1))
224
+ elif indent < current_indent:
225
+ # Decreased indentation - may need multiple DEDENTs
226
+ while self.indent_stack and indent < self.indent_stack[-1]:
227
+ self.indent_stack.pop()
228
+ self.tokens.append(Token(TokenType.DEDENT, indent, self.line, 1))
229
+
230
+ # Check for inconsistent indentation
231
+ if self.indent_stack and indent != self.indent_stack[-1]:
232
+ raise SyntaxError(
233
+ f"Inconsistent indentation at line {self.line}: "
234
+ f"got {indent} spaces, expected {self.indent_stack[-1]}"
235
+ )
236
+
237
+ def tokenize(self) -> list[Token]:
238
+ """Tokenize entire input.
239
+
240
+ Returns:
241
+ List of tokens
242
+
243
+ Raises:
244
+ SyntaxError: On lexical errors
245
+ """
246
+ while self.pos < len(self.text):
247
+ # Handle indentation at line start
248
+ if self.at_line_start:
249
+ self.at_line_start = False
250
+ indent = self.measure_indent()
251
+
252
+ # Skip blank/comment lines
253
+ if indent == -1:
254
+ self.skip_whitespace()
255
+ self.skip_comment()
256
+ if self.current_char() == "\n":
257
+ self.advance()
258
+ continue
259
+ elif self.current_char() is None:
260
+ break
261
+ else:
262
+ self.emit_indent_tokens(indent)
263
+
264
+ self.skip_whitespace()
265
+ self.skip_comment()
266
+
267
+ if not self.current_char():
268
+ break
269
+
270
+ line, col = self.line, self.column
271
+ char = self.current_char()
272
+
273
+ # Newline
274
+ if char == "\n":
275
+ self.tokens.append(Token(TokenType.NEWLINE, "\n", line, col))
276
+ self.advance()
277
+
278
+ # String
279
+ elif char in "\"'": # type: ignore[operator]
280
+ value = self.read_string()
281
+ self.tokens.append(Token(TokenType.STRING, value, line, col))
282
+
283
+ # Number
284
+ elif char.isdigit() or ( # type: ignore[union-attr]
285
+ char == "." and self.peek_char() and self.peek_char().isdigit() # type: ignore[union-attr]
286
+ ):
287
+ value = self.read_number() # type: ignore[assignment]
288
+ self.tokens.append(Token(TokenType.NUMBER, value, line, col))
289
+
290
+ # Variable
291
+ elif char == "$":
292
+ value = self.read_variable()
293
+ self.tokens.append(Token(TokenType.VARIABLE, value, line, col))
294
+
295
+ # Pipe
296
+ elif char == "|":
297
+ self.tokens.append(Token(TokenType.PIPE, "|", line, col))
298
+ self.advance()
299
+
300
+ # Assignment
301
+ elif char == "=":
302
+ self.tokens.append(Token(TokenType.ASSIGN, "=", line, col))
303
+ self.advance()
304
+
305
+ # Comma
306
+ elif char == ",":
307
+ self.tokens.append(Token(TokenType.COMMA, ",", line, col))
308
+ self.advance()
309
+
310
+ # Colon
311
+ elif char == ":":
312
+ self.tokens.append(Token(TokenType.COLON, ":", line, col))
313
+ self.advance()
314
+
315
+ # Parentheses
316
+ elif char == "(":
317
+ self.tokens.append(Token(TokenType.LPAREN, "(", line, col))
318
+ self.advance()
319
+ elif char == ")":
320
+ self.tokens.append(Token(TokenType.RPAREN, ")", line, col))
321
+ self.advance()
322
+
323
+ # Identifier or keyword
324
+ elif char.isalpha() or char == "_": # type: ignore[union-attr]
325
+ ident = self.read_identifier()
326
+ token_type = self.KEYWORDS.get(ident.lower(), TokenType.IDENTIFIER)
327
+ self.tokens.append(Token(token_type, ident, line, col))
328
+
329
+ else:
330
+ raise SyntaxError(f"Unexpected character '{char}' at line {line}, column {col}")
331
+
332
+ # Emit remaining DEDENTs at end of file
333
+ while len(self.indent_stack) > 1:
334
+ self.indent_stack.pop()
335
+ self.tokens.append(Token(TokenType.DEDENT, 0, self.line, self.column))
336
+
337
+ # Add EOF token
338
+ self.tokens.append(Token(TokenType.EOF, None, self.line, self.column))
339
+ return self.tokens
340
+
341
+
342
+ @dataclass
343
+ class ASTNode:
344
+ """Base class for AST nodes."""
345
+
346
+ line: int
347
+ column: int
348
+
349
+
350
+ @dataclass
351
+ class Assignment(ASTNode):
352
+ """Variable assignment: $var = expr."""
353
+
354
+ variable: str
355
+ expression: "Expression"
356
+
357
+
358
+ @dataclass
359
+ class Pipeline(ASTNode):
360
+ """Pipeline expression: expr | command | command."""
361
+
362
+ stages: list["Expression"]
363
+
364
+
365
+ @dataclass
366
+ class Command(ASTNode):
367
+ """Command invocation: command arg1 arg2."""
368
+
369
+ name: str
370
+ args: list["Expression"]
371
+
372
+
373
+ @dataclass
374
+ class FunctionCall(ASTNode):
375
+ """Function call: func(arg1, arg2)."""
376
+
377
+ name: str
378
+ args: list["Expression"]
379
+
380
+
381
+ @dataclass
382
+ class Variable(ASTNode):
383
+ """Variable reference: $var."""
384
+
385
+ name: str
386
+
387
+
388
+ @dataclass
389
+ class Literal(ASTNode):
390
+ """Literal value: string, number."""
391
+
392
+ value: str | int | float
393
+
394
+
395
+ @dataclass
396
+ class ForLoop(ASTNode):
397
+ """For loop: for $var in expr: body."""
398
+
399
+ variable: str
400
+ iterable: "Expression"
401
+ body: list["Statement"]
402
+
403
+
404
+ # Type aliases
405
+ Expression = Union[Pipeline, Command, FunctionCall, Variable, Literal]
406
+ Statement = Union[Assignment, Pipeline, ForLoop, FunctionCall]
407
+
408
+
409
+ class Parser:
410
+ """Recursive descent parser for Oscura DSL.
411
+
412
+ Parses token stream into abstract syntax tree.
413
+ Supports indentation-based block structure.
414
+ """
415
+
416
+ def __init__(self, tokens: list[Token]):
417
+ """Initialize parser with token list.
418
+
419
+ Args:
420
+ tokens: Token list from lexer
421
+ """
422
+ self.tokens = tokens
423
+ self.pos = 0
424
+
425
+ def current_token(self) -> Token:
426
+ """Get current token."""
427
+ if self.pos >= len(self.tokens):
428
+ return self.tokens[-1] # EOF
429
+ return self.tokens[self.pos]
430
+
431
+ def peek_token(self, offset: int = 1) -> Token:
432
+ """Peek ahead at token."""
433
+ pos = self.pos + offset
434
+ if pos >= len(self.tokens):
435
+ return self.tokens[-1] # EOF
436
+ return self.tokens[pos]
437
+
438
+ def advance(self) -> None:
439
+ """Advance to next token."""
440
+ if self.pos < len(self.tokens):
441
+ self.pos += 1
442
+
443
+ def expect(self, token_type: TokenType) -> Token:
444
+ """Expect specific token type and advance.
445
+
446
+ Args:
447
+ token_type: Expected token type
448
+
449
+ Returns:
450
+ The token
451
+
452
+ Raises:
453
+ SyntaxError: If token type doesn't match
454
+ """
455
+ token = self.current_token()
456
+ if token.type != token_type:
457
+ raise SyntaxError(
458
+ f"Expected {token_type.name}, got {token.type.name} "
459
+ f"at line {token.line}, column {token.column}"
460
+ )
461
+ self.advance()
462
+ return token
463
+
464
+ def skip_newlines(self) -> None:
465
+ """Skip optional newlines."""
466
+ while self.current_token().type == TokenType.NEWLINE:
467
+ self.advance()
468
+
469
+ def parse(self) -> list[Statement]:
470
+ """Parse complete program.
471
+
472
+ Returns:
473
+ List of statements (AST)
474
+
475
+ Note:
476
+ May raise SyntaxError on parse errors via parse_statement().
477
+ """
478
+ statements = []
479
+
480
+ while self.current_token().type != TokenType.EOF:
481
+ self.skip_newlines()
482
+ if self.current_token().type == TokenType.EOF:
483
+ break
484
+
485
+ stmt = self.parse_statement()
486
+ statements.append(stmt)
487
+ self.skip_newlines()
488
+
489
+ return statements
490
+
491
+ def parse_statement(self) -> Statement:
492
+ """Parse a single statement."""
493
+ # For loop
494
+ if self.current_token().type == TokenType.FOR:
495
+ return self.parse_for_loop()
496
+
497
+ # Assignment or expression
498
+ if self.current_token().type == TokenType.VARIABLE:
499
+ if self.peek_token().type == TokenType.ASSIGN:
500
+ return self.parse_assignment()
501
+
502
+ # Pipeline expression
503
+ return self.parse_pipeline() # type: ignore[return-value]
504
+
505
+ def parse_assignment(self) -> Assignment:
506
+ """Parse variable assignment."""
507
+ token = self.current_token()
508
+ var_token = self.expect(TokenType.VARIABLE)
509
+ self.expect(TokenType.ASSIGN)
510
+ expr = self.parse_pipeline()
511
+
512
+ return Assignment(
513
+ variable=var_token.value,
514
+ expression=expr,
515
+ line=token.line,
516
+ column=token.column,
517
+ )
518
+
519
+ def parse_pipeline(self) -> Expression:
520
+ """Parse pipeline expression."""
521
+ stages = [self.parse_primary()]
522
+
523
+ while self.current_token().type == TokenType.PIPE:
524
+ self.advance() # Skip |
525
+ stages.append(self.parse_primary())
526
+
527
+ if len(stages) == 1:
528
+ return stages[0]
529
+
530
+ return Pipeline(stages=stages, line=stages[0].line, column=stages[0].column)
531
+
532
+ def parse_primary(self) -> Expression:
533
+ """Parse primary expression."""
534
+ token = self.current_token()
535
+
536
+ # Literal string
537
+ if token.type == TokenType.STRING:
538
+ self.advance()
539
+ return Literal(value=token.value, line=token.line, column=token.column)
540
+
541
+ # Literal number
542
+ if token.type == TokenType.NUMBER:
543
+ self.advance()
544
+ return Literal(value=token.value, line=token.line, column=token.column)
545
+
546
+ # Variable
547
+ if token.type == TokenType.VARIABLE:
548
+ self.advance()
549
+ return Variable(name=token.value, line=token.line, column=token.column)
550
+
551
+ # Function call or command
552
+ if token.type in (
553
+ TokenType.IDENTIFIER,
554
+ TokenType.LOAD,
555
+ TokenType.FILTER,
556
+ TokenType.MEASURE,
557
+ TokenType.PLOT,
558
+ TokenType.EXPORT,
559
+ TokenType.GLOB,
560
+ ):
561
+ name = token.value
562
+ self.advance()
563
+
564
+ # Function call with parens
565
+ if self.current_token().type == TokenType.LPAREN:
566
+ return self.parse_function_call(name, token)
567
+
568
+ # Command with args
569
+ args = []
570
+ while self.current_token().type not in (
571
+ TokenType.PIPE,
572
+ TokenType.NEWLINE,
573
+ TokenType.EOF,
574
+ TokenType.COLON,
575
+ TokenType.INDENT,
576
+ TokenType.DEDENT,
577
+ ):
578
+ args.append(self.parse_primary())
579
+
580
+ return Command(name=name, args=args, line=token.line, column=token.column)
581
+
582
+ raise SyntaxError(
583
+ f"Unexpected token {token.type.name} at line {token.line}, column {token.column}"
584
+ )
585
+
586
+ def parse_function_call(self, name: str, token: Token) -> FunctionCall:
587
+ """Parse function call with parentheses."""
588
+ self.expect(TokenType.LPAREN)
589
+
590
+ args = []
591
+ while self.current_token().type != TokenType.RPAREN:
592
+ args.append(self.parse_primary())
593
+ if self.current_token().type == TokenType.COMMA:
594
+ self.advance()
595
+
596
+ self.expect(TokenType.RPAREN)
597
+ return FunctionCall(name=name, args=args, line=token.line, column=token.column)
598
+
599
+ def parse_for_loop(self) -> ForLoop:
600
+ """Parse for loop with indented body.
601
+
602
+ Supports both single-line body and multi-line indented blocks:
603
+
604
+ Single line:
605
+ for $f in glob("*.wfm"): load $f
606
+
607
+ Multi-line (indented block):
608
+ for $f in glob("*.wfm"):
609
+ $data = load $f
610
+ measure $data
611
+ plot $data
612
+
613
+ Returns:
614
+ ForLoop AST node.
615
+ """
616
+ token = self.current_token()
617
+ self.expect(TokenType.FOR)
618
+
619
+ var_token = self.expect(TokenType.VARIABLE)
620
+ self.expect(TokenType.IN)
621
+
622
+ iterable = self.parse_primary()
623
+ self.expect(TokenType.COLON)
624
+
625
+ body: list[Statement] = []
626
+
627
+ # Check if body follows on same line or is indented block
628
+ if self.current_token().type == TokenType.NEWLINE:
629
+ # Multi-line block: expect INDENT, statements, DEDENT
630
+ self.skip_newlines()
631
+
632
+ if self.current_token().type == TokenType.INDENT:
633
+ self.advance() # Consume INDENT
634
+
635
+ # Parse statements until DEDENT
636
+ while self.current_token().type not in (TokenType.DEDENT, TokenType.EOF):
637
+ self.skip_newlines()
638
+ if self.current_token().type in (TokenType.DEDENT, TokenType.EOF):
639
+ break
640
+ stmt = self.parse_statement()
641
+ body.append(stmt)
642
+ self.skip_newlines()
643
+
644
+ # Consume DEDENT if present
645
+ if self.current_token().type == TokenType.DEDENT:
646
+ self.advance()
647
+ else:
648
+ # No INDENT after newline - parse single statement
649
+ body = [self.parse_statement()]
650
+ else:
651
+ # Single-line body (statement on same line as colon)
652
+ body = [self.parse_statement()]
653
+
654
+ return ForLoop(
655
+ variable=var_token.value,
656
+ iterable=iterable,
657
+ body=body,
658
+ line=token.line,
659
+ column=token.column,
660
+ )
661
+
662
+
663
+ def parse_dsl(source: str) -> list[Statement]:
664
+ """Parse Oscura DSL source code.
665
+
666
+ Args:
667
+ source: DSL source code
668
+
669
+ Returns:
670
+ Abstract syntax tree (list of statements)
671
+
672
+ Example:
673
+ >>> # Single-line for loop
674
+ >>> ast = parse_dsl('for $f in glob("*.wfm"): load $f')
675
+
676
+ >>> # Multi-line indented block
677
+ >>> ast = parse_dsl('''
678
+ ... for $f in glob("*.wfm"):
679
+ ... $data = load $f
680
+ ... measure $data
681
+ ... ''')
682
+
683
+ Note:
684
+ May raise SyntaxError on parse errors via tokenize() or parse().
685
+ """
686
+ lexer = Lexer(source)
687
+ tokens = lexer.tokenize()
688
+ parser = Parser(tokens)
689
+ return parser.parse()