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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
#
|
|
31
|
-
ext = path.suffix.lower()
|
|
32
|
-
|
|
29
|
+
# Use the unified loader
|
|
33
30
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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")
|
|
81
|
+
raise OscuraError("Filtering module not available")
|
|
102
82
|
|
|
103
83
|
|
|
104
|
-
def cmd_measure(trace: 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
|
-
*
|
|
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
|
|
119
|
-
measurements as meas, # type: ignore[attr-defined]
|
|
120
|
-
)
|
|
98
|
+
from oscura.analyzers import measurements
|
|
121
99
|
|
|
122
|
-
if len(
|
|
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
|
|
128
|
-
meas_name =
|
|
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"] =
|
|
109
|
+
results["rise_time"] = measurements.rise_time(trace)
|
|
132
110
|
elif meas_name == "fall_time":
|
|
133
|
-
results["fall_time"] =
|
|
111
|
+
results["fall_time"] = measurements.fall_time(trace)
|
|
134
112
|
elif meas_name == "period":
|
|
135
|
-
results["period"] =
|
|
113
|
+
results["period"] = measurements.period(trace)
|
|
136
114
|
elif meas_name == "frequency":
|
|
137
|
-
results["frequency"] =
|
|
115
|
+
results["frequency"] = measurements.frequency(trace)
|
|
138
116
|
elif meas_name == "amplitude":
|
|
139
|
-
results["amplitude"] =
|
|
117
|
+
results["amplitude"] = measurements.amplitude(trace)
|
|
140
118
|
elif meas_name == "mean":
|
|
141
|
-
results["mean"] =
|
|
119
|
+
results["mean"] = measurements.mean(trace)
|
|
142
120
|
elif meas_name == "rms":
|
|
143
|
-
results["rms"] =
|
|
121
|
+
results["rms"] = measurements.rms(trace)
|
|
144
122
|
elif meas_name == "all":
|
|
145
123
|
# Measure all available measurements
|
|
146
|
-
results =
|
|
124
|
+
results = measurements.measure(trace, parameters=None)
|
|
147
125
|
break
|
|
148
126
|
else:
|
|
149
|
-
raise OscuraError(f"Unknown 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")
|
|
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
|
|
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
|
-
|
|
155
|
+
plot.plot_trace(trace, title=title)
|
|
180
156
|
|
|
181
157
|
if annotate:
|
|
182
|
-
|
|
158
|
+
plot.add_annotation(annotate)
|
|
159
|
+
|
|
160
|
+
# Import matplotlib.pyplot for show()
|
|
161
|
+
import matplotlib.pyplot as plt
|
|
183
162
|
|
|
184
|
-
|
|
163
|
+
plt.show()
|
|
185
164
|
|
|
186
165
|
except ImportError:
|
|
187
|
-
raise OscuraError("Visualization module not available")
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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")
|
|
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))
|
|
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}")
|
|
78
|
+
raise InterpreterError(f"oscura.loaders not available: {e}")
|
|
79
79
|
except Exception as e:
|
|
80
|
-
raise InterpreterError(f"Failed to load {filename}: {e}")
|
|
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}")
|
|
122
|
+
raise InterpreterError(f"oscura.filtering not available: {e}")
|
|
123
123
|
except Exception as e:
|
|
124
|
-
raise InterpreterError(f"Filter failed: {e}")
|
|
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}")
|
|
164
|
+
raise InterpreterError(f"oscura measurements not available: {e}")
|
|
165
165
|
except AttributeError as e:
|
|
166
|
-
raise InterpreterError(f"Measurement function not found: {e}")
|
|
166
|
+
raise InterpreterError(f"Measurement function not found: {e}")
|
|
167
167
|
except Exception as e:
|
|
168
|
-
raise InterpreterError(f"Measurement failed: {e}")
|
|
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}")
|
|
194
|
+
raise InterpreterError(f"oscura.visualization not available: {e}")
|
|
195
195
|
except Exception as e:
|
|
196
|
-
raise InterpreterError(f"Plot failed: {e}")
|
|
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
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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))
|
|
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) ->
|
|
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__}")
|
|
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.
|
oscura/{dsl → api/dsl}/parser.py
RENAMED
|
@@ -61,7 +61,7 @@ class Lexer:
|
|
|
61
61
|
Supports indentation-based block structure (Python-style).
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
|
-
KEYWORDS = {
|
|
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
|
-
|
|
202
|
-
|
|
201
|
+
current = self.current_char()
|
|
202
|
+
while current and (current.isalnum() or current in "_"):
|
|
203
|
+
chars.append(current)
|
|
203
204
|
self.advance()
|
|
204
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
self.
|
|
275
|
+
if indent == -1:
|
|
276
|
+
return self._skip_blank_line()
|
|
277
|
+
else:
|
|
278
|
+
self.emit_indent_tokens(indent)
|
|
279
|
+
return True
|
|
311
280
|
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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
|
-
|
|
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
|
oscura/{dsl → api/dsl}/repl.py
RENAMED
|
@@ -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