Added rule validation

This commit is contained in:
Borna Rajkovic 2022-04-10 14:23:18 +02:00
parent 23e63f7b6f
commit e8505f9cef
13 changed files with 284 additions and 13 deletions

View File

@ -39,5 +39,5 @@ add_executable(SchemeEditor
comdel/parser/comdellexer.cpp
main.cpp
mainwindow.ui
)
comdel/domain/comdelvalidator.cpp comdel/domain/comdelvalidator.h)
target_link_libraries(SchemeEditor Qt5::Core Qt5::Gui Qt5::Widgets)

View File

@ -16,4 +16,12 @@ long long AddressSpace::getEnd() {
return end;
}
bool AddressSpace::contains(long long int address) {
return address >= start && address < end;
}
bool AddressSpace::contains(long long int pstart, long long int pend) {
return pstart >= start && pend < end;
}
} // namespace domain

View File

@ -17,6 +17,9 @@ public:
std::string getName();
long long getStart();
long long getEnd();
bool contains(long long int address);
bool contains(long long int start, long long int end);
};
} // namespace domain

View File

@ -0,0 +1,95 @@
#include "comdelvalidator.h"
namespace domain {
std::vector<ValidationError> ComdelValidator::validateSchema(Schema &schema, ValidationContext context) {
std::vector<ValidationError> errors;
context.instance = nullptr;
context.attribute = nullptr;
for(auto &inst: schema.instances) {
auto *instance = dynamic_cast<ComponentInstance*>(inst);
if(instance) {
auto result = validateComponent(instance, context);
errors.insert(errors.end(), result.begin(), result.end());
}
}
return errors;
}
std::vector<ValidationError> ComdelValidator::validateComponent(ComponentInstance *instance, ValidationContext context) {
std::vector<ValidationError> errors;
context.instance = instance;
context.attributes.clear();
for(auto &attribute: instance->attributes) {
context.attributes[attribute->name] = attribute->value;
}
for(auto &rule: instance->component.getRules()) {
auto result = validateRule(rule, context);
if(result) {
errors.push_back(*result);
}
}
for(auto &attribute: instance->attributes) {
auto result = validateAttribute(attribute, context);
errors.insert(errors.end(), result.begin(), result.end());
}
return errors;
}
std::vector<ValidationError> ComdelValidator::validateAttribute(InstanceAttribute *attribute, ValidationContext context) {
std::vector<ValidationError> errors;
if(attribute->attribute.getPopup()) {
Popup popup = *attribute->attribute.getPopup();
context.attribute = attribute;
context.attributes = std::map<std::string, Value>{{attribute->name, attribute->value}};
for(auto &rule: popup.getRules()) {
auto result = validateRule(rule, context);
if(result) {
errors.push_back(*result);
}
}
}
return errors;
}
std::optional<ValidationError> ComdelValidator::validateRule(Rule rule, ValidationContext context) {
RuleContext ruleContext;
ruleContext.addressSpaces = context.addressSpaces;
ruleContext.attributes = context.attributes;
ruleContext.function = callbacks;
auto action = rule.evaluate(ruleContext);
if (action) {
std::string message = this->populateMessage(action->getMessage(), context);
return ValidationError{context.instance, context.attribute, action->getType(), message};
}
return nullopt;
}
std::string ComdelValidator::populateMessage(string source, ValidationContext context) {
for(auto &[key, value]: context.attributes) {
source = replacePlaceholder(source, key, value);
}
return source;
}
string ComdelValidator::replacePlaceholder(string source, string key, Value value) {
key = "{" + key + "}";
auto placeholderValue = value.string();
auto found = source.find(key);
while(found != string::npos) {
source.replace(found, key.length(), placeholderValue);
found = source.find(key);
}
return source;
}
}

View File

@ -0,0 +1,43 @@
#ifndef COMDEL_VALIDATOR_H
#define COMDEL_VALIDATOR_H
#include "instance.h"
#include "schema.h"
namespace domain {
struct ValidationError
{
Instance *instance;
InstanceAttribute *attribute;
Action::ActionType type;
std::string message;
};
struct ValidationContext {
Instance *instance;
InstanceAttribute *attribute;
std::map<std::string, AddressSpace> addressSpaces;
std::map<std::string, Value> attributes;
};
class ComdelValidator
{
public:
std::vector<ValidationError> validateSchema(Schema& schema, ValidationContext context);
std::vector<ValidationError> validateComponent(ComponentInstance *instance, ValidationContext context);
std::vector<ValidationError> validateAttribute(InstanceAttribute *attribute, ValidationContext context);
std::optional<ValidationError> validateRule(Rule rule, ValidationContext context);
ComdelValidator(std::map<std::string, FunctionCallback> callbacks): callbacks(callbacks) {}
private:
std::map<std::string, FunctionCallback> callbacks;
std::string populateMessage(string basicString, ValidationContext context);
string replacePlaceholder(string basicString, const string basicString1, Value value);
};
}
#endif //COMDEL_VALIDATOR_H

View File

@ -2,4 +2,44 @@
namespace domain {
FunctionSignature add(std::string name, std::vector<Value::ValueType> types, FunctionCallback callback) {
return {name, types, callback};
}
std::vector<FunctionSignature> getSupportedFunctions() {
std::vector<FunctionSignature> s;
s.push_back(add("divisible", std::vector<Value::ValueType>{Value::INT, Value::INT},[](std::vector<Value> values) {
long long first = values[0].asInt();
long long second = values[1].asInt();
return (first % second) == 0;
}));
s.push_back(add("less_then", std::vector<Value::ValueType>{Value::INT, Value::INT}, [](std::vector<Value> values) {
long long first = values[0].asInt();
long long second = values[1].asInt();
return first < second;
}));
s.push_back(add("greater_then", std::vector<Value::ValueType>{Value::INT, Value::INT}, [](std::vector<Value> values) {
long long first = values[0].asInt();
long long second = values[1].asInt();
return first > second;
}));
s.push_back(add("contains_address", std::vector<Value::ValueType>{Value::ADDRESS_SPACE, Value::INT}, [](std::vector<Value> values) {
AddressSpace space = values[0].asAddressSpace();
long long address = values[1].asInt();
return space.contains(address);
}));
s.push_back(add("contains", std::vector<Value::ValueType>{Value::ADDRESS_SPACE, Value::INT, Value::INT}, [](std::vector<Value> values) {
AddressSpace space = values[0].asAddressSpace();
long long start = values[1].asInt();
long long size = values[1].asInt();
return space.contains(start, start + size);
}));
s.push_back(add("unique", std::vector<Value::ValueType>{Value::ADDRESS_SPACE, Value::INT, Value::INT}, [](std::vector<Value> values) {
return true;
}));
return s;
}
} // namespace domain

View File

@ -11,10 +11,13 @@ typedef std::function<bool (std::vector<Value>)> FunctionCallback;
struct FunctionSignature {
std::string name;
std::vector<Value::ValueType> params;
FunctionCallback callback;
FunctionSignature(std::string name, std::vector<Value::ValueType> params): name(name), params(params) {}
FunctionSignature(std::string name, std::vector<Value::ValueType> params, FunctionCallback callback): name(name), params(params), callback(callback) {}
};
std::vector<FunctionSignature> getSupportedFunctions();
} // namespace domain
#endif // DOMAIN_FUNCTIONSIGNATURE_H

View File

@ -9,7 +9,9 @@ Condition::Condition(std::string function, std::vector<Value> params, bool negat
bool Condition::evaluate(RuleContext &context) {
std::vector<Value> request;
for(uint i=0; i<params.size(); i++) {
if(params[i].isType(Value::ATTRIBUTE_REFERENCE)) {
if(params[i].isType(Value::ADDRESS_SPACE_REFERENCE)) {
request.push_back(Value::fromAddressSpace(context.addressSpaces.at(params[i].asReference())));
} else if(params[i].isType(Value::ATTRIBUTE_REFERENCE)) {
request.push_back(context.attributes[params[i].asReference()]);
} else {
request.push_back(params[i]);

View File

@ -706,7 +706,7 @@ std::optional<Condition> SchemaCreator::loadCondition(ConditionNode node)
if(signatures[i].params[j] == Value::ADDRESS_SPACE) {
if(hasAddressSpace(type.asReference())) {
exists = true;
type = Value::fromReference(type.asReference(), Value::ADDRESS_SPACE);
type = Value::fromReference(type.asReference(), Value::ADDRESS_SPACE_REFERENCE);
}
}

View File

@ -4,6 +4,32 @@
namespace domain {
std::string Value::string() {
switch (type) {
case INT:
return std::to_string(intValue);
case BOOL:
return boolValue ? "true" : "false";
case STRING:
return stringValue;
case NIL:
return "null";
case UNDEFINED:
return "undefined";
case ADDRESS_SPACE:
return "AddressSpace::" + addressSpace->getName();
case WIRE_REFERENCE:
return "Wire::" + reference;
case ADDRESS_SPACE_REFERENCE:
return "AddressSpace::" + reference;
case ATTRIBUTE_REFERENCE:
return "Attribute::" + reference;
default:
return "unknown";
}
}
Value::ValueType Value::getType() {
return type;
}
@ -36,7 +62,7 @@ AddressSpace Value::asAddressSpace() {
throw std::exception();
}
std::string Value::asReference() {
if(isType(Value::WIRE_REFERENCE) || isType(Value::ATTRIBUTE_REFERENCE) || isType(Value::UNDEFINED)) {
if(isType(Value::WIRE_REFERENCE) || isType(Value::ADDRESS_SPACE_REFERENCE) || isType(Value::ATTRIBUTE_REFERENCE) || isType(Value::UNDEFINED)) {
return reference;
}
throw std::exception();

View File

@ -17,6 +17,7 @@ public:
STRING,
BOOL,
ADDRESS_SPACE,
ADDRESS_SPACE_REFERENCE,
ATTRIBUTE_REFERENCE,
WIRE_REFERENCE,
NIL,
@ -38,6 +39,8 @@ public:
this->type = UNDEFINED;
}
std::string string();
ValueType getType();
bool isType(ValueType type);

View File

@ -11,6 +11,7 @@
#include <iostream>
#include <QPlainTextEdit>
#include <sstream>
#include <comdel/domain/comdelvalidator.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
@ -21,13 +22,7 @@ MainWindow::MainWindow(QWidget *parent)
setupUi();
// define allowed methods
signatures.push_back(domain::FunctionSignature("divisible", std::vector<domain::Value::ValueType>{domain::Value::INT, domain::Value::INT}));
signatures.push_back(domain::FunctionSignature("less_then", std::vector<domain::Value::ValueType>{domain::Value::INT, domain::Value::INT}));
signatures.push_back(domain::FunctionSignature("greater_then", std::vector<domain::Value::ValueType>{domain::Value::INT, domain::Value::INT}));
signatures.push_back(domain::FunctionSignature("contains_address", std::vector<domain::Value::ValueType>{domain::Value::ADDRESS_SPACE, domain::Value::INT}));
signatures.push_back(domain::FunctionSignature("contains", std::vector<domain::Value::ValueType>{domain::Value::ADDRESS_SPACE, domain::Value::INT, domain::Value::INT}));
signatures.push_back(domain::FunctionSignature("unique", std::vector<domain::Value::ValueType>{domain::Value::ADDRESS_SPACE, domain::Value::INT, domain::Value::INT}));
signatures = domain::getSupportedFunctions();
}
void MainWindow::setupUi()
@ -40,6 +35,8 @@ void MainWindow::setupUi()
ui->toolBar->addAction("Load schema", this, &MainWindow::onLoadSchema);
ui->toolBar->addAction("Load test", this, &MainWindow::onTestModal);
connect(ui->actionValidate, &QAction::triggered, this, &MainWindow::onValidateSchema);
// setup central content
libraryDisplay = new display::Library();
@ -154,10 +151,58 @@ void MainWindow::onLoadSchema() {
schemaDisplay->setSchema(schema);
}
}
}
void MainWindow::onValidateSchema(bool /*toggled*/) {
this->validationErrors.clear();
std::map<std::string, domain::FunctionCallback> callbacks;
auto functions = domain::getSupportedFunctions();
for(auto &func: functions) {
callbacks[func.name] = func.callback;
}
domain::ComdelValidator validator{callbacks};
domain::ValidationContext context;
context.instance = nullptr;
context.attribute = nullptr;
context.addressSpaces = {};
for(auto &lib: library->getAddressSpaces()) {
context.addressSpaces.insert(std::make_pair(lib.getName(), lib));
}
auto errors = validator.validateSchema(*schema, context);
errors.erase(std::remove_if(
errors.begin(), errors.end(), [](const domain::ValidationError& error) { return error.type == domain::Action::WARNING;}),
errors.end());
std::ostringstream buff;
for(auto err: errors) {
if(err.instance != NULL) {
buff << err.instance->name;
}
if(err.attribute != NULL) {
buff << "::" << err.attribute->name;
}
if(err.type == domain::Action::ERROR) {
buff << " [ERROR] ";
} else {
buff << " [WARNING] ";
}
buff << err.message << std::endl;
}
log->appendPlainText(QString::fromStdString(buff.str()));
validationErrors = errors;
}
void MainWindow::clear() {
validationErrors.clear();
schema = std::nullopt;
library = std::nullopt;

View File

@ -10,6 +10,7 @@
#include <comdel/domain/library.h>
#include <comdel/domain/schema.h>
#include <QPlainTextEdit>
#include <comdel/domain/comdelvalidator.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
@ -38,9 +39,11 @@ private slots:
void onLoadLibrary();
void onLoadSchema();
void onTestModal();
void onValidateSchema(bool toggled);
private:
Ui::MainWindow *ui;
QPlainTextEdit *log;
std::vector<domain::ValidationError> validationErrors;
};
#endif // MAIN_WINDOW_H