Debugger: Initial implementation of conditional breakpoints

Expression class to store compiled expressions and associated variable list.

Co-authored-by:  TryTwo <taolas@gmail.com>
This commit is contained in:
smurf3tte 2020-12-16 15:40:20 -08:00 committed by TryTwo
parent 9ca1c0f533
commit 7842f9a715
17 changed files with 1240 additions and 36 deletions

View File

@ -967,6 +967,8 @@ endif()
include_directories(Externals/picojson)
add_subdirectory(Externals/expr)
add_subdirectory(Externals/rangeset)
add_subdirectory(Externals/FatFs)

2
Externals/expr/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,2 @@
add_library(expr INTERFACE)
target_include_directories(expr INTERFACE include/)

21
Externals/expr/LICENSE vendored Normal file
View File

@ -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.

921
Externals/expr/include/expr.h vendored Normal file
View File

@ -0,0 +1,921 @@
#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 <ctype.h> /* for isspace */
#include <limits.h>
#include <math.h> /* for pow */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* 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 == '$')
#define isvarchr(c) \
(((unsigned char)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;
unsigned int frac = 0;
unsigned int digits = 0;
for (unsigned int i = 0; i < len; i++) {
if (s[i] == '.' && frac == 0) {
frac++;
continue;
}
if (isdigit(s[i])) {
digits++;
if (frac > 0) {
frac++;
}
num = num * 10 + (s[i] - '0');
} else {
return NAN;
}
}
while (frac > 1) {
num = num / 10;
frac--;
}
return (digits > 0 ? 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 int to_int(double x) {
if (isnan(x)) {
return 0;
} else if (isinf(x) != 0) {
return INT_MAX * isinf(x);
} else {
return (int)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;
while ((c == '.' || isdigit(c)) && i < len) {
i++;
c = s[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(&macros, 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(&macros, m);
break;
}
}
vec_push(&es, expr_const(0));
} else {
int i = 0;
int found = -1;
struct macro m;
vec_foreach(&macros, 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(&macros, 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(&macros, m, i) {
struct expr e2;
vec_foreach(&m.body, e2, j) { expr_destroy_args(&e2); }
vec_free(&m.body);
}
vec_free(&macros);
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 */

View File

@ -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)
- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):

View File

@ -438,6 +438,8 @@ add_library(core
PowerPC/CachedInterpreter/InterpreterBlockCache.h
PowerPC/ConditionRegister.cpp
PowerPC/ConditionRegister.h
PowerPC/Expression.cpp
PowerPC/Expression.h
PowerPC/Interpreter/ExceptionUtils.h
PowerPC/Interpreter/Interpreter_Branch.cpp
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
@ -582,6 +584,7 @@ PUBLIC
cubeb
discio
enet
expr
inputcommon
${MBEDTLS_LIBRARIES}
pugixml

View File

@ -13,6 +13,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"
@ -35,17 +36,16 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const
});
}
bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const
const TBreakPoint* BreakPoints::GetBreakpoint(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;
auto bp = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
return bp.is_enabled && bp.address == address;
});
}
bool BreakPoints::IsBreakPointLogOnHit(u32 address) const
{
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(),
[address](const auto& bp) { return bp.address == address && bp.log_on_hit; });
if (bp == m_breakpoints.end() || !EvaluateCondition(bp->condition))
return nullptr;
return &*bp;
}
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
@ -57,10 +57,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
{
std::ostringstream ss;
ss.imbue(std::locale::classic());
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());
}
}
@ -76,32 +82,43 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bp_strings)
std::istringstream iss(bp_string);
iss.imbue(std::locale::classic());
if (iss.peek() == '$')
iss.ignore();
iss >> std::hex >> bp.address;
iss >> flags;
bp.is_enabled = flags.find('n') != flags.npos;
bp.log_on_hit = flags.find('l') != flags.npos;
bp.break_on_hit = flags.find('b') != flags.npos;
if (flags.find('c') != std::string::npos)
{
iss >> std::ws;
std::string condition;
std::getline(iss, 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, std::nullopt);
}
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<Expression> condition)
{
// Only add new addresses
if (IsAddressBreakPoint(address))
@ -113,8 +130,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);
}

View File

@ -4,10 +4,12 @@
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h"
namespace Common
{
@ -21,6 +23,7 @@ struct TBreakPoint
bool is_temporary = false;
bool log_on_hit = false;
bool break_on_hit = false;
std::optional<Expression> condition;
};
struct TMemCheck
@ -59,13 +62,13 @@ public:
bool IsAddressBreakPoint(u32 address) const;
bool IsBreakPointEnable(u32 adresss) const;
bool IsTempBreakPoint(u32 address) const;
bool IsBreakPointBreakOnHit(u32 address) const;
bool IsBreakPointLogOnHit(u32 address) const;
const TBreakPoint* GetBreakpoint(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<Expression> condition);
void Add(u32 address, bool temp = false);
void Add(const TBreakPoint& bp);
void Add(TBreakPoint bp);
// Modify Breakpoint
bool ToggleBreakPoint(u32 address);

View File

@ -0,0 +1,130 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/PowerPC/Expression.h"
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <expr.h>
#include "Core/PowerPC/PowerPC.h"
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;
const 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> Expression::TryParse(std::string_view text)
{
ExprVarListPointer vars{new expr_var_list{}};
ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), nullptr)};
if (!ex)
return std::nullopt;
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<double>(GPR(bind->index));
else
GPR(bind->index) = static_cast<u32>(static_cast<s64>(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<double>(rSPR(bind->index));
else
rSPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
break;
case VarBindingType::PCtr:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(PC);
break;
}
}
}
std::string Expression::GetText() const
{
return m_text;
}

View File

@ -0,0 +1,73 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
struct expr;
struct expr_var_list;
struct ExprDeleter
{
void operator()(expr* expression) const;
};
using ExprPointer = std::unique_ptr<expr, ExprDeleter>;
struct ExprVarListDeleter
{
void operator()(expr_var_list* vars) const;
};
using ExprVarListPointer = std::unique_ptr<expr_var_list, ExprVarListDeleter>;
class Expression
{
public:
static std::optional<Expression> 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<VarBinding> m_binds;
};
inline bool EvaluateCondition(const std::optional<Expression>& condition)
{
return !condition || condition->Evaluate() != 0.0;
}

View File

@ -610,16 +610,18 @@ void CheckExternalExceptions()
void CheckBreakPoints()
{
if (!PowerPC::breakpoints.IsBreakPointEnable(PC))
const TBreakPoint* bp = PowerPC::breakpoints.GetBreakpoint(PC);
if (bp == nullptr)
return;
if (PowerPC::breakpoints.IsBreakPointBreakOnHit(PC))
if (bp->break_on_hit)
{
CPU::Break();
if (GDBStub::IsActive())
GDBStub::TakeControl();
}
if (PowerPC::breakpoints.IsBreakPointLogOnHit(PC))
if (bp->log_on_hit)
{
NOTICE_LOG_FMT(MEMMAP,
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "

View File

@ -398,6 +398,7 @@
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
<ClInclude Include="Core\PowerPC\Expression.h" />
<ClInclude Include="Core\PowerPC\GDBStub.h" />
<ClInclude Include="Core\PowerPC\Gekko.h" />
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
@ -1016,6 +1017,7 @@
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
<ClCompile Include="Core\PowerPC\Expression.cpp" />
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />

View File

@ -15,6 +15,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"
@ -86,7 +87,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);
@ -160,7 +161,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);
@ -203,6 +204,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++;
}
@ -387,12 +395,15 @@ void BreakpointWidget::OnContextMenu()
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);
emit BreakpointsChanged();
Update();

View File

@ -21,7 +21,7 @@ public:
~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,

View File

@ -14,6 +14,7 @@
#include <QRadioButton>
#include <QVBoxLayout>
#include "Core/PowerPC/Expression.h"
#include "DolphinQt/Debugger/BreakpointWidget.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
@ -40,11 +41,14 @@ void NewBreakpointDialog::CreateWidgets()
type_group->addButton(m_instruction_bp);
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"));
@ -174,7 +178,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
{

View File

@ -34,6 +34,7 @@ private:
QRadioButton* m_instruction_bp;
QGroupBox* m_instruction_box;
QLineEdit* m_instruction_address;
QLineEdit* m_instruction_condition;
// Memory BPs
QRadioButton* m_memory_bp;

View File

@ -12,6 +12,7 @@
<!--For now, header-only libs don't have their own vcxproj/exports, so just supply their include paths to all Dolphin code-->
<AdditionalIncludeDirectories>$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)expr\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>