More advanced memory watches
This commit is contained in:
parent
c0faaf1fce
commit
0a41e8d901
35 changed files with 4964 additions and 940 deletions
|
@ -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
|
||||
|
|
|
@ -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
66
include/library/gc.hpp
Normal 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
|
32
include/library/mathexpr-error.hpp
Normal file
32
include/library/mathexpr-error.hpp
Normal 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
|
13
include/library/mathexpr-format.hpp
Normal file
13
include/library/mathexpr-format.hpp
Normal 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
|
8
include/library/mathexpr-ntype.hpp
Normal file
8
include/library/mathexpr-ntype.hpp
Normal 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
|
271
include/library/mathexpr.hpp
Normal file
271
include/library/mathexpr.hpp
Normal 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
|
34
include/library/memorywatch-fb.hpp
Normal file
34
include/library/memorywatch-fb.hpp
Normal 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
|
22
include/library/memorywatch-list.hpp
Normal file
22
include/library/memorywatch-list.hpp
Normal 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
|
17
include/library/memorywatch-null.hpp
Normal file
17
include/library/memorywatch-null.hpp
Normal 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
|
169
include/library/memorywatch.hpp
Normal file
169
include/library/memorywatch.hpp
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
306
manual.lyx
306
manual.lyx
|
@ -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
|
||||
|
|
200
manual.txt
200
manual.txt
|
@ -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:
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
58
src/library/gc.cpp
Normal 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();
|
||||
}
|
||||
}
|
31
src/library/mathexpr-error.cpp
Normal file
31
src/library/mathexpr-error.cpp
Normal 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";
|
||||
};
|
||||
}
|
||||
|
244
src/library/mathexpr-format.cpp
Normal file
244
src/library/mathexpr-format.cpp
Normal 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);
|
||||
}
|
1148
src/library/mathexpr-ntype.cpp
Normal file
1148
src/library/mathexpr-ntype.cpp
Normal file
File diff suppressed because it is too large
Load diff
625
src/library/mathexpr.cpp
Normal file
625
src/library/mathexpr.cpp
Normal 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());
|
||||
}
|
183
src/library/memorywatch-fb.cpp
Normal file
183
src/library/memorywatch-fb.cpp
Normal 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();
|
||||
}
|
33
src/library/memorywatch-list.cpp
Normal file
33
src/library/memorywatch-list.cpp
Normal 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()
|
||||
{
|
||||
}
|
17
src/library/memorywatch-null.cpp
Normal file
17
src/library/memorywatch-null.cpp
Normal 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
361
src/library/memorywatch.cpp
Normal 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 = ∅
|
||||
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
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(...) {
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue