]> sigrok.org Git - pulseview.git/blobdiff - pv/data/mathsignal.cpp
Make error handling generic improve math error detail
[pulseview.git] / pv / data / mathsignal.cpp
index 20a41a4cc814c9b861836da1b1395a6df8befead..e560a2e7b1a3c9a99b7aaa94ed65b4d04165252f 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "mathsignal.hpp"
 
+#include <extdef.h>
 #include <pv/globalsettings.hpp>
 #include <pv/session.hpp>
 #include <pv/data/analogsegment.hpp>
@@ -83,24 +84,20 @@ MathSignal::MathSignal(pv::Session &session) :
        use_custom_sample_rate_(false),
        use_custom_sample_count_(false),
        expression_(""),
-       error_message_(""),
        exprtk_unknown_symbol_table_(nullptr),
        exprtk_symbol_table_(nullptr),
        exprtk_expression_(nullptr),
        exprtk_parser_(nullptr),
        fnc_sig_sample_(nullptr)
 {
-       set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
+       uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
+       set_name(QString(tr("Math%1")).arg(sig_idx));
+       set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
 
-       shared_ptr<Analog> data(new data::Analog());
-       set_data(data);
+       set_data(std::make_shared<data::Analog>());
 
        connect(&session_, SIGNAL(capture_state_changed(int)),
                this, SLOT(on_capture_state_changed(int)));
-       connect(&session_, SIGNAL(data_received()),
-               this, SLOT(on_data_received()));
-
-       expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
 }
 
 MathSignal::~MathSignal()
@@ -139,11 +136,6 @@ void MathSignal::restore_settings(QSettings &settings)
                use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
 }
 
-QString MathSignal::error_message() const
-{
-       return error_message_;
-}
-
 QString MathSignal::get_expression() const
 {
        return expression_;
@@ -161,6 +153,8 @@ void MathSignal::set_error_message(QString msg)
        error_message_ = msg;
        // TODO Emulate noquote()
        qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
+
+       error_message_changed(msg);
 }
 
 uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
@@ -180,10 +174,18 @@ uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
                                const shared_ptr<SignalBase>& sb = input_signal.second.sb;
 
                                shared_ptr<Analog> a = sb->analog_data();
-                               const uint32_t last_segment = (a->analog_segments().size() - 1);
-                               if (segment_id > last_segment)
+                               auto analog_segments = a->analog_segments();
+
+                               if (analog_segments.size() == 0) {
+                                       result = 0;
                                        continue;
-                               const shared_ptr<AnalogSegment> segment = a->analog_segments()[segment_id];
+                               }
+
+                               const uint32_t highest_segment_id = (analog_segments.size() - 1);
+                               if (segment_id > highest_segment_id)
+                                       continue;
+
+                               const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
                                result = min(result, (int64_t)segment->get_sample_count());
                        }
                } else
@@ -193,6 +195,48 @@ uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
        return result;
 }
 
+void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
+{
+       bool output_complete = true;
+
+       if (input_signals_.size() > 0) {
+               for (auto input_signal : input_signals_) {
+                       const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+                       shared_ptr<Analog> a = sb->analog_data();
+                       auto analog_segments = a->analog_segments();
+
+                       if (analog_segments.size() == 0) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       const uint32_t highest_segment_id = (analog_segments.size() - 1);
+                       if (segment_id > highest_segment_id) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
+                       if (!segment->is_complete()) {
+                               output_complete = false;
+                               continue;
+                       }
+
+                       if (output_sample_count < segment->get_sample_count())
+                               output_complete = false;
+               }
+       } else {
+               // We're done when we generated as many samples as the stopped session is long
+               if ((session_.get_capture_state() != Session::Stopped) ||
+                       (output_sample_count < session_.get_segment_sample_count(segment_id)))
+                       output_complete = false;
+       }
+
+       if (output_complete)
+               analog_data()->analog_segments().at(segment_id)->set_complete();
+}
+
 void MathSignal::reset_generation()
 {
        if (gen_thread_.joinable()) {
@@ -245,6 +289,8 @@ void MathSignal::begin_generation()
                return;
        }
 
+       disconnect(this, SLOT(on_data_received()));
+
        fnc_sig_sample_ = new sig_sample<double>(*this);
 
        exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
@@ -263,7 +309,25 @@ void MathSignal::begin_generation()
        exprtk_parser_->enable_unknown_symbol_resolver();
 
        if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
-               set_error_message(tr("Error in expression"));
+               QString error_details;
+               size_t error_count = exprtk_parser_->error_count();
+
+               for (size_t i = 0; i < error_count; i++) {
+                       typedef exprtk::parser_error::type error_t;
+                       error_t error = exprtk_parser_->get_error(i);
+                       exprtk::parser_error::update_error(error, expression_.toStdString());
+
+                       QString error_detail = tr("%1 at line %2, column %3: %4");
+                       if ((error_count > 1) && (i < (error_count - 1)))
+                               error_detail += "\n";
+
+                       error_details += error_detail \
+                               .arg(exprtk::parser_error::to_str(error.mode).c_str()) \
+                               .arg(error.line_no) \
+                               .arg(error.column_no) \
+                               .arg(error.diagnostic.c_str());
+               }
+               set_error_message(error_details);
        } else {
                // Resolve unknown scalars to signals and add them to the input signal list
                vector<string> unknowns;
@@ -272,7 +336,7 @@ void MathSignal::begin_generation()
                        signal_data* sig_data = signal_from_name(unknown);
                        const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
                        if (!signal || (!signal->analog_data())) {
-                               set_error_message(QString(tr("%1 isn't a valid signal")).arg(
+                               set_error_message(QString(tr("%1 isn't a valid analog signal")).arg(
                                        QString::fromStdString(unknown)));
                        } else
                                sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
@@ -280,6 +344,11 @@ void MathSignal::begin_generation()
        }
 
        if (error_message_.isEmpty()) {
+               // Connect to the session data notification if we have no input signals
+               if (input_signals_.empty())
+                       connect(&session_, SIGNAL(data_received()),
+                               this, SLOT(on_data_received()));
+
                gen_interrupt_ = false;
                gen_thread_ = std::thread(&MathSignal::generation_proc, this);
        }
@@ -320,8 +389,6 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp
 
 void MathSignal::generation_proc()
 {
-       uint32_t segment_id = 0;
-
        // Don't do anything until we have a valid sample rate
        do {
                if (use_custom_sample_rate_)
@@ -338,7 +405,8 @@ void MathSignal::generation_proc()
        if (gen_interrupt_)
                return;
 
-       shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
+       uint32_t segment_id = 0;
+       shared_ptr<Analog> analog = analog_data();
 
        // Create initial analog segment
        shared_ptr<AnalogSegment> output_segment =
@@ -368,26 +436,26 @@ void MathSignal::generation_proc()
                                processed_samples += sample_count;
 
                                // Notify consumers of this signal's data
-                               // TODO Does this work when a conversion is active?
                                samples_added(segment_id, start_sample, start_sample + processed_samples);
                        } while (!gen_interrupt_ && (processed_samples < samples_to_process));
                }
 
-               if (samples_to_process == 0) {
-                       if (segment_id < session_.get_highest_segment_id()) {
+               update_completeness(segment_id, output_sample_count);
+
+               if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
                                // Process next segment
                                segment_id++;
 
                                output_segment =
                                        make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
                                analog->push_segment(output_segment);
-                       } else {
-                               // All segments have been processed, wait for more input
-                               unique_lock<mutex> gen_input_lock(input_mutex_);
-                               gen_input_cond_.wait(gen_input_lock);
-                       }
                }
 
+               if (!gen_interrupt_ && (samples_to_process == 0)) {
+                       // Wait for more input
+                       unique_lock<mutex> gen_input_lock(input_mutex_);
+                       gen_input_cond_.wait(gen_input_lock);
+               }
        } while (!gen_interrupt_);
 }
 
@@ -404,8 +472,17 @@ signal_data* MathSignal::signal_from_name(const std::string& name)
                const QString sig_name = QString::fromStdString(name);
 
                for (const shared_ptr<SignalBase>& sb : signalbases)
-                       if (sb->name() == sig_name)
+                       if (sb->name() == sig_name) {
+                               if (!sb->analog_data())
+                                       continue;
+
+                               connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
+                                       this, SLOT(on_data_received()));
+                               connect(sb->analog_data().get(), SIGNAL(segment_completed()),
+                                       this, SLOT(on_data_received()));
+
                                return &(input_signals_.insert({name, signal_data(sb)}).first->second);
+                       }
        }
 
        return nullptr;
@@ -430,7 +507,7 @@ void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id
        sig_data->sample_num = sample_num;
        sig_data->sample_value = segment->get_sample(sample_num);
 
-       // We only have a reference if this signal is used as a scalar,
+       // We only have a reference if this signal is used as a scalar;
        // if it's used by a function, it's null
        if (sig_data->ref)
                *(sig_data->ref) = sig_data->sample_value;
@@ -440,6 +517,10 @@ void MathSignal::on_capture_state_changed(int state)
 {
        if (state == Session::Running)
                begin_generation();
+
+       // Make sure we don't miss any input samples, just in case
+       if (state == Session::Stopped)
+               gen_input_cond_.notify_one();
 }
 
 void MathSignal::on_data_received()