diff --git a/CMakeLists.txt b/CMakeLists.txt index b01b90fc5489..ca1b5f0a7349 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -812,6 +812,8 @@ endif() include_directories(Externals/picojson) +add_subdirectory(Externals/expr) + add_subdirectory(Externals/rangeset) ######################################## diff --git a/Externals/expr/CMakeLists.txt b/Externals/expr/CMakeLists.txt new file mode 100644 index 000000000000..4eb00fa0e954 --- /dev/null +++ b/Externals/expr/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(expr INTERFACE) +target_include_directories(expr INTERFACE include/) diff --git a/Externals/expr/LICENSE b/Externals/expr/LICENSE new file mode 100644 index 000000000000..44113db6d82a --- /dev/null +++ b/Externals/expr/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Externals/expr/include/expr.h b/Externals/expr/include/expr.h new file mode 100644 index 000000000000..f531f378dd98 --- /dev/null +++ b/Externals/expr/include/expr.h @@ -0,0 +1,930 @@ +#ifndef EXPR_H +#define EXPR_H + +#ifdef _MSC_VER +#pragma warning(push) +// Disable warning for zero-sized array: +#pragma warning(disable : 4200) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for isspace */ +#include +#include /* for pow */ +#include +#include +#include +#include + +/* + * Simple expandable vector implementation + */ +static int vec_expand(char **buf, int *length, int *cap, int memsz) { + if (*length + 1 > *cap) { + void *ptr; + int n = (*cap == 0) ? 1 : *cap << 1; + ptr = realloc(*buf, n * memsz); + if (ptr == NULL) { + return -1; /* allocation failed */ + } + *buf = (char *)ptr; + *cap = n; + } + return 0; +} +#define vec(T) \ + struct { \ + T *buf; \ + int len; \ + int cap; \ + } +#define vec_init() \ + { NULL, 0, 0 } +#define vec_len(v) ((v)->len) +#define vec_unpack(v) \ + (char **)&(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) +#define vec_push(v, val) \ + vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) +#define vec_nth(v, i) (v)->buf[i] +#define vec_peek(v) (v)->buf[(v)->len - 1] +#define vec_pop(v) (v)->buf[--(v)->len] +#define vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) +#define vec_foreach(v, var, iter) \ + if ((v)->len > 0) \ + for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \ + ++(iter)) + +/* + * Expression data types + */ +struct expr; +struct expr_func; + +enum expr_type { + OP_UNKNOWN, + OP_UNARY_MINUS, + OP_UNARY_LOGICAL_NOT, + OP_UNARY_BITWISE_NOT, + + OP_POWER, + OP_DIVIDE, + OP_MULTIPLY, + OP_REMAINDER, + + OP_PLUS, + OP_MINUS, + + OP_SHL, + OP_SHR, + + OP_LT, + OP_LE, + OP_GT, + OP_GE, + OP_EQ, + OP_NE, + + OP_BITWISE_AND, + OP_BITWISE_OR, + OP_BITWISE_XOR, + + OP_LOGICAL_AND, + OP_LOGICAL_OR, + + OP_ASSIGN, + OP_COMMA, + + OP_CONST, + OP_VAR, + OP_FUNC, +}; + +static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, + 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}; + +typedef vec(struct expr) vec_expr_t; +typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context); +typedef double (*exprfn_t)(struct expr_func *f, vec_expr_t *args, void *context); + +struct expr { + enum expr_type type; + union { + struct { + double value; + } num; + struct { + double *value; + } var; + struct { + vec_expr_t args; + } op; + struct { + struct expr_func *f; + vec_expr_t args; + void *context; + } func; + } param; +}; + +#define expr_init() \ + { (enum expr_type)0 } + +struct expr_string { + const char *s; + int n; +}; +struct expr_arg { + int oslen; + int eslen; + vec_expr_t args; +}; + +typedef vec(struct expr_string) vec_str_t; +typedef vec(struct expr_arg) vec_arg_t; + +static int expr_is_unary(enum expr_type op) { + return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT || + op == OP_UNARY_BITWISE_NOT; +} + +static int expr_is_binary(enum expr_type op) { + return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR && + op != OP_FUNC && op != OP_UNKNOWN; +} + +static int expr_prec(enum expr_type a, enum expr_type b) { + int left = + expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA; + return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]); +} + +#define isfirstvarchr(c) \ + (((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$') +#define isvarchr(c) \ + (((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$' || \ + c == '#' || (c >= '0' && c <= '9')) + +static struct { + const char *s; + const enum expr_type op; +} OPS[] = { + {"-u", OP_UNARY_MINUS}, + {"!u", OP_UNARY_LOGICAL_NOT}, + {"~u", OP_UNARY_BITWISE_NOT}, + {"**", OP_POWER}, + {"*", OP_MULTIPLY}, + {"/", OP_DIVIDE}, + {"%", OP_REMAINDER}, + {"+", OP_PLUS}, + {"-", OP_MINUS}, + {"<<", OP_SHL}, + {">>", OP_SHR}, + {"<", OP_LT}, + {"<=", OP_LE}, + {">", OP_GT}, + {">=", OP_GE}, + {"==", OP_EQ}, + {"!=", OP_NE}, + {"&", OP_BITWISE_AND}, + {"|", OP_BITWISE_OR}, + {"^", OP_BITWISE_XOR}, + {"&&", OP_LOGICAL_AND}, + {"||", OP_LOGICAL_OR}, + {"=", OP_ASSIGN}, + {",", OP_COMMA}, + + /* These are used by lexer and must be ignored by parser, so we put + them at the end */ + {"-", OP_UNARY_MINUS}, + {"!", OP_UNARY_LOGICAL_NOT}, + {"~", OP_UNARY_BITWISE_NOT}, +}; + +static enum expr_type expr_op(const char *s, size_t len, int unary) { + for (unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) { + if (strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0 && + (unary == -1 || expr_is_unary(OPS[i].op) == unary)) { + return OPS[i].op; + } + } + return OP_UNKNOWN; +} + +static double expr_parse_number(const char* s, size_t len) { + double num = 0; + char buf[32]; + char* sz = buf; + char* end = NULL; + if (len >= sizeof(buf)) { + sz = (char*)calloc(1, len + 1); + if (sz == NULL) { + return NAN; + } + } + strncpy(sz, s, len); + sz[len] = '\0'; + num = strtod(sz, &end); + if (sz != buf) { + free(sz); + } + return (end == sz + len ? num : NAN); +} + +/* + * Functions + */ +struct expr_func { + const char *name; + exprfn_t f; + exprfn_cleanup_t cleanup; + size_t ctxsz; +}; + +static struct expr_func *expr_get_func(struct expr_func *funcs, const char *s, + size_t len) { + for (struct expr_func *f = funcs; f->name; f++) { + if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) { + return f; + } + } + return NULL; +} + +/* + * Variables + */ +struct expr_var { + double value; + struct expr_var *next; + char name[]; +}; + +struct expr_var_list { + struct expr_var *head; +}; + +static struct expr_var *expr_get_var(struct expr_var_list *vars, const char *s, + size_t len) { + struct expr_var *v = NULL; + if (len == 0 || !isfirstvarchr(*s)) { + return NULL; + } + for (v = vars->head; v; v = v->next) { + if (strlen(v->name) == len && strncmp(v->name, s, len) == 0) { + return v; + } + } + v = (struct expr_var *)calloc(1, sizeof(struct expr_var) + len + 1); + if (v == NULL) { + return NULL; /* allocation failed */ + } + v->next = vars->head; + v->value = 0; + strncpy(v->name, s, len); + v->name[len] = '\0'; + vars->head = v; + return v; +} + +static int64_t to_int(double x) { + if (isnan(x)) { + return 0; + } else if (isinf(x) != 0) { + return INT64_MAX * isinf(x); + } else { + return (int64_t)x; + } +} + +static double expr_eval(struct expr *e) { + double n; + switch (e->type) { + case OP_UNARY_MINUS: + return -(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_LOGICAL_NOT: + return !(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_BITWISE_NOT: + return ~(to_int(expr_eval(&e->param.op.args.buf[0]))); + case OP_POWER: + return pow(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_MULTIPLY: + return expr_eval(&e->param.op.args.buf[0]) * + expr_eval(&e->param.op.args.buf[1]); + case OP_DIVIDE: + return expr_eval(&e->param.op.args.buf[0]) / + expr_eval(&e->param.op.args.buf[1]); + case OP_REMAINDER: + return fmod(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_PLUS: + return expr_eval(&e->param.op.args.buf[0]) + + expr_eval(&e->param.op.args.buf[1]); + case OP_MINUS: + return expr_eval(&e->param.op.args.buf[0]) - + expr_eval(&e->param.op.args.buf[1]); + case OP_SHL: + return to_int(expr_eval(&e->param.op.args.buf[0])) + << to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_SHR: + return to_int(expr_eval(&e->param.op.args.buf[0])) >> + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LT: + return expr_eval(&e->param.op.args.buf[0]) < + expr_eval(&e->param.op.args.buf[1]); + case OP_LE: + return expr_eval(&e->param.op.args.buf[0]) <= + expr_eval(&e->param.op.args.buf[1]); + case OP_GT: + return expr_eval(&e->param.op.args.buf[0]) > + expr_eval(&e->param.op.args.buf[1]); + case OP_GE: + return expr_eval(&e->param.op.args.buf[0]) >= + expr_eval(&e->param.op.args.buf[1]); + case OP_EQ: + return expr_eval(&e->param.op.args.buf[0]) == + expr_eval(&e->param.op.args.buf[1]); + case OP_NE: + return expr_eval(&e->param.op.args.buf[0]) != + expr_eval(&e->param.op.args.buf[1]); + case OP_BITWISE_AND: + return to_int(expr_eval(&e->param.op.args.buf[0])) & + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_OR: + return to_int(expr_eval(&e->param.op.args.buf[0])) | + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_XOR: + return to_int(expr_eval(&e->param.op.args.buf[0])) ^ + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LOGICAL_AND: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0) { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_LOGICAL_OR: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0 && !isnan(n)) { + return n; + } else { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_ASSIGN: + n = expr_eval(&e->param.op.args.buf[1]); + if (vec_nth(&e->param.op.args, 0).type == OP_VAR) { + *e->param.op.args.buf[0].param.var.value = n; + } + return n; + case OP_COMMA: + expr_eval(&e->param.op.args.buf[0]); + return expr_eval(&e->param.op.args.buf[1]); + case OP_CONST: + return e->param.num.value; + case OP_VAR: + return *e->param.var.value; + case OP_FUNC: + return e->param.func.f->f(e->param.func.f, &e->param.func.args, + e->param.func.context); + default: + return NAN; + } +} + +#define EXPR_TOP (1 << 0) +#define EXPR_TOPEN (1 << 1) +#define EXPR_TCLOSE (1 << 2) +#define EXPR_TNUMBER (1 << 3) +#define EXPR_TWORD (1 << 4) +#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD) + +#define EXPR_UNARY (1 << 5) +#define EXPR_COMMA (1 << 6) + +static int expr_next_token(const char *s, size_t len, int *flags) { + unsigned int i = 0; + if (len == 0) { + return 0; + } + char c = s[0]; + if (c == '#') { + for (; i < len && s[i] != '\n'; i++) + ; + return i; + } else if (c == '\n') { + for (; i < len && isspace(s[i]); i++) + ; + if (*flags & EXPR_TOP) { + if (i == len || s[i] == ')') { + *flags = *flags & (~EXPR_COMMA); + } else { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA; + } + } + return i; + } else if (isspace(c)) { + while (i < len && isspace(s[i]) && s[i] != '\n') { + i++; + } + return i; + } else if (isdigit(c)) { + if ((*flags & EXPR_TNUMBER) == 0) { + return -1; // unexpected number + } + *flags = EXPR_TOP | EXPR_TCLOSE; + if (c == '0') { + i++; + if (i < len && (s[i] == 'x' || s[i] == 'X')) { + i++; + for (; i < len && isxdigit(s[i]); i++) + ; + return i; + } + } + for (; i < len && (s[i] == '.' || isdigit(s[i])); i++) + ; + if (i < len && (s[i] == 'e' || s[i] == 'E')) { + i++; + if (i < len && (s[i] == '+' || s[i] == '-')) + i++; + for (; i < len && isdigit(s[i]); i++) + ; + } + return i; + } else if (isfirstvarchr(c)) { + if ((*flags & EXPR_TWORD) == 0) { + return -2; // unexpected word + } + *flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE; + while ((isvarchr(c)) && i < len) { + i++; + c = s[i]; + } + return i; + } else if (c == '(' || c == ')') { + if (c == '(' && (*flags & EXPR_TOPEN) != 0) { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE; + } else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) { + *flags = EXPR_TOP | EXPR_TCLOSE; + } else { + return -3; // unexpected parenthesis + } + return 1; + } else { + if ((*flags & EXPR_TOP) == 0) { + if (expr_op(&c, 1, 1) == OP_UNKNOWN) { + return -4; // missing expected operand + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY; + return 1; + } else { + int found = 0; + while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && i < len) { + if (expr_op(s, i + 1, 0) != OP_UNKNOWN) { + found = 1; + } else if (found) { + break; + } + i++; + c = s[i]; + } + if (!found) { + return -5; // unknown operator + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN; + return i; + } + } +} + +#define EXPR_PAREN_ALLOWED 0 +#define EXPR_PAREN_EXPECTED 1 +#define EXPR_PAREN_FORBIDDEN 2 + +static int expr_bind(const char *s, size_t len, vec_expr_t *es) { + enum expr_type op = expr_op(s, len, -1); + if (op == OP_UNKNOWN) { + return -1; + } + + if (expr_is_unary(op)) { + if (vec_len(es) < 1) { + return -1; + } + struct expr arg = vec_pop(es); + struct expr unary = expr_init(); + unary.type = op; + vec_push(&unary.param.op.args, arg); + vec_push(es, unary); + } else { + if (vec_len(es) < 2) { + return -1; + } + struct expr b = vec_pop(es); + struct expr a = vec_pop(es); + struct expr binary = expr_init(); + binary.type = op; + if (op == OP_ASSIGN && a.type != OP_VAR) { + return -1; /* Bad assignment */ + } + vec_push(&binary.param.op.args, a); + vec_push(&binary.param.op.args, b); + vec_push(es, binary); + } + return 0; +} + +static struct expr expr_const(double value) { + struct expr e = expr_init(); + e.type = OP_CONST; + e.param.num.value = value; + return e; +} + +static struct expr expr_varref(struct expr_var *v) { + struct expr e = expr_init(); + e.type = OP_VAR; + e.param.var.value = &v->value; + return e; +} + +static struct expr expr_binary(enum expr_type type, struct expr a, + struct expr b) { + struct expr e = expr_init(); + e.type = type; + vec_push(&e.param.op.args, a); + vec_push(&e.param.op.args, b); + return e; +} + +static inline void expr_copy(struct expr *dst, struct expr *src) { + int i; + struct expr arg; + dst->type = src->type; + if (src->type == OP_FUNC) { + dst->param.func.f = src->param.func.f; + vec_foreach(&src->param.func.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.func.args, tmp); + } + if (src->param.func.f->ctxsz > 0) { + dst->param.func.context = calloc(1, src->param.func.f->ctxsz); + } + } else if (src->type == OP_CONST) { + dst->param.num.value = src->param.num.value; + } else if (src->type == OP_VAR) { + dst->param.var.value = src->param.var.value; + } else { + vec_foreach(&src->param.op.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.op.args, tmp); + } + } +} + +static void expr_destroy_args(struct expr *e); + +static struct expr *expr_create(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs) { + double num; + const char *id = NULL; + size_t idn = 0; + + struct expr *result = NULL; + + vec_expr_t es = vec_init(); + vec_str_t os = vec_init(); + vec_arg_t as = vec_init(); + + struct macro { + char *name; + vec_expr_t body; + }; + vec(struct macro) macros = vec_init(); + + int flags = EXPR_TDEFAULT; + int paren = EXPR_PAREN_ALLOWED; + for (;;) { + int n = expr_next_token(s, len, &flags); + if (n == 0) { + break; + } else if (n < 0) { + goto cleanup; + } + const char *tok = s; + s = s + n; + len = len - n; + if (*tok == '#') { + continue; + } + if (flags & EXPR_UNARY) { + if (n == 1) { + switch (*tok) { + case '-': + tok = "-u"; + break; + case '~': + tok = "~u"; + break; + case '!': + tok = "!u"; + break; + default: + goto cleanup; + } + n = 2; + } + } + if (*tok == '\n' && (flags & EXPR_COMMA)) { + flags = flags & (~EXPR_COMMA); + n = 1; + tok = ","; + } + if (isspace(*tok)) { + continue; + } + int paren_next = EXPR_PAREN_ALLOWED; + + if (idn > 0) { + struct expr_var *v; + if (n == 1 && *tok == '(') { + int i; + int has_macro = 0; + struct macro m; + vec_foreach(¯os, m, i) { + if (strlen(m.name) == idn && strncmp(m.name, id, idn) == 0) { + has_macro = 1; + break; + } + } + if ((idn == 1 && id[0] == '$') || has_macro || + expr_get_func(funcs, id, idn) != NULL) { + struct expr_string str = {id, (int)idn}; + vec_push(&os, str); + paren = EXPR_PAREN_EXPECTED; + } else { + goto cleanup; /* invalid function name */ + } + } else if ((v = expr_get_var(vars, id, idn)) != NULL) { + vec_push(&es, expr_varref(v)); + paren = EXPR_PAREN_FORBIDDEN; + } + id = NULL; + idn = 0; + } + + if (n == 1 && *tok == '(') { + if (paren == EXPR_PAREN_EXPECTED) { + struct expr_string str = {"{", 1}; + vec_push(&os, str); + struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()}; + vec_push(&as, arg); + } else if (paren == EXPR_PAREN_ALLOWED) { + struct expr_string str = {"(", 1}; + vec_push(&os, str); + } else { + goto cleanup; // Bad call + } + } else if (paren == EXPR_PAREN_EXPECTED) { + goto cleanup; // Bad call + } else if (n == 1 && *tok == ')') { + int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0); + while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' && + *vec_peek(&os).s != '{') { + struct expr_string str = vec_pop(&os); + if (expr_bind(str.s, str.n, &es) == -1) { + goto cleanup; + } + } + if (vec_len(&os) == 0) { + goto cleanup; // Bad parens + } + struct expr_string str = vec_pop(&os); + if (str.n == 1 && *str.s == '{') { + str = vec_pop(&os); + struct expr_arg arg = vec_pop(&as); + if (vec_len(&es) > arg.eslen) { + vec_push(&arg.args, vec_pop(&es)); + } + if (str.n == 1 && str.s[0] == '$') { + if (vec_len(&arg.args) < 1) { + vec_free(&arg.args); + goto cleanup; /* too few arguments for $() function */ + } + struct expr *u = &vec_nth(&arg.args, 0); + if (u->type != OP_VAR) { + vec_free(&arg.args); + goto cleanup; /* first argument is not a variable */ + } + for (struct expr_var *v = vars->head; v; v = v->next) { + if (&v->value == u->param.var.value) { + struct macro m = {v->name, arg.args}; + vec_push(¯os, m); + break; + } + } + vec_push(&es, expr_const(0)); + } else { + int i = 0; + int found = -1; + struct macro m; + vec_foreach(¯os, m, i) { + if (strlen(m.name) == (size_t)str.n && + strncmp(m.name, str.s, str.n) == 0) { + found = i; + } + } + if (found != -1) { + m = vec_nth(¯os, found); + struct expr root = expr_const(0); + struct expr *p = &root; + /* Assign macro parameters */ + for (int j = 0; j < vec_len(&arg.args); j++) { + char varname[12]; + snprintf(varname, sizeof(varname), "$%d", (j + 1)); + struct expr_var *v = expr_get_var(vars, varname, strlen(varname)); + struct expr ev = expr_varref(v); + struct expr assign = + expr_binary(OP_ASSIGN, ev, vec_nth(&arg.args, j)); + *p = expr_binary(OP_COMMA, assign, expr_const(0)); + p = &vec_nth(&p->param.op.args, 1); + } + /* Expand macro body */ + for (int j = 1; j < vec_len(&m.body); j++) { + if (j < vec_len(&m.body) - 1) { + *p = expr_binary(OP_COMMA, expr_const(0), expr_const(0)); + expr_copy(&vec_nth(&p->param.op.args, 0), &vec_nth(&m.body, j)); + } else { + expr_copy(p, &vec_nth(&m.body, j)); + } + p = &vec_nth(&p->param.op.args, 1); + } + vec_push(&es, root); + vec_free(&arg.args); + } else { + struct expr_func *f = expr_get_func(funcs, str.s, str.n); + struct expr bound_func = expr_init(); + bound_func.type = OP_FUNC; + bound_func.param.func.f = f; + bound_func.param.func.args = arg.args; + if (f->ctxsz > 0) { + void *p = calloc(1, f->ctxsz); + if (p == NULL) { + goto cleanup; /* allocation failed */ + } + bound_func.param.func.context = p; + } + vec_push(&es, bound_func); + } + } + } + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (!isnan(num = expr_parse_number(tok, n))) { + vec_push(&es, expr_const(num)); + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (expr_op(tok, n, -1) != OP_UNKNOWN) { + enum expr_type op = expr_op(tok, n, -1); + struct expr_string o2 = {NULL, 0}; + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } + for (;;) { + if (n == 1 && *tok == ',' && vec_len(&os) > 0) { + struct expr_string str = vec_peek(&os); + if (str.n == 1 && *str.s == '{') { + struct expr e = vec_pop(&es); + vec_push(&vec_peek(&as).args, e); + break; + } + } + enum expr_type type2 = expr_op(o2.s, o2.n, -1); + if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) { + struct expr_string str = {tok, n}; + vec_push(&os, str); + break; + } + + if (expr_bind(o2.s, o2.n, &es) == -1) { + goto cleanup; + } + (void)vec_pop(&os); + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } else { + o2.n = 0; + } + } + } else { + if (n > 0 && !isdigit(*tok)) { + /* Valid identifier, a variable or a function */ + id = tok; + idn = n; + } else { + goto cleanup; // Bad variable name, e.g. '2.3.4' or '4ever' + } + } + paren = paren_next; + } + + if (idn > 0) { + vec_push(&es, expr_varref(expr_get_var(vars, id, idn))); + } + + while (vec_len(&os) > 0) { + struct expr_string rest = vec_pop(&os); + if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) { + goto cleanup; // Bad paren + } + if (expr_bind(rest.s, rest.n, &es) == -1) { + goto cleanup; + } + } + + result = (struct expr *)calloc(1, sizeof(struct expr)); + if (result != NULL) { + if (vec_len(&es) == 0) { + result->type = OP_CONST; + } else { + *result = vec_pop(&es); + } + } + + int i, j; + struct macro m; + struct expr e; + struct expr_arg a; +cleanup: + vec_foreach(¯os, m, i) { + struct expr e2; + vec_foreach(&m.body, e2, j) { expr_destroy_args(&e2); } + vec_free(&m.body); + } + vec_free(¯os); + + vec_foreach(&es, e, i) { expr_destroy_args(&e); } + vec_free(&es); + + vec_foreach(&as, a, i) { + vec_foreach(&a.args, e, j) { expr_destroy_args(&e); } + vec_free(&a.args); + } + vec_free(&as); + + /*vec_foreach(&os, o, i) {vec_free(&m.body);}*/ + vec_free(&os); + return result; +} + +static void expr_destroy_args(struct expr *e) { + int i; + struct expr arg; + if (e->type == OP_FUNC) { + vec_foreach(&e->param.func.args, arg, i) { expr_destroy_args(&arg); } + vec_free(&e->param.func.args); + if (e->param.func.context != NULL) { + if (e->param.func.f->cleanup != NULL) { + e->param.func.f->cleanup(e->param.func.f, e->param.func.context); + } + free(e->param.func.context); + } + } else if (e->type != OP_CONST && e->type != OP_VAR) { + vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); } + vec_free(&e->param.op.args); + } +} + +static void expr_destroy(struct expr *e, struct expr_var_list *vars) { + if (e != NULL) { + expr_destroy_args(e); + free(e); + } + if (vars != NULL) { + for (struct expr_var *v = vars->head; v;) { + struct expr_var *next = v->next; + free(v); + v = next; + } + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* EXPR_H */ diff --git a/Externals/licenses.md b/Externals/licenses.md index f7b20041b9d9..beef359494a2 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects: [MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE) - [ENet](http://enet.bespin.org/): [MIT](http://enet.bespin.org/License.html) +- [expr](https://github.com/zserge/expr): + [MIT](https://github.com/zserge/expr/blob/master/LICENSE) - [GCEmu](http://sourceforge.net/projects/gcemu-project/): GPLv2+ - [gettext](https://www.gnu.org/software/gettext/): diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index de8fd8ac93ad..1098add11ee2 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -424,6 +424,8 @@ add_library(core PowerPC/BreakPoints.h PowerPC/ConditionRegister.cpp PowerPC/ConditionRegister.h + PowerPC/Expression.cpp + PowerPC/Expression.h PowerPC/JitInterface.cpp PowerPC/JitInterface.h PowerPC/MMU.cpp @@ -555,6 +557,7 @@ PUBLIC cubeb discio enet + expr inputcommon ${MBEDTLS_LIBRARIES} pugixml diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index a7ada5b18ecf..355a8e1eb7d9 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -241,6 +241,7 @@ + @@ -596,6 +597,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 749b81b9c2ca..e7d6d5d3e801 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -328,6 +328,9 @@ PowerPC + + PowerPC + PowerPC\Cached Interpreter @@ -1410,6 +1413,9 @@ PowerPC + + PowerPC + PowerPC diff --git a/Source/Core/Core/PowerPC/BreakPoints.cpp b/Source/Core/Core/PowerPC/BreakPoints.cpp index bf1e1643d115..0f5990fdbc0b 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.cpp +++ b/Source/Core/Core/PowerPC/BreakPoints.cpp @@ -14,6 +14,7 @@ #include "Common/DebugInterface.h" #include "Common/Logging/Log.h" #include "Core/Core.h" +#include "Core/PowerPC/Expression.h" #include "Core/PowerPC/JitInterface.h" #include "Core/PowerPC/MMU.h" @@ -33,7 +34,7 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const { return std::any_of(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) { - return bp.address == address && bp.break_on_hit; + return bp.address == address && bp.break_on_hit && EvaluateCondition(bp.condition); }); } @@ -51,9 +52,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const if (!bp.is_temporary) { std::ostringstream ss; - ss << std::hex << bp.address << " " << (bp.is_enabled ? "n" : "") - << (bp.log_on_hit ? "l" : "") << (bp.break_on_hit ? "b" : ""); - bp_strings.push_back(ss.str()); + ss << fmt::format("${:08x} ", bp.address); + if (bp.is_enabled) + ss << "n"; + if (bp.log_on_hit) + ss << "l"; + if (bp.break_on_hit) + ss << "b"; + if (bp.condition) + ss << "c " << bp.condition->GetText(); + bp_strings.emplace_back(ss.str()); } } @@ -67,31 +75,43 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bp_strings) TBreakPoint bp; std::stringstream ss; ss << std::hex << bp_string; + if (ss.peek() == '$') + ss.ignore(); ss >> bp.address; - bp.is_enabled = bp_string.find('n') != bp_string.npos; - bp.log_on_hit = bp_string.find('l') != bp_string.npos; - bp.break_on_hit = bp_string.find('b') != bp_string.npos; + std::string flags; + ss >> flags; + bp.is_enabled = flags.find('n') != std::string::npos; + bp.log_on_hit = flags.find('l') != std::string::npos; + bp.break_on_hit = flags.find('b') != std::string::npos; + if (flags.find('c') != std::string::npos) + { + ss >> std::ws; + std::string condition; + std::getline(ss, condition); + bp.condition = Expression::TryParse(condition); + } bp.is_temporary = false; - Add(bp); + Add(std::move(bp)); } } -void BreakPoints::Add(const TBreakPoint& bp) +void BreakPoints::Add(TBreakPoint bp) { if (IsAddressBreakPoint(bp.address)) return; - m_breakpoints.push_back(bp); - JitInterface::InvalidateICache(bp.address, 4, true); + + m_breakpoints.emplace_back(std::move(bp)); } void BreakPoints::Add(u32 address, bool temp) { - BreakPoints::Add(address, temp, true, false); + BreakPoints::Add(address, temp, true, false, {}); } -void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit) +void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit, + std::optional condition) { // Only add new addresses if (IsAddressBreakPoint(address)) @@ -103,8 +123,9 @@ void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit bp.break_on_hit = break_on_hit; bp.log_on_hit = log_on_hit; bp.address = address; + bp.condition = std::move(condition); - m_breakpoints.push_back(bp); + m_breakpoints.emplace_back(std::move(bp)); JitInterface::InvalidateICache(address, 4, true); } diff --git a/Source/Core/Core/PowerPC/BreakPoints.h b/Source/Core/Core/PowerPC/BreakPoints.h index 571427f47d16..4f58d011f96c 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.h +++ b/Source/Core/Core/PowerPC/BreakPoints.h @@ -5,10 +5,12 @@ #pragma once #include +#include #include #include #include "Common/CommonTypes.h" +#include "Core/PowerPC/Expression.h" namespace Common { @@ -22,6 +24,7 @@ struct TBreakPoint bool is_temporary = false; bool log_on_hit = false; bool break_on_hit = false; + std::optional condition; }; struct TMemCheck @@ -62,9 +65,10 @@ class BreakPoints bool IsBreakPointLogOnHit(u32 address) const; // Add BreakPoint - void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit); + void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit, + std::optional condition); void Add(u32 address, bool temp = false); - void Add(const TBreakPoint& bp); + void Add(TBreakPoint bp); // Remove Breakpoint void Remove(u32 address); diff --git a/Source/Core/Core/PowerPC/Expression.cpp b/Source/Core/Core/PowerPC/Expression.cpp new file mode 100644 index 000000000000..472471da765e --- /dev/null +++ b/Source/Core/Core/PowerPC/Expression.cpp @@ -0,0 +1,240 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/PowerPC/Expression.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/BitUtils.h" +#include "Common/CommonTypes.h" +#include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PowerPC.h" + +template +static T HostRead(u32 address); + +template +static void HostWrite(T var, u32 address); + +template <> +u8 HostRead(u32 address) +{ + return PowerPC::HostRead_U8(address); +} + +template <> +u16 HostRead(u32 address) +{ + return PowerPC::HostRead_U16(address); +} + +template <> +u32 HostRead(u32 address) +{ + return PowerPC::HostRead_U32(address); +} + +template <> +u64 HostRead(u32 address) +{ + return PowerPC::HostRead_U64(address); +} + +template <> +void HostWrite(u8 var, u32 address) +{ + PowerPC::HostWrite_U8(var, address); +} + +template <> +void HostWrite(u16 var, u32 address) +{ + PowerPC::HostWrite_U16(var, address); +} + +template <> +void HostWrite(u32 var, u32 address) +{ + PowerPC::HostWrite_U32(var, address); +} + +template <> +void HostWrite(u64 var, u32 address) +{ + PowerPC::HostWrite_U64(var, address); +} + +template +static double HostReadFunc(expr_func* f, vec_expr_t* args, void* c) +{ + if (vec_len(args) != 1) + return 0; + u32 address = static_cast(expr_eval(&vec_nth(args, 0))); + return Common::BitCast(HostRead(address)); +} + +template +static double HostWriteFunc(expr_func* f, vec_expr_t* args, void* c) +{ + if (vec_len(args) != 2) + return 0; + T var = static_cast(expr_eval(&vec_nth(args, 0))); + u32 address = static_cast(expr_eval(&vec_nth(args, 1))); + HostWrite(Common::BitCast(var), address); + return var; +} + +template +static double CastFunc(expr_func* f, vec_expr_t* args, void* c) +{ + if (vec_len(args) != 1) + return 0; + return Common::BitCast(static_cast(expr_eval(&vec_nth(args, 0)))); +} + +static std::array g_expr_funcs{{ + {"read_u8", HostReadFunc}, + {"read_s8", HostReadFunc}, + {"read_u16", HostReadFunc}, + {"read_s16", HostReadFunc}, + {"read_u32", HostReadFunc}, + {"read_s32", HostReadFunc}, + {"read_f32", HostReadFunc}, + {"read_f64", HostReadFunc}, + {"write_u8", HostWriteFunc}, + {"write_u16", HostWriteFunc}, + {"write_u32", HostWriteFunc}, + {"write_f32", HostWriteFunc}, + {"write_f64", HostWriteFunc}, + {"u8", CastFunc}, + {"s8", CastFunc}, + {"u16", CastFunc}, + {"s16", CastFunc}, + {"u32", CastFunc}, + {"s32", CastFunc}, + {}, +}}; + +void ExprDeleter::operator()(expr* expression) const +{ + expr_destroy(expression, nullptr); +} + +void ExprVarListDeleter::operator()(expr_var_list* vars) const +{ + // Free list elements + expr_destroy(nullptr, vars); + // Free list object + delete vars; +} + +Expression::Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars) + : m_text(text), m_expr(std::move(ex)), m_vars(std::move(vars)) +{ + for (auto* v = m_vars->head; v != nullptr; v = v->next) + { + const std::string_view name = v->name; + VarBinding bind; + + if (name.length() >= 2 && name.length() <= 3) + { + if (name[0] == 'r' || name[0] == 'f') + { + char* end = nullptr; + int index = std::strtol(name.data() + 1, &end, 10); + if (index >= 0 && index <= 31 && end == name.data() + name.length()) + { + bind.type = name[0] == 'r' ? VarBindingType::GPR : VarBindingType::FPR; + bind.index = index; + } + } + else if (name == "lr") + { + bind.type = VarBindingType::SPR; + bind.index = SPR_LR; + } + else if (name == "ctr") + { + bind.type = VarBindingType::SPR; + bind.index = SPR_CTR; + } + else if (name == "pc") + { + bind.type = VarBindingType::PCtr; + } + } + + m_binds.emplace_back(bind); + } +} + +std::optional Expression::TryParse(std::string_view text) +{ + ExprVarListPointer vars{new expr_var_list{}}; + ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), g_expr_funcs.data())}; + if (!ex) + return {}; + + return Expression{text, std::move(ex), std::move(vars)}; +} + +double Expression::Evaluate() const +{ + SynchronizeBindings(SynchronizeDirection::From); + + double result = expr_eval(m_expr.get()); + + SynchronizeBindings(SynchronizeDirection::To); + + return result; +} + +void Expression::SynchronizeBindings(SynchronizeDirection dir) const +{ + auto bind = m_binds.begin(); + for (auto* v = m_vars->head; v != nullptr; v = v->next, ++bind) + { + switch (bind->type) + { + case VarBindingType::Zero: + if (dir == SynchronizeDirection::From) + v->value = 0; + break; + case VarBindingType::GPR: + if (dir == SynchronizeDirection::From) + v->value = static_cast(GPR(bind->index)); + else + GPR(bind->index) = static_cast(static_cast(v->value)); + break; + case VarBindingType::FPR: + if (dir == SynchronizeDirection::From) + v->value = rPS(bind->index).PS0AsDouble(); + else + rPS(bind->index).SetPS0(v->value); + break; + case VarBindingType::SPR: + if (dir == SynchronizeDirection::From) + v->value = static_cast(rSPR(bind->index)); + else + rSPR(bind->index) = static_cast(static_cast(v->value)); + break; + case VarBindingType::PCtr: + if (dir == SynchronizeDirection::From) + v->value = static_cast(PC); + break; + } + } +} + +std::string Expression::GetText() const +{ + return m_text; +} diff --git a/Source/Core/Core/PowerPC/Expression.h b/Source/Core/Core/PowerPC/Expression.h new file mode 100644 index 000000000000..17679e7f46d4 --- /dev/null +++ b/Source/Core/Core/PowerPC/Expression.h @@ -0,0 +1,74 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +struct expr; +struct expr_var_list; + +struct ExprDeleter +{ + void operator()(expr* expression) const; +}; + +using ExprPointer = std::unique_ptr; + +struct ExprVarListDeleter +{ + void operator()(expr_var_list* vars) const; +}; + +using ExprVarListPointer = std::unique_ptr; + +class Expression +{ +public: + static std::optional TryParse(std::string_view text); + + double Evaluate() const; + + std::string GetText() const; + +private: + enum class SynchronizeDirection + { + From, + To, + }; + + enum class VarBindingType + { + Zero, + GPR, + FPR, + SPR, + PCtr, + }; + + struct VarBinding + { + VarBindingType type = VarBindingType::Zero; + int index = -1; + }; + + Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars); + + void SynchronizeBindings(SynchronizeDirection dir) const; + + std::string m_text; + ExprPointer m_expr; + ExprVarListPointer m_vars; + std::vector m_binds; +}; + +inline bool EvaluateCondition(const std::optional& condition) +{ + return !condition || condition->Evaluate() != 0; +} diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp index 8ba4f6144298..fc8ac79774e5 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp @@ -14,6 +14,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/PowerPC/BreakPoints.h" +#include "Core/PowerPC/Expression.h" #include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" @@ -78,7 +79,7 @@ void BreakpointWidget::CreateWidgets() m_table = new QTableWidget; m_table->setTabKeyNavigation(false); m_table->setContentsMargins(0, 0, 0, 0); - m_table->setColumnCount(5); + m_table->setColumnCount(6); m_table->setSelectionMode(QAbstractItemView::SingleSelection); m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -156,7 +157,7 @@ void BreakpointWidget::Update() m_table->clear(); m_table->setHorizontalHeaderLabels( - {tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")}); + {tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags"), tr("Condition")}); int i = 0; m_table->setRowCount(i); @@ -198,6 +199,13 @@ void BreakpointWidget::Update() m_table->setItem(i, 4, create_item(flags)); + QString condition; + + if (bp.condition) + condition = QString::fromStdString(bp.condition->GetText()); + + m_table->setItem(i, 5, create_item(condition)); + i++; } @@ -316,12 +324,15 @@ void BreakpointWidget::OnSave() void BreakpointWidget::AddBP(u32 addr) { - AddBP(addr, false, true, true); + AddBP(addr, false, true, true, {}); } -void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit) +void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, + const QString& condition) { - PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit); + PowerPC::breakpoints.Add( + addr, temp, break_on_hit, log_on_hit, + !condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt); Update(); } diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h index 570bb02b515e..b741e4e0c58d 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h @@ -22,7 +22,7 @@ class BreakpointWidget : public QDockWidget ~BreakpointWidget(); void AddBP(u32 addr); - void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit); + void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition); void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true, bool do_break = true); void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true, diff --git a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp index 4ad30016cd76..18a5c50e6c8b 100644 --- a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp +++ b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp @@ -14,6 +14,7 @@ #include #include +#include "Core/PowerPC/Expression.h" #include "DolphinQt/Debugger/BreakpointWidget.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" @@ -37,11 +38,14 @@ void NewBreakpointDialog::CreateWidgets() m_instruction_bp->setChecked(true); m_instruction_box = new QGroupBox; m_instruction_address = new QLineEdit; + m_instruction_condition = new QLineEdit; - auto* instruction_layout = new QHBoxLayout; + auto* instruction_layout = new QGridLayout; m_instruction_box->setLayout(instruction_layout); - instruction_layout->addWidget(new QLabel(tr("Address:"))); - instruction_layout->addWidget(m_instruction_address); + instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0); + instruction_layout->addWidget(m_instruction_address, 0, 1); + instruction_layout->addWidget(new QLabel(tr("Condition:")), 1, 0); + instruction_layout->addWidget(m_instruction_condition, 1, 1); // Memory BP m_memory_bp = new QRadioButton(tr("Memory Breakpoint")); @@ -165,7 +169,15 @@ void NewBreakpointDialog::accept() return; } - m_parent->AddBP(address, false, do_break, do_log); + const QString condition = m_instruction_condition->text().trimmed(); + + if (!condition.isEmpty() && !Expression::TryParse(condition.toUtf8().constData())) + { + invalid_input(tr("Condition")); + return; + } + + m_parent->AddBP(address, false, do_break, do_log, condition); } else { diff --git a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h index d9d2958c2bed..646a58c4e810 100644 --- a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h +++ b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h @@ -35,6 +35,7 @@ class NewBreakpointDialog : public QDialog QRadioButton* m_instruction_bp; QGroupBox* m_instruction_box; QLineEdit* m_instruction_address; + QLineEdit* m_instruction_condition; // Memory BPs QRadioButton* m_memory_bp; diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props index 804ed54fcc70..20800f1f4d31 100644 --- a/Source/VSProps/Base.props +++ b/Source/VSProps/Base.props @@ -26,6 +26,7 @@ $(ExternalsDir)discord-rpc\include;%(AdditionalIncludeDirectories) $(ExternalsDir)ed25519;%(AdditionalIncludeDirectories) $(ExternalsDir)enet\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)expr\include;%(AdditionalIncludeDirectories) $(ExternalsDir)ffmpeg\include;%(AdditionalIncludeDirectories) $(ExternalsDir)fmt\include;%(AdditionalIncludeDirectories) $(ExternalsDir)GL;%(AdditionalIncludeDirectories)