oscura 0.5.0__py3-none-any.whl → 0.6.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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/api/__init__.py
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
"""Expert API module for Oscura.
|
|
2
2
|
|
|
3
3
|
This module provides advanced APIs for power users including DSL,
|
|
4
|
-
fluent interfaces, performance profiling, and advanced workflow control.
|
|
4
|
+
fluent interfaces, performance profiling, REST API server, and advanced workflow control.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from oscura.api.dsl import (
|
|
8
|
-
|
|
9
|
-
DSLParser,
|
|
10
|
-
analyze,
|
|
11
|
-
parse_expression,
|
|
8
|
+
Expression,
|
|
12
9
|
)
|
|
10
|
+
from oscura.api.dsl import (
|
|
11
|
+
Interpreter as DSLParser,
|
|
12
|
+
)
|
|
13
|
+
from oscura.api.dsl import (
|
|
14
|
+
execute_dsl as analyze,
|
|
15
|
+
)
|
|
16
|
+
from oscura.api.dsl import (
|
|
17
|
+
parse_dsl as parse_expression,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Alias for backward compatibility
|
|
21
|
+
DSLExpression = Expression
|
|
13
22
|
from oscura.api.fluent import (
|
|
14
23
|
FluentResult,
|
|
15
24
|
FluentTrace,
|
|
@@ -33,11 +42,24 @@ from oscura.api.profiling import (
|
|
|
33
42
|
ProfileReport,
|
|
34
43
|
profile,
|
|
35
44
|
)
|
|
45
|
+
from oscura.api.rest_server import (
|
|
46
|
+
AnalysisRequest,
|
|
47
|
+
AnalysisResponse,
|
|
48
|
+
ErrorResponse,
|
|
49
|
+
ProtocolResponse,
|
|
50
|
+
RESTAPIServer,
|
|
51
|
+
SessionManager,
|
|
52
|
+
SessionResponse,
|
|
53
|
+
)
|
|
36
54
|
|
|
37
55
|
__all__ = [
|
|
56
|
+
# REST API Server
|
|
57
|
+
"AnalysisRequest",
|
|
58
|
+
"AnalysisResponse",
|
|
38
59
|
# DSL (API-010)
|
|
39
60
|
"DSLExpression",
|
|
40
61
|
"DSLParser",
|
|
62
|
+
"ErrorResponse",
|
|
41
63
|
# Fluent (API-019)
|
|
42
64
|
"FluentResult",
|
|
43
65
|
"FluentTrace",
|
|
@@ -49,6 +71,10 @@ __all__ = [
|
|
|
49
71
|
"ParameterSpace",
|
|
50
72
|
"ProfileReport",
|
|
51
73
|
"Profiler",
|
|
74
|
+
"ProtocolResponse",
|
|
75
|
+
"RESTAPIServer",
|
|
76
|
+
"SessionManager",
|
|
77
|
+
"SessionResponse",
|
|
52
78
|
# Operators (API-015, API-016, API-018)
|
|
53
79
|
"TimeIndex",
|
|
54
80
|
"UnitConverter",
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"""Oscura DSL - Domain-Specific Language for trace analysis.
|
|
2
|
+
|
|
3
|
+
Provides a simple, declarative language for defining trace analysis workflows.
|
|
4
|
+
|
|
5
|
+
Example usage:
|
|
6
|
+
```python
|
|
7
|
+
from oscura.api.dsl import execute_dsl
|
|
8
|
+
|
|
9
|
+
# Execute DSL script
|
|
10
|
+
script = '''
|
|
11
|
+
$data = load "capture.csv"
|
|
12
|
+
$filtered = $data | filter lowpass 1000
|
|
13
|
+
$rise = $filtered | measure rise_time
|
|
14
|
+
'''
|
|
15
|
+
|
|
16
|
+
env = execute_dsl(script)
|
|
17
|
+
print(f"Rise time: {env['$rise']}")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or start interactive REPL:
|
|
21
|
+
```python
|
|
22
|
+
from oscura.api.dsl import start_repl
|
|
23
|
+
start_repl()
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
import numpy as np
|
|
34
|
+
|
|
35
|
+
from oscura.api.dsl.commands import BUILTIN_COMMANDS
|
|
36
|
+
from oscura.api.dsl.interpreter import Interpreter, InterpreterError, execute_dsl
|
|
37
|
+
from oscura.api.dsl.parser import (
|
|
38
|
+
Assignment,
|
|
39
|
+
Command,
|
|
40
|
+
Expression,
|
|
41
|
+
ForLoop,
|
|
42
|
+
FunctionCall,
|
|
43
|
+
Lexer,
|
|
44
|
+
Literal,
|
|
45
|
+
Parser,
|
|
46
|
+
Pipeline,
|
|
47
|
+
Statement,
|
|
48
|
+
Token,
|
|
49
|
+
TokenType,
|
|
50
|
+
Variable,
|
|
51
|
+
parse_dsl,
|
|
52
|
+
)
|
|
53
|
+
from oscura.api.dsl.repl import REPL, start_repl
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Legacy API dataclass for backwards compatibility
|
|
57
|
+
@dataclass
|
|
58
|
+
class DSLExpression:
|
|
59
|
+
"""Legacy DSL expression dataclass for backwards compatibility.
|
|
60
|
+
|
|
61
|
+
Represents a single DSL operation with arguments and optional chaining.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
operation: Operation name (e.g., "fft", "lowpass", "mean").
|
|
65
|
+
args: Positional arguments.
|
|
66
|
+
kwargs: Keyword arguments.
|
|
67
|
+
chain: Optional chained expression to execute after this one.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
operation: str
|
|
71
|
+
args: list[Any] = field(default_factory=list)
|
|
72
|
+
kwargs: dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
chain: DSLExpression | None = None
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> dict[str, Any]:
|
|
76
|
+
"""Convert expression to dictionary representation.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dictionary with operation, args, kwargs, and optionally chain.
|
|
80
|
+
"""
|
|
81
|
+
result: dict[str, Any] = {
|
|
82
|
+
"operation": self.operation,
|
|
83
|
+
"args": self.args,
|
|
84
|
+
"kwargs": self.kwargs,
|
|
85
|
+
}
|
|
86
|
+
if self.chain is not None:
|
|
87
|
+
result["chain"] = self.chain.to_dict()
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Legacy API parser class
|
|
92
|
+
class DSLParser:
|
|
93
|
+
"""Legacy parser class for backwards compatibility.
|
|
94
|
+
|
|
95
|
+
Parses DSL expression strings into DSLExpression objects.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self) -> None:
|
|
99
|
+
"""Initialize parser state."""
|
|
100
|
+
self._pos = 0
|
|
101
|
+
self._text = ""
|
|
102
|
+
|
|
103
|
+
def parse(self, text: str) -> DSLExpression:
|
|
104
|
+
"""Parse DSL expression string.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
text: Expression string to parse.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Parsed DSLExpression.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If expression is invalid.
|
|
114
|
+
"""
|
|
115
|
+
self._text = text.strip()
|
|
116
|
+
self._pos = 0
|
|
117
|
+
|
|
118
|
+
if not self._text:
|
|
119
|
+
raise ValueError("Expected identifier")
|
|
120
|
+
|
|
121
|
+
return self._parse_chain()
|
|
122
|
+
|
|
123
|
+
def _parse_chain(self) -> DSLExpression:
|
|
124
|
+
"""Parse potentially chained expressions."""
|
|
125
|
+
expr = self._parse_operation()
|
|
126
|
+
|
|
127
|
+
# Check for pipe operator
|
|
128
|
+
self._skip_whitespace()
|
|
129
|
+
if self._peek() == "|":
|
|
130
|
+
self._advance() # consume |
|
|
131
|
+
chain = self._parse_chain()
|
|
132
|
+
expr.chain = chain
|
|
133
|
+
|
|
134
|
+
return expr
|
|
135
|
+
|
|
136
|
+
def _parse_operation(self) -> DSLExpression:
|
|
137
|
+
"""Parse a single operation."""
|
|
138
|
+
self._skip_whitespace()
|
|
139
|
+
|
|
140
|
+
# Parse operation name
|
|
141
|
+
operation = self._parse_identifier()
|
|
142
|
+
if not operation:
|
|
143
|
+
raise ValueError("Expected identifier")
|
|
144
|
+
|
|
145
|
+
# Validate operation
|
|
146
|
+
valid_ops = {
|
|
147
|
+
"mean",
|
|
148
|
+
"std",
|
|
149
|
+
"min",
|
|
150
|
+
"max",
|
|
151
|
+
"rms",
|
|
152
|
+
"fft",
|
|
153
|
+
"psd",
|
|
154
|
+
"lowpass",
|
|
155
|
+
"highpass",
|
|
156
|
+
"bandpass",
|
|
157
|
+
"normalize",
|
|
158
|
+
"slice",
|
|
159
|
+
"resample",
|
|
160
|
+
"load",
|
|
161
|
+
"filter",
|
|
162
|
+
}
|
|
163
|
+
if operation not in valid_ops:
|
|
164
|
+
raise ValueError(f"Unknown operation: {operation}")
|
|
165
|
+
|
|
166
|
+
# Parse arguments if present
|
|
167
|
+
args: list[Any] = []
|
|
168
|
+
kwargs: dict[str, Any] = {}
|
|
169
|
+
|
|
170
|
+
self._skip_whitespace()
|
|
171
|
+
if self._peek() == "(":
|
|
172
|
+
self._advance() # consume (
|
|
173
|
+
args, kwargs = self._parse_args()
|
|
174
|
+
if self._peek() != ")":
|
|
175
|
+
raise ValueError("Expected ')'")
|
|
176
|
+
self._advance() # consume )
|
|
177
|
+
|
|
178
|
+
return DSLExpression(operation=operation, args=args, kwargs=kwargs)
|
|
179
|
+
|
|
180
|
+
def _parse_args(self) -> tuple[list[Any], dict[str, Any]]:
|
|
181
|
+
"""Parse function arguments."""
|
|
182
|
+
args: list[Any] = []
|
|
183
|
+
kwargs: dict[str, Any] = {}
|
|
184
|
+
|
|
185
|
+
self._skip_whitespace()
|
|
186
|
+
if self._peek() == ")":
|
|
187
|
+
return args, kwargs
|
|
188
|
+
|
|
189
|
+
while True:
|
|
190
|
+
self._skip_whitespace()
|
|
191
|
+
|
|
192
|
+
# Try to parse keyword argument (identifier=value)
|
|
193
|
+
saved_pos = self._pos
|
|
194
|
+
identifier = self._parse_identifier()
|
|
195
|
+
self._skip_whitespace()
|
|
196
|
+
|
|
197
|
+
if identifier and self._peek() == "=":
|
|
198
|
+
# It's a keyword argument
|
|
199
|
+
self._advance() # consume =
|
|
200
|
+
value = self._parse_value()
|
|
201
|
+
kwargs[identifier] = value
|
|
202
|
+
else:
|
|
203
|
+
# It's a positional argument - restore position and parse value
|
|
204
|
+
self._pos = saved_pos
|
|
205
|
+
value = self._parse_value()
|
|
206
|
+
args.append(value)
|
|
207
|
+
|
|
208
|
+
self._skip_whitespace()
|
|
209
|
+
if self._peek() == ",":
|
|
210
|
+
self._advance()
|
|
211
|
+
self._skip_whitespace()
|
|
212
|
+
# Check for trailing comma before closing paren
|
|
213
|
+
if self._peek() == ")":
|
|
214
|
+
break
|
|
215
|
+
continue
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
return args, kwargs
|
|
219
|
+
|
|
220
|
+
def _parse_value(self) -> Any:
|
|
221
|
+
"""Parse a value (number, string, list, bool, etc)."""
|
|
222
|
+
self._skip_whitespace()
|
|
223
|
+
|
|
224
|
+
ch = self._peek()
|
|
225
|
+
|
|
226
|
+
# String
|
|
227
|
+
if ch in ('"', "'"):
|
|
228
|
+
return self._parse_string()
|
|
229
|
+
|
|
230
|
+
# List
|
|
231
|
+
if ch == "[":
|
|
232
|
+
return self._parse_list()
|
|
233
|
+
|
|
234
|
+
# Number or identifier
|
|
235
|
+
if ch.isdigit() or ch in ("-", "."):
|
|
236
|
+
return self._parse_number()
|
|
237
|
+
|
|
238
|
+
# Boolean/None/identifier
|
|
239
|
+
identifier = self._parse_identifier()
|
|
240
|
+
if identifier == "True":
|
|
241
|
+
return True
|
|
242
|
+
elif identifier == "False":
|
|
243
|
+
return False
|
|
244
|
+
elif identifier == "None":
|
|
245
|
+
return None
|
|
246
|
+
elif identifier:
|
|
247
|
+
return identifier
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError(f"Unexpected character: {ch}")
|
|
250
|
+
|
|
251
|
+
def _parse_number(self) -> int | float:
|
|
252
|
+
"""Parse number (int or float)."""
|
|
253
|
+
start = self._pos
|
|
254
|
+
has_dot = False
|
|
255
|
+
has_e = False
|
|
256
|
+
|
|
257
|
+
# Handle negative sign
|
|
258
|
+
if self._peek() == "-":
|
|
259
|
+
self._advance()
|
|
260
|
+
|
|
261
|
+
# Leading dot
|
|
262
|
+
if self._peek() == ".":
|
|
263
|
+
has_dot = True
|
|
264
|
+
self._advance()
|
|
265
|
+
|
|
266
|
+
while self._pos < len(self._text):
|
|
267
|
+
ch = self._peek()
|
|
268
|
+
if ch.isdigit():
|
|
269
|
+
self._advance()
|
|
270
|
+
elif ch == "." and not has_dot and not has_e:
|
|
271
|
+
has_dot = True
|
|
272
|
+
self._advance()
|
|
273
|
+
elif ch in ("e", "E") and not has_e:
|
|
274
|
+
has_e = True
|
|
275
|
+
self._advance()
|
|
276
|
+
# Handle +/- after e
|
|
277
|
+
if self._peek() in ("+", "-"):
|
|
278
|
+
self._advance()
|
|
279
|
+
else:
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
num_str = self._text[start : self._pos]
|
|
283
|
+
if "." in num_str or "e" in num_str or "E" in num_str:
|
|
284
|
+
return float(num_str)
|
|
285
|
+
else:
|
|
286
|
+
return int(num_str)
|
|
287
|
+
|
|
288
|
+
def _parse_string(self) -> str:
|
|
289
|
+
"""Parse string literal."""
|
|
290
|
+
quote = self._advance() # get quote character
|
|
291
|
+
start = self._pos
|
|
292
|
+
while self._pos < len(self._text):
|
|
293
|
+
ch = self._peek()
|
|
294
|
+
if ch == quote:
|
|
295
|
+
result = self._text[start : self._pos]
|
|
296
|
+
self._advance() # consume closing quote
|
|
297
|
+
return result
|
|
298
|
+
elif ch == "\\":
|
|
299
|
+
# Skip escaped character
|
|
300
|
+
self._advance()
|
|
301
|
+
if self._pos < len(self._text):
|
|
302
|
+
self._advance()
|
|
303
|
+
else:
|
|
304
|
+
self._advance()
|
|
305
|
+
|
|
306
|
+
raise ValueError("Unterminated string")
|
|
307
|
+
|
|
308
|
+
def _parse_list(self) -> list[Any]:
|
|
309
|
+
"""Parse list literal."""
|
|
310
|
+
self._advance() # consume [
|
|
311
|
+
items: list[Any] = []
|
|
312
|
+
|
|
313
|
+
self._skip_whitespace()
|
|
314
|
+
if self._peek() == "]":
|
|
315
|
+
self._advance()
|
|
316
|
+
return items
|
|
317
|
+
|
|
318
|
+
while True:
|
|
319
|
+
items.append(self._parse_value())
|
|
320
|
+
self._skip_whitespace()
|
|
321
|
+
if self._peek() == ",":
|
|
322
|
+
self._advance()
|
|
323
|
+
self._skip_whitespace()
|
|
324
|
+
# Handle trailing comma
|
|
325
|
+
if self._peek() == "]":
|
|
326
|
+
break
|
|
327
|
+
continue
|
|
328
|
+
break
|
|
329
|
+
|
|
330
|
+
if self._peek() != "]":
|
|
331
|
+
raise ValueError("Unterminated list")
|
|
332
|
+
self._advance()
|
|
333
|
+
|
|
334
|
+
return items
|
|
335
|
+
|
|
336
|
+
def _parse_identifier(self) -> str:
|
|
337
|
+
"""Parse identifier."""
|
|
338
|
+
start = self._pos
|
|
339
|
+
while self._pos < len(self._text):
|
|
340
|
+
ch = self._peek()
|
|
341
|
+
if ch.isalnum() or ch == "_":
|
|
342
|
+
self._advance()
|
|
343
|
+
else:
|
|
344
|
+
break
|
|
345
|
+
return self._text[start : self._pos]
|
|
346
|
+
|
|
347
|
+
def _skip_whitespace(self) -> None:
|
|
348
|
+
"""Skip whitespace characters."""
|
|
349
|
+
while self._pos < len(self._text) and self._text[self._pos].isspace():
|
|
350
|
+
self._pos += 1
|
|
351
|
+
|
|
352
|
+
def _peek(self) -> str:
|
|
353
|
+
"""Peek at current character."""
|
|
354
|
+
if self._pos < len(self._text):
|
|
355
|
+
return self._text[self._pos]
|
|
356
|
+
return ""
|
|
357
|
+
|
|
358
|
+
def _advance(self) -> str:
|
|
359
|
+
"""Advance and return current character."""
|
|
360
|
+
if self._pos < len(self._text):
|
|
361
|
+
ch = self._text[self._pos]
|
|
362
|
+
self._pos += 1
|
|
363
|
+
return ch
|
|
364
|
+
return ""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def parse_expression(text: str) -> DSLExpression:
|
|
368
|
+
"""Parse DSL expression string (legacy API).
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
text: Expression string to parse.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Parsed DSLExpression.
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
>>> expr = parse_expression("lowpass(cutoff=1e6)")
|
|
378
|
+
>>> print(expr.operation)
|
|
379
|
+
lowpass
|
|
380
|
+
"""
|
|
381
|
+
parser = DSLParser()
|
|
382
|
+
return parser.parse(text)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def analyze(data: Any, expression_str: str) -> Any:
|
|
386
|
+
"""Execute DSL expression on data (legacy API).
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
data: NumPy array to analyze.
|
|
390
|
+
expression_str: DSL expression string.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Analysis result.
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
>>> import numpy as np
|
|
397
|
+
>>> data = np.array([1, 2, 3, 4, 5])
|
|
398
|
+
>>> analyze(data, "mean")
|
|
399
|
+
3.0
|
|
400
|
+
"""
|
|
401
|
+
import numpy as np
|
|
402
|
+
|
|
403
|
+
# Parse expression using legacy parser
|
|
404
|
+
expr = parse_expression(expression_str)
|
|
405
|
+
|
|
406
|
+
# Execute using legacy executor
|
|
407
|
+
executor = DSLExecutor()
|
|
408
|
+
return executor.execute(expr, data)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# DSLExecutor class for backwards compatibility
|
|
412
|
+
class DSLExecutor:
|
|
413
|
+
"""Legacy executor class for backwards compatibility.
|
|
414
|
+
|
|
415
|
+
Executes DSL operations on numpy arrays for testing purposes.
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
def __init__(self) -> None:
|
|
419
|
+
"""Initialize executor with operation registry."""
|
|
420
|
+
self._operations: dict[str, Any] = {
|
|
421
|
+
"mean": lambda data, **kw: float(__import__("numpy").mean(data)),
|
|
422
|
+
"std": lambda data, **kw: float(__import__("numpy").std(data)),
|
|
423
|
+
"min": lambda data, **kw: float(__import__("numpy").min(data)),
|
|
424
|
+
"max": lambda data, **kw: float(__import__("numpy").max(data)),
|
|
425
|
+
"rms": lambda data, **kw: float(
|
|
426
|
+
__import__("numpy").sqrt(__import__("numpy").mean(data**2))
|
|
427
|
+
),
|
|
428
|
+
"fft": lambda data, **kw: __import__("numpy").fft.fft(
|
|
429
|
+
data, n=kw.get("nfft", len(data))
|
|
430
|
+
),
|
|
431
|
+
"normalize": self._normalize,
|
|
432
|
+
"lowpass": self._lowpass,
|
|
433
|
+
"highpass": self._highpass,
|
|
434
|
+
"bandpass": self._bandpass,
|
|
435
|
+
"slice": self._slice,
|
|
436
|
+
"resample": self._resample,
|
|
437
|
+
"psd": self._psd,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
def execute(self, expr: DSLExpression, data: Any) -> Any:
|
|
441
|
+
"""Execute DSL expression on data.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
expr: DSLExpression to execute.
|
|
445
|
+
data: NumPy array to process.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Execution result.
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
ValueError: If operation is unknown.
|
|
452
|
+
"""
|
|
453
|
+
import numpy as np
|
|
454
|
+
|
|
455
|
+
# Execute main operation
|
|
456
|
+
if expr.operation not in self._operations:
|
|
457
|
+
raise ValueError(f"Unknown operation: {expr.operation}")
|
|
458
|
+
|
|
459
|
+
# Call operation with positional args and keyword args
|
|
460
|
+
# Convert positional args to operation-specific kwargs for known operations
|
|
461
|
+
kwargs = dict(expr.kwargs)
|
|
462
|
+
|
|
463
|
+
# Handle slice operation with positional args: slice(start, end)
|
|
464
|
+
if expr.operation == "slice" and expr.args:
|
|
465
|
+
if len(expr.args) >= 1 and "start" not in kwargs:
|
|
466
|
+
kwargs["start"] = expr.args[0]
|
|
467
|
+
if len(expr.args) >= 2 and "end" not in kwargs:
|
|
468
|
+
kwargs["end"] = expr.args[1]
|
|
469
|
+
|
|
470
|
+
result = self._operations[expr.operation](data, **kwargs)
|
|
471
|
+
|
|
472
|
+
# Execute chain if present
|
|
473
|
+
if expr.chain is not None:
|
|
474
|
+
if not isinstance(result, np.ndarray):
|
|
475
|
+
raise ValueError(f"Cannot chain after {expr.operation}")
|
|
476
|
+
result = self.execute(expr.chain, result)
|
|
477
|
+
|
|
478
|
+
return result
|
|
479
|
+
|
|
480
|
+
def _normalize(self, data: Any, method: str = "minmax", **kwargs: Any) -> Any:
|
|
481
|
+
"""Normalize data."""
|
|
482
|
+
import numpy as np
|
|
483
|
+
|
|
484
|
+
if method == "minmax":
|
|
485
|
+
data_min = np.min(data)
|
|
486
|
+
data_max = np.max(data)
|
|
487
|
+
if data_max == data_min:
|
|
488
|
+
return data
|
|
489
|
+
return (data - data_min) / (data_max - data_min)
|
|
490
|
+
elif method == "zscore":
|
|
491
|
+
mean = np.mean(data)
|
|
492
|
+
std = np.std(data)
|
|
493
|
+
if std == 0:
|
|
494
|
+
return data - mean
|
|
495
|
+
return (data - mean) / std
|
|
496
|
+
else:
|
|
497
|
+
return data
|
|
498
|
+
|
|
499
|
+
def _lowpass(self, data: Any, cutoff: float = 1e5, fs: float = 1e6, **kwargs: Any) -> Any:
|
|
500
|
+
"""Apply lowpass filter."""
|
|
501
|
+
from scipy.signal import butter, filtfilt
|
|
502
|
+
|
|
503
|
+
nyquist = fs / 2
|
|
504
|
+
normalized_cutoff = cutoff / nyquist
|
|
505
|
+
b, a = butter(4, normalized_cutoff, btype="low")
|
|
506
|
+
return filtfilt(b, a, data)
|
|
507
|
+
|
|
508
|
+
def _highpass(self, data: Any, cutoff: float = 1e5, fs: float = 1e6, **kwargs: Any) -> Any:
|
|
509
|
+
"""Apply highpass filter."""
|
|
510
|
+
from scipy.signal import butter, filtfilt
|
|
511
|
+
|
|
512
|
+
nyquist = fs / 2
|
|
513
|
+
normalized_cutoff = cutoff / nyquist
|
|
514
|
+
b, a = butter(4, normalized_cutoff, btype="high")
|
|
515
|
+
return filtfilt(b, a, data)
|
|
516
|
+
|
|
517
|
+
def _bandpass(
|
|
518
|
+
self,
|
|
519
|
+
data: Any,
|
|
520
|
+
low: float = 1e3,
|
|
521
|
+
high: float = 1e6,
|
|
522
|
+
fs: float = 1e6,
|
|
523
|
+
**kwargs: Any,
|
|
524
|
+
) -> Any:
|
|
525
|
+
"""Apply bandpass filter."""
|
|
526
|
+
from scipy.signal import butter, filtfilt
|
|
527
|
+
|
|
528
|
+
nyquist = fs / 2
|
|
529
|
+
normalized_low = low / nyquist
|
|
530
|
+
normalized_high = high / nyquist
|
|
531
|
+
b, a = butter(4, [normalized_low, normalized_high], btype="band")
|
|
532
|
+
return filtfilt(b, a, data)
|
|
533
|
+
|
|
534
|
+
def _slice(self, data: Any, start: int = 0, end: int | None = None, **kwargs: Any) -> Any:
|
|
535
|
+
"""Slice data."""
|
|
536
|
+
return data[start:end]
|
|
537
|
+
|
|
538
|
+
def _resample(self, data: Any, factor: int = 2, **kwargs: Any) -> Any:
|
|
539
|
+
"""Resample data by decimation factor."""
|
|
540
|
+
return data[::factor]
|
|
541
|
+
|
|
542
|
+
def _psd(self, data: Any, nperseg: int = 256, **kwargs: Any) -> Any:
|
|
543
|
+
"""Compute power spectral density."""
|
|
544
|
+
from scipy.signal import welch
|
|
545
|
+
|
|
546
|
+
_, psd = welch(data, nperseg=nperseg)
|
|
547
|
+
return psd
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
__all__ = [
|
|
551
|
+
# Commands
|
|
552
|
+
"BUILTIN_COMMANDS",
|
|
553
|
+
# REPL
|
|
554
|
+
"REPL",
|
|
555
|
+
# AST nodes
|
|
556
|
+
"Assignment",
|
|
557
|
+
"Command",
|
|
558
|
+
# Legacy API (backwards compatibility)
|
|
559
|
+
"DSLExecutor",
|
|
560
|
+
"DSLExpression",
|
|
561
|
+
"DSLParser",
|
|
562
|
+
"Expression",
|
|
563
|
+
"ForLoop",
|
|
564
|
+
"FunctionCall",
|
|
565
|
+
# Interpreter
|
|
566
|
+
"Interpreter",
|
|
567
|
+
"InterpreterError",
|
|
568
|
+
# Parser
|
|
569
|
+
"Lexer",
|
|
570
|
+
"Literal",
|
|
571
|
+
"Parser",
|
|
572
|
+
"Pipeline",
|
|
573
|
+
"Statement",
|
|
574
|
+
"Token",
|
|
575
|
+
"TokenType",
|
|
576
|
+
"Variable",
|
|
577
|
+
"analyze",
|
|
578
|
+
"execute_dsl",
|
|
579
|
+
"parse_dsl",
|
|
580
|
+
"parse_expression",
|
|
581
|
+
"start_repl",
|
|
582
|
+
]
|