diff --git a/include/core/memorywatch.hpp b/include/core/memorywatch.hpp index e1d6c655..0489abc6 100644 --- a/include/core/memorywatch.hpp +++ b/include/core/memorywatch.hpp @@ -4,11 +4,171 @@ #include #include #include +#include "library/memorywatch.hpp" +#include "library/json.hpp" -std::string evaluate_watch(const std::string& expr) throw(std::bad_alloc); std::set 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 \ No newline at end of file +/** + * 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 get_printer_obj( + std::function(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>& list); +/** + * Set multiple items at once from JSON descriptions. + * + * Parameter list: The list of items. + */ + void set_multi(std::list>& 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& name); +/** + * Enumerate item names. + */ + std::set 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& nitems); + std::map items; + memorywatch_set watch_set; +}; + +extern lsnes_memorywatch_set lsnes_memorywatch; + +#endif diff --git a/include/library/framebuffer.hpp b/include/library/framebuffer.hpp index 2efa0905..e7ab6382 100644 --- a/include/library/framebuffer.hpp +++ b/include/library/framebuffer.hpp @@ -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 void set_palette(struct fb& s) throw() { diff --git a/include/library/gc.hpp b/include/library/gc.hpp new file mode 100644 index 00000000..b494addf --- /dev/null +++ b/include/library/gc.hpp @@ -0,0 +1,66 @@ +#ifndef _library__gc__hpp__included__ +#define _library__gc__hpp__included__ + +#include + +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 gcroot_pointer +{ +public: + gcroot_pointer() + { + ptr = NULL; + } + gcroot_pointer(T* obj) + { + ptr = obj; + } + template 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 diff --git a/include/library/mathexpr-error.hpp b/include/library/mathexpr-error.hpp new file mode 100644 index 00000000..3b79c454 --- /dev/null +++ b/include/library/mathexpr-error.hpp @@ -0,0 +1,32 @@ +#ifndef _library__mathexpr_error__hpp__included__ +#define _library__mathexpr_error__hpp__included__ + +#include +#include + +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 diff --git a/include/library/mathexpr-format.hpp b/include/library/mathexpr-format.hpp new file mode 100644 index 00000000..6db005f1 --- /dev/null +++ b/include/library/mathexpr-format.hpp @@ -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 diff --git a/include/library/mathexpr-ntype.hpp b/include/library/mathexpr-ntype.hpp new file mode 100644 index 00000000..dedd2686 --- /dev/null +++ b/include/library/mathexpr-ntype.hpp @@ -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 diff --git a/include/library/mathexpr.hpp b/include/library/mathexpr.hpp new file mode 100644 index 00000000..bfad0c9a --- /dev/null +++ b/include/library/mathexpr.hpp @@ -0,0 +1,271 @@ +#ifndef _library__mathexpr__hpp__included__ +#define _library__mathexpr__hpp__included__ + +#include +#include +#include +#include "gc.hpp" +#include "mathexpr-error.hpp" +#include + +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> 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 operations() = 0; + void* copy_allocate(void* src) + { + void* p = allocate(); + try { + copy(p, src); + } catch(...) { + deallocate(p); + throw; + } + return p; + } +}; + +template struct mathexpr_operinfo_wrapper : public mathexpr_operinfo +{ + mathexpr_operinfo_wrapper(std::string funcname, T (*_fn)(std::vector> promises)) + : mathexpr_operinfo(funcname), fn(_fn) + { + } + mathexpr_operinfo_wrapper(std::string opername, unsigned _operands, int _percedence, bool _rtl, + T (*_fn)(std::vector> promises)) + : mathexpr_operinfo(opername, _operands, _percedence, _rtl), fn(_fn) + { + } + ~mathexpr_operinfo_wrapper() + { + } + void evaluate(mathexpr_value target, std::vector> promises) + { + std::vector> _promises; + for(auto i : promises) { + std::function 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> promises); +}; + +template struct mathexpr_opfun_info +{ + std::string name; + T (*_fn)(std::vector> promises); + bool is_operator; + unsigned operands; + int precedence; + bool rtl; +}; + +template struct mathexpr_operinfo_set +{ + mathexpr_operinfo_set(std::initializer_list> list) + { + for(auto i : list) { + if(i.is_operator) + set.insert(new mathexpr_operinfo_wrapper(i.name, i.operands, i.precedence, + i.rtl, i._fn)); + else + set.insert(new mathexpr_operinfo_wrapper(i.name, i._fn)); + } + } + ~mathexpr_operinfo_set() + { + for(auto i : set) + delete i; + } + std::set& get_set() + { + return set; + } +private: + std::set set; +}; + +template 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 operations() + { + std::set 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 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> _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 parse(mathexpr_typeinfo& _type, const std::string& expr, + std::function(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 arguments; + mutable bool owns_operator; +}; + +#endif diff --git a/include/library/memorywatch-fb.hpp b/include/library/memorywatch-fb.hpp new file mode 100644 index 00000000..a918c9e9 --- /dev/null +++ b/include/library/memorywatch-fb.hpp @@ -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 cb); + void show(const std::string& iname, const std::string& val); + void reset(); + bool cond_enable; + gcroot_pointer enabled; + gcroot_pointer pos_x; + gcroot_pointer 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 dtor_cb; +}; + +#endif diff --git a/include/library/memorywatch-list.hpp b/include/library/memorywatch-list.hpp new file mode 100644 index 00000000..e2a59b8f --- /dev/null +++ b/include/library/memorywatch-list.hpp @@ -0,0 +1,22 @@ +#ifndef _library__memorywatch_list__hpp__included__ +#define _library__memorywatch_list__hpp__included__ + +#include "memorywatch.hpp" +#include "mathexpr.hpp" +#include +#include + +struct memorywatch_output_list : public memorywatch_item_printer +{ + memorywatch_output_list(); + ~memorywatch_output_list(); + void set_output(std::function _fn); + void show(const std::string& iname, const std::string& val); + void reset(); + bool cond_enable; + gcroot_pointer enabled; + //State variables. + std::function fn; +}; + +#endif diff --git a/include/library/memorywatch-null.hpp b/include/library/memorywatch-null.hpp new file mode 100644 index 00000000..1f0bbcb0 --- /dev/null +++ b/include/library/memorywatch-null.hpp @@ -0,0 +1,17 @@ +#ifndef _library__memorywatch_null__hpp__included__ +#define _library__memorywatch_null__hpp__included__ + +#include "memorywatch.hpp" +#include "mathexpr.hpp" +#include +#include + +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 diff --git a/include/library/memorywatch.hpp b/include/library/memorywatch.hpp new file mode 100644 index 00000000..42b4096b --- /dev/null +++ b/include/library/memorywatch.hpp @@ -0,0 +1,169 @@ +#ifndef _library__memorywatch__hpp__included__ +#define _library__memorywatch__hpp__included__ + +#include "mathexpr.hpp" +#include "memoryspace.hpp" +#include +#include +#include + +/** + * 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> 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 printer; //Printer to use. + gcroot_pointer 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 rate); +/** + * Get the set of memory watch names. + */ + std::set 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 cb); +/** + * Swap set with another. + */ + void swap(memorywatch_set& s) throw(); +private: + static size_t utflength_rate(const std::string& s); + std::map roots; +}; + +#endif diff --git a/include/platform/wxwidgets/loadsave.hpp b/include/platform/wxwidgets/loadsave.hpp index b3617053..1e1bd328 100644 --- a/include/platform/wxwidgets/loadsave.hpp +++ b/include/platform/wxwidgets/loadsave.hpp @@ -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); diff --git a/include/platform/wxwidgets/platform.hpp b/include/platform/wxwidgets/platform.hpp index 382b2002..3c78a6d0 100644 --- a/include/platform/wxwidgets/platform.hpp +++ b/include/platform/wxwidgets/platform.hpp @@ -48,7 +48,7 @@ std::vector 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); diff --git a/manual.lyx b/manual.lyx index db9d257d..eb5da592 100644 --- a/manual.lyx +++ b/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 right (integers only). + Arithmetic for signed. \end_layout -\begin_layout Itemize -a : atan(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 -Cz : Push 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 -C0xz : Push 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 : 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 : round a to 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 diff --git a/manual.txt b/manual.txt index d566c93a..20337504 100644 --- a/manual.txt +++ b/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 right (integers only). Arithmetic for signed. -• a : atan(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) - -• Cz : Push number to stack. - -• D : read_unsigned_dword(a) - -• C0xz : Push number (hexadecimal) to stack. - -• F: read_double(a) - -• H : 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 : round a to 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: diff --git a/src/core/framebuffer.cpp b/src/core/framebuffer.cpp index db576d58..fe1a3c99 100644 --- a/src/core/framebuffer.cpp +++ b/src/core/framebuffer.cpp @@ -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> dtb(lsnes_vset, "top-border", "UI‣Top padding", 0); + settingvar::variable> dbb(lsnes_vset, "bottom-border", + "UI‣Bottom padding", 0); + settingvar::variable> dlb(lsnes_vset, "left-border", + "UI‣Left padding", 0); + settingvar::variable> 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; diff --git a/src/core/mainloop.cpp b/src/core/mainloop.cpp index e3cd3f7c..52ec6bb3 100644 --- a/src/core/mainloop.cpp +++ b/src/core/mainloop.cpp @@ -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) diff --git a/src/core/memorywatch.cpp b/src/core/memorywatch.cpp index bd9154db..bac530c8 100644 --- a/src/core/memorywatch.cpp +++ b/src/core/memorywatch.cpp @@ -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 #include @@ -16,441 +24,459 @@ #include #include +void update_movie_state(); + namespace { - std::map watches; + std::map> 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& 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 void stack_push(std::stack& 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 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 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(s, atan(stack_pop(s).as_double())); - break; - case 'A': - a = stack_pop(s); - b = stack_pop(s); - stack_push(s, atan2(a.as_double(), b.as_double())); - break; - case 'c': - stack_push(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(s, sqrt(a.as_double())); - break; - case 's': - stack_push(s, sin(stack_pop(s).as_double())); - break; - case 't': - stack_push(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(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'B': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'w': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'W': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'o': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'O': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'd': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'D': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'q': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'Q': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'f': - stack_push(s, lsnes_memory.read(stack_pop(s).as_address())); - break; - case 'F': - stack_push(s, lsnes_memory.read(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 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 lsnes_memorywatch_printer::get_printer_obj( + std::function(const std::string& n)> vars) +{ + gcroot_pointer ptr; + memorywatch_output_list* l; + memorywatch_output_fb* f; + + std::string _enabled = (enabled != "") ? enabled : "true"; + + switch(position) { + case PC_DISABLED: + ptr = gcroot_pointer(new memorywatch_output_null); + break; + case PC_MEMORYWATCH: + ptr = gcroot_pointer(new memorywatch_output_list); + l = dynamic_cast(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(new memorywatch_output_fb); + f = dynamic_cast(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 lsnes_memorywatch_set::enumerate() { std::set r; - auto p = project_get(); - std::map* 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* 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 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 add_watch(lsnes_cmd, "add-watch", "Add a memory watch", - "Syntax: add-watch \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 remove_watch(lsnes_cmd, "remove-watch", "Remove a memory watch", - "Syntax: remove-watch \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(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 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 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>& list) +{ + std::map 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>& list) +{ + std::list> _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& names) +{ + std::map 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& nitems) +{ + { + memorywatch_set new_set; + std::map> vars; + auto vars_fn = [&vars](const std::string& n) -> gcroot_pointer { + if(!vars.count(n)) + vars[n] = gcroot_pointer(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 rt_expr; + gcroot_pointer rt_printer; + std::vector> 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(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; diff --git a/src/core/project.cpp b/src/core/project.cpp index 38cbaaf1..51a706e5 100644 --- a/src/core/project.cpp +++ b/src/core/project.cpp @@ -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) diff --git a/src/library/framebuffer.cpp b/src/library/framebuffer.cpp index 54f324bc..c136f196 100644 --- a/src/library/framebuffer.cpp +++ b/src/library/framebuffer.cpp @@ -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(_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(number).substr(2); + else + return "#" + hex::to(number); +} + void color::set_palette(unsigned rshift, unsigned gshift, unsigned bshift, bool X) throw() { if(X) { diff --git a/src/library/gc.cpp b/src/library/gc.cpp new file mode 100644 index 00000000..02413302 --- /dev/null +++ b/src/library/gc.cpp @@ -0,0 +1,58 @@ +#include "gc.hpp" +#include +#include +#include + +namespace +{ + std::set 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(); + } +} diff --git a/src/library/mathexpr-error.cpp b/src/library/mathexpr-error.cpp new file mode 100644 index 00000000..690d7113 --- /dev/null +++ b/src/library/mathexpr-error.cpp @@ -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"; + }; +} + diff --git a/src/library/mathexpr-format.cpp b/src/library/mathexpr-format.cpp new file mode 100644 index 00000000..dc79ed0f --- /dev/null +++ b/src/library/mathexpr-format.cpp @@ -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); +} diff --git a/src/library/mathexpr-ntype.cpp b/src/library/mathexpr-ntype.cpp new file mode 100644 index 00000000..c9ed3235 --- /dev/null +++ b/src/library/mathexpr-ntype.cpp @@ -0,0 +1,1148 @@ +#include "mathexpr-ntype.hpp" +#include "mathexpr-error.hpp" +#include "mathexpr-format.hpp" +#include "string.hpp" +#include +#include + +namespace +{ + void throw_domain(const std::string& err) + { + throw mathexpr_error(mathexpr_error::WDOMAIN, err); + } + + template int _cmp_values(T a, T b) + { + if(a < b) + return -1; + if(a > b) + return 1; + return 0; + } + + class expr_val; + + class expr_val_numeric + { + enum _type + { + T_UNSIGNED, + T_SIGNED, + T_FLOAT, + T_COMPLEX, + }; + public: + struct unsigned_tag {}; + struct signed_tag {}; + struct float_tag {}; + struct complex_tag {}; + expr_val_numeric() + { + type = T_SIGNED; + v_signed = 0; + v_imag = 0; + } + expr_val_numeric(unsigned_tag, uint64_t v) + { + type = T_UNSIGNED; + v_unsigned = v; + v_imag = 0; + } + expr_val_numeric(signed_tag, int64_t v) + { + type = T_SIGNED; + v_signed = v; + v_imag = 0; + } + expr_val_numeric(float_tag, double v) + { + type = T_FLOAT; + v_float = v; + v_imag = 0; + } + expr_val_numeric(complex_tag, double re, double im) + { + type = T_COMPLEX; + v_float = re; + v_imag = im; + } + expr_val_numeric(const std::string& str) + { + if(str == "i") { + type = T_COMPLEX; + v_float = 0; + v_imag = 1; + } else if(regex("[0-9]+|0x[0-9a-fA-F]+", str)) { + //UNSIGNED. + v_unsigned = parse_value(str); + v_imag = 0; + type = T_UNSIGNED; + } else if(regex("[+-][0-9]+|[+-]0x[0-9a-fA-F]+", str)) { + //SIGNED. + v_signed = parse_value(str); + v_imag = 0; + type = T_SIGNED; + } else if(regex("[+-]?([0-9]+|[0-9]*\\.[0-9]+)([eE][0-9]+)?", str)) { + //FLOAT. + v_float = parse_value(str); + v_imag = 0; + type = T_FLOAT; + } else + throw std::runtime_error("Bad number '" + str + "'"); + } + double as_float() const + { + switch(type) { + case T_UNSIGNED: return v_unsigned; + case T_SIGNED: return v_signed; + case T_FLOAT: return v_float; + case T_COMPLEX: return v_float; + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + int64_t as_signed() const + { + switch(type) { + case T_UNSIGNED: return v_unsigned; + case T_SIGNED: return v_signed; + case T_FLOAT: return v_float; + case T_COMPLEX: return v_float; + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + uint64_t as_unsigned() const + { + switch(type) { + case T_UNSIGNED: return v_unsigned; + case T_SIGNED: return v_signed; + case T_FLOAT: return v_float; + case T_COMPLEX: return v_float; + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + std::string tostring() + { + switch(type) { + case T_UNSIGNED: + return (stringfmt() << v_unsigned).str(); + case T_SIGNED: + return (stringfmt() << v_signed).str(); + case T_FLOAT: + //FIXME: Saner formatting. + return (stringfmt() << v_float).str(); + case T_COMPLEX: + //FIXME: Saner formatting. + if(v_imag < 0) + return (stringfmt() << v_float << v_imag << "*i").str(); + if(v_imag > 0) + return (stringfmt() << v_float << "+" << v_imag << "*i").str(); + return (stringfmt() << v_float).str(); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + uint64_t tounsigned() + { + return as_unsigned(); + } + int64_t tosigned() + { + return as_signed(); + } + void scale(uint64_t scale) + { + switch(type) { + case T_UNSIGNED: + type = T_FLOAT; + v_float = (1.0 * v_unsigned / scale); + break; + case T_SIGNED: + type = T_FLOAT; + v_float = (1.0 * v_signed / scale); + break; + case T_COMPLEX: + v_imag /= scale; + case T_FLOAT: + v_float /= scale; + break; + } + } + bool toboolean() + { + switch(type) { + case T_UNSIGNED: return (v_unsigned != 0); + case T_SIGNED: return (v_signed != 0); + case T_FLOAT: return (v_float != 0); + case T_COMPLEX: return (v_float != 0) || (v_imag != 0); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + std::string format(mathexpr_format fmt) + { + switch(type) { + case T_UNSIGNED: return math_format_unsigned(v_unsigned, fmt); + case T_SIGNED: return math_format_signed(v_signed, fmt); + case T_FLOAT: return math_format_float(v_float, fmt); + case T_COMPLEX: return math_format_complex(v_float, v_imag, fmt); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Don't know how to print numeric type"); + } + expr_val_numeric operator~() const + { + switch(type) { + case T_UNSIGNED: return expr_val_numeric(unsigned_tag(), ~v_unsigned); + case T_SIGNED: return expr_val_numeric(signed_tag(), ~v_signed); + case T_FLOAT: throw_domain("Bit operations are only for integers"); + case T_COMPLEX: throw_domain("Bit operations are only for integers"); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + expr_val_numeric operator&(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("Bit operations are only for integers"); + if(type == T_FLOAT || b.type == T_FLOAT) + throw_domain("Bit operations are only for integers"); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() & b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() & b.as_unsigned()); + } + expr_val_numeric operator|(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("Bit operations are only for integers"); + if(type == T_FLOAT || b.type == T_FLOAT) + throw_domain("Bit operations are only for integers"); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() | b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() | b.as_unsigned()); + } + expr_val_numeric operator^(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("Bit operations are only for integers"); + if(type == T_FLOAT || b.type == T_FLOAT) + throw_domain("Bit operations are only for integers"); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() ^ b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() ^ b.as_unsigned()); + } + expr_val_numeric operator-() const + { + switch(type) { + case T_COMPLEX: return expr_val_numeric(complex_tag(), -v_float, -v_imag); + case T_UNSIGNED: return expr_val_numeric(signed_tag(), -(int64_t)v_unsigned); + case T_SIGNED: return expr_val_numeric(signed_tag(), -v_signed); + case T_FLOAT: return expr_val_numeric(float_tag(), -v_float); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + expr_val_numeric operator+(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + return expr_val_numeric(complex_tag(), as_float() + b.as_float(), + v_imag + b.v_imag); + if(type == T_FLOAT || b.type == T_FLOAT) + return expr_val_numeric(float_tag(), as_float() + b.as_float()); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() + b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() + b.as_unsigned()); + } + expr_val_numeric operator-(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + return expr_val_numeric(complex_tag(), as_float() - b.as_float(), + v_imag - b.v_imag); + if(type == T_FLOAT || b.type == T_FLOAT) + return expr_val_numeric(float_tag(), as_float() - b.as_float()); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() - b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() - b.as_unsigned()); + } + expr_val_numeric operator*(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + return expr_val_numeric(complex_tag(), as_float() * b.as_float() - v_imag * b.v_imag, + as_float() * b.v_imag + b.as_float() * v_imag); + if(type == T_FLOAT || b.type == T_FLOAT) + return expr_val_numeric(float_tag(), as_float() * b.as_float()); + if(type == T_SIGNED || b.type == T_SIGNED) + return expr_val_numeric(signed_tag(), as_signed() * b.as_signed()); + return expr_val_numeric(unsigned_tag(), as_unsigned() * b.as_unsigned()); + } + expr_val_numeric operator/(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) { + double div = b.as_float() * b.as_float() + b.v_imag * b.v_imag; + if(div == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(complex_tag(), + (as_float() * b.as_float() + v_imag * b.v_imag) / div, + (v_imag * b.as_float() - as_float() * b.v_imag) / div); + } + if(type == T_FLOAT || b.type == T_FLOAT) { + if(b.as_float() == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(float_tag(), as_float() / b.as_float()); + } + if(type == T_SIGNED || b.type == T_SIGNED) { + if(b.as_signed() == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(signed_tag(), as_signed() / b.as_signed()); + } + if(b.as_unsigned() == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(unsigned_tag(), as_unsigned() / b.as_unsigned()); + } + expr_val_numeric operator%(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("Remainder is only for integers"); + if(type == T_FLOAT || b.type == T_FLOAT) + throw_domain("Remainder is only for integers"); + if(type == T_SIGNED || b.type == T_SIGNED) { + if(b.as_signed() == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(signed_tag(), as_signed() / b.as_signed()); + } + if(b.as_unsigned() == 0) + throw mathexpr_error(mathexpr_error::DIV_BY_0, "Division by 0"); + return expr_val_numeric(unsigned_tag(), as_unsigned() / b.as_unsigned()); + } + expr_val_numeric log() const + { + if(type == T_COMPLEX) { + double mag = as_float() * as_float() + v_imag * v_imag; + if(mag == 0) + throw mathexpr_error(mathexpr_error::LOG_BY_0, "Can't take logarithm of 0"); + double r = 0.5 * ::log(mag); + double i = ::atan2(v_imag, as_float()); + return expr_val_numeric(complex_tag(), r, i); + } + if(as_float() == 0) + throw mathexpr_error(mathexpr_error::LOG_BY_0, "Can't take logarithm of 0"); + if(as_float() <= 0) + return expr_val_numeric(complex_tag(), ::log(std::abs(as_float())), 4 * ::atan(1)); + return expr_val_numeric(float_tag(), ::log(as_float())); + } + static expr_val_numeric log2(expr_val_numeric a, expr_val_numeric b) + { + return b.log() / a.log(); + } + expr_val_numeric exp() const + { + if(type == T_COMPLEX) { + double mag = ::exp(as_float()); + return expr_val_numeric(complex_tag(), mag * ::cos(v_imag), + mag * ::sin(v_imag)); + } + return expr_val_numeric(float_tag(), ::exp(as_float())); + } + static expr_val_numeric exp2(expr_val_numeric a, expr_val_numeric b) + { + expr_val_numeric tmp = b * a.log(); + return tmp.exp(); + } + expr_val_numeric sqrt() const + { + if(as_float() < 0 && type != T_COMPLEX) + return expr_val_numeric(complex_tag(), 0, ::sqrt(-as_float())); + if(type == T_COMPLEX) { + double mag = ::sqrt(::sqrt(as_float() * as_float() + v_imag * v_imag)); + double ar = 0.5 * ::atan2(v_imag, as_float()); + return expr_val_numeric(complex_tag(), mag * ::cos(ar), mag * ::sin(ar)); + } + return expr_val_numeric(float_tag(), ::sqrt(as_float())); + } + expr_val_numeric sin() const + { + if(type == T_COMPLEX) { + return expr_val_numeric(complex_tag(), ::sin(as_float()) * ::cosh(v_imag), + ::cos(as_float()) * ::sinh(v_imag)); + } + return expr_val_numeric(float_tag(), ::sin(as_float())); + } + expr_val_numeric cos() const + { + if(type == T_COMPLEX) { + return expr_val_numeric(complex_tag(), ::cos(as_float()) * ::cosh(v_imag), + -::sin(as_float()) * ::sinh(v_imag)); + } + return expr_val_numeric(float_tag(), ::cos(as_float())); + } + expr_val_numeric tan() const + { + return sin()/cos(); + } + expr_val_numeric atan() const + { + if(type == T_COMPLEX) { + expr_val_numeric x = expr_val_numeric(complex_tag(), 0, 1) * *this; + expr_val_numeric n = expr_val_numeric(complex_tag(), 1, 0) + x; + expr_val_numeric d = expr_val_numeric(complex_tag(), 1, 0) - x; + expr_val_numeric y = n / d; + expr_val_numeric w = y.log(); + return w / expr_val_numeric(complex_tag(), 0, 2); + } + return expr_val_numeric(float_tag(), ::atan(as_float())); + } + expr_val_numeric acos() const + { + expr_val_numeric sinesqr = (expr_val_numeric(float_tag(), 1) - *this * *this); + expr_val_numeric sine = sinesqr.sqrt(); + expr_val_numeric tangent = sine / *this; + return tangent.atan(); + } + expr_val_numeric asin() const + { + expr_val_numeric cosinesqr = (expr_val_numeric(float_tag(), 1) - *this * *this); + expr_val_numeric cosine = cosinesqr.sqrt(); + expr_val_numeric tangent = *this / cosine; + return tangent.atan(); + } + expr_val_numeric sinh() const + { + return (exp() - (-*this).exp()) / expr_val_numeric(float_tag(), 2); + } + expr_val_numeric cosh() const + { + return (exp() + (-*this).exp()) / expr_val_numeric(float_tag(), 2); + } + expr_val_numeric tanh() const + { + return sinh() / cosh(); + } + expr_val_numeric arsinh() const + { + //x - 1/x = 2u + //x^2 - 2ux - 1 = 0 + //(x-u)^2 - x^2 + 2ux - u^2 + x^2 - 2ux - 1 = 0 + //(x-u)^2 = u^2 + 1 + expr_val_numeric xmu = (*this * *this) + expr_val_numeric(float_tag(), 1); + expr_val_numeric x = xmu.sqrt() + *this; + return x.log(); + } + expr_val_numeric arcosh() const + { + expr_val_numeric xmu = (*this * *this) - expr_val_numeric(float_tag(), 1); + expr_val_numeric x = xmu.sqrt() + *this; + return x.log(); + } + expr_val_numeric artanh() const + { + //(x-1/x)/(x+1/x)=u + //x^2=u+1/(1-u) + expr_val_numeric t(float_tag(), 1); + return ((t + *this) / (t - *this)).sqrt().log(); + } + static expr_val_numeric atan2(expr_val_numeric a, expr_val_numeric b) + { + if(a.type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("atan2 is only for reals"); + return expr_val_numeric(float_tag(), ::atan2(a.as_float(), b.as_float())); + } + expr_val_numeric torad() const + { + return expr_val_numeric(float_tag(), ::atan(1) / 45 * as_float()); + } + expr_val_numeric todeg() const + { + return expr_val_numeric(float_tag(), 45 / ::atan(1) * as_float()); + } + static expr_val_numeric shift(expr_val_numeric a, expr_val_numeric b, bool inv) + { + int64_t s = b.as_signed(); + if(inv) + s = -s; + if(a.type == T_SIGNED) { + int64_t _a = a.v_signed; + if(s < -63) + return expr_val_numeric(signed_tag(), -1); + if(s > 63) + return expr_val_numeric(signed_tag(), 0); + if(s < 0) { + uint64_t r = _a; + uint64_t r2 = r >> -s; + uint64_t m = 0xFFFFFFFFFFFFFFFFULL - ((1ULL << (64 - s)) - 1); + return expr_val_numeric(signed_tag(), m | r2); + } else if(s > 0) + return expr_val_numeric(signed_tag(), _a << s); + else + return expr_val_numeric(signed_tag(), _a); + } else if(a.type == T_UNSIGNED) { + uint64_t _a = a.v_unsigned; + if(s < -63 || s > 63) + return expr_val_numeric(unsigned_tag(), 0); + if(s < 0) + return expr_val_numeric(unsigned_tag(), _a >> -s); + else if(s > 0) + return expr_val_numeric(unsigned_tag(), _a << s); + else + return expr_val_numeric(unsigned_tag(), _a); + } else + throw_domain("Bit operations are only for integers"); + } + static expr_val_numeric op_pi() + { + return expr_val_numeric(float_tag(), 4 * ::atan(1)); + } + static expr_val_numeric op_e() + { + return expr_val_numeric(float_tag(), ::exp(1)); + } + bool operator==(const expr_val_numeric& b) const + { + if(type == T_COMPLEX || b.type == T_COMPLEX) + return as_float() == b.as_float() && v_imag == b.v_imag; + return (_cmp(*this, b) == 0); + } + static int _cmp_float_unsigned(uint64_t a, float b) + { + if(b < 0) + return 1; + //TODO: Handle values too large for exact integer representation. + if((double)a < b) + return -1; + if((double)a > b) + return 1; + return 0; + } + static int _cmp_float_signed(int64_t a, float b) + { + //TODO: Handle values too large for exact integer representation. + if((double)a < b) + return -1; + if((double)a > b) + return 1; + return 0; + } + static int _cmp(expr_val_numeric a, expr_val_numeric b) + { + if(a.type == T_COMPLEX || b.type == T_COMPLEX) + throw_domain("Can't compare complex numbers"); + switch(a.type) { + case T_UNSIGNED: + switch(b.type) { + case T_UNSIGNED: + return _cmp_values(a.v_unsigned, b.v_unsigned); + case T_SIGNED: + if(b.v_signed < 0) + return 1; + if((int64_t)a.v_unsigned < 0) + return 1; + return _cmp_values((int64_t)a.v_unsigned, b.v_signed); + case T_FLOAT: + return _cmp_float_unsigned(a.v_unsigned, b.v_float); + case T_COMPLEX: + throw mathexpr_error(mathexpr_error::INTERNAL, + "Internal error (shouldn't be here)"); + } + case T_SIGNED: + switch(b.type) { + case T_UNSIGNED: + if(a.v_signed < 0) + return -1; + if((int64_t)b.v_unsigned < 0) + return -1; + return _cmp_values(a.v_signed, (int64_t)b.v_unsigned); + case T_SIGNED: + return _cmp_values(a.v_signed, b.v_signed); + case T_FLOAT: + return _cmp_float_signed(a.v_signed, b.v_float); + case T_COMPLEX: + throw mathexpr_error(mathexpr_error::INTERNAL, + "Internal error (shouldn't be here)"); + } + case T_FLOAT: + switch(b.type) { + case T_UNSIGNED: + return -_cmp_float_unsigned(b.v_unsigned, a.v_float); + case T_SIGNED: + return -_cmp_float_signed(b.v_signed, a.v_float); + case T_FLOAT: + if(a.v_float < b.v_float) + return -1; + if(a.v_float > b.v_float) + return 1; + return 0; + case T_COMPLEX: + throw mathexpr_error(mathexpr_error::INTERNAL, + "Internal error (shouldn't be here)"); + } + case T_COMPLEX: + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + static expr_val_numeric x_unsigned(expr_val_numeric a) + { + switch(a.type) { + case T_UNSIGNED: return expr_val_numeric(unsigned_tag(), a.v_unsigned); + case T_SIGNED: return expr_val_numeric(unsigned_tag(), a.v_signed); + case T_FLOAT: return expr_val_numeric(unsigned_tag(), a.v_float); + default: throw_domain("Can't convert non-real into unsigned"); + } + } + static expr_val_numeric x_signed(expr_val_numeric a) + { + switch(a.type) { + case T_UNSIGNED: return expr_val_numeric(signed_tag(), a.v_unsigned); + case T_SIGNED: return expr_val_numeric(signed_tag(), a.v_signed); + case T_FLOAT: return expr_val_numeric(signed_tag(), a.v_float); + default: throw_domain("Can't convert non-real into signed"); + } + } + static expr_val_numeric x_float(expr_val_numeric a) + { + switch(a.type) { + case T_UNSIGNED: return expr_val_numeric(float_tag(), a.v_unsigned); + case T_SIGNED: return expr_val_numeric(float_tag(), a.v_signed); + case T_FLOAT: return expr_val_numeric(float_tag(), a.v_float); + default: throw_domain("Can't convert non-real into float"); + } + } + expr_val_numeric re() const + { + if(type == T_COMPLEX) + return expr_val_numeric(float_tag(), v_float); + return expr_val_numeric(float_tag(), as_float()); + } + expr_val_numeric im() const + { + if(type == T_COMPLEX) + return expr_val_numeric(float_tag(), v_imag); + return expr_val_numeric(float_tag(), 0); + } + expr_val_numeric conj() const + { + if(type == T_COMPLEX) + return expr_val_numeric(complex_tag(), v_float, -v_imag); + return expr_val_numeric(float_tag(), as_float()); + } + expr_val_numeric abs() const + { + switch(type) { + case T_COMPLEX: return expr_val_numeric(float_tag(), v_float * v_float + v_imag * v_imag); + case T_FLOAT: return expr_val_numeric(float_tag(), ::fabs(v_float)); + case T_SIGNED: return expr_val_numeric(signed_tag(), ::abs(v_signed)); + case T_UNSIGNED: return expr_val_numeric(unsigned_tag(), v_unsigned); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + expr_val_numeric arg() const + { + switch(type) { + case T_COMPLEX: return expr_val_numeric(float_tag(), ::atan2(v_imag, v_float)); + default: + if(as_float() < 0) + return expr_val_numeric(float_tag(), 4 * ::atan(1)); + else + return expr_val_numeric(float_tag(), 0); + } + } + private: + enum _type type; + union { + uint64_t v_unsigned; + int64_t v_signed; + double v_float; + }; + double v_imag; + }; + + class expr_val + { + struct boolean_tag {}; + struct number_tag {}; + struct string_tag {}; + enum _type + { + T_BOOLEAN, + T_NUMERIC, + T_STRING, + }; + expr_val_numeric& as_numeric() + { + if(type != T_NUMERIC) + throw_domain("Can't operate with non-numbers"); + return v_numeric; + } + public: + expr_val() + { + type = T_NUMERIC; + } + expr_val(const std::string& str, bool string) + { + if(string) { + v_string = str; + type = T_STRING; + } else if(str == "false") { + v_boolean = false; + type = T_BOOLEAN; + } else if(str == "true") { + v_boolean = true; + type = T_BOOLEAN; + } else if(str == "e") { + v_numeric = expr_val_numeric::op_e(); + type = T_NUMERIC; + } else if(str == "pi") { + v_numeric = expr_val_numeric::op_pi(); + type = T_NUMERIC; + } else { + v_numeric = expr_val_numeric(str); + type = T_NUMERIC; + } + } + expr_val(mathexpr_typeinfo_wrapper::unsigned_tag t, uint64_t v) + : type(T_NUMERIC), v_numeric(expr_val_numeric::unsigned_tag(), v) + { + } + expr_val(mathexpr_typeinfo_wrapper::signed_tag t, int64_t v) + : type(T_NUMERIC), v_numeric(expr_val_numeric::signed_tag(), v) + { + } + expr_val(mathexpr_typeinfo_wrapper::float_tag t, double v) + : type(T_NUMERIC), v_numeric(expr_val_numeric::float_tag(), v) + { + } + expr_val(boolean_tag, bool b) + : type(T_BOOLEAN), v_boolean(b) + { + } + expr_val(expr_val_numeric v) + : type(T_NUMERIC), v_numeric(v) + { + } + expr_val(string_tag, std::string s) + : type(T_STRING), v_string(s) + { + } + std::string tostring() + { + switch(type) { + case T_BOOLEAN: + if(v_boolean) + return "true"; + else + return "false"; + case T_NUMERIC: + return v_numeric.tostring(); + case T_STRING: + return v_string; + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + uint64_t tounsigned() + { + if(type != T_NUMERIC) + throw_domain("Can't convert non-number into unsigned"); + return v_numeric.tounsigned(); + } + int64_t tosigned() + { + if(type != T_NUMERIC) + throw_domain("Can't convert non-number into signed"); + return v_numeric.tosigned(); + } + void scale(uint64_t _scale) + { + if(type != T_NUMERIC) + throw_domain("Can't scale non-number"); + v_numeric.scale(_scale); + } + bool toboolean() + { + switch(type) { + case T_BOOLEAN: + return v_boolean; + case T_NUMERIC: + return v_numeric.toboolean(); + case T_STRING: + return (v_string.length() != 0); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + static expr_val op_lnot(std::vector> promises) + { + if(promises.size() != 1) + throw mathexpr_error(mathexpr_error::ARGCOUNT, "logical not takes 1 argument"); + return expr_val(boolean_tag(), !(promises[0]().toboolean())); + } + static expr_val op_lor(std::vector> promises) + { + if(promises.size() != 2) + throw mathexpr_error(mathexpr_error::ARGCOUNT, "logical or takes 2 arguments"); + if(promises[0]().toboolean()) + return expr_val(boolean_tag(), true); + return expr_val(boolean_tag(), promises[1]().toboolean()); + } + static expr_val op_land(std::vector> promises) + { + if(promises.size() != 2) + throw mathexpr_error(mathexpr_error::ARGCOUNT, "logical and takes 2 arguments"); + if(!(promises[0]().toboolean())) + return expr_val(boolean_tag(), false); + return expr_val(boolean_tag(), promises[1]().toboolean()); + } + static expr_val fun_if(std::vector> promises) + { + if(promises.size() == 2) { + if((promises[0]().toboolean())) + return promises[1](); + else + return expr_val(boolean_tag(), false); + } else if(promises.size() == 3) { + if((promises[0]().toboolean())) + return promises[1](); + else + return promises[2](); + } else + throw mathexpr_error(mathexpr_error::ARGCOUNT, "if takes 2 or 3 arguments"); + } + static expr_val fun_select(std::vector> promises) + { + for(auto& i : promises) { + expr_val v = i(); + if(v.type != T_BOOLEAN || v.v_boolean) + return v; + } + return expr_val(boolean_tag(), false); + } + static expr_val fun_pyth(std::vector> promises) + { + std::vector v; + for(auto& i : promises) + v.push_back(i()); + expr_val_numeric n(expr_val_numeric::float_tag(), 0); + expr_val_numeric one(expr_val_numeric::float_tag(), 1); + for(auto& i : v) { + if(i.type != T_NUMERIC) + throw mathexpr_error(mathexpr_error::WDOMAIN, "pyth requires numeric args"); + n = n + one * i.v_numeric * i.v_numeric; + } + return n.sqrt(); + } + template + static expr_val fun_fold(std::vector> promises) + { + if(!promises.size()) + return expr_val(boolean_tag(), false); + expr_val v = promises[0](); + for(size_t i = 1; i < promises.size(); i++) + v = T(v, promises[i]()); + return v; + } + static expr_val fold_min(expr_val& a, expr_val& b) + { + int t = _cmp_values(a.type, b.type); + if(t < 0) + return a; + if(t > 0) + return b; + return (_cmp(a, b) < 0) ? a : b; + } + static expr_val fold_max(expr_val& a, expr_val& b) + { + int t = _cmp_values(a.type, b.type); + if(t < 0) + return a; + if(t > 0) + return b; + return (_cmp(a, b) > 0) ? a : b; + } + static expr_val fold_sum(expr_val& a, expr_val& b) + { + return add(a, b); + } + static expr_val fold_prod(expr_val& a, expr_val& b) + { + return mul(a, b); + } + template + static expr_val op_binary(std::vector> promises) + { + if(promises.size() != 2) + throw mathexpr_error(mathexpr_error::ARGCOUNT, "Operation takes 2 arguments"); + expr_val a = promises[0](); + expr_val b = promises[1](); + return T(a, b); + } + template + static expr_val op_unary(std::vector> promises) + { + if(promises.size() != 1) + throw mathexpr_error(mathexpr_error::ARGCOUNT, "Operation takes 1 argument"); + expr_val a = promises[0](); + return T(a); + } + template + static expr_val op_unary_binary(std::vector> promises) + { + if(promises.size() == 1) + return T(promises[0]()); + if(promises.size() == 2) + return U(promises[0](), promises[1]()); + throw mathexpr_error(mathexpr_error::ARGCOUNT, "Operation takes 1 or 2 arguments"); + } + static expr_val bnot(expr_val a) + { + return ~a.as_numeric(); + } + static expr_val band(expr_val a, expr_val b) + { + return a.as_numeric() & b.as_numeric(); + } + static expr_val bor(expr_val a, expr_val b) + { + return a.as_numeric() | b.as_numeric(); + } + static expr_val bxor(expr_val a, expr_val b) + { + return a.as_numeric() ^ b.as_numeric(); + } + static expr_val neg(expr_val a) + { + return -a.as_numeric(); + } + template + static expr_val f_n_fn(expr_val a) + { + return (a.as_numeric().*T)(); + } + template + static expr_val f_n_fn2(expr_val a, expr_val b) + { + return T(a.as_numeric(), b.as_numeric()); + } + static expr_val lshift(expr_val a, expr_val b) + { + return expr_val_numeric::shift(a.as_numeric(), b.as_numeric(), false); + } + static expr_val rshift(expr_val a, expr_val b) + { + return expr_val_numeric::shift(a.as_numeric(), b.as_numeric(), true); + } + static expr_val op_pi(std::vector> promises) + { + return expr_val_numeric::op_pi(); + } + static expr_val add(expr_val a, expr_val b) + { + if(a.type == T_STRING && b.type == T_STRING) + return expr_val(string_tag(), a.v_string + b.v_string); + return a.as_numeric() + b.as_numeric(); + } + static expr_val sub(expr_val a, expr_val b) + { + return a.as_numeric() - b.as_numeric(); + } + static expr_val mul(expr_val a, expr_val b) + { + return a.as_numeric() * b.as_numeric(); + } + static expr_val div(expr_val a, expr_val b) + { + return a.as_numeric() / b.as_numeric(); + } + static expr_val rem(expr_val a, expr_val b) + { + return a.as_numeric() % b.as_numeric(); + } + static bool _eq(const expr_val& a, const expr_val& b) + { + if(a.type != b.type) + return false; + switch(a.type) { + case T_BOOLEAN: return (a.v_boolean == b.v_boolean); + case T_STRING: return (a.v_string == b.v_string); + case T_NUMERIC: return (a.v_numeric == b.v_numeric); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + static expr_val eq(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), _eq(a, b)); + } + static expr_val ne(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), !_eq(a, b)); + } + static int _cmp(expr_val a, expr_val b) + { + if(a.type != b.type) + throw_domain("Can't compare distinct value types"); + switch(a.type) { + case T_BOOLEAN: return _cmp_values(a.v_boolean, b.v_boolean); + case T_STRING: return _cmp_values(a.v_string, b.v_string); + case T_NUMERIC: return expr_val_numeric::_cmp(a.v_numeric, b.v_numeric); + } + throw mathexpr_error(mathexpr_error::INTERNAL, "Internal error (shouldn't be here)"); + } + template + static expr_val x_nconv(expr_val a) + { + return T(a.as_numeric()); + } + static expr_val lt(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), _cmp(a, b) < 0); + } + static expr_val le(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), _cmp(a, b) <= 0); + } + static expr_val ge(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), _cmp(a, b) >= 0); + } + static expr_val gt(expr_val a, expr_val b) + { + return expr_val(boolean_tag(), _cmp(a, b) > 0); + } + std::string format_string(std::string val, mathexpr_format fmt) + { + if((int)val.length() > fmt.precision && fmt.precision >= 0) + val = val.substr(0, fmt.precision); + while((int)val.length() < fmt.width) + val = " " + val; + return val; + } + std::string print_bool_numeric(bool val, mathexpr_format fmt) + { + std::string out = val ? "1" : "0"; + if(fmt.precision > 0) { + out += "."; + for(int i = 0; i < fmt.precision; i++) + out += "0"; + } + while((int)out.length() < fmt.width) + out = ((fmt.fillzeros) ? "0" : " ") + out; + return out; + } + std::string format(mathexpr_format fmt) + { + switch(type) { + case T_BOOLEAN: return math_format_bool(v_boolean, fmt); + case T_NUMERIC: return v_numeric.format(fmt); + case T_STRING: return math_format_string(v_string, fmt); + default: + return "#Notprintable"; + } + } + static std::set operations() + { + static mathexpr_operinfo_set x({ + {"-", expr_val::op_unary, true, 1, -3, true}, + {"!", expr_val::op_lnot, true, 1, -3, true}, + {"~", expr_val::op_unary, true, 1, -3, true}, + {"*", expr_val::op_binary, true, 2, -5, false}, + {"/", expr_val::op_binary, true, 2, -5, false}, + {"%", expr_val::op_binary, true, 2, -5, false}, + {"+", expr_val::op_binary, true, 2, -6, false}, + {"-", expr_val::op_binary, true, 2, -6, false}, + {"<<", expr_val::op_binary, true, 2, -7, false}, + {">>", expr_val::op_binary, true, 2, -7, false}, + {"<", expr_val::op_binary, true, 2, -8, false}, + {"<=", expr_val::op_binary, true, 2, -8, false}, + {">", expr_val::op_binary, true, 2, -8, false}, + {">=", expr_val::op_binary, true, 2, -8, false}, + {"==", expr_val::op_binary, true, 2, -9, false}, + {"!=", expr_val::op_binary, true, 2, -9, false}, + {"&", expr_val::op_binary, true, 2, -10, false}, + {"^", expr_val::op_binary, true, 2, -11, false}, + {"|", expr_val::op_binary, true, 2, -12, false}, + {"&&", expr_val::op_land, true, 2, -13, false}, + {"||", expr_val::op_lor, true, 2, -14, false}, + {"π", expr_val::op_pi, true, 0, 0, false}, + {"if", expr_val::fun_if}, + {"select", expr_val::fun_select}, + {"unsigned", expr_val::op_unary>}, + {"signed", expr_val::op_unary>}, + {"float", expr_val::op_unary>}, + {"min", expr_val::fun_fold}, + {"max", expr_val::fun_fold}, + {"sum", expr_val::fun_fold}, + {"prod", expr_val::fun_fold}, + {"sqrt", expr_val::op_unary>}, + {"log", expr_val::op_unary_binary, + expr_val::f_n_fn2>}, + {"exp", expr_val::op_unary_binary, + expr_val::f_n_fn2>}, + {"sin", expr_val::op_unary>}, + {"cos", expr_val::op_unary>}, + {"tan", expr_val::op_unary>}, + {"atan", expr_val::op_unary_binary, + expr_val::f_n_fn2>}, + {"asin", expr_val::op_unary>}, + {"acos", expr_val::op_unary>}, + {"sinh", expr_val::op_unary>}, + {"cosh", expr_val::op_unary>}, + {"tanh", expr_val::op_unary>}, + {"artanh", expr_val::op_unary>}, + {"arsinh", expr_val::op_unary>}, + {"arcosh", expr_val::op_unary>}, + {"torad", expr_val::op_unary>}, + {"todeg", expr_val::op_unary>}, + {"re", expr_val::op_unary>}, + {"im", expr_val::op_unary>}, + {"conj", expr_val::op_unary>}, + {"abs", expr_val::op_unary>}, + {"arg", expr_val::op_unary>}, + {"pyth", expr_val::fun_pyth}, + }); + return x.get_set(); + } + private: + _type type; + bool v_boolean; + expr_val_numeric v_numeric; + std::string v_string; + }; +} + +struct mathexpr_typeinfo* expression_value() +{ + static mathexpr_typeinfo_wrapper expession_value_obj; + return &expession_value_obj; +} + +#ifdef TEST_MATHEXPR +int main2(int argc, char** argv) +{ + std::map> vars; + std::function(const std::string&)> vars_fn = [&vars](const std::string& n) -> + gcroot_pointer { + if(!vars.count(n)) + vars.insert(std::make_pair(n, gcroot_pointer(expression_value()))); + gcroot_pointer& tmp = vars.find(n)->second; + return tmp; + }; + 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[2], vars_fn); + } + garbage_collectable::do_gc(); + garbage_collectable::do_gc(); + for(auto i : vars) { + try { + auto v = i.second->evaluate(); + std::cout << i.first << " --> " << v.type->tostring(v.value) << std::endl; + } catch(std::exception& e) { + std::cout << i.first << " --> " << e.what() << std::endl; + } + } + return 0; +} + +int main(int argc, char** argv) +{ + int r = main2(argc, argv); + garbage_collectable::do_gc(); + return r; +} + + +#endif diff --git a/src/library/mathexpr.cpp b/src/library/mathexpr.cpp new file mode 100644 index 00000000..42323a55 --- /dev/null +++ b/src/library/mathexpr.cpp @@ -0,0 +1,625 @@ +#include "mathexpr.hpp" +#include "string.hpp" +#include +#include + +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 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> _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 _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> 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& 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& 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& 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& 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 e) : expr(e), typei(NULL) {} + expr_or_op(std::string o) : op(o), typei(NULL) {} + gcroot_pointer expr; + std::string op; + mathexpr_operinfo* typei; + }; + + gcroot_pointer parse_rec(mathexpr_typeinfo& _type, std::vector& operands, + size_t first, size_t last) + { + if(operands.empty()) + return gcroot_pointer(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> args; + args.push_back(parse_rec(_type, operands, first + 1, j + 1)); + return gcroot_pointer(gcroot_pointer_object_tag(), &_type, + operands[best].typei, args); + } else { + //Binary operator. + std::vector> args; + args.push_back(parse_rec(_type, operands, first, best)); + args.push_back(parse_rec(_type, operands, best + 1, last)); + return gcroot_pointer(gcroot_pointer_object_tag(), &_type, + operands[best].typei, args); + } + } + return operands[first].expr; + } + + gcroot_pointer parse_rec(mathexpr_typeinfo& _type, std::vector& ss, + std::set& operations, + std::function(const std::string&)> vars, size_t first, size_t last) + { + operations_set opset(operations); + std::vector operands; + std::vector> 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(gcroot_pointer_object_tag(), &_type, + ss[i].string, false)); + break; + case TT_STRING: + operands.push_back(gcroot_pointer(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(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(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(gcroot_pointer_object_tag(), &_type, fn, + std::vector>()); + } + } + //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& operations, + std::vector& 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::parse(mathexpr_typeinfo& _type, const std::string& expr, + std::function(const std::string&)> vars) +{ + if(expr == "") + throw std::runtime_error("Empty expression"); + auto operations = _type.operations(); + std::vector tokenization; + tokenize(expr, operations, tokenization); + return parse_rec(_type, tokenization, operations, vars, 0, tokenization.size()); +} diff --git a/src/library/memorywatch-fb.cpp b/src/library/memorywatch-fb.cpp new file mode 100644 index 00000000..553979d6 --- /dev/null +++ b/src/library/memorywatch-fb.cpp @@ -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& scr) throw(); + void operator()(struct framebuffer::fb& scr) throw(); + void clone(framebuffer::queue& q) const throw(std::bad_alloc); + private: + template void draw(struct framebuffer::fb& 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& scr) throw() { draw(scr); } + void memorywatch_fb_object::operator()(struct framebuffer::fb& scr) throw() { draw(scr); } + + void memorywatch_fb_object::clone(framebuffer::queue& q) const throw(std::bad_alloc) { q.clone_helper(this); } + + template void memorywatch_fb_object::draw(struct framebuffer::fb& 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 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(p, val); + } catch(...) { + } +} + +void memorywatch_output_fb::reset() +{ + enabled->reset(); + pos_x->reset(); + pos_y->reset(); +} diff --git a/src/library/memorywatch-list.cpp b/src/library/memorywatch-list.cpp new file mode 100644 index 00000000..caf55997 --- /dev/null +++ b/src/library/memorywatch-list.cpp @@ -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 _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() +{ +} diff --git a/src/library/memorywatch-null.cpp b/src/library/memorywatch-null.cpp new file mode 100644 index 00000000..328024c1 --- /dev/null +++ b/src/library/memorywatch-null.cpp @@ -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() +{ +} diff --git a/src/library/memorywatch.cpp b/src/library/memorywatch.cpp new file mode 100644 index 00000000..035506a6 --- /dev/null +++ b/src/library/memorywatch.cpp @@ -0,0 +1,361 @@ +#include "memorywatch.hpp" +#include "int24.hpp" +#include "mathexpr-error.hpp" +#include +#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> 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(r[2]); + } catch(...) {} + if(r[4] != "") + try { + fmt.precision = parse_value(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 memorywatch_set::set() +{ + std::set 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 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 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 printer(new stdout_item_printer); + std::function(const std::string&)> vars_fn = [&mset] + (const std::string& n) -> gcroot_pointer { + 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 + diff --git a/src/lua/memory.cpp b/src/lua/memory.cpp index 00688e85..fc425b91 100644 --- a/src/lua/memory.cpp +++ b/src/lua/memory.cpp @@ -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 int memory_scattergather(lua::state& L, const std::string& fname) { uint64_t val = 0; diff --git a/src/platform/wxwidgets/editor-hexedit.cpp b/src/platform/wxwidgets/editor-hexedit.cpp index 37cd64fd..1533a5e9 100644 --- a/src/platform/wxwidgets/editor-hexedit.cpp +++ b/src/platform/wxwidgets/editor-hexedit.cpp @@ -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) { } } diff --git a/src/platform/wxwidgets/editor-memorywatch.cpp b/src/platform/wxwidgets/editor-memorywatch.cpp index 5cdb07d0..6a07a433 100644 --- a/src/platform/wxwidgets/editor-memorywatch.cpp +++ b/src/platform/wxwidgets/editor-memorywatch.cpp @@ -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 +#include #include #include #include #include +#include #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 label_control + { + public: + label_control() + { + lbl = NULL; + ctrl = NULL; + } + template 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 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 type; + label_control expr; + label_control format; + label_control endianess; + label_control scale; + label_control vma; + label_control addrbase; + label_control addrsize; + label_control position; + wxCheckBox* cond_enable; + wxTextCtrl* enabled; + label_control xpos; + label_control ypos; + wxCheckBox* alt_origin_x; + wxCheckBox* alt_origin_y; + wxCheckBox* cliprange_x; + wxCheckBox* cliprange_y; + label_control font; + wxButton* font_sel; + label_control fg_color; + label_control bg_color; + label_control halo_color; + wxButton* ok; + wxButton* cancel; + std::string name; + std::string old_addrbase; + std::string old_addrsize; + bool was_free; + std::map> 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(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(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(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(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(this, "Base:", wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(100, -1)); + addrbase.add(s5, true); + addrsize = label_control(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(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(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(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(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(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(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(this, "Foreground:", wxID_ANY, wxT("#FFFFFF"), + wxDefaultPosition, wxSize(100, -1)); + fg_color.add(s11, true); + bg_color = label_control(this, "Background:", wxID_ANY, wxT("transparent"), wxDefaultPosition, + wxSize(100, -1)); + bg_color.add(s11, true); + halo_color = label_control(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(it.addr_base))); + addrsize->SetValue(towxstring(hex::to(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(tostdstring(addrbase->GetValue())); + it.addr_size = hex::from(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 bind; + std::set bind; runemufn([&bind]() { - std::set 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(...) { } diff --git a/src/platform/wxwidgets/loadsave.cpp b/src/platform/wxwidgets/loadsave.cpp index a1b7b935..436e147d 100644 --- a/src/platform/wxwidgets/loadsave.cpp +++ b/src/platform/wxwidgets/loadsave.cpp @@ -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"); diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index 3fdd69b5..9eb140e3 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -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 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 old_watches; - runemufn([&old_watches]() { old_watches = get_watches(); }); + runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); }); std::map new_watches; std::string filename = choose_file_load(this, "Choose memory watch file", project_otherpath(), filetype_watch); diff --git a/src/platform/wxwidgets/memorysearch.cpp b/src/platform/wxwidgets/memorysearch.cpp index 0cadbf1a..e42ca61c 100644 --- a/src/platform/wxwidgets/memorysearch.cpp +++ b/src/platform/wxwidgets/memorysearch.cpp @@ -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) { } }