Merge pull request #11015 from TryTwo/Conditional_Breakpoints
Conditional breakpoints
This commit is contained in:
commit
2a81fa6c26
|
@ -967,6 +967,8 @@ endif()
|
||||||
|
|
||||||
include_directories(Externals/picojson)
|
include_directories(Externals/picojson)
|
||||||
|
|
||||||
|
add_subdirectory(Externals/expr)
|
||||||
|
|
||||||
add_subdirectory(Externals/rangeset)
|
add_subdirectory(Externals/rangeset)
|
||||||
|
|
||||||
add_subdirectory(Externals/FatFs)
|
add_subdirectory(Externals/FatFs)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
add_library(expr INTERFACE)
|
||||||
|
target_include_directories(expr INTERFACE include/)
|
|
@ -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.
|
|
@ -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 <ctype.h> /* for isspace */
|
||||||
|
#include <limits.h>
|
||||||
|
#include <math.h> /* for pow */
|
||||||
|
#include <stdint.h>
|
||||||
|
#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 != '~') || 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 */
|
|
@ -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)
|
[MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE)
|
||||||
- [ENet](http://enet.bespin.org/):
|
- [ENet](http://enet.bespin.org/):
|
||||||
[MIT](http://enet.bespin.org/License.html)
|
[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):
|
- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
|
||||||
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
|
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
|
||||||
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
|
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
|
||||||
|
|
|
@ -449,6 +449,8 @@ add_library(core
|
||||||
PowerPC/CachedInterpreter/InterpreterBlockCache.h
|
PowerPC/CachedInterpreter/InterpreterBlockCache.h
|
||||||
PowerPC/ConditionRegister.cpp
|
PowerPC/ConditionRegister.cpp
|
||||||
PowerPC/ConditionRegister.h
|
PowerPC/ConditionRegister.h
|
||||||
|
PowerPC/Expression.cpp
|
||||||
|
PowerPC/Expression.h
|
||||||
PowerPC/Interpreter/ExceptionUtils.h
|
PowerPC/Interpreter/ExceptionUtils.h
|
||||||
PowerPC/Interpreter/Interpreter_Branch.cpp
|
PowerPC/Interpreter/Interpreter_Branch.cpp
|
||||||
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
|
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
|
||||||
|
@ -593,6 +595,7 @@ PUBLIC
|
||||||
cubeb
|
cubeb
|
||||||
discio
|
discio
|
||||||
enet
|
enet
|
||||||
|
expr
|
||||||
inputcommon
|
inputcommon
|
||||||
${MBEDTLS_LIBRARIES}
|
${MBEDTLS_LIBRARIES}
|
||||||
pugixml
|
pugixml
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "Common/DebugInterface.h"
|
#include "Common/DebugInterface.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
|
#include "Core/PowerPC/Expression.h"
|
||||||
#include "Core/PowerPC/JitInterface.h"
|
#include "Core/PowerPC/JitInterface.h"
|
||||||
#include "Core/PowerPC/MMU.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) {
|
auto bp = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
|
||||||
return bp.address == address && bp.break_on_hit;
|
return bp.is_enabled && bp.address == address;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
bool BreakPoints::IsBreakPointLogOnHit(u32 address) const
|
if (bp == m_breakpoints.end() || !EvaluateCondition(bp->condition))
|
||||||
{
|
return nullptr;
|
||||||
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(),
|
|
||||||
[address](const auto& bp) { return bp.address == address && bp.log_on_hit; });
|
return &*bp;
|
||||||
}
|
}
|
||||||
|
|
||||||
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
||||||
|
@ -57,10 +57,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
||||||
{
|
{
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss.imbue(std::locale::classic());
|
ss.imbue(std::locale::classic());
|
||||||
|
ss << fmt::format("${:08x} ", bp.address);
|
||||||
ss << std::hex << bp.address << " " << (bp.is_enabled ? "n" : "")
|
if (bp.is_enabled)
|
||||||
<< (bp.log_on_hit ? "l" : "") << (bp.break_on_hit ? "b" : "");
|
ss << "n";
|
||||||
bp_strings.push_back(ss.str());
|
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);
|
std::istringstream iss(bp_string);
|
||||||
iss.imbue(std::locale::classic());
|
iss.imbue(std::locale::classic());
|
||||||
|
|
||||||
|
if (iss.peek() == '$')
|
||||||
|
iss.ignore();
|
||||||
|
|
||||||
iss >> std::hex >> bp.address;
|
iss >> std::hex >> bp.address;
|
||||||
iss >> flags;
|
iss >> flags;
|
||||||
bp.is_enabled = flags.find('n') != flags.npos;
|
bp.is_enabled = flags.find('n') != flags.npos;
|
||||||
bp.log_on_hit = flags.find('l') != flags.npos;
|
bp.log_on_hit = flags.find('l') != flags.npos;
|
||||||
bp.break_on_hit = flags.find('b') != 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;
|
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))
|
if (IsAddressBreakPoint(bp.address))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_breakpoints.push_back(bp);
|
|
||||||
|
|
||||||
JitInterface::InvalidateICache(bp.address, 4, true);
|
JitInterface::InvalidateICache(bp.address, 4, true);
|
||||||
|
|
||||||
|
m_breakpoints.emplace_back(std::move(bp));
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakPoints::Add(u32 address, bool temp)
|
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
|
// Only add new addresses
|
||||||
if (IsAddressBreakPoint(address))
|
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.break_on_hit = break_on_hit;
|
||||||
bp.log_on_hit = log_on_hit;
|
bp.log_on_hit = log_on_hit;
|
||||||
bp.address = address;
|
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);
|
JitInterface::InvalidateICache(address, 4, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Core/PowerPC/Expression.h"
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
|
@ -21,6 +23,7 @@ struct TBreakPoint
|
||||||
bool is_temporary = false;
|
bool is_temporary = false;
|
||||||
bool log_on_hit = false;
|
bool log_on_hit = false;
|
||||||
bool break_on_hit = false;
|
bool break_on_hit = false;
|
||||||
|
std::optional<Expression> condition;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TMemCheck
|
struct TMemCheck
|
||||||
|
@ -59,13 +62,13 @@ public:
|
||||||
bool IsAddressBreakPoint(u32 address) const;
|
bool IsAddressBreakPoint(u32 address) const;
|
||||||
bool IsBreakPointEnable(u32 adresss) const;
|
bool IsBreakPointEnable(u32 adresss) const;
|
||||||
bool IsTempBreakPoint(u32 address) const;
|
bool IsTempBreakPoint(u32 address) const;
|
||||||
bool IsBreakPointBreakOnHit(u32 address) const;
|
const TBreakPoint* GetBreakpoint(u32 address) const;
|
||||||
bool IsBreakPointLogOnHit(u32 address) const;
|
|
||||||
|
|
||||||
// Add BreakPoint
|
// 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(u32 address, bool temp = false);
|
||||||
void Add(const TBreakPoint& bp);
|
void Add(TBreakPoint bp);
|
||||||
|
|
||||||
// Modify Breakpoint
|
// Modify Breakpoint
|
||||||
bool ToggleBreakPoint(u32 address);
|
bool ToggleBreakPoint(u32 address);
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "Core/PowerPC/Expression.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <expr.h>
|
||||||
|
|
||||||
|
#include "Common/BitUtils.h"
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Core/Core.h"
|
||||||
|
#include "Core/PowerPC/MMU.h"
|
||||||
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T HostRead(u32 address);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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 <typename T, typename U = T>
|
||||||
|
static double HostReadFunc(expr_func* f, vec_expr_t* args, void* c)
|
||||||
|
{
|
||||||
|
if (vec_len(args) != 1)
|
||||||
|
return 0;
|
||||||
|
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 0)));
|
||||||
|
return Common::BitCast<T>(HostRead<U>(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U = T>
|
||||||
|
static double HostWriteFunc(expr_func* f, vec_expr_t* args, void* c)
|
||||||
|
{
|
||||||
|
if (vec_len(args) != 2)
|
||||||
|
return 0;
|
||||||
|
const T var = static_cast<T>(expr_eval(&vec_nth(args, 0)));
|
||||||
|
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 1)));
|
||||||
|
HostWrite<U>(Common::BitCast<U>(var), address);
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U = T>
|
||||||
|
static double CastFunc(expr_func* f, vec_expr_t* args, void* c)
|
||||||
|
{
|
||||||
|
if (vec_len(args) != 1)
|
||||||
|
return 0;
|
||||||
|
return Common::BitCast<T>(static_cast<U>(expr_eval(&vec_nth(args, 0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::array<expr_func, 21> g_expr_funcs{{
|
||||||
|
// For internal storage and comparisons, everything is auto-converted to Double.
|
||||||
|
// If u64 ints are added, this could produce incorrect results.
|
||||||
|
{"read_u8", HostReadFunc<u8>},
|
||||||
|
{"read_s8", HostReadFunc<s8, u8>},
|
||||||
|
{"read_u16", HostReadFunc<u16>},
|
||||||
|
{"read_s16", HostReadFunc<s16, u16>},
|
||||||
|
{"read_u32", HostReadFunc<u32>},
|
||||||
|
{"read_s32", HostReadFunc<s32, u32>},
|
||||||
|
{"read_f32", HostReadFunc<float, u32>},
|
||||||
|
{"read_f64", HostReadFunc<double, u64>},
|
||||||
|
{"write_u8", HostWriteFunc<u8>},
|
||||||
|
{"write_u16", HostWriteFunc<u16>},
|
||||||
|
{"write_u32", HostWriteFunc<u32>},
|
||||||
|
{"write_f32", HostWriteFunc<float, u32>},
|
||||||
|
{"write_f64", HostWriteFunc<double, u64>},
|
||||||
|
{"u8", CastFunc<u8>},
|
||||||
|
{"s8", CastFunc<s8, u8>},
|
||||||
|
{"u16", CastFunc<u16>},
|
||||||
|
{"s16", CastFunc<s16, u16>},
|
||||||
|
{"u32", CastFunc<u32>},
|
||||||
|
{"s32", CastFunc<s32, u32>},
|
||||||
|
{},
|
||||||
|
}};
|
||||||
|
|
||||||
|
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(), g_expr_funcs.data())};
|
||||||
|
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);
|
||||||
|
|
||||||
|
Reporting(result);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Expression::Reporting(const double result) const
|
||||||
|
{
|
||||||
|
bool is_nan = std::isnan(result);
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
for (auto* v = m_vars->head; v != nullptr; v = v->next)
|
||||||
|
{
|
||||||
|
if (std::isnan(v->value))
|
||||||
|
is_nan = true;
|
||||||
|
|
||||||
|
fmt::format_to(std::back_inserter(message), " {}={}", v->name, v->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_nan)
|
||||||
|
{
|
||||||
|
message.append("\nBreakpoint condition encountered a NaN");
|
||||||
|
Core::DisplayMessage("Breakpoint condition has encountered a NaN.", 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != 0.0 || is_nan)
|
||||||
|
NOTICE_LOG_FMT(MEMMAP, "Breakpoint condition returned: {}. Vars:{}", result, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Expression::GetText() const
|
||||||
|
{
|
||||||
|
return m_text;
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// 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;
|
||||||
|
void Reporting(const double result) 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;
|
||||||
|
}
|
|
@ -610,16 +610,18 @@ void CheckExternalExceptions()
|
||||||
|
|
||||||
void CheckBreakPoints()
|
void CheckBreakPoints()
|
||||||
{
|
{
|
||||||
if (!PowerPC::breakpoints.IsBreakPointEnable(PC))
|
const TBreakPoint* bp = PowerPC::breakpoints.GetBreakpoint(PC);
|
||||||
|
|
||||||
|
if (bp == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (PowerPC::breakpoints.IsBreakPointBreakOnHit(PC))
|
if (bp->break_on_hit)
|
||||||
{
|
{
|
||||||
CPU::Break();
|
CPU::Break();
|
||||||
if (GDBStub::IsActive())
|
if (GDBStub::IsActive())
|
||||||
GDBStub::TakeControl();
|
GDBStub::TakeControl();
|
||||||
}
|
}
|
||||||
if (PowerPC::breakpoints.IsBreakPointLogOnHit(PC))
|
if (bp->log_on_hit)
|
||||||
{
|
{
|
||||||
NOTICE_LOG_FMT(MEMMAP,
|
NOTICE_LOG_FMT(MEMMAP,
|
||||||
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "
|
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "
|
||||||
|
|
|
@ -405,6 +405,7 @@
|
||||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
|
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
|
||||||
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
|
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
|
||||||
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
|
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
|
||||||
|
<ClInclude Include="Core\PowerPC\Expression.h" />
|
||||||
<ClInclude Include="Core\PowerPC\GDBStub.h" />
|
<ClInclude Include="Core\PowerPC\GDBStub.h" />
|
||||||
<ClInclude Include="Core\PowerPC\Gekko.h" />
|
<ClInclude Include="Core\PowerPC\Gekko.h" />
|
||||||
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
|
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
|
||||||
|
@ -1029,6 +1030,7 @@
|
||||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
|
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
|
||||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
|
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
|
||||||
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
|
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
|
||||||
|
<ClCompile Include="Core\PowerPC\Expression.cpp" />
|
||||||
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
|
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
|
||||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
|
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
|
||||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />
|
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/PowerPC/BreakPoints.h"
|
#include "Core/PowerPC/BreakPoints.h"
|
||||||
|
#include "Core/PowerPC/Expression.h"
|
||||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ void BreakpointWidget::CreateWidgets()
|
||||||
m_table = new QTableWidget;
|
m_table = new QTableWidget;
|
||||||
m_table->setTabKeyNavigation(false);
|
m_table->setTabKeyNavigation(false);
|
||||||
m_table->setContentsMargins(0, 0, 0, 0);
|
m_table->setContentsMargins(0, 0, 0, 0);
|
||||||
m_table->setColumnCount(5);
|
m_table->setColumnCount(6);
|
||||||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
@ -160,7 +161,7 @@ void BreakpointWidget::Update()
|
||||||
m_table->clear();
|
m_table->clear();
|
||||||
|
|
||||||
m_table->setHorizontalHeaderLabels(
|
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;
|
int i = 0;
|
||||||
m_table->setRowCount(i);
|
m_table->setRowCount(i);
|
||||||
|
@ -203,6 +204,13 @@ void BreakpointWidget::Update()
|
||||||
|
|
||||||
m_table->setItem(i, 4, create_item(flags));
|
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++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,12 +395,15 @@ void BreakpointWidget::OnContextMenu()
|
||||||
|
|
||||||
void BreakpointWidget::AddBP(u32 addr)
|
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();
|
emit BreakpointsChanged();
|
||||||
Update();
|
Update();
|
||||||
|
|
|
@ -21,7 +21,7 @@ public:
|
||||||
~BreakpointWidget();
|
~BreakpointWidget();
|
||||||
|
|
||||||
void AddBP(u32 addr);
|
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,
|
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
|
||||||
bool do_break = true);
|
bool do_break = true);
|
||||||
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
|
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
|
||||||
|
|
|
@ -11,9 +11,11 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "Core/PowerPC/Expression.h"
|
||||||
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
||||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||||
|
|
||||||
|
@ -35,16 +37,25 @@ void NewBreakpointDialog::CreateWidgets()
|
||||||
auto* type_group = new QButtonGroup(this);
|
auto* type_group = new QButtonGroup(this);
|
||||||
|
|
||||||
// Instruction BP
|
// Instruction BP
|
||||||
|
auto* top_layout = new QHBoxLayout;
|
||||||
m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint"));
|
m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint"));
|
||||||
m_instruction_bp->setChecked(true);
|
m_instruction_bp->setChecked(true);
|
||||||
type_group->addButton(m_instruction_bp);
|
type_group->addButton(m_instruction_bp);
|
||||||
m_instruction_box = new QGroupBox;
|
m_instruction_box = new QGroupBox;
|
||||||
m_instruction_address = new QLineEdit;
|
m_instruction_address = new QLineEdit;
|
||||||
|
m_instruction_condition = new QLineEdit;
|
||||||
|
m_cond_help_btn = new QPushButton(tr("Help"));
|
||||||
|
|
||||||
auto* instruction_layout = new QHBoxLayout;
|
top_layout->addWidget(m_instruction_bp);
|
||||||
|
top_layout->addStretch();
|
||||||
|
top_layout->addWidget(m_cond_help_btn);
|
||||||
|
|
||||||
|
auto* instruction_layout = new QGridLayout;
|
||||||
m_instruction_box->setLayout(instruction_layout);
|
m_instruction_box->setLayout(instruction_layout);
|
||||||
instruction_layout->addWidget(new QLabel(tr("Address:")));
|
instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0);
|
||||||
instruction_layout->addWidget(m_instruction_address);
|
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
|
// Memory BP
|
||||||
m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
|
m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
|
||||||
|
@ -102,7 +113,7 @@ void NewBreakpointDialog::CreateWidgets()
|
||||||
|
|
||||||
auto* layout = new QVBoxLayout;
|
auto* layout = new QVBoxLayout;
|
||||||
|
|
||||||
layout->addWidget(m_instruction_bp);
|
layout->addLayout(top_layout);
|
||||||
layout->addWidget(m_instruction_box);
|
layout->addWidget(m_instruction_box);
|
||||||
layout->addWidget(m_memory_bp);
|
layout->addWidget(m_memory_bp);
|
||||||
layout->addWidget(m_memory_box);
|
layout->addWidget(m_memory_box);
|
||||||
|
@ -119,6 +130,8 @@ void NewBreakpointDialog::ConnectWidgets()
|
||||||
connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept);
|
connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept);
|
||||||
connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject);
|
connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject);
|
||||||
|
|
||||||
|
connect(m_cond_help_btn, &QPushButton::clicked, this, &NewBreakpointDialog::ShowConditionHelp);
|
||||||
|
|
||||||
connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
||||||
connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
||||||
|
|
||||||
|
@ -174,7 +187,15 @@ void NewBreakpointDialog::accept()
|
||||||
return;
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -205,3 +226,46 @@ void NewBreakpointDialog::accept()
|
||||||
|
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NewBreakpointDialog::ShowConditionHelp()
|
||||||
|
{
|
||||||
|
const auto message = QStringLiteral(
|
||||||
|
"Set a code breakpoint for when an instruction is executed. Use with the code widget.\n"
|
||||||
|
"\n"
|
||||||
|
"Conditions:\n"
|
||||||
|
"Sets an expression that is evaluated when a breakpoint is hit. If the expression is false "
|
||||||
|
"or 0, the breakpoint is ignored until hit again. Statements should be separated by a comma. "
|
||||||
|
"Only the last statement will be used to determine what to do.\n"
|
||||||
|
"\n"
|
||||||
|
"Registers that can be referenced:\n"
|
||||||
|
"GPRs : r0..r31\n"
|
||||||
|
"FPRs : f0..f31\n LR, CTR, PC\n"
|
||||||
|
"\n"
|
||||||
|
"Functions:\n"
|
||||||
|
"Set a register: r1 = 8\n"
|
||||||
|
"Casts: s8(0xff). Available: s8, u8, s16, u16, s32, u32\n"
|
||||||
|
"Read Memory: read_u32(0x80000000). Available: u8, s8, u16, s16, u32, s32, f32, f64\n"
|
||||||
|
"Write Memory: write_u32(r3, 0x80000000). Available: u8, u16, u32, f32, f64\n"
|
||||||
|
"*currently writing will always be triggered\n"
|
||||||
|
"\n"
|
||||||
|
"Operations:\n"
|
||||||
|
"Unary: -u, !u, ~u\n"
|
||||||
|
"Math: * / + -, power: **, remainder: %, shift: <<, >>\n"
|
||||||
|
"Compare: <, <=, >, >=, ==, !=, &&, ||\n"
|
||||||
|
"Bitwise: &, |, ^\n"
|
||||||
|
"\n"
|
||||||
|
"Examples:\n"
|
||||||
|
"r4 == 1\n"
|
||||||
|
"f0 == 1.0 && f2 < 10.0\n"
|
||||||
|
"r26 <= r0 && ((r5 + 3) & -4) * ((r6 + 3) & -4)* 4 > r0\n"
|
||||||
|
"p = r3 + 0x8, p == 0x8003510 && read_u32(p) != 0\n"
|
||||||
|
"Write and break: r4 = 8, 1\n"
|
||||||
|
"Write and continue: f3 = f1 + f2, 0\n"
|
||||||
|
"The condition must always be last\n\n"
|
||||||
|
"All variables will be printed in the Memory Interface log, if there's a hit or a NaN "
|
||||||
|
"result. To check for issues, assign a variable to your equation, so it can be printed.\n\n"
|
||||||
|
"Note: All values are internally converted to Doubles for calculations. It's possible for "
|
||||||
|
"them to go out of range or to become NaN. A warning will be given if NaN is returned, and "
|
||||||
|
"the var that became NaN will be logged.");
|
||||||
|
ModalMessageBox::information(this, tr("Conditional help"), message);
|
||||||
|
}
|
||||||
|
|
|
@ -29,11 +29,14 @@ private:
|
||||||
|
|
||||||
void OnBPTypeChanged();
|
void OnBPTypeChanged();
|
||||||
void OnAddressTypeChanged();
|
void OnAddressTypeChanged();
|
||||||
|
void ShowConditionHelp();
|
||||||
|
|
||||||
// Instruction BPs
|
// Instruction BPs
|
||||||
QRadioButton* m_instruction_bp;
|
QRadioButton* m_instruction_bp;
|
||||||
QGroupBox* m_instruction_box;
|
QGroupBox* m_instruction_box;
|
||||||
QLineEdit* m_instruction_address;
|
QLineEdit* m_instruction_address;
|
||||||
|
QLineEdit* m_instruction_condition;
|
||||||
|
QPushButton* m_cond_help_btn;
|
||||||
|
|
||||||
// Memory BPs
|
// Memory BPs
|
||||||
QRadioButton* m_memory_bp;
|
QRadioButton* m_memory_bp;
|
||||||
|
|
|
@ -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-->
|
<!--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)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
<AdditionalIncludeDirectories>$(ExternalsDir)expr\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
|
Loading…
Reference in New Issue