commit 8bec1dda934fa75cbb1402c58cb879b23305dc40 Author: tpearson Date: Sat Jan 9 06:41:55 2010 +0000 Add abakus git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/abakus@1071969 283d02a7-25f6-0310-bc7c-ecb5cbfe19da diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0b732ef --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Michael Pyne diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..905537c --- /dev/null +++ b/COPYING @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..f04797d --- /dev/null +++ b/README @@ -0,0 +1,183 @@ +abakus, by Michael Pyne +Version: 0.91 + +This is my attempt at creating a light-calculator based on Roberto Alsina's +initial usability suggestions, and based on the ideas of a few other KDE +hackers. + +This software is distributed under the terms of the GNU GPL v2. + +Synopsis: + $ tar xvjf abakus-0.91.tar.bz2 + $ cd abakus-0.91 + $ ./configure && make && make install + $ abakus + +Type away, and press Enter to see the result. + +Changes since 0.90: +* Add ability to remove all functions and variables to the context menus of + their respective list boxes. +* Convert out-of-range numbers to NaN. +* Accept "," as a decimal separator for the benefit of European users. +* Use correct decimal separator (per KLocale settings) in output. +* For long results, show the beginning instead of the end of the result, + since the beginning is the most significant part of the result. + +Changes since 0.85: +* You now have the option of using the GNU Multiple Precision library for + high-precision mathematics. It requires the MPFR library to also be + installed (normally comes with GNU MP 4.x). It is used automatically if + detected. +* Jes Hall has contributed DocBook documentation to make Abakus integrate into + KDE even more tightly. Thanks, Jes! +* User defined functions can now be defined over. +* Error handling with deriv() function improved. +* Ariya Hidayat's name was misspelled everywhere in Abakus. Sorry, Ariya. :( +* Speaking of Ariya, Abakus now uses the impressive user interface code from + his SpeedCrunch calculator (http://speedcrunch.berlios.de/). This includes + the Calc-as-you-Type tooltip, the function and variable dropdown, and + syntax highlighting. It's not configurable at this point, expect that in + the next release. +* You can use the F6 key to select the expression editor if you're a big fan + of the keyboard. +* Raising negative numbers to integral powers should work with the internal + high-precision library now. +* You can no longer deselect the current precision menu item. +* Fix crash bug when a user-defined function refers to another user-defined + function, and then you remove or edit the function it referred to. +* Add exact numerical derivatives for all functions supported. +* Added the asinh, acosh, and atanh functions. +* Fixed bug with loading of prior compact mode status. +* Fixed bug where result text had wrong precision when you changed the + precision and tried drag-and-drop. +* Drag-and-drop improvements. +* Fixed bug where Custom Precision menu entry was checked even if you canceled + the dialog. +* Made hyperbolic functions consistently ignore trigonometric mode. (Both + Degrees and Radians make no sense for hyperbolic trig). +* Whew! :) + +v0.85 adds a lot: +* Improvements to the configure script. Since I didn't end up using libCLN it + was mostly for naught, but the changes will be useful for the future. +* abakus now uses the high-precision math routines from Ariya Hidayat's + SpeedCrunch program. Thanks, Ariya! +* High precision arithmetic can have between 0 and 75 digits of precision. +* Support for approximate derivatives. For most functions the derivatives will + be numerically accurate. For those functions where I didn't feel like typing + in the exact form of the derivative an approximation is used instead. +* Implicit multiplication has been added to the parser. That means you can + type stuff like "3 sin pi" without having to manually add the * in between + 3 and sin. This also works with numbers and variables, and numbers and + parenthesized expressions. +* GUI changes. The main result view now uses KListView, so it gains tooltip + support for long answers for free, along with a bevy of other improvements. +* You can right-click on an answer and copy it to the clipboard. +* Corrected information in the about box. +* Restarting abakus with compact mode enabled should is much improved. + +v0.80.2 fixed an issue with the configure script for people who don't have +exactly the same version of Python I do, and forcibly prevents flex/bison +errors. + +v0.80.1 fixed an issue with the configure script for people who don't already +have scons installed. + +Major changes since 0.76: +* There is no more C code to interface between the parser and program. +* RPN mode is improved. Now the stack is retained between calls, and there + are a few commands only in RPN mode: + 1. pop - Return the top of the stack. + 2. clear - Clear the stack. +* bksys is used instead of the custom Makefiles. +* Lots of code cleanups, including license headers. +* The nifty drag-and-drop image looks more rectangular, and is used with the + two listviews on the right as well. +* Improved error checking, with messages that should hopefully be more + descriptive. + +Major changes since 0.75: +* Reorder internal macro so that functions are declared *before* they're + referenced, which helps build the program on systems with math.h files that + don't export the long double version of their math functions. +* Hitting a number or letter key right after evaluating an expression in RPN + mode automatically prepends the 'ans' variable, which was a feature of the + Normal mode. + +Major changes since 0.70: +* Build system switched (somehow) to using qmake. The parser and lexer are + still included, so bison and flex are still not required. Hopefully this + will improve the ease of building. Of course, this means no more colored + make output. +* Changed most of the keyboard shortcuts to use Shift + Alt + foo instead of + Alt + foo since that was interfering with the menu bar. +* RPN mode!! If you enable RPN mode, then your expressions will be evaluated + using the Reverse Polish Notation popular with users of HP calculators. Note + that although you can use values and functions while in RPN mode, you cannot + set or remove them from the expression editor like you can in normal mode. +* abakus will display a small token starting with a dollar sign ($) in italics + next to results. You can use these tokens to quickly reference a result in + your expression. The most recent result is always $0, with the number + increasing from most recent to least recent result. For example, typing + 2 3 $0 ^ $1 would give a result of 9. +* You can right click on functions and values in the list views to remove them + from the GUI. +* Changed the result items to use word wrapping when needed to fit all the + text. +* Very small DCOP interface. +* More code cleanup. +* Added a .desktop file. +* Test client removed again. +* Double-clicking on an error message (or OK message) no longer inserts them + into the edit box. + +Major changes since 0.61: +* User defined Functions. +* Save state of program between runs. +* Miscellaneous fun stuff. + +Currently implemented features: +* Parser built using flex and bison. The generated files are included so it + should compile fine for you. +* Fully C++. The parser and lexer code require C++ to compile. +* Supports several built-in functions: + - sin, cos, tan, sinh, cosh, tanh, asin, acos, atan in either radian or + degree mode. + abs, exp (e raised to the given power), ln, log (base 10), + sqrt, ceil, floor +* Supported operators: +, -, *, /, ^ (or **). +* Includes a window showing the values and user-defined functions you have. +* Predefined constants: pi, and e (Euler' constant). +* You can assign to variables by using an expression of the form: + identifier = expression. You can then reuse these variables later. +* You can create user-defined functions of one variable using the syntax + set foo(var) = , where calculates the value in terms of var. +* You can delete user-defined variables by doing: remove var +* You can delete user-defined functions by doing: remove foo(). Notice that + the variable is NOT included in that expression. +* Functions and variables are saved on exit, and then loaded when abakus is + started again. +* The ans variable contains the result of the last computation. +* Pressing +, -, *, or / immediately after your last computation automatically + inserts ans for you, saving you typing. +* A compact mode for the program. +* Operator precedence should be correct, including the right association of + the power operator. So, 2 ^ 3 ^ 2 == 512, just as it does when you write + it out. You can use parentheses to force precedence. +* Parentheses are not required around functions. So, sin 3 is a valid + expression. Note that sin 3 + cos 4 translates internally as (sin 3) + + (cos 4), not as sin (3 + cos (4)). +* I took some pains to try to make things like 3 + -2 work right. +* inf and nan are accepted as numeric input for completeness. +* abakus will automatically add ) characters to the end of the expression as + needed to balance your expression. This means that expressions like + sin (cos (2 + 3 will evaluate with no error. +* A rudimentary RPN mode is included. Most everything works, except for + derivatives and creating functions or new variables. + +Bugs: +* More functions would be nice. +* The lexer assumes that the decimal marker is a period. (.) I'm not exactly + sure how to cleanly solve this problem with flex. :-( +* Documentation could be better. diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..a4a2438 --- /dev/null +++ b/SConstruct @@ -0,0 +1,36 @@ +#! /usr/bin/env python + +################################################################### +# LOAD THE ENVIRONMENT AND SET UP THE TOOLS +################################################################### + +## Load the builders in config +tools = [ 'default', 'help', 'generic', 'kde', 'abakus' ] +toolpath = [ './', './bksys' ] + +# Required as part of SCons +env = Environment(tools = tools, toolpath = toolpath) + +# Pull in some default settings. +env.KDEuse("environ rpath nohelp") +#env.KDEuse("environ rpath lang_qt thread nohelp") + +# If we're asking for help just go ahead and exit now. +if env['HELP']: + print env.helpText() + Exit() + +if env['flex'] and env['bison']: + env['PARSER_INCLUDED'] = True + +# Export the environment so that SConscript files in subdirs can access it. +Export('env') + +################################################################### +# SCRIPTS FOR BUILDING THE TARGETS +################################################################### + +env.subdirs('src') + +env.docfolder('doc/en', 'en', 'abakus/') +env.SConscript('doc/en/SConscript') diff --git a/bksys/abakus.py b/bksys/abakus.py new file mode 100644 index 0000000..7b6a20c --- /dev/null +++ b/bksys/abakus.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +""" +Run scons -h to display the associated help, or look below .. +""" + +BOLD ="\033[1m" +RED ="\033[91m" +GREEN ="\033[92m" +YELLOW ="\033[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +def exists(env): + return true + +def printColorCoded(msg): + msg = msg.replace(']', NORMAL) + msg = msg.replace('b[', BOLD) + msg = msg.replace('g[', GREEN) + msg = msg.replace('r[', RED) + msg = msg.replace('c[', CYAN) + msg = msg.replace('y[', YELLOW) + + print msg + +def generate(env): + import SCons.Util, os + + env.addHelpText("""b[hi] +b[*** abakus options *** +----------------------] +b[* bison=(no|yes): Enable parser support. Only needed for developers. +b[* flex=(no|yes): Enable lexer support. Only needed for developers. +b[* mpfr=(no|yes|check): Enable the MPFR library, which is faster and more + precise than abakus's high-precision code. + +ie: b[scons configure] +""") + + if env['HELP']: + # Don't even bother. + return env + + from SCons.Options import Options, PackageOption, EnumOption + import os + + def CheckFlags(context): + context.Message('Checking if ld supports --as-needed... ') + lastLINKFLAGS = context.env['LINKFLAGS'] + context.env.Append(LINKFLAGS = '-Wl,--as-needed') + + ret = context.TryLink(""" +#include +using namespace std; +int main() +{ + cout << "Test" << endl; +} +""", ".cpp") + if not ret: + context.env.Replace(LINKFLAGS = lastLINKFLAGS) + context.Result(ret) + return ret + + def CheckPath(context, prog, versionFlag = ''): + if context.env[prog] == 'yes': + context.env[prog] = prog + + context.Message('Checking for %s... ' % prog) + + ret = True + + # If absolute path, just try this one. + if prog[0] == '/': + ret = context.TryAction('%s %s' % (context.env[prog], versionFlag))[0] + if ret: + context.Result(ret) + return True + + path = context.env.WhereIs(prog) + if ret and path != None: + context.env[prog] = path + context.Result(1) + else: + context.env[prog] = False + context.Result(0) + + print """ +The $foo program was not found! You asked to use it so we will stop here. It +is not required, you may use $foo=no on the command line to go without it.""".replace('$foo', prog) + + Exit(1) + + return False + + context.Result(1) + return True + + cachefile = env['CACHEDIR'] + '/abakus.cache.py' + + fixup = lambda x: "%s installed here (yes = search)" % x + + opts = None + if env.doConfigure(): + opts = Options(None, env['ARGS']) + else: + opts = Options(cachefile, env['ARGS']) + + opts.AddOptions( + PackageOption('bison', fixup('use the Bison parser generator'), 'yes'), + PackageOption('flex', fixup('use the Flex scanner generator'), 'yes'), + EnumOption ('mpfr', 'use the MPFR high-precision library', 'check', + allowed_values=('yes', 'no', 'check'), map={}, ignorecase=1), + ('ABAKUS_CONFIGURED', '', 0), + ('HAVE_ASNEEDED', '', 0) + ) + + # We must manually pass the ARGS in. + opts.Update(env, env['ARGS']) + + if env.doConfigure() or not env['ABAKUS_CONFIGURED']: + # Configure stuff + conf = env.Configure(custom_tests = {'CheckPath': CheckPath, 'CheckFlags' : CheckFlags}) + + if env['bison'] and env['bison'] != 'no': + conf.CheckPath('bison', '-V') + if env['flex'] and env['flex'] != 'no': + conf.CheckPath('flex', '-V') + if env['mpfr'] != 'no': + oldLibs = conf.env.get('LIBS', '') + conf.env.AppendUnique(LIBS = 'gmp') + + if conf.CheckLibWithHeader('mpfr', 'mpfr.h', 'c++', ''' +mpfr_t a; +mpfr_ptr ptr; +__mpfr_struct debug; + +mpfr_init(a); +''', autoadd = True): + env['mpfr'] = 'yes' + else: + conf.env.Replace(LIBS = oldLibs) + + if env['mpfr'] == 'yes': + print "Unable to find requested library mpfr!" + env.Exit(1) + else: + env['mpfr'] = 'no' + + env['HAVE_ASNEEDED'] = 0 + if conf.CheckFlags(): + env['HAVE_ASNEEDED'] = 1 + + env['ABAKUS_CONFIGURED'] = 1 + env = conf.Finish() + + try: + f = open("config.h", "w+") + f.write("""/* config.h -- Automatically generated by abakus.py + * Any changes you make to this file will be overwritten! + */ + +""") + f.write("/* HAVE_MPFR -- Defined if the MPFR library is being used. */\n") + if env['mpfr'] == 'yes': + f.write ("#define HAVE_MPFR 1\n") + else: + f.write ("/* #undef HAVE_MPFR */\n") + + f.close() + + except IOError: + print "Unable to write config.h!" + + opts.Save(cachefile, env) + +# vim: set et ts=8 sw=4: diff --git a/bksys/generic.py b/bksys/generic.py new file mode 100644 index 0000000..580cbd5 --- /dev/null +++ b/bksys/generic.py @@ -0,0 +1,498 @@ +## Thomas Nagy, 2005 + +""" +Detect and store the most common options +* kdecxxflags : debug=1 (-g) or debug=full (-g3, slower) + else use the user CXXFLAGS if any, - or -O2 by default +* prefix : the installation path +* extraincludes : a list of paths separated by ':' +ie: scons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local +""" + +BOLD ="\033[1m" +RED ="\033[91m" +GREEN ="\033[92m" +YELLOW ="\033[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +import os, re, types, sys, string, shutil, stat + +import SCons.Defaults +import SCons.Tool +import SCons.Util +from SCons.Script.SConscript import SConsEnvironment +from SCons.Options import Options, PathOption + +class genobj: + def __init__(self, val, env): + if not val in "program shlib kioslave staticlib".split(): + print "unknown genobj given: "+val + env.Exit(1) + + self.type = val + self.orenv = env + self.env = None + self.executed = 0 + + self.target='' + self.src=None + + self.cxxflags='' + self.cflags='' + self.includes='' + + self.linkflags='' + self.libpaths='' + self.libs='' + + # vars used by shlibs + self.vnum='' + self.libprefix='' + + # a directory where to install the targets (optional) + self.instdir='' + # ignore the DESTDIR (optional) + self.nodestdir='' + + # change the working directory before reading the targets + self.chdir='' + + # these members are private + self.chdir_lock=None + self.old_os_dir='' + self.old_fs_dir='' + self.p_local_shlibs=[] + self.p_local_staticlibs=[] + self.p_global_shlibs=[] + + #if not env.has_key('USE_THE_FORCE_LUKE'): env['USE_THE_FORCE_LUKE']=[self] + #else: env['USE_THE_FORCE_LUKE'].append(self) + + def lockchdir(self): + if not self.chdir: return + self.chdir_lock=1 + SConfFS=SCons.Node.FS.default_fs + self.old_fs_dir=SConfFS.getcwd() + self.old_os_dir=os.getcwd() + #os.chdir(old_os_dir+'/'+self.chdir) + SConfFS.chdir( SConfFS.Dir('#/'+self.chdir), change_os_dir=1) + + def unlockchdir(self): + if not self.chdir: return + if self.chdir_lock: + #os.chdir(self.old_os_dir) + SCons.Node.FS.default_fs.chdir(self.old_fs_dir, change_os_dir=0) + self.chdir_lock=None + + def execute(self): + if self.orenv.has_key('DUMPCONFIG'): + print self.xml() + return + + self.lockchdir() + + self.env = self.orenv.Copy() + + if not self.src or len(self.src) == 0: + print RED+"no source file given to object - self.src"+NORMAL + self.env.Exit(1) + if not self.env.has_key('nosmart_includes'): self.env.AppendUnique(CPPPATH=['./']) + if self.type == "kioslave": self.libprefix='' + + if len(self.includes)>0: self.env.AppendUnique(CPPPATH=self.env.make_list(self.includes)) + if len(self.cxxflags)>0: self.env.AppendUnique(CXXFLAGS=self.env.make_list(self.cxxflags)) + if len(self.cflags)>0: self.env.AppendUnique(CCFLAGS=self.env.make_list(self.cflags)) + + llist=self.env.make_list(self.libs) + lext='.so .la'.split() + sext='.a'.split() + for l in llist: + sal=SCons.Util.splitext(l) + if len(sal)>1: + if sal[1] in lext: self.p_local_shlibs.append(sal[0]+'.so') + elif sal[1] in sext: self.p_local_staticlibs.append(sal[0]+'.a') + else: self.p_global_shlibs.append(l) + + if len(self.p_global_shlibs)>0: self.env.AppendUnique(LIBS=self.p_global_shlibs) + if len(self.libpaths)>0: self.env.PrependUnique(LIBPATH=self.env.make_list(self.libpaths)) + if len(self.linkflags)>0: self.env.PrependUnique(LINKFLAGS=self.env.make_list(self.linkflags)) + + # the target to return + ret=None + if self.type=='shlib' or self.type=='kioslave': + ret=self.env.bksys_shlib(self.target, self.src, self.instdir, + self.libprefix, self.vnum, nodestdir=self.nodestdir) + elif self.type=='program': + ret=self.env.Program(self.target, self.src) + if not self.env.has_key('NOAUTOINSTALL'): + self.env.bksys_install(self.instdir, ret, nodestdir=self.nodestdir) + elif self.type=='staticlib': + ret=self.env.StaticLibrary(self.target, self.src) + + # we link the program against a shared library made locally, add the dependency + if len(self.p_local_shlibs)>0: + self.env.link_local_shlib(self.p_local_shlibs) + if ret: self.env.Depends( ret, self.p_local_shlibs ) + if len(self.p_local_staticlibs)>0: + self.env.link_local_staticlib(self.p_local_staticlibs) + if ret: self.env.Depends( ret, self.p_local_staticlibs ) + + self.unlockchdir() + +## Copy function that honors symlinks +def copy_bksys(dest, source, env): + if os.path.islink(source): + #print "symlinking "+source+" "+dest + if os.path.islink(dest): + os.unlink(dest) + os.symlink(os.readlink(source), dest) + else: + shutil.copy2(source, dest) + st=os.stat(source) + os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + return 0 + +## Return a list of things +def make_list(env, s): + if type(s) is types.ListType: + return s + else: + return s.split() + +def exists(env): + return true + +def generate(env): + ## Bksys requires scons 0.96 + env.EnsureSConsVersion(0, 96) + + SConsEnvironment.make_list = make_list + def doConfigure(env): + return not env['HELP'] and (env['_CONFIGURE'] or not env.has_key('ISCONFIGURED')) + + SConsEnvironment.doConfigure = doConfigure + env['HELP']=0 + if '--help' in sys.argv or '-h' in sys.argv or 'help' in sys.argv: + env['HELP']=1 + + env.addHelpText(""" +b[*** Generic options *** +-----------------------] +b[* debug ]: debug=1 (-g) or debug=full (-g3, slower), otherwise use + environment CXXFLAGS, or -O2 by default. +b[* prefix ]: the installation path +b[* extraincludes ]: a list of paths separated by ':' + +ie: b[scons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local] +""") + + ## Global cache directory + # Put all project files in it so a rm -rf cache will clean up the config + if not env.has_key('CACHEDIR'): + env['CACHEDIR'] = os.getcwd()+'/cache/' + if not os.path.isdir(env['CACHEDIR']): + os.mkdir(env['CACHEDIR']) + + ## SCons cache directory + # This avoids recompiling the same files over and over again: + # very handy when working with cvs + if os.getuid() != 0: + env.CacheDir(os.getcwd()+'/cache/objects') + + # Avoid spreading .sconsign files everywhere - keep this line + env.SConsignFile(env['CACHEDIR']+'/scons_signatures') + + def makeHashTable(args): + table = { } + for arg in args: + if len(arg) > 1: + lst=arg.split('=') + if len(lst) < 2: + continue + key=lst[0] + value=lst[1] + if len(key) > 0 and len(value) >0: + table[key] = value + return table + + env['ARGS']=makeHashTable(sys.argv) + + ## Special trick for installing rpms ... + env['DESTDIR']='' + if 'install' in sys.argv: + dd='' + if os.environ.has_key('DESTDIR'): + dd=os.environ['DESTDIR'] + if not dd: + if env['ARGS']: dd=env['ARGS']['DESTDIR'] + if dd: + env['DESTDIR']=dd+'/' + print CYAN+'** Enabling DESTDIR for the project ** ' + NORMAL + env['DESTDIR'] + + ## install symlinks for shared libraries properly + env['INSTALL'] = copy_bksys + + ## Use the same extension .o for all object files + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + + ## load the options + cachefile=env['CACHEDIR']+'generic.cache.py' + opts = Options(cachefile) + opts.AddOptions( + ( 'GENCCFLAGS', 'C flags' ), + ( 'GENCXXFLAGS', 'debug level for the project : full or just anything' ), + ( 'GENLINKFLAGS', 'additional link flags' ), + ( 'PREFIX', 'prefix for installation' ), + ( 'EXTRAINCLUDES', 'extra include paths for the project' ), + ( 'ISCONFIGURED', 'is the project configured' ), + ) + opts.Update(env) + + # Use this to avoid an error message 'how to make target configure ?' + env.Alias('configure', None) + + # Check if the following command line arguments have been given + # and set a flag in the environment to show whether or not it was + # given. + if 'install' in sys.argv: + env['_INSTALL']=1 + else: + env['_INSTALL']=0 + if 'configure' in sys.argv: + env['_CONFIGURE']=1 + else: + env['_CONFIGURE']=0 + + # Configure the environment if needed + if doConfigure(env): + # be paranoid, unset existing variables + for var in "GENCXXFLAGS GENCCFLAGS GENLINKFLAGS PREFIX EXTRAINCLUDES ISCONFIGURED EXTRAINCLUDES".split(): + if env.has_key(var): env.__delitem__(var) + + if env['ARGS'].get('debug', None): + debuglevel = env['ARGS'].get('debug', None) + print CYAN+'** Enabling debug for the project **' + NORMAL + if (debuglevel == "full"): + env['GENCXXFLAGS'] = ['-DDEBUG', '-g3', '-Wall'] + else: + env['GENCXXFLAGS'] = ['-DDEBUG', '-g', '-Wall'] + else: + if os.environ.has_key('CXXFLAGS'): + # user-defined flags (gentooers will be elighted) + env['GENCXXFLAGS'] = SCons.Util.CLVar( os.environ['CXXFLAGS'] ) + env.Append( GENCXXFLAGS = ['-DNDEBUG', '-DNO_DEBUG'] ) + else: + env.Append(GENCXXFLAGS = ['-O2', '-DNDEBUG', '-DNO_DEBUG']) + + if os.environ.has_key('CFLAGS'): + env['GENCCFLAGS'] = SCons.Util.CLVar( os.environ['CFLAGS'] ) + + ## FreeBSD settings (contributed by will at freebsd dot org) + if os.uname()[0] == "FreeBSD": + if os.environ.has_key('PTHREAD_LIBS'): + env.AppendUnique( GENLINKFLAGS = SCons.Util.CLVar( os.environ['PTHREAD_LIBS'] ) ) + else: + syspf = os.popen('/sbin/sysctl kern.osreldate') + osreldate = int(syspf.read().split()[1]) + syspf.close() + if osreldate < 500016: + env.AppendUnique( GENLINKFLAGS = ['-pthread']) + env.AppendUnique( GENCXXFLAGS = ['-D_THREAD_SAFE']) + elif osreldate < 502102: + env.AppendUnique( GENLINKFLAGS = ['-lc_r']) + env.AppendUnique( GENCXXFLAGS = ['-D_THREAD_SAFE']) + else: + env.AppendUnique( GENLINKFLAGS = ['-pthread']) + + # User-specified prefix + if env['ARGS'].has_key('prefix'): + env['PREFIX'] = os.path.abspath( env['ARGS'].get('prefix', '') ) + print (CYAN+'** installation prefix for the project set to : ' + + env['PREFIX'] +' **'+ NORMAL) + + # User-specified include paths + env['EXTRAINCLUDES'] = env['ARGS'].get('extraincludes', None) + if env['EXTRAINCLUDES']: + print (CYAN+'** extra include paths for the project set to: ' + + env['EXTRAINCLUDES'] +' **'+ NORMAL) + + env['ISCONFIGURED']=1 + + # And finally save the options in the cache + opts.Save(cachefile, env) + + def bksys_install(lenv, subdir, files, destfile=None, nodestdir=None): + """ Install files on "scons install" + If the DESTDIR env variable has been set, (e.g. by + "scons install DESTDIR=$CURDIR/debian) then install files to that + directory, regardless of where the configure stage showed that + files should be installed. + This feature is useful for packagers, and users of GNU stow. + + NB. The DESTDIR will be ignored if NODESTDIR is also set, although + the same effect can be acheived by not setting DESTDIR in the first + place.""" + + if not env['_INSTALL']: + return + basedir = env['DESTDIR'] + if nodestdir or env.has_key('NODESTDIR') : basedir = "/" + install_list = None + if not destfile: + install_list = env.Install(basedir+subdir+'/', files) + else: + if subdir: + install_list = env.InstallAs(basedir+subdir+'/'+destfile, files) + else: + install_list = env.InstallAs(basedir+'/'+destfile, files) + env.Alias('install', install_list) + return install_list + + def build_la_file(target, source, env): + """ Action for building libtool files. + Writes a .la file, as used by libtool.""" + dest=open(target[0].path, 'w') + sname=source[0].name + dest.write("dlname='%s'\n" % sname) + if len(env['BKSYS_VNUM'])>0: + vnum=env['BKSYS_VNUM'] + nums=vnum.split('.') + src=source[0].name + name = src.split('so.')[0] + 'so' + strn = src+" "+name+"."+str(nums[0])+" "+name + dest.write("library_names='%s'\n" % (strn) ) + else: + dest.write("library_names='%s %s %s'\n" % (sname, sname, sname) ) + dest.write("old_library=''\ndependency_libs=''\ncurrent=0\n") + dest.write("age=0\nrevision=0\ninstalled=yes\nshouldnotlink=no\n") + dest.write("dlopen=''\ndlpreopen=''\n") + dest.write("libdir='%s'" % env['BKSYS_DESTDIR']) + dest.close() + return 0 + + def string_la_file(target, source, env): + print "building '%s' from '%s'" % (target[0].name, source[0].name) + la_file = env.Action(build_la_file, string_la_file, ['BKSYS_VNUM', 'BKSYS_DESTDIR']) + env['BUILDERS']['LaFile'] = env.Builder(action=la_file,suffix='.la',src_suffix=env['SHLIBSUFFIX']) + + ## Function for building shared libraries + def bksys_shlib(lenv, target, source, libdir, libprefix='lib', vnum='', noinst=None, nodestdir=None): + """ Install a shared library. + + Installs a shared library, with or without a version number, and create a + .la file for use by libtool. + + If library version numbering is to be used, the version number + should be passed as a period-delimited version number (e.g. + vnum = '1.2.3'). This causes the library to be installed + with its full version number, and with symlinks pointing to it. + + For example, for libfoo version 1.2.3, install the file + libfoo.so.1.2.3, and create symlinks libfoo.so and + libfoo.so.1 that point to it. + """ + thisenv = lenv.Copy() # copying an existing environment is cheap + thisenv['BKSYS_DESTDIR']=libdir + thisenv['BKSYS_VNUM']=vnum + thisenv['SHLIBPREFIX']=libprefix + + if len(vnum)>0: + thisenv['SHLIBSUFFIX']='.so.'+vnum + thisenv.Depends(target, thisenv.Value(vnum)) + + # Fix against a scons bug - shared libs and ordinal out of range(128) + if type(source) is types.ListType: + src2=[] + for i in source: + src2.append( str(i) ) + source=src2 + + library_list = thisenv.SharedLibrary(target, source) + lafile_list = thisenv.LaFile(target, library_list) + + ## Install the libraries automatically + if not thisenv.has_key('NOAUTOINSTALL') and not noinst: + thisenv.bksys_install(libdir, library_list, nodestdir=nodestdir) + thisenv.bksys_install(libdir, lafile_list, nodestdir=nodestdir) + + ## Handle the versioning + if len(vnum)>0: + nums=vnum.split('.') + symlinkcom = ('cd $TARGET.dir && ' + + 'rm -f $TARGET.name && ' + + 'ln -s $SOURCE.name $TARGET.name') + tg = target+'.so.'+vnum + nm1 = target+'.so' + nm2 = target+'.so.'+nums[0] + + thisenv.Command(nm1, tg, symlinkcom) + thisenv.Command(nm2, tg, symlinkcom) + + #base=env['DESTDIR']+libdir+'/' + thisenv.bksys_install(libdir, nm1, nodestdir=nodestdir) + thisenv.bksys_install(libdir, nm2, nodestdir=nodestdir) + + # Declare scons scripts to process + def subdirs(lenv, folderlist): + flist=[] + if type(folderlist) is types.ListType: flist = folderlist + else: flist = folderlist.split() + for i in flist: + lenv.SConscript(i+"/SConscript") + + def link_local_shlib(lenv, str): + """ Links against a shared library made in the project """ + lst = lenv.make_list(str) + for file in lst: + import re + reg = re.compile("(.*)/lib(.*).(la|so)") + result = reg.match(file) + if not result: + print "Unknown la file given "+file + continue + dir = result.group(1) + link = result.group(2) + lenv.AppendUnique(LIBS = [link]) + lenv.PrependUnique(LIBPATH = [dir]) + + def link_local_staticlib(lenv, str): + """ Links against a shared library made in the project """ + lst = lenv.make_list(str) + for file in lst: + import re + reg = re.compile("(.*)/(lib.*.a)") + result = reg.match(file) + if not result: + print "Unknown archive file given "+file + continue + + f=SCons.Node.FS.default_fs.File(file) + lenv.Append(LINKFLAGS=[f.path]) + + #valid_targets = "program shlib kioslave staticlib".split() + SConsEnvironment.bksys_install = bksys_install + SConsEnvironment.bksys_shlib = bksys_shlib + SConsEnvironment.subdirs = subdirs + SConsEnvironment.link_local_shlib = link_local_shlib + SConsEnvironment.link_local_staticlib = link_local_staticlib + + SConsEnvironment.genobj=genobj + + if env.has_key('GENCXXFLAGS'): + env.PrependUnique( CXXFLAGS = env['GENCXXFLAGS'] ) + + if env.has_key('GENCCFLAGS'): + env.AppendUnique( CCFLAGS = env['GENCCFLAGS'] ) + + if env.has_key('GENLINKFLAGS'): + env.AppendUnique( LINKFLAGS = env['GENLINKFLAGS'] ) + + if env.has_key('EXTRAINCLUDES'): + if env['EXTRAINCLUDES']: + incpaths = [] + for dir in str(env['EXTRAINCLUDES']).split(':'): + incpaths.append( dir ) + env.Append(CPPPATH = incpaths) + + env.Export('env') diff --git a/bksys/help.py b/bksys/help.py new file mode 100644 index 0000000..5cb6281 --- /dev/null +++ b/bksys/help.py @@ -0,0 +1,43 @@ +## Thomas Nagy, 2005 + +""" +Detect and store the most common options +* kdecxxflags : debug=1 (-g) or debug=full (-g3, slower) + else use the user CXXFLAGS if any, - or -O2 by default +* prefix : the installation path +* extraincludes : a list of paths separated by ':' +ie: scons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local +""" + +BOLD ="\033[1m" +RED ="\033[91m" +GREEN ="\033[92m" +YELLOW ="\033[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +def exists(env): + return true + +def generate(env): + ## Bksys requires scons 0.96 + env.EnsureSConsVersion(0, 96) + + env._help = '' + + def addHelpText(env, text): + env._help = env._help + text + + def helpText(env): + text = env._help.replace(']', NORMAL) + text = text.replace('b[', BOLD) + text = text.replace('g[', GREEN) + text = text.replace('r[', RED) + text = text.replace('y[', YELLOW) + text = text.replace('c[', CYAN) + + return text + + from SCons.Script.SConscript import SConsEnvironment + SConsEnvironment.addHelpText = addHelpText + SConsEnvironment.helpText = helpText diff --git a/bksys/kde.py b/bksys/kde.py new file mode 100644 index 0000000..e93098e --- /dev/null +++ b/bksys/kde.py @@ -0,0 +1,820 @@ +# Made from scons qt.py and (heavily) modified into kde.py +# Thomas Nagy, 2004, 2005 + +""" +Run scons -h to display the associated help, or look below .. +""" + +BOLD ="\033[1m" +RED ="\033[91m" +GREEN ="\033[92m" +YELLOW ="\033[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +import os, re, types +from SCons.Script.SConscript import SConsEnvironment + +# Returns the name of the shared object (i.e. libkdeui.so.4) +# referenced by a libtool archive (like libkdeui.la) +def getSOfromLA(lafile): + contents = open(lafile, 'r').read() + match = re.search("^dlname='([^']*)'$", contents, re.M) + if match: + return match.group(1) + return None + +# A helper, needed .. everywhere +def KDEuse(lenv, flags): + if lenv['HELP']: lenv.Exit(0) + + _flags=lenv.make_list(flags) + if 'environ' in _flags: + ## The scons developers advise against using this but it is mostly innocuous :) + lenv.AppendUnique( ENV = os.environ ) + if not 'lang_qt' in _flags: + ## Use this define if you are using the kde translation scheme (.po files) + lenv.Append( CPPFLAGS = '-DQT_NO_TRANSLATION' ) + if 'rpath' in _flags: + ## Use this to set rpath - this may cause trouble if folders are moved (chrpath) + lenv.Append( RPATH = [lenv['QTLIBPATH'], lenv['KDELIBPATH'], lenv['KDEMODULE']] ) + kdelibpaths=[] + if lenv['KDELIBPATH'] == lenv['KDELIB']: + kdelibpaths = [lenv['KDELIB']] + else: + kdelibpaths = [lenv['KDELIBPATH'], lenv['KDELIB']] + lenv.Append( RPATH = [lenv['QTLIBPATH'], lenv['KDEMODULE']]+kdelibpaths ) + if 'thread' in _flags: + ## Uncomment the following if you need threading support + lenv.KDEaddflags_cxx( ['-DQT_THREAD_SUPPORT', '-D_REENTRANT'] ) + if 'fastmoc' in _flags: + lenv['BKSYS_FASTMOC']=1 + if not 'nohelp' in _flags: + if lenv['_CONFIGURE'] or lenv['HELP']: + lenv.Exit(0) + if not 'nosmart' or not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['#/']) + lst=[] + if lenv.has_key('USE_THE_FORCE_LUKE'): + lst=lenv['USE_THE_FORCE_LUKE'] + lenv.__delitem__('USE_THE_FORCE_LUKE') + for v in lst: + v.execute() + else: + lenv['nosmart_includes']=1 + + ## To use kdDebug(intvalue)<<"some trace"</dev/null").read().strip() + if len(kde_config): + print GREEN+"kde-config was found"+NORMAL + else: + print RED+"kde-config was NOT found in your PATH"+NORMAL + print "Make sure kde is installed properly" + print "(missing package kdebase-devel?)" + env.Exit(1) + env['KDEDIR'] = os.popen('kde-config -prefix').read().strip() + + print "Checking for kde version : ", + kde_version = os.popen("kde-config --version|grep KDE").read().strip().split()[1] + if int(kde_version[0]) != 3 or int(kde_version[2]) < 2: + print RED+kde_version + print RED+"Your kde version can be too old"+NORMAL + print RED+"Please make sure kde is at least 3.2"+NORMAL + else: + print GREEN+kde_version+NORMAL + + ## Detect the qt library + print "Checking for the qt library : ", + qtdir = os.getenv("QTDIR") + if qtdir: + print GREEN+"qt is in "+qtdir+NORMAL + else: + try: + tmplibdir = os.popen('kde-config --expandvars --install lib').read().strip() + libkdeuiSO = tmplibdir+'/'+getSOfromLA(tmplibdir+'/libkdeui.la') + m = re.search('(.*)/lib/libqt.*', os.popen('ldd ' + libkdeuiSO + ' | grep libqt').read().strip().split()[2]) + except: + m=None + if m: + qtdir = m.group(1) + print YELLOW+"qt was found as "+m.group(1)+NORMAL + else: + print RED+"qt was not found"+NORMAL + print RED+"Please set QTDIR first (/usr/lib/qt3?) or try scons -h for more options"+NORMAL + env.Exit(1) + env['QTDIR'] = qtdir.strip() + + ## Find the necessary programs uic and moc + print "Checking for uic : ", + uic = qtdir + "/bin/uic" + if os.path.isfile(uic): + print GREEN+"uic was found as "+uic+NORMAL + else: + uic = os.popen("which uic 2>/dev/null").read().strip() + if len(uic): + print YELLOW+"uic was found as "+uic+NORMAL + else: + uic = os.popen("which uic 2>/dev/null").read().strip() + if len(uic): + print YELLOW+"uic was found as "+uic+NORMAL + else: + print RED+"uic was not found - set QTDIR put it in your PATH ?"+NORMAL + env.Exit(1) + env['QT_UIC'] = uic + + print "Checking for moc : ", + moc = qtdir + "/bin/moc" + if os.path.isfile(moc): + print GREEN + "moc was found as " + moc + NORMAL + else: + moc = os.popen("which moc 2>/dev/null").read().strip() + if len(moc): + print YELLOW + "moc was found as " + moc + NORMAL + elif os.path.isfile("/usr/share/qt3/bin/moc"): + moc = "/usr/share/qt3/bin/moc" + print YELLOW + "moc was found as " + moc + NORMAL + else: + print RED + "moc was not found - set QTDIR or put it in your PATH ?" + NORMAL + env.Exit(1) + env['QT_MOC'] = moc + + ## check for the qt and kde includes + print "Checking for the qt includes : ", + if qtincludes and os.path.isfile(qtincludes + "/qlayout.h"): + # The user told where to look for and it looks valid + print GREEN + "ok " + qtincludes + NORMAL + else: + if os.path.isfile(qtdir + "/include/qlayout.h"): + # Automatic detection + print GREEN + "ok " + qtdir + "/include/ " + NORMAL + qtincludes = qtdir + "/include/" + elif os.path.isfile("/usr/include/qt3/qlayout.h"): + # Debian probably + print YELLOW + "the qt headers were found in /usr/include/qt3/ " + NORMAL + qtincludes = "/usr/include/qt3" + else: + print RED + "the qt headers were not found" + NORMAL + env.Exit(1) + + print "Checking for the kde includes : ", + kdeprefix = os.popen("kde-config --prefix").read().strip() + if not kdeincludes: + kdeincludes = kdeprefix+"/include/" + if os.path.isfile(kdeincludes + "/klineedit.h"): + print GREEN + "ok " + kdeincludes + NORMAL + else: + if os.path.isfile(kdeprefix+"/include/kde/klineedit.h"): + # Debian, Fedora probably + print YELLOW + "the kde headers were found in " + kdeprefix + "/include/kde/" + NORMAL + kdeincludes = kdeprefix + "/include/kde/" + else: + print RED + "The kde includes were NOT found" + NORMAL + env.Exit(1) + + # kde-config options + kdec_opts = {'KDEBIN' : 'exe', 'KDEAPPS' : 'apps', + 'KDEDATA' : 'data', 'KDEICONS' : 'icon', + 'KDEMODULE' : 'module', 'KDELOCALE' : 'locale', + 'KDEKCFG' : 'kcfg', 'KDEDOC' : 'html', + 'KDEMENU' : 'apps', 'KDEXDG' : 'xdgdata-apps', + 'KDEMIME' : 'mime', 'KDEXDGDIR' : 'xdgdata-dirs', + 'KDESERV' : 'services','KDESERVTYPES' : 'servicetypes', + 'KDEINCLUDE': 'include' + } + + if prefix: + ## use the user-specified prefix + if not execprefix: + execprefix = prefix + if not datadir: + datadir=prefix+"/share" + if not libdir: + libdir=execprefix+"/lib"+libsuffix + + subst_vars = lambda x: x.replace('${exec_prefix}', execprefix)\ + .replace('${datadir}', datadir)\ + .replace('${libdir}', libdir) + debian_fix = lambda x: x.replace('/usr/share', '${datadir}') + env['PREFIX'] = prefix + env['KDELIB'] = libdir + for (var, option) in kdec_opts.items(): + dir = os.popen('kde-config --install ' + option).read().strip() + if var == 'KDEDOC': dir = debian_fix(dir) + env[var] = subst_vars(dir) + + else: + env['PREFIX'] = os.popen('kde-config --expandvars --prefix').read().strip() + env['KDELIB'] = os.popen('kde-config --expandvars --install lib').read().strip() + for (var, option) in kdec_opts.items(): + dir = os.popen('kde-config --expandvars --install ' + option).read().strip() + env[var] = dir + + env['QTPLUGINS']=os.popen('kde-config --expandvars --install qtplugins').read().strip() + + ## kde libs and includes + env['KDEINCLUDEPATH']=kdeincludes + if not kdelibs: + kdelibs=os.popen('kde-config --expandvars --install lib').read().strip() + env['KDELIBPATH']=kdelibs + + ## qt libs and includes + env['QTINCLUDEPATH']=qtincludes + if not qtlibs: + qtlibs=qtdir+"/lib"+libsuffix + env['QTLIBPATH']=qtlibs + +def generate(env): + """"Set up the qt and kde environment and builders - the moc part is difficult to understand """ + + # attach this function immediately + SConsEnvironment.KDEuse = KDEuse + env.addHelpText(""" +b[*** KDE options *** +-------------------] +b[* prefix ]: base install path, ie: /usr/local +b[* execprefix ]: install path for binaries, ie: /usr/bin +b[* datadir ]: install path for the data, ie: /usr/local/share +b[* libdir ]: install path for the libs, ie: /usr/lib +b[* libsuffix ]: suffix of libraries on amd64, ie: 64, 32 +b[* kdeincludes]: path to the kde includes (/usr/include/kde on debian, ...) +b[* qtincludes ]: path to the for qt includes (/usr/include/qt on debian, ...) +b[* kdelibs ]: path to the kde libs, for linking the programs +b[* qtlibs ]: path to the qt libs, for linking the programs + +ie: b[scons configure libdir=/usr/local/lib qtincludes=/usr/include/qt] +""") + + import SCons.Defaults + import SCons.Tool + import SCons.Util + import SCons.Node + + CLVar = SCons.Util.CLVar + splitext = SCons.Util.splitext + Builder = SCons.Builder.Builder + + # Detect the environment - replaces ./configure implicitely and store the options into a cache + from SCons.Options import Options + cachefile=env['CACHEDIR']+'kde.cache.py' + opts = Options(cachefile) + opts.AddOptions( + ('PREFIX', 'root of the program installation'), + + ('QTDIR', ''), + ('QTLIBPATH', 'path to the qt libraries'), + ('QTINCLUDEPATH', 'path to the qt includes'), + ('QT_UIC', 'uic command'), + ('QT_MOC', 'moc command'), + ('QTPLUGINS', 'uic executable command'), + + ('KDEDIR', ''), + ('KDELIBPATH', 'path to the installed kde libs'), + ('KDEINCLUDEPATH', 'path to the installed kde includes'), + + ('KDEBIN', 'inst path of the kde binaries'), + ('KDEINCLUDE', 'inst path of the kde include files'), + ('KDELIB', 'inst path of the kde libraries'), + ('KDEMODULE', 'inst path of the parts and libs'), + ('KDEDATA', 'inst path of the application data'), + ('KDELOCALE', ''), ('KDEDOC', ''), ('KDEKCFG', ''), + ('KDEXDG', ''), ('KDEXDGDIR', ''), ('KDEMENU', ''), + ('KDEMIME', ''), ('KDEICONS', ''), ('KDESERV', ''), + ('KDESERVTYPES', ''), ('KDEAPPS', ''), + ) + opts.Update(env) + + def getInstDirForResType(lenv,restype): + if len(restype) == 0 or not lenv.has_key(restype): + print RED+"unknown resource type "+restype+NORMAL + lenv.Exit(1) + else: + instdir = lenv[restype] + basedir=lenv['DESTDIR'] + ## support for installing into other folders when PREFIX is set - used by gnu stow + if basedir: instdir = instdir.replace(lenv['PREFIX'], basedir) + return instdir + + # reconfigure when things are missing + if not env['HELP'] and (env['_CONFIGURE'] or not env.has_key('QTDIR') or not env.has_key('KDEDIR')): + detect_kde(env) + opts.Save(cachefile, env) + + ## set default variables, one can override them in sconscript files + env.Append(CXXFLAGS = ['-I'+env['KDEINCLUDEPATH'], '-I'+env['QTINCLUDEPATH'] ], + LIBPATH = [env['KDELIBPATH'], env['QTLIBPATH'] ]) + + env['QT_AUTOSCAN'] = 1 + env['QT_DEBUG'] = 0 + + env['MEINPROC'] = 'meinproc' + env['MSGFMT'] = 'msgfmt' + + ## ui file processing + def uic_processing(target, source, env): + inc_kde ='#include \n#include \n' + inc_moc ='#include "%s"\n' % target[2].name + comp_h ='$QT_UIC -L $QTPLUGINS -nounload -o %s %s' % (target[0].path, source[0].path) + comp_c ='$QT_UIC -L $QTPLUGINS -nounload -tr tr2i18n -impl %s %s' % (target[0].path, source[0].path) + comp_moc ='$QT_MOC -o %s %s' % (target[2].path, target[0].path) + if env.Execute(comp_h): + return ret + dest = open( target[1].path, "w" ) + dest.write(inc_kde) + dest.close() + if env.Execute( comp_c+" >> "+target[1].path ): + return ret + dest = open( target[1].path, "a" ) + dest.write(inc_moc) + dest.close() + ret = env.Execute( comp_moc ) + return ret + def uicEmitter(target, source, env): + adjustixes = SCons.Util.adjustixes + bs = SCons.Util.splitext(str(source[0].name))[0] + bs = os.path.join(str(target[0].get_dir()),bs) + target.append(bs+'.cpp') + target.append(bs+'.moc') + return target, source + env['BUILDERS']['Uic']=Builder(action=uic_processing,emitter=uicEmitter,suffix='.h',src_suffix='.ui') + + def kcfg_buildit(target, source, env): + comp='kconfig_compiler -d%s %s %s' % (str(source[0].get_dir()), source[1].path, source[0].path) + return env.Execute(comp) + def kcfg_stringit(target, source, env): + print "processing %s to get %s and %s" % (source[0].name, target[0].name, target[1].name) + def kcfgEmitter(target, source, env): + adjustixes = SCons.Util.adjustixes + bs = SCons.Util.splitext(str(source[0].name))[0] + bs = os.path.join(str(target[0].get_dir()),bs) + # .h file is already there + target.append(bs+'.cpp') + + if not os.path.isfile(str(source[0])): + print RED+'kcfg file given '+str(source[0])+' does not exist !'+NORMAL + print os.popen('pwd').read() + return target, source + kfcgfilename="" + kcfgFileDeclRx = re.compile("^[fF]ile\s*=\s*(.+)\s*$") + for line in file(str(source[0]), "r").readlines(): + match = kcfgFileDeclRx.match(line.strip()) + if match: + kcfgfilename = match.group(1) + break + if not kcfgfilename: + print 'invalid kcfgc file' + return 0 + source.append(str(source[0].get_dir())+'/'+kcfgfilename) + return target, source + + env['BUILDERS']['Kcfg']=Builder(action=env.Action(kcfg_buildit, kcfg_stringit), + emitter=kcfgEmitter, suffix='.h', src_suffix='.kcfgc') + + ## MOC processing + env['BUILDERS']['Moc']=Builder(action='$QT_MOC -o $TARGET $SOURCE',suffix='.moc',src_suffix='.h') + env['BUILDERS']['Moccpp']=Builder(action='$QT_MOC -o $TARGET $SOURCE',suffix='_moc.cpp',src_suffix='.h') + + ## KIDL file + env['BUILDERS']['Kidl']=Builder(action= 'dcopidl $SOURCE > $TARGET || (rm -f $TARGET ; false)', + suffix='.kidl', src_suffix='.h') + ## DCOP + env['BUILDERS']['Dcop']=Builder(action='dcopidl2cpp --c++-suffix cpp --no-signals --no-stub $SOURCE', + suffix='_skel.cpp', src_suffix='.kidl') + ## STUB + env['BUILDERS']['Stub']=Builder(action= 'dcopidl2cpp --c++-suffix cpp --no-signals --no-skel $SOURCE', + suffix='_stub.cpp', src_suffix='.kidl') + ## DOCUMENTATION + env['BUILDERS']['Meinproc']=Builder(action='$MEINPROC --check --cache $TARGET $SOURCE',suffix='.cache.bz2') + ## TRANSLATIONS + env['BUILDERS']['Transfiles']=Builder(action='$MSGFMT $SOURCE -o $TARGET',suffix='.gmo',src_suffix='.po') + + ## Handy helpers for building kde programs + ## You should not have to modify them .. + + ui_ext = [".ui"] + kcfg_ext = ['.kcfgc'] + header_ext = [".h", ".hxx", ".hpp", ".hh"] + cpp_ext = [".cpp", ".cxx", ".cc"] + skel_ext = [".skel", ".SKEL"] + stub_ext = [".stub", ".STUB"] + + def KDEfiles(lenv, target, source): + """ Returns a list of files for scons (handles kde tricks like .skel) + It also makes custom checks against double includes like : ['file.ui', 'file.cpp'] + (file.cpp is already included because of file.ui) """ + + q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]') + def scan_moc(bs, file_cpp): + addfile=None + # try to find the header + h_ext='' + for n_h_ext in header_ext: + if os.path.isfile(bs+n_h_ext): + h_ext=n_h_ext + break + # We have the header corresponding to the cpp file + if h_ext: + needscan=0 + # User asked for fastmoc, try to avoid scanning + if env.has_key('BKSYS_FASTMOC'): + if os.path.isfile(bs+'.moc'): + lenv.Moc(bs+h_ext) + elif os.path.isfile(bs+'_moc.cpp'): + lenv.Moccpp(bs+h_ext) + addfile=bs+'_moc.cpp' + else: + #print "need scanning "+os.getcwd()+'/'+bs+".moc" + needscan=1 + else: + needscan=1 + # We cannot avoid scanning the files ... + if needscan: + file_h=bs+h_ext + h_contents = open(file_h, 'rb').read() + if q_object_search.search(h_contents): + # we know now there is Q_OBJECT macro + lst = bs.split('/') + val = lst[ len(lst) - 1 ] + reg = '\n\s*#include\s*("|<)'+val+'.moc("|>)' + meta_object_search = re.compile(reg) + cpp_contents = open(file_cpp, 'rb').read() + if meta_object_search.search(cpp_contents): + lenv.Moc(file_h) + else: + lenv.Moccpp(file_h) + addfile=bs+'_moc.cpp' + print "WARNING: moc.cpp for "+bs+h_ext+" consider using #include instead" + return addfile + + src=[] + ui_files=[] + kcfg_files=[] + other_files=[] + kidl=[] + + source_=lenv.make_list(source) + + # For each file, check wether it is a dcop file or not, and create the complete list of sources + for file in source_: + bs = SCons.Util.splitext(file)[0] + ext = SCons.Util.splitext(file)[1] + if ext in skel_ext: + if not bs in kidl: + kidl.append(bs) + lenv.Dcop(bs+'.kidl') + src.append(bs+'_skel.cpp') + elif ext in stub_ext: + if not bs in kidl: + kidl.append(bs) + lenv.Stub(bs+'.kidl') + src.append(bs+'_stub.cpp') + elif ext == ".moch": + lenv.Moccpp(bs+'.h') + src.append(bs+'_moc.cpp') + elif ext in cpp_ext: + src.append(file) + if not env.has_key('NOMOCFILE'): + ret = scan_moc(bs, file) + if ret: + src.append( ret ) + elif ext in ui_ext: + lenv.Uic(file) + src.append(bs+'.cpp') + elif ext in kcfg_ext: + lenv.Kcfg(file) + src.append(bs+'.cpp') + else: + src.append(file) + + for base in kidl: + lenv.Kidl(base+'.h') + + # Now check against typical newbie errors + for file in ui_files: + for ofile in other_files: + if ofile == file: + print RED+"WARNING: You have included "+file+".ui and another file of the same prefix"+NORMAL + print "Files generated by uic (file.h, file.cpp must not be included" + for file in kcfg_files: + for ofile in other_files: + if ofile == file: + print RED+"WARNING: You have included "+file+".kcfg and another file of the same prefix"+NORMAL + print "Files generated by kconfig_compiler (settings.h, settings.cpp) must not be included" + return src + + + """ In the future, these functions will contain the code that will dump the + configuration for re-use from an IDE """ + import glob + def KDEinstall(lenv, restype, subdir, files): + if env.has_key('DUMPCONFIG'): + print "" % (restype, subdir) + for i in lenv.make_list(files): + print " " % i + print "" + return + + if not env['_INSTALL']: + return + dir = getInstDirForResType(lenv, restype) + install_list = lenv.bksys_install(dir+'/'+subdir, files, nodestdir=1) + return install_list + + def KDEinstallas(lenv, restype, destfile, file): + if not env['_INSTALL']: + return + dir = getInstDirForResType(lenv, restype) + install_list = lenv.InstallAs(dir+'/'+destfile, file) + env.Alias('install', install_list) + return install_list + + def KDEprogram(lenv, target, source, + includes='', localshlibs='', globallibs='', globalcxxflags=''): + """ Makes a kde program + The program is installed except if one sets env['NOAUTOINSTALL'] """ + src = KDEfiles(lenv, target, source) + program_list = lenv.Program(target, src) + + # we link the program against a shared library done locally, add the dependency + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + if len(localshlibs)>0: + lst=lenv.make_list(localshlibs) + lenv.link_local_shlib(lst) + lenv.Depends( program_list, lst ) + + if len(includes)>0: + lenv.KDEaddpaths_includes(includes) + if len(globallibs)>0: + lenv.KDEaddlibs(globallibs) + if len(globalcxxflags)>0: + lenv.KDEaddflags_cxx(globalcxxflags) + + if not lenv.has_key('NOAUTOINSTALL'): + KDEinstall(lenv, 'KDEBIN', '', target) + return program_list + + def KDEshlib(lenv, target, source, kdelib=0, libprefix='lib', + includes='', localshlibs='', globallibs='', globalcxxflags='', vnum=''): + """ Makes a shared library for kde (.la file for klibloader) + The library is installed except if one sets env['NOAUTOINSTALL'] """ + src = KDEfiles(lenv, target, source) + + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + # we link the program against a shared library done locally, add the dependency + lst=[] + if len(localshlibs)>0: + lst=lenv.make_list(localshlibs) + lenv.link_local_shlib(lst) + if len(includes)>0: + lenv.KDEaddpaths_includes(includes) + if len(globallibs)>0: + lenv.KDEaddlibs(globallibs) + if len(globalcxxflags)>0: + lenv.KDEaddflags_cxx(globalcxxflags) + + restype = 'KDEMODULE' + if kdelib==1: + restype = 'KDELIB' + + library_list = lenv.bksys_shlib(target, src, getInstDirForResType(lenv, restype), libprefix, vnum, nodestdir=1) + if len(lst)>0: lenv.Depends( library_list, lst ) + + return library_list + + def KDEstaticlib(lenv, target, source): + """ Makes a static library for kde - in practice you should not use static libraries + 1. they take more memory than shared ones + 2. makefile.am needed it because of limitations + (cannot handle sources in separate folders - takes extra processing) """ + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + src = KDEfiles(lenv, target, source) + return lenv.StaticLibrary(target, src) + # do not install static libraries by default + + def KDEaddflags_cxx(lenv, fl): + """ Compilation flags for C++ programs """ + lenv.AppendUnique(CXXFLAGS = lenv.make_list(fl)) + + def KDEaddflags_c(lenv, fl): + """ Compilation flags for C programs """ + lenv.AppendUnique(CFLAGS = lenv.make_list(fl)) + + def KDEaddflags_link(lenv, fl): + """ Add link flags - Use this if KDEaddlibs below is not enough """ + lenv.PrependUnique(LINKFLAGS = lenv.make_list(fl)) + + def KDEaddlibs(lenv, libs): + """ Helper function """ + lenv.AppendUnique(LIBS = lenv.make_list(libs)) + + def KDEaddpaths_includes(lenv, paths): + """ Add new include paths """ + lenv.AppendUnique(CPPPATH = lenv.make_list(paths)) + + def KDEaddpaths_libs(lenv, paths): + """ Add paths to libraries """ + lenv.PrependUnique(LIBPATH = lenv.make_list(paths)) + + def KDElang(lenv, folder, appname): + """ Process translations (.po files) in a po/ dir """ + transfiles = glob.glob(folder+'/*.po') + for lang in transfiles: + result = lenv.Transfiles(lang) + country = SCons.Util.splitext(result[0].name)[0] + KDEinstallas(lenv, 'KDELOCALE', country+'/LC_MESSAGES/'+appname+'.mo', result) + + def KDEicon(lenv, icname='*', path='./', restype='KDEICONS', subdir=''): + """Contributed by: "Andrey Golovizin" + modified by "Martin Ellis" + + Installs icons with filenames such as cr22-action-frame.png into + KDE icon hierachy with names like icons/crystalsvg/22x22/actions/frame.png. + + Global KDE icons can be installed simply using env.KDEicon('name'). + The second parameter, path, is optional, and specifies the icons + location in the source, relative to the SConscript file. + + To install icons that need to go under an applications directory (to + avoid name conflicts, for example), use e.g. + env.KDEicon('name', './', 'KDEDATA', 'appname/icons')""" + + if env.has_key('DUMPCONFIG'): + print "" % (path+subdir) + return + + type_dic = { 'action' : 'actions', 'app' : 'apps', 'device' : + 'devices', 'filesys' : 'filesystems', 'mime' : 'mimetypes' } + dir_dic = { + 'los' :'locolor/16x16', + 'lom' :'locolor/32x32', + 'him' :'hicolor/32x32', + 'hil' :'hicolor/48x48', + 'lo16' :'locolor/16x16', + 'lo22' :'locolor/22x22', + 'lo32' :'locolor/32x32', + 'hi16' :'hicolor/16x16', + 'hi22' :'hicolor/22x22', + 'hi32' :'hicolor/32x32', + 'hi48' :'hicolor/48x48', + 'hi64' :'hicolor/64x64', + 'hi128':'hicolor/128x128', + 'hisc' :'hicolor/scalable', + 'cr16' :'crystalsvg/16x16', + 'cr22' :'crystalsvg/22x22', + 'cr32' :'crystalsvg/32x32', + 'cr48' :'crystalsvg/48x48', + 'cr64' :'crystalsvg/64x64', + 'cr128':'crystalsvg/128x128', + 'crsc' :'crystalsvg/scalable' + } + + iconfiles = [] + for ext in "png xpm mng svg svgz".split(): + files = glob.glob(path+'/'+'*-*-%s.%s' % (icname, ext)) + iconfiles += files + for iconfile in iconfiles: + lst = iconfile.split('/') + filename = lst[ len(lst) - 1 ] + tmp = filename.split('-') + if len(tmp)!=3: + print RED+'WARNING: icon filename has unknown format: '+iconfile+NORMAL + continue + [icon_dir, icon_type, icon_filename]=tmp + try: + basedir=getInstDirForResType(lenv, restype) + destfile = '%s/%s/%s/%s/%s' % (basedir, subdir, dir_dic[icon_dir], type_dic[icon_type], icon_filename) + except KeyError: + print RED+'WARNING: unknown icon type: '+iconfile+NORMAL + continue + ## Do not use KDEinstallas here, as parsing from an ide will be necessary + if env['_INSTALL']: + env.Alias('install', env.InstallAs( destfile, iconfile ) ) + + ## This function uses env imported above + def docfolder(lenv, folder, lang, destination=""): + # folder is the folder to process + # lang is the language + # destination is the subdirectory in KDEDOC + docfiles = glob.glob(folder+"/???*.*") # file files that are at least 4 chars wide :) + # warn about errors + #if len(lang) != 2: + # print "error, lang must be a two-letter string, like 'en'" + + # when the destination is not given, use the folder + if len(destination) == 0: + destination=folder + docbook_list = [] + for file in docfiles: + # do not process folders + if not os.path.isfile(file): + continue + # do not process the cache file + if file == 'index.cache.bz2': + continue + # ignore invalid files (TODO??) + if len( SCons.Util.splitext( file ) ) <= 1 : + continue + + ext = SCons.Util.splitext( file )[1] + + # docbook files are processed by meinproc + if ext != '.docbook': + continue + docbook_list.append( file ) + lenv.KDEinstall('KDEDOC', lang+'/'+destination, file) + # Now process the index.docbook files .. + if len(docbook_list) == 0: + return + if not os.path.isfile( folder+'/index.docbook' ): + print "Error, index.docbook was not found in "+folder+'/index.docbook' + return + ## Define this to 1 if you are writing documentation else to 0 :) + if env.has_key('i_am_a_documentation_writer'): + for file in docbook_list: + lenv.Depends( folder+'index.cache.bz2', file ) + lenv.Meinproc( folder+'/index.cache.bz2', folder+'/index.docbook' ) + lenv.KDEinstall( 'KDEDOC', lang+'/'+destination, folder+'/index.cache.bz2' ) + + #valid_targets = "program shlib kioslave staticlib".split() + import generic + class kobject(generic.genobj): + def __init__(self, val, senv=None): + if senv: generic.genobj.__init__(self, val, senv) + else: generic.genobj.__init__(self, val, env) + self.iskdelib=0 + def it_is_a_kdelib(self): self.iskdelib=1 + def execute(self): + self.lockchdir() + if self.orenv.has_key('DUMPCONFIG'): + print self.xml() + return + if (self.type=='shlib' or self.type=='kioslave'): + install_dir = 'KDEMODULE' + if self.iskdelib==1: install_dir = 'KDELIB' + self.instdir=getInstDirForResType(self.orenv, install_dir) + self.nodestdir=1 + elif self.type=='program': + self.instdir=getInstDirForResType(self.orenv, 'KDEBIN') + self.nodestdir=1 + + self.src=KDEfiles(env, self.target, self.source) + generic.genobj.execute(self) + self.unlockchdir() + + def xml(self): + ret= '\n' % (self.type, self.chdir, self.target, self.cxxflags, self.cflags, self.includes, self.linkflags, self.libpaths, self.libs, self.vnum, self.iskdelib, self.libprefix) + if self.source: + for i in self.orenv.make_list(self.source): + ret += ' \n' % i + ret += "" + return ret + + # Attach the functions to the environment so that SConscripts can use them + SConsEnvironment.KDEprogram = KDEprogram + SConsEnvironment.KDEshlib = KDEshlib + SConsEnvironment.KDEstaticlib = KDEstaticlib + SConsEnvironment.KDEinstall = KDEinstall + SConsEnvironment.KDEinstallas = KDEinstallas + SConsEnvironment.KDElang = KDElang + SConsEnvironment.KDEicon = KDEicon + + SConsEnvironment.KDEaddflags_cxx = KDEaddflags_cxx + SConsEnvironment.KDEaddflags_c = KDEaddflags_c + SConsEnvironment.KDEaddflags_link = KDEaddflags_link + SConsEnvironment.KDEaddlibs = KDEaddlibs + SConsEnvironment.KDEaddpaths_includes = KDEaddpaths_includes + SConsEnvironment.KDEaddpaths_libs = KDEaddpaths_libs + + SConsEnvironment.docfolder = docfolder + SConsEnvironment.kobject = kobject + diff --git a/bksys/scons-mini.tar.bz2 b/bksys/scons-mini.tar.bz2 new file mode 100644 index 0000000..53d41bb Binary files /dev/null and b/bksys/scons-mini.tar.bz2 differ diff --git a/config.h b/config.h new file mode 100644 index 0000000..b469afd --- /dev/null +++ b/config.h @@ -0,0 +1,6 @@ +/* config.h -- Automatically generated by abakus.py + * Any changes you make to this file will be overwritten! + */ + +/* HAVE_MPFR -- Defined if the MPFR library is being used. */ +/* #undef HAVE_MPFR */ diff --git a/configure b/configure new file mode 100755 index 0000000..9a62bdd --- /dev/null +++ b/configure @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# Configure script for abakus. I think it's better than the sample that came +# with bksys. Feel free to do with it what you want. +# By Michael Pyne + +import sys +import re + +BOLD="\033[1m" +RED="\033[91m" +GREEN="\033[92m" +YELLOW="\033[93m" +CYAN="\033[96m" +NORMAL="\033[0m" + +PROGRAM_NAME='abakus' + +# First let's see if they asked for help. +if '--help' in sys.argv: + print '''This is a configure script to prepare %s for building. + +You can pass the command line argument debug=1 to enable debugging for the +application, which can be useful if you have problems. + +Otherwise, just run ./configure, and if you don't already have scons, a mini +version will be installed suitable for building %s. +'''.replace('%s', PROGRAM_NAME) + sys.exit(0) + +# Check that we have the minimum version of Python needs to run SCons. +if sys.hexversion < 0x02030000: + # Use regexp for compatibility with ancient Python + version = re.split(' ', sys.version)[0] + + print RED + 'Sorry, your version of Python is too old.' + NORMAL + print PROGRAM_NAME + ' requires Python 2.3 or greater to build.' + print "\nYou have Python " + version + sys.exit(1) + +import os + +# Check if scons is installed. We can use cool Python features now that we +# know we aren't using an ancient version of Python. +result = os.system('scons -v > /dev/null 2>&1') +scons = 'scons' + +if os.WEXITSTATUS(result) == 0: + print GREEN + "scons already installed." + NORMAL +else: + # If we didn't find scons, don't whine to the user about it, just fix it. + print YELLOW + 'scons not installed, installing local copy.' + NORMAL + + # Split this into two steps since some tars don't use j to mean bzip2 + # compressed. + result = os.system('bzcat bksys/scons-mini.tar.bz2 | tar x') + + if os.WEXITSTATUS(result) != 0: + print RED + 'Unable to extract scons' + NORMAL + sys.exit(2) + + scons = '%s/scons' % os.getcwd() + +# Now we now where scons is. Let's create a Makefile (after we configure) so +# that all the user has to do is type 'make'. Allow the user to pass command +# line arguments, which will be passed to the configure process. +if len(sys.argv) < 2: + options = '' +else: + options = " ".join(sys.argv[1:]) + # reduce is pretty cool +# options = reduce(lambda x, y: x + '\n' + y, sys.argv[1:]) + +result = os.system(scons + ' configure ' + options) +if os.WEXITSTATUS(result) != 0: + print RED + 'Unable to configure scons' + NORMAL + sys.exit(3) + +# Recursive generates a makefile for a directory. If topDir is True, the +# Makefile is slightly different. +def generate_makefile(dir, topDir = False): + + file = name + "/Makefile" + + # Write out Makefile. + try: + makefile = open(file, 'w') + except IOError: + print RED + "Unable to open " + file + NORMAL + sys.exit(4) + + text = ''' +## Makefile automatically generated by configure + +SCONS=$scons + +# $scons : compile +# $scons -c : clean +# $scons install : install +# $scons -c install : uninstall and clean + +# Default target: use scons to build the program +all: + @$(SCONS) -Q + +# Debugging possibilies: +# $scons --debug=explain, $scons --debug=tree + +# To optimize the runtime: +# $scons --max-drift=1 --implicit-deps-unchanged +debug: + @$(SCONS) -Q --debug=tree + +clean: + @$(SCONS) -c + +install: + @$(SCONS) install + +uninstall: + @$(SCONS) uninstall + +# This target creates a tarball of the project (in theory) +dist: + @$(SCONS) dist +''' + + if topDir: + text = text.replace('$scons', scons) + else: + text = text.replace('$scons', scons + ' -u') + + try: + print "Generating " + GREEN + file + NORMAL + makefile.write(text) + makefile.close() + except IOError: + print RED + "Unable to write to the Makefile!" + NORMAL + sys.exit(5) + +# Recursively generate Makefiles for convienience. +for name, dirs, files in os.walk('.'): + # Don't try to build hidden directories. + remove = filter(lambda x: x[0] == '.', dirs) + for i in remove: + dirs.remove(i) + + if 'SConstruct' in files: + # We're in the very top directory. + generate_makefile(name, topDir = True) + + for dir in ['cache', 'bksys']: + if dir in dirs: + dirs.remove(dir) + elif 'SConscript' in files: + generate_makefile(name) + +# The Makefile has been written, we're pretty much done. +message = ''' +The Makefile(s) have been generated. Type: + `make' to build %s, and + `make install' to install %s. +'''.replace('%s', PROGRAM_NAME) + +print GREEN + message + NORMAL diff --git a/doc/en/SConscript b/doc/en/SConscript new file mode 100644 index 0000000..ef81fcd --- /dev/null +++ b/doc/en/SConscript @@ -0,0 +1,12 @@ +#!/usr/bin/python + +Import('env') + +import glob + +sources = glob.glob("*.png") + +destination = 'abakus' +for lang in ['en']: + for pic in sources: + env.KDEinstall('KDEDOC', "%s/%s" % (lang, destination), pic) diff --git a/doc/en/abakus-degrees-mode.png b/doc/en/abakus-degrees-mode.png new file mode 100644 index 0000000..945ff30 Binary files /dev/null and b/doc/en/abakus-degrees-mode.png differ diff --git a/doc/en/abakus-dnd.png b/doc/en/abakus-dnd.png new file mode 100644 index 0000000..5e7b25c Binary files /dev/null and b/doc/en/abakus-dnd.png differ diff --git a/doc/en/abakus-result.png b/doc/en/abakus-result.png new file mode 100644 index 0000000..76c971c Binary files /dev/null and b/doc/en/abakus-result.png differ diff --git a/doc/en/index.docbook b/doc/en/index.docbook new file mode 100644 index 0000000..6b35998 --- /dev/null +++ b/doc/en/index.docbook @@ -0,0 +1,463 @@ + +abakus"> + + + +]> + + + + +The &abakus; handbook + + + +Michael +Pyne +michael.pyne@kdemail.net + +&J.Hall; &J.Hall.mail; + + + +&FDLNotice; + +2005-07-06 +0.90 + + +&abakus; is a calculator designed with computer usability in mind +Please contact michael.pyne@kdemail.net for bug reports and feature requests + + + +KDE +Calculator + + + + + +What is abakus? +&abakus; is a calculator designed with computer usability in mind, as opposed to just being a clone of your desktop calculator. Instead of using your powerful computer to put a limited simulation of a calculator on-screen, &abakus; instead allows you to use your computer to its greater potential. + +Enough of the metaphysics, how about an example of what I'm talking about? Let's use&kcalc;, the normal &kde; calculator program as an example, trying to calculate 3 multiplied by the sine of 50 degrees. First you would do something like the following: + +1. Make sure you're in Degrees mode: + + + + + +2. Then, click on the 5 and the 0 buttons: + + + + + +3. You would then search for the Sin button and click it, which would immediately calculate a result: + + + + + +4. Then, click the X button, and notice that there is no user feedback whatsoever. Even real calculators temporarily blank the display: + + + + + + +5. Then, we click the 3 button and watch as our previous result suddenly disappears. This isn't &kcalc;s fault: It has nowhere else to display the 3: + + + + + +6. Proceeding boldly forward with the faith that our previous result wasn't lost, we click the = button to perform the multiplication: + + + + + +Although we did get the result, this is unsatisfactory for several reasons. A computer monitor has plenty of room to display text, there's no reason why you should ever be confused about what step your calculator is about to make, or whether it remembered an intermediate result. Computers are also much more powerful than your $10 desktop calculator, so there's no reason that you should be forced to type your expression in a form suitable for your calculator. Roberto Alsina picked up on this in a rant he published, A Modest Usability Improvement + + +Now let's try the same thing in &abakus;, which is designed to help you with your calculating, instead of bending you to its will. As with &kcalc;, extraneous portions of the GUI have been hidden. + +1. We still need to make sure we're in Degrees mode: + + + + + + +2. Now we can type 3sin 50, just as we'd write it on paper. Sometimes it's better to clarify things, both for reading and to clarify your intentions to &abakus;. So you can also use the parentheses to group operations like you would on paper, and the '*' operator to explicitly multiply, like this: "3 * sin (50)": + + + + + + And we're all done! We even typed in the same expression two different ways to demonstrate how &abakus; will try very hard to guess what you're trying to calculate. You'll notice that &kcalc; and &abakus; both agree on the answer. + +If you're still reading you've probably been sold by Roberto's argument, just as I was when I started writing &abakus;. So read on, if you want to find out all that &abakus; can do for you. + + + +How to use &abakus; + + +Basic usage +The basic synposis is: Type your expression, and hit Enter. &abakus; will calculate the value of what you typed and display it on the screen. You can use many functions from mathematics, and even define your own functions. You can also define and use variables. + +You can define your own functions in abakus. To do so, at the expression prompt, you would type something like: set funcname(var) = expr and hit Enter. If all went well &abakus; will simply output OK, and you'll see your function appear in the user-defined function list. Now you can use your function as normal. If you'd like to remove your function, you can either right-click on it in the user function list and select Remove Function, or enter remove funcname() in the expression prompt and hit Enter. Note that you don't enter the variable name in the parentheses since only the function name is needed. (The reason you still need the parentheses is because your variables can have the same name as a function). + +You can also define your own variables. &abakus; comes with the basic mathematical constants pi (π) and e (Euler's Constant) defined by default. To define your own variable, at the expression prompt you would type: name = value, or set name = value. You will then see your variable in the list of variables. To remove your variable, either right-click on it in the list and select Remove Variable, or enter remove varname in the expression prompt. Notice that there are no parentheses this time. ;-) + + +Placeholder Variables + +You may have noticed that when you type in expressions, &abakus; will show a value beginning with $ (such as $0) after the result. This is a placeholder variable. What happens is that the most recent result is always $0. The result directly before is $1, and so on. You may use the placeholder values in your expression to avoid having to re-type it or use the drag-and-drop. Note that there is a special variable defined called ans, which is the same as $0. In other words, whenever you want to reference the last expression's result, you can use $0 or ans. + + + + +Decimal Precision + +&abakus; supports high-precision arithmetic using Ariya Hidayat's hmath code from his excellent calculator SpeedCrunch. You can change the displayed precision by using the View Menu, where you can select between Automatic precision, or some pre-defined precision levels. You can also select Custom precision to select your own precision (between 0-75 digits). + + + +Operators +&abakus; supports all the standard operators like -, +, *, and /. It also supports both the ^ and ** symbols to mean exponentiation. Exponentiation is right-associative in &abakus;, meaning that 2^3^2 will return 512 instead of 64. (2^(3^2)). Operator precedence is performed as in mathematics as well (e.g. 2 + 3 * 2 and 3 * 2 + 2 both return the same answer). &abakus; also supports parentheses to group expressions for when the default operator precedence is invalid. + + + +Functions +&abakus; has quite a few functions built-in: + +sin, cos, tan: Trigonometric functions. Supports Degrees and Radians mode. +asin, acos, atan: Inverse trigonometric functions. Supports Degrees and Radians mode. +abs: The absolute value of a number. +sqrt: Square root of a number. +ln / log: Logarithms. ln uses the "natural base", e, which log uses base 10. +exp: Exponential. Returns e to the given power. exp(x) is equivalent to e^x. +round, ceil, floor, int: Converts an answer to an integer. ceil rounds to the next highest integer, while floor rounds to the next lower. int simply drops the fractional part. round rounds to the nearest integer. +frac: Returns the fractional part of a number. +sinh, cosh, tanh: Hyperbolic trigonometric functions. +deriv: Returns the numerical derivative of the given expression. The graphical interpretation of a derivative is the slope of the given function, at the given point. It is used like this: deriv(exp, pt). Note that since deriv takes two arguments that the parentheses are required to avoid ambiguity. For most functions, the value that deriv returns will be exact (at least within the bounds allowed by the underlying decimal representation). + + + + + + + + +Advanced Features + +&abakus; supports some features not typically seen in computer +calculators. + + +RPN Mode +RPN Mode is a different input method for &abakus;, designed to emulate the +input style of various old calculators which are still popular. If you do not +already know what RPN Mode is, &abakus; is not the best way to find out. +However, I will give a brief description of it. + + +In RPN Mode, the calculator operates from what is called the stack. +Number are always added to the end of the stack, and operators always work from +the end of the stack. One nice thing about RPN (and the +reason it was developed in the first place) is that RPN expressions don't +require parentheses, since the order of operations is completely unambiguous. + + +So the way things work is that in RPN mode, you type in numbers and operators separated by +spaces. For each number you type, &abakus; will add to the end of the stack. +Every time &abakus; encounters an operator, &abakus; will remove the appropriate +number of values from the end of the stack, apply the operator, and then place +the result back on the end of the stack. &abakus; continues in this fashion +until it reaches the end of your expression, and then returns whatever value +is left at the top of the stack as its result. + +Let's see how this works with an example: + + + + +2 3 4 * + 2 / would return +7 + + +The way that this works is that first 2, and then +3 are added to the end of the +stack. 4 is read and is also +added to the end. So at this point there are 3 numbers on the +stack. When &abakus; reads the operator +*, it removes 3 and 4 from the end of the stack and +multiplies them, resulting in 12. 12 is then added to the end of the stack, and +the stack has 2 numbers (2 and 12). + + +&abakus; then reads the + and performs the same +process, adding 12 and 2, and replacing them in the stack with 14. +2 is then added to the end, and then &abakus; performs +the division of 14 by 2, leaving 7 on the stack, which becomes the final +result. + + + + +You can also use functions in place of operators. For example, +pi 2 / sin would calculate the value of sin(pi / 2). + + +If the stack has more than one element by the end +of the expression, &abakus; will only remove the value at the end. The values +left in the stack will be retained for later +calculations. You can see how many values are on the +stack using the special variable +stackCount which appears in the +Variable List while in RPN mode. + + +&abakus; supports a few special commands while in RPN mode, that affect +the stack as follows: + + + +pop, which removes the value on the end +of the stack. + +clear, which clears the +stack completely. + + + + +The set and remove commands +are currently unsupported in RPN Mode. + + + + +Drag and Drop + +You can drag and drop results to other applications. When doing so, +&abakus; will construct an image for the mouse cursor which includes the text +you are dragging so that you always can tell exactly what you're about to drop +into an application. + + +Demonstration of &abakus; drag and drop + + + + + +Demonstration of &abakus; drag and drop + + + + + + + + +Command Reference + +Here is a brief description of the commands in the &abakus; interface. + + + +Menu Commands + + + + +&Ctrl;Q +FileQuit + +Quit &abakus; + + + + + + +&Alt;&Shift;H +ViewShow History List + +If checked, &abakus; will show the list of previous results. + + + + + +&Alt;&Shift;V +ViewShow Variable List + +If checked, &abakus; will show the list of variables. + + + + + +&Alt;&Shift;F +ViewShow Function List + +If checked, &abakus; will show the list of functions. + + + + + +ViewAutomatic Precision + +If checked, &abakus; will select a precision automatically. + + + + + +View3 Decimal Digits + +If checked, &abakus; will 8 decimal digits of precision (e.g. +1 / 3 = 0.333). + + + + + +View8 Decimal Digits + +If checked, &abakus; will show 8 decimal digits of precision (e.g. +1 / 3 = 0.33333333). + + + + + +View15 Decimal Digits + +If checked, &abakus; will show 15 decimal digits of precision. + + + + + +View50 Decimal Digits + +If checked, &abakus; will show 50 decimal digits of precision. + + + + + +ViewCustom Precision... + +This command will bring open a dialog allowing you to enter a +custom precision level. &abakus; supports precision levels from 0 to 75 +decimal digits. + + + + + +&Alt;&Shift;C +ViewActivate Compact Mode + +This command is a shortcut to disable the Result, Function, +and Variable views, so that &abakus; shows just the input line. In this mode, +the answer is given in the input line instead of the Result list. + + + + + +&Alt;&Shift;L +ViewClear History + +This command clears the Result view. + + + + + + + + + +&Alt;&Shift;D +ModeDegrees + +This command causes &abakus; to treat values for the +trigonometric functions as angles measured in degrees (360 degrees make up a +full circle). +The deriv() function will not return correct +results for trigonometric functions in this mode. + + + + + +&Alt;&Shift;R +ModeRadians + +This command causes &abakus; to treat values for the +trigonometric functions as angles measured in radians (2 * π radians make up +a full circle). +The deriv() function only returns correct +results for trigonometric functions when in this mode. + + + + + +&Alt;&Shift;P +ModeUse RPN Mode + +This command enables RPN Mode. + + + + + + + + + + +Limitations + +There are many cool things yet to add to &abakus;. Here's a partial list of things that &abakus; doesn't support: + + +Complex numbers. +User-defined functions of more than one variable. +Unit analysis (for example: 3 A / 1.5 ohm -> 1.5 V) +Advanced input as in SpeedCrunch. +Numerical integration (finding the area under a given curve). +Graphing (? - I'll admit I'm not sure if this would be a great fit for &abakus;) +Matrices +Functions on lists (e.g. sin {0, pi / 2} -> {0, 1}) +Session export/import (the session is still saved/loaded automatically). +More functions. Although many functions that aren't built-in can be simulated. For instance, to take the logarithm of x to a different base (b), you could do (ln x / ln b). And the xth root of a number is just that number raised to the (1 / x) power. + + + + + +Credits and License + +&abakus; +Program copyright 2005 Michael Pyne +Original documentation copyright 2005 Michael Pyne +Docbook conversion by &J.Hall; &J.Hall.mail; + +&underFDL; + + diff --git a/doc/en/kcalc-degrees-mode.png b/doc/en/kcalc-degrees-mode.png new file mode 100644 index 0000000..0f737ec Binary files /dev/null and b/doc/en/kcalc-degrees-mode.png differ diff --git a/doc/en/kcalc-fifty.png b/doc/en/kcalc-fifty.png new file mode 100644 index 0000000..39a3082 Binary files /dev/null and b/doc/en/kcalc-fifty.png differ diff --git a/doc/en/kcalc-result.png b/doc/en/kcalc-result.png new file mode 100644 index 0000000..85410c0 Binary files /dev/null and b/doc/en/kcalc-result.png differ diff --git a/doc/en/kcalc-sine.png b/doc/en/kcalc-sine.png new file mode 100644 index 0000000..4be5726 Binary files /dev/null and b/doc/en/kcalc-sine.png differ diff --git a/doc/en/kcalc-three.png b/doc/en/kcalc-three.png new file mode 100644 index 0000000..f58cffa Binary files /dev/null and b/doc/en/kcalc-three.png differ diff --git a/src/SConscript b/src/SConscript new file mode 100644 index 0000000..66e701d --- /dev/null +++ b/src/SConscript @@ -0,0 +1,81 @@ +#! /usr/bin/env python +## This script demonstrates how to build and install +## a simple kde program having KconfigXT settings +## with scons +## +## Thomas Nagy, 2004, 2005 + +## This file can be reused freely for any project (see COPYING) + +############################ +## load the config + +## Use the environment and the tools set in the top-level +## SConstruct file (set with 'Export') - this is very important + +Import( 'env' ) +myenv=env.Copy() + +############################# +## the programs to build + +# The sources for our program - only .ui, .skel and .cpp are accepted +abakus_sources = """ +abakus.cpp +abakuslistview.cpp +dragsupport.cpp +editor.cpp +evaluator.cpp +function.cpp +lexer_lex.cpp +mainwindow.cpp +node.cpp +numerictypes.cpp +parser_yacc.cpp +result.cpp +resultlistview.cpp +resultlistviewtext.cpp +rpnmuncher.cpp +valuemanager.cpp +dcopIface.skel +""" + +if myenv.get('mpfr', 'no') == 'yes': + myenv.Append(LIBS = ['mpfr', 'gmp']) +else: + abakus_sources = abakus_sources + " hmath.cpp number.c" + +myenv.KDEprogram( "abakus", abakus_sources ) +myenv.KDEicon( 'abakus' ) + +# Mark these as being created by flex and bison if it's installed. +if myenv.Dictionary().has_key('PARSER_INCLUDED'): + myenv.CXXFile( "lexer_lex.cpp", "lexer.ll" ) + myenv.CXXFile( "parser_yacc.cpp", "parser.yy", YACCFLAGS="-d" ) + +if myenv['HAVE_ASNEEDED']: + myenv.Append(LINKFLAGS = '-Wl,--as-needed') + +myenv.Append(CXXFLAGS = '-Wno-non-virtual-dtor') + +############################ +## Customization + +## Additional include paths for compiling the source files +## Always add '../' (top-level directory) because moc makes code that needs it +myenv.KDEaddpaths_includes('#/src/ #/') + +## Necessary libraries to link against +myenv.KDEaddlibs( 'qt-mt kio kdecore kdeprint kdeui' ) + +############################# +## Data to install + +## The ui.rc file and the tips go into datadir/appname/ +myenv.KDEinstall( 'KDEDATA', 'abakus', 'abakusui.rc' ) + +## Warning : there is a difference between the normal destop file used for the menu +## and the servicetype desktop file, so they go in different directories +## you will find more information in 'test3' +myenv.KDEinstall( 'KDEMENU', 'Utilities', 'abakus.desktop') + diff --git a/src/abakus.cpp b/src/abakus.cpp new file mode 100644 index 0000000..6200641 --- /dev/null +++ b/src/abakus.cpp @@ -0,0 +1,74 @@ +/* + * abakus.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include + +#include + +#if HAVE_MPFR +#include +#endif + +#include "mainwindow.h" + +const char *const version = "0.91"; + +int main(int argc, char **argv) +{ + KAboutData *about = new KAboutData("abakus", I18N_NOOP("abakus"), version, + I18N_NOOP("A simple keyboard-driven calculator"), KAboutData::License_GPL, + "(c) 2004, 2005 Michael Pyne", 0 /* text */, "http://grammarian.homelinux.net/abakus/", + "michael.pyne@kdemail.net"); + + about->addAuthor("Michael Pyne", + I18N_NOOP("Developer"), + "michael.pyne@kdemail.net", + "http://grammarian.homelinux.net/"); + about->addCredit("Ariya Hidayat", + I18N_NOOP("High precision math routines, and inspiration for the new design came from his C++ implementation (SpeedCrunch)"), + "ariya@kde.org", + "http://speedcrunch.berlios.de/"); + about->addCredit("Roberto Alsina", + I18N_NOOP("Came up with the initial idea, along with a Python implementation."), + "ralsina@netline.com.ar", + "http://dot.kde.org/1096309744/"); + about->addCredit("Zack Rusin", + I18N_NOOP("Inspiration/code for the initial design came from his Ruby implementation."), + "zack@kde.org"); + +#if HAVE_MPFR + mpfr_set_default_prec(6 * 78); // 78 digits, figure about 6 bits needed. + kdDebug() << "Using the MPFR high-precision library.\n"; +#else + kdDebug() << "Using the internal high-precision library.\n"; +#endif + + KCmdLineArgs::init(argc, argv, about); + KApplication app; + MainWindow *win = new MainWindow; + + app.setMainWidget(win); + app.connect(&app, SIGNAL(lastWindowClosed()), SLOT(quit())); + win->show(); + win->resize(500, 300); + + return app.exec(); +} diff --git a/src/abakus.desktop b/src/abakus.desktop new file mode 100644 index 0000000..282bc4e --- /dev/null +++ b/src/abakus.desktop @@ -0,0 +1,38 @@ +# KDE Config File +[Desktop Entry] +Encoding=UTF-8 +Type=Application +X-KDE-StartupNotify=true +Exec=abakus -caption "%c" %i %m +Icon=abakus +Comment= +Comment[xx]=xxxx +Terminal=0 +Name=Abakus +Name[tr]=Abaküs +Name[xx]=xxAbakusxx +GenericName=Calculator +GenericName[bg]=Калкулатор +GenericName[br]=Jederez +GenericName[cs]=Kalkulátor +GenericName[cy]=Cyfrifiannell +GenericName[da]=Lommeregner +GenericName[el]=Υπολογιστής Τσέπης +GenericName[es]=Calculadora +GenericName[et]=Kalkulaator +GenericName[fr]=Calculateur +GenericName[ga]=Áireamhán +GenericName[gl]=Calculadora +GenericName[it]=Calcolatrice +GenericName[ka]=კალკულატორი +GenericName[lt]=Skaičiuotuvas +GenericName[nl]=Rekenmachine +GenericName[pt]=Calculadora +GenericName[rw]=Mubazi +GenericName[sk]=Kalkulačka +GenericName[sr]=Рачунаљка +GenericName[sr@Latn]=Računaljka +GenericName[sv]=Miniräknare +GenericName[tr]=Hesap Makinesi +GenericName[xx]=xxCalculatorxx +Categories=Qt;KDE;Utility diff --git a/src/abakuscommon.h b/src/abakuscommon.h new file mode 100644 index 0000000..83a71f2 --- /dev/null +++ b/src/abakuscommon.h @@ -0,0 +1,22 @@ +// header file for pch support. +#if defined __cplusplus + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "function.h" +#include "node.h" +#include "valuemanager.h" + +#endif diff --git a/src/abakuslistview.cpp b/src/abakuslistview.cpp new file mode 100644 index 0000000..964c8a7 --- /dev/null +++ b/src/abakuslistview.cpp @@ -0,0 +1,232 @@ +/* + * abakuslistview.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include +#include +#include + +#include +#include +#include + +#include "dragsupport.h" +#include "abakuslistview.h" +#include "valuemanager.h" +#include "function.h" + +ListView::ListView(QWidget *parent, const char *name) : + KListView(parent, name), m_menu(0), m_usePopup(false), m_removeSingleId(0), + m_removeAllId(0) +{ + setResizeMode(LastColumn); + setDragEnabled(true); + + connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), + SLOT(rightClicked(QListViewItem *, const QPoint &))); +} + +QDragObject *ListView::dragObject() +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + QListViewItem *item = itemAt(viewportPos); + + if(!item) + return 0; + + int column = header()->sectionAt(viewportPos.x()); + QString dragText = item->text(column); + + QDragObject *drag = new QTextDrag(dragText, this, "list item drag"); + drag->setPixmap(DragSupport::makePixmap(dragText, font())); + + return drag; +} + +void ListView::enablePopupHandler(bool enable) +{ + if(enable == m_usePopup) + return; + + m_usePopup = enable; + + if(m_usePopup) { + if(m_menu) + kdError() << "ListView menu shouldn't exist here!\n"; + + m_menu = new KPopupMenu(this); + + m_removeSingleId = m_menu->insertItem(removeItemString(), this, SLOT(removeSelected())); + m_removeAllId = m_menu->insertItem("Placeholder", this, SLOT(removeAllItems())); + } + else { + delete m_menu; + m_menu = 0; + } +} + +QString ListView::removeItemString() const +{ + return QString(); +} + +QString ListView::removeAllItemsString(unsigned count) const +{ + Q_UNUSED(count); + + return QString(); +} + +void ListView::removeSelectedItem(QListViewItem *item) +{ + Q_UNUSED(item); +} + +void ListView::removeAllItems() +{ +} + +bool ListView::isItemRemovable(QListViewItem *item) const +{ + Q_UNUSED(item); + + return false; +} + +void ListView::rightClicked(QListViewItem *item, const QPoint &pt) +{ + if(!m_usePopup) + return; + + m_menu->setItemEnabled(m_removeSingleId, item && isItemRemovable(item)); + m_menu->changeItem(m_removeAllId, removeAllItemsString(childCount())); + m_menu->popup(pt); +} + +void ListView::removeSelected() +{ + removeSelectedItem(selectedItem()); +} + +ValueListViewItem::ValueListViewItem(QListView *listView, const QString &name, + const Abakus::number_t &value) : + KListViewItem(listView, name), m_value(value) +{ + valueChanged(); +} + +void ValueListViewItem::valueChanged() +{ + setText(1, m_value.toString()); + repaint(); +} + +void ValueListViewItem::valueChanged(const Abakus::number_t &newValue) +{ + m_value = newValue; + + valueChanged(); +} + +Abakus::number_t ValueListViewItem::itemValue() const +{ + return m_value; +} + +VariableListView::VariableListView(QWidget *parent, const char *name) : + ListView(parent, name) +{ + enablePopupHandler(true); +} + +QString VariableListView::removeItemString() const +{ + return i18n("Remove selected variable"); +} + +QString VariableListView::removeAllItemsString(unsigned count) const +{ + // count is unreliable because not all of the elements in the list view + // can be removed. + count = 0; + QStringList values = ValueManager::instance()->valueNames(); + + for(QStringList::ConstIterator value = values.constBegin(); value != values.constEnd(); ++value) + if(!ValueManager::instance()->isValueReadOnly(*value)) + ++count; + + return i18n("Remove all variables (1 variable)", + "Remove all variables (%n variables)", + count); +} + +bool VariableListView::isItemRemovable(QListViewItem *item) const +{ + return !ValueManager::instance()->isValueReadOnly(item->text(0)); +} + +void VariableListView::removeSelectedItem(QListViewItem *item) +{ + ValueManager::instance()->removeValue(item->text(0)); +} + +void VariableListView::removeAllItems() +{ + ValueManager::instance()->slotRemoveUserVariables(); +} + +FunctionListView::FunctionListView(QWidget *parent, const char *name) : + ListView(parent, name) +{ + enablePopupHandler(true); +} + +QString FunctionListView::removeItemString() const +{ + return i18n("Remove selected function"); +} + +QString FunctionListView::removeAllItemsString(unsigned count) const +{ + return i18n("Remove all functions (1 function)", + "Remove all functions (%n functions)", + count); +} + +bool FunctionListView::isItemRemovable(QListViewItem *item) const +{ + return true; +} + +void FunctionListView::removeSelectedItem(QListViewItem *item) +{ + // Use section to get the beginning of the string up to (and not + // including) the first ( + QString name = item->text(0).section('(', 0, 0); + + FunctionManager::instance()->removeFunction(name); +} + +void FunctionListView::removeAllItems() +{ + QStringList fns = FunctionManager::instance()->functionList(FunctionManager::UserDefined); + + for(QStringList::ConstIterator fn = fns.constBegin(); fn != fns.constEnd(); ++fn) + FunctionManager::instance()->removeFunction(*fn); +} + +#include "abakuslistview.moc" diff --git a/src/abakuslistview.h b/src/abakuslistview.h new file mode 100644 index 0000000..1712b6e --- /dev/null +++ b/src/abakuslistview.h @@ -0,0 +1,147 @@ +#ifndef ABAKUS_LISTVIEW_H +#define ABAKUS_LISTVIEW_H +/* + * abakuslistview.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include + +#include "numerictypes.h" + +class KPopupMenu; + +class ListView : public KListView +{ + Q_OBJECT + + public: + ListView(QWidget *parent, const char *name = 0); + + protected: + virtual QDragObject *dragObject(); + + /** + * Used to enable fancy popup handling support in subclasses. Subclasses + * also need to reimplement a few functions if they want to use this. + * + * This should be called in the subclass's constructor. + */ + void enablePopupHandler(bool enable); + + /** + * If using the popup menu handling, the subclass needs to return a + * translated string of the form "Remove selected ". + */ + virtual QString removeItemString() const; + + /** + * If using the popup menu handling, the subclass needs to return a + * translated string of the form "Remove all s." I recommend + * also appending a " (%n s), which you can use the @p count + * parameter for. + */ + virtual QString removeAllItemsString(unsigned count) const; + + protected slots: + /** + * If using the popup menu handing, the subclass needs to reimplement this + * function to remove the selected item, which is passed in as a + * parameter. + */ + virtual void removeSelectedItem(QListViewItem *item); + + /** + * If using the popup menu handling, the subclass needs to reimplement this + * function to remove all items. + */ + virtual void removeAllItems(); + + /** + * If using the popup menu handling, this function may be called to + * determine whether the selected item given by @p item is removable. + */ + virtual bool isItemRemovable(QListViewItem *item) const; + + private slots: + void rightClicked(QListViewItem *item, const QPoint &pt); + void removeSelected(); + + private: + KPopupMenu *m_menu; + bool m_usePopup; + + int m_removeSingleId; + int m_removeAllId; +}; + +class ValueListViewItem : public KListViewItem +{ + public: + ValueListViewItem (QListView *listView, const QString &name, const Abakus::number_t &value); + + // Will cause the list item to rethink the text. + void valueChanged(); + void valueChanged(const Abakus::number_t &newValue); + + Abakus::number_t itemValue() const; + + private: + Abakus::number_t m_value; +}; + +/** + * Subclass used for the list of variables. + */ +class VariableListView : public ListView +{ + Q_OBJECT + public: + + VariableListView(QWidget *parent, const char *name = 0); + + protected: + virtual QString removeItemString() const; + virtual QString removeAllItemsString(unsigned count) const; + virtual bool isItemRemovable(QListViewItem *item) const; + + protected slots: + virtual void removeSelectedItem(QListViewItem *item); + virtual void removeAllItems(); +}; + +/** + * Subclass used for the list of functions. + */ +class FunctionListView : public ListView +{ + Q_OBJECT + public: + + FunctionListView(QWidget *parent, const char *name = 0); + + protected: + virtual QString removeItemString() const; + virtual QString removeAllItemsString(unsigned count) const; + virtual bool isItemRemovable(QListViewItem *item) const; + + protected slots: + virtual void removeSelectedItem(QListViewItem *item); + virtual void removeAllItems(); +}; + +#endif diff --git a/src/abakusui.rc b/src/abakusui.rc new file mode 100644 index 0000000..395323e --- /dev/null +++ b/src/abakusui.rc @@ -0,0 +1,35 @@ + + + + &View + + + + + + + + + + + + + + + + + + + + + + &Mode + + + + + + + + + diff --git a/src/dcopIface.h b/src/dcopIface.h new file mode 100644 index 0000000..505f411 --- /dev/null +++ b/src/dcopIface.h @@ -0,0 +1,47 @@ +#ifndef ABAKUS_DCOP_IFACE +#define ABAKUS_DCOP_IFACE +/* + * dcopIface.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include + +#include "mainwindow.h" +#include "numerictypes.h" +#include "function.h" + +class AbakusIface : virtual public DCOPObject +{ + K_DCOP + public: + AbakusIface() : DCOPObject("Calculator") + { + } + + k_dcop: + virtual double evaluate(const QString &expr) + { + Abakus::number_t result = parseString(expr.latin1()); + return result.asDouble(); + } +}; + +#endif diff --git a/src/dragsupport.cpp b/src/dragsupport.cpp new file mode 100644 index 0000000..0a8b875 --- /dev/null +++ b/src/dragsupport.cpp @@ -0,0 +1,87 @@ +/* + * dragsupport.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dragsupport.h" + +namespace DragSupport +{ + +QPixmap makePixmap(const QString &text, const QFont &font) +{ + QColor background(234, 178, 230); + QFontMetrics fm(font); + + int height = 2 * fm.height(); + QSize bonusSize (height, 0); + QSize size(fm.width(text), height); + QImage image(size + bonusSize, 32); + + image.setAlphaBuffer(false); + image.fill(0); // All transparent pixels + image.setAlphaBuffer(true); + + QPixmap pix(size + bonusSize); + pix.fill(Qt::magenta); // Watch for incoming hacks + + QPainter painter(&pix); + painter.setFont(font); + + // Outline black, background white + painter.setPen(Qt::black); + painter.setBrush(background); + + // roundRect is annoying in that the four "pies" in each corner aren't + // circular, they're elliptical. Try to make the radii force it circular + // again. + painter.drawRoundRect(pix.rect(), 75 * pix.height() / pix.width(), 75); + + // Alias better names for some constants. + int textLeft = height / 2; + + // Draw text + painter.setPen(Qt::black); + painter.drawText(textLeft, height / 4, size.width(), size.height(), 0, text); + + QImage overlay(pix.convertToImage()); + + // The images should have the same size, copy pixels from overlay to the + // bottom unless the pixel is called magenta. The pixels we don't copy + // are transparent in the QImage, and will remain transparent when + // converted to a QPixmap. + + for(int i = 0; i < image.width(); ++i) + for(int j = 0; j < image.height(); ++j) { + if(QColor(overlay.pixel(i, j)) != Qt::magenta) + image.setPixel(i, j, overlay.pixel(i, j)); + } + + pix.convertFromImage(image); + return pix; +} + +} // DragSupport diff --git a/src/dragsupport.h b/src/dragsupport.h new file mode 100644 index 0000000..76ebe81 --- /dev/null +++ b/src/dragsupport.h @@ -0,0 +1,33 @@ +#ifndef ABAKUS_DRAGSUPPORT_H +#define ABAKUS_DRAGSUPPORT_H +/* + * dragsupport.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +class QPixmap; +class QString; +class QFont; + +namespace DragSupport { + + QPixmap makePixmap(const QString &text, const QFont &font); + +} +#endif + +// vim: set et ts=8 sw=4: diff --git a/src/editor.cpp b/src/editor.cpp new file mode 100644 index 0000000..1ae425f --- /dev/null +++ b/src/editor.cpp @@ -0,0 +1,892 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004,2005 Ariya Hidayat + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#include "function.h" +#include "valuemanager.h" +#include "editor.h" +#include "evaluator.h" +#include "result.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // netwm.h includes X11 headers which conflict with qevent +#include + +#include + +#include + +// XXX: QT 4: Replace this with qBinaryFind(). +using std::binary_search; + +class CalcResultLabel : public QLabel +{ +public: + CalcResultLabel(QWidget *parent, const char *name, int WFlags) : + QLabel(parent, name, WFlags) + { + } + +protected: + virtual void mousePressEvent(QMouseEvent *) + { + hide(); + } +}; + +class EditorHighlighter : public QSyntaxHighlighter +{ +public: + EditorHighlighter( Editor* ); + int highlightParagraph ( const QString & text, int ); + +private: + Editor* editor; +}; + +class Editor::Private +{ +public: + Evaluator* eval; + QStringList history; + int index; + bool autoCompleteEnabled; + EditorCompletion* completion; + QTimer* completionTimer; + bool autoCalcEnabled; + char format; + int decimalDigits; + QTimer* autoCalcTimer; + QLabel* autoCalcLabel; + bool syntaxHighlightEnabled; + EditorHighlighter* highlighter; + QMap highlightColors; + QTimer* matchingTimer; +}; + +class EditorCompletion::Private +{ +public: + Editor* editor; + QVBox *completionPopup; + QListBox *completionListBox; +}; + +class ChoiceItem: public QListBoxText +{ + public: + ChoiceItem( QListBox*, const QString& ); + void setMinNameWidth (int w) { minNameWidth = w; } + int nameWidth() const; + + protected: + void paint( QPainter* p ); + + private: + QString item; + QString desc; + int minNameWidth; +}; + +ChoiceItem::ChoiceItem( QListBox* listBox, const QString& text ): + QListBoxText( listBox, text ), minNameWidth(0) +{ + QStringList list = QStringList::split( ':', text ); + if( list.count() ) item = list[0]; + if( list.count()>1 ) desc = list[1]; +} + +// Returns width of this particular list item's name. +int ChoiceItem::nameWidth() const +{ + if(item.isEmpty()) + return 0; + + QFontMetrics fm = listBox()->fontMetrics(); + return fm.width( item ); +} + +void ChoiceItem::paint( QPainter* painter ) +{ + int itemHeight = height( listBox() ); + QFontMetrics fm = painter->fontMetrics(); + int yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent(); + painter->drawText( 3, yPos, item ); + + //int xPos = fm.width( item ); + int xPos = QMAX(fm.width(item), minNameWidth); + if( !isSelected() ) + painter->setPen( listBox()->palette().disabled().text().dark() ); + painter->drawText( 10 + xPos, yPos, desc ); +} + +EditorHighlighter::EditorHighlighter( Editor* e ): + QSyntaxHighlighter( e ) +{ + editor = e; +} + +int EditorHighlighter::highlightParagraph ( const QString & text, int ) +{ + if( !editor->isSyntaxHighlightEnabled() ) + { + setFormat( 0, text.length(), editor->colorGroup().text() ); + return 0; + } + + QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); + fnames.sort(); // Sort list so we can bin search it. + + Tokens tokens = Evaluator::scan( text ); + for( unsigned i = 0; i < tokens.count(); i++ ) + { + Token& token = tokens[i]; + QString text = token.text().lower(); + QFont font = editor->font(); + QColor color = Qt::black; + switch( token.type() ) + { + case Token::Number: + color = editor->highlightColor( Editor::Number ); + break; + + case Token::Identifier: + { + color = editor->highlightColor( Editor::Variable ); + if( binary_search( fnames.constBegin(), fnames.constEnd(), text) ) { + color = editor->highlightColor( Editor::FunctionName ); + } + } + break; + + case Token::Operator: + break; + + default: break; + }; + if( token.pos() >= 0 ) { + setFormat( token.pos(), token.text().length(), font, color ); + } + } + return 0; +} + + + +Editor::Editor( QWidget* parent, const char* name ): + QTextEdit( parent, name ) +{ + d = new Private; + d->eval = 0; + d->index = 0; + d->autoCompleteEnabled = true; + d->completion = new EditorCompletion( this ); + d->completionTimer = new QTimer( this ); + d->autoCalcEnabled = true; + d->syntaxHighlightEnabled = true; + d->highlighter = new EditorHighlighter( this ); + d->autoCalcTimer = new QTimer( this ); + d->matchingTimer = new QTimer( this ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + setWordWrap( NoWrap ); + setHScrollBarMode( AlwaysOff ); + setVScrollBarMode( AlwaysOff ); + setTextFormat( PlainText ); + setAutoFormatting( AutoNone ); + setTabChangesFocus( true ); + setLinkUnderline( false ); + + connect( d->completion, SIGNAL( selectedCompletion( const QString& ) ), + SLOT( autoComplete( const QString& ) ) ); + connect( this, SIGNAL( textChanged() ), SLOT( checkAutoComplete() ) ); + connect( d->completionTimer, SIGNAL( timeout() ), SLOT( triggerAutoComplete() ) ); + + connect( this, SIGNAL( textChanged() ), SLOT( checkMatching() ) ); + connect( d->matchingTimer, SIGNAL( timeout() ), SLOT( doMatchingLeft() ) ); + connect( d->matchingTimer, SIGNAL( timeout() ), SLOT( doMatchingRight() ) ); + connect( this, SIGNAL( textChanged() ), SLOT( checkAutoCalc() ) ); + connect( d->autoCalcTimer, SIGNAL( timeout() ), SLOT( autoCalc() ) ); + d->autoCalcLabel = new CalcResultLabel( 0, "autocalc", WStyle_StaysOnTop | + WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM ); + d->autoCalcLabel->setFrameStyle( QFrame::Plain | QFrame::Box ); + d->autoCalcLabel->setPalette( QToolTip::palette() ); + d->autoCalcLabel->hide(); + + setHighlightColor( Number, QColor(0,0,127) ); + setHighlightColor( FunctionName, QColor(85,0,0) ); + setHighlightColor( Variable, QColor(0,85,0) ); + setHighlightColor( MatchedPar, QColor(255,255,183) ); +} + +Editor::~Editor() +{ + d->autoCalcLabel->hide(); + delete d; +} + +QSize Editor::sizeHint() const +{ + constPolish(); + QFontMetrics fm = fontMetrics(); + int h = QMAX(fm.lineSpacing(), 14); + int w = fm.width( 'x' ) * 20; + int m = frameWidth() * 2; + return( style().sizeFromContents(QStyle::CT_LineEdit, this, + QSize( w + m, h + m + 4 ). + expandedTo(QApplication::globalStrut()))); +} + +QStringList Editor::history() const +{ + return d->history; +} + +void Editor::setHistory( const QStringList& h ) +{ + d->history = h; + d->index = d->history.count(); +} + +bool Editor::autoCompleteEnabled() const +{ + return d->autoCompleteEnabled; +} + +void Editor::setAutoCompleteEnabled( bool enable ) +{ + d->autoCompleteEnabled = enable; +} + +bool Editor::autoCalcEnabled() const +{ + return d->autoCalcEnabled; +} + +void Editor::setAutoCalcEnabled( bool enable ) +{ + d->autoCalcEnabled = enable; +} + +void Editor::setFormat( char format ) +{ + d->format = format; +} + +void Editor::setDecimalDigits( int digits ) +{ + d->decimalDigits = digits; +} + +void Editor::appendHistory( const QString& text ) +{ + if( text.isEmpty() ) return; + + QString lastText; + if( d->history.count() ) + lastText = d->history[ d->history.count()-1 ]; + if( text == lastText ) return; + + d->history.append( text ); + d->index = d->history.count()-1; +} + +void Editor::clearHistory() +{ + d->history.clear(); + d->index = 0; +} + +void Editor::squelchNextAutoCalc() +{ + d->autoCalcTimer->stop(); +} + +void Editor::setText(const QString &txt) +{ + QTextEdit::setText(txt); + squelchNextAutoCalc(); +} + +void Editor::checkAutoComplete() +{ + if( !d->autoCompleteEnabled ) return; + + d->completionTimer->stop(); + d->completionTimer->start( 500, true ); +} + +void Editor::checkMatching() +{ + if( !d->syntaxHighlightEnabled ) return; + + d->matchingTimer->stop(); + d->matchingTimer->start( 200, true ); +} + +void Editor::checkAutoCalc() +{ + // Calc-As-You-Type + if( !d->autoCalcEnabled ) return; + + d->autoCalcTimer->stop(); + d->autoCalcTimer->start( 1000, true ); + d->autoCalcLabel->hide(); +} + +void Editor::doMatchingLeft() +{ + if( !d->syntaxHighlightEnabled ) return; + + // tokenize the expression + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + // check for right par + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if( !tokens.valid() ) return; + if( tokens.count()<1 ) return; + Token lastToken = tokens[ tokens.count()-1 ]; + + // right par ? + if( lastToken.isOperator() ) + if( lastToken.asOperator() == Token::RightPar ) + if( lastToken.pos() == curPos-1 ) + { + // find the matching left par + unsigned par = 1; + int k = 0; + Token matchToken; + int matchPos = -1; + + for( k = tokens.count()-2; k >= 0; k-- ) + { + if( par < 1 ) break; + Token matchToken = tokens[k]; + if( matchToken.isOperator() ) + { + if( matchToken.asOperator() == Token::RightPar ) + par++; + if( matchToken.asOperator() == Token::LeftPar ) + par--; + if( par == 0 ) matchPos = matchToken.pos(); + } + } + + if( matchPos >= 0 ) + { + setSelection( 0, matchPos, 0, matchPos+1, 2 ); + setSelection( 0, lastToken.pos(), 0, lastToken.pos()+1, 1 ); + setCursorPosition( para, curPos ); + } + } +} + +void Editor::doMatchingRight() +{ + if( !d->syntaxHighlightEnabled ) return; + + // tokenize the expression + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + // check for left par + QString subtext = text().right( text().length() - curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if( !tokens.valid() ) return; + if( tokens.count()<1 ) return; + Token firstToken = tokens[ 0 ]; + + // left par ? + if( firstToken.isOperator() ) + if( firstToken.asOperator() == Token::LeftPar ) + if( firstToken.pos() == 0 ) + { + // find the matching right par + unsigned par = 1; + unsigned int k = 0; + Token matchToken; + int matchPos = -1; + + for( k = 1; k < tokens.count(); k++ ) + { + if( par < 1 ) break; + Token matchToken = tokens[k]; + if( matchToken.isOperator() ) + { + if( matchToken.asOperator() == Token::LeftPar ) + par++; + if( matchToken.asOperator() == Token::RightPar ) + par--; + if( par == 0 ) matchPos = matchToken.pos(); + } + } + + if( matchPos >= 0 ) + { + setSelection( 0, curPos+matchPos, 0, curPos+matchPos+1, 2 ); + setSelection( 0, curPos+firstToken.pos(), 0, curPos+firstToken.pos()+1, 1 ); + setCursorPosition( para, curPos ); + } + } + +} + +void Editor::triggerAutoComplete() +{ + if( !d->autoCompleteEnabled ) return; + + // tokenize the expression (don't worry, this is very fast) + // faster now that it uses flex. ;) + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if(!tokens.valid()) + { + kdWarning() << "invalid tokens.\n"; + return; + } + + if(tokens.isEmpty() || subtext.endsWith(" ")) + return; + + Token lastToken = tokens[ tokens.count()-1 ]; + + // last token must be an identifier + if( !lastToken.isIdentifier() ) + return; + + QString id = lastToken.text(); + if( id.isEmpty() ) + return; + + // find matches in function names + QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); + QStringList choices; + + for( unsigned i=0; ifunction( str ); + if( f && !f->description.isEmpty() ) + str.append( ':' ).append( f->description ); + + choices.append( str ); + } + + choices.sort(); + + // find matches in variables names + QStringList vchoices; + QStringList values = ValueManager::instance()->valueNames(); + + for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) + if( (*it).startsWith( id, false ) ) + { + QString choice = ValueManager::description(*it); + if(choice.isEmpty()) + choice = ValueManager::instance()->value(*it).toString(); + + vchoices.append( QString("%1:%2").arg( *it, choice ) ); + } + + vchoices.sort(); + choices += vchoices; + + // no match, don't bother with completion + if( !choices.count() ) return; + + // one match, complete it for the user + if( choices.count()==1 ) + { + QString str = QStringList::split( ':', choices[0] )[0]; + + // single perfect match, no need to give choices. + if(str == id.lower()) + return; + + str = str.remove( 0, id.length() ); + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + blockSignals( true ); + insert( str ); + setSelection( 0, curPos, 0, curPos+str.length() ); + blockSignals( false ); + return; + } + + // present the user with completion choices + d->completion->showCompletion( choices ); +} + +void Editor::autoComplete( const QString& item ) +{ + if( !d->autoCompleteEnabled || item.isEmpty() ) + return; + + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + + if( !tokens.valid() || tokens.count() < 1 ) + return; + + Token lastToken = tokens[ tokens.count()-1 ]; + if( !lastToken.isIdentifier() ) + return; + + QStringList str = QStringList::split( ':', item ); + + blockSignals( true ); + setSelection( 0, lastToken.pos(), 0, lastToken.pos()+lastToken.text().length() ); + insert( str[0] ); + blockSignals( false ); +} + +void Editor::autoCalc() +{ + if( !d->autoCalcEnabled ) + return; + + QString str = Evaluator::autoFix( text() ); + if( str.isEmpty() ) + return; + + // too short? do not bother... + Tokens tokens = Evaluator::scan( str ); + if( tokens.count() < 2 ) + return; + + // If we're using set for a function don't try. + QRegExp setFn("\\s*set.*\\(.*="); + if( str.find(setFn) != -1 ) + return; + + // strip off assignment operator, e.g. "x=1+2" becomes "1+2" only + // the reason is that we want only to evaluate (on the fly) the expression, + // not to update (put the result in) the variable + if( tokens.count() > 2 && tokens[0].isIdentifier() && + tokens[1].asOperator() == Token::Equal ) + { + Tokens::const_iterator it = tokens.begin(); + ++it; + ++it; // Skip first two tokens. + + // Reconstruct string to evaluate using the tokens. + str = ""; + while(it != tokens.end()) + { + str += (*it).text(); + str += ' '; + ++it; + } + } + + Abakus::number_t result = parseString(str.latin1()); + if( Result::lastResult()->type() == Result::Value ) + { + QString ss = QString("Result: %2").arg(result.toString()); + d->autoCalcLabel->setText( ss ); + d->autoCalcLabel->adjustSize(); + + // reposition nicely + QPoint pos = mapToGlobal( QPoint( 0, 0 ) ); + pos.setY( pos.y() - d->autoCalcLabel->height() - 1 ); + d->autoCalcLabel->move( pos ); + d->autoCalcLabel->show(); + d->autoCalcLabel->raise(); + + // do not show it forever + QTimer::singleShot( 5000, d->autoCalcLabel, SLOT( hide()) ); + } + else + { + // invalid expression + d->autoCalcLabel->hide(); + } +} + +QString Editor::formatNumber( const Abakus::number_t &value ) const +{ + return value.toString(); +} + +void Editor::historyBack() +{ + if( d->history.isEmpty() ) + return; + + d->index--; + + if( d->index < 0 ) + d->index = 0; + + setText( d->history[ d->index ] ); + setCursorPosition( 0, text().length() ); + ensureCursorVisible(); +} + +void Editor::historyForward() +{ + if( d->history.isEmpty() ) + return; + + d->index++; + + if( d->index >= (int) d->history.count() ) + d->index = d->history.count() - 1; + + setText( d->history[ d->index ] ); + setCursorPosition( 0, text().length() ); + ensureCursorVisible(); +} + +void Editor::keyPressEvent( QKeyEvent* e ) +{ + if( e->key() == Key_Up ) + { + historyBack(); + e->accept(); + return; + } + + if( e->key() == Key_Down ) + { + historyForward(); + e->accept(); + return; + } + + if( e->key() == Key_Enter || e->key() == Key_Return ) + { + emit returnPressed(); + return; + } + + if( e->key() == Key_Left || + e->key() == Key_Right || + e->key() == Key_Home || + e->key() == Key_End ) + { + checkMatching(); + } + + QTextEdit::keyPressEvent( e ); +} + +void Editor::wheelEvent( QWheelEvent *e ) +{ + if( e->delta() > 0 ) + historyBack(); + else if( e->delta() < 0 ) + historyForward(); + + e->accept(); +} + +void Editor::setSyntaxHighlight( bool enable ) +{ + d->syntaxHighlightEnabled = enable; + d->highlighter->rehighlight(); +} + +bool Editor::isSyntaxHighlightEnabled() const +{ + return d->syntaxHighlightEnabled; +} + +void Editor::setHighlightColor( ColorType type, QColor color ) +{ + d->highlightColors[ type ] = color; + + setSelectionAttributes( 1, highlightColor( Editor::MatchedPar ), false ); + setSelectionAttributes( 2, highlightColor( Editor::MatchedPar ), false ); + + d->highlighter->rehighlight(); +} + +QColor Editor::highlightColor( ColorType type ) +{ + return d->highlightColors[ type ]; +} + + +EditorCompletion::EditorCompletion( Editor* editor ): QObject( editor ) +{ + d = new Private; + d->editor = editor; + + d->completionPopup = new QVBox( editor->topLevelWidget(), 0, WType_Popup ); + d->completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); + d->completionPopup->setLineWidth( 1 ); + d->completionPopup->installEventFilter( this ); + d->completionPopup->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum); + + d->completionListBox = new QListBox( d->completionPopup ); + d->completionPopup->setFocusProxy( d->completionListBox ); + d->completionListBox->setFrameStyle( QFrame::NoFrame ); + d->completionListBox->setVariableWidth( true ); + d->completionListBox->installEventFilter( this ); +} + +EditorCompletion::~EditorCompletion() +{ + delete d; +} + +bool EditorCompletion::eventFilter( QObject *obj, QEvent *ev ) +{ + if ( obj == d->completionPopup || obj == d->completionListBox ) + { + + if ( ev->type() == QEvent::KeyPress ) + { + QKeyEvent *ke = (QKeyEvent*)ev; + if ( ke->key() == Key_Enter || ke->key() == Key_Return ) + { + doneCompletion(); + return true; + } + else if ( ke->key() == Key_Left || ke->key() == Key_Right || + ke->key() == Key_Up || ke->key() == Key_Down || + ke->key() == Key_Home || ke->key() == Key_End || + ke->key() == Key_Prior || ke->key() == Key_Next ) + return false; + + d->completionPopup->close(); + d->editor->setFocus(); + QApplication::sendEvent( d->editor, ev ); + return true; + } + + if ( ev->type() == QEvent::MouseButtonDblClick ) + { + doneCompletion(); + return true; + } + + } + + return false; +} + +void EditorCompletion::doneCompletion() +{ + d->completionPopup->close(); + d->editor->setFocus(); + emit selectedCompletion( d->completionListBox->currentText() ); +} + +void EditorCompletion::showCompletion( const QStringList &choices ) +{ + static bool shown = false; + if( !choices.count() ) return; + + d->completionListBox->clear(); + int maxWidth = 0; + for( unsigned i = 0; i < choices.count(); i++ ) { + ChoiceItem *item = new ChoiceItem( d->completionListBox, choices[i] ); + int itemMaxWidth = item->nameWidth(); + + if(itemMaxWidth > maxWidth) + maxWidth = itemMaxWidth; + } + + for(unsigned i = 0; i < d->completionListBox->count(); ++i) { + ChoiceItem *item = static_cast(d->completionListBox->item(i)); + item->setMinNameWidth(maxWidth); + } + + d->completionListBox->setCurrentItem( 0 ); + + // size of the pop-up + d->completionPopup->setMaximumHeight( 120 ); + d->completionPopup->resize( d->completionListBox->sizeHint() + + QSize( d->completionListBox->verticalScrollBar()->width() + 4, + d->completionListBox->horizontalScrollBar()->height() + 4 ) ); + + if(!shown) + { + d->completionPopup->show(); + QTimer::singleShot ( 0, this, SLOT(moveCompletionPopup()) ); + } + else + { + moveCompletionPopup(); + d->completionPopup->show(); + } +} + +void EditorCompletion::moveCompletionPopup() +{ + int h = d->completionListBox->height(); + int w = d->completionListBox->width(); + + // position, reference is editor's cursor position in global coord + QFontMetrics fm( d->editor->font() ); + int para = 0, curPos = 0; + + d->editor->getCursorPosition( ¶, &curPos ); + + int pixelsOffset = fm.width( d->editor->text(), curPos ); + pixelsOffset -= d->editor->contentsX(); + QPoint pos = d->editor->mapToGlobal( QPoint( pixelsOffset, d->editor->height() ) ); + + // if popup is partially invisible, move to other position + NETRootInfo info(d->completionPopup->x11Display(), + NET::CurrentDesktop | NET::WorkArea | NET::NumberOfDesktops, + -1, false); + info.activate(); // wtf is this needed for? + NETRect NETarea = info.workArea(info.currentDesktop()); + + QRect area(NETarea.pos.x, NETarea.pos.y, NETarea.size.width, NETarea.size.height); + + if( pos.y() + h > area.y() + area.height() ) + pos.setY( pos.y() - h - d->editor->height() ); + if( pos.x() + w > area.x() + area.width() ) + pos.setX( area.x() + area.width() - w ); + + d->completionPopup->move( pos ); + d->completionListBox->setFocus(); +} + +#include "editor.moc" + +// vim: set et sw=2 ts=8: diff --git a/src/editor.h b/src/editor.h new file mode 100644 index 0000000..a71d661 --- /dev/null +++ b/src/editor.h @@ -0,0 +1,131 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004,2005 Ariya Hidayat + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + + +#ifndef ABAKUS_EDITOR_H +#define ABAKUS_EDITOR_H + +#include +#include +#include + +#include "hmath.h" + +class QEvent; +class QKeyEvent; +class QWidget; +class Evaluator; + +class Editor : public QTextEdit +{ + Q_OBJECT + + public: + + typedef enum + { + Number, FunctionName, Variable, MatchedPar + } ColorType; + + Editor( QWidget* parent = 0, const char* name = 0 ); + ~Editor(); + + QSize sizeHint() const; + QSize xminimumSizeHint() const; + + QStringList history() const; + void setHistory( const QStringList& history ); + + bool autoCompleteEnabled() const; + void setAutoCompleteEnabled( bool enable ); + + bool autoCalcEnabled() const; + void setAutoCalcEnabled( bool enable ); + void setFormat( char format ); + void setDecimalDigits( int digits ); + + void setSyntaxHighlight( bool enable ); + bool isSyntaxHighlightEnabled() const; + void setHighlightColor( ColorType type, QColor color ); + QColor highlightColor( ColorType type ); + + public slots: + void appendHistory( const QString& text ); + void clearHistory(); + + // Stop the timer from going off. + void squelchNextAutoCalc(); + + void setText(const QString &txt); + + protected slots: + void checkAutoComplete(); + void triggerAutoComplete(); + void autoComplete( const QString& item ); + void checkAutoCalc(); + void autoCalc(); + void checkMatching(); + void doMatchingLeft(); + void doMatchingRight(); + void historyBack(); + void historyForward(); + + protected: + void keyPressEvent( QKeyEvent* ); + void wheelEvent( QWheelEvent* ); + QString formatNumber( const Abakus::number_t &value ) const; + + private: + class Private; + Private* d; + Editor( const Editor& ); + Editor& operator=( const Editor& ); +}; + + +class EditorCompletion : public QObject +{ + Q_OBJECT + + public: + EditorCompletion( Editor* editor ); + ~EditorCompletion(); + + bool eventFilter( QObject *o, QEvent *e ); + void doneCompletion(); + void showCompletion( const QStringList &choices ); + + protected slots: + void moveCompletionPopup(); + + signals: + void selectedCompletion( const QString& item ); + + private: + class Private; + Private* d; + EditorCompletion( const EditorCompletion& ); + EditorCompletion& operator=( const EditorCompletion& ); +}; + +#endif // ABAKUS_EDITOR_H + +// vim: set et ts=8 sw=4: diff --git a/src/evaluator.cpp b/src/evaluator.cpp new file mode 100644 index 0000000..4b47d5e --- /dev/null +++ b/src/evaluator.cpp @@ -0,0 +1,261 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004 Ariya Hidayat + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#include "evaluator.h" +#include "function.h" +#include "node.h" // For parser_yacc.hpp below +#include "parser_yacc.hpp" + +#include +#include +#include +#include +#include + +#include + +// +// Reimplementation of goodies from Evaluator follows. +// + +Evaluator::Evaluator() +{ +} + +Evaluator::~Evaluator() +{ +} + +void Evaluator::setExpression(const QString &expr) +{ + kdError() << k_funcinfo << " not implemented.\n"; +} + +QString Evaluator::expression() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return QString(); +} + +void Evaluator::clear() +{ + kdError() << k_funcinfo << " not implemented.\n"; + // Yeah, whatever. +} + +bool Evaluator::isValid() const +{ + return true; +} + +Tokens Evaluator::tokens() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return Tokens(); +} + +Tokens Evaluator::scan(const QString &expr) +{ + Lexer l(expr); + Tokens tokens; + + while(l.hasNext()) + { + int t = l.nextType(); + Token::Type type = Token::Unknown; + + switch(t) + { + case POWER: + case '*': + case '(': + case ')': + case '-': + case '+': + case ',': + case '=': + type = Token::Operator; + break; + + case NUM: + type = Token::Number; + break; + + case SET: + case REMOVE: + case DERIV: + case FN: + case ID: + type = Token::Identifier; + break; + + default: + type = Token::Unknown; + break; + } + + tokens.append(Token(type, l.tokenValue(), l.tokenPos())); + } + + return tokens; +} + +QString Evaluator::error() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return "No Error Yet"; +} + +/// +/// ARIYA'S CLASS CODE FOLLOWS +/// + +// for null token +const Token Token::null; + +// helper function: return operator of given token text +// e.g. "*" yields Operator::Asterisk, and so on +static Token::Op matchOperator( const QString& text ) +{ + Token::Op result = Token::InvalidOp; + + if( text.length() == 1 ) + { + QChar p = text[0]; + switch( p.unicode() ) + { + case '+': result = Token::Plus; break; + case '-': result = Token::Minus; break; + case '*': result = Token::Asterisk; break; + case '/': result = Token::Slash; break; + case '^': result = Token::Caret; break; + case ',': result = Token::Comma; break; + case '(': result = Token::LeftPar; break; + case ')': result = Token::RightPar; break; + case '%': result = Token::Percent; break; + case '=': result = Token::Equal; break; + default : result = Token::InvalidOp; break; + } + } + + if( text.length() == 2 ) + { + if( text == "**" ) result = Token::Caret; + } + + return result; +} + +// creates a token +Token::Token( Type type, const QString& text, int pos ) +{ + m_type = type; + m_text = text; + m_pos = pos; +} + +// copy constructor +Token::Token( const Token& token ) +{ + m_type = token.m_type; + m_text = token.m_text; + m_pos = token.m_pos; +} + +// assignment operator +Token& Token::operator=( const Token& token ) +{ + m_type = token.m_type; + m_text = token.m_text; + m_pos = token.m_pos; + return *this; +} + +Abakus::number_t Token::asNumber() const +{ + if( isNumber() ) + return Abakus::number_t( m_text.latin1() ); + else + return Abakus::number_t(); +} + +Token::Op Token::asOperator() const +{ + if( isOperator() ) return matchOperator( m_text ); + else return InvalidOp; +} + +QString Token::description() const +{ + QString desc; + + switch (m_type ) + { + case Number: desc = "Number"; break; + case Identifier: desc = "Identifier"; break; + case Operator: desc = "Operator"; break; + default: desc = "Unknown"; break; + } + + while( desc.length() < 10 ) desc.prepend( ' ' ); + desc.prepend( " " ); + desc.prepend( QString::number( m_pos ) ); + desc.append( " : " ).append( m_text ); + + return desc; +} + + +QString Evaluator::autoFix( const QString& expr ) +{ + int par = 0; + QString result; + + // strip off all funny characters + for( unsigned c = 0; c < expr.length(); c++ ) + if( expr[c] >= QChar(32) ) + result.append( expr[c] ); + + // automagically close all parenthesis + Tokens tokens = Evaluator::scan( result ); + for( unsigned i=0; i 0; par-- ) + result.append( ')' ); + + // special treatment for simple function + // e.g. "cos" is regarded as "cos(ans)" + if( !result.isEmpty() ) + { + Tokens tokens = Evaluator::scan( result ); + if( (tokens.count() == 1) && + FunctionManager::instance()->isFunction(tokens[0].text()) + ) + { + result.append( "(ans)" ); + } + } + + return result; +} + +// vim: set et ts=8 sw=4: diff --git a/src/evaluator.h b/src/evaluator.h new file mode 100644 index 0000000..2d156eb --- /dev/null +++ b/src/evaluator.h @@ -0,0 +1,131 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004 Ariya Hidayat + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#ifndef ABAKUS_EVALUATOR_H +#define ABAKUS_EVALUATOR_H + +#include +#include + +#include "numerictypes.h" + +class Token +{ +public: + typedef enum + { + Unknown, + Number, + Operator, + Identifier + } Type; + + typedef enum + { + InvalidOp = 0, + Plus, // + (addition) + Minus, // - (substraction, negation) + Asterisk, // * (multiplication) + Slash, // / (division) + Caret, // ^ (power) or **. + LeftPar, // ( + RightPar, // ) + Comma, // argument separator + Percent, + Equal // variable assignment + } Op; + + Token( Type type = Unknown, const QString& text = QString::null, int pos = -1 ); + + Token( const Token& ); + Token& operator=( const Token& ); + + Type type() const { return m_type; } + QString text() const { return m_text; } + int pos() const { return m_pos; }; + + bool isNumber() const { return m_type == Number; } + bool isOperator() const { return m_type == Operator; } + bool isIdentifier() const { return m_type == Identifier; } + + Abakus::number_t asNumber() const; + Op asOperator() const; + + QString description() const; + + static const Token null; + +protected: + Type m_type; + QString m_text; + int m_pos; +}; + + +class Tokens: public QValueVector +{ +public: + Tokens(): QValueVector(), m_valid(true) {}; + + bool valid() const { return m_valid; } + void setValid( bool v ) { m_valid = v; } + +protected: + bool m_valid; +}; + +class Variable +{ +public: + QString name; + Abakus::number_t value; +}; + +class Evaluator +{ +public: + Evaluator(); + ~Evaluator(); + + void setExpression( const QString& expr ); + QString expression() const; + + void clear(); + bool isValid() const; + + Tokens tokens() const; + static Tokens scan( const QString& expr ); + + QString error() const; + + // Abakus::number_t eval(); + + static QString autoFix( const QString& expr ); + +private: + Evaluator( const Evaluator& ); + Evaluator& operator=( const Evaluator& ); +}; + + +#endif // EVALUATOR + +// vim: set et ts=8 sw=4: diff --git a/src/function.cpp b/src/function.cpp new file mode 100644 index 0000000..70c4f1c --- /dev/null +++ b/src/function.cpp @@ -0,0 +1,298 @@ +/* + * function.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "numerictypes.h" + +#include + +#include +#include +#include + +#include + +#include "function.h" +#include "node.h" +#include "valuemanager.h" +#include "hmath.h" + +// Used to try and avoid recursive function definitions +class DupFinder : public NodeFunctor +{ + public: + DupFinder(const QString &nameToFind) : + m_name(nameToFind), m_valid(true) + { + } + + virtual ~DupFinder() { } + + bool isValid() const { return m_valid; } + + virtual void operator()(const Node *node) + { + if(!m_valid) + return; + + const BaseFunction *fn = dynamic_cast(node); + if(fn && fn->name() == m_name) + m_valid = false; // Duplicate detected + } + + private: + QString m_name; + bool m_valid; +}; + +// Define static member for FunctionManager +FunctionManager *FunctionManager::m_manager = 0; + +FunctionManager *FunctionManager::instance() +{ + if(!m_manager) + m_manager = new FunctionManager; + + return m_manager; +} + +FunctionManager::FunctionManager(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_dict.setAutoDelete(true); +} + +// Dummy return value to enable static initialization in the DECL_*() +// macros. +bool FunctionManager::addFunction(const QString &name, function_t fn, const QString &desc) +{ + Function *newFn = new Function; + QRegExp returnTrigRE("^a(cos|sin|tan)"); + QRegExp needsTrigRE("^(cos|sin|tan)"); + QString fnName(name); + + newFn->name = name; + newFn->description = desc; + newFn->fn = fn; + newFn->userDefined = false; + newFn->returnsTrig = fnName.contains(returnTrigRE); + newFn->needsTrig = fnName.contains(needsTrigRE); + + m_dict.insert(name, newFn); + + return false; +} + +#define DECLARE_FUNC(name, fn, desc) bool dummy##name = FunctionManager::instance()->addFunction(#name, fn, desc) + +// Declares a function name that is implemented by the function of a different +// name. e.g. atan -> Abakus::number_t::arctan() +#define DECLARE_FUNC2(name, fnName, desc) DECLARE_FUNC(name, &Abakus::number_t::fnName, desc) + +// Declares a function name that is implemented by the function of the +// same base name. +#define DECLARE_FUNC1(name, desc) DECLARE_FUNC2(name, name, desc) + +DECLARE_FUNC1(sin, "Trigonometric sine"); +DECLARE_FUNC1(cos, "Trigonometric cosine"); +DECLARE_FUNC1(tan, "Trigonometric tangent"); + +DECLARE_FUNC1(sinh, "Hyperbolic sine"); +DECLARE_FUNC1(cosh, "Hyperbolic cosine"); +DECLARE_FUNC1(tanh, "Hyperbolic tangent"); + +DECLARE_FUNC1(atan, "Inverse tangent"); +DECLARE_FUNC1(acos, "Inverse cosine"); +DECLARE_FUNC1(asin, "Inverse sine"); + +DECLARE_FUNC1(asinh, "Inverse hyperbolic sine"); +DECLARE_FUNC1(acosh, "Inverse hyperbolic cosine"); +DECLARE_FUNC1(atanh, "Inverse hyperbolic tangent"); + +DECLARE_FUNC1(abs, "Absolute value of number"); +DECLARE_FUNC1(sqrt, "Square root"); +DECLARE_FUNC1(ln, "Natural logarithm (base e)"); +DECLARE_FUNC1(log, "Logarithm (base 10)"); +DECLARE_FUNC1(exp, "Natural exponential function"); + +DECLARE_FUNC1(round, "Round to nearest number"); +DECLARE_FUNC1(ceil, "Nearest greatest integer"); +DECLARE_FUNC1(floor, "Nearest lesser integer"); +DECLARE_FUNC2(int, integer, "Integral part of number"); +DECLARE_FUNC1(frac, "Fractional part of number"); + +Function *FunctionManager::function(const QString &name) +{ + return m_dict[name]; +} + +// Returns true if the named identifier is a function, false otherwise. +bool FunctionManager::isFunction(const QString &name) +{ + return function(name) != 0; +} + +bool FunctionManager::isFunctionUserDefined(const QString &name) +{ + const Function *fn = function(name); + return (fn != 0) && (fn->userDefined); +} + +bool FunctionManager::addFunction(BaseFunction *fn, const QString &dependantVar) +{ + // First see if this function is recursive + DupFinder dupFinder(fn->name()); + UnaryFunction *unFunction = dynamic_cast(fn); + if(unFunction && unFunction->operand()) { + unFunction->operand()->applyMap(dupFinder); + if(!dupFinder.isValid()) + return false; + } + + // Structure holds extra data needed to call the user defined + // function. + UserFunction *newFn = new UserFunction; + newFn->sequenceNumber = m_dict.count(); + newFn->fn = fn; + newFn->varName = QString(dependantVar); + + // Now setup the Function data structure that holds the information + // we need to access and call the function later. + Function *fnTabEntry = new Function; + fnTabEntry->name = fn->name(); + fnTabEntry->userFn = newFn; + fnTabEntry->returnsTrig = false; + fnTabEntry->needsTrig = false; + fnTabEntry->userDefined = true; + + if(m_dict.find(fn->name())) + emit signalFunctionRemoved(fn->name()); + + m_dict.replace(fn->name(), fnTabEntry); + emit signalFunctionAdded(fn->name()); + + return true; +} + +void FunctionManager::removeFunction(const QString &name) +{ + Function *fn = function(name); + + // If we remove a function, we need to decrement the sequenceNumber of + // functions after this one. + if(fn && fn->userDefined) { + int savedSeqNum = fn->userFn->sequenceNumber; + + // Emit before we actually remove it so that the info on the function + // can still be looked up. + emit signalFunctionRemoved(name); + + delete fn->userFn; + fn->userFn = 0; + m_dict.remove(name); + + QDictIterator it(m_dict); + for (; it.current(); ++it) { + UserFunction *userFn = it.current()->userDefined ? it.current()->userFn : 0; + if(userFn && userFn->sequenceNumber > savedSeqNum) + --it.current()->userFn->sequenceNumber; + } + } +} + +QStringList FunctionManager::functionList(FunctionManager::FunctionType type) +{ + QDictIterator it(m_dict); + QStringList functions; + + switch(type) { + case Builtin: + for(; it.current(); ++it) + if(!it.current()->userDefined) + functions += it.current()->name; + break; + + case UserDefined: + // We want to return the function names in the order they were + // added. + { + QValueVector fnTable(m_dict.count(), 0); + QValueVector sequenceNumberTable(m_dict.count(), -1); + + // First find out what sequence numbers we have. + for(; it.current(); ++it) + if(it.current()->userDefined) { + int id = it.current()->userFn->sequenceNumber; + fnTable[id] = it.current(); + sequenceNumberTable.append(id); + } + + // Now sort the sequence numbers and return the ordered list + qHeapSort(sequenceNumberTable.begin(), sequenceNumberTable.end()); + + for(unsigned i = 0; i < sequenceNumberTable.count(); ++i) + if(sequenceNumberTable[i] >= 0) + functions += fnTable[sequenceNumberTable[i]]->name; + } + break; + + case All: + functions += functionList(Builtin); + functions += functionList(UserDefined); + break; + } + + return functions; +} + +// Applies the function identified by func, using value as a parameter. +Abakus::number_t evaluateFunction(const Function *func, const Abakus::number_t value) +{ + if(func->userDefined) { + // Pull real entry from userFunctionTable + UserFunction *realFunction = func->userFn; + + bool wasSet = ValueManager::instance()->isValueSet(realFunction->varName); + Abakus::number_t oldValue; + if(wasSet) + oldValue = ValueManager::instance()->value(realFunction->varName); + + ValueManager::instance()->setValue(realFunction->varName, value); + Abakus::number_t result = realFunction->fn->value(); + + if(wasSet) + ValueManager::instance()->setValue(realFunction->varName, oldValue); + else + ValueManager::instance()->removeValue(realFunction->varName); + + return result; + } + + return (value.*(func->fn))(); +} + +void setTrigMode(Abakus::TrigMode mode) +{ + Abakus::m_trigMode = mode; +} + +Abakus::TrigMode trigMode() +{ + return Abakus::m_trigMode; +} + +#include "function.moc" diff --git a/src/function.h b/src/function.h new file mode 100644 index 0000000..3b56cbb --- /dev/null +++ b/src/function.h @@ -0,0 +1,122 @@ +#ifndef ABAKUS_FUNCTION_H +#define ABAKUS_FUNCTION_H +/* + * function.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "numerictypes.h" + +#include +#include +#include +#include +#include + + + +class BaseFunction; + +struct UserFunction +{ + int sequenceNumber; + BaseFunction *fn; + QString varName; +}; + +// Ugly pointer-to-member typedef ahead +typedef Abakus::number_t (Abakus::number_t::*function_t)() const; + +struct Function { + QString name; + QString description; + + // A function is either builtin or user defined, this union is + // used for both cases. + union { + function_t fn; // Builtin. + UserFunction *userFn; // User defined + }; + + bool returnsTrig; + bool needsTrig; + + bool userDefined; +}; + +void setTrigMode(Abakus::TrigMode mode); +Abakus::TrigMode trigMode(); + +class FunctionManager : public QObject +{ + Q_OBJECT + public: + typedef QDict functionDict; + + static FunctionManager *instance(); + + Function *function(const QString &name); + + bool isFunction(const QString &name); + bool isFunctionUserDefined(const QString &name); + + bool addFunction(BaseFunction *fn, const QString &dependantVar); + bool addFunction(const QString &name, function_t fn, const QString &desc); + void removeFunction(const QString &name); + + typedef enum { Builtin, UserDefined, All } FunctionType; + + QStringList functionList(FunctionType type); + + signals: + void signalFunctionAdded(const QString &name); + void signalFunctionRemoved(const QString &name); + + private: + FunctionManager(QObject *parent = 0, const char *name = "function manager"); + + static FunctionManager *m_manager; + functionDict m_dict; +}; + +Abakus::number_t evaluateFunction(const Function *func, const Abakus::number_t value); + +// Implemented in lexer.l due to prototype issues. +Abakus::number_t parseString(const char *str); + +// Implemented in lexer.l due to prototype issues. +class Lexer +{ +public: + Lexer(const QString &expr); + ~Lexer(); + + bool hasNext() const; + int nextType(); + + int tokenPos() const; + + // Can call this after nextType to find the associated string value of the + // token. + QString tokenValue() const; + +private: + class Private; + Private *m_private; +}; + +#endif diff --git a/src/hi64-app-abakus.png b/src/hi64-app-abakus.png new file mode 100644 index 0000000..5644c45 Binary files /dev/null and b/src/hi64-app-abakus.png differ diff --git a/src/hmath.cpp b/src/hmath.cpp new file mode 100644 index 0000000..8dcf346 --- /dev/null +++ b/src/hmath.cpp @@ -0,0 +1,1797 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat + Last update: November 15, 2004 + + This file was copied from the SpeedCrunch program. Please visit + http://speedcrunch.berlios.de/ for more information. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, write to: + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02110-1301 USA. + + */ + +#include "hmath.h" +#include "number.h" + +#include +#include +#include +#include + +#include + +// internal number of decimal digits +#define HMATH_MAX_PREC 150 + +// digits used for number comparison +// (not all are used, to work around propagated error problem) +#define HMATH_COMPARE_PREC 70 + +// maximum shown digits if prec is negative +#define HMATH_MAX_SHOWN 20 + +// from number.c, need to be freed somehow +extern bc_num _zero_; +extern bc_num _one_; +extern bc_num _two_; + +class HNumber::Private +{ +public: + bc_num num; + bool nan; +}; + +void out_of_memory(void){ + return; +} + +void rt_warn(char * ,...){ + return; +} + +void rt_error(char * ,...){ + return; +} + +static bc_num h_create( int len = 1, int scale = 0 ) +{ + bc_num temp; + temp = (bc_num) malloc( sizeof(bc_struct) ); + temp->n_sign = PLUS; + temp->n_len = len; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = (char*) malloc( len+scale+1 ); + temp->n_value = temp->n_ptr; + temp->n_next = 0; + memset (temp->n_ptr, 0, len+scale+1); + return temp; +} + +static void h_destroy( bc_num n ) +{ + free( n->n_ptr ); + free( n ); +} + +// reclaim and free one bc_num from the freelist +// workaround for number.c, because it doesn't really free a number +// but instead put it in the pool of unused numbers +// this function will take it back from that pool and set it really free +static void h_grabfree() +{ + bc_num t = bc_new_num( 1, 0 ); + h_destroy( t ); +} + +// make an exact (explicit) copy +static bc_num h_copy( bc_num n ) +{ + int len = n->n_len; + int scale = n->n_scale; + bc_num result = h_create( len, scale ); + memcpy( result->n_ptr, n->n_value, len+scale+1 ); + result->n_sign = n->n_sign; + result->n_value = result->n_ptr; + h_grabfree(); + return result; +} + +// same as copy, but readjust decimal digits +static bc_num h_rescale( bc_num n, int sc ) +{ + int len = n->n_len; + int scale = MIN( sc, n->n_scale ); + bc_num result = h_create( len, scale ); + memcpy( result->n_ptr, n->n_value, len+scale+1 ); + result->n_sign = n->n_sign; + result->n_value = result->n_ptr; + h_grabfree(); + return result; +} + +// convert simple string to number +static bc_num h_str2num( const char* str, int scale = HMATH_MAX_PREC ) +{ + int digits, strscale; + const char *ptr; + char *nptr; + char zero_int; + + /* Check for valid number and count digits. */ + ptr = str; + digits = 0; + strscale = 0; + zero_int = FALSE; + if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ + if (*ptr == '.') ptr++; /* decimal point */ + while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ + if ((*ptr != '\0') || (digits+strscale == 0)) + return h_create(); + + /* Adjust numbers and allocate storage and initialize fields. */ + strscale = MIN(strscale, scale); + if (digits == 0) + { + zero_int = TRUE; + digits = 1; + } + + bc_num num = h_create( digits, strscale ); + + ptr = str; + if (*ptr == '-') + { + num->n_sign = MINUS; + ptr++; + } + else + { + num->n_sign = PLUS; + if (*ptr == '+') ptr++; + } + while (*ptr == '0') ptr++; + nptr = num->n_value; + if (zero_int) + { + *nptr++ = 0; + digits = 0; + } + for (;digits > 0; digits--) + *nptr++ = (char)CH_VAL(*ptr++); + + + if (strscale > 0) + { + ptr++; + for (;strscale > 0; strscale--) + *nptr++ = (char)CH_VAL(*ptr++); + } + + return num; +} + + +// add two numbers, return newly allocated number +static bc_num h_add( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_add( n1, n2, &r, 1 ); + h_grabfree(); + return r; +} + +// subtract two numbers, return newly allocated number +static bc_num h_sub( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_sub( n1, n2, &r, 1 ); + h_grabfree(); + return r; +} + +// multiply two numbers, return newly allocated number +static bc_num h_mul( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_multiply( n1, n2, &r, HMATH_MAX_PREC ); + h_grabfree(); + return r; +} + +// divide two numbers, return newly allocated number +static bc_num h_div( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_divide( n1, n2, &r, HMATH_MAX_PREC ); + h_grabfree(); + return r; +} + +// find 10 raise to num +// e.g.: when num is 5, it results 100000 +static bc_num h_raise10( int n ) +{ + // calculate proper factor + int len = abs(n)+2; + char* sf = new char[len+1]; + sf[len] = '\0'; + if( n >= 0 ) + { + sf[0] = '1'; + sf[len-1] = '\0'; + sf[len-2] = '\0'; + for( int i = 0; i < n; i++ ) + sf[i+1] = '0'; + } + else + { + sf[0] = '0'; sf[1] = '.'; + for( int i = 0; i < -n; i++ ) + sf[i+2] = '0'; + sf[len-1] = '1'; + } + + bc_num factor = h_str2num( sf, abs(n) ); + delete[] sf; + + return factor; +} + +// round up to certain decimal digits +static bc_num h_round( bc_num n, int prec ) +{ + // no need to round? + if( prec >= n->n_scale ) + return h_copy( n ); + + // example: rounding "3.14159" to 4 decimal digits means + // adding 0.5e-4 to 3.14159, it becomes 3.14164 + // taking only 4 decimal digits, so it's now 3.1416 + if( prec < 0 ) prec = 0; + bc_num x = h_raise10( -prec-1 ); + bc_num y = 0; + bc_int2num( &y, 5 ); + bc_num z = h_mul( x, y ); + z->n_sign = n->n_sign; + bc_num r = h_add( n, z ); + h_destroy( x ); + h_destroy( y ); + h_destroy( z ); + + // only digits we are interested in + bc_num v = h_rescale( r, prec ); + h_destroy( r ); + + return v; +} + +// remove trailing zeros +static void h_trimzeros( bc_num num ) +{ + while( ( num->n_scale > 0 ) && ( num->n_len+num->n_scale > 0 ) ) + if( num->n_value[num->n_len+num->n_scale-1] == 0 ) + num->n_scale--; + else break; +} + +static void h_init() +{ + static bool h_initialized = false; + if( !h_initialized ) + { + h_initialized = true; + bc_init_numbers(); + } +} + + +HNumber::HNumber() +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); +} + +HNumber::HNumber( const HNumber& hn ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + operator=( hn ); +} + +HNumber::HNumber( int i ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + bc_int2num( &d->num, i ); +} + +HNumber::HNumber( const char* str ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + + if( str ) + if( strlen(str) == 3 ) + if( tolower(str[0])=='n' ) + if( tolower(str[1])=='a' ) + if( tolower(str[2])=='n' ) + d->nan = true; + + if( str && !d->nan ) + { + char* s = new char[ strlen(str)+1 ]; + strcpy( s, str ); + + char* p = s; + for( ;; p++ ) + { + if( *p != '+' ) + if( *p != '-' ) + if( *p != '.' ) + if( !isdigit(*p) ) + break; + } + + int expd = 0; + + if( ( *p == 'e' ) || ( *p == 'E' ) ) + { + *p = '\0'; + expd = atoi( p+1 ); + } + + h_destroy( d->num ); + d->num = h_str2num( s ); + delete [] s; + + if( expd >= HMATH_MAX_PREC || // too large + expd <= -HMATH_MAX_PREC ) // too small + { + d->nan = true; + } + + if( expd != 0 ) + { + bc_num factor = h_raise10( expd ); + bc_num nn = h_copy( d->num ); + h_destroy( d->num ); + d->num = h_mul( nn, factor ); + h_destroy( nn ); + h_destroy( factor ); + } + h_trimzeros( d->num ); + } + +} + +HNumber::~HNumber() +{ + h_destroy( d->num ); + delete d; +} + +bool HNumber::isNan() const +{ + return d->nan; +} + +bool HNumber::isZero() const +{ + return !d->nan && ( bc_is_zero( d->num )!=0 ); +} + +bool HNumber::isPositive() const +{ + return !d->nan && !isNegative() && !isZero(); +} + +bool HNumber::isNegative() const +{ + return !d->nan && ( bc_is_neg( d->num )!=0 ); +} + +HNumber HNumber::nan() +{ + HNumber n; + n.d->nan = true; + return n; +} + +HNumber& HNumber::operator=( const HNumber& hn ) +{ + d->nan = hn.d->nan; + h_destroy( d->num ); + d->num = h_copy( hn.d->num ); + return *this; +} + +HNumber HNumber::operator+( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_add( d->num, num.d->num ); + + return result; +} + +HNumber& HNumber::operator+=( const HNumber& num ) +{ + HNumber n = HNumber(*this) + num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator-( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_sub( d->num, num.d->num ); + + return result; +} + +HNumber& HNumber::operator-=( const HNumber& num ) +{ + HNumber n = HNumber(*this) - num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator*( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_mul( d->num, num.d->num ); + return result; +} + +HNumber& HNumber::operator*=( const HNumber& num ) +{ + HNumber n = HNumber(*this) * num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator/( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_div( d->num, num.d->num ); + if(num == HNumber(0)) + result.d->nan = true; + + return result; +} + +HNumber& HNumber::operator/=( const HNumber& num ) +{ + HNumber n = HNumber(*this) / num; + operator=( n ); + return *this; +} + +bool HNumber::operator>( const HNumber& n ) const +{ + return HMath::compare( *this, n ) > 0; +} + +bool HNumber::operator<( const HNumber& n ) const +{ + return HMath::compare( *this, n ) < 0; +} + +bool HNumber::operator>=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) >= 0; +} + +bool HNumber::operator<=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) <= 0; +} + +bool HNumber::operator==( const HNumber& n ) const +{ + return HMath::compare( *this, n ) == 0; +} + +bool HNumber::operator!=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) != 0; +} + + +// format number with fixed number of decimal digits +char* HMath::formatFixed( const HNumber& hn, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + bc_num n = h_copy( hn.d->num ); + h_trimzeros( n ); + + int oprec = prec; + if( prec < 0 ) + { + prec = HMATH_MAX_SHOWN; + if( n->n_scale < HMATH_MAX_SHOWN ) + prec = n->n_scale; + } + + // yes, this is necessary! + bc_num m = h_round( n, prec ); + h_trimzeros( m ); + h_destroy( n ); + n = m; + if( oprec < 0 ) + { + prec = HMATH_MAX_SHOWN; + if( n->n_scale < HMATH_MAX_SHOWN ) + prec = n->n_scale; + } + + // how many to allocate? + int len = n->n_len + prec; + if( n->n_sign != PLUS ) len++; + if( prec > 0 ) len++; + + char* str = (char*)malloc( len+1 ); + char* p = str; + + // the sign and the integer part + // but avoid printing "-0" + if( n->n_sign != PLUS ) + if( !bc_is_zero( n ) ) *p++ = '-'; + for( int c=0; cn_len; c++ ) + *p++ = (char)BCD_CHAR( n->n_value[c] ); + + // the fraction part + if( prec > 0 ) + { + *p++ = '.'; + int k = (prec < n->n_scale) ? prec : n->n_scale; + for( int d=0; dn_value[n->n_len+d] ); + for( int r=n->n_scale; rnum->n_len+hn.d->num->n_scale; c++, tzeros++ ) + if( hn.d->num->n_value[c]!= 0 ) break; + int expd = hn.d->num->n_len - tzeros - 1; + + // extra digits needed for the exponent part + int expn = 0; + for( int e = ::abs(expd); e > 0; e/=10 ) expn++; + if( expd <= 0 ) expn++; + + // scale the number by a new factor + HNumber nn = hn * HMath::raise( 10, -expd ); + + // too close to zero? + if( hn.isZero() || ( expd <= -HMATH_COMPARE_PREC ) ) + { + nn = HNumber(0); + expd = 0; + expn = 1; + } + + char* str = formatFixed( nn, prec ); + char* result = (char*) malloc( strlen(str)+expn+2 ); + strcpy( result, str ); + free( str ); + + // the exponential part + char* p = result + strlen(result); + *p++ = 'e'; p[expn] = '\0'; + if( expd < 0 ) *p = '-'; + for( int k=expn; k>0; k-- ) + { + int digit = expd % 10; + p[k-1] = (char)('0' + ::abs( digit )); + expd = expd / 10; + if( expd == 0 ) break; + } + + return result; +} + +char* HMath::formatGeneral( const HNumber& hn, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + // find the exponent and the factor + int tzeros = 0; + for( int c=0; cnum->n_len+hn.d->num->n_scale; c++, tzeros++ ) + if( hn.d->num->n_value[c]!= 0 ) break; + int expd = hn.d->num->n_len - tzeros - 1; + + char* str; + if( expd > 5 ) + str = formatExp( hn, prec ); + else if( ( expd < -4 ) && (expd>-HMATH_COMPARE_PREC ) ) + str = formatExp( hn, prec ); + else if ( (expd < 0) && (prec>0) && (expd < -prec) ) + str = formatExp( hn, prec ); + else + str = formatFixed( hn, prec ); + + return str; +} + +QString HMath::formatGenString( const HNumber &n, int prec ) +{ + char *foo = formatGeneral(n, prec); + QString s(foo); + free(foo); + + return s; +} + +char* HMath::format( const HNumber& hn, char format, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + if( format=='g' ) + return formatGeneral( hn, prec ); + else if( format=='f' ) + return formatFixed( hn, prec ); + else if( format=='e' ) + return formatExp( hn, prec ); + + // fallback to 'g' + return formatGeneral( hn, prec ); +} + +HNumber HMath::pi() +{ + return HNumber("3.14159265358979323846264338327950288419716939937510" + "58209749445923078164062862089986280348253421170679" + "82148086513282306647093844609550582231725359408128" + "48111745028410270193852110555964462294895493038196" + "44288109756659334461284756482337867831652712019091" + "45648566923460348610454326648213393607260249141273" + "72458700660631558817488152092096282925409171536436" + "78925903600113305305488204665213841469519415116094" + "33057270365759591953092186117381932611793105118548" + "07446237996274956735188575272489122793818301194912" + "98336733624406566430860213949463952247371907021798" + "60943702770539217176293176752384674818467669405132" + "00056812714526356082778577134275778960917363717872" + "14684409012249534301465495853710507922796892589235" + "42019956112129021960864034418159813629774771309960" + "51870721134999999837297804995105973173281609631859" + "50244594553469083026425223082533446850352619311881" + "71010003137838752886587533208381420617177669147303" + "59825349042875546873115956286388235378759375195778" + "1857780532171226806613001927876611195909216420198" ); +} + +HNumber HMath::add( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 + n2; + return result; +} + +HNumber HMath::sub( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 - n2; + return result; +} + +HNumber HMath::mul( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 * n2; + return result; +} + +HNumber HMath::div( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 / n2; + return result; +} + +int HMath::compare( const HNumber& n1, const HNumber& n2 ) +{ + if( n1.isNan() && n2.isNan() ) return 0; + + HNumber delta = sub( n1, n2 ); + delta = HMath::round( delta, HMATH_COMPARE_PREC ); + if( delta.isZero() ) return 0; + else if( delta.isNegative() ) return -1; + return 1; +} + +HNumber HMath::abs( const HNumber& n ) +{ + HNumber r( n ); + r.d->num->n_sign = PLUS; + return r; +} + +HNumber HMath::negate( const HNumber& n ) +{ + if( n.isNan() || n.isZero() ) + return HNumber( n ); + + HNumber result( n ); + result.d->num->n_sign = ( n.d->num->n_sign == PLUS ) ? MINUS : PLUS; + return result; +} + +HNumber HMath::round( const HNumber& n, int prec ) +{ + if( n.isNan() ) + return HNumber::nan(); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_round( n.d->num, prec ); + return result; +} + +HNumber HMath::integer( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isZero() ) + return HNumber( 0 ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_rescale( n.d->num, 0 ); + return result; +} + +HNumber HMath::frac( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + return n - integer(n); +} + +HNumber HMath::sqrt( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isZero() ) + return n; + + if( n.isNegative() ) + return HNumber::nan(); + + // useful constant + HNumber half("0.5"); + + // Use Netwon-Raphson algorithm + HNumber r( 1 ); + for( int i = 0; i < HMATH_MAX_PREC; i++ ) + { + HNumber q = n / r; + if( r == q ) break; + HNumber s = r + q; + r = s * half; + } + + return r; +} + +HNumber HMath::raise( const HNumber& n1, int n ) +{ + if( n1.isNan() ) return n1; + + if( n1.isZero() ) return n1; + if( n1 == HNumber(1) ) return n1; + if( n == 0 ) return HNumber(1); + if( n == 1 ) return n1; + + HNumber result = n1; + for( ; n > 1; n-- ) result *= n1; + for( ; n < 1; n++ ) result /= n1; + + return result; +} + +HNumber HMath::raise( const HNumber& n1, const HNumber& n2 ) +{ + if( n1.isNan() ) return HNumber::nan(); + if( n2.isNan() ) return HNumber::nan(); + + if( n1.isZero() ) return HNumber(0); + if( n1 == HNumber(1) ) return n1; + if( n2.isZero() ) return HNumber(1); + if( n2 == HNumber(1) ) return n1; + + if( n2 == HMath::integer(n2) ) + { + // Evil hack. + char *str = HMath::format( n2 ); + int i = atoi(str); + free (str); + + return HMath::raise( n1, i ); + } + + // x^y = exp( y*ln(x) ) + HNumber result = n2 * HMath::ln(n1); + result = HMath::exp( result ); + + return result; +} + +HNumber HMath::exp( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + bool negative = x.isNegative(); + HNumber xs = HMath::abs( x ); + + // adjust so that x is less than 1 + // Taylor expansion: e^x = 1 + x + x^2/2! + x^3/3! + ... + HNumber num = xs; + HNumber den = 1; + HNumber sum = xs + 1; + + // now loop to sum the series + for( int i = 2; i < HMATH_MAX_PREC; i++ ) + { + num *= xs; + den *= HNumber(i); + if( num.isZero() ) break; + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum; + if( negative ) + result = HMath::div( HNumber(1), result ); + + return result; +}; + +HNumber HMath::ln( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + if( !x.isPositive() ) + return HNumber::nan(); + + // short circuit + if( x == HNumber(10) ) + return HNumber("2.30258509299404568401799145468436420760110148862877" + "29760333279009675726102948650438303813865953227795" + "49054520440916779445247118780973037711833599749301" + "72118016928228381938415404059160910960135436620869" ); + + // useful constants + HNumber two(2); + HNumber one(1); + HNumber half("0.5"); + + // adjust so that x is between 0.5 and 2.0 + // use the fact that ln(x^2) = 2*ln(x) + HNumber xs( x ); + unsigned factor = 2; + while( xs >= two ) + { + factor *= 2; + xs = HMath::sqrt( xs ); + } + while( xs <= half ) + { + factor *= 2; + xs = HMath::sqrt( xs ); + } + + // Taylor expansion: ln(x) = 2(a+a^3/3+a^5/5+...) + // where a=(x-1)/(x+1) + HNumber p = xs - 1; + HNumber q = xs + 1; + HNumber a = p / q; + HNumber as = a*a; + HNumber t = a; + HNumber sum = a; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+= 2 ) + { + t *= as; + if( t.isZero() ) break; + HNumber s = HMath::div( t, HNumber(i) ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum * HNumber( factor ); + return result; +} + +HNumber HMath::log( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + if( !x.isPositive() ) + return HNumber::nan(); + + HNumber result = HMath::ln( x ) / HMath::ln(10); + return result; +} + +// ensure angle is within 0 to 2*pi +// useful for sin, cos +static HNumber simplifyAngle( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + HNumber pi2 = HMath::pi() * 2; + HNumber nn = x / pi2; + HNumber xs = x - HMath::integer(nn)*pi2; + if( xs.isNegative() ) xs += pi2; + + return xs; +} + +HNumber HMath::sin( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // short circuit + if( x.isZero() ) + return x; + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // Taylor expansion: sin(x) = x - x^3/3! + x^5/5! - x^7/7! ... + HNumber xsq = xs*xs; + HNumber num = xs; + HNumber den = 1; + HNumber sum = xs; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den *= HNumber(i-1); + den *= HNumber(i); + den = HMath::negate( den ); + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum; + return result; +} + + +HNumber HMath::cos( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // short circuit + if( x.isZero() ) + return HNumber( 1 ); + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // Taylor expansion: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! ... + HNumber xsq = xs*xs; + HNumber num = 1; + HNumber den = 1; + HNumber sum = 1; + + // loop for the series (limited to avoid nasty cases) + for( int i = 2; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den *= HNumber(i-1); + den *= HNumber(i); + den = HMath::negate( den ); + HNumber s = num / den; + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum; + return result; +} + +HNumber HMath::tan( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // tan(x) = cos(x)/sin(x) + + HNumber s = HMath::sin( x ); + if( s.isZero() ) + return s; + + HNumber c = HMath::cos( x ); + if( c.isZero() ) + return HNumber::nan(); + + HNumber result = s / c; + return result; +} + +HNumber HMath::atan( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // useful constants + HNumber one("1.0"); + HNumber c( "0.2" ); + + // short circuit + if( x == c ) + return HNumber("0.19739555984988075837004976519479029344758510378785" + "21015176889402410339699782437857326978280372880441" + "12628118073691360104456479886794239355747565495216" + "30327005221074700156450155600612861855266332573187" ); + + if( x == one ) + // essentially equals to HMath::pi()/4; + return HNumber("0.78539816339744830961566084581987572104929234984377" + "64552437361480769541015715522496570087063355292669" + "95537021628320576661773461152387645557931339852032" + "12027936257102567548463027638991115573723873259549" ); + + bool negative = x.isNegative(); + HNumber xs = HMath::abs( x ); + + // adjust so that x is less than c (we choose c = 0.2) + // use the fact that atan(x) = atan(c) + atan((x-c)/(1+xc)) + HNumber factor(0); + HNumber base(0); + while( xs > c ) + { + base = HMath::atan( c ); + factor += one; + HNumber p = xs - c; + HNumber q = xs * c; + xs = p / (q+one); + } + + // Taylor series: atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ... + HNumber num = xs; + HNumber xsq = xs*xs; + HNumber den = 1; + HNumber sum = xs; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den = HNumber(i); + int n = (i-1)/2; + if( n&1 ) den = HNumber(-i); + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = factor*base + sum; + if( negative ) result = HMath::negate( result ); + return result; +}; + +HNumber HMath::asin( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // asin(x) = atan(x/sqrt(1-x*x)); + HNumber d = HMath::sqrt( HNumber(1) - x*x ); + if( d.isZero() ) + { + HNumber result = HMath::pi()/2; + if( x.isNegative() ) + result = HMath::negate( result ); + return result; + } + + HNumber result = HMath::atan( x / d ); + return result; +}; + +HNumber HMath::acos( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + if( x.isZero() ) + return HMath::pi()/2; + + // acos(x) = atan(sqrt(1-x*x)/x); + HNumber n = HMath::sqrt( HNumber(1) - x*x ); + + HNumber result = HMath::atan( n / x ); + return result; +}; + +HNumber HMath::sinh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // sinh(x) = 0.5*(e^x - e^(-x) ) + HNumber result = HMath::exp(x) - HMath::exp( HMath::negate(x) ); + result = result / 2; + + return result; +} + +HNumber HMath::asinh( const HNumber& x ) +{ + HNumber one(1); + + if(x.isNan()) + return HNumber::nan(); + + return HMath::ln(x + HMath::sqrt(x * x + one)); +} + +HNumber HMath::cosh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // cosh(x) = 0.5*(e^x - e^(-x) ) + HNumber result = HMath::exp(x) + HMath::exp( HMath::negate(x) ); + result = result / 2; + + return result; +} + +HNumber HMath::acosh( const HNumber& x ) +{ + HNumber one(1), zero(0); + + if(x.isNan() || x < one) + return HNumber::nan(); + + // We always return the positive arc hyperbolic cosine. + return HMath::ln(x + HMath::sqrt(x * x - one)); +} + +HNumber HMath::tanh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // tanh(h) = sinh(x)/cosh(x) + HNumber c = HMath::cosh( x ); + if( c.isZero() ) + return HNumber::nan(); + + HNumber s = HMath::sinh( x ); + HNumber result = s / c; + + return result; +} + +HNumber HMath::atanh( const HNumber& x ) +{ + HNumber one(1), two(2); + + if(x.isNan() || HMath::abs(x) >= one) + return HNumber::nan(); + + return HMath::ln((one + x) / (one - x)) / two; +} + +void HMath::finalize() +{ + bc_free_num( &_zero_ ); + bc_free_num( &_one_ ); + bc_free_num( &_two_ ); + free( _one_ ); + free( _zero_ ); + free( _two_ ); + h_grabfree(); + h_grabfree(); + h_grabfree(); + h_grabfree(); +} + +std::ostream& operator<<( std::ostream& s, HNumber num ) +{ + char* str = HMath::formatFixed( num ); + s << str; + delete[] str; + return s; +} + +#ifdef HMATH_TEST + +#include +#include + +static int hmath_total_tests = 0; +static int hmath_failed_tests = 0; + +#define CHECK(x,y) check_value(__FILE__,__LINE__,#x,x,y) +#define CHECK_FORMAT(f,p,x,y) check_format(__FILE__,__LINE__,#x,x,f,p,y) +#define CHECK_PRECISE(x,y) check_precise(__FILE__,__LINE__,#x,x,y) + +static void check_value( const char *file, int line, const char* msg, +const HNumber&n, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::formatFixed( n ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +static void check_format( const char *file, int line, const char* msg, +const HNumber&n, char format, int prec, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::format( n, format, prec ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +static void check_precise( const char *file, int line, const char* msg, +const HNumber&n, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::formatFixed( n, 50 ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +void test_create() +{ + CHECK( HNumber("1.0"), "1" ); + CHECK( HNumber("2.0"), "2" ); + CHECK( HNumber("1e-3"), "0.001" ); + + // too large or small + CHECK( HNumber("1e200"), "NaN" ); + CHECK( HNumber("1e-200"), "NaN" ); +} + +void test_format() +{ + // fixed decimal digits + CHECK_FORMAT( 'f', 0, HNumber("NaN"), "NaN" ); + CHECK_FORMAT( 'f', 0, HNumber("0"), "0" ); + CHECK_FORMAT( 'f', 0, HNumber("1.1"), "1" ); + CHECK_FORMAT( 'f', 1, HNumber("2.11"), "2.1" ); + CHECK_FORMAT( 'f', 2, HNumber("3.111"), "3.11" ); + CHECK_FORMAT( 'f', 3, HNumber("4.1111"), "4.111" ); + CHECK_FORMAT( 'f', 2, HNumber("3.14"), "3.14" ); + CHECK_FORMAT( 'f', 3, HNumber("3.14"), "3.140" ); + CHECK_FORMAT( 'f', 5, HNumber("3.14"), "3.14000" ); + CHECK_FORMAT( 'f', 7, HNumber("3.14"), "3.1400000" ); + CHECK_FORMAT( 'f', 7, HNumber("-0.001"), "-0.0010000" ); + CHECK_FORMAT( 'f', 8, HNumber("-0.001"), "-0.00100000" ); + CHECK_FORMAT( 'f', 9, HNumber("-0.001"), "-0.001000000" ); + CHECK_FORMAT( 'f', 1, HNumber("4.001"), "4.0" ); + CHECK_FORMAT( 'f', -1, HNumber("4.000000000000000000000000000000000000000000001"), "4" ); + + // exponential format + CHECK_FORMAT( 'e', 0, HNumber("NaN"), "NaN" ); + CHECK_FORMAT( 'e', 0, HNumber("0"), "0e0" ); + CHECK_FORMAT( 'e', 0, HNumber("3.14"), "3e0" ); + CHECK_FORMAT( 'e', 1, HNumber("3.14"), "3.1e0" ); + CHECK_FORMAT( 'e', 2, HNumber("3.14"), "3.14e0" ); + CHECK_FORMAT( 'e', 3, HNumber("3.14"), "3.140e0" ); + CHECK_FORMAT( 'e', 5, HNumber("3.14"), "3.14000e0" ); + CHECK_FORMAT( 'e', 7, HNumber("3.14"), "3.1400000e0" ); + CHECK_FORMAT( 'e', 3, HNumber("-0.001"), "-1.000e-3" ); + CHECK_FORMAT( 'e', 2, HNumber("0.0001"), "1.00e-4" ); + CHECK_FORMAT( 'e', 2, HNumber("0.001"), "1.00e-3" ); + CHECK_FORMAT( 'e', 2, HNumber("0.01"), "1.00e-2" ); + CHECK_FORMAT( 'e', 2, HNumber("0.1"), "1.00e-1" ); + CHECK_FORMAT( 'e', 2, HNumber("1"), "1.00e0" ); + CHECK_FORMAT( 'e', 2, HNumber("10"), "1.00e1" ); + CHECK_FORMAT( 'e', 2, HNumber("100"), "1.00e2" ); + CHECK_FORMAT( 'e', 2, HNumber("1000"), "1.00e3" ); + CHECK_FORMAT( 'e', 2, HNumber("10000"), "1.00e4" ); + CHECK_FORMAT( 'e', 2, HNumber("100000"), "1.00e5" ); + CHECK_FORMAT( 'e', 2, HNumber("1000000"), "1.00e6" ); + CHECK_FORMAT( 'e', 2, HNumber("10000000"), "1.00e7" ); + + // general format + CHECK_FORMAT( 'g', -1, HMath::pi(), "3.14159265358979323846" ); + CHECK_FORMAT( 'g', 3, HNumber("0"), "0.000" ); + CHECK_FORMAT( 'g', 3, HNumber("0.000000001"), "1.000e-9" ); + CHECK_FORMAT( 'g', 3, HNumber("0.00000001"), "1.000e-8" ); + CHECK_FORMAT( 'g', 3, HNumber("0.0000001"), "1.000e-7" ); + CHECK_FORMAT( 'g', 3, HNumber("0.000001"), "1.000e-6" ); + CHECK_FORMAT( 'g', 3, HNumber("0.00001"), "1.000e-5" ); + CHECK_FORMAT( 'g', 3, HNumber("0.0001"), "1.000e-4" ); + CHECK_FORMAT( 'g', 3, HNumber("0.001"), "0.001" ); + CHECK_FORMAT( 'g', 3, HNumber("0.01"), "0.010" ); + CHECK_FORMAT( 'g', 3, HNumber("0.1"), "0.100" ); + CHECK_FORMAT( 'g', 3, HNumber("10"), "10.000" ); + CHECK_FORMAT( 'g', 3, HNumber("100"), "100.000" ); + CHECK_FORMAT( 'g', 3, HNumber("1000"), "1000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("10000"), "10000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("100000"), "100000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("1000000"), "1.000e6" ); + CHECK_FORMAT( 'g', 3, HNumber("10000000"), "1.000e7" ); + CHECK_FORMAT( 'g', 3, HNumber("100000000"), "1.000e8" ); + CHECK_FORMAT( 'g', 3, HNumber("1403.1977"), "1403.198" ); + CHECK_FORMAT( 'g', 3, HNumber("2604.1980"), "2604.198" ); + CHECK_FORMAT( 'g', 3, HNumber("2.47e4"), "24700.000" ); + +} + +void test_op() +{ + // addition + CHECK( HNumber(0)+HNumber(0), "0" ); + CHECK( HNumber(1)+HNumber(0), "1" ); + CHECK( HNumber(1)+HNumber(1), "2" ); + CHECK( HNumber(1)+HNumber(2), "3" ); + CHECK( HNumber(1)+HNumber(10), "11" ); + CHECK( HNumber(1)+HNumber(100), "101" ); + CHECK( HNumber(1)+HNumber(1000), "1001" ); + + // subtraction + CHECK( HNumber(0)-HNumber(0), "0" ); + CHECK( HNumber(1)-HNumber(0), "1" ); + CHECK( HNumber(1)-HNumber(2), "-1" ); + + // division + CHECK( HNumber(1)/HNumber(2), "0.5" ); + CHECK_PRECISE( HNumber(1)/HNumber(3), "0.33333333333333333333333333333333333333333333333333" ); + CHECK_PRECISE( HNumber(2)/HNumber(3), "0.66666666666666666666666666666666666666666666666667" ); + CHECK_PRECISE( HNumber(1)/HNumber(7), "0.14285714285714285714285714285714285714285714285714" ); + CHECK_PRECISE( HNumber(2)/HNumber(7), "0.28571428571428571428571428571428571428571428571429" ); + CHECK_PRECISE( HNumber(3)/HNumber(7), "0.42857142857142857142857142857142857142857142857143" ); + CHECK_PRECISE( HNumber(4)/HNumber(7), "0.57142857142857142857142857142857142857142857142857" ); + CHECK_PRECISE( HNumber(1)/HNumber(9), "0.11111111111111111111111111111111111111111111111111" ); + + // multiplication + CHECK( HNumber(0)*HNumber(0), "0" ); + CHECK( HNumber("1.0")*HNumber("0.0"), "0" ); + CHECK( HNumber(1)*HNumber(1), "1" ); + CHECK( HNumber(3)*HNumber(-4), "-12" ); + CHECK( HNumber(-2)*HNumber(5), "-10" ); + CHECK( HNumber(6)*HNumber(7), "42" ); + CHECK( HNumber("1.5")*HNumber("1.5"), "2.25" ); +} + +void test_functions() +{ + // pi + CHECK( HMath::pi(), "3.14159265358979323846" ); + CHECK_PRECISE( HMath::pi(), "3.14159265358979323846264338327950288419716939937511" ); + + // abs + CHECK( HMath::abs("0"), "0" ); + CHECK( HMath::abs("1"), "1" ); + CHECK( HMath::abs("100"), "100" ); + CHECK( HMath::abs("-100"), "100" ); + CHECK( HMath::abs("-3.14159"), "3.14159" ); + CHECK( HMath::abs("NaN"), "NaN" ); + + // round + CHECK( HMath::round( "3.14" ), "3" ); + CHECK( HMath::round( "1.77" ), "2" ); + CHECK( HMath::round( "3.14159", 4 ), "3.1416" ); + CHECK( HMath::round( "3.14159", 3 ), "3.142" ); + CHECK( HMath::round( "3.14159", 2 ), "3.14" ); + CHECK( HMath::round( "3.14159", 1 ), "3.1" ); + CHECK( HMath::round( "3.14159", 0 ), "3" ); + CHECK( HMath::round( "-2.6041980", 4 ), "-2.6042" ); + CHECK( HMath::round( "-2.6041980", 3 ), "-2.604" ); + CHECK( HMath::round( "-2.6041980", 2 ), "-2.6" ); + CHECK( HMath::round( "-2.6041980", 1 ), "-2.6" ); + CHECK( HMath::round( "-2.6041980", 0 ), "-3" ); + CHECK( HMath::round( "NaN" ), "NaN" ); + + // integer + CHECK( HMath::integer( "0" ), "0" ); + CHECK( HMath::integer( "0.25" ), "0" ); + CHECK( HMath::integer( "0.85" ), "0" ); + CHECK( HMath::integer( "14.0377" ), "14" ); + CHECK( HMath::integer( "-0.25" ), "0" ); + CHECK( HMath::integer( "-0.85" ), "0" ); + CHECK( HMath::integer( "-14.0377" ), "-14" ); + CHECK( HMath::integer( "NaN" ), "NaN" ); + + // frac + CHECK( HMath::frac( "0" ), "0" ); + CHECK( HMath::frac( "3.14159" ), "0.14159" ); + CHECK( HMath::frac( "0.14159" ), "0.14159" ); + CHECK( HMath::frac( "-3.14159" ), "-0.14159" ); + CHECK( HMath::frac( "-0.14159" ), "-0.14159" ); + CHECK( HMath::frac( "NaN" ), "NaN" ); + + // checking function 'sqrt' + CHECK( HMath::sqrt(1), "1" ); + CHECK( HMath::sqrt(4), "2" ); + CHECK( HMath::sqrt(9), "3" ); + CHECK( HMath::sqrt(16), "4" ); + CHECK_PRECISE( HMath::sqrt(2), "1.41421356237309504880168872420969807856967187537695" ); + CHECK_PRECISE( HMath::sqrt(3), "1.73205080756887729352744634150587236694280525381038" ); + CHECK_PRECISE( HMath::sqrt(5), "2.23606797749978969640917366873127623544061835961153" ); + CHECK_PRECISE( HMath::sqrt(7), "2.64575131106459059050161575363926042571025918308245" ); + CHECK_PRECISE( HMath::sqrt(8), "2.82842712474619009760337744841939615713934375075390" ); + CHECK_PRECISE( HMath::sqrt(10), "3.16227766016837933199889354443271853371955513932522" ); + CHECK_PRECISE( HMath::sqrt(11), "3.31662479035539984911493273667068668392708854558935" ); + CHECK_PRECISE( HMath::sqrt(12), "3.46410161513775458705489268301174473388561050762076" ); + CHECK_PRECISE( HMath::sqrt(13), "3.60555127546398929311922126747049594625129657384525" ); + CHECK_PRECISE( HMath::sqrt(14), "3.74165738677394138558374873231654930175601980777873" ); + CHECK_PRECISE( HMath::sqrt(15), "3.87298334620741688517926539978239961083292170529159" ); + CHECK_PRECISE( HMath::sqrt(17), "4.12310562561766054982140985597407702514719922537362" ); + CHECK_PRECISE( HMath::sqrt(18), "4.24264068711928514640506617262909423570901562613084" ); + CHECK_PRECISE( HMath::sqrt(19), "4.35889894354067355223698198385961565913700392523244" ); + CHECK_PRECISE( HMath::sqrt(20), "4.47213595499957939281834733746255247088123671922305" ); + CHECK( HMath::sqrt("0.04"), "0.2" ); + CHECK( HMath::sqrt("0.09"), "0.3" ); + CHECK( HMath::sqrt("0.16"), "0.4" ); + CHECK( HMath::sqrt(-1), "NaN" ); + CHECK( HMath::sqrt("NaN"), "NaN" ); + + // raise + CHECK( HMath::raise(10,-3), "0.001" ); + CHECK( HMath::raise(10,-2), "0.01" ); + CHECK( HMath::raise(10,-1), "0.1" ); + CHECK( HMath::raise(10,0), "1" ); + CHECK( HMath::raise(10,1), "10" ); + CHECK( HMath::raise(10,2), "100" ); + CHECK( HMath::raise(10,3), "1000" ); + CHECK( HMath::raise(10,4), "10000" ); + CHECK( HMath::raise("2","2"), "4" ); + CHECK( HMath::raise("3","3"), "27" ); + CHECK( HMath::raise("4","4"), "256" ); + CHECK_PRECISE( HMath::raise("2","0.1"), "1.07177346253629316421300632502334202290638460497756" ); + CHECK_PRECISE( HMath::raise("2","0.2"), "1.14869835499703500679862694677792758944385088909780" ); + CHECK_PRECISE( HMath::raise("2","0.3"), "1.23114441334491628449939306916774310987613776110082" ); + CHECK( HMath::raise("NaN","0"), "NaN" ); + CHECK( HMath::raise("-1","NaN"), "NaN" ); + + // exp + CHECK_PRECISE( HMath::exp("0.1"), "1.10517091807564762481170782649024666822454719473752" ); + CHECK_PRECISE( HMath::exp("0.2"), "1.22140275816016983392107199463967417030758094152050" ); + CHECK_PRECISE( HMath::exp("0.3"), "1.34985880757600310398374431332800733037829969735937" ); + CHECK_PRECISE( HMath::exp("0.4"), "1.49182469764127031782485295283722228064328277393743" ); + CHECK_PRECISE( HMath::exp("0.5"), "1.64872127070012814684865078781416357165377610071015" ); + CHECK_PRECISE( HMath::exp("0.6"), "1.82211880039050897487536766816286451338223880854644" ); + CHECK_PRECISE( HMath::exp("0.7"), "2.01375270747047652162454938858306527001754239414587" ); + CHECK_PRECISE( HMath::exp("0.8"), "2.22554092849246760457953753139507675705363413504848" ); + CHECK_PRECISE( HMath::exp("0.9"), "2.45960311115694966380012656360247069542177230644008" ); + CHECK_PRECISE( HMath::exp("1.0"), "2.71828182845904523536028747135266249775724709369996" ); + + // ln + CHECK_PRECISE( HMath::ln("0.1"), "-2.30258509299404568401799145468436420760110148862877" ); + CHECK_PRECISE( HMath::ln("0.2"), "-1.60943791243410037460075933322618763952560135426852" ); + CHECK_PRECISE( HMath::ln("0.3"), "-1.20397280432593599262274621776183850295361093080602" ); + CHECK_PRECISE( HMath::ln("0.4"), "-0.91629073187415506518352721176801107145010121990826" ); + CHECK_PRECISE( HMath::ln("0.5"), "-0.69314718055994530941723212145817656807550013436026" ); + CHECK_PRECISE( HMath::ln("0.6"), "-0.51082562376599068320551409630366193487811079644577" ); + CHECK_PRECISE( HMath::ln("0.7"), "-0.35667494393873237891263871124118447796401675904691" ); + CHECK_PRECISE( HMath::ln("0.8"), "-0.22314355131420975576629509030983450337460108554801" ); + CHECK_PRECISE( HMath::ln("0.9"), "-0.10536051565782630122750098083931279830612037298327" ); + CHECK_PRECISE( HMath::ln("1.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::ln("1.1"), "0.09531017980432486004395212328076509222060536530864" ); + CHECK_PRECISE( HMath::ln("1.2"), "0.18232155679395462621171802515451463319738933791449" ); + CHECK_PRECISE( HMath::ln("1.3"), "0.26236426446749105203549598688095439720416645613143" ); + CHECK_PRECISE( HMath::ln("1.4"), "0.33647223662121293050459341021699209011148337531334" ); + CHECK_PRECISE( HMath::ln("1.5"), "0.40546510810816438197801311546434913657199042346249" ); + CHECK_PRECISE( HMath::ln("1.6"), "0.47000362924573555365093703114834206470089904881225" ); + CHECK_PRECISE( HMath::ln("1.7"), "0.53062825106217039623154316318876232798710152395697" ); + CHECK_PRECISE( HMath::ln("1.8"), "0.58778666490211900818973114061886376976937976137698" ); + CHECK_PRECISE( HMath::ln("1.9"), "0.64185388617239477599103597720348932963627777267036" ); + CHECK_PRECISE( HMath::ln("2.0"), "0.69314718055994530941723212145817656807550013436026" ); + CHECK_PRECISE( HMath::ln("3.0"), "1.09861228866810969139524523692252570464749055782275" ); + CHECK_PRECISE( HMath::ln("4.0"), "1.38629436111989061883446424291635313615100026872051" ); + CHECK_PRECISE( HMath::ln("100"), "4.60517018598809136803598290936872841520220297725755" ); + + // log + CHECK( HMath::log("1e-5"), "-5" ); + CHECK( HMath::log("1e-4"), "-4" ); + CHECK( HMath::log("1e-3"), "-3" ); + CHECK( HMath::log("10"), "1" ); + CHECK( HMath::log("100"), "2" ); + CHECK( HMath::log("1000"), "3" ); + CHECK( HMath::log("10000"), "4" ); + CHECK( HMath::log("1e5"), "5" ); + CHECK( HMath::log("1e6"), "6" ); + CHECK( HMath::log("1e7"), "7" ); + CHECK( HMath::log("1e8"), "8" ); + CHECK( HMath::log("1e9"), "9" ); + CHECK( HMath::log("1e10"), "10" ); + CHECK( HMath::log("-1"), "NaN" ); + CHECK( HMath::log("NaN"), "NaN" ); + + // sin + CHECK( HMath::sin( "0" ), "0" ); + CHECK( HMath::sin( HMath::pi()/4 ), "0.7071067811865475244" ); + CHECK( HMath::sin( HMath::pi()/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()/2 ), "1" ); + CHECK( HMath::sin( HMath::pi()/1 ), "0" ); + CHECK( HMath::sin( HMath::pi()*2/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*4/3 ), "-0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*5/3 ), "-0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*6/3 ), "0"); + CHECK( HMath::sin( HMath::pi()*7/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*9/3 ), "0"); + CHECK_PRECISE( HMath::sin("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::sin("0.1"), "0.09983341664682815230681419841062202698991538801798" ); + CHECK_PRECISE( HMath::sin("0.2"), "0.19866933079506121545941262711838975037020672954021" ); + CHECK_PRECISE( HMath::sin("0.3"), "0.29552020666133957510532074568502737367783211174262" ); + CHECK_PRECISE( HMath::sin("0.4"), "0.38941834230865049166631175679570526459306018344396" ); + CHECK_PRECISE( HMath::sin("0.5"), "0.47942553860420300027328793521557138808180336794060" ); + CHECK_PRECISE( HMath::sin("0.6"), "0.56464247339503535720094544565865790710988808499415" ); + CHECK_PRECISE( HMath::sin("0.7"), "0.64421768723769105367261435139872018306581384457369" ); + CHECK_PRECISE( HMath::sin("0.8"), "0.71735609089952276162717461058138536619278523779142" ); + CHECK_PRECISE( HMath::sin("0.9"), "0.78332690962748338846138231571354862314014792572031" ); + CHECK_PRECISE( HMath::sin("1.0"), "0.84147098480789650665250232163029899962256306079837" ); + CHECK_PRECISE( HMath::sin("2.0"), "0.90929742682568169539601986591174484270225497144789" ); + CHECK_PRECISE( HMath::sin("3.0"), "0.14112000805986722210074480280811027984693326425227" ); + CHECK_PRECISE( HMath::sin("4.0"), "-0.75680249530792825137263909451182909413591288733647" ); + CHECK_PRECISE( HMath::sin("5.0"), "-0.95892427466313846889315440615599397335246154396460" ); + + // cos + CHECK( HMath::cos( "0" ), "1" ); + CHECK( HMath::cos( HMath::pi()/4 ), "0.7071067811865475244" ); + CHECK( HMath::cos( HMath::pi()/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()/2 ), "0" ); + CHECK( HMath::cos( HMath::pi()/1 ), "-1" ); + CHECK( HMath::cos( HMath::pi()*2/3 ), "-0.5"); + CHECK( HMath::cos( HMath::pi()*4/3 ), "-0.5"); + CHECK( HMath::cos( HMath::pi()*5/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()*6/3 ), "1"); + CHECK( HMath::cos( HMath::pi()*7/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()*9/3 ), "-1"); + CHECK_PRECISE( HMath::cos("0.0"), "1.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::cos("0.1"), "0.99500416527802576609556198780387029483857622541508" ); + CHECK_PRECISE( HMath::cos("0.2"), "0.98006657784124163112419651674816887739352436080657" ); + CHECK_PRECISE( HMath::cos("0.3"), "0.95533648912560601964231022756804989824421408263204" ); + CHECK_PRECISE( HMath::cos("0.4"), "0.92106099400288508279852673205180161402585956931985" ); + CHECK_PRECISE( HMath::cos("0.5"), "0.87758256189037271611628158260382965199164519710974" ); + CHECK_PRECISE( HMath::cos("0.6"), "0.82533561490967829724095249895537603887809103918847" ); + CHECK_PRECISE( HMath::cos("0.7"), "0.76484218728448842625585999019186490926821055037370" ); + CHECK_PRECISE( HMath::cos("0.8"), "0.69670670934716542092074998164232492610178601370806" ); + CHECK_PRECISE( HMath::cos("0.9"), "0.62160996827066445648471615140713350872176136659124" ); + CHECK_PRECISE( HMath::cos("1.0"), "0.54030230586813971740093660744297660373231042061792" ); + CHECK_PRECISE( HMath::cos("2.0"), "-0.41614683654714238699756822950076218976600077107554" ); + CHECK_PRECISE( HMath::cos("3.0"), "-0.98999249660044545727157279473126130239367909661559" ); + CHECK_PRECISE( HMath::cos("4.0"), "-0.65364362086361191463916818309775038142413359664622" ); + + // tan + CHECK( HMath::tan( HMath::pi()/4 ), "1" ); + CHECK( HMath::tan( HMath::pi()/3 ), "1.73205080756887729353"); + CHECK( HMath::tan( HMath::pi()/1 ), "0" ); + CHECK_PRECISE( HMath::tan("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::tan("0.1"), "0.10033467208545054505808004578111153681900480457644" ); + CHECK_PRECISE( HMath::tan("0.2"), "0.20271003550867248332135827164753448262687566965163" ); + CHECK_PRECISE( HMath::tan("0.3"), "0.30933624960962323303530367969829466725781590680046" ); + CHECK_PRECISE( HMath::tan("0.4"), "0.42279321873816176198163542716529033394198977271569" ); + CHECK_PRECISE( HMath::tan("0.5"), "0.54630248984379051325517946578028538329755172017979" ); + CHECK_PRECISE( HMath::tan("0.6"), "0.68413680834169231707092541746333574524265408075678" ); + CHECK_PRECISE( HMath::tan("0.7"), "0.84228838046307944812813500221293771718722125080420" ); + CHECK_PRECISE( HMath::tan("0.8"), "1.02963855705036401274636117282036528416821960677231" ); + CHECK_PRECISE( HMath::tan("0.9"), "1.26015821755033913713457548539574847783362583439629" ); + CHECK_PRECISE( HMath::tan("1.0"), "1.55740772465490223050697480745836017308725077238152" ); + CHECK_PRECISE( HMath::tan("2.0"), "-2.18503986326151899164330610231368254343201774622766" ); + CHECK_PRECISE( HMath::tan("3.0"), "-0.14254654307427780529563541053391349322609228490180" ); + CHECK_PRECISE( HMath::tan("4.0"), "1.15782128234957758313734241826732392311976276736714" ); + + // atan + CHECK_PRECISE( HMath::atan("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::atan("0.1"), "0.09966865249116202737844611987802059024327832250431" ); + CHECK_PRECISE( HMath::atan("0.2"), "0.19739555984988075837004976519479029344758510378785" ); + CHECK_PRECISE( HMath::atan("0.3"), "0.29145679447786709199560462143289119350316759901207" ); + CHECK_PRECISE( HMath::atan("0.4"), "0.38050637711236488630358791681043310449740571365810" ); + CHECK_PRECISE( HMath::atan("0.5"), "0.46364760900080611621425623146121440202853705428612" ); + CHECK_PRECISE( HMath::atan("0.6"), "0.54041950027058415544357836460859991013514825146259" ); + CHECK_PRECISE( HMath::atan("1.0"), "0.78539816339744830961566084581987572104929234984378" ); + CHECK_PRECISE( HMath::atan("-0.1"), "-0.09966865249116202737844611987802059024327832250431" ); + CHECK_PRECISE( HMath::atan("-0.2"), "-0.19739555984988075837004976519479029344758510378785" ); + CHECK_PRECISE( HMath::atan("-0.3"), "-0.29145679447786709199560462143289119350316759901207" ); + CHECK_PRECISE( HMath::atan("-0.4"), "-0.38050637711236488630358791681043310449740571365810" ); + CHECK_PRECISE( HMath::atan("-0.5"), "-0.46364760900080611621425623146121440202853705428612" ); + CHECK_PRECISE( HMath::atan("-0.6"), "-0.54041950027058415544357836460859991013514825146259" ); + CHECK_PRECISE( HMath::atan("-1.0"), "-0.78539816339744830961566084581987572104929234984378" ); + + // asin + CHECK_PRECISE( HMath::asin("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::asin("0.1"), "0.10016742116155979634552317945269331856867597222963" ); + CHECK_PRECISE( HMath::asin("0.2"), "0.20135792079033079145512555221762341024003808140223" ); + CHECK_PRECISE( HMath::asin("0.3"), "0.30469265401539750797200296122752916695456003170678" ); + CHECK_PRECISE( HMath::asin("0.4"), "0.41151684606748801938473789761733560485570113512703" ); + + // acos + CHECK_PRECISE( HMath::acos("0.1"), "1.47062890563333682288579851218705812352990872745792" ); + CHECK_PRECISE( HMath::acos("0.2"), "1.36943840600456582777619613942212803185854661828532" ); + CHECK_PRECISE( HMath::acos("0.3"), "1.26610367277949911125931873041222227514402466798078" ); + CHECK_PRECISE( HMath::acos("0.4"), "1.15927948072740859984658379402241583724288356456053" ); + + // consistency: tan vs atan + CHECK( HMath::atan("0.10033467208545054505808004578111153681900480457644"), "0.1" ); + CHECK( HMath::atan("0.20271003550867248332135827164753448262687566965163"), "0.2" ); + CHECK( HMath::atan("0.30933624960962323303530367969829466725781590680046"), "0.3" ); + CHECK( HMath::atan("0.42279321873816176198163542716529033394198977271569"), "0.4" ); + CHECK( HMath::atan("0.54630248984379051325517946578028538329755172017979"), "0.5" ); + CHECK( HMath::atan("0.68413680834169231707092541746333574524265408075678"), "0.6" ); + CHECK( HMath::atan("0.84228838046307944812813500221293771718722125080420"), "0.7" ); + CHECK( HMath::atan("1.02963855705036401274636117282036528416821960677231"), "0.8" ); + CHECK( HMath::atan("1.26015821755033913713457548539574847783362583439629"), "0.9" ); + CHECK( HMath::atan("1.55740772465490223050697480745836017308725077238152"), "1" ); + + // consistency: sin vs asin for small angle + CHECK( HMath::asin("0.09983341664682815230681419841062202698991538801798" ), "0.1"); + CHECK( HMath::asin("0.19866933079506121545941262711838975037020672954021" ), "0.2"); + CHECK( HMath::asin("0.29552020666133957510532074568502737367783211174262" ), "0.3"); + CHECK( HMath::asin("0.38941834230865049166631175679570526459306018344396" ), "0.4"); + CHECK( HMath::asin("0.47942553860420300027328793521557138808180336794060" ), "0.5"); + CHECK( HMath::asin("0.56464247339503535720094544565865790710988808499415" ), "0.6"); + CHECK( HMath::asin("0.64421768723769105367261435139872018306581384457369" ), "0.7"); + CHECK( HMath::asin("0.71735609089952276162717461058138536619278523779142" ), "0.8"); + + // sinh + CHECK_PRECISE( HMath::sinh("0.1"), "0.10016675001984402582372938352190502351492091687856" ); + CHECK_PRECISE( HMath::sinh("0.2"), "0.20133600254109398762556824301031737297449484262574" ); + CHECK_PRECISE( HMath::sinh("0.3"), "0.30452029344714261895843526700509522909802423268018" ); + CHECK_PRECISE( HMath::sinh("0.4"), "0.41075232580281550854021001384469810435315092436331" ); + CHECK_PRECISE( HMath::sinh("0.5"), "0.52109530549374736162242562641149155910592898261148" ); + CHECK_PRECISE( HMath::sinh("0.6"), "0.63665358214824127112345437546514831902496342592790" ); + CHECK_PRECISE( HMath::sinh("0.7"), "0.75858370183953350345987464759276815415493761421703" ); + CHECK_PRECISE( HMath::sinh("0.8"), "0.88810598218762300657471757318975698055970959688815" ); + CHECK_PRECISE( HMath::sinh("0.9"), "1.02651672570817527595833616197842235379403446513485" ); + CHECK_PRECISE( HMath::sinh("1.0"), "1.17520119364380145688238185059560081515571798133410" ); + + // cosh + CHECK_PRECISE( HMath::cosh("0.1"), "1.00500416805580359898797844296834164470962627785896" ); + CHECK_PRECISE( HMath::cosh("0.2"), "1.02006675561907584629550375162935679733308609889476" ); + CHECK_PRECISE( HMath::cosh("0.3"), "1.04533851412886048502530904632291210128027546467919" ); + CHECK_PRECISE( HMath::cosh("0.4"), "1.08107237183845480928464293899252417629013184957412" ); + CHECK_PRECISE( HMath::cosh("0.5"), "1.12762596520638078522622516140267201254784711809867" ); + CHECK_PRECISE( HMath::cosh("0.6"), "1.18546521824226770375191329269771619435727538261853" ); + CHECK_PRECISE( HMath::cosh("0.7"), "1.25516900563094301816467474099029711586260477992884" ); + CHECK_PRECISE( HMath::cosh("0.8"), "1.33743494630484459800481995820531977649392453816033" ); + CHECK_PRECISE( HMath::cosh("0.9"), "1.43308638544877438784179040162404834162773784130523" ); + CHECK_PRECISE( HMath::cosh("1.0"), "1.54308063481524377847790562075706168260152911236586" ); + +} + +int test_hmath() +{ + hmath_total_tests = 0; + hmath_failed_tests = 0; + + test_create(); + test_format(); + test_op(); + test_functions(); + + std::cout << hmath_total_tests << " total, "; + std::cout << hmath_failed_tests << " failed\n"; + + HMath::finalize(); + return hmath_failed_tests; +}; + +#endif + +// vim: set et sw=2 ts=8: diff --git a/src/hmath.h b/src/hmath.h new file mode 100644 index 0000000..dae4749 --- /dev/null +++ b/src/hmath.h @@ -0,0 +1,359 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat + + This file was copied from the SpeedCrunch program. Please visit + http://speedcrunch.berlios.de/ for more information. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, write to: + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02110-1301 USA. + + */ + +#ifndef HMATH_H +#define HMATH_H + +#include + +class HMath; + +class HNumber +{ +friend class HMath; + +public: + + /*! + * Creates a new number. + */ + HNumber(); + + /*! + * Destroys this number. + */ + ~HNumber(); + + /*! + * Copies from another number. + */ + HNumber( const HNumber& ); + + /*! + * Assigns from another number. + */ + HNumber& operator=( const HNumber& ); + + /*! + * Creates a new number from an integer value. + */ + HNumber( int i ); + + /*! + * Creates a new number from a string. + */ + HNumber( const char* ); + + /*! + * Returns true if this number is Not a Number (NaN). + */ + bool isNan() const; + + /*! + * Returns true if this number is zero. + */ + bool isZero() const; + + /*! + * Returns true if this number is more than zero. + */ + bool isPositive() const; + + /*! + * Returns true if this number is less than zero. + */ + bool isNegative() const; + + /*! + * Adds another number. + */ + HNumber operator+( const HNumber& ) const; + + /*! + * Adds another number. + */ + HNumber& operator+=( const HNumber& ); + + /*! + * Subtract from another number. + */ + HNumber operator-( const HNumber& ) const; + + /*! + * Subtract from another number. + */ + HNumber& operator-=( const HNumber& ); + + /*! + * Multiplies with another number. + */ + HNumber operator*( const HNumber& ) const; + + /*! + * Multiplies with another number. + */ + HNumber& operator*=( const HNumber& ); + + /*! + * Divides with another number. + */ + HNumber operator/( const HNumber& ) const; + + /*! + * Divides with another number. + */ + HNumber& operator/=( const HNumber& ); + + /*! + * Returns true if this number is greater than n. + */ + bool operator>( const HNumber& n ) const; + + /*! + * Returns true if this number is less than n. + */ + bool operator<( const HNumber& n ) const; + + /*! + * Returns true if this number is greater than or equal to n. + */ + bool operator>=( const HNumber& n ) const; + + /*! + * Returns true if this number is less than or equal to n. + */ + bool operator<=( const HNumber& n ) const; + + /*! + * Returns true if this number is equal to n. + */ + bool operator==( const HNumber& n ) const; + + /*! + * Returns true if this number is not equal to n. + */ + bool operator!=( const HNumber& n ) const; + + /*! + * Returns a NaN (Not a Number). + */ + static HNumber nan(); + +private: + class Private; + Private* d; +}; + +class QString; + +class HMath +{ +public: + + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* format( const HNumber&n, char format = 'g', int prec = -1 ); + + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* formatFixed( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, in exponential format. + * Note that the returned string must be freed. + */ + static char* formatExp( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* formatGeneral( const HNumber&n, int prec = -1 ); + + static QString formatGenString( const HNumber &n, int prec = -1 ); + + /*! + * Returns the constant pi. + */ + static HNumber pi(); + + /*! + * Adds two numbers. + */ + static HNumber add( const HNumber& n1, const HNumber& n2 ); + + /*! + * Subtracts two numbers. + */ + static HNumber sub( const HNumber& n1, const HNumber& n2 ); + + /*! + * Multiplies two numbers. + */ + static HNumber mul( const HNumber& n1, const HNumber& n2 ); + + /*! + * Divides two numbers. + */ + static HNumber div( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns -1, 0, 1 if n1 is less than, equal to, or more than n2. + */ + static int compare( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns the absolute value of n. + */ + static HNumber abs( const HNumber& n ); + + /*! + * Returns the negative of n. + */ + static HNumber negate( const HNumber& n ); + + /*! + * Returns the integer part of n. + */ + static HNumber integer( const HNumber& n ); + + /*! + * Returns the fraction part of n. + */ + static HNumber frac( const HNumber& n ); + + /*! + * Rounds n to the specified decimal digits. + */ + static HNumber round( const HNumber&n, int prec = 0 ); + + /*! + * Returns the square root of n. If n is negative, returns NaN. + */ + static HNumber sqrt( const HNumber& n ); + + /*! + * Raises n1 to an integer n. + */ + static HNumber raise( const HNumber& n1, int n ); + + /*! + * Raises n1 to n2. + */ + static HNumber raise( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns e raised to x. + */ + static HNumber exp( const HNumber& x ); + + /*! + * Returns the natural logarithm of x. + * If x is non positive, returns NaN. + */ + static HNumber ln( const HNumber& x ); + + /*! + * Returns the base-10 logarithm of x. + * If x is non positive, returns NaN. + */ + static HNumber log( const HNumber& x ); + + /*! + * Returns the sine of x. Note that x must be in radians. + */ + static HNumber sin( const HNumber& x ); + + /*! + * Returns the cosine of x. Note that x must be in radians. + */ + static HNumber cos( const HNumber& x ); + + /*! + * Returns the tangent of x. Note that x must be in radians. + */ + static HNumber tan( const HNumber& x ); + + /*! + * Returns the arc sine of x. + */ + static HNumber asin( const HNumber& x ); + + /*! + * Returns the arc cosine of x. + */ + static HNumber acos( const HNumber& x ); + + /*! + * Returns the arc tangent of x. + */ + static HNumber atan( const HNumber& x ); + + /*! + * Returns the hyperbolic sine of x. Note that x must be in radians. + */ + static HNumber sinh( const HNumber& x ); + + /*! + * Returns the arc hyperbolic sine of x. The result is in radians. + */ + static HNumber asinh( const HNumber & x ); + + /*! + * Returns the hyperbolic cosine of x. Note that x must be in radians. + */ + static HNumber cosh( const HNumber& x ); + + /*! + * Returns the arc hyperbolic cosine of x. The result is in radians. + */ + static HNumber acosh( const HNumber & x ); + + /*! + * Returns the hyperbolic tangent of x. Note that x must be in radians. + */ + static HNumber tanh( const HNumber& x ); + + /*! + * Returns the arc hyperbolic tangent of x. The result is in radians. + */ + static HNumber atanh( const HNumber & x ); + + /*! + * Releases all resources. After calling this function, you can not use + * any other functions as well as class HNumber. + */ + static void finalize(); + +}; + +std::ostream& operator<<( std::ostream& s, HNumber ); + +#endif // HMATH_H + +// vim: set et sw=2 ts=8: diff --git a/src/lexer.ll b/src/lexer.ll new file mode 100644 index 0000000..8ec48e2 --- /dev/null +++ b/src/lexer.ll @@ -0,0 +1,223 @@ +/* + * lexer.ll - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +%option noyywrap +%{ +#define YY_NO_UNPUT + +#include + +#include "node.h" +#include "function.h" +#include "parser_yacc.hpp" +#include "result.h" + +int yyCurTokenPos; +int yyThisTokenLength; + +int yyparse(void); +%} + +DIGITS [0-9]+ +HEX [0-9A-Fa-f]+ +%% + + /* Always skip whitespace */ +[ \t]* { yyCurTokenPos += yyThisTokenLength; yyThisTokenLength = yyleng; } + + /* Power operator */ +"**" { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 2; + return POWER; +} + +"^" { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 1; + return POWER; +} + +[sS][eE][tT] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 3; + return SET; +} + +[rR][eE][mM][oO][vV][eE] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 6; + return REMOVE; +} + +[dD][eE][rR][iI][vV] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 5; + return DERIV; +} + + /* Read numbers of the form with at least the decimal point and trailing + * digits, such as .32, -234.45, .0, etc. Numbers are only read in the BEGIN + * state. + */ +{DIGITS}*([\.,]{DIGITS}+)(e[-+]?{DIGITS}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* Read Hex */ +0x({HEX}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* Read numbers with at least the integral part, such as +4234, -34e8, etc. + * Numbers are only read in the BEGIN state. + */ +{DIGITS}+([\.,]{DIGITS}*)?(e[-+]?{DIGITS}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + +[nN][aA][nN] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + +[iI][nN][fF] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* This detects textual input, and if it isn't pre-declared by the parser (in + * other words, if it isn't a function), then it is returned as an identifier. + */ +[a-zA-Z_][a-zA-Z_0-9]* { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + + if(FunctionManager::instance()->isFunction(yytext)) + return FN; + else { + return ID; + } +} + + /* All other characters are returned as-is to the parser, who can accept or + * reject it as needed. + */ +. { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 1; + return *yytext; +} + +%% + +class Lexer::Private +{ +public: + YY_BUFFER_STATE buffer; + int lastToken, thisToken; + int lastPos, thisPos; + QString lastTokenData, thisTokenData; +}; + +/* Declared in function.h, implemented here in lexer.l since this is where + * all the yy_*() functions and types are defined. + */ +Lexer::Lexer(const QString &expr) : + m_private(new Private) +{ + const char *exprString = expr.latin1(); + + yyCurTokenPos = 0; + yyThisTokenLength = 0; + + m_private->buffer = yy_scan_string(exprString ? exprString : ""); + m_private->lastToken = -1; + m_private->lastPos = -1; + + m_private->thisToken = yylex(); + m_private->thisTokenData = QString(yytext); + + if(yyCurTokenPos != 0) + { + kdError() << "yyCurTokenPos should be 0!!\n"; + } + + m_private->thisPos = yyCurTokenPos; +} + +Lexer::~Lexer() +{ + yy_delete_buffer(m_private->buffer); + delete m_private; +} + +bool Lexer::hasNext() const +{ + return m_private->thisToken > 0; +} + +int Lexer::nextType() +{ + m_private->lastTokenData = m_private->thisTokenData; + m_private->lastPos = m_private->thisPos; + m_private->lastToken = m_private->thisToken; + + m_private->thisToken = yylex(); + m_private->thisTokenData = QString(yytext); + m_private->thisPos = yyCurTokenPos; + + return m_private->lastToken; +} + +QString Lexer::tokenValue() const +{ + return m_private->lastTokenData; +} + +int Lexer::tokenPos() const +{ + return m_private->lastPos; +} + +/* Declared in function.h, implemented here in lexer.l since this is where + * all the yy_*() functions and types are defined. + */ +Abakus::number_t parseString(const char *str) +{ + YY_BUFFER_STATE buffer = yy_scan_string(str); + + yyCurTokenPos = 0; + yyThisTokenLength = 0; + + yyparse(); + yy_delete_buffer(buffer); + + if(Result::lastResult()->type() != Result::Value) + return Abakus::number_t(); + + return Result::lastResult()->result()->value(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..8300dbb --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,825 @@ +/* + * mainwindow.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "mainwindow.h" +#include "abakuscommon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "editor.h" +#include "evaluator.h" +#include "function.h" +#include "resultlistview.h" +#include "resultlistviewtext.h" +#include "valuemanager.h" +#include "node.h" +#include "rpnmuncher.h" +#include "dcopIface.h" +#include "abakuslistview.h" +#include "result.h" + +MainWindow::MainWindow() : KMainWindow(0, "abakus-mainwindow"), m_popup(0), m_insert(false) +{ + m_mainSplitter = new QSplitter(this); + QWidget *box = new QWidget(m_mainSplitter); + QVBoxLayout *layout = new QVBoxLayout(box); + m_layout = layout; + layout->setSpacing(6); + layout->setMargin(6); + + QWidget *configBox = new QWidget(box); + layout->addWidget(configBox); + + QHBoxLayout *configLayout = new QHBoxLayout(configBox); + + configLayout->addWidget(new QWidget(configBox)); + + QLabel *label = new QLabel(i18n("History: "), configBox); + label->setAlignment(AlignCenter); + configLayout->addWidget(label); + + QButtonGroup *buttonGroup = new QButtonGroup(0); + + QWidget *buttonGroupBox = new QWidget(configBox); + QHBoxLayout *buttonGroupLayout = new QHBoxLayout(buttonGroupBox); + buttonGroupLayout->addStretch(0); + + configLayout->addWidget(buttonGroupBox); + + m_degrees = new QRadioButton(i18n("&Degrees"), buttonGroupBox); + buttonGroup->insert(m_degrees); + buttonGroupLayout->addWidget(m_degrees); + slotDegrees(); + connect(m_degrees, SIGNAL(clicked()), SLOT(slotDegrees())); + + m_radians = new QRadioButton(i18n("&Radians"), buttonGroupBox); + buttonGroup->insert(m_radians); + buttonGroupLayout->addWidget(m_radians); + connect(m_radians, SIGNAL(clicked()), SLOT(slotRadians())); + + m_history = new QVBox(box); + layout->addWidget(m_history); + m_history->setSpacing(6); + m_history->setMargin(0); + + m_result = new ResultListView(m_history); + m_result->setSelectionMode(QListView::NoSelection); + m_result->setHScrollBarMode(ResultListView::AlwaysOff); + connect(m_result, SIGNAL(signalEntrySelected(const QString &)), + SLOT(slotEntrySelected(const QString &))); + connect(m_result, SIGNAL(signalResultSelected(const QString &)), + SLOT(slotResultSelected(const QString &))); + + m_history->setStretchFactor(m_result, 1); + layout->setStretchFactor(m_history, 1); + + QHBox *editBox = new QHBox(box); + layout->addWidget(editBox); + editBox->setSpacing(6); + + m_edit = new Editor(editBox); + m_edit->setFocus(); + editBox->setStretchFactor(m_edit, 1); + + KPushButton *evalButton = new KPushButton(i18n("&Evaluate"), editBox); + + connect(evalButton, SIGNAL(clicked()), SLOT(slotEvaluate())); + + connect(m_edit, SIGNAL(returnPressed()), SLOT(slotReturnPressed())); + connect(m_edit, SIGNAL(textChanged()), SLOT(slotTextChanged())); + + m_listSplitter = new QSplitter(Vertical, m_mainSplitter); + m_fnList = new FunctionListView(m_listSplitter); + m_fnList->addColumn("Functions"); + m_fnList->addColumn("Value"); + + m_varList = new VariableListView(m_listSplitter); + m_varList->addColumn("Variables"); + m_varList->addColumn("Value"); + + connect(FunctionManager::instance(), SIGNAL(signalFunctionAdded(const QString &)), + this, SLOT(slotNewFunction(const QString &))); + connect(FunctionManager::instance(), SIGNAL(signalFunctionRemoved(const QString &)), + this, SLOT(slotRemoveFunction(const QString &))); + + connect(ValueManager::instance(), SIGNAL(signalValueAdded(const QString &, Abakus::number_t)), + this, SLOT(slotNewValue(const QString &, Abakus::number_t))); + connect(ValueManager::instance(), SIGNAL(signalValueChanged(const QString &, Abakus::number_t)), + this, SLOT(slotChangeValue(const QString &, Abakus::number_t))); + connect(ValueManager::instance(), SIGNAL(signalValueRemoved(const QString &)), + this, SLOT(slotRemoveValue(const QString &))); + + setupLayout(); + + setCentralWidget(m_mainSplitter); + +#if KDE_IS_VERSION(3,4,89) + setupGUI(QSize(450, 400), Keys | StatusBar | Save | Create); +#else + setupGUI(Keys | StatusBar | Save | Create); +#endif + + m_dcopInterface = new AbakusIface(); +} + +bool MainWindow::inRPNMode() const +{ + return action("toggleExpressionMode")->isChecked(); +} + +bool MainWindow::eventFilter(QObject *o, QEvent *e) +{ + return KMainWindow::eventFilter(o, e); +} + +bool MainWindow::queryExit() +{ + saveConfig(); + return KMainWindow::queryExit(); +} + +void MainWindow::contextMenuEvent(QContextMenuEvent *e) +{ + static KPopupMenu *popup = 0; + + if(!popup) { + popup = new KPopupMenu(this); + action("options_show_menubar")->plug(popup); + } + + if(!action("options_show_menubar")->isChecked()) + popup->popup(e->globalPos()); +} + +void MainWindow::polish() +{ + KMainWindow::polish(); + loadConfig(); +} + +void MainWindow::slotReturnPressed() +{ + QString text = m_edit->text(); + + text.replace("\n", ""); + + m_edit->appendHistory(text); + + // Item to insert after + ResultListViewText *after = m_result->lastItem(); + + // Expand $foo references. + QString str = interpolateExpression(text, after); + + QString resultVal; + ResultListViewText *item; + + if(str.isNull()) + return; // Error already has been posted + + m_insert = false; // Assume we failed + + if(inRPNMode()) { + // We're in RPN mode. + Abakus::number_t result = RPNParser::rpnParseString(str); + + if(!RPNParser::wasError()) { + resultVal = result.toString(); + ValueManager::instance()->setValue("ans", result); + m_insert = true; + } + else { + m_insert = false; + resultVal = i18n("Error: %1").arg(RPNParser::errorString()); + } + + // Skip creating list view items if in compact mode. + if(!m_history->isShown()) { + m_edit->setText(resultVal); + QTimer::singleShot(0, m_edit, SLOT(selectAll())); + + return; + } + + item = new ResultListViewText(m_result, str, resultVal, after, false); + } + else { + + // Check to see if it's just a function name, if so, add (ans). + if(FunctionManager::instance()->isFunction(str)) + str += " ans"; + + // Add right parentheses as needed to balance out the expression. + int parenLevel = getParenthesesLevel(str); + for(int i = 0; i < parenLevel; ++i) + str += ')'; + + Abakus::number_t result = parseString(str.latin1()); + + bool compact = !m_history->isShown(); + + switch(Result::lastResult()->type()) { + case Result::Value: + resultVal = result.toString(); + + ValueManager::instance()->setValue("ans", result); + if(!compact) + item = new ResultListViewText(m_result, str, result, after, false); + + m_insert = true; + break; + + case Result::Null: // OK, no result to speak of + resultVal = "OK"; + if(!compact) + item = new ResultListViewText(m_result, str, resultVal, after, true); + break; + + default: + resultVal = Result::lastResult()->message(); + if(!compact) + item = new ResultListViewText(m_result, str, resultVal, after, true); + } + + // Skip creating list view items if in compact mode. + if(compact) { + m_edit->setText(resultVal); + QTimer::singleShot(0, m_edit, SLOT(selectAll())); + + return; + } + } + + m_edit->setText(text); + + m_result->setCurrentItem(item); + m_result->ensureItemVisible(item); + + QTimer::singleShot(0, m_edit, SLOT(selectAll())); +} + +void MainWindow::slotTextChanged() +{ + QString str = m_edit->text(); + + if(str.length() == 1 && m_insert) { + m_insert = false; + + // Don't do anything if in RPN Mode. + if(inRPNMode()) + return; + + if(str.find(QRegExp("^[-+*/^]")) != -1) { + m_edit->setText("ans " + str + " "); + m_edit->moveCursor(QTextEdit::MoveEnd, false); + } + } +} + +void MainWindow::slotEvaluate() +{ + slotReturnPressed(); +} + +void MainWindow::slotUpdateSize() +{ + if(m_newSize != QSize(0, 0)) + resize(m_newSize); + else + resize(width(), minimumSize().height()); +} + +void MainWindow::slotDegrees() +{ + setTrigMode(Abakus::Degrees); + m_degrees->setChecked(true); + if(action("setDegreesMode")) + action("setDegreesMode")->setChecked(true); +} + +void MainWindow::slotRadians() +{ + setTrigMode(Abakus::Radians); + m_radians->setChecked(true); + if(action("setRadiansMode")) + action("setRadiansMode")->setChecked(true); +} + +int MainWindow::getParenthesesLevel(const QString &str) +{ + int level = 0; + + for(unsigned i = 0; i < str.length(); ++i) + if(str[i] == '(') + ++level; + else if(str[i] == ')') + --level; + + return level; +} + +void MainWindow::loadConfig() +{ + { + KConfigGroup config(KGlobal::config(), "Settings"); + + QString mode = config.readEntry("Trigonometric mode", "Degrees"); + if(mode == "Degrees") { + setTrigMode(Abakus::Degrees); + m_degrees->setChecked(true); + } + else { + setTrigMode(Abakus::Radians); + m_radians->setChecked(true); + } + + bool useRPN = config.readBoolEntry("Use RPN Mode", false); + action("toggleExpressionMode")->setChecked(useRPN); + + int precision = config.readNumEntry("Decimal Precision", -1); + if(precision < -1 || precision > 75) + precision = -1; + + Abakus::m_prec = precision; + selectCorrectPrecisionAction(); + } + + { + KConfigGroup config(KGlobal::config(), "Variables"); + + QStringList list = config.readListEntry("Saved Variables"); + for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { + QStringList values = QStringList::split('=', *it); + if(values.count() != 2) { + kdWarning() << "Your configuration file has somehow been corrupted!\n"; + continue; + } + + ValueManager::instance()->setValue(values[0], Abakus::number_t(values[1].latin1())); + } + } + + { + KConfigGroup config(KGlobal::config(), "GUI"); + + bool showHistory = config.readBoolEntry("ShowHistory", true); + action("toggleHistoryList")->setChecked(showHistory); + m_history->setShown(showHistory); + + bool showFunctions = config.readBoolEntry("ShowFunctions", true); + action("toggleFunctionList")->setChecked(showFunctions); + m_fnList->setShown(showFunctions); + + bool showVariables = config.readBoolEntry("ShowVariables", true); + action("toggleVariableList")->setChecked(showVariables); + m_varList->setShown(showVariables); + + bool compactMode = config.readBoolEntry("InCompactMode", false); + compactMode = compactMode || !showHistory; + action("toggleCompactMode")->setChecked(compactMode); + + if(compactMode) + QTimer::singleShot(0, this, SLOT(slotToggleCompactMode())); + } + + { + KConfigGroup config(KGlobal::config(), "Functions"); + + QStringList fnList = config.readListEntry("FunctionList"); + for(QStringList::ConstIterator it = fnList.begin(); it != fnList.end(); ++it) + parseString(*it); // Run the function definitions through the parser + } + + populateListViews(); +} + +void MainWindow::saveConfig() +{ + { + KConfigGroup config(KGlobal::config(), "Settings"); + + config.writeEntry("Trigonometric mode", + trigMode() == Abakus::Degrees + ? "Degrees" + : "Radians"); + + config.writeEntry("Use RPN Mode", inRPNMode()); + config.writeEntry("Decimal Precision", Abakus::m_prec); + } + + { + KConfigGroup config(KGlobal::config(), "Variables"); + + QStringList list; + QStringList values = ValueManager::instance()->valueNames(); + QStringList::ConstIterator it = values.begin(); + + // Set precision to max for most accuracy + Abakus::m_prec = 75; + + for(; it != values.end(); ++it) { + if(ValueManager::instance()->isValueReadOnly(*it)) + continue; + + list += QString("%1=%2") + .arg(*it) + .arg(ValueManager::instance()->value(*it).toString()); + } + + config.writeEntry("Saved Variables", list); + } + + { + KConfigGroup config(KGlobal::config(), "GUI"); + bool inCompactMode = action("toggleCompactMode")->isChecked(); + + config.writeEntry("InCompactMode", inCompactMode); + + if(!inCompactMode) { + config.writeEntry("ShowHistory", m_history->isShown()); + config.writeEntry("ShowFunctions", m_fnList->isShown()); + config.writeEntry("ShowVariables", m_varList->isShown()); + } + else { + config.writeEntry("ShowHistory", m_wasHistoryShown); + config.writeEntry("ShowFunctions", m_wasFnShown); + config.writeEntry("ShowVariables", m_wasVarShown); + } + } + + { + KConfigGroup config(KGlobal::config(), "Functions"); + + FunctionManager *manager = FunctionManager::instance(); + + QStringList userFunctions = manager->functionList(FunctionManager::UserDefined); + QStringList saveList; + + for(QStringList::ConstIterator it = userFunctions.begin(); it != userFunctions.end(); ++it) { + UnaryFunction *fn = dynamic_cast(manager->function(*it)->userFn->fn); + QString var = manager->function(*it)->userFn->varName; + QString expr = fn->operand()->infixString(); + + saveList += QString("set %1(%2) = %3").arg(*it).arg(var).arg(expr); + } + + config.writeEntry("FunctionList", saveList); + } +} + +void MainWindow::setupLayout() +{ + KActionCollection *ac = actionCollection(); + + KStdAction::quit(kapp, SLOT(quit()), ac); + KStdAction::showMenubar(this, SLOT(slotToggleMenuBar()), ac); + + KToggleAction *ta = new KToggleAction(i18n("&Degrees"), SHIFT + ALT + Key_D, this, SLOT(slotDegrees()), ac, "setDegreesMode"); + ta->setExclusiveGroup("TrigMode"); + ta->setChecked(trigMode() == Abakus::Degrees); + + ta = new KToggleAction(i18n("&Radians"), SHIFT + ALT + Key_R, this, SLOT(slotRadians()), ac, "setRadiansMode"); + ta->setExclusiveGroup("TrigMode"); + ta->setChecked(trigMode() == Abakus::Radians); + + ta = new KToggleAction(i18n("Show &History List"), SHIFT + ALT + Key_H, this, SLOT(slotToggleHistoryList()), ac, "toggleHistoryList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Show &Variables"), SHIFT + ALT + Key_V, this, SLOT(slotToggleVariableList()), ac, "toggleVariableList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Show &Functions"), SHIFT + ALT + Key_F, this, SLOT(slotToggleFunctionList()), ac, "toggleFunctionList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Activate &Compact Mode"), SHIFT + ALT + Key_C, this, SLOT(slotToggleCompactMode()), ac, "toggleCompactMode"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("Use R&PN Mode"), SHIFT + ALT + Key_P, this, SLOT(slotToggleExpressionMode()), ac, "toggleExpressionMode"); + ta->setChecked(false); + + // Precision actions. + ta = new KToggleAction(i18n("&Automatic Precision"), 0, this, SLOT(slotPrecisionAuto()), ac, "precisionAuto"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("&3 Decimal Digits"), 0, this, SLOT(slotPrecision3()), ac, "precision3"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&8 Decimal Digits"), 0, this, SLOT(slotPrecision8()), ac, "precision8"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&15 Decimal Digits"), 0, this, SLOT(slotPrecision15()), ac, "precision15"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&50 Decimal Digits"), 0, this, SLOT(slotPrecision50()), ac, "precision50"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("C&ustom Precision..."), 0, this, SLOT(slotPrecisionCustom()), ac, "precisionCustom"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + new KAction(i18n("Clear &History"), "editclear", SHIFT + ALT + Key_L, m_result, SLOT(clear()), ac, "clearHistory"); + + new KAction(i18n("Select Editor"), "goto", Key_F6, m_edit, SLOT(setFocus()), ac, "select_edit"); +} + +void MainWindow::populateListViews() +{ + QStringList values = ValueManager::instance()->valueNames(); + + Abakus::number_t value = ValueManager::instance()->value("pi"); + new ValueListViewItem(m_varList, "pi", value); + + value = ValueManager::instance()->value("e"); + new ValueListViewItem(m_varList, "e", value); +} + +KAction *MainWindow::action(const char *key) const +{ + return actionCollection()->action(key); +} + +void MainWindow::slotEntrySelected(const QString &text) +{ + m_edit->setText(text); + m_edit->moveCursor(QTextEdit::MoveEnd, false); +} + +void MainWindow::slotResultSelected(const QString &text) +{ + m_edit->insert(text); +} + +void MainWindow::slotToggleMenuBar() +{ + menuBar()->setShown(!menuBar()->isShown()); +} + +void MainWindow::slotToggleFunctionList() +{ + bool show = action("toggleFunctionList")->isChecked(); + m_fnList->setShown(show); + + if(!m_history->isShown()) { + m_history->setShown(true); + action("toggleHistoryList")->setChecked(true); + slotToggleHistoryList(); + } + + action("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotToggleVariableList() +{ + bool show = action("toggleVariableList")->isChecked(); + m_varList->setShown(show); + + if(!m_history->isShown()) { + m_history->setShown(true); + action("toggleHistoryList")->setChecked(true); + slotToggleHistoryList(); + } + + action("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotToggleHistoryList() +{ + bool show = action("toggleHistoryList")->isChecked(); + m_history->setShown(show); + + action("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotNewFunction(const QString &name) +{ + UserFunction *userFn = FunctionManager::instance()->function(name)->userFn; + UnaryFunction *fn = dynamic_cast(userFn->fn); + QString fnName = QString("%1(%2)").arg(name, userFn->varName); + QString expr = fn->operand()->infixString(); + + new KListViewItem(m_fnList, fnName, expr); +} + +void MainWindow::slotRemoveFunction(const QString &name) +{ + UserFunction *userFn = FunctionManager::instance()->function(name)->userFn; + QString fnName = QString("%1(%2)").arg(name, userFn->varName); + + QListViewItem *item = 0; + while((item = m_fnList->findItem(fnName, 0)) != 0) + delete item; +} + +void MainWindow::slotNewValue(const QString &name, Abakus::number_t value) +{ + new ValueListViewItem(m_varList, name, value); +} + +void MainWindow::slotChangeValue(const QString &name, Abakus::number_t value) +{ + ValueListViewItem *item = static_cast(m_varList->findItem(name, 0)); + + if(item) + item->valueChanged(value); +} + +void MainWindow::slotRemoveValue(const QString &name) +{ + delete m_varList->findItem(name, 0); +} + +void MainWindow::slotToggleCompactMode() +{ + if(action("toggleCompactMode")->isChecked()) { + m_wasFnShown = m_fnList->isShown(); + m_wasVarShown = m_varList->isShown(); + m_wasHistoryShown = m_history->isShown(); + + m_fnList->setShown(false); + m_varList->setShown(false); + m_history->setShown(false); + + action("toggleFunctionList")->setChecked(false); + action("toggleVariableList")->setChecked(false); + action("toggleHistoryList")->setChecked(false); + + m_oldSize = size(); + m_newSize = QSize(0, 0); + QTimer::singleShot(0, this, SLOT(slotUpdateSize())); + } + else { + m_fnList->setShown(m_wasFnShown); + m_varList->setShown(m_wasVarShown); + m_history->setShown(m_wasHistoryShown); + + action("toggleFunctionList")->setChecked(m_wasFnShown); + action("toggleVariableList")->setChecked(m_wasVarShown); + action("toggleHistoryList")->setChecked(m_wasHistoryShown); + + m_newSize = m_oldSize; + QTimer::singleShot(0, this, SLOT(slotUpdateSize())); + } +} + +void MainWindow::slotToggleExpressionMode() +{ +} + +QString MainWindow::interpolateExpression(const QString &text, ResultListViewText *after) +{ + QString str(text); + QRegExp stackRE("\\$\\d+"); + int pos; + + while((pos = stackRE.search(str)) != -1) { + QString stackStr = stackRE.cap(); + Abakus::number_t value; + unsigned numPos = stackStr.mid(1).toUInt(); + + if(!m_result->getStackValue(numPos, value)) { + new ResultListViewText(m_result, text, i18n("Marker %1 isn't set").arg(stackStr), after, true); + return QString::null; + } + + str.replace(pos, stackStr.length(), value.toString()); + } + + return str; +} + +void MainWindow::slotPrecisionAuto() +{ + Abakus::m_prec = -1; + redrawResults(); +} + +void MainWindow::slotPrecision3() +{ + Abakus::m_prec = 3; + redrawResults(); +} + +void MainWindow::slotPrecision8() +{ + Abakus::m_prec = 8; + redrawResults(); +} + +void MainWindow::slotPrecision15() +{ + Abakus::m_prec = 15; + redrawResults(); +} + +void MainWindow::slotPrecision50() +{ + Abakus::m_prec = 50; + redrawResults(); +} + +void MainWindow::slotPrecisionCustom() +{ + bool ok = false; + int precision = KInputDialog::getInteger(i18n("Select number of decimal digits to display"), + i18n("Decimal precision:"), Abakus::m_prec, 0, 75, 1, &ok, this); + + if(ok) { + Abakus::m_prec = precision; + redrawResults(); + } + + selectCorrectPrecisionAction(); +} + +void MainWindow::redrawResults() +{ + QListViewItemIterator it(m_result); + + while(it.current()) { + static_cast(it.current())->precisionChanged(); + ++it; + } + + it = QListViewItemIterator (m_varList); + + while(it.current()) { + static_cast(it.current())->valueChanged(); + ++it; + } + + // Because of the way we implemented the menu, it is possible to deselect + // every possibility, so make sure we have at least one selected. + selectCorrectPrecisionAction(); +} + +// This function selects the correct precision action based on the value of +// Abakus::m_prec. Useful for loading settings, or for custom precision +// selection. +void MainWindow::selectCorrectPrecisionAction() +{ + switch(Abakus::m_prec) { + case 3: + action("precision3")->setChecked(true); + break; + + case 8: + action("precision8")->setChecked(true); + break; + + case 15: + action("precision15")->setChecked(true); + break; + + case 50: + action("precision50")->setChecked(true); + break; + + case -1: + action("precisionAuto")->setChecked(true); + break; + + default: + action("precisionCustom")->setChecked(true); + } +} + +#include "mainwindow.moc" + +// vim: set et ts=8 sw=4: diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..ee24cdd --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,135 @@ +#ifndef ABAKUS_MAINWINDOW_H +#define ABAKUS_MAINWINDOW_H +/* + * mainwindow.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include + +#include "numerictypes.h" + +class QPoint; +class QVBox; +class QCheckBox; +class QRadioButton; +class QBoxLayout; +class QListViewItem; +class QSplitter; +class QTimer; + +//class KComboBox; +class Editor; +class KPopupMenu; +class KAction; +class KListView; +class ResultListView; +class ResultListViewText; + +class AbakusIface; + +// Main window class, handles events and layout and stuff +class MainWindow : public KMainWindow +{ + Q_OBJECT + public: + MainWindow(); + + bool inRPNMode() const; + + protected: + virtual void contextMenuEvent(QContextMenuEvent *e); + virtual bool eventFilter(QObject *o, QEvent *e); + virtual bool queryExit(); + virtual void polish(); + + private slots: + void slotReturnPressed(); + void slotTextChanged(); + void slotEvaluate(); + + void slotPrecisionAuto(); + void slotPrecision3(); + void slotPrecision8(); + void slotPrecision15(); + void slotPrecision50(); + void slotPrecisionCustom(); + + void slotUpdateSize(); + + void slotDegrees(); + void slotRadians(); + + void slotEntrySelected(const QString &text); + void slotResultSelected(const QString &text); + + void slotToggleMenuBar(); + void slotToggleFunctionList(); + void slotToggleVariableList(); + void slotToggleHistoryList(); + void slotToggleCompactMode(); + void slotToggleExpressionMode(); + + void slotNewValue(const QString &name, Abakus::number_t value); + void slotChangeValue(const QString &name, Abakus::number_t value); + void slotRemoveValue(const QString &name); + + void slotNewFunction(const QString &name); + void slotRemoveFunction(const QString &name); + + private: + int getParenthesesLevel(const QString &str); + + void redrawResults(); + void selectCorrectPrecisionAction(); + + void loadConfig(); + void saveConfig(); + void setupLayout(); + void populateListViews(); + QString interpolateExpression(const QString &text, ResultListViewText *after); + + // Donated via JuK + KAction *action(const char *key) const; + + template T *action(const char *key) const + { + return dynamic_cast(action(key)); + } + + private: + QVBox *m_history; + QRadioButton *m_degrees; + QRadioButton *m_radians; + Editor *m_edit; + KPopupMenu *m_popup; + ResultListView *m_result; + QString m_lastError; + QBoxLayout *m_layout; + KListView *m_fnList, *m_varList; + QSplitter *m_mainSplitter, *m_listSplitter; + QSize m_newSize, m_oldSize; + + AbakusIface *m_dcopInterface; + + bool m_wasFnShown, m_wasVarShown, m_wasHistoryShown; + bool m_compactMode; + + bool m_insert; +}; + +#endif diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 0000000..bb49676 --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,419 @@ +/* + * node.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include + +#include + +#include "node.h" +#include "valuemanager.h" +#include "function.h" + +void Node::deleteNode(Node *node) +{ + if(dynamic_cast(node) != 0) + delete node; +} + +BaseFunction::BaseFunction(const char *name) : + m_name(name) +{ +} + +const Function *BaseFunction::function() const +{ + return FunctionManager::instance()->function(m_name); +} + +UnaryFunction::UnaryFunction(const char *name, Node *operand) : + BaseFunction(name), m_node(operand) +{ +} + +UnaryFunction::~UnaryFunction() +{ + deleteNode(m_node); + m_node = 0; +} + +void UnaryFunction::setOperand(Node *operand) +{ + m_node = operand; +} + +void UnaryFunction::applyMap(NodeFunctor &fn) const +{ + fn(operand()); + fn(this); +} + +QString UnaryFunction::infixString() const +{ + return QString("%1(%2)").arg(name(), operand()->infixString()); +} + +BuiltinFunction::BuiltinFunction(const char *name, Node *operand) : + UnaryFunction(name, operand) +{ +} + +Abakus::number_t BuiltinFunction::value() const +{ + if(function() && operand()) { + Abakus::number_t fnValue = operand()->value(); + return evaluateFunction(function(), fnValue); + } + + return Abakus::number_t(0); +} + +Abakus::number_t BuiltinFunction::derivative() const +{ + Abakus::number_t du = operand()->derivative(); + Abakus::number_t value = operand()->value(); + Abakus::number_t one(1), zero(0); + + if(du == zero) + return du; + + // In case these functions get added later, these derivatives may + // be useful: + // d/dx(asinh u) = (du/dx * 1 / sqrt(x^2 + 1)) + // d/dx(acosh u) = (du/dx * 1 / sqrt(x^2 - 1)) + // d/dx(atanh u) = (du/dx * 1 / (1 - x^2)) + + // This is very unfortunate duplication. + if(name() == "sin") + return value.cos() * du; + else if(name() == "cos") + return -value.sin() * du; + else if(name() == "tan") { + Abakus::number_t cosResult; + + cosResult = value.cos(); + cosResult = cosResult * cosResult; + return one / cosResult; + } + else if(name() == "asinh") { + value = value * value + one; + return du / value.sqrt(); + } + else if(name() == "acosh") { + value = value * value - one; + return du / value.sqrt(); + } + else if(name() == "atanh") { + value = one - value * value; + return du / value; + } + else if(name() == "sinh") { + return du * value.cosh(); + } + else if(name() == "cosh") { + return du * value.sinh(); // Yes the sign is correct. + } + else if(name() == "tanh") { + Abakus::number_t tanh = value.tanh(); + + return du * (one - tanh * tanh); + } + else if(name() == "atan") { + return one * du / (one + value * value); + } + else if(name() == "acos") { + // Same as asin but with inverted sign. + return -(one / (value * value - one).sqrt() * du); + } + else if(name() == "asin") { + return one / (value * value - one).sqrt() * du; + } + else if(name() == "ln") { + return du / value; + } + else if(name() == "exp") { + return du * value.exp(); + } + else if(name() == "log") { + return du / value / Abakus::number_t(10).ln(); + } + else if(name() == "sqrt") { + Abakus::number_t half("0.5"); + return half * value.pow(-half) * du; + } + else if(name() == "abs") { + return (value / value.abs()) * du; + } + + // Approximate it. + + Abakus::number_t epsilon("1e-15"); + Abakus::number_t fxh = evaluateFunction(function(), value + epsilon); + Abakus::number_t fx = evaluateFunction(function(), value); + return (fxh - fx) / epsilon; +} + +DerivativeFunction::~DerivativeFunction() +{ + deleteNode(m_operand); + m_operand = 0; +} + +Abakus::number_t DerivativeFunction::value() const +{ + ValueManager *vm = ValueManager::instance(); + Abakus::number_t result; + + if(vm->isValueSet("x")) { + Abakus::number_t oldValue = vm->value("x"); + + vm->setValue("x", m_where->value()); + result = m_operand->derivative(); + vm->setValue("x", oldValue); + } + else { + vm->setValue("x", m_where->value()); + result = m_operand->derivative(); + vm->removeValue("x"); + } + + return result; +} + +Abakus::number_t DerivativeFunction::derivative() const +{ + kdError() << k_funcinfo << endl; + kdError() << "This function is never supposed to be called!\n"; + + return m_operand->derivative(); +} + +void DerivativeFunction::applyMap(NodeFunctor &fn) const +{ + fn(m_operand); + fn(this); +} + +QString DerivativeFunction::infixString() const +{ + return QString("deriv(%1, %2)").arg(m_operand->infixString(), m_where->infixString()); +} + +UnaryOperator::UnaryOperator(Type type, Node *operand) + : m_type(type), m_node(operand) +{ +} + +UnaryOperator::~UnaryOperator() +{ + deleteNode(m_node); + m_node = 0; +} + +void UnaryOperator::applyMap(NodeFunctor &fn) const +{ + fn(operand()); + fn(this); +} + +QString UnaryOperator::infixString() const +{ + if(dynamic_cast(operand())) + return QString("-(%1)").arg(operand()->infixString()); + + return QString("-%1").arg(operand()->infixString()); +} + +Abakus::number_t UnaryOperator::derivative() const +{ + switch(type()) { + case Negation: + return -(operand()->derivative()); + + default: + kdError() << "Impossible case encountered for UnaryOperator!\n"; + return Abakus::number_t(0); + } +} + +Abakus::number_t UnaryOperator::value() const +{ + switch(type()) { + case Negation: + return -(operand()->value()); + + default: + kdError() << "Impossible case encountered for UnaryOperator!\n"; + return Abakus::number_t(0); + } +} + +BinaryOperator::BinaryOperator(Type type, Node *left, Node *right) : + m_type(type), m_left(left), m_right(right) +{ +} + +BinaryOperator::~BinaryOperator() +{ + deleteNode(m_left); + m_left = 0; + + deleteNode(m_right); + m_right = 0; +} + +void BinaryOperator::applyMap(NodeFunctor &fn) const +{ + fn(leftNode()); + fn(rightNode()); + fn(this); +} + +QString BinaryOperator::infixString() const +{ + QString op; + + switch(type()) { + case Addition: + op = "+"; + break; + + case Subtraction: + op = "-"; + break; + + case Multiplication: + op = "*"; + break; + + case Division: + op = "/"; + break; + + case Exponentiation: + op = "^"; + break; + + default: + op = "Error"; + } + + QString left = QString(isSimpleNode(leftNode()) ? "%1" : "(%1)").arg(leftNode()->infixString()); + QString right = QString(isSimpleNode(rightNode()) ? "%1" : "(%1)").arg(rightNode()->infixString()); + + return QString("%1 %2 %3").arg(left, op, right); +} + +Abakus::number_t BinaryOperator::derivative() const +{ + if(!leftNode() || !rightNode()) { + kdError() << "Can't evaluate binary operator!\n"; + return Abakus::number_t(0); + } + + Abakus::number_t f = leftNode()->value(); + Abakus::number_t fPrime = leftNode()->derivative(); + Abakus::number_t g = rightNode()->value(); + Abakus::number_t gPrime = rightNode()->derivative(); + + switch(type()) { + case Addition: + return fPrime + gPrime; + + case Subtraction: + return fPrime - gPrime; + + case Multiplication: + return f * gPrime + fPrime * g; + + case Division: + return (g * fPrime - f * gPrime) / (g * g); + + case Exponentiation: + return f.pow(g) * ((g / f) * fPrime + gPrime * f.ln()); + + default: + kdError() << "Impossible case encountered evaluating binary operator!\n"; + return Abakus::number_t(0); + } +} + +Abakus::number_t BinaryOperator::value() const +{ + if(!leftNode() || !rightNode()) { + kdError() << "Can't evaluate binary operator!\n"; + return Abakus::number_t(0); + } + + Abakus::number_t lValue = leftNode()->value(); + Abakus::number_t rValue = rightNode()->value(); + + switch(type()) { + case Addition: + return lValue + rValue; + + case Subtraction: + return lValue - rValue; + + case Multiplication: + return lValue * rValue; + + case Division: + return lValue / rValue; + + case Exponentiation: + return lValue.pow(rValue); + + default: + kdError() << "Impossible case encountered evaluating binary operator!\n"; + return Abakus::number_t(0); + } +} + +bool BinaryOperator::isSimpleNode(Node *node) const +{ + if(dynamic_cast(node) || + dynamic_cast(node) || + dynamic_cast(node) || + dynamic_cast(node)) + { + return true; + } + + return false; +} + +Identifier::Identifier(const char *name) : m_name(name) +{ +} + +Abakus::number_t Identifier::value() const +{ + return ValueManager::instance()->value(name()); +} + +void Identifier::applyMap(NodeFunctor &fn) const +{ + fn(this); +} + +QString NumericValue::infixString() const +{ + return value().toString(); +} + +// vim: set et ts=8 sw=4: diff --git a/src/node.h b/src/node.h new file mode 100644 index 0000000..e2331ab --- /dev/null +++ b/src/node.h @@ -0,0 +1,219 @@ +#ifndef ABAKUS_NODE_H +#define ABAKUS_NODE_H +/* + * node.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include + +#include "numerictypes.h" + +class Node; +class Function; + +template class SharedPtr; +typedef SharedPtr NodePtr; + +/** + * A class that operates on a Node. Called recursively on a node and all + * of its children. + */ +class NodeFunctor +{ + public: + virtual void operator()(const Node *node) = 0; + virtual ~NodeFunctor() { } +}; + +class Node +{ + public: + virtual ~Node() { } + virtual Abakus::number_t value() const = 0; + + // Deletes a node only if it isn't a function, since those are + // typically read-only. + virtual void deleteNode(Node *node); + + // Calls functor() on all subchildren and this. + virtual void applyMap(NodeFunctor &fn) const = 0; + + // Returns an infix representation of the expression. + virtual QString infixString() const = 0; + + // Returns the derivative of the node. + virtual Abakus::number_t derivative() const = 0; +}; + +class BaseFunction : public Node +{ + public: + BaseFunction(const char *name); + + virtual QString name() const { return m_name; } + virtual const Function *function() const; + + private: + QString m_name; + const Function *m_function; +}; + +class UnaryFunction : public BaseFunction +{ + public: + UnaryFunction(const char *name, Node *operand); + virtual ~UnaryFunction(); + + virtual Node *operand() const { return m_node; } + virtual void setOperand(Node *operand); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + private: + Node *m_node; +}; + +// Calculates the approximate derivative of its operand. +class DerivativeFunction : public Node +{ + public: + DerivativeFunction(Node *operand, Node *where) : + m_operand(operand), m_where(where) { } + ~DerivativeFunction(); + + virtual Abakus::number_t value() const; + + virtual void applyMap(NodeFunctor &fn) const; + + // Returns an infix representation of the expression. + virtual QString infixString() const; + + virtual Abakus::number_t derivative() const; + + private: + Node *m_operand; + Node *m_where; +}; + +class UserDefinedFunction : public UnaryFunction +{ + public: + UserDefinedFunction(const char *name, Node *operand) : UnaryFunction(name, operand) { }; + + virtual Abakus::number_t value() const { return operand()->value(); } + virtual Abakus::number_t derivative() const { return operand()->derivative(); } +}; + +class BuiltinFunction : public UnaryFunction +{ + public: + BuiltinFunction(const char *name, Node *operand); + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; +}; + +class UnaryOperator : public Node +{ + public: + typedef enum { Negation } Type; + + UnaryOperator(Type type, Node *operand); + virtual ~UnaryOperator(); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; + + virtual Type type() const { return m_type; } + virtual Node *operand() const { return m_node; } + + private: + Type m_type; + Node *m_node; +}; + +class BinaryOperator : public Node +{ + public: + typedef enum { Addition, Subtraction, Multiplication, Division, Exponentiation } Type; + + BinaryOperator(Type type, Node *left, Node *right); + virtual ~BinaryOperator(); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; + + virtual Type type() const { return m_type; } + virtual Node *leftNode() const { return m_left; } + virtual Node *rightNode() const { return m_right; } + + private: + bool isSimpleNode(Node *node) const; + + Type m_type; + Node *m_left, *m_right; +}; + +class Identifier : public Node +{ + public: + Identifier(const char *name); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const { return name(); } + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const + { + if(name() == "x") + return "1"; + else + return "0"; + } + + virtual QString name() const { return m_name; } + + private: + QString m_name; +}; + +class NumericValue : public Node +{ + public: + NumericValue(const Abakus::number_t value) : m_value(value) { } + + virtual void applyMap(NodeFunctor &fn) const { fn(this); } + virtual QString infixString() const; + + virtual Abakus::number_t value() const { return m_value; } + virtual Abakus::number_t derivative() const { return "0"; } + + private: + Abakus::number_t m_value; +}; + +#endif + +// vim: set et sw=4 ts=8: diff --git a/src/number.c b/src/number.c new file mode 100644 index 0000000..3ba4297 --- /dev/null +++ b/src/number.c @@ -0,0 +1,1809 @@ +/* number.c: Implements arbitrary precision numbers. */ +/* + Copyright (C) 1991, 1992, 1993, 1994, 1997, 2000 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License , or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to: + + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02110-1301 USA. + + + You may contact the author by: + e-mail: philnelson@acm.org + us-mail: Philip A. Nelson + Computer Science Department, 9062 + Western Washington University + Bellingham, WA 98226-9062 + +*************************************************************************/ + +#include "number.h" + +#include +#include +#include +#include +#include /* Prototypes needed for external utility routines. */ + +#define bc_rt_warn rt_warn +#define bc_rt_error rt_error +#define bc_out_of_memory out_of_memory + +_PROTOTYPE(void rt_warn, (char *mesg ,...)); +_PROTOTYPE(void rt_error, (char *mesg ,...)); +_PROTOTYPE(void out_of_memory, (void)); + + +void out_of_memory(void){ + return; +} + +void rt_warn(char *mesg ,...){ + return; +} + +void rt_error(char *mesg ,...){ + return; +} + +/* Storage used for special numbers. */ +bc_num _zero_; +bc_num _one_; +bc_num _two_; + +static bc_num _bc_Free_list = NULL; + +/* new_num allocates a number and sets fields to known values. */ + +bc_num +bc_new_num (length, scale) + int length, scale; +{ + bc_num temp; + + if (_bc_Free_list != NULL) { + temp = _bc_Free_list; + _bc_Free_list = temp->n_next; + } else { + temp = (bc_num) malloc (sizeof(bc_struct)); + if (temp == NULL) bc_out_of_memory (); + } + temp->n_sign = PLUS; + temp->n_len = length; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = (char *) malloc (length+scale+1); + if (temp->n_ptr == NULL) bc_out_of_memory(); + temp->n_value = temp->n_ptr; + memset (temp->n_ptr, 0, length+scale); + return temp; +} + +/* "Frees" a bc_num NUM. Actually decreases reference count and only + frees the storage if reference count is zero. */ + +void +bc_free_num (num) + bc_num *num; +{ + if (*num == NULL) return; + (*num)->n_refs--; + if ((*num)->n_refs == 0) { + if ((*num)->n_ptr) + free ((*num)->n_ptr); + (*num)->n_next = _bc_Free_list; + _bc_Free_list = *num; + } + *num = NULL; +} + + +/* Intitialize the number package! */ + +void +bc_init_numbers () +{ + _zero_ = bc_new_num (1,0); + _one_ = bc_new_num (1,0); + _one_->n_value[0] = 1; + _two_ = bc_new_num (1,0); + _two_->n_value[0] = 2; +} + + +/* Make a copy of a number! Just increments the reference count! */ + +bc_num +bc_copy_num (num) + bc_num num; +{ + num->n_refs++; + return num; +} + + +/* Initialize a number NUM by making it a copy of zero. */ + +void +bc_init_num (num) + bc_num *num; +{ + *num = bc_copy_num (_zero_); +} + +/* For many things, we may have leading zeros in a number NUM. + _bc_rm_leading_zeros just moves the data "value" pointer to the + correct place and adjusts the length. */ + +static void +_bc_rm_leading_zeros (num) + bc_num num; +{ + /* We can move n_value to point to the first non zero digit! */ + while (*num->n_value == 0 && num->n_len > 1) { + num->n_value++; + num->n_len--; + } +} + + +/* Compare two bc numbers. Return value is 0 if equal, -1 if N1 is less + than N2 and +1 if N1 is greater than N2. If USE_SIGN is false, just + compare the magnitudes. */ + +static int +_bc_do_compare (n1, n2, use_sign, ignore_last) + bc_num n1, n2; + int use_sign; + int ignore_last; +{ + char *n1ptr, *n2ptr; + int count; + + /* First, compare signs. */ + if (use_sign && n1->n_sign != n2->n_sign) + { + if (n1->n_sign == PLUS) + return (1); /* Positive N1 > Negative N2 */ + else + return (-1); /* Negative N1 < Positive N1 */ + } + + /* Now compare the magnitude. */ + if (n1->n_len != n2->n_len) + { + if (n1->n_len > n2->n_len) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + else + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + + /* If we get here, they have the same number of integer digits. + check the integer part and the equal length part of the fraction. */ + count = n1->n_len + MIN (n1->n_scale, n2->n_scale); + n1ptr = n1->n_value; + n2ptr = n2->n_value; + + while ((count > 0) && (*n1ptr == *n2ptr)) + { + n1ptr++; + n2ptr++; + count--; + } + if (ignore_last && count == 1 && n1->n_scale == n2->n_scale) + return (0); + if (count != 0) + { + if (*n1ptr > *n2ptr) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + else + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + + /* They are equal up to the last part of the equal part of the fraction. */ + if (n1->n_scale != n2->n_scale) + { + if (n1->n_scale > n2->n_scale) + { + for (count = n1->n_scale-n2->n_scale; count>0; count--) + if (*n1ptr++ != 0) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + } + else + { + for (count = n2->n_scale-n1->n_scale; count>0; count--) + if (*n2ptr++ != 0) + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + } + + /* They must be equal! */ + return (0); +} + + +/* This is the "user callable" routine to compare numbers N1 and N2. */ + +int +bc_compare (n1, n2) + bc_num n1, n2; +{ + return _bc_do_compare (n1, n2, TRUE, FALSE); +} + +/* In some places we need to check if the number is negative. */ + +char +bc_is_neg (num) + bc_num num; +{ + return num->n_sign == MINUS; +} + +/* In some places we need to check if the number NUM is zero. */ + +char +bc_is_zero (num) + bc_num num; +{ + int count; + char *nptr; + + /* Quick check. */ + if (num == _zero_) return TRUE; + + /* Initialize */ + count = num->n_len + num->n_scale; + nptr = num->n_value; + + /* The check */ + while ((count > 0) && (*nptr++ == 0)) count--; + + if (count != 0) + return FALSE; + else + return TRUE; +} + +/* In some places we need to check if the number NUM is almost zero. + Specifically, all but the last digit is 0 and the last digit is 1. + Last digit is defined by scale. */ + +char +bc_is_near_zero (num, scale) + bc_num num; + int scale; +{ + int count; + char *nptr; + + /* Error checking */ + if (scale > num->n_scale) + scale = num->n_scale; + + /* Initialize */ + count = num->n_len + scale; + nptr = num->n_value; + + /* The check */ + while ((count > 0) && (*nptr++ == 0)) count--; + + if (count != 0 && (count != 1 || *--nptr != 1)) + return FALSE; + else + return TRUE; +} + + +/* Perform addition: N1 is added to N2 and the value is + returned. The signs of N1 and N2 are ignored. + SCALE_MIN is to set the minimum scale of the result. */ + +static bc_num +_bc_do_add (n1, n2, scale_min) + bc_num n1, n2; + int scale_min; +{ + bc_num sum; + int sum_scale, sum_digits; + char *n1ptr, *n2ptr, *sumptr; + int carry, n1bytes, n2bytes; + int count; + + /* Prepare sum. */ + sum_scale = MAX (n1->n_scale, n2->n_scale); + sum_digits = MAX (n1->n_len, n2->n_len) + 1; + sum = bc_new_num (sum_digits, MAX(sum_scale, scale_min)); + + /* Zero extra digits made by scale_min. */ + if (scale_min > sum_scale) + { + sumptr = (char *) (sum->n_value + sum_scale + sum_digits); + for (count = scale_min - sum_scale; count > 0; count--) + *sumptr++ = 0; + } + + /* Start with the fraction part. Initialize the pointers. */ + n1bytes = n1->n_scale; + n2bytes = n2->n_scale; + n1ptr = (char *) (n1->n_value + n1->n_len + n1bytes - 1); + n2ptr = (char *) (n2->n_value + n2->n_len + n2bytes - 1); + sumptr = (char *) (sum->n_value + sum_scale + sum_digits - 1); + + /* Add the fraction part. First copy the longer fraction.*/ + if (n1bytes != n2bytes) + { + if (n1bytes > n2bytes) + while (n1bytes>n2bytes) + { *sumptr-- = *n1ptr--; n1bytes--;} + else + while (n2bytes>n1bytes) + { *sumptr-- = *n2ptr--; n2bytes--;} + } + + /* Now add the remaining fraction part and equal size integer parts. */ + n1bytes += n1->n_len; + n2bytes += n2->n_len; + carry = 0; + while ((n1bytes > 0) && (n2bytes > 0)) + { + *sumptr = *n1ptr-- + *n2ptr-- + carry; + if (*sumptr > (BASE-1)) + { + carry = 1; + *sumptr -= BASE; + } + else + carry = 0; + sumptr--; + n1bytes--; + n2bytes--; + } + + /* Now add carry the longer integer part. */ + if (n1bytes == 0) + { n1bytes = n2bytes; n1ptr = n2ptr; } + while (n1bytes-- > 0) + { + *sumptr = *n1ptr-- + carry; + if (*sumptr > (BASE-1)) + { + carry = 1; + *sumptr -= BASE; + } + else + carry = 0; + sumptr--; + } + + /* Set final carry. */ + if (carry == 1) + *sumptr += 1; + + /* Adjust sum and return. */ + _bc_rm_leading_zeros (sum); + return sum; +} + + +/* Perform subtraction: N2 is subtracted from N1 and the value is + returned. The signs of N1 and N2 are ignored. Also, N1 is + assumed to be larger than N2. SCALE_MIN is the minimum scale + of the result. */ + +static bc_num +_bc_do_sub (n1, n2, scale_min) + bc_num n1, n2; + int scale_min; +{ + bc_num diff; + int diff_scale, diff_len; + int min_scale, min_len; + char *n1ptr, *n2ptr, *diffptr; + int borrow, count, val; + + /* Allocate temporary storage. */ + diff_len = MAX (n1->n_len, n2->n_len); + diff_scale = MAX (n1->n_scale, n2->n_scale); + min_len = MIN (n1->n_len, n2->n_len); + min_scale = MIN (n1->n_scale, n2->n_scale); + diff = bc_new_num (diff_len, MAX(diff_scale, scale_min)); + + /* Zero extra digits made by scale_min. */ + if (scale_min > diff_scale) + { + diffptr = (char *) (diff->n_value + diff_len + diff_scale); + for (count = scale_min - diff_scale; count > 0; count--) + *diffptr++ = 0; + } + + /* Initialize the subtract. */ + n1ptr = (char *) (n1->n_value + n1->n_len + n1->n_scale -1); + n2ptr = (char *) (n2->n_value + n2->n_len + n2->n_scale -1); + diffptr = (char *) (diff->n_value + diff_len + diff_scale -1); + + /* Subtract the numbers. */ + borrow = 0; + + /* Take care of the longer scaled number. */ + if (n1->n_scale != min_scale) + { + /* n1 has the longer scale */ + for (count = n1->n_scale - min_scale; count > 0; count--) + *diffptr-- = *n1ptr--; + } + else + { + /* n2 has the longer scale */ + for (count = n2->n_scale - min_scale; count > 0; count--) + { + val = - *n2ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + } + + /* Now do the equal length scale and integer parts. */ + + for (count = 0; count < min_len + min_scale; count++) + { + val = *n1ptr-- - *n2ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + + /* If n1 has more digits then n2, we now do that subtract. */ + if (diff_len != min_len) + { + for (count = diff_len - min_len; count > 0; count--) + { + val = *n1ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + } + + /* Clean up and return. */ + _bc_rm_leading_zeros (diff); + return diff; +} + + +/* Here is the full subtract routine that takes care of negative numbers. + N2 is subtracted from N1 and the result placed in RESULT. SCALE_MIN + is the minimum scale for the result. */ + +void +bc_sub (n1, n2, result, scale_min) + bc_num n1, n2, *result; + int scale_min; +{ + bc_num diff = NULL; + int cmp_res; + int res_scale; + + if (n1->n_sign != n2->n_sign) + { + diff = _bc_do_add (n1, n2, scale_min); + diff->n_sign = n1->n_sign; + } + else + { + /* subtraction must be done. */ + /* Compare magnitudes. */ + cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); + switch (cmp_res) + { + case -1: + /* n1 is less than n2, subtract n1 from n2. */ + diff = _bc_do_sub (n2, n1, scale_min); + diff->n_sign = (n2->n_sign == PLUS ? MINUS : PLUS); + break; + case 0: + /* They are equal! return zero! */ + res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); + diff = bc_new_num (1, res_scale); + memset (diff->n_value, 0, res_scale+1); + break; + case 1: + /* n2 is less than n1, subtract n2 from n1. */ + diff = _bc_do_sub (n1, n2, scale_min); + diff->n_sign = n1->n_sign; + break; + } + } + + /* Clean up and return. */ + bc_free_num (result); + *result = diff; +} + + +/* Here is the full add routine that takes care of negative numbers. + N1 is added to N2 and the result placed into RESULT. SCALE_MIN + is the minimum scale for the result. */ + +void +bc_add (n1, n2, result, scale_min) + bc_num n1, n2, *result; + int scale_min; +{ + bc_num sum = NULL; + int cmp_res; + int res_scale; + + if (n1->n_sign == n2->n_sign) + { + sum = _bc_do_add (n1, n2, scale_min); + sum->n_sign = n1->n_sign; + } + else + { + /* subtraction must be done. */ + cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); /* Compare magnitudes. */ + switch (cmp_res) + { + case -1: + /* n1 is less than n2, subtract n1 from n2. */ + sum = _bc_do_sub (n2, n1, scale_min); + sum->n_sign = n2->n_sign; + break; + case 0: + /* They are equal! return zero with the correct scale! */ + res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); + sum = bc_new_num (1, res_scale); + memset (sum->n_value, 0, res_scale+1); + break; + case 1: + /* n2 is less than n1, subtract n2 from n1. */ + sum = _bc_do_sub (n1, n2, scale_min); + sum->n_sign = n1->n_sign; + } + } + + /* Clean up and return. */ + bc_free_num (result); + *result = sum; +} + +/* Recursive vs non-recursive multiply crossover ranges. */ +#if defined(MULDIGITS) +#include "muldigits.h" +#else +#define MUL_BASE_DIGITS 80 +#endif + +int mul_base_digits = MUL_BASE_DIGITS; +#define MUL_SMALL_DIGITS mul_base_digits/4 + +/* Multiply utility routines */ + +static bc_num +new_sub_num (length, scale, value) + int length, scale; + char *value; +{ + bc_num temp; + + if (_bc_Free_list != NULL) { + temp = _bc_Free_list; + _bc_Free_list = temp->n_next; + } else { + temp = (bc_num) malloc (sizeof(bc_struct)); + if (temp == NULL) bc_out_of_memory (); + } + temp->n_sign = PLUS; + temp->n_len = length; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = NULL; + temp->n_value = value; + return temp; +} + +static void +_bc_simp_mul (bc_num n1, int n1len, bc_num n2, int n2len, bc_num *prod, + int full_scale) +{ + char *n1ptr, *n2ptr, *pvptr; + char *n1end, *n2end; /* To the end of n1 and n2. */ + int indx, sum, prodlen; + + prodlen = n1len+n2len+1; + + *prod = bc_new_num (prodlen, 0); + + n1end = (char *) (n1->n_value + n1len - 1); + n2end = (char *) (n2->n_value + n2len - 1); + pvptr = (char *) ((*prod)->n_value + prodlen - 1); + sum = 0; + + /* Here is the loop... */ + for (indx = 0; indx < prodlen-1; indx++) + { + n1ptr = (char *) (n1end - MAX(0, indx-n2len+1)); + n2ptr = (char *) (n2end - MIN(indx, n2len-1)); + while ((n1ptr >= n1->n_value) && (n2ptr <= n2end)) + sum += *n1ptr-- * *n2ptr++; + *pvptr-- = sum % BASE; + sum = sum / BASE; + } + *pvptr = sum; +} + + +/* A special adder/subtractor for the recursive divide and conquer + multiply algorithm. Note: if sub is called, accum must + be larger that what is being subtracted. Also, accum and val + must have n_scale = 0. (e.g. they must look like integers. *) */ +static void +_bc_shift_addsub (bc_num accum, bc_num val, int shift, int sub) +{ + signed char *accp, *valp; + int count, carry; + + count = val->n_len; + if (val->n_value[0] == 0) + count--; + assert (accum->n_len+accum->n_scale >= shift+count); + + /* Set up pointers and others */ + accp = (signed char *)(accum->n_value + + accum->n_len + accum->n_scale - shift - 1); + valp = (signed char *)(val->n_value + val->n_len - 1); + carry = 0; + + if (sub) { + /* Subtraction, carry is really borrow. */ + while (count--) { + *accp -= *valp-- + carry; + if (*accp < 0) { + carry = 1; + *accp-- += BASE; + } else { + carry = 0; + accp--; + } + } + while (carry) { + *accp -= carry; + if (*accp < 0) + *accp-- += BASE; + else + carry = 0; + } + } else { + /* Addition */ + while (count--) { + *accp += *valp-- + carry; + if (*accp > (BASE-1)) { + carry = 1; + *accp-- -= BASE; + } else { + carry = 0; + accp--; + } + } + while (carry) { + *accp += carry; + if (*accp > (BASE-1)) + *accp-- -= BASE; + else + carry = 0; + } + } +} + +/* Recursive divide and conquer multiply algorithm. + Based on + Let u = u0 + u1*(b^n) + Let v = v0 + v1*(b^n) + Then uv = (B^2n+B^n)*u1*v1 + B^n*(u1-u0)*(v0-v1) + (B^n+1)*u0*v0 + + B is the base of storage, number of digits in u1,u0 close to equal. +*/ +static void +_bc_rec_mul (bc_num u, int ulen, bc_num v, int vlen, bc_num *prod, + int full_scale) +{ + bc_num u0, u1, v0, v1; + int u0len, v0len; + bc_num m1, m2, m3, d1, d2; + int n, prodlen, m1zero; + int d1len, d2len; + + /* Base case? */ + if ((ulen+vlen) < mul_base_digits + || ulen < MUL_SMALL_DIGITS + || vlen < MUL_SMALL_DIGITS ) { + _bc_simp_mul (u, ulen, v, vlen, prod, full_scale); + return; + } + + /* Calculate n -- the u and v split point in digits. */ + n = (MAX(ulen, vlen)+1) / 2; + + /* Split u and v. */ + if (ulen < n) { + u1 = bc_copy_num (_zero_); + u0 = new_sub_num (ulen,0, u->n_value); + } else { + u1 = new_sub_num (ulen-n, 0, u->n_value); + u0 = new_sub_num (n, 0, u->n_value+ulen-n); + } + if (vlen < n) { + v1 = bc_copy_num (_zero_); + v0 = new_sub_num (vlen,0, v->n_value); + } else { + v1 = new_sub_num (vlen-n, 0, v->n_value); + v0 = new_sub_num (n, 0, v->n_value+vlen-n); + } + _bc_rm_leading_zeros (u1); + _bc_rm_leading_zeros (u0); + u0len = u0->n_len; + _bc_rm_leading_zeros (v1); + _bc_rm_leading_zeros (v0); + v0len = v0->n_len; + + m1zero = bc_is_zero(u1) || bc_is_zero(v1); + + /* Calculate sub results ... */ + + bc_init_num(&d1); + bc_init_num(&d2); + bc_sub (u1, u0, &d1, 0); + d1len = d1->n_len; + bc_sub (v0, v1, &d2, 0); + d2len = d2->n_len; + + + /* Do recursive multiplies and shifted adds. */ + if (m1zero) + m1 = bc_copy_num (_zero_); + else + _bc_rec_mul (u1, u1->n_len, v1, v1->n_len, &m1, 0); + + if (bc_is_zero(d1) || bc_is_zero(d2)) + m2 = bc_copy_num (_zero_); + else + _bc_rec_mul (d1, d1len, d2, d2len, &m2, 0); + + if (bc_is_zero(u0) || bc_is_zero(v0)) + m3 = bc_copy_num (_zero_); + else + _bc_rec_mul (u0, u0->n_len, v0, v0->n_len, &m3, 0); + + /* Initialize product */ + prodlen = ulen+vlen+1; + *prod = bc_new_num(prodlen, 0); + + if (!m1zero) { + _bc_shift_addsub (*prod, m1, 2*n, 0); + _bc_shift_addsub (*prod, m1, n, 0); + } + _bc_shift_addsub (*prod, m3, n, 0); + _bc_shift_addsub (*prod, m3, 0, 0); + _bc_shift_addsub (*prod, m2, n, d1->n_sign != d2->n_sign); + + /* Now clean up! */ + bc_free_num (&u1); + bc_free_num (&u0); + bc_free_num (&v1); + bc_free_num (&m1); + bc_free_num (&v0); + bc_free_num (&m2); + bc_free_num (&m3); + bc_free_num (&d1); + bc_free_num (&d2); +} + +/* The multiply routine. N2 times N1 is put int PROD with the scale of + the result being MIN(N2 scale+N1 scale, MAX (SCALE, N2 scale, N1 scale)). + */ + +void +bc_multiply (n1, n2, prod, scale) + bc_num n1, n2, *prod; + int scale; +{ + bc_num pval; + int len1, len2; + int full_scale, prod_scale; + + /* Initialize things. */ + len1 = n1->n_len + n1->n_scale; + len2 = n2->n_len + n2->n_scale; + full_scale = n1->n_scale + n2->n_scale; + prod_scale = MIN(full_scale,MAX(scale,MAX(n1->n_scale,n2->n_scale))); + + /* Do the multiply */ + _bc_rec_mul (n1, len1, n2, len2, &pval, full_scale); + + /* Assign to prod and clean up the number. */ + pval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); + pval->n_value = pval->n_ptr; + pval->n_len = len2 + len1 + 1 - full_scale; + pval->n_scale = prod_scale; + _bc_rm_leading_zeros (pval); + if (bc_is_zero (pval)) + pval->n_sign = PLUS; + bc_free_num (prod); + *prod = pval; +} + +/* Some utility routines for the divide: First a one digit multiply. + NUM (with SIZE digits) is multiplied by DIGIT and the result is + placed into RESULT. It is written so that NUM and RESULT can be + the same pointers. */ + +static void +_one_mult (num, size, digit, result) + unsigned char *num; + int size, digit; + unsigned char *result; +{ + int carry, value; + unsigned char *nptr, *rptr; + + if (digit == 0) + memset (result, 0, size); + else + { + if (digit == 1) + memcpy (result, num, size); + else + { + /* Initialize */ + nptr = (unsigned char *) (num+size-1); + rptr = (unsigned char *) (result+size-1); + carry = 0; + + while (size-- > 0) + { + value = *nptr-- * digit + carry; + *rptr-- = value % BASE; + carry = value / BASE; + } + + if (carry != 0) *rptr = carry; + } + } +} + + +/* The full division routine. This computes N1 / N2. It returns + 0 if the division is ok and the result is in QUOT. The number of + digits after the decimal point is SCALE. It returns -1 if division + by zero is tried. The algorithm is found in Knuth Vol 2. p237. */ + +int +bc_divide (n1, n2, quot, scale) + bc_num n1, n2, *quot; + int scale; +{ + bc_num qval; + unsigned char *num1, *num2; + unsigned char *ptr1, *ptr2, *n2ptr, *qptr; + int scale1, val; + unsigned int len1, len2, scale2, qdigits, extra, count; + unsigned int qdig, qguess, borrow, carry; + unsigned char *mval; + char zero; + unsigned int norm; + + /* Test for divide by zero. */ + if (bc_is_zero (n2)) return -1; + + /* Test for divide by 1. If it is we must truncate. */ + if (n2->n_scale == 0) + { + if (n2->n_len == 1 && *n2->n_value == 1) + { + qval = bc_new_num (n1->n_len, scale); + qval->n_sign = (n1->n_sign == n2->n_sign ? PLUS : MINUS); + memset (&qval->n_value[n1->n_len],0,scale); + memcpy (qval->n_value, n1->n_value, + n1->n_len + MIN(n1->n_scale,scale)); + bc_free_num (quot); + *quot = qval; + } + } + + /* Set up the divide. Move the decimal point on n1 by n2's scale. + Remember, zeros on the end of num2 are wasted effort for dividing. */ + scale2 = n2->n_scale; + n2ptr = (unsigned char *) n2->n_value+n2->n_len+scale2-1; + while ((scale2 > 0) && (*n2ptr-- == 0)) scale2--; + + len1 = n1->n_len + scale2; + scale1 = n1->n_scale - scale2; + if (scale1 < scale) + extra = scale - scale1; + else + extra = 0; + num1 = (unsigned char *) malloc (n1->n_len+n1->n_scale+extra+2); + if (num1 == NULL) bc_out_of_memory(); + memset (num1, 0, n1->n_len+n1->n_scale+extra+2); + memcpy (num1+1, n1->n_value, n1->n_len+n1->n_scale); + + len2 = n2->n_len + scale2; + num2 = (unsigned char *) malloc (len2+1); + if (num2 == NULL) bc_out_of_memory(); + memcpy (num2, n2->n_value, len2); + *(num2+len2) = 0; + n2ptr = num2; + while (*n2ptr == 0) + { + n2ptr++; + len2--; + } + + /* Calculate the number of quotient digits. */ + if (len2 > len1+scale) + { + qdigits = scale+1; + zero = TRUE; + } + else + { + zero = FALSE; + if (len2>len1) + qdigits = scale+1; /* One for the zero integer part. */ + else + qdigits = len1-len2+scale+1; + } + + /* Allocate and zero the storage for the quotient. */ + qval = bc_new_num (qdigits-scale,scale); + memset (qval->n_value, 0, qdigits); + + /* Allocate storage for the temporary storage mval. */ + mval = (unsigned char *) malloc (len2+1); + if (mval == NULL) bc_out_of_memory (); + + /* Now for the full divide algorithm. */ + if (!zero) + { + /* Normalize */ + norm = 10 / ((int)*n2ptr + 1); + if (norm != 1) + { + _one_mult (num1, len1+scale1+extra+1, norm, num1); + _one_mult (n2ptr, len2, norm, n2ptr); + } + + /* Initialize divide loop. */ + qdig = 0; + if (len2 > len1) + qptr = (unsigned char *) qval->n_value+len2-len1; + else + qptr = (unsigned char *) qval->n_value; + + /* Loop */ + while (qdig <= len1+scale-len2) + { + /* Calculate the quotient digit guess. */ + if (*n2ptr == num1[qdig]) + qguess = 9; + else + qguess = (num1[qdig]*10 + num1[qdig+1]) / *n2ptr; + + /* Test qguess. */ + if (n2ptr[1]*qguess > + (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + + num1[qdig+2]) + { + qguess--; + /* And again. */ + if (n2ptr[1]*qguess > + (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + + num1[qdig+2]) + qguess--; + } + + /* Multiply and subtract. */ + borrow = 0; + if (qguess != 0) + { + *mval = 0; + _one_mult (n2ptr, len2, qguess, mval+1); + ptr1 = (unsigned char *) num1+qdig+len2; + ptr2 = (unsigned char *) mval+len2; + for (count = 0; count < len2+1; count++) + { + val = (int) *ptr1 - (int) *ptr2-- - borrow; + if (val < 0) + { + val += 10; + borrow = 1; + } + else + borrow = 0; + *ptr1-- = val; + } + } + + /* Test for negative result. */ + if (borrow == 1) + { + qguess--; + ptr1 = (unsigned char *) num1+qdig+len2; + ptr2 = (unsigned char *) n2ptr+len2-1; + carry = 0; + for (count = 0; count < len2; count++) + { + val = (int) *ptr1 + (int) *ptr2-- + carry; + if (val > 9) + { + val -= 10; + carry = 1; + } + else + carry = 0; + *ptr1-- = val; + } + if (carry == 1) *ptr1 = (*ptr1 + 1) % 10; + } + + /* We now know the quotient digit. */ + *qptr++ = qguess; + qdig++; + } + } + + /* Clean up and return the number. */ + qval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); + if (bc_is_zero (qval)) qval->n_sign = PLUS; + _bc_rm_leading_zeros (qval); + bc_free_num (quot); + *quot = qval; + + /* Clean up temporary storage. */ + free (mval); + free (num1); + free (num2); + + return 0; /* Everything is OK. */ +} + + +/* Division *and* modulo for numbers. This computes both NUM1 / NUM2 and + NUM1 % NUM2 and puts the results in QUOT and REM, except that if QUOT + is NULL then that store will be omitted. + */ + +int +bc_divmod (num1, num2, quot, rem, scale) + bc_num num1, num2, *quot, *rem; + int scale; +{ + bc_num quotient = NULL; + bc_num temp; + int rscale; + + /* Check for correct numbers. */ + if (bc_is_zero (num2)) return -1; + + /* Calculate final scale. */ + rscale = MAX (num1->n_scale, num2->n_scale+scale); + bc_init_num(&temp); + + /* Calculate it. */ + bc_divide (num1, num2, &temp, scale); + if (quot) + quotient = bc_copy_num (temp); + bc_multiply (temp, num2, &temp, rscale); + bc_sub (num1, temp, rem, rscale); + bc_free_num (&temp); + + if (quot) + { + bc_free_num (quot); + *quot = quotient; + } + + return 0; /* Everything is OK. */ +} + + +/* Modulo for numbers. This computes NUM1 % NUM2 and puts the + result in RESULT. */ + +int +bc_modulo (num1, num2, result, scale) + bc_num num1, num2, *result; + int scale; +{ + return bc_divmod (num1, num2, NULL, result, scale); +} + +/* Raise BASE to the EXPO power, reduced modulo MOD. The result is + placed in RESULT. If a EXPO is not an integer, + only the integer part is used. */ + +int +bc_raisemod (base, expo, mod, result, scale) + bc_num base, expo, mod, *result; + int scale; +{ + bc_num power, exponent, parity, temp; + int rscale; + + /* Check for correct numbers. */ + if (bc_is_zero(mod)) return -1; + if (bc_is_neg(expo)) return -1; + + /* Set initial values. */ + power = bc_copy_num (base); + exponent = bc_copy_num (expo); + temp = bc_copy_num (_one_); + bc_init_num(&parity); + + /* Check the base for scale digits. */ + if (base->n_scale != 0) + bc_rt_warn ("non-zero scale in base"); + + /* Check the exponent for scale digits. */ + if (exponent->n_scale != 0) + { + bc_rt_warn ("non-zero scale in exponent"); + bc_divide (exponent, _one_, &exponent, 0); /*truncate */ + } + + /* Check the modulus for scale digits. */ + if (mod->n_scale != 0) + bc_rt_warn ("non-zero scale in modulus"); + + /* Do the calculation. */ + rscale = MAX(scale, base->n_scale); + while ( !bc_is_zero(exponent) ) + { + (void) bc_divmod (exponent, _two_, &exponent, &parity, 0); + if ( !bc_is_zero(parity) ) + { + bc_multiply (temp, power, &temp, rscale); + (void) bc_modulo (temp, mod, &temp, scale); + } + + bc_multiply (power, power, &power, rscale); + (void) bc_modulo (power, mod, &power, scale); + } + + /* Assign the value. */ + bc_free_num (&power); + bc_free_num (&exponent); + bc_free_num (result); + *result = temp; + return 0; /* Everything is OK. */ +} + +/* Raise NUM1 to the NUM2 power. The result is placed in RESULT. + Maximum exponent is LONG_MAX. If a NUM2 is not an integer, + only the integer part is used. */ + +void +bc_raise (num1, num2, result, scale) + bc_num num1, num2, *result; + int scale; +{ + bc_num temp, power; + long exponent; + int rscale; + int pwrscale; + int calcscale; + char neg; + + /* Check the exponent for scale digits and convert to a long. */ + if (num2->n_scale != 0) + bc_rt_warn ("non-zero scale in exponent"); + exponent = bc_num2long (num2); + if (exponent == 0 && (num2->n_len > 1 || num2->n_value[0] != 0)) + bc_rt_error ("exponent too large in raise"); + + /* Special case if exponent is a zero. */ + if (exponent == 0) + { + bc_free_num (result); + *result = bc_copy_num (_one_); + return; + } + + /* Other initializations. */ + if (exponent < 0) + { + neg = TRUE; + exponent = -exponent; + rscale = scale; + } + else + { + neg = FALSE; + rscale = MIN (num1->n_scale*exponent, MAX(scale, num1->n_scale)); + } + + /* Set initial value of temp. */ + power = bc_copy_num (num1); + pwrscale = num1->n_scale; + while ((exponent & 1) == 0) + { + pwrscale = 2*pwrscale; + bc_multiply (power, power, &power, pwrscale); + exponent = exponent >> 1; + } + temp = bc_copy_num (power); + calcscale = pwrscale; + exponent = exponent >> 1; + + /* Do the calculation. */ + while (exponent > 0) + { + pwrscale = 2*pwrscale; + bc_multiply (power, power, &power, pwrscale); + if ((exponent & 1) == 1) { + calcscale = pwrscale + calcscale; + bc_multiply (temp, power, &temp, calcscale); + } + exponent = exponent >> 1; + } + + /* Assign the value. */ + if (neg) + { + bc_divide (_one_, temp, result, rscale); + bc_free_num (&temp); + } + else + { + bc_free_num (result); + *result = temp; + if ((*result)->n_scale > rscale) + (*result)->n_scale = rscale; + } + bc_free_num (&power); +} + +/* Take the square root NUM and return it in NUM with SCALE digits + after the decimal place. */ + +int +bc_sqrt (num, scale) + bc_num *num; + int scale; +{ + int rscale, cmp_res, done; + int cscale; + bc_num guess, guess1, point5, diff; + + /* Initial checks. */ + cmp_res = bc_compare (*num, _zero_); + if (cmp_res < 0) + return 0; /* error */ + else + { + if (cmp_res == 0) + { + bc_free_num (num); + *num = bc_copy_num (_zero_); + return 1; + } + } + cmp_res = bc_compare (*num, _one_); + if (cmp_res == 0) + { + bc_free_num (num); + *num = bc_copy_num (_one_); + return 1; + } + + /* Initialize the variables. */ + rscale = MAX (scale, (*num)->n_scale); + bc_init_num(&guess); + bc_init_num(&guess1); + bc_init_num(&diff); + point5 = bc_new_num (1,1); + point5->n_value[1] = 5; + + + /* Calculate the initial guess. */ + if (cmp_res < 0) + { + /* The number is between 0 and 1. Guess should start at 1. */ + guess = bc_copy_num (_one_); + cscale = (*num)->n_scale; + } + else + { + /* The number is greater than 1. Guess should start at 10^(exp/2). */ + bc_int2num (&guess,10); + + bc_int2num (&guess1,(*num)->n_len); + bc_multiply (guess1, point5, &guess1, 0); + guess1->n_scale = 0; + bc_raise (guess, guess1, &guess, 0); + bc_free_num (&guess1); + cscale = 3; + } + + /* Find the square root using Newton's algorithm. */ + done = FALSE; + while (!done) + { + bc_free_num (&guess1); + guess1 = bc_copy_num (guess); + bc_divide (*num, guess, &guess, cscale); + bc_add (guess, guess1, &guess, 0); + bc_multiply (guess, point5, &guess, cscale); + bc_sub (guess, guess1, &diff, cscale+1); + if (bc_is_near_zero (diff, cscale)) + { + if (cscale < rscale+1) + cscale = MIN (cscale*3, rscale+1); + else + done = TRUE; + } + } + + /* Assign the number and clean up. */ + bc_free_num (num); + bc_divide (guess,_one_,num,rscale); + bc_free_num (&guess); + bc_free_num (&guess1); + bc_free_num (&point5); + bc_free_num (&diff); + return 1; +} + + +/* The following routines provide output for bcd numbers package + using the rules of POSIX bc for output. */ + +/* This structure is used for saving digits in the conversion process. */ +typedef struct stk_rec { + long digit; + struct stk_rec *next; +} stk_rec; + +/* The reference string for digits. */ +static char ref_str[] = "0123456789ABCDEF"; + + +/* A special output routine for "multi-character digits." Exactly + SIZE characters must be output for the value VAL. If SPACE is + non-zero, we must output one space before the number. OUT_CHAR + is the actual routine for writing the characters. */ + +void +bc_out_long (val, size, space, out_char) + long val; + int size, space; +#ifdef NUMBER__STDC__ + void (*out_char)(int); +#else + void (*out_char)(); +#endif +{ + char digits[40]; + int len, ix; + + if (space) (*out_char) (' '); + sprintf (digits, "%ld", val); + len = strlen (digits); + while (size > len) + { + (*out_char) ('0'); + size--; + } + for (ix=0; ix < len; ix++) + (*out_char) (digits[ix]); +} + +/* Output of a bcd number. NUM is written in base O_BASE using OUT_CHAR + as the routine to do the actual output of the characters. */ + +void +bc_out_num (num, o_base, out_char, leading_zero) + bc_num num; + int o_base; +#ifdef NUMBER__STDC__ + void (*out_char)(int); +#else + void (*out_char)(); +#endif + int leading_zero; +{ + char *nptr; + int index, fdigit, pre_space; + stk_rec *digits, *temp; + bc_num int_part, frac_part, base, cur_dig, t_num, max_o_digit; + + /* The negative sign if needed. */ + if (num->n_sign == MINUS) (*out_char) ('-'); + + /* Output the number. */ + if (bc_is_zero (num)) + (*out_char) ('0'); + else + if (o_base == 10) + { + /* The number is in base 10, do it the fast way. */ + nptr = num->n_value; + if (num->n_len > 1 || *nptr != 0) + for (index=num->n_len; index>0; index--) + (*out_char) (BCD_CHAR(*nptr++)); + else + nptr++; + + if (leading_zero && bc_is_zero (num)) + (*out_char) ('0'); + + /* Now the fraction. */ + if (num->n_scale > 0) + { + (*out_char) ('.'); + for (index=0; indexn_scale; index++) + (*out_char) (BCD_CHAR(*nptr++)); + } + } + else + { + /* special case ... */ + if (leading_zero && bc_is_zero (num)) + (*out_char) ('0'); + + /* The number is some other base. */ + digits = NULL; + bc_init_num (&int_part); + bc_divide (num, _one_, &int_part, 0); + bc_init_num (&frac_part); + bc_init_num (&cur_dig); + bc_init_num (&base); + bc_sub (num, int_part, &frac_part, 0); + /* Make the INT_PART and FRAC_PART positive. */ + int_part->n_sign = PLUS; + frac_part->n_sign = PLUS; + bc_int2num (&base, o_base); + bc_init_num (&max_o_digit); + bc_int2num (&max_o_digit, o_base-1); + + + /* Get the digits of the integer part and push them on a stack. */ + while (!bc_is_zero (int_part)) + { + bc_modulo (int_part, base, &cur_dig, 0); + temp = (stk_rec *) malloc (sizeof(stk_rec)); + if (temp == NULL) bc_out_of_memory(); + temp->digit = bc_num2long (cur_dig); + temp->next = digits; + digits = temp; + bc_divide (int_part, base, &int_part, 0); + } + + /* Print the digits on the stack. */ + if (digits != NULL) + { + /* Output the digits. */ + while (digits != NULL) + { + temp = digits; + digits = digits->next; + if (o_base <= 16) + (*out_char) (ref_str[ (int) temp->digit]); + else + bc_out_long (temp->digit, max_o_digit->n_len, 1, out_char); + free (temp); + } + } + + /* Get and print the digits of the fraction part. */ + if (num->n_scale > 0) + { + (*out_char) ('.'); + pre_space = 0; + t_num = bc_copy_num (_one_); + while (t_num->n_len <= num->n_scale) { + bc_multiply (frac_part, base, &frac_part, num->n_scale); + fdigit = bc_num2long (frac_part); + bc_int2num (&int_part, fdigit); + bc_sub (frac_part, int_part, &frac_part, 0); + if (o_base <= 16) + (*out_char) (ref_str[fdigit]); + else { + bc_out_long (fdigit, max_o_digit->n_len, pre_space, out_char); + pre_space = 1; + } + bc_multiply (t_num, base, &t_num, 0); + } + bc_free_num (&t_num); + } + + /* Clean up. */ + bc_free_num (&int_part); + bc_free_num (&frac_part); + bc_free_num (&base); + bc_free_num (&cur_dig); + bc_free_num (&max_o_digit); + } +} +/* Convert a number NUM to a long. The function returns only the integer + part of the number. For numbers that are too large to represent as + a long, this function returns a zero. This can be detected by checking + the NUM for zero after having a zero returned. */ + +long +bc_num2long (num) + bc_num num; +{ + long val; + char *nptr; + int index; + + /* Extract the int value, ignore the fraction. */ + val = 0; + nptr = num->n_value; + for (index=num->n_len; (index>0) && (val<=(LONG_MAX/BASE)); index--) + val = val*BASE + *nptr++; + + /* Check for overflow. If overflow, return zero. */ + if (index>0) val = 0; + if (val < 0) val = 0; + + /* Return the value. */ + if (num->n_sign == PLUS) + return (val); + else + return (-val); +} + + +/* Convert an integer VAL to a bc number NUM. */ + +void +bc_int2num (num, val) + bc_num *num; + int val; +{ + char buffer[30]; + char *bptr, *vptr; + int ix = 1; + char neg = 0; + + /* Sign. */ + if (val < 0) + { + neg = 1; + val = -val; + } + + /* Get things going. */ + bptr = buffer; + *bptr++ = val % BASE; + val = val / BASE; + + /* Extract remaining digits. */ + while (val != 0) + { + *bptr++ = val % BASE; + val = val / BASE; + ix++; /* Count the digits. */ + } + + /* Make the number. */ + bc_free_num (num); + *num = bc_new_num (ix, 0); + if (neg) (*num)->n_sign = MINUS; + + /* Assign the digits. */ + vptr = (*num)->n_value; + while (ix-- > 0) + *vptr++ = *--bptr; +} + +/* Convert a numbers to a string. Base 10 only.*/ + +char +*bc_num2str (num) + bc_num num; +{ + char *str, *sptr; + char *nptr; + int index, signch; + + /* Allocate the string memory. */ + signch = ( num->n_sign == PLUS ? 0 : 1 ); /* Number of sign chars. */ + if (num->n_scale > 0) + str = (char *) malloc (num->n_len + num->n_scale + 2 + signch); + else + str = (char *) malloc (num->n_len + 1 + signch); + if (str == NULL) bc_out_of_memory(); + + /* The negative sign if needed. */ + sptr = str; + if (signch) *sptr++ = '-'; + + /* Load the whole number. */ + nptr = num->n_value; + for (index=num->n_len; index>0; index--) + *sptr++ = BCD_CHAR(*nptr++); + + /* Now the fraction. */ + if (num->n_scale > 0) + { + *sptr++ = '.'; + for (index=0; indexn_scale; index++) + *sptr++ = BCD_CHAR(*nptr++); + } + + /* Terminate the string and return it! */ + *sptr = '\0'; + return (str); +} +/* Convert strings to bc numbers. Base 10 only.*/ + +void +bc_str2num (num, str, scale) + bc_num *num; + char *str; + int scale; +{ + int digits, strscale; + char *ptr, *nptr; + char zero_int; + + /* Prepare num. */ + bc_free_num (num); + + /* Check for valid number and count digits. */ + ptr = str; + digits = 0; + strscale = 0; + zero_int = FALSE; + if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ + if (*ptr == '.') ptr++; /* decimal point */ + while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ + if ((*ptr != '\0') || (digits+strscale == 0)) + { + *num = bc_copy_num (_zero_); + return; + } + + /* Adjust numbers and allocate storage and initialize fields. */ + strscale = MIN(strscale, scale); + if (digits == 0) + { + zero_int = TRUE; + digits = 1; + } + *num = bc_new_num (digits, strscale); + + /* Build the whole number. */ + ptr = str; + if (*ptr == '-') + { + (*num)->n_sign = MINUS; + ptr++; + } + else + { + (*num)->n_sign = PLUS; + if (*ptr == '+') ptr++; + } + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + nptr = (*num)->n_value; + if (zero_int) + { + *nptr++ = 0; + digits = 0; + } + for (;digits > 0; digits--) + *nptr++ = CH_VAL(*ptr++); + + + /* Build the fractional part. */ + if (strscale > 0) + { + ptr++; /* skip the decimal point! */ + for (;strscale > 0; strscale--) + *nptr++ = CH_VAL(*ptr++); + } +} + +/* pn prints the number NUM in base 10. */ + +static void +out_char (int c) +{ + putchar(c); +} + + +void +pn (num) + bc_num num; +{ + bc_out_num (num, 10, out_char, 0); + out_char ('\n'); +} + + +/* pv prints a character array as if it was a string of bcd digits. */ +void +pv (name, num, len) + char *name; + unsigned char *num; + int len; +{ + int i; + printf ("%s=", name); + for (i=0; i(b)?(a):(b)) +#define MIN(a,b) ((a)>(b)?(b):(a)) +#define ODD(a) ((a)&1) + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 0x7ffffff +#endif + + +/* Global numbers. */ +extern bc_num _zero_; +extern bc_num _one_; +extern bc_num _two_; + + +/* Function Prototypes */ + +/* Define the _PROTOTYPE macro if it is needed. */ + +#ifndef _PROTOTYPE +#ifdef NUMBER__STDC__ +#define _PROTOTYPE(func, args) func args +#else +#define _PROTOTYPE(func, args) func() +#endif +#endif + +_PROTOTYPE(void bc_init_numbers, (void)); + +_PROTOTYPE(bc_num bc_new_num, (int length, int scale)); + +_PROTOTYPE(void bc_free_num, (bc_num *num)); + +_PROTOTYPE(bc_num bc_copy_num, (bc_num num)); + +_PROTOTYPE(void bc_init_num, (bc_num *num)); + +_PROTOTYPE(void bc_str2num, (bc_num *num, char *str, int scale)); + +_PROTOTYPE(char *bc_num2str, (bc_num num)); + +_PROTOTYPE(void bc_int2num, (bc_num *num, int val)); + +_PROTOTYPE(long bc_num2long, (bc_num num)); + +_PROTOTYPE(int bc_compare, (bc_num n1, bc_num n2)); + +_PROTOTYPE(char bc_is_zero, (bc_num num)); + +_PROTOTYPE(char bc_is_near_zero, (bc_num num, int scale)); + +_PROTOTYPE(char bc_is_neg, (bc_num num)); + +_PROTOTYPE(void bc_add, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); + +_PROTOTYPE(void bc_sub, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); + +_PROTOTYPE(void bc_multiply, (bc_num n1, bc_num n2, bc_num *prod, int scale)); + +_PROTOTYPE(int bc_divide, (bc_num n1, bc_num n2, bc_num *quot, int scale)); + +_PROTOTYPE(int bc_modulo, (bc_num num1, bc_num num2, bc_num *result, + int scale)); + +_PROTOTYPE(int bc_divmod, (bc_num num1, bc_num num2, bc_num *quot, + bc_num *rem, int scale)); + +_PROTOTYPE(int bc_raisemod, (bc_num base, bc_num expo, bc_num mod, + bc_num *result, int scale)); + +_PROTOTYPE(void bc_raise, (bc_num num1, bc_num num2, bc_num *result, + int scale)); + +_PROTOTYPE(int bc_sqrt, (bc_num *num, int scale)); + +_PROTOTYPE(void bc_out_num, (bc_num num, int o_base, void (* out_char)(int), + int leading_zero)); + +#ifdef __cplusplus +} +#endif + +#endif + +// vim: set et sw=2 ts=8: diff --git a/src/numerictypes.cpp b/src/numerictypes.cpp new file mode 100644 index 0000000..f2aaf7a --- /dev/null +++ b/src/numerictypes.cpp @@ -0,0 +1,205 @@ +/* + * numerictypes.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "numerictypes.h" +#include "hmath.h" + +#include +#include +#include + +Abakus::TrigMode Abakus::m_trigMode = Abakus::Degrees; +int Abakus::m_prec = -1; + +#if HAVE_MPFR + +namespace Abakus +{ + +QString convertToString(const mpfr_ptr &number) +{ + char *str = 0; + QRegExp zeroKiller ("0*$"); + mp_exp_t exp; + int desiredPrecision = Abakus::m_prec; + QString decimalSymbol = KGlobal::locale()->decimalSymbol(); + + if(desiredPrecision < 0) + desiredPrecision = 8; + + // This first call is to see approximately how many digits of precision + // the fractional part has. + str = mpfr_get_str (0, &exp, 10, desiredPrecision, number, GMP_RNDN); + + // Check for ginormously small numbers. + if(exp < -74) + return "0"; + + if(exp < -2 || exp > desiredPrecision) + { + // Use exponential notation. + QString numbers (str); + mpfr_free_str(str); + + QString sign, l, r; + if(numbers[0] == '-') + { + sign = "-"; + l = numbers[1]; + r = numbers.right(numbers.length() - 2); + } + else + { + l = numbers[0]; + r = numbers.right(numbers.length() - 1); + } + + // Remove trailing zeroes. + if(Abakus::m_prec < 0) + r.replace(zeroKiller, ""); + + // But don't display numbers like 2.e10 either. + if(r.isEmpty()) + r = "0"; + + r.append(QString("e%1").arg(exp - 1)); + + return sign + l + decimalSymbol + r; + } + else + { + mpfr_free_str(str); + + // This call is to adjust the result so that the fractional part has at + // most m_prec digits of precision. + str = mpfr_get_str (0, &exp, 10, exp + desiredPrecision, number, GMP_RNDN); + } + + QString result = str; + mpfr_free_str(str); + str = 0; + + int position = exp; + QString l, r, sign; + + if(position < 0) { // Number < 0.1 + l.fill('0', -position); + + if(result[0] == '-') { + sign = "-"; + r = result.right(result.length() - 1); + } + else + r = result; + + r = l + r; + l = '0'; + } + else { // Number >= 0.1 + if(result[0] == '-') { + l = result.mid(1, position); + sign = "-"; + position++; + } + else + l = result.left(position); + + r = result.right(result.length() - position); + } + + // Remove trailing zeroes. + r.replace(zeroKiller, ""); + + // Don't display numbers of the form .23 + if(l.isEmpty()) + l = "0"; + + // If we have an integer don't display the decimal part. + if(r.isEmpty()) + return sign + l; + + return sign + l + decimalSymbol + r; +} + +} // namespace Abakus + +Abakus::number_t::value_type setupPi() +{ + static mpfr_t pi; + + mpfr_init2 (pi, 250); + mpfr_const_pi (pi, GMP_RNDN); + + return pi; +} + +Abakus::number_t::value_type setupExponential() +{ + static mpfr_t exponential; + mpfr_t one; + + mpfr_init2 (exponential, 250); + mpfr_init_set_ui (one, 1, GMP_RNDN); + mpfr_exp (exponential, one, GMP_RNDN); + mpfr_clear (one); + + return exponential; +} + +const Abakus::number_t::value_type Abakus::number_t::PI = setupPi(); +const Abakus::number_t::value_type Abakus::number_t::E = setupExponential(); + +#else + +// Converts hmath number to a string. + +namespace Abakus +{ + +QString convertToString(const HNumber &num) +{ + QString str = HMath::formatGenString(num, m_prec); + QString decimalSymbol = KGlobal::locale()->decimalSymbol(); + str.replace('.', decimalSymbol); + + QStringList parts = QStringList::split("e", str); + QRegExp zeroKiller("(" + QRegExp::escape(decimalSymbol) + + "\\d*[1-9])0*$"); // Remove trailing zeroes. + QRegExp zeroKiller2("(" + QRegExp::escape(decimalSymbol) + ")0*$"); + + str = parts[0]; + str.replace(zeroKiller, "\\1"); + str.replace(zeroKiller2, "\\1"); + if(str.endsWith(decimalSymbol)) + str.truncate(str.length() - 1); // Remove trailing period. + + if(parts.count() > 1 && parts[1] != "0") + str += QString("e%1").arg(parts[1]); + + return str; +} + +} // namespace Abakus. + +const Abakus::number_t::value_type Abakus::number_t::PI = HMath::pi(); +const Abakus::number_t::value_type Abakus::number_t::E = HMath::exp(1); + +#endif /* HAVE_MPFR */ + +// vim: set et ts=8 sw=4: diff --git a/src/numerictypes.h b/src/numerictypes.h new file mode 100644 index 0000000..7a82d06 --- /dev/null +++ b/src/numerictypes.h @@ -0,0 +1,693 @@ +#ifndef ABAKUS_NUMERICTYPES_H +#define ABAKUS_NUMERICTYPES_H +/* + * numerictypes.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include +#include + +#include "hmath.h" +#include "config.h" + +#if HAVE_MPFR +#include +#endif + +namespace Abakus +{ + +/* What trigonometric mode we're in. */ +typedef enum { Degrees, Radians } TrigMode; + +/* Shared application-wide */ +extern TrigMode m_trigMode; + +/* Precision to display at. */ +extern int m_prec; + +/** + * Representation of a number type. Includes the basic operators, along with + * built-in functions such as abs() and mod(). + * + * You need to actually define it using template specializations though. You + * can add functions in a specialization, it may be worth it to have the + * functions declared here as well so that you get a compiler error if you + * forget to implement it. + * + * Note that since we're using a specialization, and then typedef'ing the + * new specialized class to number_t, that means we only support one type of + * number at a time, and the choice is made at compile-time. + */ +template +class number +{ +public: + /// Default ctor and set-and-assign ctor wrapped in one. + number(const T& t = T()); + + /// Copy constructor. + number(const number &other); + + /// Create number from textual representation, useful for ginormously + /// precise numbers. + number(const char *str); + + /// Convienience constructor to create a number from an integer. + explicit number(int i); + + /// Assignment operator. Be sure to check for &other == this if necessary! + number &operator =(const number &other); + + // You need to implement the suite of comparison operators as well, along + // with the negation operator. Sorry. + + bool operator!=(const number &other) const; + bool operator==(const number &other) const; + bool operator<(const number &other) const; + bool operator>(const number &other) const; + bool operator<=(const number &other) const; + bool operator>=(const number &other) const; + + number operator -() const; + + // These functions must be implemented by all specializations to be used. + // Note that when implementing these functions, the implicit value is the + // value that this object is wrapping. E.g. you'd call the function on + // a number object, kind of like 3.sin() if you were using Ruby. + + // Trigonometric, must accept values in degrees. + number sin() const; + number cos() const; + number tan() const; + + // Inverse trigonometric, must return result in Degrees if necessary. + number asin() const; + number acos() const; + number atan() const; + + // Hyperbolic trigonometric (doesn't use Degrees). + number sinh() const; + number cosh() const; + number tanh() const; + + // Inverse hyperbolic trigonometric (doesn't use degrees). + number asinh() const; + number acosh() const; + number atanh() const; + + /// @return Number rounded to closest integer less than or equal to value. + number floor() const; + + /// @return Number rounded to closest integer greater than or equal to value. + number ceil() const; + + /// @return Number with only integer component of result. + number integer() const; + + /// @return Number with only fractional component of result. + number frac() const; + + /** + * @return Number rounded to nearest integer. What to do in 'strange' + * situations is specialization-dependant, I don't really care enough to + * mandate one or the other. + */ + number round() const; + + /// @return Absolute value of number. + number abs() const; + + /// @return Square root of number. + number sqrt() const; + + /// @return Natural-base logarithm of value. + number ln() const; + + /// @return base-10 logarithm of value. + number log() const; + + /// @return Natural base raised to the power given by our value. + number exp() const; + + /// @return Our value raised to the \p exponent power. Would be nice if + /// it supported even exponents on negative numbers correctly. + number pow(const number &exponent); + + /// @return value rounded to double precision. + double asDouble() const; + + /// @return Textual representation of the number, adjusted to the user's + /// current precision. + QString toString() const; + + /// @return Our value. + T value() const; +}; + +// You should also remember to overload the math operators for your +// specialization. These generic ones should work for templates wrapping a +// type that C++ already has operators for. + +template +inline number operator+(const number &l, const number &r) +{ + return number(l.value() + r.value()); +} + +template +inline number operator-(const number &l, const number &r) +{ + return number(l.value() - r.value()); +} + +template +inline number operator*(const number &l, const number &r) +{ + return number(l.value() * r.value()); +} + +template +inline number operator/(const number &l, const number &r) +{ + return number(l.value() / r.value()); +} + +#if HAVE_MPFR + +/** + * Utility function to convert a MPFR number to a string. This is declared + * this way so that when it changes we don't have to recompile all of Abakus. + * + * This function obeys the precision settings of the user. This means that if + * you change the precision between function calls, you may get different + * results, even on the same number! + * + * But, don't use this directly, you should be using + * number::toString() instead! + * + * @param number MPFR number to convert to string. + * @return The number converted to a string, in US Decimal format at this time. + * @see number<>::toString() + */ +QString convertToString(const mpfr_ptr &number); + +/** + * This is a specialization of the number<> template for the MPFR numeric type. + * It uses a weird hack in that it is declared as specializing mpfr_ptr instead + * of mpfr_t like is used everywhere in MPFR's public API. + * + * This is because mpfr_t does not seem to play well with C++ templates (it + * is implemented internally as a 1-length array to get pointer semantics + * while also allocating memory. + * + * What this means is that should you ever have to deal with allocating + * memory, you need to use allocate space for it (mpfr_ptr is a pointer to + * __mpfr_struct). + * + * I don't like using the internal API this way, but I have little choice. + * + * @author Michael Pyne + */ +template<> +class number +{ +public: + typedef mpfr_ptr value_type; + + static const mp_rnd_t RoundDirection = GMP_RNDN; + + number(const value_type& t) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set(m_t, t, RoundDirection); + } + + number(const number &other) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set(m_t, other.m_t, RoundDirection); + } + + number(const char *str) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set_str (m_t, str, 10, RoundDirection); + } + + explicit number(int i) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set_si(m_t, (signed long int) i, RoundDirection); + } + + /// Construct a number with a value of NaN. + number() + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init(m_t); + } + + ~number() + { + mpfr_clear(m_t); + delete (__mpfr_struct *) m_t; + } + + number &operator=(const number &other) + { + if(&other == this) + return *this; + + mpfr_clear (m_t); + mpfr_init_set (m_t, other.m_t, RoundDirection); + + return *this; + } + + bool operator!=(const number &other) const + { + return mpfr_equal_p(m_t, other.m_t) == 0; + } + + bool operator==(const number &other) const + { + return mpfr_equal_p(m_t, other.m_t) != 0; + } + + bool operator<(const number &other) const + { + return mpfr_less_p(m_t, other.m_t) != 0; + } + + bool operator>(const number &other) const + { + return mpfr_greater_p(m_t, other.m_t) != 0; + } + + bool operator<=(const number &other) const + { + return mpfr_lessequal_p(m_t, other.m_t) != 0; + } + + bool operator>=(const number &other) const + { + return mpfr_greaterequal_p(m_t, other.m_t) != 0; + } + + number operator -() const + { + number result(m_t); + mpfr_neg(result.m_t, result.m_t, RoundDirection); + + return result; + } + + // internal + number asRadians() const + { + if(m_trigMode == Degrees) + { + number result(m_t); + mpfr_t pi; + + mpfr_init (pi); + mpfr_const_pi (pi, RoundDirection); + mpfr_mul (result.m_t, result.m_t, pi, RoundDirection); + mpfr_div_ui (result.m_t, result.m_t, 180, RoundDirection); + + mpfr_clear (pi); + + return result; + } + else + return m_t; + } + + // internal + number toTrig() const + { + // Assumes num is in radians. + if(m_trigMode == Degrees) + { + number result(m_t); + mpfr_t pi; + + mpfr_init (pi); + mpfr_const_pi (pi, RoundDirection); + mpfr_mul_ui (result.m_t, result.m_t, 180, RoundDirection); + mpfr_div (result.m_t, result.m_t, pi, RoundDirection); + + mpfr_clear (pi); + + return result; + } + else + return m_t; + } + +/* There is a lot of boilerplate ahead, so define a macro to declare and + * define some functions for us to forward the call to MPFR. + */ +#define DECLARE_IMPL_BASE(name, func, in, out) number name() const \ +{ \ + number result = in; \ + mpfr_##func (result.m_t, result.m_t, RoundDirection); \ + \ + return out; \ +} + +// Normal function, uses 2 rather than 3 params +#define DECLARE_NAMED_IMPL2(name, func) number name() const \ +{ \ + number result = m_t; \ + mpfr_##func (result.m_t, result.m_t); \ + \ + return result; \ +} + +// Normal function, but MPFL uses a different name than abakus. +#define DECLARE_NAMED_IMPL(name, func) DECLARE_IMPL_BASE(name, func, m_t, result) + +// Normal function, just routes call to MPFR. +#define DECLARE_IMPL(name) DECLARE_NAMED_IMPL(name, name) + +// Trig function, degrees in +#define DECLARE_TRIG_IN_IMPL(name) DECLARE_IMPL_BASE(name, name, asRadians(), result) + +// Trig function, degrees out +#define DECLARE_TRIG_OUT_IMPL(name) DECLARE_IMPL_BASE(name, name, m_t, result.toTrig()) + +// Now declare our functions. + DECLARE_TRIG_IN_IMPL(sin) + DECLARE_TRIG_IN_IMPL(cos) + DECLARE_TRIG_IN_IMPL(tan) + + DECLARE_IMPL(sinh) + DECLARE_IMPL(cosh) + DECLARE_IMPL(tanh) + + DECLARE_TRIG_OUT_IMPL(asin) + DECLARE_TRIG_OUT_IMPL(acos) + DECLARE_TRIG_OUT_IMPL(atan) + + DECLARE_IMPL(asinh) + DECLARE_IMPL(acosh) + DECLARE_IMPL(atanh) + + DECLARE_NAMED_IMPL2(floor, floor) + DECLARE_NAMED_IMPL2(ceil, ceil) + DECLARE_NAMED_IMPL(integer, rint) + DECLARE_IMPL(frac) + DECLARE_NAMED_IMPL2(round, round) + + DECLARE_IMPL(abs) + DECLARE_IMPL(sqrt) + DECLARE_NAMED_IMPL(ln, log) + DECLARE_NAMED_IMPL(log, log10) + DECLARE_IMPL(exp) + + // Can't use macro for this one, it's sorta weird. + number pow(const number &exponent) + { + number result = m_t; + + mpfr_pow(result.m_t, result.m_t, exponent.m_t, RoundDirection); + return result; + } + + double asDouble() const + { + return mpfr_get_d(m_t, RoundDirection); + } + + // Note that this can be used dangerously, be careful. + value_type value() const { return m_t; } + + QString toString() const + { + // Move this to .cpp to avoid recompiling as I fix it. + return convertToString(m_t); + } + + static number nan() + { + // Doesn't apply, but the default value when initialized happens + // to be nan. + return number(); + } + + static const value_type PI; + static const value_type E; + +private: + mpfr_ptr m_t; +}; + +// Specializations of math operators for mpfr. + +template<> +inline number operator+(const number &l, const number &r) +{ + number result; + mpfr_add(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number operator-(const number &l, const number &r) +{ + number result; + mpfr_sub(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number operator*(const number &l, const number &r) +{ + number result; + mpfr_mul(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number operator/(const number &l, const number &r) +{ + number result; + mpfr_div(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + + // Abakus namespace continues. + typedef number number_t; + +#else + +// Defined in numerictypes.cpp for ease of reimplementation. +QString convertToString(const HNumber &num); + +/** + * Specialization for internal HMath library, used if MPFR isn't usable. + * + * @author Michael Pyne + */ +template<> +class number +{ +public: + typedef HNumber value_type; + + number(const HNumber& t = HNumber()) : m_t(t) + { + } + explicit number(int i) : m_t(i) { } + number(const number &other) : m_t(other.m_t) { } + + number(const char *s) : m_t(s) { } + + bool operator!=(const number &other) const + { + return m_t != other.m_t; + } + + bool operator==(const number &other) const + { + return m_t == other.m_t; + } + + bool operator<(const number &other) const + { + return m_t < other.m_t; + } + + bool operator>(const number &other) const + { + return m_t > other.m_t; + } + + bool operator<=(const number &other) const + { + return m_t <= other.m_t; + } + + bool operator>=(const number &other) const + { + return m_t >= other.m_t; + } + + number &operator=(const number &other) + { + m_t = other.m_t; + return *this; + } + + HNumber asRadians() const + { + if(m_trigMode == Degrees) + return m_t * PI / HNumber("180.0"); + else + return m_t; + } + + HNumber toTrig(const HNumber &num) const + { + // Assumes num is in radians. + if(m_trigMode == Degrees) + return num * HNumber("180.0") / PI; + else + return num; + } + + number sin() const + { + return HMath::sin(asRadians()); + } + + number cos() const + { + return HMath::cos(asRadians()); + } + + number tan() const + { + return HMath::tan(asRadians()); + } + + number asin() const + { + return toTrig(HMath::asin(m_t)); + } + + number acos() const + { + return toTrig(HMath::acos(m_t)); + } + + number atan() const + { + return toTrig(HMath::atan(m_t)); + } + + number floor() const + { + if(HMath::frac(m_t) == HNumber("0.0")) + return integer(); + if(HMath::integer(m_t) < HNumber("0.0")) + return HMath::integer(m_t) - 1; + return integer(); + } + + number ceil() const + { + return floor().value() + HNumber(1); + } + +/* There is a lot of boilerplate ahead, so define a macro to declare and + * define some functions for us to forward the call to HMath. + */ +#define DECLARE_IMPL(name) number name() const \ +{ return HMath::name(m_t); } + + DECLARE_IMPL(frac) + DECLARE_IMPL(integer) + DECLARE_IMPL(round) + + DECLARE_IMPL(abs) + + DECLARE_IMPL(sqrt) + + DECLARE_IMPL(ln) + DECLARE_IMPL(log) + DECLARE_IMPL(exp) + + DECLARE_IMPL(sinh) + DECLARE_IMPL(cosh) + DECLARE_IMPL(tanh) + + DECLARE_IMPL(asinh) + DECLARE_IMPL(acosh) + DECLARE_IMPL(atanh) + + HNumber value() const { return m_t; } + + double asDouble() const { return toString().toDouble(); } + + number operator-() const { return HMath::negate(m_t); } + + // TODO: I believe this doesn't work for negative numbers with even + // exponents. Which breaks simple stuff like (-2)^2. :( + number pow(const number &exponent) + { + return HMath::raise(m_t, exponent.m_t); + } + + QString toString() const + { + return convertToString(m_t); + } + + static number nan() + { + return HNumber::nan(); + } + + static const HNumber PI; + static const HNumber E; + +private: + HNumber m_t; +}; + + // Abakus namespace continues. + typedef number number_t; + +#endif /* HAVE_MPFR */ + +}; // namespace Abakus + +#endif /* ABAKUS_NUMERICTYPES_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/parser.yy b/src/parser.yy new file mode 100644 index 0000000..5a93621 --- /dev/null +++ b/src/parser.yy @@ -0,0 +1,386 @@ +/* + * parser.yy - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +%{ + +/* Add necessary includes here. */ +#include +#include +#include + +#include +#include +#include +#include + +#include "result.h" +#include "node.h" +#include "function.h" +#include "valuemanager.h" + +extern char *yytext; + +extern int gCheckIdents; + +int yylex(void); +int yyerror(const char *); + +%} + +%union { + Node *node; + NumericValue *value; + UnaryFunction *fn; + Identifier *ident; +} + +%token NUM +%type EXP FACTOR TERM S EXPONENT NUMBER VALUE FINAL +%type FUNC +%token FN +%token ID +%type IDENT ASSIGN +%token POWER "**" +%token SET "set" +%token REMOVE "remove" +%token DERIV "deriv" + +%% + +/** + * Parser design: + * + * This is pretty standard stuff for the calculator part (read in tokens from + * the lexer, and form a syntax tree using the Node* objects). The unusual + * part is that due to the design of bison, we don't actually return a value + * normally to the calling function. + * + * Instead, we make use of the static Result::setLastResult() call in order + * to notify the calling function of the result of the parse. There are + * different statuses you can set, including Error (with a message), Null + * (which indicates that some action happened that doesn't generate a result), + * and Value (with a Node* that holds the result). + * + * If you are done parsing before reaching the FINAL token, you can call: + * YYACCEPT: Done, parsed successfully. + * YYERROR : Done, there was an error. + * + * Note that if you let the parse bubble back up to FINAL, then the result + * will always be a Value. + */ + +FINAL: { gCheckIdents = 1; } S { + Result::setLastResult(NodePtr($2)); + $$ = 0; +} + +S: EXP { $$ = $1; } + +// Rudimentary error handling +S: error '=' { + Result::setLastResult(i18n("This is an invalid assignment.")); + + YYABORT; +} + +// Can't assign to a function. +S: FUNC '=' { + QString s(i18n("You can't assign to function %1").arg($1->name())); + Result::setLastResult(s); + + YYABORT; +} + +// This is a function prototype. abakus currently only supports one-argument +// functions. +ASSIGN: '(' { --gCheckIdents; } IDENT ')' '=' { + $$ = $3; +} + +// Blocking a variable with the name deriv is a slight feature regression +// since normally functions and variables with the same name can coexist, but +// I don't want to duplicate code all over the place. +S: SET DERIV { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv")); + Result::setLastResult(s); + + YYABORT; +} + +S: DERIV '=' { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv")); + Result::setLastResult(s); + + YYABORT; +} + +S: SET FUNC ASSIGN EXP { + ++gCheckIdents; + + // We're trying to reassign an already defined function, make sure it's + // not a built-in. + QString funcName = $2->name(); + QString ident = $3->name(); + FunctionManager *manager = FunctionManager::instance(); + + if(manager->isFunction(funcName) && !manager->isFunctionUserDefined(funcName)) { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg(funcName)); + Result::setLastResult(s); + + YYABORT; + } + + if(manager->isFunction(funcName)) + manager->removeFunction(funcName); + + BaseFunction *newFn = new UserDefinedFunction(funcName, $4); + if(!manager->addFunction(newFn, ident)) { + QString s(i18n("Unable to define function %1 because it is recursive.").arg(funcName)); + Result::setLastResult(s); + + YYABORT; + } + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// IDENT is the same as FUNC, except that the lexer has determined that IDENT +// is not already a FUNC. +S: SET IDENT ASSIGN EXP { + ++gCheckIdents; + + QString funcName = $2->name(); + QString ident = $3->name(); + + // No need to check if the function is already defined, because the + // lexer checked for us before returning the IDENT token. + BaseFunction *newFn = new UserDefinedFunction(funcName, $4); + FunctionManager::instance()->addFunction(newFn, ident); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Remove a defined function. +S: REMOVE FUNC '(' ')' { + FunctionManager::instance()->removeFunction($2->name()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Can't remove an ident using remove-func syntax. +S: REMOVE IDENT '(' ')' { + // This is an error + Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name()))); + YYABORT; +} + +// This happens when the user tries to remove a function that's not defined. +S: REMOVE IDENT '(' IDENT ')' { + // This is an error + Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name()))); + YYABORT; +} + +S: REMOVE IDENT { + ValueManager *manager = ValueManager::instance(); + + if(manager->isValueSet($2->name()) && !manager->isValueReadOnly($2->name())) { + manager->removeValue($2->name()); + + Result::setLastResult(Result::Null); + YYACCEPT; + } + else { + QString s; + if(manager->isValueSet($2->name())) + s = i18n("Can't remove predefined variable %1.").arg($2->name()); + else + s = i18n("Can't remove undefined variable %1.").arg($2->name()); + + Result::setLastResult(s); + + YYABORT; + } +} + +S: SET IDENT '=' EXP { + ValueManager *vm = ValueManager::instance(); + + if(vm->isValueReadOnly($2->name())) { + if($2->name() == "pi" && $4->value() == Abakus::number_t("3.0")) + Result::setLastResult(i18n("This isn't Indiana, you can't just change pi")); + else + Result::setLastResult(i18n("%1 is a constant").arg($2->name())); + + YYABORT; + } + + ValueManager::instance()->setValue($2->name(), $4->value()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Set a variable. +S: IDENT '=' EXP { + ValueManager *vm = ValueManager::instance(); + + if(vm->isValueReadOnly($1->name())) { + if($1->name() == "pi" && $3->value() == Abakus::number_t("3.0")) + Result::setLastResult(i18n("This isn't Indiana, you can't just change pi")); + else + Result::setLastResult(i18n("%1 is a constant").arg($1->name())); + + YYABORT; + } + + ValueManager::instance()->setValue($1->name(), $3->value()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +S: NUMBER '=' { + Result::setLastResult(i18n("Can't assign to %1").arg($1->value().toString())); + YYABORT; +} + +// Can't call this as a function. +TERM: IDENT '(' { + Result::setLastResult(i18n("%1 isn't a function (or operator expected)").arg($1->name())); + YYABORT; +} + +// Can't do this either. +TERM: IDENT IDENT { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +TERM: IDENT NUMBER { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +TERM: NUMBER NUMBER { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +S: error { + Result::setLastResult(i18n("Sorry, I can't figure it out.")); + YYABORT; +} + +/** + * Here be the standard calculator-parsing part. Nothing here should be too + * fancy. + */ +EXP: EXP '+' FACTOR { $$ = new BinaryOperator(BinaryOperator::Addition, $1, $3); } +EXP: EXP '-' FACTOR { $$ = new BinaryOperator(BinaryOperator::Subtraction, $1, $3); } +EXP: FACTOR { $$ = $1; } + +FACTOR: FACTOR '*' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3); } +FACTOR: FACTOR '/' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Division, $1, $3); } +FACTOR: EXPONENT { $$ = $1; } + +EXPONENT: TERM POWER EXPONENT { $$ = new BinaryOperator(BinaryOperator::Exponentiation, $1, $3); } +EXPONENT: TERM { $$ = $1; } + +TERM: '+' VALUE { $$ = $2; } +TERM: '-' VALUE { $$ = new UnaryOperator(UnaryOperator::Negation, $2); } +TERM: '(' EXP ')' { $$ = $2; } +TERM: '-' '(' EXP ')' { $$ = new UnaryOperator(UnaryOperator::Negation, $3); } + +TERM: VALUE { $$ = $1; } + +VALUE: NUMBER { $$ = $1; } + +NUMBER: NUM { + KLocale *locale = KGlobal::locale(); + QChar decimal = locale->decimalSymbol()[0]; + + // Replace current decimal separator with US Decimal separator to be + // evil. + unsigned len = strlen(yytext); + for(unsigned i = 0; i < len; ++i) + if(yytext[i] == decimal) + yytext[i] = '.'; + + Abakus::number_t value(yytext); + + $$ = new NumericValue(value); +} + +TERM: DERIV { --gCheckIdents; } '(' EXP ',' { ++gCheckIdents; } EXP ')' { + $$ = new DerivativeFunction($4, $7); +} + +TERM: FUNC TERM { + $1->setOperand($2); + $$ = $1; +} + +/* Handle implicit multiplication */ +TERM: NUMBER FUNC TERM { + $2->setOperand($3); + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2); +} + +TERM: NUMBER '(' EXP ')' { + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3); +} + +TERM: NUMBER IDENT { + if(gCheckIdents > 0 && !ValueManager::instance()->isValueSet($2->name())) { + Result::setLastResult(i18n("Unknown variable %1").arg($2->name())); + YYABORT; + } + + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2); +} + +VALUE: IDENT { + if(gCheckIdents <= 0 || ValueManager::instance()->isValueSet($1->name())) + $$ = $1; + else { + Result::setLastResult(i18n("Unknown variable %1").arg($1->name())); + YYABORT; + } +} + +IDENT: ID { + $$ = new Identifier(yytext); +} + +FUNC: FN { + /* No check necessary, the lexer has already checked for us. */ + $$ = new BuiltinFunction(yytext, 0); +} + +%% + +int gCheckIdents = 0; + +int yyerror(const char *) +{ + return 0; +} diff --git a/src/result.cpp b/src/result.cpp new file mode 100644 index 0000000..4aa06ca --- /dev/null +++ b/src/result.cpp @@ -0,0 +1,33 @@ +/* + * result.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "result.h" + +Result *Result::m_lastResult = new Result; + +Result::Result(const QString &message) : m_type(Error), m_message(message) +{ +} + +Result::Result(NodePtr node) : m_node(node), m_type(Value) +{ +} + +Result::Result(Type type) : m_type(type) +{ +} diff --git a/src/result.h b/src/result.h new file mode 100644 index 0000000..682324c --- /dev/null +++ b/src/result.h @@ -0,0 +1,77 @@ +#ifndef ABAKUS_RESULT_H +#define ABAKUS_RESULT_H +/* + * result.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include + +#include "node.h" +#include "sharedptr.h" + +/** + * A conceptual result from an expression parse. Used to determine if the + * parse succeeded or failed. If it succeeded it will have a node value you + * can query as the answer. + */ +class Result +{ +public: + typedef enum { Error, Null, Value } Type; + + /** + * Default constructor, which constructs a "failed" Result. + */ + Result(const QString &message = ""); + + /** + * Node constructor, which constructs a "succeeded" result. + */ + Result(NodePtr node); + + /** + * Constructor, constructs a "null" result. This means that the + * operation was successful, but did not result in a normal value. + */ + Result(Type type); + + bool failed() const { return m_type == Error; } + + Type type() const { return m_type; } + + QString message() const { return m_message; } + + const NodePtr result() const { return m_node; } + NodePtr result() { return m_node; } + + static Result *lastResult() { return m_lastResult; } + static void setLastResult(const Result &result) + { + *m_lastResult = result; + } + +private: + NodePtr m_node; + Type m_type; + QString m_message; + static Result *m_lastResult; +}; + +#endif /* ABAKUS_RESULT_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/resultlistview.cpp b/src/resultlistview.cpp new file mode 100644 index 0000000..abe76c1 --- /dev/null +++ b/src/resultlistview.cpp @@ -0,0 +1,149 @@ +/* + * resultlistview.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "resultlistview.h" +#include "resultlistviewtext.h" +#include "dragsupport.h" + +using DragSupport::makePixmap; +using namespace ResultList; + +ResultListView::ResultListView(QWidget *parent, const char *name) : + KListView(parent, name), m_itemRightClicked(0) +{ + connect(this, SIGNAL(doubleClicked(QListViewItem *, const QPoint &, int)), + SLOT(slotDoubleClicked(QListViewItem *, const QPoint &, int))); + + addColumn(i18n("Expression")); + addColumn(i18n("Result")); + addColumn(i18n("Shortcut")); + + header()->hide(); // I hate that header + header()->setStretchEnabled(ResultColumn, true); + + setDragEnabled(true); + setItemMargin(2); + setColumnAlignment(ResultColumn, AlignLeft); + setColumnAlignment(ShortcutColumn, AlignHCenter); + setColumnWidthMode(ResultColumn, Maximum); + setSortColumn(-1); +} + +bool ResultListView::getStackValue(unsigned stackPosition, Abakus::number_t &result) +{ + QListViewItem *it = firstChild(); + for(; it; it = it->itemBelow()) { + ResultListViewText *resultItem = dynamic_cast(it); + if(!resultItem->wasError() && resultItem->stackPosition() == stackPosition) { + result = Abakus::number_t(resultItem->resultText().latin1()); + return true; + } + } + + return false; +} + +QDragObject *ResultListView::dragObject() +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + ResultListViewText *item = itemUnderCursor(); + + if(item) { + QString text = item->resultText(); + + int column = header()->sectionAt(viewportPos.x()); + + if(column == ExpressionColumn) + text = item->expressionText(); + + QDragObject *drag = new QTextDrag(text, this); + drag->setPixmap(makePixmap(text, font())); + + return drag; + } + + return 0; +} + +void ResultListView::contextMenuEvent(QContextMenuEvent *e) +{ + m_itemRightClicked = itemUnderCursor(); + KPopupMenu *menu = constructPopupMenu(m_itemRightClicked); + + menu->popup(e->globalPos()); +} + +void ResultListView::slotDoubleClicked(QListViewItem *item, const QPoint &, int c) +{ + ResultListViewText *textItem = dynamic_cast(item); + if(!textItem) + return; + + if(c == ExpressionColumn) + emit signalEntrySelected(textItem->expressionText()); + else if(c == ResultColumn) + emit signalResultSelected(textItem->resultText()); +} + +KPopupMenu *ResultListView::constructPopupMenu(const ResultListViewText *item) +{ + KPopupMenu *menu = new KPopupMenu(this, "list view context menu"); + + menu->insertItem(i18n("Clear &History"), this, SLOT(clear()), ALT+Key_R); + + int id = menu->insertItem(i18n("Copy Result to Clipboard"), this, SLOT(slotCopyResult())); + if(!item || item->wasError()) + menu->setItemEnabled(id, false); + + return menu; +} + +void ResultListView::slotCopyResult() +{ + if(!m_itemRightClicked) + return; + + QClipboard *clipboard = QApplication::clipboard(); + + clipboard->setText(m_itemRightClicked->resultText(), QClipboard::Clipboard); +} + +ResultListViewText *ResultListView::lastItem() const +{ + return static_cast(KListView::lastItem()); +} + +ResultListViewText *ResultListView::itemUnderCursor() const +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + QListViewItem *underCursor = itemAt(viewportPos); + return static_cast(underCursor); +} + +#include "resultlistview.moc" diff --git a/src/resultlistview.h b/src/resultlistview.h new file mode 100644 index 0000000..891443c --- /dev/null +++ b/src/resultlistview.h @@ -0,0 +1,64 @@ +#ifndef ABAKUS_RESULTLISTVIEW_H +#define ABAKUS_RESULTLISTVIEW_H +/* + * resultlistview.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include +#include "numerictypes.h" + +class KPopupMenu; +class QLabel; +class QDragObject; +class ResultListViewText; + +namespace ResultList { + enum { ExpressionColumn = 0, ResultColumn, ShortcutColumn }; +} + +class ResultListView : public KListView +{ + Q_OBJECT + + public: + ResultListView(QWidget *parent = 0, const char *name = "result list view"); + + bool getStackValue(unsigned stackPosition, Abakus::number_t &result); + + ResultListViewText *lastItem() const; + + protected: + virtual void contextMenuEvent(QContextMenuEvent *e); + virtual QDragObject *dragObject(); + + signals: + void signalEntrySelected(const QString &text); + void signalResultSelected(const QString &text); + + private slots: + void slotDoubleClicked(QListViewItem *item, const QPoint & /* Ignored */, int c); + void slotCopyResult(); + + private: + KPopupMenu *constructPopupMenu(const ResultListViewText *item); + ResultListViewText *itemUnderCursor() const; + + ResultListViewText *m_itemRightClicked; +}; + +#endif diff --git a/src/resultlistviewtext.cpp b/src/resultlistviewtext.cpp new file mode 100644 index 0000000..cbf7bfb --- /dev/null +++ b/src/resultlistviewtext.cpp @@ -0,0 +1,135 @@ +/* + * resultlistviewtext.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include + +#include +#include +#include +#include +#include + +#include "resultlistviewtext.h" + +using namespace ResultList; + +ResultListViewText::ResultListViewText(KListView *listView, + const QString &text, + const QString &result, + ResultListViewText *after, + bool isError) + : KListViewItem(listView, after, text, result), m_text(text), + m_result(result), m_wasError(isError), m_stackPosition(0) +{ + // This is some kind of non-result answer, don't bother worrying about the + // stack status, it hasn't changed. +} + +ResultListViewText::ResultListViewText(KListView *listView, + const QString &text, + const Abakus::number_t &result, + ResultListViewText *after, + bool isError) + : KListViewItem(listView, after, text), m_text(text), + m_result(result.toString()), m_wasError(isError), m_stackPosition(0), + m_value(result) +{ + if(after) { + ResultListViewText *item = static_cast(listView->firstChild()); + for (; item && item != this; item = static_cast(item->itemBelow())) { + if(!item->wasError()) { + item->setStackPosition(item->stackPosition() + 1); + item->repaint(); + } + } + } + + setStackPosition(0); + + // Call this manually to be rid of trailing zeroes. + setText(ResultColumn, m_value.toString()); +} + +void ResultListViewText::setStackPosition(unsigned pos) +{ + setText(ShortcutColumn, QString("$%1").arg(pos)); + m_stackPosition = pos; +} + +void ResultListViewText::precisionChanged() +{ + if(m_wasError) + return; + + m_result = m_value.toString(); + setText(ResultColumn, m_result); +} + +void ResultListViewText::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align) +{ + QColorGroup group(cg); + + // XXX: The Qt::red may not provide good contrast with weird color schemes. + // If so I apologize. + if(m_wasError && column == ResultColumn) + group.setColor(QColorGroup::Text, m_result == "OK" ? cg.link() : Qt::red); + + if(column == ResultColumn) { + QFont f = p->font(); + f.setBold(true); + p->setFont(f); + } + + if(column == ShortcutColumn) { + QFont f = p->font(); + f.setItalic(true); + f.setPointSize(QMIN(f.pointSize() * 9 / 11, 10)); + p->setFont(f); + } + + KListViewItem::paintCell(p, group, column, width, align); +} + +int ResultListViewText::width(const QFontMetrics &fm, const QListView *lv, int c) const +{ + // Simulate painting the text to get accurate results. + if(c == ResultColumn) { + QFont f = lv->font(); + f.setBold(true); + return KListViewItem::width(QFontMetrics(f), lv, c); + } + + if(c == ShortcutColumn) { + QFont f = lv->font(); + f.setItalic(true); + f.setPointSize(QMIN(f.pointSize() * 9 / 11, 10)); + return KListViewItem::width(QFontMetrics(f), lv, c); + } + + return KListViewItem::width(fm, lv, c); +} + +void ResultListViewText::setText(int column, const QString &text) +{ + if(!m_wasError && column == ResultColumn) { + KListViewItem::setText(column, m_value.toString()); + return; + } + + KListViewItem::setText(column, text); +} diff --git a/src/resultlistviewtext.h b/src/resultlistviewtext.h new file mode 100644 index 0000000..d624b0b --- /dev/null +++ b/src/resultlistviewtext.h @@ -0,0 +1,70 @@ +#ifndef ABAKUS_RESULTLISTVIEWTEXT_H +#define ABAKUS_RESULTLISTVIEWTEXT_H +/* + * resultlistviewtext.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "resultlistview.h" +#include "numerictypes.h" + +class QPainter; +class QColorGroup; +class QFontMetrics; + +// This class shows the results shown in the MainWindow result pane. +class ResultListViewText : public KListViewItem +{ + public: + ResultListViewText(KListView *listView, + const QString &text, + const QString &result, + ResultListViewText *after, + bool isError = false); + + ResultListViewText(KListView *listView, + const QString &text, + const Abakus::number_t &result, + ResultListViewText *after, + bool isError = false); + + QString expressionText() const { return m_text; } + QString resultText() const { return m_result; } + + bool wasError() const { return m_wasError; } + unsigned stackPosition() const { return m_stackPosition; } + + void setStackPosition(unsigned pos); + + // Redisplays the text by calling value.toString again. + void precisionChanged(); + + // Reimplemented from KListViewItem + virtual void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align); + virtual int width(const QFontMetrics &fm, const QListView *lv, int c) const; + + // Reimplemented to remove trailing zeroes from results. + virtual void setText(int column, const QString &text); + + private: + QString m_text, m_result; + bool m_wasError; + unsigned m_stackPosition; + Abakus::number_t m_value; +}; + +#endif diff --git a/src/rpnmuncher.cpp b/src/rpnmuncher.cpp new file mode 100644 index 0000000..ad75495 --- /dev/null +++ b/src/rpnmuncher.cpp @@ -0,0 +1,267 @@ +/* + * rpnmuncher.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include + +#include +#include + +#include +#include +#include +#include + +#include "rpnmuncher.h" +#include "valuemanager.h" +#include "function.h" + +/** + * Holds either a textual identifier, or a numeric value. + */ +class Operand +{ + public: + Operand() : m_isValue(true), m_value(0) { } + Operand(const QString &ident) : m_isValue(false), m_text(ident) { } + Operand(Abakus::number_t value) : m_isValue(true), m_value(value) { } + + Abakus::number_t value() const + { + if(m_isValue) + return m_value; + + return ValueManager::instance()->value(m_text); + } + + operator Abakus::number_t() const + { + return value(); + } + + QString text() const { return m_text; } + + private: + bool m_isValue; + QString m_text; + Abakus::number_t m_value; +}; + +typedef enum { Number = 256, Func, Ident, Power, Set, Remove, Pop, Clear, Unknown } Token; + +static int tokenize (const QString &token); + +QString RPNParser::m_errorStr; +bool RPNParser::m_error(false); +OperandStack RPNParser::m_stack; + +struct Counter +{ + ~Counter() { + Abakus::number_t count( static_cast(RPNParser::stack().count()) ); + ValueManager::instance()->setValue("stackCount", count); + } +}; + +Abakus::number_t RPNParser::rpnParseString(const QString &text) +{ + QStringList tokens = QStringList::split(QRegExp("\\s"), text); + Counter counter; // Will update stack count when we leave proc. + (void) counter; // Avoid warnings about it being unused. + + // Used in the case statements below + Operand l, r; + FunctionManager *manager = FunctionManager::instance(); + Function *fn = 0; + + m_error = false; + m_errorStr = QString::null; + + for(QStringList::ConstIterator it = tokens.begin(); it != tokens.end(); ++it) { + switch(tokenize(*it)) + { + case Number: + m_stack.push(Abakus::number_t((*it).latin1())); + break; + + case Pop: + if(m_stack.isEmpty()) { + m_error = true; + m_errorStr = i18n("Can't pop from an empty stack."); + return Abakus::number_t::nan(); + } + + m_stack.pop(); + break; + + case Clear: + m_stack.clear(); + break; + + case Func: + if(m_stack.count() < 1) { + m_error = true; + m_errorStr = i18n("Insufficient operands for function %1").arg(*it); + return Abakus::number_t::nan(); + } + + fn = manager->function(*it); + + l = m_stack.pop(); + m_stack.push(evaluateFunction(fn, l)); + break; + + case Ident: + m_stack.push(*it); + break; + + case Set: + case Remove: + m_error = true; + m_errorStr = i18n("The set and remove commands can only be used in normal mode."); + return Abakus::number_t::nan(); + break; + + case Power: + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for exponentiation operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value().pow(r)); + break; + + case Unknown: + m_error = true; + m_errorStr = i18n("Unknown token %1").arg(*it); + return Abakus::number_t::nan(); + break; + + case '=': + r = m_stack.pop(); + l = m_stack.pop(); + ValueManager::instance()->setValue(l.text(), r); + + m_stack.push(l); + break; + + case '+': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for addition operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() + r.value()); + break; + + case '-': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for subtraction operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() - r.value()); + break; + + case '*': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for multiplication operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() * r.value()); + break; + + case '/': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for division operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() / r.value()); + break; + + default: + // Impossible case happened. + kdError() << "Impossible case happened in " << k_funcinfo << endl; + m_error = true; + m_errorStr = "Bug found in program, please report."; + return Abakus::number_t::nan(); + } + } + + // TODO: Should this be an error? + if(m_stack.isEmpty()) + return Abakus::number_t::nan(); + + return m_stack.top(); +} + +static int tokenize (const QString &token) +{ + bool isOK; + + token.toDouble(&isOK); + if(isOK) + return Number; + + if(token == "**" || token == "^") + return Power; + + if(FunctionManager::instance()->isFunction(token)) + return Func; + + if(token.lower() == "set") + return Set; + + if(token.lower() == "pop") + return Pop; + + if(token.lower() == "clear") + return Clear; + + if(token.lower() == "remove") + return Remove; + + if(QRegExp("^\\w+$").search(token) != -1 && + QRegExp("\\d").search(token) == -1) + { + return Ident; + } + + if(QRegExp("^[-+*/=]$").search(token) != -1) + return token[0]; + + return Unknown; +} + +// vim: set et sw=4 ts=8: diff --git a/src/rpnmuncher.h b/src/rpnmuncher.h new file mode 100644 index 0000000..71c340e --- /dev/null +++ b/src/rpnmuncher.h @@ -0,0 +1,44 @@ +#ifndef ABAKUS_RPNMUNCHER_H +#define ABAKUS_RPNMUNCHER_H +/* + * rpnmuncher.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +class QString; +class Operand; + +template class QValueStack; +typedef QValueStack OperandStack; + +#include "numerictypes.h" + +class RPNParser +{ + public: + static Abakus::number_t rpnParseString(const QString &text); + static bool wasError() { return m_error; } + static QString errorString() { return m_errorStr; } + static OperandStack &stack() { return m_stack; } + + private: + static QString m_errorStr; + static bool m_error; + static OperandStack m_stack; +}; + +#endif diff --git a/src/sharedptr.h b/src/sharedptr.h new file mode 100644 index 0000000..2837870 --- /dev/null +++ b/src/sharedptr.h @@ -0,0 +1,122 @@ +#ifndef ABAKUS_SHARED_PTR_H +#define ABAKUS_SHARED_PTR_H +/* + * sharedptr.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include + +template +class SharedPtr +{ +public: + typedef T value_type; + + class Data + { + public: + Data(T *ptr = 0, unsigned refCount = 1) : m_ptr(ptr), m_refCount(refCount) + { + } + + void deref() + { + --m_refCount; + + if(!m_refCount) { + delete m_ptr; + m_ptr = 0; + } + } + + void ref() + { + ++m_refCount; + } + + T *ptr() { return m_ptr; } + const T* ptr() const { return m_ptr; } + + unsigned refCount() const { return m_refCount; } + + private: + T *m_ptr; + unsigned m_refCount; + }; + + SharedPtr() : m_data(new Data) + { + } + + SharedPtr(T* ptr) : m_data(new Data(ptr)) + { + } + + SharedPtr(const SharedPtr &other) : m_data(other.m_data) + { + m_data->ref(); + } + + ~SharedPtr() + { + m_data->deref(); + } + + SharedPtr &operator=(const SharedPtr &other) + { + if(&other == this) + return *this; + + m_data->deref(); + m_data = other.m_data; + m_data->ref(); + + return *this; + } + + T *operator ->() + { + return m_data->ptr(); + } + + const T *operator ->() const + { + return m_data->ptr(); + } + + T &operator *() + { + return *m_data->ptr(); + } + + const T& operator *() const + { + return *m_data->ptr(); + } + + bool isNull() const { return m_data->ptr() == 0; } + + unsigned refCount() const { return m_data->refCount(); } + +private: + Data *m_data; +}; + +#endif /* ABAKUS_SHARED_PTR_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/valuemanager.cpp b/src/valuemanager.cpp new file mode 100644 index 0000000..db8ef99 --- /dev/null +++ b/src/valuemanager.cpp @@ -0,0 +1,105 @@ +/* + * valuemanager.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include +#include + +#include + +#include "numerictypes.h" +#include "valuemanager.h" + +ValueManager *ValueManager::m_manager = 0; + +ValueManager *ValueManager::instance() +{ + if(!m_manager) + m_manager = new ValueManager; + + return m_manager; +} + +ValueManager::ValueManager(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_values.insert("pi", Abakus::number_t::PI); + m_values.insert("e", Abakus::number_t::E); +} + +Abakus::number_t ValueManager::value(const QString &name) const +{ + return m_values[name]; +} + +bool ValueManager::isValueSet(const QString &name) const +{ + return m_values.contains(name); +} + +bool ValueManager::isValueReadOnly(const QString &name) const +{ + QRegExp readOnlyValues("^(ans|pi|e|stackCount)$"); + + return name.find(readOnlyValues) != -1; +} + +void ValueManager::setValue(const QString &name, const Abakus::number_t value) +{ + if(m_values.contains(name) && this->value(name) != value) + emit signalValueChanged(name, value); + else if(!m_values.contains(name)) + emit signalValueAdded(name, value); + + m_values.replace(name, value); +} + +void ValueManager::removeValue(const QString &name) +{ + if(m_values.contains(name)) + emit signalValueRemoved(name); + + m_values.remove(name); +} + +void ValueManager::slotRemoveUserVariables() +{ + QStringList vars = valueNames(); + + for(QStringList::ConstIterator var = vars.constBegin(); var != vars.constEnd(); ++var) + if(!isValueReadOnly(*var)) + removeValue(*var); +} + +QStringList ValueManager::valueNames() const +{ + return m_values.keys(); +} + +QString ValueManager::description(const QString &valueName) +{ + if(valueName == "e") + return i18n("Natural exponential base - 2.7182818"); + if(valueName == "pi") + return i18n("pi (π) - 3.1415926"); + + return QString(); +} + +#include "valuemanager.moc" + +// vim: set et ts=8 sw=4 encoding=utf-8: diff --git a/src/valuemanager.h b/src/valuemanager.h new file mode 100644 index 0000000..f57016c --- /dev/null +++ b/src/valuemanager.h @@ -0,0 +1,69 @@ +#ifndef ABAKUS_VALUEMANAGER_H +#define ABAKUS_VALUEMANAGER_H +/* + * valuemanager.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "numerictypes.h" + +class ValueManager : public QObject +{ + Q_OBJECT + public: + typedef QMap valueMap; + + static ValueManager *instance(); + + Abakus::number_t value(const QString &name) const; + + bool isValueSet(const QString &name) const; + bool isValueReadOnly(const QString &name) const; + + void setValue(const QString &name, const Abakus::number_t value); + void removeValue(const QString &name); + + QStringList valueNames() const; + + /** + * Returns a textual description of a constant built-into abakus. + */ + static QString description(const QString &valueName); + + signals: + void signalValueAdded(const QString &name, Abakus::number_t value); + void signalValueRemoved(const QString &name); + void signalValueChanged(const QString &name, Abakus::number_t newValue); + + public slots: + void slotRemoveUserVariables(); + + private: + ValueManager(QObject *parent = 0, const char *name = "value manager"); + + static ValueManager *m_manager; + valueMap m_values; +}; + +#endif + +// vim: set et sw=4 ts=8: