More advanced memory watches

This commit is contained in:
Ilari Liusvaara 2014-01-10 12:09:19 +02:00
parent c0faaf1fce
commit 0a41e8d901
35 changed files with 4964 additions and 940 deletions

View file

@ -4,11 +4,171 @@
#include <string>
#include <stdexcept>
#include <set>
#include "library/memorywatch.hpp"
#include "library/json.hpp"
std::string evaluate_watch(const std::string& expr) throw(std::bad_alloc);
std::set<std::string> get_watches() throw(std::bad_alloc);
std::string get_watchexpr_for(const std::string& w) throw(std::bad_alloc);
void set_watchexpr_for(const std::string& w, const std::string& expr) throw(std::bad_alloc);
void do_watch_memory();
#endif
/**
* lsnes memory watch printer variables.
*/
struct lsnes_memorywatch_printer
{
/**
* Ctor.
*/
lsnes_memorywatch_printer();
/**
* Serialize the printer to JSON value.
*/
JSON::node serialize();
/**
* Unserialize the printer from JSON value.
*/
void unserialize(const JSON::node& node);
/**
* Get a printer object corresponding to this object.
*/
gcroot_pointer<memorywatch_item_printer> get_printer_obj(
std::function<gcroot_pointer<mathexpr>(const std::string& n)> vars);
//Fields.
enum position_category {
PC_DISABLED,
PC_MEMORYWATCH,
PC_ONSCREEN
} position;
bool cond_enable; //Ignored for disabled.
std::string enabled; //Ignored for disabled.
std::string onscreen_xpos;
std::string onscreen_ypos;
bool onscreen_alt_origin_x;
bool onscreen_alt_origin_y;
bool onscreen_cliprange_x;
bool onscreen_cliprange_y;
std::string onscreen_font; //"" is system default.
int64_t onscreen_fg_color;
int64_t onscreen_bg_color;
int64_t onscreen_halo_color;
};
/**
* lsnes memory watch item.
*/
struct lsnes_memorywatch_item
{
/**
* Ctor.
*/
lsnes_memorywatch_item();
/**
* Serialize the item to JSON value.
*/
JSON::node serialize();
/**
* Unserialize the item from JSON value.
*/
void unserialize(const JSON::node& node);
/**
* Get memory read operator.
*
* If bytes == 0, returns NULL.
*/
memorywatch_memread_oper* get_memread_oper();
//Fields
lsnes_memorywatch_printer printer; //The printer.
std::string expr; //The main expression.
std::string format; //Format.
unsigned bytes; //Number of bytes to read (0 => Not memory read operator).
bool signed_flag; //Is signed?
bool float_flag; //Is float?
int endianess; //Endianess (-1 => little, 0 => host, 1 => Big).
uint64_t scale_div; //Scale divisor.
uint64_t addr_base; //Address base.
uint64_t addr_size; //Address size (0 => All).
memory_space* mspace; //Memory space to read.
};
struct lsnes_memorywatch_set
{
/**
* Get the specified memory watch item.
*/
lsnes_memorywatch_item& get(const std::string& name);
/**
* Get the specified memory watch item as JSON serialization.
*
* Parameter name: The item name.
* Parameter printer: JSON pretty-printer to use.
*/
std::string get_string(const std::string& name, JSON::printer* printer = NULL);
/**
* Set the specified memory watch item. Fills the runtime variables.
*
* Parameter name: The name of the new item.
* Parameter item: The item to insert. Fields are shallow-copied.
*/
void set(const std::string& name, lsnes_memorywatch_item& item);
/**
* Set the specified memory watch item from JSON serialization. Fills the runtime variables.
*
* Parameter name: The name of the new item.
* Parameter item: The serialization of item to insert.
*/
void set(const std::string& name, const std::string& item);
/**
* Set multiple items at once.
*
* Parameter list: The list of items.
*/
void set_multi(std::list<std::pair<std::string, lsnes_memorywatch_item>>& list);
/**
* Set multiple items at once from JSON descriptions.
*
* Parameter list: The list of items.
*/
void set_multi(std::list<std::pair<std::string, std::string>>& list);
/**
* Rename a memory watch item.
*
* Parameter oname: The old name.
* Parameter newname: The new name.
* Returns: True on success, false if failed (new item already exists).
*/
bool rename(const std::string& oname, const std::string& newname);
/**
* Delete an item.
*
* Parameter name: The name of the item to delete.
*/
void clear(const std::string& name);
/**
* Delete multiple items.
*
* Parameter names: The names of the items to delete.
*/
void clear_multi(const std::set<std::string>& name);
/**
* Enumerate item names.
*/
std::set<std::string> enumerate();
/**
* Get value of specified memory watch as a string.
*/
std::string get_value(const std::string& name);
/**
* Watch all the items.
*
* Parameter rq: The render queue to use.
*/
void watch(struct framebuffer::queue& rq);
private:
void rebuild(std::map<std::string, lsnes_memorywatch_item>& nitems);
std::map<std::string, lsnes_memorywatch_item> items;
memorywatch_set watch_set;
};
extern lsnes_memorywatch_set lsnes_memorywatch;
#endif

View file

@ -457,6 +457,7 @@ struct color
//std::cerr << "Color " << color << " -> hi=" << hi << " lo=" << lo << " inv=" << inv << std::endl;
}
color(const std::string& color) throw(std::bad_alloc, std::runtime_error);
static std::string stringify(int64_t number);
void set_palette(unsigned rshift, unsigned gshift, unsigned bshift, bool X) throw();
template<bool X> void set_palette(struct fb<X>& s) throw()
{

66
include/library/gc.hpp Normal file
View file

@ -0,0 +1,66 @@
#ifndef _library__gc__hpp__included__
#define _library__gc__hpp__included__
#include <cstdlib>
class garbage_collectable
{
public:
garbage_collectable();
virtual ~garbage_collectable();
void mark_root();
void unmark_root();
static void do_gc();
protected:
virtual void trace() = 0;
void mark();
private:
size_t root_count;
bool reachable;
};
struct gcroot_pointer_object_tag {};
template<class T> class gcroot_pointer
{
public:
gcroot_pointer()
{
ptr = NULL;
}
gcroot_pointer(T* obj)
{
ptr = obj;
}
template<typename... U> gcroot_pointer(gcroot_pointer_object_tag tag, U... args)
{
ptr = new T(args...);
}
gcroot_pointer(const gcroot_pointer& p)
{
if(p.ptr) p.ptr->mark_root();
ptr = p.ptr;
}
gcroot_pointer& operator=(const gcroot_pointer& p)
{
if(ptr == p.ptr) return *this;
if(ptr) ptr->unmark_root();
if(p.ptr) p.ptr->mark_root();
ptr = p.ptr;
return *this;
}
~gcroot_pointer()
{
if(ptr) ptr->unmark_root();
}
operator bool()
{
return (ptr != NULL);
}
T* operator->() { return ptr; }
T& operator*() { return *ptr; }
T* as_pointer() { return ptr; }
private:
T* ptr;
};
#endif

View file

@ -0,0 +1,32 @@
#ifndef _library__mathexpr_error__hpp__included__
#define _library__mathexpr_error__hpp__included__
#include <string>
#include <stdexcept>
class mathexpr_error : public std::runtime_error
{
public:
enum errorcode
{
UNDEFINED, //Evaluation encountered undefined value.
CIRCULAR, //Evaluation encountered circular reference.
TYPE_MISMATCH, //Evaluation encountered mismatching types.
INTERNAL, //Evaluation encountered internal error.
WDOMAIN, //Evaluation encountered domain error.
DIV_BY_0, //Evaluation encountered division by zero.
LOG_BY_0, //Evaluation encountered logarithm of zero.
ARGCOUNT, //Evaluation encountered wrong argument count.
SIZE, //Bad size for memory watch.
ADDR, //Bad address for memory watch.
FORMAT, //Bad format string.
UNKNOWN, //Unknown error.
};
mathexpr_error(errorcode code, const std::string& message);
errorcode get_code();
const char* get_short_error();
private:
errorcode code;
};
#endif

View file

@ -0,0 +1,13 @@
#ifndef _library__mathexpr_format__hpp__included__
#define _library__mathexpr_format__hpp__included__
#include "mathexpr.hpp"
std::string math_format_bool(bool v, mathexpr_format fmt);
std::string math_format_unsigned(uint64_t v, mathexpr_format fmt);
std::string math_format_signed(int64_t v, mathexpr_format fmt);
std::string math_format_float(double v, mathexpr_format fmt);
std::string math_format_complex(double vr, double vi, mathexpr_format fmt);
std::string math_format_string(std::string v, mathexpr_format fmt);
#endif

View file

@ -0,0 +1,8 @@
#ifndef _library__mathexpr_ntype__hpp__included__
#define _library__mathexpr_ntype__hpp__included__
#include "mathexpr.hpp"
struct mathexpr_typeinfo* expression_value();
#endif

View file

@ -0,0 +1,271 @@
#ifndef _library__mathexpr__hpp__included__
#define _library__mathexpr__hpp__included__
#include <functional>
#include <vector>
#include <stdexcept>
#include "gc.hpp"
#include "mathexpr-error.hpp"
#include <set>
struct mathexpr_typeinfo;
struct mathexpr;
struct mathexpr_value
{
mathexpr_typeinfo* type;
void* value;
};
struct mathexpr_format
{
enum _type
{
BOOLEAN,
BINARY,
OCTAL,
DECIMAL,
HEXADECIMAL,
STRING,
DEFAULT,
} type;
bool showsign;
bool fillzeros;
int width;
int precision;
bool uppercasehex;
};
struct mathexpr_operinfo
{
mathexpr_operinfo(std::string funcname);
mathexpr_operinfo(std::string opername, unsigned _operands, int _percedence, bool _rtl = false);
virtual ~mathexpr_operinfo();
virtual void evaluate(mathexpr_value target, std::vector<std::function<mathexpr_value()>> promises) = 0;
const std::string fnname;
const bool is_operator;
const unsigned operands; //Only for operators (max 2 operands).
const int precedence; //Higher binds more tightly.
const bool rtl; //If true, Right-to-left associvity.
};
struct mathexpr_typeinfo
{
virtual ~mathexpr_typeinfo();
virtual void* allocate() = 0;
virtual void* parse(const std::string& str, bool string) = 0;
virtual void parse_u(void* obj, uint64_t v) = 0;
virtual void parse_s(void* obj, int64_t v) = 0;
virtual void parse_f(void* obj, double v) = 0;
virtual void scale(void* val, uint64_t scale) = 0;
virtual void deallocate(void* obj) = 0;
virtual void copy(void* target, void* source) = 0;
virtual std::string tostring(void* obj) = 0;
virtual std::string format(void* obj, mathexpr_format fmt) = 0;
virtual uint64_t tounsigned(void* obj) = 0;
virtual int64_t tosigned(void* obj) = 0;
virtual bool toboolean(void* obj) = 0;
virtual std::set<mathexpr_operinfo*> operations() = 0;
void* copy_allocate(void* src)
{
void* p = allocate();
try {
copy(p, src);
} catch(...) {
deallocate(p);
throw;
}
return p;
}
};
template<class T> struct mathexpr_operinfo_wrapper : public mathexpr_operinfo
{
mathexpr_operinfo_wrapper(std::string funcname, T (*_fn)(std::vector<std::function<T&()>> promises))
: mathexpr_operinfo(funcname), fn(_fn)
{
}
mathexpr_operinfo_wrapper(std::string opername, unsigned _operands, int _percedence, bool _rtl,
T (*_fn)(std::vector<std::function<T&()>> promises))
: mathexpr_operinfo(opername, _operands, _percedence, _rtl), fn(_fn)
{
}
~mathexpr_operinfo_wrapper()
{
}
void evaluate(mathexpr_value target, std::vector<std::function<mathexpr_value()>> promises)
{
std::vector<std::function<T&()>> _promises;
for(auto i : promises) {
std::function<mathexpr_value()> f = i;
_promises.push_back([f, this]() -> T& {
auto r = f();
return *(T*)r.value;
});
}
*(T*)(target.value) = fn(_promises);
}
private:
T (*fn)(std::vector<std::function<T&()>> promises);
};
template<class T> struct mathexpr_opfun_info
{
std::string name;
T (*_fn)(std::vector<std::function<T&()>> promises);
bool is_operator;
unsigned operands;
int precedence;
bool rtl;
};
template<class T> struct mathexpr_operinfo_set
{
mathexpr_operinfo_set(std::initializer_list<mathexpr_opfun_info<T>> list)
{
for(auto i : list) {
if(i.is_operator)
set.insert(new mathexpr_operinfo_wrapper<T>(i.name, i.operands, i.precedence,
i.rtl, i._fn));
else
set.insert(new mathexpr_operinfo_wrapper<T>(i.name, i._fn));
}
}
~mathexpr_operinfo_set()
{
for(auto i : set)
delete i;
}
std::set<mathexpr_operinfo*>& get_set()
{
return set;
}
private:
std::set<mathexpr_operinfo*> set;
};
template<class T> struct mathexpr_typeinfo_wrapper : public mathexpr_typeinfo
{
struct unsigned_tag {};
struct signed_tag {};
struct float_tag {};
~mathexpr_typeinfo_wrapper()
{
}
void* allocate()
{
return new T;
}
void* parse(const std::string& str, bool string)
{
return new T(str, string);
}
void parse_u(void* obj, uint64_t v)
{
*(T*)obj = T(unsigned_tag(), v);
}
void parse_s(void* obj, int64_t v)
{
*(T*)obj = T(signed_tag(), v);
}
void parse_f(void* obj, double v)
{
*(T*)obj = T(float_tag(), v);
}
void deallocate(void* obj)
{
delete (T*)obj;
}
void copy(void* target, void* source)
{
*(T*)target = *(T*)source;
}
std::string tostring(void* obj)
{
return ((T*)obj)->tostring();
}
std::string format(void* obj, mathexpr_format fmt)
{
return ((T*)obj)->format(fmt);
}
uint64_t tounsigned(void* obj)
{
return ((T*)obj)->tounsigned();
}
int64_t tosigned(void* obj)
{
return ((T*)obj)->tosigned();
}
bool toboolean(void* obj)
{
return ((T*)obj)->toboolean();
}
void scale(void* val, uint64_t _scale)
{
return ((T*)val)->scale(_scale);
}
std::set<mathexpr_operinfo*> operations()
{
std::set<mathexpr_operinfo*> ret;
auto tmp = T::operations();
for(auto i : tmp)
ret.insert(i);
return ret;
}
};
class mathexpr : public garbage_collectable
{
public:
enum eval_state
{
TO_BE_EVALUATED, //Not even attempted to evaluate yet.
EVALUATING, //Evaluation in progress.
EVALUATED, //Evaluation completed, value available.
FIXED, //This operand has fixed value.
UNDEFINED, //This operand has undefined value.
ERROR, //Evaluation failed.
FORWARD, //Forward evaluation to first of args.
FORWARD_EVALING, //Forward evaluation to first of args, evaluating.
FORWARD_EVALD, //Forward evaluation to first of args, evaluated.
};
//Undefined value of specified type.
mathexpr(mathexpr_typeinfo* _type);
//Forward evaluation.
mathexpr(mathexpr_typeinfo* _type, gcroot_pointer<mathexpr> fwd);
//Value of specified type.
mathexpr(mathexpr_value value);
//Value of specified type.
mathexpr(mathexpr_typeinfo* _type, const std::string& value, bool string);
//Specified Operator.
mathexpr(mathexpr_typeinfo* _type, mathexpr_operinfo* fn, std::vector<gcroot_pointer<mathexpr>> _args,
bool _owns_operator = false);
//Dtor.
~mathexpr();
//Copy ctor.
mathexpr(const mathexpr& m);
mathexpr& operator=(const mathexpr& m);
//Evaluate. Returns pointer to internal state.
mathexpr_value evaluate();
//Get type.
mathexpr_typeinfo& get_type() { return type; }
//Reset.
void reset();
//Parse an expression.
static gcroot_pointer<mathexpr> parse(mathexpr_typeinfo& _type, const std::string& expr,
std::function<gcroot_pointer<mathexpr>(const std::string&)> vars);
protected:
void trace();
private:
void mark_error_and_throw(mathexpr_error::errorcode _errcode, const std::string& _error);
eval_state state;
mathexpr_typeinfo& type; //Type of value.
void* value; //Value if state is EVALUATED or FIXED.
mathexpr_operinfo* fn; //Function (if state is TO_BE_EVALUATED, EVALUATING or EVALUATED)
mathexpr_error::errorcode errcode; //Error code if state is ERROR.
std::string error; //Error message if state is ERROR.
std::vector<mathexpr*> arguments;
mutable bool owns_operator;
};
#endif

View file

@ -0,0 +1,34 @@
#ifndef _library__memorywatch_fb__hpp__included__
#define _library__memorywatch_fb__hpp__included__
#include "framebuffer.hpp"
#include "memorywatch.hpp"
#include "mathexpr.hpp"
#include "framebuffer-font2.hpp"
struct memorywatch_output_fb : public memorywatch_item_printer
{
memorywatch_output_fb();
~memorywatch_output_fb();
void set_rqueue(framebuffer::queue& rqueue);
void set_dtor_cb(std::function<void(memorywatch_output_fb&)> cb);
void show(const std::string& iname, const std::string& val);
void reset();
bool cond_enable;
gcroot_pointer<mathexpr> enabled;
gcroot_pointer<mathexpr> pos_x;
gcroot_pointer<mathexpr> pos_y;
bool alt_origin_x;
bool alt_origin_y;
bool cliprange_x;
bool cliprange_y;
framebuffer::font2* font;
framebuffer::color fg;
framebuffer::color bg;
framebuffer::color halo;
//State variables.
framebuffer::queue* queue;
std::function<void(memorywatch_output_fb&)> dtor_cb;
};
#endif

View file

@ -0,0 +1,22 @@
#ifndef _library__memorywatch_list__hpp__included__
#define _library__memorywatch_list__hpp__included__
#include "memorywatch.hpp"
#include "mathexpr.hpp"
#include <string>
#include <functional>
struct memorywatch_output_list : public memorywatch_item_printer
{
memorywatch_output_list();
~memorywatch_output_list();
void set_output(std::function<void(const std::string& n, const std::string& v)> _fn);
void show(const std::string& iname, const std::string& val);
void reset();
bool cond_enable;
gcroot_pointer<mathexpr> enabled;
//State variables.
std::function<void(const std::string& n, const std::string& v)> fn;
};
#endif

View file

@ -0,0 +1,17 @@
#ifndef _library__memorywatch_null__hpp__included__
#define _library__memorywatch_null__hpp__included__
#include "memorywatch.hpp"
#include "mathexpr.hpp"
#include <string>
#include <functional>
struct memorywatch_output_null : public memorywatch_item_printer
{
memorywatch_output_null();
~memorywatch_output_null();
void show(const std::string& iname, const std::string& val);
void reset();
};
#endif

View file

@ -0,0 +1,169 @@
#ifndef _library__memorywatch__hpp__included__
#define _library__memorywatch__hpp__included__
#include "mathexpr.hpp"
#include "memoryspace.hpp"
#include <list>
#include <set>
#include <map>
/**
* Read memory operator.
*/
struct memorywatch_memread_oper : public mathexpr_operinfo
{
/**
* Ctor
*/
memorywatch_memread_oper();
/**
* Dtor
*/
~memorywatch_memread_oper();
/**
* Evaluate the operator.
*
* Note: The first promise is for the address.
*/
void evaluate(mathexpr_value target, std::vector<std::function<mathexpr_value()>> promises);
//Fields.
unsigned bytes; //Number of bytes to read.
bool signed_flag; //Is signed?
bool float_flag; //Is float?
int endianess; //Endianess (-1 => little, 0 => host, 1 => Big).
uint64_t scale_div; //Scale divisor.
uint64_t addr_base; //Address base.
uint64_t addr_size; //Address size (0 => All).
memory_space* mspace; //Memory space to read.
};
/**
* Memory watch item printer.
*/
struct memorywatch_item_printer : public garbage_collectable
{
/**
* Dtor.
*/
virtual ~memorywatch_item_printer();
/**
* Show the watched value.
*/
virtual void show(const std::string& iname, const std::string& val) = 0;
/**
* Reset the printer.
*/
virtual void reset() = 0;
protected:
void trace();
};
/**
* Memory watch item.
*/
struct memorywatch_item
{
/**
* Ctor.
*
* Parameter t: The type of the result.
*/
memorywatch_item(mathexpr_typeinfo& t)
: expr(gcroot_pointer_object_tag(), &t)
{
}
/**
* Get the value as string.
*/
std::string get_value();
/**
* Print the value to specified printer.
*
* Parameter iname: The name of the watch.
*/
void show(const std::string& iname);
//Fields.
gcroot_pointer<memorywatch_item_printer> printer; //Printer to use.
gcroot_pointer<mathexpr> expr; //Expression to watch.
std::string format; //Formatting to use.
};
/**
* A set of memory watches.
*/
struct memorywatch_set
{
/**
* Dtor.
*/
~memorywatch_set();
/**
* Call reset on all items and their printers in the set.
*/
void reset();
/**
* Call reset and then show on all items in the set.
*/
void refresh();
/**
* Get the longest name (by UTF-8 length) in the set.
*
* Returns: The longest nontrivial name, or "" if none.
*/
const std::string& get_longest_name()
{
return get_longest_name(memorywatch_set::utflength_rate);
}
/**
* Get the longest name (by arbitrary function) in the set.
*
* Parameter rate: Get length for name function.
* Returns: The longest nontrivial name, or "" if none.
*/
const std::string& get_longest_name(std::function<size_t(const std::string& n)> rate);
/**
* Get the set of memory watch names.
*/
std::set<std::string> set();
/**
* Get specified memory watch item.
*
* Parameter name: The name of the item.
* Returns: The item.
* Throws std::runtime_error: No such item in set.
*/
memorywatch_item& get(const std::string& name);
/**
* Get specified memory watch item (without throwing).
*
* Parameter name: The name of the item.
* Returns: The item, or NULL if no such item exists.
*/
memorywatch_item* get_soft(const std::string& name);
/**
* Create a new memory watch item.
*
* Parameter name: The name of the new item.
* Parameter item: The new item. All fields are shallow-copied.
*/
memorywatch_item* create(const std::string& name, memorywatch_item& item);
/**
* Destroy a memory watch item.
*
* Parameter name: The name of the item to destroy.
*/
void destroy(const std::string& name);
/**
* Call routine for all roots.
*/
void foreach(std::function<void(memorywatch_item& item)> cb);
/**
* Swap set with another.
*/
void swap(memorywatch_set& s) throw();
private:
static size_t utflength_rate(const std::string& s);
std::map<std::string, memorywatch_item> roots;
};
#endif

View file

@ -61,6 +61,7 @@ extern single_type filetype_hexbookmarks;
extern single_type filetype_memorysearch;
extern single_type filetype_textfile;
extern single_type filetype_trace;
extern single_type filetype_font;
filedialog_output_params show_filedialog(wxWindow* parent, const std::string& title, const std::string& basepath,
const filedialog_input_params& p, const std::string& defaultname, bool saving);

View file

@ -48,7 +48,7 @@ std::vector<interface_action_paramval> prompt_action_params(wxWindow* parent, co
//Editor dialogs.
void wxeditor_authors_display(wxWindow* parent);
void wxeditor_hotkeys_display(wxWindow* parent);
void wxeditor_memorywatch_display(wxWindow* parent);
void wxeditor_memorywatches_display(wxWindow* parent);
void wxeditor_subtitles_display(wxWindow* parent);
std::string wxeditor_keyselect(wxWindow* parent, bool clearable);
void show_wxeditor_voicesub(wxWindow* parent);

View file

@ -1148,203 +1148,299 @@ Memory watch expression syntax
\end_layout
\begin_layout Standard
Memory watch expressions are in RPN (Reverse Polish Notation).
At the end of expression, the top entry on stack is taken as the final
result.
Memory watch expressions has the following syntax elements:
\end_layout
\begin_layout Standard
Notations:
\begin_layout LyX-Code
${foo} The value of memory watch foo.
\end_layout
\begin_layout Itemize
Evaluation order is strictly left to right.
\begin_layout LyX-Code
0x1234 Hexadecimal number 1234
\end_layout
\begin_layout Itemize
a is the entry on top of stack
\begin_layout LyX-Code
12345 Decimal number 12345
\end_layout
\begin_layout Itemize
b is the entry immediately below top of stack
\begin_layout LyX-Code
3.141 Decimal number 3.141
\end_layout
\begin_layout Itemize
; separates values to be pushed (no intermediate pop).
\begin_layout LyX-Code
-a Unary negation
\end_layout
\begin_layout Itemize
After end of element, all used stack slots are popped and all results are
pushed.
\begin_layout LyX-Code
~a Bitwise NOT (integers only)
\end_layout
\begin_layout Itemize
When pushing multiple values, the pushes occur in order shown.
\begin_layout LyX-Code
a*b Multiplication
\end_layout
\begin_layout Standard
The following operators are available:
\begin_layout LyX-Code
a/b Division/quotent
\end_layout
\begin_layout Itemize
+ : a + b
\begin_layout LyX-Code
a%b Remainder (integers only)
\end_layout
\begin_layout Itemize
- : a - b
\begin_layout LyX-Code
a+b Sum or string concatenation
\end_layout
\begin_layout Itemize
* : a * b
\begin_layout LyX-Code
a-b Difference
\end_layout
\begin_layout Itemize
/ : a / b
\begin_layout LyX-Code
a<<b Shift left (integers only)
\end_layout
\begin_layout Itemize
% : a % b
\begin_layout LyX-Code
a>>b Shift right (integers only).
Arithmetic for signed.
\end_layout
\begin_layout Itemize
a : atan(a)
\begin_layout LyX-Code
a<b Less than
\end_layout
\begin_layout Itemize
b : read_signed_byte(a)
\begin_layout LyX-Code
a<=b Less or equal to
\end_layout
\begin_layout Itemize
c : cos(a)
\begin_layout LyX-Code
a==b Equal to
\end_layout
\begin_layout Itemize
d : read_signed_dword(a)
\begin_layout LyX-Code
a!=b Not equal to
\end_layout
\begin_layout Itemize
f: read_float(a)
\begin_layout LyX-Code
a>=b Greater or equal to
\end_layout
\begin_layout Itemize
i : quotent(a / b)
\begin_layout LyX-Code
a>b Greater than
\end_layout
\begin_layout Itemize
o: read_signed_hword(a)
\begin_layout LyX-Code
a&b Bitwise AND (integers only)
\end_layout
\begin_layout Itemize
p :
\begin_inset Formula $\pi$
\end_inset
\begin_layout LyX-Code
a^b Bitwise XOR (integers only)
\end_layout
\begin_layout Itemize
q : read_signed_qword(a)
\begin_layout LyX-Code
a|b Bitwise OR (integers only)
\end_layout
\begin_layout Itemize
r : sqrt(a)
\begin_layout LyX-Code
a&&b Logical AND
\end_layout
\begin_layout Itemize
s : sin(a)
\begin_layout LyX-Code
a||b Logical OR
\end_layout
\begin_layout Itemize
t : tan(a)
\begin_layout LyX-Code
π Numeric constant pi.
\end_layout
\begin_layout Itemize
u : a; a
\begin_layout LyX-Code
i Imaginary unit
\end_layout
\begin_layout Itemize
w : read_signed_word(a)
\begin_layout LyX-Code
if(x,y) If x is true, y, else false.
\end_layout
\begin_layout Itemize
A : atan2(a, b)
\begin_layout LyX-Code
if(x,y,z) If x is true, y, else z.
\end_layout
\begin_layout Itemize
B : read_unsigned_byte(a)
\begin_layout LyX-Code
select(x...)
\end_layout
\begin_layout Itemize
C<number>z : Push number <number> to stack.
\begin_layout LyX-Code
First value in x...
that is not false, or false if none.
\end_layout
\begin_layout Itemize
D : read_unsigned_dword(a)
\begin_layout LyX-Code
unsigned(x)
\end_layout
\begin_layout Itemize
C0x<number>z : Push number <number> (hexadecimal) to stack.
\begin_layout LyX-Code
Cast x to unsigned.
\end_layout
\begin_layout Itemize
F: read_double(a)
\begin_layout LyX-Code
signed(x)
\end_layout
\begin_layout Itemize
H<digit> : Set hexadecimal mode with specified number of digits (use A-G
for 10-16 digits).
\begin_layout LyX-Code
Cast x to signed.
\end_layout
\begin_layout Itemize
O: read_unsigned_hword(a)
\begin_layout LyX-Code
float(x)
\end_layout
\begin_layout Itemize
Q : read_unsigned_qword(a)
\begin_layout LyX-Code
Cast x to float.
\end_layout
\begin_layout Itemize
R<digit> : round a to <digit> digits.
\begin_layout LyX-Code
min(x...) The smallest value among x...
or false if empty.
\end_layout
\begin_layout Itemize
W : read_unsigned_word(a)
\begin_layout LyX-Code
max(x...) The largest value among x...
or false if empty.
\end_layout
\begin_layout Subsection
Example:
\begin_layout LyX-Code
sum(x...) Sum/concatenation of x...
or false if empty.
\end_layout
\begin_layout Standard
C0x007e0878zWC0x007e002czW-
\begin_layout LyX-Code
prod(x...) Product of x...
or false if empty.
\end_layout
\begin_layout Enumerate
Push value 0x7e0878 on top of stack (C0x007e0878z).
\begin_layout LyX-Code
sqrt(x) Square root of x.
\end_layout
\begin_layout Enumerate
Pop the value on top of stack (0x7e0878), read word value at that address
and push the result,call it x1 (W).
\begin_layout LyX-Code
log(x) Natural log of x.
\end_layout
\begin_layout Enumerate
Push value 0x7e002c on top of stack (C0x007e002cz).
\begin_layout LyX-Code
log(x,y) Log of y to base x.
\end_layout
\begin_layout Enumerate
Pop the value on top of stack (0x7e002c), read word value at that address
and push the result,call it x2 (W).
\begin_layout LyX-Code
exp(x) e^x.
\end_layout
\begin_layout Enumerate
Pop the two top numbers on stack, x1 and x2, substract x1 from x2 and push
x2 - x1 (-).
\begin_layout LyX-Code
exp(x,y) x^y
\end_layout
\begin_layout Enumerate
Since the expression ends, the final memory watch result is the top one
on stack, which is x2 - x1.
\begin_layout LyX-Code
sin(x) Sine of x
\end_layout
\begin_layout LyX-Code
cos(x) Cosine of x
\end_layout
\begin_layout LyX-Code
tan(x) Tangent of x
\end_layout
\begin_layout LyX-Code
asin(x) Arcsine of x
\end_layout
\begin_layout LyX-Code
acos(x) Arccosine of x
\end_layout
\begin_layout LyX-Code
atan(x) Arctangent of x
\end_layout
\begin_layout LyX-Code
atan(x,y) Angle between vector (x,y) and x-axis.
\end_layout
\begin_layout LyX-Code
sinh(x) Hyperbolic sine of x
\end_layout
\begin_layout LyX-Code
cosh(x) Hyperbolic cosine of x
\end_layout
\begin_layout LyX-Code
tanh(x) Hyperbolic tangent of x
\end_layout
\begin_layout LyX-Code
arsinh(x) Hyperbolic arsine of x
\end_layout
\begin_layout LyX-Code
arcosh(x) Hyperbolic arcosine of x
\end_layout
\begin_layout LyX-Code
artanh(x) Hyperbolic artangent of x
\end_layout
\begin_layout LyX-Code
torad(x) Convert x degrees to radians.
\end_layout
\begin_layout LyX-Code
todeg(x) Convert x radians to degrees.
\end_layout
\begin_layout LyX-Code
re(x) Real part of complex number x.
\end_layout
\begin_layout LyX-Code
im(x) Imaginary part of complex number x.
\end_layout
\begin_layout LyX-Code
conj(x) Complex conjugate of x.
\end_layout
\begin_layout LyX-Code
abs(x) Absolute value of x.
\end_layout
\begin_layout LyX-Code
arg(x) Argument of x.
\end_layout
\begin_layout LyX-Code
pyth(x...) sqrt(sum(x^2)).
I.e.
pythagorean distance.
\end_layout
\begin_layout LyX-Code
e Base of natural logarithm
\end_layout
\begin_layout LyX-Code
pi Pi
\end_layout
\begin_layout LyX-Code
true Constant true
\end_layout
\begin_layout LyX-Code
false Constant false
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Section

View file

@ -558,108 +558,150 @@ The right mouse button pops up a context-sensitive menu:
8 Memory watch expression syntax
Memory watch expressions are in RPN (Reverse Polish Notation). At
the end of expression, the top entry on stack is taken as the
final result.
Memory watch expressions has the following syntax elements:
Notations:
${foo} The value of memory watch foo.
• Evaluation order is strictly left to right.
0x1234 Hexadecimal number 1234
• a is the entry on top of stack
12345 Decimal number 12345
• b is the entry immediately below top of stack
3.141 Decimal number 3.141
• ; separates values to be pushed (no intermediate pop).
-a Unary negation
• After end of element, all used stack slots are popped and all
results are pushed.
~a Bitwise NOT (integers only)
• When pushing multiple values, the pushes occur in order shown.
a*b Multiplication
The following operators are available:
a/b Division/quotent
• + : a + b
a%b Remainder (integers only)
• - : a - b
a+b Sum or string concatenation
• * : a * b
a-b Difference
• / : a / b
a<<b Shift left (integers only)
• % : a % b
a>>b Shift right (integers only). Arithmetic for signed.
• a : atan(a)
a<b Less than
• b : read_signed_byte(a)
a<=b Less or equal to
• c : cos(a)
a==b Equal to
• d : read_signed_dword(a)
a!=b Not equal to
• f: read_float(a)
a>=b Greater or equal to
• i : quotent(a / b)
a>b Greater than
• o: read_signed_hword(a)
a&b Bitwise AND (integers only)
• p :\pi
a^b Bitwise XOR (integers only)
a|b Bitwise OR (integers only)
a&&b Logical AND
a||b Logical OR
π Numeric constant pi.
i Imaginary unit
if(x,y) If x is true, y, else false.
if(x,y,z) If x is true, y, else z.
select(x...)
First value in x... that is not false, or false if
none.
unsigned(x)
Cast x to unsigned.
signed(x)
Cast x to signed.
float(x)
Cast x to float.
min(x...) The smallest value among x... or false if empty.
max(x...) The largest value among x... or false if empty.
sum(x...) Sum/concatenation of x... or false if empty.
prod(x...) Product of x... or false if empty.
sqrt(x) Square root of x.
log(x) Natural log of x.
log(x,y) Log of y to base x.
exp(x) e^x.
exp(x,y) x^y
sin(x) Sine of x
cos(x) Cosine of x
tan(x) Tangent of x
asin(x) Arcsine of x
acos(x) Arccosine of x
atan(x) Arctangent of x
atan(x,y) Angle between vector (x,y) and x-axis.
sinh(x) Hyperbolic sine of x
cosh(x) Hyperbolic cosine of x
tanh(x) Hyperbolic tangent of x
arsinh(x) Hyperbolic arsine of x
arcosh(x) Hyperbolic arcosine of x
artanh(x) Hyperbolic artangent of x
torad(x) Convert x degrees to radians.
todeg(x) Convert x radians to degrees.
re(x) Real part of complex number x.
im(x) Imaginary part of complex number x.
conj(x) Complex conjugate of x.
abs(x) Absolute value of x.
arg(x) Argument of x.
pyth(x...) sqrt(sum(x^2)). I.e. pythagorean distance.
e Base of natural logarithm
pi Pi
true Constant true
false Constant false
• q : read_signed_qword(a)
• r : sqrt(a)
• s : sin(a)
• t : tan(a)
• u : a; a
• w : read_signed_word(a)
• A : atan2(a, b)
• B : read_unsigned_byte(a)
• C<number>z : Push number <number> to stack.
• D : read_unsigned_dword(a)
• C0x<number>z : Push number <number> (hexadecimal) to stack.
• F: read_double(a)
• H<digit> : Set hexadecimal mode with specified number of digits
(use A-G for 10-16 digits).
• O: read_unsigned_hword(a)
• Q : read_unsigned_qword(a)
• R<digit> : round a to <digit> digits.
• W : read_unsigned_word(a)
8.1 Example:
C0x007e0878zWC0x007e002czW-
1. Push value 0x7e0878 on top of stack (C0x007e0878z).
2. Pop the value on top of stack (0x7e0878), read word value at
that address and push the result,call it x1 (W).
3. Push value 0x7e002c on top of stack (C0x007e002cz).
4. Pop the value on top of stack (0x7e002c), read word value at
that address and push the result,call it x2 (W).
5. Pop the two top numbers on stack, x1 and x2, substract x1 from
x2 and push x2 - x1 (-).
6. Since the expression ends, the final memory watch result is
the top one on stack, which is x2 - x1.
9 Modifier and key names:

View file

@ -2,15 +2,18 @@
#include "core/dispatch.hpp"
#include "core/framebuffer.hpp"
#include "core/keymapper.hpp"
#include "core/memorywatch.hpp"
#include "core/misc.hpp"
#include "core/moviedata.hpp"
#include "core/moviefile.hpp"
#include "core/subtitles.hpp"
#include "core/settings.hpp"
#include "core/window.hpp"
#include "lua/lua.hpp"
#include "fonts/wrapper.hpp"
#include "library/framebuffer.hpp"
#include "library/framebuffer-pixfmt-lrgb.hpp"
#include "library/minmax.hpp"
framebuffer::raw screen_corrupt;
@ -129,6 +132,14 @@ namespace
messages << "Saved PNG screenshot" << std::endl;
});
settingvar::variable<settingvar::model_int<0, 8191>> dtb(lsnes_vset, "top-border", "UI‣Top padding", 0);
settingvar::variable<settingvar::model_int<0, 8191>> dbb(lsnes_vset, "bottom-border",
"UI‣Bottom padding", 0);
settingvar::variable<settingvar::model_int<0, 8191>> dlb(lsnes_vset, "left-border",
"UI‣Left padding", 0);
settingvar::variable<settingvar::model_int<0, 8191>> drb(lsnes_vset, "right-border", "UI‣Right padding",
0);
bool last_redraw_no_lua = true;
}
@ -186,10 +197,11 @@ void redraw_framebuffer(framebuffer::raw& todraw, bool no_lua, bool spontaneous)
ri.fbuf = todraw;
ri.hscl = hscl;
ri.vscl = vscl;
ri.lgap = lrc.left_gap;
ri.rgap = lrc.right_gap;
ri.tgap = lrc.top_gap;
ri.bgap = lrc.bottom_gap;
ri.lgap = max(lrc.left_gap, (unsigned)dlb);
ri.rgap = max(lrc.right_gap, (unsigned)drb);
ri.tgap = max(lrc.top_gap, (unsigned)dtb);
ri.bgap = max(lrc.bottom_gap, (unsigned)dbb);
lsnes_memorywatch.watch(ri.rq);
buffering.end_write();
notify_screen_update();
last_redraw_no_lua = no_lua;

View file

@ -381,8 +381,6 @@ void update_movie_state()
}
_status.set("!macros", mss.str());
do_watch_memory();
controller_frame c;
if(!multitrack_editor.any_records())
c = movb.get_movie().get_controls();
@ -1251,6 +1249,9 @@ void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_
core_core::uninstall_all_handlers();
voicethread_kill();
platform::system_thread_available(false);
//Kill all memory watches (so dtor ordering doesn't cause a crash).
project_set(NULL, true);
lsnes_memorywatch.clear_multi(lsnes_memorywatch.enumerate());
}
void set_stop_at_frame(uint64_t frame)

View file

@ -1,11 +1,19 @@
#include "core/command.hpp"
#include "core/dispatch.hpp"
#include "core/framebuffer.hpp"
#include "core/memorymanip.hpp"
#include "core/memorywatch.hpp"
#include "core/project.hpp"
#include "core/window.hpp"
#include "fonts/wrapper.hpp"
#include "library/directory.hpp"
#include "library/string.hpp"
#include "library/int24.hpp"
#include "library/mathexpr-ntype.hpp"
#include "library/memorywatch.hpp"
#include "library/memorywatch-list.hpp"
#include "library/memorywatch-fb.hpp"
#include "library/memorywatch-null.hpp"
#include <cstdio>
#include <cstdlib>
@ -16,441 +24,459 @@
#include <sstream>
#include <map>
void update_movie_state();
namespace
{
std::map<std::string, std::string> watches;
std::map<std::string, std::pair<framebuffer::font2*, size_t>> fonts_in_use;
struct numeric_type
framebuffer::font2& get_builtin_font2()
{
numeric_type() { t = VT_NAN; hex = 0; }
numeric_type(int8_t x) { t = VT_SIGNED; s = x; hex = 0; }
numeric_type(uint8_t x) { t = VT_UNSIGNED; u = x; hex = 0; }
numeric_type(int16_t x) { t = VT_SIGNED; s = x; hex = 0; }
numeric_type(uint16_t x) { t = VT_UNSIGNED; u = x; hex = 0; }
numeric_type(int32_t x) { t = VT_SIGNED; s = x; hex = 0; }
numeric_type(uint32_t x) { t = VT_UNSIGNED; u = x; hex = 0; }
numeric_type(int64_t x) { t = VT_SIGNED; s = x; hex = 0; }
numeric_type(uint64_t x) { t = VT_UNSIGNED; u = x; hex = 0; }
numeric_type(double x) { t = VT_FLOAT; f = x; }
numeric_type(const std::string& _s)
{
char* end;
if(_s.length() > 2 && _s[0] == '0' && _s[1] == 'x') {
t = VT_UNSIGNED;
u = strtoull(_s.c_str() + 2, &end, 16);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else if(_s.length() > 3 && _s[0] == '+' && _s[1] == '0' && _s[2] == 'x') {
t = VT_SIGNED;
s = (int64_t)strtoull(_s.c_str() + 3, &end, 16);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else if(_s.length() > 3 && _s[0] == '-' && _s[1] == '0' && _s[2] == 'x') {
t = VT_SIGNED;
s = -(int64_t)strtoull(_s.c_str() + 3, &end, 16);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else if(_s.find_first_of(".") < _s.length()) {
t = VT_FLOAT;
f = strtod(_s.c_str(), &end);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else if(_s.length() > 1 && _s[0] == '+') {
t = VT_SIGNED;
s = (int64_t)strtoull(_s.c_str() + 1, &end, 10);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else if(_s.length() > 1 && _s[0] == '-') {
t = VT_SIGNED;
s = -(int64_t)strtoull(_s.c_str() + 1, &end, 10);
if(*end)
throw std::runtime_error("#syntax (badval)");
} else {
t = VT_UNSIGNED;
u = strtoull(_s.c_str(), &end, 10);
if(*end)
throw std::runtime_error("#syntax (badval)");
}
hex = 0;
}
uint64_t as_address() const
{
switch(t) {
case VT_SIGNED: return s;
case VT_UNSIGNED: return u;
case VT_FLOAT: return f;
case VT_NAN: throw std::runtime_error("#NAN");
};
return 0;
}
int64_t as_integer() const
{
switch(t) {
case VT_SIGNED: return s;
case VT_UNSIGNED: return u;
case VT_FLOAT: return f;
case VT_NAN: throw std::runtime_error("#NAN");
};
return 0;
}
double as_double() const
{
switch(t) {
case VT_SIGNED: return s;
case VT_UNSIGNED: return u;
case VT_FLOAT: return f;
case VT_NAN: throw std::runtime_error("#NAN");
};
return 0;
}
std::string str() const
{
uint64_t wmasks[] = {
0x0000000000000000ULL, 0x000000000000000FULL, 0x00000000000000FFULL,
0x0000000000000FFFULL, 0x000000000000FFFFULL, 0x00000000000FFFFFULL,
0x0000000000FFFFFFULL, 0x000000000FFFFFFFULL, 0x00000000FFFFFFFFULL,
0x0000000FFFFFFFFFULL, 0x000000FFFFFFFFFFULL, 0x00000FFFFFFFFFFFULL,
0x0000FFFFFFFFFFFFULL, 0x000FFFFFFFFFFFFFULL, 0x00FFFFFFFFFFFFFFULL,
0x0FFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFFULL
};
std::ostringstream x;
if(hex && (t == VT_SIGNED || t == VT_UNSIGNED)) {
uint64_t wmask = wmasks[hex];
uint64_t w = (t == VT_SIGNED) ? s : u;
x << std::hex << std::setw(hex) << std::setfill('0') << (w & wmask);
return x.str();
}
switch(t) {
case VT_SIGNED: x << s; break;
case VT_UNSIGNED: x << u; break;
case VT_FLOAT: x << f; break;
case VT_NAN: x << "#NAN"; break;
};
return x.str();
}
numeric_type round(int prec) const
{
double b = 0, c = 0;
switch(t) {
case VT_FLOAT:
b = pow(10, prec);
c = floor(b * f + 0.5) / b;
return numeric_type(c);
default:
return *this;
}
}
numeric_type operator+(const numeric_type& b) const
{
if(t == VT_NAN || b.t == VT_NAN)
return numeric_type();
else if(t == VT_FLOAT || b.t == VT_FLOAT)
return numeric_type(as_double() + b.as_double());
else if(t == VT_SIGNED || b.t == VT_SIGNED)
return numeric_type(as_integer() + b.as_integer());
else
return numeric_type(as_address() + b.as_address());
}
numeric_type operator-(const numeric_type& b) const
{
if(t == VT_NAN || b.t == VT_NAN)
return numeric_type();
else if(t == VT_FLOAT || b.t == VT_FLOAT)
return numeric_type(as_double() - b.as_double());
else if(t == VT_SIGNED || b.t == VT_SIGNED)
return numeric_type(as_integer() - b.as_integer());
else
return numeric_type(as_address() - b.as_address());
}
numeric_type operator*(const numeric_type& b) const
{
if(t == VT_NAN || b.t == VT_NAN)
return numeric_type();
else if(t == VT_FLOAT || b.t == VT_FLOAT)
return numeric_type(as_double() * b.as_double());
else if(t == VT_SIGNED || b.t == VT_SIGNED)
return numeric_type(as_integer() * b.as_integer());
else
return numeric_type(as_address() * b.as_address());
}
numeric_type operator/(const numeric_type& b) const
{
if(b.t != VT_NAN && fabs(b.as_double()) < 1e-30)
throw std::runtime_error("#DIV-BY-0");
if(t == VT_NAN || b.t == VT_NAN)
return numeric_type();
else
return numeric_type(as_double() / b.as_double());
}
numeric_type operator%(const numeric_type& b) const
{
return numeric_type(*this - b * idiv(b));
}
numeric_type idiv(const numeric_type& b) const
{
if(b.t != VT_NAN && fabs(b.as_double()) < 1e-30)
throw std::runtime_error("#DIV-BY-0");
if(t == VT_NAN || b.t == VT_NAN)
return numeric_type();
else if(t == VT_FLOAT || b.t == VT_FLOAT)
return numeric_type(floor(as_double() / b.as_double()));
else if(t == VT_SIGNED || b.t == VT_SIGNED)
return numeric_type(as_integer() / b.as_integer());
else
return numeric_type(as_address() / b.as_address());
}
void sethex(char ch)
{
if(ch >= '0' && ch <= '9')
hex = (ch - '0');
if(ch >= 'A' && ch <= 'G')
hex = (ch - 'A') + 10;
if(ch >= 'a' && ch <= 'g')
hex = (ch - 'a') + 10;
}
private:
enum value_type
{
VT_SIGNED,
VT_UNSIGNED,
VT_FLOAT,
VT_NAN
} t;
int64_t s;
uint64_t u;
double f;
unsigned hex;
};
numeric_type stack_pop(std::stack<numeric_type>& s, bool norm = false)
{
if(s.size() < 1)
throw std::runtime_error("#syntax (underflow)");
numeric_type r = s.top();
if(!norm)
s.pop();
return r;
static framebuffer::font2 f(main_font);
return f;
}
template<typename T> void stack_push(std::stack<numeric_type>& s, T val)
framebuffer::font2* get_font(const std::string filename)
{
s.push(numeric_type(val));
//Handle NULL font.
if(filename == "")
return &get_builtin_font2();
std::string abs_filename = get_absolute_path(filename);
if(fonts_in_use.count(abs_filename)) {
fonts_in_use[abs_filename].second++;
return fonts_in_use[abs_filename].first;
}
framebuffer::font2* f = new framebuffer::font2(abs_filename);
try {
fonts_in_use[abs_filename] = std::make_pair(f, 1);
} catch(...) {
delete f;
throw;
}
return f;
}
void put_font(framebuffer::font2* font)
{
//Handle NULL font (always there).
if(!font)
return;
//Find font using this.
std::string filename;
for(auto& i : fonts_in_use)
if(i.second.first == font)
filename = i.first;
if(filename == "")
return;
fonts_in_use[filename].second--;
if(!fonts_in_use[filename].second) {
delete fonts_in_use[filename].first;
fonts_in_use.erase(filename);
}
}
std::map<std::string, bool> used_memorywatches;
void memorywatch_output_fn(const std::string& name, const std::string& value)
{
auto& status = platform::get_emustatus();
used_memorywatches[name] = true;
status.set("M[" + name + "]", value);
}
void erase_unused_watches()
{
auto& status = platform::get_emustatus();
for(auto& i : used_memorywatches) {
if(!i.second)
status.erase("M[" + i.first + "]");
i.second = false;
}
}
std::string json_string_default(const JSON::node& node, const std::string& pointer, const std::string& dflt)
{
return (node.type_of(pointer) == JSON::string) ? node[pointer].as_string8() : dflt;
}
uint64_t json_unsigned_default(const JSON::node& node, const std::string& pointer, uint64_t dflt)
{
return (node.type_of(pointer) == JSON::number) ? node[pointer].as_uint() : dflt;
}
int64_t json_signed_default(const JSON::node& node, const std::string& pointer, int64_t dflt)
{
return (node.type_of(pointer) == JSON::number) ? node[pointer].as_int() : dflt;
}
bool json_boolean_default(const JSON::node& node, const std::string& pointer, bool dflt)
{
return (node.type_of(pointer) == JSON::boolean) ? node[pointer].as_bool() : dflt;
}
}
std::string evaluate_watch(const std::string& expr) throw(std::bad_alloc)
lsnes_memorywatch_printer::lsnes_memorywatch_printer()
{
std::stack<numeric_type> s;
size_t y;
std::string _expr = expr;
std::string t;
numeric_type a;
numeric_type b;
int d;
try {
for(size_t i = 0; i < expr.length(); i++) {
numeric_type r;
switch(expr[i]) {
case 'C':
y = expr.find_first_of("z", i);
if(y > expr.length())
return "#syntax (noterm)";
t = _expr.substr(i + 1, y - i - 1);
stack_push(s, numeric_type(t));
i = y;
break;
case 'R':
if(i + 1 == expr.length())
throw std::runtime_error("#syntax (noparam)");
a = stack_pop(s);
d = expr[++i] - '0';
stack_push(s, a.round(d));
break;
case 'H':
if(i + 1 == expr.length())
throw std::runtime_error("#syntax (noparam)");
s.top().sethex(expr[++i]);
break;
case 'a':
stack_push<double>(s, atan(stack_pop(s).as_double()));
break;
case 'A':
a = stack_pop(s);
b = stack_pop(s);
stack_push<double>(s, atan2(a.as_double(), b.as_double()));
break;
case 'c':
stack_push<double>(s, cos(stack_pop(s).as_double()));
break;
case 'r':
a = stack_pop(s);
if(a.as_double() < 0)
throw std::runtime_error("#NAN");
stack_push<double>(s, sqrt(a.as_double()));
break;
case 's':
stack_push<double>(s, sin(stack_pop(s).as_double()));
break;
case 't':
stack_push<double>(s, tan(stack_pop(s).as_double()));
break;
case 'u':
stack_push(s, stack_pop(s, true));
break;
case 'p':
stack_push(s, 4 * atan(1));
break;
case '+':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a + b);
break;
case '-':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a - b);
break;
case '*':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a * b);
break;
case 'i':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a.idiv(b));
break;
case '/':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a / b);
break;
case '%':
a = stack_pop(s);
b = stack_pop(s);
stack_push(s, a % b);
break;
case 'b':
stack_push<int8_t>(s, lsnes_memory.read<uint8_t>(stack_pop(s).as_address()));
break;
case 'B':
stack_push<uint8_t>(s, lsnes_memory.read<uint8_t>(stack_pop(s).as_address()));
break;
case 'w':
stack_push<int16_t>(s, lsnes_memory.read<uint16_t>(stack_pop(s).as_address()));
break;
case 'W':
stack_push<uint16_t>(s, lsnes_memory.read<uint16_t>(stack_pop(s).as_address()));
break;
case 'o':
stack_push<ss_int24_t>(s, lsnes_memory.read<ss_uint24_t>(stack_pop(s).as_address()));
break;
case 'O':
stack_push<ss_uint24_t>(s, lsnes_memory.read<ss_uint24_t>(stack_pop(s).as_address()));
break;
case 'd':
stack_push<int32_t>(s, lsnes_memory.read<uint32_t>(stack_pop(s).as_address()));
break;
case 'D':
stack_push<uint32_t>(s, lsnes_memory.read<uint32_t>(stack_pop(s).as_address()));
break;
case 'q':
stack_push<int64_t>(s, lsnes_memory.read<uint64_t>(stack_pop(s).as_address()));
break;
case 'Q':
stack_push<uint64_t>(s, lsnes_memory.read<uint64_t>(stack_pop(s).as_address()));
break;
case 'f':
stack_push<float>(s, lsnes_memory.read<float>(stack_pop(s).as_address()));
break;
case 'F':
stack_push<double>(s, lsnes_memory.read<double>(stack_pop(s).as_address()));
break;
default:
throw std::runtime_error("#syntax (illchar)");
}
}
if(s.empty())
return "#ERR";
else
return s.top().str();
} catch(std::exception& e) {
return e.what();
}
position = PC_MEMORYWATCH;
cond_enable = false;
onscreen_alt_origin_x = false;
onscreen_alt_origin_y = false;
onscreen_cliprange_x = false;
onscreen_cliprange_y = false;
onscreen_fg_color = 0xFFFFFF;
onscreen_bg_color = -1;
onscreen_halo_color = 0;
}
std::set<std::string> get_watches() throw(std::bad_alloc)
JSON::node lsnes_memorywatch_printer::serialize()
{
JSON::node ndata(JSON::object);
switch(position) {
case PC_DISABLED: ndata["position"] = JSON::s("disabled"); break;
case PC_MEMORYWATCH: ndata["position"] = JSON::s("memorywatch"); break;
case PC_ONSCREEN: ndata["position"] = JSON::s("onscreen"); break;
};
ndata["cond_enable"] = JSON::b(cond_enable);
ndata["enabled"] = JSON::s(enabled);
ndata["onscreen_xpos"] = JSON::s(onscreen_xpos);
ndata["onscreen_ypos"] = JSON::s(onscreen_ypos);
ndata["onscreen_alt_origin_x"] = JSON::b(onscreen_alt_origin_x);
ndata["onscreen_alt_origin_y"] = JSON::b(onscreen_alt_origin_y);
ndata["onscreen_cliprange_x"] = JSON::b(onscreen_cliprange_x);
ndata["onscreen_cliprange_y"] = JSON::b(onscreen_cliprange_y);
ndata["onscreen_font"] = JSON::s(onscreen_font);
ndata["onscreen_fg_color"] = JSON::i(onscreen_fg_color);
ndata["onscreen_bg_color"] = JSON::i(onscreen_bg_color);
ndata["onscreen_halo_color"] = JSON::i(onscreen_halo_color);
return ndata;
}
void lsnes_memorywatch_printer::unserialize(const JSON::node& node)
{
std::string _position = json_string_default(node, "position", "");
if(_position == "disabled") position = PC_DISABLED;
else if(_position == "memorywatch") position = PC_MEMORYWATCH;
else if(_position == "onscreen") position = PC_ONSCREEN;
else position = PC_MEMORYWATCH;
cond_enable = json_boolean_default(node, "cond_enable", false);
enabled = json_string_default(node, "enabled", "");
onscreen_xpos = json_string_default(node, "onscreen_xpos", "");
onscreen_ypos = json_string_default(node, "onscreen_ypos", "");
onscreen_alt_origin_x = json_boolean_default(node, "onscreen_alt_origin_x", false);
onscreen_alt_origin_y = json_boolean_default(node, "onscreen_alt_origin_y", false);
onscreen_cliprange_x = json_boolean_default(node, "onscreen_cliprange_x", false);
onscreen_cliprange_y = json_boolean_default(node, "onscreen_cliprange_y", false);
onscreen_font = json_string_default(node, "onscreen_font", "");
onscreen_fg_color = json_signed_default(node, "onscreen_fg_color", false);
onscreen_bg_color = json_signed_default(node, "onscreen_bg_color", false);
onscreen_halo_color = json_signed_default(node, "onscreen_halo_color", false);
}
gcroot_pointer<memorywatch_item_printer> lsnes_memorywatch_printer::get_printer_obj(
std::function<gcroot_pointer<mathexpr>(const std::string& n)> vars)
{
gcroot_pointer<memorywatch_item_printer> ptr;
memorywatch_output_list* l;
memorywatch_output_fb* f;
std::string _enabled = (enabled != "") ? enabled : "true";
switch(position) {
case PC_DISABLED:
ptr = gcroot_pointer<memorywatch_item_printer>(new memorywatch_output_null);
break;
case PC_MEMORYWATCH:
ptr = gcroot_pointer<memorywatch_item_printer>(new memorywatch_output_list);
l = dynamic_cast<memorywatch_output_list*>(ptr.as_pointer());
l->cond_enable = cond_enable;
try {
if(l->cond_enable)
l->enabled = mathexpr::parse(*expression_value(), _enabled, vars);
else
l->enabled = mathexpr::parse(*expression_value(), "true", vars);
} catch(std::exception& e) {
(stringfmt() << "Error while parsing conditional: " << e.what()).throwex();
}
l->set_output(memorywatch_output_fn);
break;
case PC_ONSCREEN:
ptr = gcroot_pointer<memorywatch_item_printer>(new memorywatch_output_fb);
f = dynamic_cast<memorywatch_output_fb*>(ptr.as_pointer());
f->font = NULL;
f->set_dtor_cb([](memorywatch_output_fb& obj) { put_font(obj.font); });
f->cond_enable = cond_enable;
std::string while_parsing = "(unknown)";
try {
while_parsing = "conditional";
if(f->cond_enable)
f->enabled = mathexpr::parse(*expression_value(), _enabled, vars);
else
f->enabled = mathexpr::parse(*expression_value(), "true", vars);
while_parsing = "X position";
f->pos_x = mathexpr::parse(*expression_value(), onscreen_xpos, vars);
while_parsing = "Y position";
f->pos_y = mathexpr::parse(*expression_value(), onscreen_ypos, vars);
} catch(std::exception& e) {
(stringfmt() << "Error while parsing " << while_parsing << ": " << e.what()).throwex();
}
f->alt_origin_x = onscreen_alt_origin_x;
f->alt_origin_y = onscreen_alt_origin_y;
f->cliprange_x = onscreen_cliprange_x;
f->cliprange_y = onscreen_cliprange_y;
f->fg = onscreen_fg_color;
f->bg = onscreen_bg_color;
f->halo = onscreen_halo_color;
try {
f->font = get_font(onscreen_font);
} catch(std::exception& e) {
messages << "Bad font '" << onscreen_font << "': " << e.what() << std::endl;
f->font = &get_builtin_font2();
}
break;
}
return ptr;
}
lsnes_memorywatch_item::lsnes_memorywatch_item()
{
bytes = 0;
signed_flag = false;
float_flag = false;
endianess = 0;
scale_div = 1;
addr_base = 0;
addr_size = 0;
mspace = &lsnes_memory;
}
JSON::node lsnes_memorywatch_item::serialize()
{
JSON::node ndata(JSON::object);
ndata["printer"] = printer.serialize();
ndata["expr"] = JSON::s(expr);
ndata["format"] = JSON::s(format);
ndata["bytes"] = JSON::u(bytes);
ndata["signed"] = JSON::b(signed_flag);
ndata["float"] = JSON::b(float_flag);
ndata["endianess"] = JSON::i(endianess);
ndata["scale_div"] = JSON::u(scale_div);
ndata["addr_base"] = JSON::u(addr_base);
ndata["addr_size"] = JSON::u(addr_size);
return ndata;
}
void lsnes_memorywatch_item::unserialize(const JSON::node& node)
{
if(node.type_of("printer") == JSON::object)
printer.unserialize(node["printer"]);
else
printer = lsnes_memorywatch_printer();
expr = json_string_default(node, "expr", "0");
format = json_string_default(node, "format", "");
bytes = json_unsigned_default(node, "bytes", 0);
signed_flag = json_boolean_default(node, "signed", false);
float_flag = json_boolean_default(node, "float", false);
endianess = json_signed_default(node, "endianess", false);
scale_div = json_unsigned_default(node, "scale_div", 1);
addr_base = json_unsigned_default(node, "addr_base", 0);
addr_size = json_unsigned_default(node, "addr_size", 0);
}
memorywatch_memread_oper* lsnes_memorywatch_item::get_memread_oper()
{
if(!bytes)
return NULL;
memorywatch_memread_oper* o = new memorywatch_memread_oper;
o->bytes = bytes;
o->signed_flag = signed_flag;
o->float_flag = float_flag;
o->endianess = endianess;
o->scale_div = scale_div;
o->addr_base = addr_base;
o->addr_size = addr_size;
o->mspace = mspace;
return o;
}
std::set<std::string> lsnes_memorywatch_set::enumerate()
{
std::set<std::string> r;
auto p = project_get();
std::map<std::string, std::string>* ws;
if(p)
ws = &p->watches;
else
ws = &watches;
for(auto i : *ws)
for(auto& i : items)
r.insert(i.first);
return r;
}
std::string get_watchexpr_for(const std::string& w) throw(std::bad_alloc)
void lsnes_memorywatch_set::clear(const std::string& name)
{
auto p = project_get();
std::map<std::string, std::string>* ws;
if(p)
ws = &p->watches;
else
ws = &watches;
if(ws->count(w))
return (*ws)[w];
else
return "";
}
void set_watchexpr_for(const std::string& w, const std::string& expr) throw(std::bad_alloc)
{
auto& status = platform::get_emustatus();
auto p = project_get();
if(expr != "") {
if(p)
p->watches[w] = expr;
else
watches[w] = expr;
status.set("M[" + w + "]", evaluate_watch(expr));
} else {
if(p)
p->watches.erase(w);
else
watches.erase(w);
status.erase("M[" + w + "]");
std::map<std::string, lsnes_memorywatch_item> nitems = items;
nitems.erase(name);
rebuild(nitems);
std::swap(items, nitems);
auto pr = project_get();
if(pr) {
pr->watches.erase(name);
project_flush(pr);
}
if(p)
project_flush(p);
notify_status_update();
redraw_framebuffer();
}
void do_watch_memory()
void lsnes_memorywatch_set::set(const std::string& name, const std::string& item)
{
auto& status = platform::get_emustatus();
auto p = project_get();
auto w = p ? &(p->watches) : &watches;
for(auto i : *w)
status.set("M[" + i.first + "]", evaluate_watch(i.second));
lsnes_memorywatch_item _item;
_item.unserialize(JSON::node(item));
set(name, _item);
}
namespace
lsnes_memorywatch_item& lsnes_memorywatch_set::get(const std::string& name)
{
command::fnptr<const std::string&> add_watch(lsnes_cmd, "add-watch", "Add a memory watch",
"Syntax: add-watch <name> <expression>\nAdds a new memory watch\n",
[](const std::string& t) throw(std::bad_alloc, std::runtime_error) {
auto r = regex("([^ \t]+)[ \t]+(|[^ \t].*)", t, "Name and expression required.");
set_watchexpr_for(r[1], r[2]);
});
command::fnptr<const std::string&> remove_watch(lsnes_cmd, "remove-watch", "Remove a memory watch",
"Syntax: remove-watch <name>\nRemoves a memory watch\n",
[](const std::string& t) throw(std::bad_alloc, std::runtime_error) {
auto r = regex("([^ \t]+)[ \t]*", t, "Name required.");
set_watchexpr_for(r[1], "");
});
if(!items.count(name))
throw std::runtime_error("No such memory watch named '" + name + "'");
return items[name];
}
std::string lsnes_memorywatch_set::get_string(const std::string& name, JSON::printer* printer)
{
auto& x = get(name);
auto y = x.serialize();
auto z = y.serialize(printer);
return z;
}
void lsnes_memorywatch_set::watch(struct framebuffer::queue& rq)
{
//Set framebuffer for all FB watches.
watch_set.foreach([&rq](memorywatch_item& i) {
memorywatch_output_fb* fb = dynamic_cast<memorywatch_output_fb*>(i.printer.as_pointer());
if(fb)
fb->set_rqueue(rq);
});
watch_set.refresh();
erase_unused_watches();
}
bool lsnes_memorywatch_set::rename(const std::string& oldname, const std::string& newname)
{
std::map<std::string, lsnes_memorywatch_item> nitems = items;
if(nitems.count(newname))
return false;
if(!nitems.count(oldname))
return false;
nitems[newname] = nitems[oldname];
nitems.erase(oldname);
rebuild(nitems);
std::swap(items, nitems);
redraw_framebuffer();
update_movie_state();
return true;
}
void lsnes_memorywatch_set::set(const std::string& name, lsnes_memorywatch_item& item)
{
std::map<std::string, lsnes_memorywatch_item> nitems = items;
nitems[name] = item;
rebuild(nitems);
std::swap(items, nitems);
auto pr = project_get();
if(pr) {
pr->watches[name] = get_string(name);
project_flush(pr);
}
redraw_framebuffer();
update_movie_state();
}
std::string lsnes_memorywatch_set::get_value(const std::string& name)
{
return watch_set.get(name).get_value();
}
void lsnes_memorywatch_set::set_multi(std::list<std::pair<std::string, lsnes_memorywatch_item>>& list)
{
std::map<std::string, lsnes_memorywatch_item> nitems = items;
for(auto& i : list)
nitems[i.first] = i.second;
rebuild(nitems);
std::swap(items, nitems);
auto pr = project_get();
if(pr) {
for(auto& i : list)
pr->watches[i.first] = get_string(i.first);
project_flush(pr);
}
redraw_framebuffer();
update_movie_state();
}
void lsnes_memorywatch_set::set_multi(std::list<std::pair<std::string, std::string>>& list)
{
std::list<std::pair<std::string, lsnes_memorywatch_item>> _list;
for(auto& i: list) {
lsnes_memorywatch_item it;
it.unserialize(JSON::node(i.second));
_list.push_back(std::make_pair(i.first, it));
}
set_multi(_list);
}
void lsnes_memorywatch_set::clear_multi(const std::set<std::string>& names)
{
std::map<std::string, lsnes_memorywatch_item> nitems = items;
for(auto& i : names)
nitems.erase(i);
rebuild(nitems);
std::swap(items, nitems);
auto pr = project_get();
if(pr) {
for(auto& i : names)
pr->watches.erase(i);
project_flush(pr);
}
redraw_framebuffer();
update_movie_state();
}
void lsnes_memorywatch_set::rebuild(std::map<std::string, lsnes_memorywatch_item>& nitems)
{
{
memorywatch_set new_set;
std::map<std::string, gcroot_pointer<mathexpr>> vars;
auto vars_fn = [&vars](const std::string& n) -> gcroot_pointer<mathexpr> {
if(!vars.count(n))
vars[n] = gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(),
expression_value());
return vars[n];
};
for(auto& i : nitems) {
mathexpr_operinfo* memread_oper = i.second.get_memread_oper();
try {
gcroot_pointer<mathexpr> rt_expr;
gcroot_pointer<memorywatch_item_printer> rt_printer;
std::vector<gcroot_pointer<mathexpr>> v;
try {
rt_expr = mathexpr::parse(*expression_value(), i.second.expr, vars_fn);
} catch(std::exception& e) {
(stringfmt() << "Error while parsing address/expression: "
<< e.what()).throwex();
}
v.push_back(rt_expr);
if(memread_oper) {
rt_expr = gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(),
expression_value(), memread_oper, v, true);
memread_oper = NULL;
}
rt_printer = i.second.printer.get_printer_obj(vars_fn);
memorywatch_item it(*expression_value());
*vars_fn(i.first) = *rt_expr;
it.expr = vars_fn(i.first);
it.printer = rt_printer;
it.format = i.second.format;
new_set.create(i.first, it);
} catch(...) {
delete memread_oper;
throw;
}
}
watch_set.swap(new_set);
}
garbage_collectable::do_gc();
}
lsnes_memorywatch_set lsnes_memorywatch;

View file

@ -424,11 +424,15 @@ bool project_set(project_info* p, bool current)
skip_rom_movie:
active_project = p;
switched = true;
for(auto i : get_watches())
if(p->watches.count(i))
set_watchexpr_for(i, p->watches[i]);
else
set_watchexpr_for(i, "");
for(auto i : lsnes_memorywatch.enumerate())
try {
if(p->watches.count(i))
lsnes_memorywatch.set(i, p->watches[i]);
else
lsnes_memorywatch.clear(i);
} catch(std::exception& e) {
messages << "Can't set/clear watch '" << i << "': " << e.what() << std::endl;
}
voicesub_load_collection(p->directory + "/" + p->prefix + ".lsvs");
lsnes_cmd.invoke("reset-lua");
for(auto i : p->luascripts)
@ -494,8 +498,13 @@ std::string project_savestate_ext()
void project_copy_watches(project_info& p)
{
for(auto i : get_watches())
p.watches[i] = get_watchexpr_for(i);
for(auto i : lsnes_memorywatch.enumerate()) {
try {
p.watches[i] = lsnes_memorywatch.get_string(i);
} catch(std::exception& e) {
messages << "Can't read memory watch '" << i << "': " << e.what() << std::endl;
}
}
}
void project_copy_macros(project_info& p, controller_state& s)

View file

@ -1,4 +1,5 @@
#include "framebuffer.hpp"
#include "hex.hpp"
#include "png.hpp"
#include "serialization.hpp"
#include "string.hpp"
@ -894,6 +895,12 @@ color::color(const std::string& clr) throw(std::bad_alloc, std::runtime_error)
bool first = true;
auto& cspecs = colornames();
for(auto& t : token_iterator_foreach(clr, {" ","\t"}, true)) {
if(t.length() > 0 && t[0] == '#') {
if(!first)
throw std::runtime_error("Base color (" + t + ") can't be used as modifier");
std::string _t = t;
col = hex::from<uint32_t>(_t.substr(1));
}
if(!cspecs.count(t))
throw std::runtime_error("Invalid color (modifier) '" + t + "'");
if(!first && !cspecs[t].second)
@ -906,6 +913,25 @@ color::color(const std::string& clr) throw(std::bad_alloc, std::runtime_error)
*this = color(col);
}
std::string color::stringify(int64_t number)
{
auto& cspecs = colornames();
for(auto& i : colornames()) {
int64_t col = -1;
if(i.second.second)
continue;
(i.second.first)(col);
if(col == number)
return i.first;
}
if(number < 0)
return "transparent";
else if(number < 16777216)
return "#" + hex::to<uint32_t>(number).substr(2);
else
return "#" + hex::to<uint32_t>(number);
}
void color::set_palette(unsigned rshift, unsigned gshift, unsigned bshift, bool X) throw()
{
if(X) {

58
src/library/gc.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "gc.hpp"
#include <list>
#include <iostream>
#include <set>
namespace
{
std::set<garbage_collectable*> gc_items;
}
garbage_collectable::garbage_collectable()
{
gc_items.insert(this);
root_count = 1;
}
garbage_collectable::~garbage_collectable()
{
gc_items.erase(this);
}
void garbage_collectable::mark_root()
{
root_count++;
}
void garbage_collectable::unmark_root()
{
if(root_count) root_count--;
}
void garbage_collectable::do_gc()
{
for(auto i : gc_items)
i->reachable = false;
for(auto i : gc_items) {
if(i->root_count) {
i->mark();
}
}
for(auto i = gc_items.begin(); i != gc_items.end();) {
if(!(*i)->reachable) {
auto ptr = i;
i++;
delete *ptr;
} else
i++;
}
}
void garbage_collectable::mark()
{
bool was_reachable = reachable;
reachable = true;
if(!was_reachable) {
trace();
}
}

View file

@ -0,0 +1,31 @@
#include "mathexpr-error.hpp"
mathexpr_error::mathexpr_error(errorcode _code, const std::string& message)
: std::runtime_error(message), code(_code)
{
}
mathexpr_error::errorcode mathexpr_error::get_code()
{
return code;
}
const char* mathexpr_error::get_short_error()
{
switch(code) {
case UNDEFINED: return "#Undefined";
case CIRCULAR: return "#Circular";
case TYPE_MISMATCH: return "#Type";
case INTERNAL: return "#Internal";
case WDOMAIN: return "#Domain";
case DIV_BY_0: return "#Div-by-0";
case LOG_BY_0: return "#Log-of-0";
case ARGCOUNT: return "#Argcount";
case SIZE: return "#Badsize";
case ADDR: return "#Badaddr";
case FORMAT: return "#Badformat";
case UNKNOWN: return "#???";
default: return "#Unknownerr";
};
}

View file

@ -0,0 +1,244 @@
#include "mathexpr-format.hpp"
#include "string.hpp"
namespace
{
std::string pad(const std::string& orig, int width, bool zeropad, bool nosign)
{
std::string out = orig;
if(!nosign && (orig == "" || (orig[0] != '+' && orig[0] != '-')))
nosign = true;
if(nosign) {
while((int)out.length() < width)
out = (zeropad ? "0" : " ") + out;
} else {
if(zeropad)
while((int)out.length() < width)
out = out.substr(0,1) + "0" + out.substr(1);
else
while((int)out.length() < width)
out = " " + out;
}
return out;
}
std::string int_to_bits(uint64_t v)
{
if(v < 2) return std::string(1, 48 + v);
return int_to_bits(v >> 1) + std::string(1, 48 + (v & 1));
}
const char* numbers_l = "0123456789abcdef";
const char* numbers_u = "0123456789ABCDEF";
std::string print_decimals(double tail, unsigned base, int places, bool trimzero, bool uppercase)
{
std::string y;
for(int i = 0; i < places; i++) {
uint32_t n = floor(tail);
y += std::string(1, (uppercase ? numbers_u : numbers_l)[n % base]);
tail *= base;
tail = tail - n;
}
if(trimzero) {
size_t p = y.find_last_not_of("0");
if(p < y.length())
y = y.substr(0, p);
}
return y;
}
std::string format_float_base(double v, mathexpr_format fmt, unsigned base)
{
bool exponential = false;
std::string out;
char expsep = (base == 16) ? 'g' : 'e';
if(fmt.uppercasehex) expsep -= 32;
int exponent = 0;
v += 0.5 * pow(base, fmt.precision); //Round.
again:
out = "";
if(fmt.showsign || v < 0)
out += (v < 0) ? "-" : "+";
if(exponential) {
//TODO.
out += std::string(1, expsep);
out += (stringfmt() << exponent).str();
} else {
double vint = floor(v);
//TODO: Print whole part.
double tail = v - floor(v);
if(fmt.precision < 0) {
//Print up to 5 places.
std::string y = print_decimals(tail, base, fmt.precision, true, fmt.uppercasehex);
if(y != "")
out = out + "." + y;
} else if(fmt.precision > 0) {
//Print . and fmt.precision places.
out += ".";
out += print_decimals(tail, base, fmt.precision, false, fmt.uppercasehex);
}
}
if(!exponential && out.length() > fmt.width) {
exponential = true;
goto again;
}
}
std::string format_float_10(double v, mathexpr_format fmt)
{
std::string format;
format += "%";
if(fmt.showsign)
format += "+";
if(fmt.fillzeros)
format += "0";
if(fmt.width >= 0)
format += (stringfmt() << fmt.width).str();
if(fmt.precision >= 0)
format += (stringfmt() << "." << fmt.precision).str();
format += "g";
char buffer[256];
snprintf(buffer, sizeof(buffer) - 1, format.c_str(), v);
return buffer;
}
}
std::string math_format_bool(bool v, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad(v ? "true" : "false", fmt.width, false, true);
return math_format_unsigned(v ? 1 : 0, fmt);
}
std::string math_format_unsigned(uint64_t v, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad(v ? "true" : "false", fmt.width, false, true);
if(fmt.type == mathexpr_format::STRING)
return "<#Badformat>";
if(fmt.type == mathexpr_format::DEFAULT)
return (stringfmt() << v).str();
std::ostringstream _out;
switch(fmt.type) {
case mathexpr_format::BINARY:
_out << int_to_bits(v);
break;
case mathexpr_format::OCTAL:
_out << std::oct << v;
break;
case mathexpr_format::DECIMAL:
_out << v;
break;
case mathexpr_format::HEXADECIMAL:
if(fmt.uppercasehex) _out << std::uppercase;
_out << std::hex << v;
break;
}
std::string out = _out.str();
if(fmt.showsign)
out = "+" + out;
if(fmt.precision > 0) {
out += ".";
for(int i = 0; i < fmt.precision; i++)
out += "0";
}
return pad(out, fmt.width, fmt.fillzeros, false);
}
std::string math_format_signed(int64_t v, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad(v ? "true" : "false", fmt.width, false, true);
if(fmt.type == mathexpr_format::STRING)
return "<#Badformat>";
if(fmt.type == mathexpr_format::DEFAULT)
return (stringfmt() << v).str();
std::ostringstream _out;
switch(fmt.type) {
case mathexpr_format::BINARY:
if(v < 0) _out << "-";
_out << int_to_bits(std::abs(v));
break;
case mathexpr_format::OCTAL:
if(v < 0) _out << "-";
_out << std::oct << std::abs(v);
break;
case mathexpr_format::DECIMAL:
_out << v;
break;
case mathexpr_format::HEXADECIMAL:
if(v < 0) _out << "-";
if(fmt.uppercasehex) _out << std::uppercase;
_out << std::hex << std::abs(v);
break;
}
std::string out = _out.str();
if(fmt.showsign && v >= 0)
out = "+" + out;
if(fmt.precision > 0) {
out += ".";
for(int i = 0; i < fmt.precision; i++)
out += "0";
}
return pad(out, fmt.width, fmt.fillzeros, false);
}
std::string math_format_float(double v, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad(v ? "true" : "false", fmt.width, false, true);
if(fmt.type == mathexpr_format::STRING)
return "<#Badformat>";
if(fmt.type == mathexpr_format::DEFAULT)
return (stringfmt() << v).str();
std::string out;
switch(fmt.type) {
case mathexpr_format::BINARY:
//out = format_float_base(v, fmt, 2);
//break;
return "<#Badbase>";
case mathexpr_format::OCTAL:
//out = format_float_base(v, fmt, 8);
//break;
return "<#Badbase>";
case mathexpr_format::DECIMAL:
//out = format_float_base(v, fmt, 10);
out = format_float_10(v, fmt);
break;
case mathexpr_format::HEXADECIMAL:
//out = format_float_base(v, fmt, 16);
//break;
return "<#Badbase>";
}
return pad(out, fmt.width, fmt.fillzeros, false);
}
std::string math_format_complex(double vr, double vi, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad((vr || vi) ? "true" : "false", fmt.width, false, true);
if(fmt.type == mathexpr_format::STRING)
return "<#Badformat>";
if(fmt.type == mathexpr_format::DEFAULT) {
if(vi >= 0)
return (stringfmt() << vr << "+" << vi << "i").str();
else
return (stringfmt() << vr << vi << "i").str();
}
std::string xr = math_format_float(vr, fmt);
fmt.showsign = true;
std::string xi = math_format_float(vi, fmt);
return xr + xi + "i";
}
std::string math_format_string(std::string v, mathexpr_format fmt)
{
if(fmt.type == mathexpr_format::BOOLEAN)
return pad((v != "") ? "true" : "false", fmt.width, false, true);
if(fmt.type != mathexpr_format::STRING)
return "<#Badformat>";
if(fmt.precision > 0 && v.length() > fmt.precision)
v = v.substr(0, fmt.precision);
return pad(v, fmt.width, false, true);
}

File diff suppressed because it is too large Load diff

625
src/library/mathexpr.cpp Normal file
View file

@ -0,0 +1,625 @@
#include "mathexpr.hpp"
#include "string.hpp"
#include <set>
#include <map>
mathexpr_operinfo::mathexpr_operinfo(std::string funcname)
: fnname(funcname), is_operator(false), operands(0), precedence(0), rtl(false)
{
}
mathexpr_operinfo::mathexpr_operinfo(std::string opername, unsigned _operands, int _percedence, bool _rtl)
: fnname(opername), is_operator(true), operands(_operands), precedence(_percedence), rtl(_rtl)
{
}
mathexpr_operinfo::~mathexpr_operinfo()
{
}
mathexpr_typeinfo::~mathexpr_typeinfo()
{
}
mathexpr::mathexpr(mathexpr_typeinfo* _type)
: type(*_type)
{
owns_operator = false;
state = UNDEFINED;
value = NULL;
fn = (mathexpr_operinfo*)0xDEADBEEF;
}
mathexpr::mathexpr(mathexpr_typeinfo* _type, gcroot_pointer<mathexpr> fwd)
: type(*_type)
{
owns_operator = false;
state = FORWARD;
value = type.allocate();
arguments.push_back(&*fwd);
fn = NULL;
}
mathexpr::mathexpr(mathexpr_value _value)
: type(*_value.type)
{
owns_operator = false;
state = FIXED;
value = type.copy_allocate(_value.value);
fn = NULL;
}
mathexpr::mathexpr(mathexpr_typeinfo* _type, const std::string& _value, bool string)
: type(*_type)
{
owns_operator = false;
state = FIXED;
value = type.parse(_value, string);
fn = NULL;
}
mathexpr::mathexpr(mathexpr_typeinfo* _type, mathexpr_operinfo* _fn, std::vector<gcroot_pointer<mathexpr>> _args,
bool _owns_operator)
: type(*_type), fn(_fn), owns_operator(_owns_operator)
{
try {
for(auto& i : _args)
arguments.push_back(&*i);
value = type.allocate();
state = TO_BE_EVALUATED;
} catch(...) {
if(owns_operator)
delete fn;
throw;
}
}
mathexpr::~mathexpr()
{
if(owns_operator && fn)
delete fn;
type.deallocate(value);
}
void mathexpr::reset()
{
if(state == TO_BE_EVALUATED || state == FIXED || state == UNDEFINED || state == FORWARD)
return;
if(state == FORWARD_EVALD || state == FORWARD_EVALING) {
state = FORWARD;
return;
}
state = TO_BE_EVALUATED;
for(auto i : arguments)
i->reset();
}
mathexpr::mathexpr(const mathexpr& m)
: state(m.state), type(m.type), fn(m.fn), error(m.error), arguments(m.arguments)
{
value = m.value ? type.copy_allocate(m.value) : NULL;
if(state == EVALUATING) state = TO_BE_EVALUATED;
}
mathexpr& mathexpr::operator=(const mathexpr& m)
{
if(this == &m)
return *this;
std::string _error = m.error;
std::vector<mathexpr*> _arguments = m.arguments;
if(m.value) {
if(!value)
value = m.type.copy_allocate(m.value);
else
m.type.copy(value, m.value);
} else if(value) {
m.type.deallocate(value);
value = NULL;
} else
value = NULL;
type = m.type;
fn = m.fn;
state = m.state;
owns_operator = m.owns_operator;
m.owns_operator = false;
std::swap(arguments, _arguments);
std::swap(error, _error);
return *this;
}
mathexpr_value mathexpr::evaluate()
{
mathexpr_value ret;
ret.type = &type;
switch(state) {
case TO_BE_EVALUATED:
//Need to evaluate.
try {
for(auto i : arguments) {
if(&i->type != &type) {
throw mathexpr_error(mathexpr_error::TYPE_MISMATCH,
"Types for function mismatch");
}
}
state = EVALUATING;
std::vector<std::function<mathexpr_value()>> promises;
for(auto i : arguments) {
mathexpr* m = i;
promises.push_back([m]() { return m->evaluate(); });
}
mathexpr_value tmp;
tmp.type = &type;
tmp.value = value;
fn->evaluate(tmp, promises);
state = EVALUATED;
} catch(mathexpr_error& e) {
state = ERROR;
errcode = e.get_code();
error = e.what();
throw;
} catch(std::exception& e) {
state = ERROR;
errcode = mathexpr_error::UNKNOWN;
error = e.what();
throw;
} catch(...) {
state = ERROR;
errcode = mathexpr_error::UNKNOWN;
error = "Unknown error";
throw;
}
ret.value = value;
return ret;
case EVALUATING:
case FORWARD_EVALING:
//Circular dependency.
mark_error_and_throw(mathexpr_error::CIRCULAR, "Circular dependency");
case EVALUATED:
case FIXED:
case FORWARD_EVALD:
ret.value = value;
return ret;
case UNDEFINED:
throw mathexpr_error(mathexpr_error::UNDEFINED, "Undefined variable");
case ERROR:
throw mathexpr_error(errcode, error);
case FORWARD:
try {
state = FORWARD_EVALING;
mathexpr_value v = arguments[0]->evaluate();
type.copy(value, v.value);
state = FORWARD_EVALD;
return v;
} catch(...) {
state = FORWARD;
throw;
}
}
throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)");
}
void mathexpr::trace()
{
for(auto i : arguments)
i->mark();
}
void mathexpr::mark_error_and_throw(mathexpr_error::errorcode _errcode, const std::string& _error)
{
if(state == EVALUATING) {
state = ERROR;
errcode = _errcode;
error = _error;
}
if(state == FORWARD_EVALING) {
state = FORWARD;
}
throw mathexpr_error(_errcode, _error);
}
namespace
{
/*
X_EXPR -> VALUE
X_EXPR -> STRING
X_EXPR -> NONARY-OP
X_EXPR -> FUNCTION X_ARGS
X_EXPR -> UNARY-OP X_EXPR
X_EXPR -> X_LAMBDA X_EXPR
X_LAMBDA -> X_EXPR BINARY-OP
X_ARGS -> OPEN-PAREN CLOSE_PAREN
X_ARGS -> OPEN-PAREN X_TAIL
X_TAIL -> X_PAIR X_TAIL
X_TAIL -> X_EXPR CLOSE-PAREN
X_PAIR -> X_EXPR COMMA
*/
//SUBEXPRESSION -> VALUE
//SUBEXPRESSION -> STRING
//SUBEXPRESSION -> FUNCTION OPEN-PAREN CLOSE-PAREN
//SUBEXPRESSION -> FUNCTION OPEN-PAREN (SUBEXPRESSION COMMA)* SUBEXPRESSION CLOSE-PAREN
//SUBEXPRESSION -> OPEN-PAREN SUBEXPRESSION CLOSE-PAREN
//SUBEXPRESSION -> SUBEXPRESSION BINARY-OP SUBEXPRESSION
//SUBEXPRESSION -> UNARY-OP SUBEXPRESSION
//SUBEXPRESSION -> NONARY-OP
bool is_alphanumeric(char ch)
{
if(ch >= '0' && ch <= '9') return true;
if(ch >= 'a' && ch <= 'z') return true;
if(ch >= 'A' && ch <= 'Z') return true;
if(ch == '_') return true;
if(ch == '.') return true;
return false;
}
enum token_kind
{
TT_OPEN_PAREN,
TT_CLOSE_PAREN,
TT_COMMA,
TT_FUNCTION,
TT_OPERATOR,
TT_VARIABLE,
TT_VALUE,
TT_STRING,
};
struct operations_set
{
operations_set(std::set<mathexpr_operinfo*>& ops)
: operations(ops)
{
}
mathexpr_operinfo* find_function(const std::string& name)
{
mathexpr_operinfo* fn = NULL;
for(auto j : operations) {
if(name == j->fnname && !j->is_operator)
fn = j;
}
if(!fn) throw std::runtime_error("No such function '" + name + "'");
return fn;
}
mathexpr_operinfo* find_operator(const std::string& name, unsigned arity)
{
for(auto j : operations) {
if(name == j->fnname && j->is_operator && j->operands == arity)
return j;
}
return NULL;
}
private:
std::set<mathexpr_operinfo*>& operations;
};
struct subexpression
{
subexpression(token_kind k) : kind(k) {}
subexpression(token_kind k, const std::string& str) : kind(k), string(str) {}
token_kind kind;
std::string string;
};
size_t find_last_in_sub(std::vector<subexpression>& ss, size_t first)
{
size_t depth;
switch(ss[first].kind) {
case TT_FUNCTION:
if(first + 1 == ss.size() || ss[first + 1].kind != TT_OPEN_PAREN)
throw std::runtime_error("Function requires argument list");
first++;
case TT_OPEN_PAREN:
depth = 0;
while(first < ss.size()) {
if(ss[first].kind == TT_OPEN_PAREN)
depth++;
if(ss[first].kind == TT_CLOSE_PAREN)
if(!--depth) break;
first++;
}
if(first == ss.size())
throw std::runtime_error("Unmatched '('");
return first;
case TT_CLOSE_PAREN:
throw std::runtime_error("Unmatched ')'");
case TT_COMMA:
throw std::runtime_error("',' only allowed in function arguments");
case TT_VALUE:
case TT_STRING:
case TT_OPERATOR:
case TT_VARIABLE:
return first;
}
throw std::runtime_error("Internal error (shouldn't be here)");
}
size_t find_end_of_arg(std::vector<subexpression>& ss, size_t first)
{
size_t depth = 0;
while(first < ss.size()) {
if(depth == 0 && ss[first].kind == TT_COMMA)
return first;
if(ss[first].kind == TT_OPEN_PAREN)
depth++;
if(ss[first].kind == TT_CLOSE_PAREN) {
if(depth == 0)
return first;
depth--;
}
first++;
}
return ss.size();
}
struct expr_or_op
{
expr_or_op(gcroot_pointer<mathexpr> e) : expr(e), typei(NULL) {}
expr_or_op(std::string o) : op(o), typei(NULL) {}
gcroot_pointer<mathexpr> expr;
std::string op;
mathexpr_operinfo* typei;
};
gcroot_pointer<mathexpr> parse_rec(mathexpr_typeinfo& _type, std::vector<expr_or_op>& operands,
size_t first, size_t last)
{
if(operands.empty())
return gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type);
if(last - first > 1) {
//Find the highest percedence operator.
size_t best = last;
for(size_t i = first; i < last; i++) {
if(operands[i].typei) {
if(best == last)
best = i;
else if(operands[i].typei->precedence < operands[best].typei->precedence) {
best = i;
} else if(!operands[best].typei->rtl &&
operands[i].typei->precedence == operands[best].typei->precedence) {
best = i;
}
}
}
if(best == last) throw std::runtime_error("Internal error: No operands?");
if(operands[best].typei->operands == 1) {
//The operator is unary, collect up all following unary operators.
size_t j = first;
while(operands[j].typei)
j++;
std::vector<gcroot_pointer<mathexpr>> args;
args.push_back(parse_rec(_type, operands, first + 1, j + 1));
return gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type,
operands[best].typei, args);
} else {
//Binary operator.
std::vector<gcroot_pointer<mathexpr>> args;
args.push_back(parse_rec(_type, operands, first, best));
args.push_back(parse_rec(_type, operands, best + 1, last));
return gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type,
operands[best].typei, args);
}
}
return operands[first].expr;
}
gcroot_pointer<mathexpr> parse_rec(mathexpr_typeinfo& _type, std::vector<subexpression>& ss,
std::set<mathexpr_operinfo*>& operations,
std::function<gcroot_pointer<mathexpr>(const std::string&)> vars, size_t first, size_t last)
{
operations_set opset(operations);
std::vector<expr_or_op> operands;
std::vector<gcroot_pointer<mathexpr>> args;
mathexpr_operinfo* fn;
for(size_t i = first; i < last; i++) {
size_t l = find_last_in_sub(ss, i);
if(l >= last) throw std::runtime_error("Internal error: Improper nesting");
switch(ss[i].kind) {
case TT_OPEN_PAREN:
operands.push_back(parse_rec(_type, ss, operations, vars, i + 1, l));
break;
case TT_VALUE:
operands.push_back(gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type,
ss[i].string, false));
break;
case TT_STRING:
operands.push_back(gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type,
ss[i].string, true));
break;
case TT_VARIABLE:
//We have to warp this is identify transform to make the evaluation lazy.
operands.push_back(gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type,
vars(ss[i].string)));
break;
case TT_FUNCTION:
fn = opset.find_function(ss[i].string);
i += 2;
while(ss[i].kind != TT_CLOSE_PAREN) {
size_t k = find_end_of_arg(ss, i);
args.push_back(parse_rec(_type, ss, operations, vars, i, k));
if(k < ss.size() && ss[k].kind == TT_COMMA)
i = k + 1;
else
i = k;
}
operands.push_back(gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type, fn,
args));
args.clear();
break;
case TT_OPERATOR:
operands.push_back(ss[i].string);
break;
case TT_CLOSE_PAREN:
case TT_COMMA:
; //Can't appen.
}
i = l;
}
if(operands.empty())
throw std::runtime_error("Empty subexpression");
//Translate nonary operators to values.
for(auto& i : operands) {
if(!(bool)i.expr) {
auto fn = opset.find_operator(i.op, 0);
if(fn)
i.expr = gcroot_pointer<mathexpr>(gcroot_pointer_object_tag(), &_type, fn,
std::vector<gcroot_pointer<mathexpr>>());
}
}
//Check that there aren't two consequtive subexpressions and mark operators.
bool was_operand = false;
for(auto& i : operands) {
bool is_operand = (bool)i.expr;
if(!is_operand && !was_operand)
if(!(i.typei = opset.find_operator(i.op, 1)))
throw std::runtime_error("'" + i.op + "' is not an unary operator");
if(!is_operand && was_operand)
if(!(i.typei = opset.find_operator(i.op, 2)))
throw std::runtime_error("'" + i.op + "' is not a binary operator");
if(was_operand && is_operand)
throw std::runtime_error("Expected operator, got operand");
was_operand = is_operand;
}
if(!was_operand)
throw std::runtime_error("Expected operand, got end of subexpression");
//Okay, now the expression has been reduced into series of operators and subexpressions.
//If there are multiple consequtive operators, the first (except as first item) is binary,
//and the others are unary.
return parse_rec(_type, operands, 0, operands.size());
}
void tokenize(const std::string& expr, std::set<mathexpr_operinfo*>& operations,
std::vector<subexpression>& tokenization)
{
for(size_t i = 0; i < expr.length();) {
if(expr[i] == '(') {
tokenization.push_back(subexpression(TT_OPEN_PAREN));
i++;
} else if(expr[i] == ')') {
tokenization.push_back(subexpression(TT_CLOSE_PAREN));
i++;
} else if(expr[i] == ',') {
tokenization.push_back(subexpression(TT_COMMA));
i++;
} else if(expr[i] == ' ') {
i++;
} else if(expr[i] == '$') {
//Variable. If the next character is {, parse until }, otherwise parse until
//non-alphanum.
std::string varname = "";
if(i + 1 < expr.length() && expr[i + 1] == '{') {
//Terminate by '}'.
i++;
while(i + 1 < expr.length() && expr[i + 1] != '}')
varname += std::string(1, expr[++i]);
if(i + 1 >= expr.length() || expr[i + 1] != '}')
throw std::runtime_error("${ without matching }");
i++;
} else {
//Terminate by non-alphanum.
while(i + 1 < expr.length() && is_alphanumeric(expr[i + 1]))
varname += std::string(1, expr[++i]);
}
tokenization.push_back(subexpression(TT_VARIABLE, varname));
i++;
} else if(expr[i] == '"') {
bool escape = false;
size_t endpos = i;
endpos++;
while(endpos < expr.length() && (escape || expr[endpos] != '"')) {
if(!escape) {
if(expr[endpos] == '\\')
escape = true;
endpos++;
} else {
escape = false;
endpos++;
}
}
if(endpos == expr.length())
throw std::runtime_error("Unmatched \"");
//Copy (i,endpos-1) and descape.
std::string tmp;
escape = false;
for(size_t j = i + 1; j < endpos; j++) {
if(!escape) {
if(expr[j] != '\\')
tmp += std::string(1, expr[j]);
else
escape = true;
} else {
tmp += std::string(1, expr[j]);
escape = false;
}
}
tokenization.push_back(subexpression(TT_STRING, tmp));
i = endpos + 1;
} else {
bool found = false;
//Function names are only recognized if it begins here and is followed by '('.
for(auto j : operations) {
if(j->is_operator) continue; //Not a function.
if(i + j->fnname.length() + 1 > expr.length()) continue; //Too long.
if(expr[i + j->fnname.length()] != '(') continue; //Not followed by '('.
for(size_t k = 0; k < j->fnname.length(); k++)
if(expr[i + k] != j->fnname[k]) goto nomatch; //No match.
tokenization.push_back(subexpression(TT_FUNCTION, j->fnname));
i += j->fnname.length();
found = true;
break;
nomatch: ;
}
if(found) continue;
//Operators. These use longest match rule.
size_t longest_match = 0;
std::string op;
for(auto j : operations) {
if(!j->is_operator) continue; //Not an operator.
if(i + j->fnname.length() > expr.length()) continue; //Too long.
for(size_t k = 0; k < j->fnname.length(); k++)
if(expr[i + k] != j->fnname[k]) goto next; //No match.
if(j->fnname.length() <= longest_match) continue; //Not longest.
found = true;
op = j->fnname;
longest_match = op.length();
next: ;
}
if(found) {
tokenization.push_back(subexpression(TT_OPERATOR, op));
i += op.length();
continue;
}
//Okay, token until next non-alphanum.
std::string tmp;
while(i < expr.length() && is_alphanumeric(expr[i]))
tmp += std::string(1, expr[i++]);
if(tmp.length()) {
tokenization.push_back(subexpression(TT_VALUE, tmp));
continue;
}
std::string summary;
size_t j;
size_t utfcount = 0;
for(j = i; j < expr.length() && (j < i + 20 || utfcount); j++) {
if(utfcount) utfcount--;
summary += std::string(1, expr[j]);
if((uint8_t)expr[j] >= 0xF0) utfcount = 3;
if((uint8_t)expr[j] >= 0xE0) utfcount = 2;
if((uint8_t)expr[j] >= 0xC0) utfcount = 1;
if((uint8_t)expr[j] < 0x80) utfcount = 0;
}
if(j < expr.length()) summary += "[...]";
throw std::runtime_error("Expression parse error, at '" + summary + "'");
}
}
}
}
gcroot_pointer<mathexpr> mathexpr::parse(mathexpr_typeinfo& _type, const std::string& expr,
std::function<gcroot_pointer<mathexpr>(const std::string&)> vars)
{
if(expr == "")
throw std::runtime_error("Empty expression");
auto operations = _type.operations();
std::vector<subexpression> tokenization;
tokenize(expr, operations, tokenization);
return parse_rec(_type, tokenization, operations, vars, 0, tokenization.size());
}

View file

@ -0,0 +1,183 @@
#include "memorywatch-fb.hpp"
#include "utf8.hpp"
#include "minmax.hpp"
namespace
{
struct memorywatch_fb_object : public framebuffer::object
{
struct params
{
int64_t x;
int64_t y;
bool cliprange_x;
bool cliprange_y;
bool alt_origin_x;
bool alt_origin_y;
framebuffer::font2* font;
framebuffer::color fg;
framebuffer::color bg;
framebuffer::color halo;
};
memorywatch_fb_object(const params& _p, const std::string& _msg);
~memorywatch_fb_object() throw();
void operator()(struct framebuffer::fb<false>& scr) throw();
void operator()(struct framebuffer::fb<true>& scr) throw();
void clone(framebuffer::queue& q) const throw(std::bad_alloc);
private:
template<bool ext> void draw(struct framebuffer::fb<ext>& scr) throw();
params p;
std::u32string msg;
};
memorywatch_fb_object::memorywatch_fb_object(const memorywatch_fb_object::params& _p, const std::string& _msg)
: p(_p)
{
msg = utf8::to32(_msg);
}
memorywatch_fb_object::~memorywatch_fb_object() throw() {}
void memorywatch_fb_object::operator()(struct framebuffer::fb<false>& scr) throw() { draw(scr); }
void memorywatch_fb_object::operator()(struct framebuffer::fb<true>& scr) throw() { draw(scr); }
void memorywatch_fb_object::clone(framebuffer::queue& q) const throw(std::bad_alloc) { q.clone_helper(this); }
template<bool ext> void memorywatch_fb_object::draw(struct framebuffer::fb<ext>& scr) throw()
{
p.x += scr.get_origin_x();
p.y += scr.get_origin_y();
if(p.alt_origin_x)
p.x += scr.get_last_blit_width();
if(p.alt_origin_y)
p.y += scr.get_last_blit_height();
p.fg.set_palette(scr);
p.bg.set_palette(scr);
p.halo.set_palette(scr);
bool has_halo = p.halo;
//Layout the text.
int64_t orig_x = p.x;
int64_t drawx = p.x;
int64_t drawy = p.y;
int64_t max_drawx = p.x;
for(size_t i = 0; i < msg.size();) {
uint32_t cp = msg[i];
std::u32string k = p.font->best_ligature_match(msg, i);
const framebuffer::font2::glyph& glyph = p.font->lookup_glyph(k);
if(k.length())
i += k.length();
else
i++;
if(cp == 9) {
drawx = (drawx + 64) >> 6 << 6;
} else if(cp == 10) {
drawx = orig_x;
drawy += p.font->get_rowadvance();
} else {
drawx += glyph.width;
max_drawx = max(max_drawx, drawx);
}
}
uint64_t width = max_drawx - p.x;
uint64_t height = drawy - p.y;
if(has_halo) {
width += 2;
height += 2;
}
drawx = p.x;
drawy = p.y;
orig_x = p.x;
if(p.cliprange_x) {
if(drawx < 0)
drawx = 0;
else if(drawx + width > scr.get_width())
drawx = scr.get_width() - width;
}
if(p.cliprange_y) {
if(drawy < 0)
drawy = 0;
else if(drawy + height > scr.get_height())
drawy = scr.get_height() - height;
}
if(has_halo) {
orig_x++;
drawx++;
drawy++;
}
for(size_t i = 0; i < msg.size();) {
uint32_t cp = msg[i];
std::u32string k = p.font->best_ligature_match(msg, i);
const framebuffer::font2::glyph& glyph = p.font->lookup_glyph(k);
if(k.length())
i += k.length();
else
i++;
if(cp == 9) {
drawx = (drawx + 64) >> 6 << 6;
} else if(cp == 10) {
drawx = orig_x;
drawy += p.font->get_rowadvance();
} else {
glyph.render(scr, drawx, drawy, p.fg, p.bg, p.halo);
drawx += glyph.width;
}
}
}
}
memorywatch_output_fb::memorywatch_output_fb()
{
font = NULL;
}
memorywatch_output_fb::~memorywatch_output_fb()
{
if(dtor_cb)
dtor_cb(*this);
}
void memorywatch_output_fb::set_rqueue(framebuffer::queue& rqueue)
{
queue = &rqueue;
}
void memorywatch_output_fb::set_dtor_cb(std::function<void(memorywatch_output_fb&)> cb)
{
dtor_cb = cb;
}
void memorywatch_output_fb::show(const std::string& iname, const std::string& val)
{
memorywatch_fb_object::params p;
try {
if(cond_enable) {
enabled->reset();
auto e = enabled->evaluate();
if(!e.type->toboolean(e.value))
return;
}
pos_x->reset();
pos_y->reset();
auto x = pos_x->evaluate();
auto y = pos_y->evaluate();
p.x = x.type->tosigned(x.value);
p.y = y.type->tosigned(y.value);
p.alt_origin_x = alt_origin_x;
p.alt_origin_y = alt_origin_y;
p.cliprange_x = cliprange_x;
p.cliprange_y = cliprange_y;
p.font = font;
p.fg = fg;
p.bg = bg;
p.halo = halo;
queue->create_add<memorywatch_fb_object>(p, val);
} catch(...) {
}
}
void memorywatch_output_fb::reset()
{
enabled->reset();
pos_x->reset();
pos_y->reset();
}

View file

@ -0,0 +1,33 @@
#include "memorywatch-list.hpp"
memorywatch_output_list::memorywatch_output_list()
{
}
memorywatch_output_list::~memorywatch_output_list()
{
}
void memorywatch_output_list::set_output(std::function<void(const std::string& n, const std::string& v)> _fn)
{
fn = _fn;
}
void memorywatch_output_list::show(const std::string& iname, const std::string& val)
{
if(cond_enable) {
try {
enabled->reset();
auto e = enabled->evaluate();
if(!e.type->toboolean(e.value))
return;
} catch(...) {
return;
}
}
fn(iname, val);
}
void memorywatch_output_list::reset()
{
}

View file

@ -0,0 +1,17 @@
#include "memorywatch-null.hpp"
memorywatch_output_null::memorywatch_output_null()
{
}
memorywatch_output_null::~memorywatch_output_null()
{
}
void memorywatch_output_null::show(const std::string& iname, const std::string& val)
{
}
void memorywatch_output_null::reset()
{
}

361
src/library/memorywatch.cpp Normal file
View file

@ -0,0 +1,361 @@
#include "memorywatch.hpp"
#include "int24.hpp"
#include "mathexpr-error.hpp"
#include <sstream>
#include "string.hpp"
memorywatch_memread_oper::memorywatch_memread_oper()
: mathexpr_operinfo("(readmemory)")
{
}
memorywatch_memread_oper::~memorywatch_memread_oper() {}
void memorywatch_memread_oper::evaluate(mathexpr_value target, std::vector<std::function<mathexpr_value()>> promises)
{
if(promises.size() != 1)
throw mathexpr_error(mathexpr_error::ARGCOUNT, "Memory read operator takes 1 argument");
static const int system_endian = memory_space::get_system_endian();
uint64_t addr;
mathexpr_value val;
try {
val = promises[0]();
void* res = val.value;
addr = val.type->tounsigned(res);
if(addr_size)
addr %= addr_size;
addr += addr_base;
} catch(std::exception& e) {
throw mathexpr_error(mathexpr_error::ADDR, e.what());
}
if(bytes > 8)
throw mathexpr_error(mathexpr_error::SIZE, "Memory read size out of range");
char buf[8];
mspace->read_range(addr, buf, bytes);
//Endian swap if needed.
if(system_endian != endianess)
for(unsigned i = 0; i < bytes / 2; i++)
std::swap(buf[i], buf[bytes - i - 1]);
switch(bytes) {
case 1:
if(float_flag)
throw mathexpr_error(mathexpr_error::SIZE, "1 byte floats not supported");
else if(signed_flag)
target.type->parse_s(target.value, *(int8_t*)buf);
else
target.type->parse_u(target.value, *(uint8_t*)buf);
break;
case 2:
if(float_flag)
throw mathexpr_error(mathexpr_error::SIZE, "2 byte floats not supported");
else if(signed_flag)
target.type->parse_s(target.value, *(int16_t*)buf);
else
target.type->parse_u(target.value, *(uint16_t*)buf);
break;
case 3:
if(float_flag)
throw mathexpr_error(mathexpr_error::SIZE, "3 byte floats not supported");
else if(signed_flag)
target.type->parse_s(target.value, *(ss_int24_t*)buf);
else
target.type->parse_u(target.value, *(ss_uint24_t*)buf);
break;
case 4:
if(float_flag)
target.type->parse_f(target.value, *(float*)buf);
else if(signed_flag)
target.type->parse_s(target.value, *(int32_t*)buf);
else
target.type->parse_u(target.value, *(uint32_t*)buf);
break;
case 8:
if(float_flag)
target.type->parse_f(target.value, *(double*)buf);
else if(signed_flag)
target.type->parse_s(target.value, *(int64_t*)buf);
else
target.type->parse_u(target.value, *(uint64_t*)buf);
break;
default:
throw mathexpr_error(mathexpr_error::SIZE, "Memory address size not supported");
}
if(scale_div > 1)
target.type->scale(target.value, scale_div);
}
namespace
{
bool is_terminal(char ch)
{
if(ch == '%') return true;
if(ch == 'b') return true;
if(ch == 'B') return true;
if(ch == 'd') return true;
if(ch == 'i') return true;
if(ch == 'o') return true;
if(ch == 's') return true;
if(ch == 'u') return true;
if(ch == 'x') return true;
if(ch == 'X') return true;
return false;
}
std::string get_placeholder(const std::string& str, size_t idx)
{
std::ostringstream p;
for(size_t i = idx; i < str.length(); i++) {
p << str[i];
if(is_terminal(str[idx]))
break;
}
return p.str();
}
}
/*
bool showsign;
bool fillzeros;
int width;
int precision;
bool uppercasehex;
*/
memorywatch_item_printer::~memorywatch_item_printer()
{
}
void memorywatch_item_printer::trace()
{
}
std::string memorywatch_item::get_value()
{
if(format == "") {
//Default.
mathexpr_format fmt;
fmt.type = mathexpr_format::DEFAULT;
mathexpr_value v = expr->evaluate();
return v.type->format(v.value, fmt);
}
std::ostringstream out;
for(size_t i = 0; i < format.length(); i++) {
if(format[i] != '%')
out << format[i];
else {
//Format placeholder.
std::string p = get_placeholder(format, i + 1);
if(p == "")
continue;
i += p.length();
if(p[p.length() - 1] == '%') {
out << '%';
continue;
}
mathexpr_format fmt;
fmt.showsign = false;
fmt.fillzeros = false;
fmt.width = -1;
fmt.precision = -1;
fmt.uppercasehex = false;
auto r = regex("([+0]*)([1-9][0-9]*)?(\\.(0|[1-9][0-9]*))?([bBdiosuxX])", p);
if(!r) {
throw mathexpr_error(mathexpr_error::FORMAT, "Bad format placeholder");
continue;
}
std::string flags = r[1];
size_t i;
for(i = 0; i < flags.length(); i++) {
if(flags[i] == '+')
fmt.showsign = true;
if(flags[i] == '0')
fmt.fillzeros = true;
}
//The remaining part is width.precision.
if(r[2] != "")
try {
fmt.width = parse_value<int>(r[2]);
} catch(...) {}
if(r[4] != "")
try {
fmt.precision = parse_value<int>(r[4]);
} catch(...) {}
switch(r[5][0]) {
case 'b': fmt.type = mathexpr_format::BINARY; break;
case 'B': fmt.type = mathexpr_format::BOOLEAN; break;
case 'd': fmt.type = mathexpr_format::DECIMAL; break;
case 'i': fmt.type = mathexpr_format::DECIMAL; break;
case 'o': fmt.type = mathexpr_format::OCTAL; break;
case 's': fmt.type = mathexpr_format::STRING; break;
case 'u': fmt.type = mathexpr_format::DECIMAL; break;
case 'x': fmt.type = mathexpr_format::HEXADECIMAL; break;
case 'X': fmt.type = mathexpr_format::HEXADECIMAL; fmt.uppercasehex = true; break;
}
mathexpr_value v = expr->evaluate();
out << v.type->format(v.value, fmt);
}
}
return out.str();
}
void memorywatch_item::show(const std::string& n)
{
std::string x;
try {
x = get_value();
} catch(std::bad_alloc& e) {
throw;
} catch(mathexpr_error& e) {
x = e.get_short_error();
} catch(std::runtime_error& e) {
x = e.what();
}
if(printer)
printer->show(n, x);
}
memorywatch_set::~memorywatch_set()
{
roots.clear();
garbage_collectable::do_gc();
}
void memorywatch_set::reset()
{
for(auto& i : roots) {
if(i.second.printer)
i.second.printer->reset();
i.second.expr->reset();
}
}
void memorywatch_set::refresh()
{
for(auto& i : roots)
i.second.expr->reset();
for(auto& i : roots)
i.second.show(i.first);
}
std::set<std::string> memorywatch_set::set()
{
std::set<std::string> r;
for(auto i : roots)
r.insert(i.first);
return r;
}
memorywatch_item& memorywatch_set::get(const std::string& name)
{
auto i = get_soft(name);
if(!i)
throw std::runtime_error("No such watch '" + name + "'");
return *i;
}
memorywatch_item* memorywatch_set::get_soft(const std::string& name)
{
if(!roots.count(name))
return NULL;
return &(roots.find(name)->second);
}
memorywatch_item* memorywatch_set::create(const std::string& name, memorywatch_item& item)
{
roots.insert(std::make_pair(name, item));
return &(roots.find(name)->second);
}
void memorywatch_set::destroy(const std::string& name)
{
if(!roots.count(name))
return;
roots.erase(name);
garbage_collectable::do_gc();
}
const std::string& memorywatch_set::get_longest_name(std::function<size_t(const std::string& n)> rate)
{
static std::string empty;
size_t best_len = 0;
const std::string* best = &empty;
for(auto& i : roots) {
size_t r = rate(i.first);
if(r > best_len) {
best = &i.first;
best_len = r;
}
}
return *best;
}
size_t memorywatch_set::utflength_rate(const std::string& n)
{
return utf8::strlen(n);
}
void memorywatch_set::foreach(std::function<void(memorywatch_item& item)> cb)
{
for(auto& i : roots)
cb(i.second);
}
void memorywatch_set::swap(memorywatch_set& s) throw()
{
std::swap(roots, s.roots);
}
#ifdef TEST_MEMORYWATCH
#include "mathexpr-ntype.hpp"
struct stdout_item_printer : public memorywatch_item_printer
{
~stdout_item_printer()
{
}
void show(const std::string& n, const std::string& v)
{
std::cout << n << " --> " << v << std::endl;
}
void reset()
{
}
};
int main2(int argc, char** argv)
{
memorywatch_set mset;
gcroot_pointer<memorywatch_item_printer> printer(new stdout_item_printer);
std::function<gcroot_pointer<mathexpr>(const std::string&)> vars_fn = [&mset]
(const std::string& n) -> gcroot_pointer<mathexpr> {
auto p = mset.get_soft(n);
if(!p) {
memorywatch_item i(*expression_value());
p = mset.create(n, i);
}
return p->expr;
};
for(int i = 1; i < argc; i++) {
regex_results r = regex("([^=]+)=\\[(.*)\\](.*)", argv[i]);
if(!r)
throw std::runtime_error("Bad argument '" + std::string(argv[i]) + "'");
*vars_fn(r[1]) = *mathexpr::parse(*expression_value(), r[3], vars_fn);
mset.get(r[1]).format = r[2];
mset.get(r[1]).printer = printer;
}
garbage_collectable::do_gc();
garbage_collectable::do_gc();
mset.refresh();
return 0;
}
int main(int argc, char** argv)
{
int r = main2(argc, argv);
garbage_collectable::do_gc();
return r;
}
#endif

View file

@ -548,13 +548,6 @@ namespace
return 1;
});
lua::fnptr memory_watchexpr(lua_func_misc, "memory.read_expr", [](lua::state& L,
const std::string& fname) -> int {
std::string val = evaluate_watch(L.get_string(1, fname.c_str()));
L.pushstring(val.c_str());
return 1;
});
template<bool write, bool sign> int memory_scattergather(lua::state& L, const std::string& fname)
{
uint64_t val = 0;

View file

@ -36,30 +36,32 @@ namespace
const char* name;
unsigned len;
bool hard_bigendian;
const char* watch;
const char* format;
int type; //0 => Unsigned, 1 => Signed, 2 => Float
int scale;
std::string (*read)(const uint8_t* x);
};
val_type datatypes[] = {
{"1 byte (signed)", 1, false, "b", [](const uint8_t* x) -> std::string {
{"1 byte (signed)", 1, false, "", 1, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << (int)(char)x[0]).str();
}},
{"1 byte (unsigned)", 1, false, "B", [](const uint8_t* x) -> std::string {
{"1 byte (unsigned)", 1, false, "", 0, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << (int)x[0]).str();
}},
{"1 byte (hex)", 1, false, "BH2", [](const uint8_t* x) -> std::string {
{"1 byte (hex)", 1, false, "%02x", 0, 0, [](const uint8_t* x) -> std::string {
return hex::to(x[0]);
}},
{"2 bytes (signed)", 2, false, "w", [](const uint8_t* x) -> std::string {
{"2 bytes (signed)", 2, false, "", 1, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int16_t*)x).str();
}},
{"2 bytes (unsigned)", 2, false, "W", [](const uint8_t* x) -> std::string {
{"2 bytes (unsigned)", 2, false, "", 0, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint16_t*)x).str();
}},
{"2 bytes (hex)", 2, false, "WH4", [](const uint8_t* x) -> std::string {
{"2 bytes (hex)", 2, false, "%04x", 0, 0, [](const uint8_t* x) -> std::string {
return hex::to(*(uint16_t*)x);
}},
{"3 bytes (signed)", 3, true, "o", [](const uint8_t* x) -> std::string {
{"3 bytes (signed)", 3, true, "", 1, 0, [](const uint8_t* x) -> std::string {
int32_t a = 0;
a |= (uint32_t)x[0] << 16;
a |= (uint32_t)x[1] << 8;
@ -68,57 +70,57 @@ namespace
a -= 0x1000000;
return (stringfmt() << a).str();
}},
{"3 bytes (unsigned)", 3, true, "O", [](const uint8_t* x) -> std::string {
{"3 bytes (unsigned)", 3, true, "", 0, 0, [](const uint8_t* x) -> std::string {
int32_t a = 0;
a |= (uint32_t)x[0] << 16;
a |= (uint32_t)x[1] << 8;
a |= (uint32_t)x[2];
return (stringfmt() << a).str();
}},
{"3 bytes (hex)", 3, true, "OH6", [](const uint8_t* x) -> std::string {
{"3 bytes (hex)", 3, true, "%06x", 0, 0, [](const uint8_t* x) -> std::string {
int32_t a = 0;
a |= (uint32_t)x[0] << 16;
a |= (uint32_t)x[1] << 8;
a |= (uint32_t)x[2];
return hex::to24(a);
}},
{"4 bytes (signed)", 4, false, "d", [](const uint8_t* x) -> std::string {
{"4 bytes (signed)", 4, false, "", 1, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int32_t*)x).str();
}},
{"4 bytes (unsigned)", 4, false, "D", [](const uint8_t* x) -> std::string {
{"4 bytes (unsigned)", 4, false, "", 0, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint32_t*)x).str();
}},
{"4 bytes (hex)", 4, false, "DH8", [](const uint8_t* x) -> std::string {
{"4 bytes (hex)", 4, false, "%08x", 0, 0, [](const uint8_t* x) -> std::string {
return hex::to(*(uint32_t*)x);
}},
{"4 bytes (float)", 4, false, "f", [](const uint8_t* x) -> std::string {
{"4 bytes (float)", 4, false, "", 2, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(float*)x).str();
}},
{"8 bytes (signed)", 8, false, "q", [](const uint8_t* x) -> std::string {
{"8 bytes (signed)", 8, false, "", 1, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int64_t*)x).str();
}},
{"8 bytes (unsigned)", 8, false, "Q", [](const uint8_t* x) -> std::string {
{"8 bytes (unsigned)", 8, false, "", 0, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint64_t*)x).str();
}},
{"8 bytes (hex)", 8, false, "QHG", [](const uint8_t* x) -> std::string {
{"8 bytes (hex)", 8, false, "%016x", 0, 0, [](const uint8_t* x) -> std::string {
return hex::to(*(uint64_t*)x);
}},
{"8 bytes (float)", 8, false, "F", [](const uint8_t* x) -> std::string {
{"8 bytes (float)", 8, false, "", 2, 0, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(double*)x).str();
}},
{"Q8.8 (signed)", 2, false, "C256z$w/", [](const uint8_t* x) -> std::string {
{"Q8.8 (signed)", 2, false, "", 1, 8, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int16_t*)x / 256.0).str();
}},
{"Q8.8 (unsigned)", 2, false, "C256z$W/", [](const uint8_t* x) -> std::string {
{"Q8.8 (unsigned)", 2, false, "", 0, 8, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint16_t*)x / 256.0).str();
}},
{"Q12.4 (signed)", 2, false, "C16z$w/", [](const uint8_t* x) -> std::string {
{"Q12.4 (signed)", 2, false, "", 1, 4, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int16_t*)x / 16.0).str();
}},
{"Q12.4 (unsigned)", 2, false, "C16z$W/", [](const uint8_t* x) -> std::string {
{"Q12.4 (unsigned)", 2, false, "", 0, 4, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint16_t*)x / 16.0).str();
}},
{"Q16.8 (signed)", 3, false, "C256z$o/", [](const uint8_t* x) -> std::string {
{"Q16.8 (signed)", 3, true, "", 1, 8, [](const uint8_t* x) -> std::string {
int32_t a = 0;
a |= (uint32_t)x[0] << 16;
a |= (uint32_t)x[1] << 8;
@ -127,29 +129,29 @@ namespace
a -= 0x1000000;
return (stringfmt() << a / 256.0).str();
}},
{"Q16.8 (unsigned)", 3, false, "C256z$O/", [](const uint8_t* x) -> std::string {
{"Q16.8 (unsigned)", 3, true, "", 0, 8, [](const uint8_t* x) -> std::string {
int32_t a = 0;
a |= (uint32_t)x[0] << 16;
a |= (uint32_t)x[1] << 8;
a |= (uint32_t)x[2];
return (stringfmt() << a / 256.0).str();
}},
{"Q24.8 (signed)", 4, false, "C256z$d/", [](const uint8_t* x) -> std::string {
{"Q24.8 (signed)", 4, false, "", 1, 8, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int32_t*)x / 256.0).str();
}},
{"Q24.8 (unsigned)", 4, false, "C256z$D/", [](const uint8_t* x) -> std::string {
{"Q24.8 (unsigned)", 4, false, "", 0, 8, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint32_t*)x / 256.0).str();
}},
{"Q20.12 (signed)", 4, false, "C4096z$d/", [](const uint8_t* x) -> std::string {
{"Q20.12 (signed)", 4, false, "", 1, 12, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int32_t*)x / 4096.0).str();
}},
{"Q20.12 (unsigned)", 4, false, "C4096z$D/", [](const uint8_t* x) -> std::string {
{"Q20.12 (unsigned)", 4, false, "", 0, 12, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint32_t*)x / 4096.0).str();
}},
{"Q16.16 (signed)", 4, false, "C65536z$d/", [](const uint8_t* x) -> std::string {
{"Q16.16 (signed)", 4, false, "", 1, 16, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(int32_t*)x / 65536.0).str();
}},
{"Q16.16 (unsigned)", 4, false, "C65536z$D/", [](const uint8_t* x) -> std::string {
{"Q16.16 (unsigned)", 4, false, "", 0, 16, [](const uint8_t* x) -> std::string {
return (stringfmt() << *(uint32_t*)x / 65536.0).str();
}},
};
@ -499,15 +501,22 @@ public:
<< "Enter name for watch at 0x" << std::hex << addr << ":").str());
if(n == "")
return;
std::string wch = datatypes[curtype].watch;
size_t sz = wch.find_first_of("$");
std::string e;
if(sz < wch.length())
e = (stringfmt() << wch.substr(0, sz) << "C0x" << std::hex << addr << "z"
<< wch.substr(sz + 1)).str();
else
e = (stringfmt() << "C0x" << std::hex << addr << "z" << wch).str();
runemufn([n, e]() { set_watchexpr_for(n, e); });
lsnes_memorywatch_item e;
e.expr = (stringfmt() << addr).str();
e.format = datatypes[curtype].format;
e.bytes = datatypes[curtype].len;
e.signed_flag = (datatypes[curtype].type == 1);
e.float_flag = (datatypes[curtype].type == 2);
//Handle hostendian VMAs.
auto i = lsnes_memory.get_regions();
bool hostendian = false;
for(auto& j : i) {
if(addr >= j->base && addr < j->base + j->size && !j->endian)
hostendian = true;
}
e.endianess = hostendian ? 0 : (littleendian ? -1 : 1);
e.scale_div = 1ULL << datatypes[curtype].scale;
runemufn([n, &e]() { lsnes_memorywatch.set(n, e); });
} catch(canceled_exception& e) {
}
}

View file

@ -1,262 +1,532 @@
#include "core/moviedata.hpp"
#include "core/memorywatch.hpp"
#include "core/memorymanip.hpp"
#include "core/project.hpp"
#include "platform/wxwidgets/platform.hpp"
#include "platform/wxwidgets/loadsave.hpp"
#include <wx/wx.h>
#include <wx/statline.h>
#include <wx/event.h>
#include <wx/control.h>
#include <wx/combobox.h>
#include <wx/radiobut.h>
#include <wx/spinctrl.h>
#include "library/string.hpp"
#include "library/hex.hpp"
#include "interface/romtype.hpp"
class wxeditor_watchexpr : public wxDialog
{
public:
wxeditor_watchexpr(wxWindow* parent, const std::string& name, const std::string& expr);
bool ShouldPreventAppExit() const;
void on_ok(wxCommandEvent& e);
void on_cancel(wxCommandEvent& e);
void on_rb_structured(wxCommandEvent& e);
void on_rb_arbitrary(wxCommandEvent& e);
void on_rb_busaddr(wxCommandEvent& e);
void on_rb_mapaddr(wxCommandEvent& e);
void on_addr_change(wxCommandEvent& e);
std::string get_expr();
private:
std::string out;
wxRadioButton* structured;
wxRadioButton* arbitrary;
wxComboBox* typesel;
wxRadioButton* busaddr;
wxRadioButton* mapaddr;
wxTextCtrl* addr;
wxCheckBox* hex;
wxTextCtrl* watchtxt;
wxButton* ok;
wxButton* cancel;
};
/*
int memorywatch_recognize_typech(char ch)
std::string main_expr;
std::string format;
unsigned memread_bytes; //0 if not memory read.
bool memread_signed_flag;
bool memread_float_flag;
int memread_endianess;
uint64_t memread_scale_div;
uint64_t memread_addr_base;
uint64_t memread_addr_size;
enum position_category {
PC_DISABLED,
PC_MEMORYWATCH,
PC_ONSCREEN
} position;
std::string enabled; //Ignored for disabled.
std::string onscreen_xpos;
std::string onscreen_ypos;
bool onscreen_alt_origin_x;
bool onscreen_alt_origin_y;
bool onscreen_cliprange_x;
bool onscreen_cliprange_y;
std::string onscreen_font; //"" is system default.
int64_t onscreen_fg_color;
int64_t onscreen_bg_color;
int64_t onscreen_halo_color;
*/
namespace
{
switch(ch) {
case 'b': return 0;
case 'B': return 1;
case 'w': return 2;
case 'W': return 3;
case 'o': return 4;
case 'O': return 5;
case 'd': return 6;
case 'D': return 7;
case 'q': return 8;
case 'Q': return 9;
case 'f': return 10;
case 'F': return 11;
default: return 0;
int log2i(uint64_t v)
{
unsigned l = 0;
while(v > 1) {
v >>= 1;
l++;
}
return l;
}
}
wxeditor_watchexpr::wxeditor_watchexpr(wxWindow* parent, const std::string& name, const std::string& expr)
: wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit watch " + name), wxDefaultPosition, wxSize(-1, -1))
{
wxString types[] = {
wxT("Signed byte"), wxT("Unsigned byte"), wxT("Signed word"), wxT("Unsigned word"),
wxT("Signed onehalfword"), wxT("Unsigned onehalfword"), wxT("Signed doubleword"),
wxT("Unsigned doubleword"), wxT("Signed quadword"), wxT("Unsigned quadword"),
wxT("Float"), wxT("double")
template<class T>
class label_control
{
public:
label_control()
{
lbl = NULL;
ctrl = NULL;
}
template<typename... U> label_control(wxWindow* parent, const std::string& label, U... args)
{
lbl = new wxStaticText(parent, wxID_ANY, towxstring(label));
ctrl = new T(parent, args...);
}
void show(bool state)
{
lbl->Show(state);
ctrl->Show(state);
}
void enable(bool state)
{
lbl->Enable(state);
ctrl->Enable(state);
}
void add(wxSizer* s, bool prop = false)
{
s->Add(lbl, 0, wxALIGN_CENTER_VERTICAL);
s->Add(ctrl, prop ? 1 : 0, wxGROW);
}
void add_cb(std::function<void(wxStaticText* l, T* c)> cb)
{
cb(lbl, ctrl);
}
wxStaticText* label() { return lbl; }
T* operator->() { return ctrl; }
private:
wxStaticText* lbl;
T* ctrl;
};
Centre();
wxFlexGridSizer* top_s = new wxFlexGridSizer(9, 1, 0, 0);
SetSizer(top_s);
top_s->Add(structured = new wxRadioButton(this, wxID_ANY, wxT("Value"), wxDefaultPosition, wxDefaultSize,
wxRB_GROUP), 0, wxGROW);
top_s->Add(arbitrary = new wxRadioButton(this, wxID_ANY, wxT("Expression"), wxDefaultPosition, wxDefaultSize,
0), 0, wxGROW);
top_s->Add(typesel = new wxComboBox(this, wxID_ANY, types[0], wxDefaultPosition, wxDefaultSize,
12, types, wxCB_READONLY), 0, wxGROW);
top_s->Add(busaddr = new wxRadioButton(this, wxID_ANY, wxT("Bus address"), wxDefaultPosition, wxDefaultSize,
wxRB_GROUP), 0, wxGROW);
top_s->Add(mapaddr = new wxRadioButton(this, wxID_ANY, wxT("Map address"), wxDefaultPosition, wxDefaultSize,
0), 0, wxGROW);
top_s->Add(addr = new wxTextCtrl(this, wxID_ANY, towxstring(""), wxDefaultPosition, wxSize(200, -1)), 0,
wxGROW);
top_s->Add(hex = new wxCheckBox(this, wxID_ANY, towxstring("Hex formatting")), 0, wxGROW);
top_s->Add(watchtxt = new wxTextCtrl(this, wxID_ANY, towxstring(expr), wxDefaultPosition, wxSize(400, -1)), 1,
wxGROW);
structured->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
wxCommandEventHandler(wxeditor_watchexpr::on_rb_structured), NULL, this);
arbitrary->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
wxCommandEventHandler(wxeditor_watchexpr::on_rb_arbitrary), NULL, this);
busaddr->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
wxCommandEventHandler(wxeditor_watchexpr::on_rb_busaddr), NULL, this);
mapaddr->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
wxCommandEventHandler(wxeditor_watchexpr::on_rb_mapaddr), NULL, this);
addr->Connect(wxEVT_COMMAND_TEXT_UPDATED,
wxCommandEventHandler(wxeditor_watchexpr::on_addr_change), NULL, this);
addr->SetMaxLength(16);
watchtxt->Connect(wxEVT_COMMAND_TEXT_UPDATED,
wxCommandEventHandler(wxeditor_watchexpr::on_addr_change), NULL, this);
wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
pbutton_s->AddStretchSpacer();
pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("Ok")), 0, wxGROW);
pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")), 0, wxGROW);
ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_watchexpr::on_ok), NULL, this);
cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_watchexpr::on_cancel), NULL, this);
top_s->Add(pbutton_s);
pbutton_s->SetSizeHints(this);
top_s->SetSizeHints(this);
Fit();
regex_results r;
if(expr == "") {
structured->SetValue(true);
busaddr->SetValue(our_rom.rtype->get_bus_map().second);
mapaddr->SetValue(!our_rom.rtype->get_bus_map().second);
busaddr->Enable(our_rom.rtype->get_bus_map().second);
watchtxt->Disable();
} else if(r = regex("C0x([0-9A-Fa-f]{1,16})z([bBwWdDqQ])(H([0-9A-Ga-g]))?", expr)) {
structured->SetValue(true);
mapaddr->SetValue(true);
busaddr->Enable(our_rom.rtype->get_bus_map().second);
watchtxt->Disable();
std::string addr2 = r[1];
char* end;
char buf[512];
std::copy(addr2.begin(), addr2.end(), buf);
uint64_t parsed = strtoull(buf, &end, 16);
auto r2 = our_rom.rtype->get_bus_map();
if(parsed >= r2.first && parsed < r2.first + r2.second) {
parsed -= r2.first;
busaddr->SetValue(true);
}
addr->SetValue(towxstring((stringfmt() << std::hex << parsed).str()));
typesel->SetSelection(memorywatch_recognize_typech(r[2][0]));
hex->SetValue(r[3] != "");
} else {
arbitrary->SetValue(true);
(our_rom.rtype->get_bus_map().second ? busaddr : mapaddr)->SetValue(true);
mapaddr->Disable();
busaddr->Disable();
hex->Disable();
watchtxt->Enable();
std::string format_color(int64_t x)
{
return framebuffer::color::stringify(x);
}
wxCommandEvent e;
on_addr_change(e);
}
bool wxeditor_watchexpr::ShouldPreventAppExit() const
{
return false;
}
void wxeditor_watchexpr::on_ok(wxCommandEvent& e)
{
const char* letters = "bBwWoOdDqQfF";
const char* hexwidths = "22446688GGGG";
if(structured->GetValue()) {
std::string hexmod;
std::string addr2 = tostdstring(addr->GetValue());
char* end;
uint64_t parsed = strtoull(addr2.c_str(), &end, 16);
if(busaddr->GetValue())
parsed += our_rom.rtype->get_bus_map().first;
if(hex->GetValue())
hexmod = std::string("H") + hexwidths[typesel->GetSelection()];
out = (stringfmt() << "C0x" << std::hex << parsed << "z" << letters[typesel->GetSelection()]
<< hexmod).str();
} else
out = tostdstring(watchtxt->GetValue());
EndModal(wxID_OK);
}
void wxeditor_watchexpr::on_cancel(wxCommandEvent& e)
{
EndModal(wxID_CANCEL);
}
void wxeditor_watchexpr::on_rb_arbitrary(wxCommandEvent& e)
{
typesel->Disable();
busaddr->Disable();
mapaddr->Disable();
addr->Disable();
hex->Disable();
watchtxt->Enable();
on_addr_change(e);
}
void wxeditor_watchexpr::on_rb_structured(wxCommandEvent& e)
{
typesel->Enable();
busaddr->Enable(our_rom.rtype->get_bus_map().second);
mapaddr->Enable();
addr->Enable();
hex->Enable();
watchtxt->Disable();
on_addr_change(e);
}
void wxeditor_watchexpr::on_rb_busaddr(wxCommandEvent& e)
{
on_addr_change(e);
}
void wxeditor_watchexpr::on_rb_mapaddr(wxCommandEvent& e)
{
on_addr_change(e);
}
void wxeditor_watchexpr::on_addr_change(wxCommandEvent& e)
{
if(structured->GetValue()) {
std::string addr2 = tostdstring(addr->GetValue());
if(!regex_match("[0-9A-Fa-f]{1,16}", addr2)) {
ok->Enable(false);
return;
}
char* end;
uint64_t parsed = strtoull(addr2.c_str(), &end, 16);
if(busaddr->GetValue() && parsed >= our_rom.rtype->get_bus_map().second) {
ok->Enable(false);
return;
}
ok->Enable(true);
} else
ok->Enable(tostdstring(watchtxt->GetValue()) != "");
}
std::string wxeditor_watchexpr::get_expr()
{
return out;
}
std::string memorywatch_edit_watchexpr(wxWindow* parent, const std::string& name, const std::string& expr)
{
wxeditor_watchexpr* d = new wxeditor_watchexpr(parent, name, expr);
if(d->ShowModal() != wxID_OK) {
d->Destroy();
throw canceled_exception();
int64_t get_color(std::string x)
{
return framebuffer::color(x).asnumber();
}
std::string out = d->get_expr();
d->Destroy();
return out;
}
class wxeditor_memorywatch : public wxDialog
{
public:
wxeditor_memorywatch(wxWindow* parent);
wxeditor_memorywatch(wxWindow* parent, const std::string& name);
bool ShouldPreventAppExit() const;
void on_position_change(wxCommandEvent& e);
void on_fontsel(wxCommandEvent& e);
void on_ok(wxCommandEvent& e);
void on_cancel(wxCommandEvent& e);
void enable_condenable2(wxCommandEvent& e);
private:
void enable_for_pos(lsnes_memorywatch_printer::position_category p);
void enable_for_addr(bool is_addr);
void enable_for_vma(bool free, uint64_t _base, uint64_t _size);
void enable_condenable();
lsnes_memorywatch_printer::position_category get_poscategory();
label_control<wxComboBox> type;
label_control<wxTextCtrl> expr;
label_control<wxTextCtrl> format;
label_control<wxComboBox> endianess;
label_control<wxSpinCtrl> scale;
label_control<wxComboBox> vma;
label_control<wxTextCtrl> addrbase;
label_control<wxTextCtrl> addrsize;
label_control<wxComboBox> position;
wxCheckBox* cond_enable;
wxTextCtrl* enabled;
label_control<wxTextCtrl> xpos;
label_control<wxTextCtrl> ypos;
wxCheckBox* alt_origin_x;
wxCheckBox* alt_origin_y;
wxCheckBox* cliprange_x;
wxCheckBox* cliprange_y;
label_control<wxTextCtrl> font;
wxButton* font_sel;
label_control<wxTextCtrl> fg_color;
label_control<wxTextCtrl> bg_color;
label_control<wxTextCtrl> halo_color;
wxButton* ok;
wxButton* cancel;
std::string name;
std::string old_addrbase;
std::string old_addrsize;
bool was_free;
std::map<int, std::pair<uint64_t, uint64_t>> vmas_available;
};
wxeditor_memorywatch::wxeditor_memorywatch(wxWindow* parent, const std::string& _name)
: wxDialog(parent, wxID_ANY, towxstring("Edit memory watch '" + _name + "'")), name(_name)
{
Center();
wxSizer* top_s = new wxBoxSizer(wxVERTICAL);
SetSizer(top_s);
//Type.
wxSizer* s1 = new wxBoxSizer(wxHORIZONTAL);
type = label_control<wxComboBox>(this, "Data type", wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, 0,
nullptr, wxCB_READONLY);
type.add(s1);
type->Append("(Expression)");
type->Append("Signed byte");
type->Append("Unsigned byte");
type->Append("Signed word");
type->Append("Unsigned word");
type->Append("Signed 3-byte");
type->Append("Unsigned 3-byte");
type->Append("Signed dword");
type->Append("Unsigned dword");
type->Append("Float");
type->Append("Signed qword");
type->Append("Unsigned qword");
type->Append("Double");
type->SetSelection(3);
type->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
wxCommandEventHandler(wxeditor_memorywatch::on_position_change), NULL, this);
endianess = label_control<wxComboBox>(this, "Endian:", wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
0, nullptr, wxCB_READONLY);
endianess.add(s1, true);
endianess->Append(wxT("Little"));
endianess->Append(wxT("Host"));
endianess->Append(wxT("Big"));
endianess->SetSelection(0);
scale = label_control<wxSpinCtrl>(this, "Scale bits:", wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
wxSP_ARROW_KEYS, 0, 63, 0);
scale.add(s1);
top_s->Add(s1, 1, wxGROW);
//Memory range.
wxSizer* s5 = new wxBoxSizer(wxHORIZONTAL);
vma = label_control<wxComboBox>(this, "Memory:", wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, 0,
nullptr, wxCB_READONLY);
vma.add(s5, true);
vma->Append(wxT("(All)"));
auto i = lsnes_memory.get_regions();
for(auto j : i) {
int id = vma->GetCount();
vma->Append(towxstring(j->name));
vmas_available[id] = std::make_pair(j->base, j->size);
}
vma->SetSelection(0);
vma->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
wxCommandEventHandler(wxeditor_memorywatch::on_position_change), NULL, this);
addrbase = label_control<wxTextCtrl>(this, "Base:", wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(100, -1));
addrbase.add(s5, true);
addrsize = label_control<wxTextCtrl>(this, "Size:", wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(100, -1));
addrsize.add(s5, true);
top_s->Add(s5, 1, wxGROW);
//Expression.
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
expr = label_control<wxTextCtrl>(this, "Address:", wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1));
expr.add(s2, true);
top_s->Add(s2, 1, wxGROW);
//Format:
wxSizer* s3 = new wxBoxSizer(wxHORIZONTAL);
format = label_control<wxTextCtrl>(this, "Format:", wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1));
format.add(s3, true);
top_s->Add(s3, 1, wxGROW);
wxSizer* sx = new wxBoxSizer(wxVERTICAL);
sx->Add(1, 11);
top_s->Add(sx, 0, wxGROW);
wxSizer* s6 = new wxBoxSizer(wxHORIZONTAL);
position = label_control<wxComboBox>(this, "Position: ", wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
0, nullptr, wxCB_READONLY);
position.add(s6, true);
position->Append(wxT("Disabled"));
position->Append(wxT("Memory watch"));
position->Append(wxT("On screen"));
position->SetSelection(1);
position->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
wxCommandEventHandler(wxeditor_memorywatch::on_position_change), NULL, this);
top_s->Add(s6, 0, wxGROW);
wxSizer* s7 = new wxBoxSizer(wxHORIZONTAL);
s7->Add(cond_enable = new wxCheckBox(this, wxID_ANY, wxT("Conditional on: ")), 0, wxGROW);
s7->Add(enabled = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1)), 1, wxGROW);
cond_enable->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::enable_condenable2), NULL, this);
top_s->Add(s7, 0, wxGROW);
wxSizer* s8 = new wxBoxSizer(wxHORIZONTAL);
xpos = label_control<wxTextCtrl>(this, "X:", wxID_ANY, wxT(""), wxDefaultPosition, wxSize(300, -1));
xpos.add(s8, true);
s8->Add(alt_origin_x = new wxCheckBox(this, wxID_ANY, wxT("Alt. origin")), 0, wxGROW);
s8->Add(cliprange_x = new wxCheckBox(this, wxID_ANY, wxT("Clip range")), 0, wxGROW);
top_s->Add(s8, 0, wxGROW);
wxSizer* s9 = new wxBoxSizer(wxHORIZONTAL);
ypos = label_control<wxTextCtrl>(this, "Y:", wxID_ANY, wxT(""), wxDefaultPosition, wxSize(300, -1));
ypos.add(s9, true);
s9->Add(alt_origin_y = new wxCheckBox(this, wxID_ANY, wxT("Alt. origin")), 0, wxGROW);
s9->Add(cliprange_y = new wxCheckBox(this, wxID_ANY, wxT("Clip range")), 0, wxGROW);
top_s->Add(s9, 0, wxGROW);
wxSizer* s10 = new wxBoxSizer(wxHORIZONTAL);
font = label_control<wxTextCtrl>(this, "Font:", wxID_ANY, wxT(""), wxDefaultPosition, wxSize(300, -1));
font.add(s10, true);
s10->Add(font_sel = new wxButton(this, wxID_ANY, wxT("...")), 0, wxGROW);
font_sel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_fontsel), NULL, this);
top_s->Add(s10, 0, wxGROW);
wxSizer* s11 = new wxBoxSizer(wxHORIZONTAL);
fg_color = label_control<wxTextCtrl>(this, "Foreground:", wxID_ANY, wxT("#FFFFFF"),
wxDefaultPosition, wxSize(100, -1));
fg_color.add(s11, true);
bg_color = label_control<wxTextCtrl>(this, "Background:", wxID_ANY, wxT("transparent"), wxDefaultPosition,
wxSize(100, -1));
bg_color.add(s11, true);
halo_color = label_control<wxTextCtrl>(this, "Halo:", wxID_ANY, wxT("transparent"), wxDefaultPosition,
wxSize(100, -1));
halo_color.add(s11, true);
top_s->Add(s11, 0, wxGROW);
wxSizer* s12 = new wxBoxSizer(wxHORIZONTAL);
s12->AddStretchSpacer();
s12->Add(ok = new wxButton(this, wxID_ANY, wxT("Ok")), 0, wxGROW);
ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_memorywatch::on_ok), NULL, this);
s12->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")), 0, wxGROW);
cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_memorywatch::on_cancel), NULL,
this);
top_s->Add(s12, 0, wxGROW);
lsnes_memorywatch_item it;
bool had_it = false;
try {
it = lsnes_memorywatch.get(name);
had_it = true;
} catch(...) {
}
if(had_it) {
expr->SetValue(towxstring(it.expr));
format->SetValue(towxstring(it.format));
cond_enable->SetValue(it.printer.cond_enable);
enabled->SetValue(it.printer.enabled);
xpos->SetValue(it.printer.onscreen_xpos);
ypos->SetValue(it.printer.onscreen_ypos);
alt_origin_x->SetValue(it.printer.onscreen_alt_origin_x);
alt_origin_y->SetValue(it.printer.onscreen_alt_origin_y);
cliprange_x->SetValue(it.printer.onscreen_cliprange_x);
cliprange_y->SetValue(it.printer.onscreen_cliprange_y);
endianess->SetSelection(it.endianess + 1);
addrbase->SetValue(towxstring(hex::to<uint64_t>(it.addr_base)));
addrsize->SetValue(towxstring(hex::to<uint64_t>(it.addr_size)));
if(it.printer.position == lsnes_memorywatch_printer::PC_DISABLED) position->SetSelection(0);
if(it.printer.position == lsnes_memorywatch_printer::PC_MEMORYWATCH) position->SetSelection(1);
if(it.printer.position == lsnes_memorywatch_printer::PC_ONSCREEN) position->SetSelection(2);
switch(it.bytes) {
case 0: type->SetSelection(0); break;
case 1: type->SetSelection(it.signed_flag ? 1 : 2); break;
case 2: type->SetSelection(it.signed_flag ? 3 : 4); break;
case 3: type->SetSelection(it.signed_flag ? 5 : 6); break;
case 4: type->SetSelection(it.float_flag ? 9 : (it.signed_flag ? 7 : 8)); break;
case 8: type->SetSelection(it.float_flag ? 12 : (it.signed_flag ? 11 : 10)); break;
}
scale->SetValue(log2i(it.scale_div));
for(auto j : vmas_available) {
if(j.second.first == it.addr_base && j.second.second == it.addr_size)
vma->SetSelection(j.first);
}
font->SetValue(it.printer.onscreen_font);
fg_color->SetValue(towxstring(format_color(it.printer.onscreen_fg_color)));
bg_color->SetValue(towxstring(format_color(it.printer.onscreen_bg_color)));
halo_color->SetValue(towxstring(format_color(it.printer.onscreen_halo_color)));
}
wxCommandEvent e;
on_position_change(e);
top_s->SetSizeHints(this);
Fit();
}
bool wxeditor_memorywatch::ShouldPreventAppExit() const
{
return false;
}
lsnes_memorywatch_printer::position_category wxeditor_memorywatch::get_poscategory()
{
if(position->GetSelection() == 0) return lsnes_memorywatch_printer::PC_DISABLED;
if(position->GetSelection() == 1) return lsnes_memorywatch_printer::PC_MEMORYWATCH;
if(position->GetSelection() == 2) return lsnes_memorywatch_printer::PC_ONSCREEN;
}
void wxeditor_memorywatch::enable_for_pos(lsnes_memorywatch_printer::position_category p)
{
bool full_disable = (p == lsnes_memorywatch_printer::PC_DISABLED);
cond_enable->Enable(!full_disable);
enabled->Enable(cond_enable->GetValue() && !full_disable);
xpos.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
ypos.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
alt_origin_x->Enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
alt_origin_y->Enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
cliprange_x->Enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
cliprange_y->Enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
font.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
font_sel->Enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
fg_color.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
bg_color.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
halo_color.enable(p == lsnes_memorywatch_printer::PC_ONSCREEN);
}
void wxeditor_memorywatch::enable_condenable()
{
lsnes_memorywatch_printer::position_category p = get_poscategory();
bool full_disable = (p == lsnes_memorywatch_printer::PC_DISABLED);
enabled->Enable(cond_enable->GetValue() && !full_disable);
}
void wxeditor_memorywatch::enable_condenable2(wxCommandEvent& e)
{
enable_condenable();
}
void wxeditor_memorywatch::enable_for_addr(bool is_addr)
{
expr.label()->SetLabel(towxstring(is_addr ? "Address:" : "Expr:"));
endianess.enable(is_addr);
scale.enable(is_addr);
vma.enable(is_addr);
addrbase.enable(is_addr && was_free);
addrsize.enable(is_addr && was_free);
Fit();
}
void wxeditor_memorywatch::enable_for_vma(bool free, uint64_t _base, uint64_t _size)
{
//TODO: Set default endian.
if(!free && !was_free) {
addrbase->SetValue(towxstring((stringfmt() << std::hex << _base).str()));
addrsize->SetValue(towxstring((stringfmt() << std::hex << _size).str()));
} else if(free && !was_free) {
addrbase->SetValue(towxstring(old_addrbase));
addrsize->SetValue(towxstring(old_addrsize));
addrbase.enable(true);
addrsize.enable(true);
} else if(!free && was_free) {
old_addrbase = tostdstring(addrbase->GetValue());
old_addrsize = tostdstring(addrsize->GetValue());
addrbase->SetValue(towxstring((stringfmt() << std::hex << _base).str()));
addrsize->SetValue(towxstring((stringfmt() << std::hex << _size).str()));
addrbase.enable(false);
addrsize.enable(false);
}
was_free = free;
}
void wxeditor_memorywatch::on_position_change(wxCommandEvent& e)
{
enable_for_pos(get_poscategory());
int vmasel = vma->GetSelection();
if(vmasel == 0)
enable_for_vma(true, 0, 0);
else if(vmas_available.count(vmasel)) {
enable_for_vma(false, vmas_available[vmasel].first, vmas_available[vmasel].second);
} else {
vma->SetSelection(0);
enable_for_vma(true, 0, 0);
}
enable_for_addr(type->GetSelection() != 0);
}
void wxeditor_memorywatch::on_fontsel(wxCommandEvent& e)
{
try {
std::string filename = choose_file_load(this, "Choose font file", project_otherpath(), filetype_font);
font->SetValue(towxstring(filename));
} catch(canceled_exception& e) {
}
}
void wxeditor_memorywatch::on_ok(wxCommandEvent& e)
{
lsnes_memorywatch_item it;
it.expr = tostdstring(expr->GetValue());
it.format = tostdstring(format->GetValue());
it.printer.cond_enable = cond_enable->GetValue();
it.printer.enabled = tostdstring(enabled->GetValue());
it.printer.onscreen_xpos = tostdstring(xpos->GetValue());
it.printer.onscreen_ypos = tostdstring(ypos->GetValue());
it.printer.onscreen_alt_origin_x = alt_origin_x->GetValue();
it.printer.onscreen_alt_origin_y = alt_origin_y->GetValue();
it.printer.onscreen_cliprange_x = cliprange_x->GetValue();
it.printer.onscreen_cliprange_y = cliprange_y->GetValue();
it.printer.onscreen_font = tostdstring(font->GetValue());
it.endianess = endianess->GetSelection() - 1;
try {
it.addr_base = hex::from<uint64_t>(tostdstring(addrbase->GetValue()));
it.addr_size = hex::from<uint64_t>(tostdstring(addrsize->GetValue()));
} catch(std::exception& e) {
show_message_ok(NULL, "Bad memory range", std::string("Error parsing memory range: ") + e.what(),
wxICON_EXCLAMATION);
return;
}
if(position->GetSelection() == 0) it.printer.position = lsnes_memorywatch_printer::PC_DISABLED;
else if(position->GetSelection() == 1) it.printer.position = lsnes_memorywatch_printer::PC_MEMORYWATCH;
else if(position->GetSelection() == 2) it.printer.position = lsnes_memorywatch_printer::PC_ONSCREEN;
else it.printer.position = lsnes_memorywatch_printer::PC_MEMORYWATCH;
it.signed_flag = false;
it.float_flag = false;
switch(type->GetSelection()) {
case 0: it.bytes = 0; break;
case 1: it.bytes = 1; it.signed_flag = !false; break;
case 2: it.bytes = 1; it.signed_flag = !true; break;
case 3: it.bytes = 2; it.signed_flag = !false; break;
case 4: it.bytes = 2; it.signed_flag = !true; break;
case 5: it.bytes = 3; it.signed_flag = !false; break;
case 6: it.bytes = 3; it.signed_flag = !true; break;
case 7: it.bytes = 4; it.signed_flag = !false; break;
case 8: it.bytes = 4; it.signed_flag = !true; break;
case 9: it.bytes = 4; it.float_flag = true; break;
case 10: it.bytes = 8; it.signed_flag = !false; break;
case 11: it.bytes = 8; it.signed_flag = !true; break;
case 12: it.bytes = 8; it.float_flag = true; break;
};
it.scale_div = 1ULL << scale->GetValue();
try {
it.printer.onscreen_fg_color = get_color(tostdstring(fg_color->GetValue()));
it.printer.onscreen_bg_color = get_color(tostdstring(bg_color->GetValue()));
it.printer.onscreen_halo_color = get_color(tostdstring(halo_color->GetValue()));
} catch(std::exception& e) {
show_message_ok(NULL, "Bad colors", std::string("Error parsing colors: ") + e.what(),
wxICON_EXCLAMATION);
return;
}
bool did_error = false;
std::string error;
runemufn([this, &it, &did_error, &error]() {
try {
lsnes_memorywatch.set(name, it);
} catch(std::exception& e) {
did_error = true;
error = e.what();
}
});
if(did_error) {
show_message_ok(NULL, "Bad values", std::string("Error setting memory watch: ") + error,
wxICON_EXCLAMATION);
return;
}
EndModal(wxID_OK);
}
void wxeditor_memorywatch::on_cancel(wxCommandEvent& e)
{
EndModal(wxID_CANCEL);
}
class wxeditor_memorywatches : public wxDialog
{
public:
wxeditor_memorywatches(wxWindow* parent);
bool ShouldPreventAppExit() const;
void on_memorywatch_change(wxCommandEvent& e);
void on_new(wxCommandEvent& e);
@ -266,6 +536,7 @@ public:
void on_close(wxCommandEvent& e);
private:
void refresh();
//TODO: Make this a wxGrid.
wxListBox* watches;
wxButton* newbutton;
wxButton* renamebutton;
@ -275,7 +546,7 @@ private:
};
wxeditor_memorywatch::wxeditor_memorywatch(wxWindow* parent)
wxeditor_memorywatches::wxeditor_memorywatches(wxWindow* parent)
: wxDialog(parent, wxID_ANY, wxT("lsnes: Edit memory watches"), wxDefaultPosition, wxSize(-1, -1))
{
Centre();
@ -284,7 +555,7 @@ wxeditor_memorywatch::wxeditor_memorywatch(wxWindow* parent)
top_s->Add(watches = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(400, 300)), 1, wxGROW);
watches->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
wxCommandEventHandler(wxeditor_memorywatch::on_memorywatch_change), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_memorywatch_change), NULL, this);
wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
pbutton_s->AddStretchSpacer();
@ -294,15 +565,15 @@ wxeditor_memorywatch::wxeditor_memorywatch(wxWindow* parent)
pbutton_s->Add(deletebutton = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW);
pbutton_s->Add(closebutton = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW);
newbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_new), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_new), NULL, this);
editbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_edit), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_edit), NULL, this);
renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_rename), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_rename), NULL, this);
deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_delete), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_delete), NULL, this);
closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxeditor_memorywatch::on_close), NULL, this);
wxCommandEventHandler(wxeditor_memorywatches::on_close), NULL, this);
top_s->Add(pbutton_s, 0, wxGROW);
pbutton_s->SetSizeHints(this);
@ -312,12 +583,12 @@ wxeditor_memorywatch::wxeditor_memorywatch(wxWindow* parent)
refresh();
}
bool wxeditor_memorywatch::ShouldPreventAppExit() const
bool wxeditor_memorywatches::ShouldPreventAppExit() const
{
return false;
}
void wxeditor_memorywatch::on_memorywatch_change(wxCommandEvent& e)
void wxeditor_memorywatches::on_memorywatch_change(wxCommandEvent& e)
{
std::string watch = tostdstring(watches->GetStringSelection());
editbutton->Enable(watch != "");
@ -325,15 +596,15 @@ void wxeditor_memorywatch::on_memorywatch_change(wxCommandEvent& e)
renamebutton->Enable(watch != "");
}
void wxeditor_memorywatch::on_new(wxCommandEvent& e)
void wxeditor_memorywatches::on_new(wxCommandEvent& e)
{
try {
std::string newname = pick_text(this, "New watch", "Enter name for watch:");
if(newname == "")
return;
std::string newtext = memorywatch_edit_watchexpr(this, newname, "");
if(newtext != "")
runemufn([newname, newtext]() { set_watchexpr_for(newname, newtext); });
wxeditor_memorywatch* nwch = new wxeditor_memorywatch(this, newname);
nwch->ShowModal();
nwch->Destroy();
refresh();
} catch(canceled_exception& e) {
//Ignore.
@ -341,7 +612,7 @@ void wxeditor_memorywatch::on_new(wxCommandEvent& e)
on_memorywatch_change(e);
}
void wxeditor_memorywatch::on_rename(wxCommandEvent& e)
void wxeditor_memorywatches::on_rename(wxCommandEvent& e)
{
std::string watch = tostdstring(watches->GetStringSelection());
if(watch == "")
@ -350,14 +621,7 @@ void wxeditor_memorywatch::on_rename(wxCommandEvent& e)
bool exists = false;
std::string newname = pick_text(this, "Rename watch", "Enter New name for watch:");
runemufn([watch, newname, &exists]() {
std::string x = get_watchexpr_for(watch);
std::string y = get_watchexpr_for(newname);
if(y != "")
exists = true;
else {
set_watchexpr_for(watch, "");
set_watchexpr_for(newname, x);
}
exists = !lsnes_memorywatch.rename(watch, newname);
});
if(exists)
show_message_ok(this, "Error", "The target watch already exists", wxICON_EXCLAMATION);
@ -368,26 +632,25 @@ void wxeditor_memorywatch::on_rename(wxCommandEvent& e)
on_memorywatch_change(e);
}
void wxeditor_memorywatch::on_delete(wxCommandEvent& e)
void wxeditor_memorywatches::on_delete(wxCommandEvent& e)
{
std::string watch = tostdstring(watches->GetStringSelection());
if(watch != "")
runemufn([watch]() { set_watchexpr_for(watch, ""); });
runemufn([watch]() { lsnes_memorywatch.clear(watch); });
refresh();
on_memorywatch_change(e);
}
void wxeditor_memorywatch::on_edit(wxCommandEvent& e)
void wxeditor_memorywatches::on_edit(wxCommandEvent& e)
{
std::string watch = tostdstring(watches->GetStringSelection());
if(watch == "")
return;
try {
std::string watch = tostdstring(watches->GetStringSelection());
if(watch == "")
return;
std::string wtxt;
runemufn([watch, &wtxt]() { wtxt = get_watchexpr_for(watch); });
wtxt = memorywatch_edit_watchexpr(this, watch, wtxt);
if(wtxt != "")
runemufn([watch, wtxt]() { set_watchexpr_for(watch, wtxt); });
wxeditor_memorywatch* ewch = new wxeditor_memorywatch(this, watch);
ewch->ShowModal();
ewch->Destroy();
refresh();
} catch(canceled_exception& e) {
//Ignore.
@ -395,34 +658,32 @@ void wxeditor_memorywatch::on_edit(wxCommandEvent& e)
on_memorywatch_change(e);
}
void wxeditor_memorywatch::on_close(wxCommandEvent& e)
void wxeditor_memorywatches::on_close(wxCommandEvent& e)
{
EndModal(wxID_OK);
}
void wxeditor_memorywatch::refresh()
void wxeditor_memorywatches::refresh()
{
std::map<std::string, std::string> bind;
std::set<std::string> bind;
runemufn([&bind]() {
std::set<std::string> x = get_watches();
for(auto i : x)
bind[i] = get_watchexpr_for(i);
bind = lsnes_memorywatch.enumerate();
});
watches->Clear();
for(auto i : bind)
watches->Append(towxstring(i.first));
watches->Append(towxstring(i));
if(watches->GetCount())
watches->SetSelection(0);
wxCommandEvent e;
on_memorywatch_change(e);
}
void wxeditor_memorywatch_display(wxWindow* parent)
void wxeditor_memorywatches_display(wxWindow* parent)
{
modal_pause_holder hld;
wxDialog* editor;
try {
editor = new wxeditor_memorywatch(parent);
editor = new wxeditor_memorywatches(parent);
editor->ShowModal();
} catch(...) {
}

View file

@ -102,3 +102,4 @@ single_type filetype_hexbookmarks("lhb", "Hex editor bookmarks");
single_type filetype_memorysearch("lms", "Memory search save");
single_type filetype_textfile("txt", "Text file");
single_type filetype_trace("trace", "Trace file");
single_type filetype_font("font", "Font file");

View file

@ -432,9 +432,15 @@ namespace
std::string name = i.first;
while(true) {
if(!old_watches.count(name)) {
set_watchexpr_for(name, i.second);
try {
if(name != "" && i.second != "")
lsnes_memorywatch.set(name, i.second);
} catch(std::exception& e) {
messages << "Can't set memory watch '" << name << "': "
<< e.what() << std::endl;
}
break;
} else if(get_watchexpr_for(name) == i.second)
} else if(lsnes_memorywatch.get_string(name) == i.second)
break;
else
name = munge_name(name);
@ -442,10 +448,21 @@ namespace
}
} else {
for(auto i : new_watches)
set_watchexpr_for(i.first, i.second);
try {
if(i.first != "" && i.second != "")
lsnes_memorywatch.set(i.first, i.second);
} catch(std::exception& e) {
messages << "Can't set memory watch '" << i.first << "': "
<< e.what() << std::endl;
}
for(auto i : old_watches)
if(!new_watches.count(i))
set_watchexpr_for(i, "");
try {
lsnes_memorywatch.clear(i);
} catch(std::exception& e) {
messages << "Can't clear memory watch '" << i << "': "
<< e.what() << std::endl;
}
}
}
@ -1448,18 +1465,25 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
show_wxeditor_voicesub(this);
return;
case wxID_EDIT_MEMORYWATCH:
wxeditor_memorywatch_display(this);
wxeditor_memorywatches_display(this);
return;
case wxID_SAVE_MEMORYWATCH: {
modal_pause_holder hld;
std::set<std::string> old_watches;
runemufn([&old_watches]() { old_watches = get_watches(); });
runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
std::string filename = choose_file_save(this, "Save watches to file", project_otherpath(),
filetype_watch);
std::ofstream out(filename.c_str());
for(auto i : old_watches) {
std::string val;
runemufn([i, &val]() { val = get_watchexpr_for(i); });
runemufn([i, &val]() {
try {
val = lsnes_memorywatch.get_string(i);
} catch(std::exception& e) {
messages << "Can't get value of watch '" << i << "': " << e.what()
<< std::endl;
}
});
out << i << std::endl << val << std::endl;
}
out.close();
@ -1468,7 +1492,7 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
case wxID_LOAD_MEMORYWATCH: {
modal_pause_holder hld;
std::set<std::string> old_watches;
runemufn([&old_watches]() { old_watches = get_watches(); });
runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
std::map<std::string, std::string> new_watches;
std::string filename = choose_file_load(this, "Choose memory watch file", project_otherpath(),
filetype_watch);

View file

@ -50,7 +50,24 @@ memory_search* wxwindow_memorysearch_active();
namespace
{
unsigned UNDOHISTORY_MAXSIZE = 48;
const char* watchchars = "bBwWoOdDqQfF";
struct _watch_properties {
unsigned len;
int type; //0 => Unsigned, 1 => Signed, 2 => Float.
const char* hformat;
} watch_properties[] = {
{1, 1, "%02x"},
{1, 0, "%02x"},
{2, 1, "%04x"},
{2, 0, "%04x"},
{3, 1, "%06x"},
{3, 0, "%06x"},
{4, 1, "%08x"},
{4, 0, "%08x"},
{8, 1, "%016x"},
{8, 0, "%016x"},
{4, 2, ""},
{8, 2, ""},
};
wxwindow_memorysearch* mwatch;
@ -994,7 +1011,6 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
start = act_line;
end = act_line + 1;
}
char wch = watchchars[typecode];
for(long r = start; r < end; r++) {
if(!addresses.count(r))
continue;
@ -1004,8 +1020,22 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
<< "Enter name for watch at 0x" << std::hex << addr << ":").str());
if(n == "")
continue;
std::string e = (stringfmt() << "C0x" << std::hex << addr << "z" << wch).str();
runemufn([n, e]() { set_watchexpr_for(n, e); });
lsnes_memorywatch_item e;
e.expr = (stringfmt() << addr).str();
bool is_hex = hexmode2->GetValue();
e.bytes = watch_properties[typecode].len;
e.signed_flag = !is_hex && (watch_properties[typecode].type == 1);
e.float_flag = (watch_properties[typecode].type == 2);
if(e.float_flag) is_hex = false;
e.format = is_hex ? watch_properties[typecode].hformat : "";
auto i = lsnes_memory.get_regions();
int endianess = 0;
for(auto& j : i) {
if(addr >= j->base && addr < j->base + j->size)
endianess = j->endian;
}
e.endianess = endianess;
runemufn([n, &e]() { lsnes_memorywatch.set(n, e); });
} catch(canceled_exception& e) {
}
}