diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d4adaa6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,146 @@ +cmake_minimum_required(VERSION 2.6) +project(Teapot) + +set(Teapot_VERSION_MAJOR 2) +set(Teapot_VERSION_MINOR 3) +set(Teapot_VERSION_PATCH 0) + +option(ENABLE_HELP "Enable built-in documentation" ON) +option(ENABLE_UTF8 "Enable UTF-8 support" ON) +option(ENABLE_STATIC "Link FLTK statically" OFF) + +add_library(teapotlib context.c csv.c eval.c func.c htmlio.c latex.c main.c misc.c parser.c sc.c scanner.c sheet.c utf8.c wk1.c xdr.c) + +find_library(LIB_PORTABLEXDR portablexdr) +if (NOT LIB_PORTABLEXDR) + set(LIB_PORTABLEXDR "") +endif () + +find_package(Curses) +if (CURSES_FOUND AND ENABLE_UTF8) + find_library(LIB_CURSESW ncursesw) + if (NOT LIB_CURSESW) + find_library(LIB_CURSESW ncursesw5) + endif () + if (LIB_CURSESW) + set(teapot_DEB_DEPENDS ", libncursesw5 (>= 5.6+20071006-3)") + set(CURSES_CURSES_LIBRARY ${LIB_CURSESW}) + else () + set(ENABLE_UTF8 OFF) + set(teapot_DEB_DEPENDS ", libncurses5 (>= 5.6+20071006-3)") + endif () +elseif (CURSES_FOUND) + set(teapot_DEB_DEPENDS ", libncurses5 (>= 5.6+20071006-3)") +endif () + +if (CURSES_FOUND) + include_directories("${CURSES_INCLUDE_DIR}") + add_executable(teapot display.c complete.c) + target_link_libraries(teapot teapotlib m ${CURSES_CURSES_LIBRARY} ${LIB_PORTABLEXDR}) + install(TARGETS teapot DESTINATION bin) +endif () + +find_package(FLTK NO_MODULE) +if (FLTK_FOUND) + include("${FLTK_USE_FILE}") + + fltk_wrap_ui(fteapot fteapot.fl) + add_executable(fteapot WIN32 ${fteapot_FLTK_UI_SRCS}) + set(fteapot_DEB_DEPENDS ", libstdc++6 (>= 4.1.1), libfltk1.3") + if (ENABLE_HELP) + set(fteapot_DEB_DEPENDS "${fteapot_DEB_DEPENDS}, libfltk-images1.3") + if (ENABLE_STATIC) + target_link_libraries(fteapot teapotlib fltk fltk_images ${LIB_PORTABLEXDR}) + else () + target_link_libraries(fteapot teapotlib fltk_SHARED fltk_images_SHARED ${LIB_PORTABLEXDR}) + endif () + else () + if (ENABLE_STATIC) + target_link_libraries(fteapot teapotlib fltk fltk_images ${LIB_PORTABLEXDR}) + else () + target_link_libraries(fteapot teapotlib fltk_SHARED ${LIB_PORTABLEXDR}) + endif () + endif () + install(TARGETS fteapot DESTINATION bin) +endif () + +if (ENABLE_HELP) + add_custom_command( + OUTPUT teapot.tex teapot.lyx + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/doc/teapot.lyx + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/teapot.lyx teapot.lyx + COMMAND lyx -e pdflatex -f main teapot.lyx + ) + add_custom_command( + OUTPUT teapot.pdf teapot.aux.old teapot.log teapot.toc + MAIN_DEPENDENCY teapot.tex + VERBATIM + COMMAND pdflatex teapot.tex; diff -q teapot.aux.old teapot.aux && cp teapot.aux teapot.aux.old + COMMAND pdflatex teapot.tex; diff -q teapot.aux.old teapot.aux && cp teapot.aux teapot.aux.old + ) + add_custom_command(OUTPUT teapot.pdf teapot.out MAIN_DEPENDENCY teapot.tex teapot.aux.old VERBATIM COMMAND pdflatex teapot.tex; diff -q teapot.aux.old teapot.aux && cp teapot.aux teapot.aux.old) + add_custom_command( + OUTPUT html/ html/index.html .latex2html-init + MAIN_DEPENDENCY teapot.tex + DEPENDS teapot.tex teapot.pdf ${CMAKE_CURRENT_SOURCE_DIR}/doc/.latex2html-init + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/.latex2html-init .latex2html-init + COMMAND ${CMAKE_COMMAND} -E make_directory html + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/contents.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/next.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/next_g.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/prev.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/prev_g.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/up.png html/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/doc/up_g.png html/ + COMMAND latex2html teapot.tex + ) + add_custom_target(pdf DEPENDS teapot.pdf) + add_custom_target(html DEPENDS html/index.html) + add_custom_target(doc ALL DEPENDS teapot.pdf html/index.html) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/teapot FILES_MATCHING PATTERN *.html PATTERN *.png) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/teapot.pdf DESTINATION share/doc/teapot) + set(HELPFILE "${CMAKE_INSTALL_PREFIX}/share/doc/teapot/index.html") +endif () + +install(FILES COPYING README DESTINATION share/doc/teapot) +install(FILES teapot.1 DESTINATION share/man/man1) + +add_custom_target(dist + COMMAND ${CMAKE_COMMAND} -E remove_directory teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH} + COMMAND ${CMAKE_COMMAND} -E make_directory teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH} + COMMAND LANG=C bash -c "cp -r ${CMAKE_CURRENT_SOURCE_DIR}/doc ${CMAKE_CURRENT_SOURCE_DIR}/examples ${CMAKE_CURRENT_SOURCE_DIR}/[A-Z]*[A-Z] ${CMAKE_CURRENT_SOURCE_DIR}/*.*[a-z1] teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH}" + COMMAND tar czf teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH}.tar.gz teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH} + COMMAND setarch ${CPACK_RPM_PACKAGE_ARCHITECTURE} cpack + COMMAND bash -c "[ ! -f fteapot ] || for i in Teapot-*-Linux.*; do mv \"\$i\" \"\${i%-Linux.*}-Linux-${CPACK_RPM_PACKAGE_ARCHITECTURE}.\${i##*-Linux.}\"; done" + COMMAND bash -c "[ ! -f fteapot.exe -o ${ENABLE_HELP} != OFF ] || { rm Teapot-*.zip; i586-mingw32msvc-strip fteapot.exe && upx -9 -o Teapot-${Teapot_VERSION_MAJOR}.${Teapot_VERSION_MINOR}.${Teapot_VERSION_PATCH}-win32-minimal.exe fteapot.exe; }" + VERBATIM +) + +configure_file("${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h") +include_directories("${PROJECT_BINARY_DIR}") +include_directories("${PROJECT_SOURCE_DIR}") + +set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ARCH-NOTFOUND CACHE ".deb Architecture" STRING) +set(CPACK_RPM_PACKAGE_ARCHITECTURE ARCH-NOTFOUND CACHE ".rpm Architecture" STRING) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Table Editor And Planner, Or: Teapot!") +set(CPACK_PACKAGE_DESCRIPTION "A three-dimensional spreadsheet specialized in advanced calculations. Comes in GUI (FLTK) and console (curses) flavours.") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VENDOR "Jörg Walter, http://www.syntax-k.de/projekte/teapot") + +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") +set(CPACK_PACKAGE_VERSION_MAJOR "${Teapot_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${Teapot_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${Teapot_VERSION_PATCH}") +set(CPACK_PACKAGE_CONTACT "Jörg Walter ") +set(CPACK_PACKAGE_EXECUTABLES "fteapot" "Teapot") +set(CPACK_STRIP_FILES TRUE) + +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11), libgcc1 (>= 1:4.1.1)${teapot_DEB_DEPENDS}${fteapot_DEB_DEPENDS}") +include(InstallRequiredSystemLibraries) +include(CPack) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif () diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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 Lesser General +Public License instead of this License. But first, please read +. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..6c433bb --- /dev/null +++ b/INSTALL @@ -0,0 +1,31 @@ +The new build system uses CMake (http://www.cmake.org). It handles +all platform differences. + +See the README for required third-party libraries. + +The documentation can only be built if you have latex2html (version +2008), pdflatex and lyx installed. If you don't, turn off the +ENABLE_HELP option (using the ccmake command or any other CMake +frontend). + +Note that the GUI version definitely needs FLTK-1.3, so you may need +to download and build an SVN snapshot. SVN r7704 was used for development. +You MUST use CMake to configure FLTK, not "./configure". You don't need +to install it, as FLTK is so small that statically linking it (the default) +is safe. If you don't install FLTK, set the path to your FLTK directory + using ccmake. + +For the console version you will have to make sure your system +offers a SYSV compatible curses. You can check that you do by looking +at its include file curses.h; it should e.g. contain KEY_ macros. If you +have a system which contains BSD and SYSV curses libraries, make sure (by +checking the cmake variables) that the SYSV curses.h is used. This +particularly applies if you use the SYSV curses clone ncurses. If you do +so, further make sure that you use version 4 or higher. Otherwise teapot +may not work correctly and you get corrupted screen output. Since version +4 makes it easy to be built as shared (ELF) library, this can reduce the +size of the teapot binary considerably. + +If you want the curses frontend to be fully UTF-8 aware, you need +libncursesw (Debian package name: libncursesw5-dev). + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..ffcb6a9 --- /dev/null +++ b/NEWS @@ -0,0 +1,62 @@ +This is release 2.3.0 of teapot (Table Editor And Planner, Or: Teapot), +a spread sheet program for UNIX. + +The changes compared to 2.2.1 are: + +o visual garbage fixed (curses version) +o made unquoted strings default, added "-q" flag to turn quotes back on +o made attributes menu more user friendly: no more question when changing a single cell, one menu option optimized away +o added "-H" command line flag to hide row/column headers; in FLTK, this means you can only change sheets via Ctrl-Shift-PgUp/PgDn +o expanded command line help + +The changes compared to 2.2.0 are: + +o Help system improved +o Compile fix for MacOS +o Link shared by default. Set option ENABLE_STATIC to statically link FLTK. +o Bugfixes for CSV export +o Bugfixes for console mode +o Added font styles bold and underline + +The changes compared to 2.1.0 are: + +o UTF-8 support for curses frontend +o various bug fixes + +The changes compared to 2.0.2 are: + +o switched to CMake as build system +o Win32 compatibility +o usage improvements in the GUI version +o various bug fixes + +The changes compared to 2.0.1 are: + +o Low-resource builds (leaving out the help system) +o various bug fixes + +The changes compared to 2.0 are: + +o Fix a possible crash and some minor bugs +o improve FLTK navigation and cell editing (moving around and + selecting cells now works while editing a formula) +o automatically start a string value when typing a letter +o fix build system + +The changes compared to 1.09 are: + +o Several bug fixes and changes how files are handled +o FLTK user interface +o Built-in Help and About screen (currently only in FLTK) +o License changed to GPL (see mbox file in doc folder for permission) + +The changes compared to 1.07 are: + +o Use KEY_HOME additional to KEY_BEG to move to the beginning of + the line in the line editor + +A GNU zipped tar file is available from: + + http://www.syntax-k.de/projekte/teapot/ + +Michael diff --git a/README b/README new file mode 100644 index 0000000..36af125 --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +This is release 2.3.0 of teapot (Table Editor And Planner, Or: Teapot), +a spread sheet program for UNIX. + + Copyright (C) 1995-2006 Michael Haardt + Copyright (C) 2009-2012 Joerg Walter + 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 3 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, see . + +The current release has the following features: + +o curses based user interface with easy to understand menus +o FLTK 1.3 based GUI following common user interface conventions +o Cross-platform compatibility +o UTF-8 support +o portable sheet file format uses XDR or ASCII format +o tbl, LaTeX, HTML, CSV or formatted text files can be generated and + simple SC and WK1 sheets can be imported +o typed expression evaluator with the types int, float, string, error, + pointer to cell and empty +o iterative expressions +o powerful cell addressing +o three-dimensional sheets +o new expression evaluator functions can be added very easy +o a user guide, available as PDF and HTML +o It is still a small and simple program! + +teapot should work on any system which supports the following: + +o ANSI C +o An XDR library (included in the GNU C library, otherwise + use http://people.redhat.com/~rjones/portablexdr/ ) +o For the text interface: SYSV curses or ncurses library +o For UTF-8 in the text interface: libncursesw and a UTF-8 locale +o For the FLTK interface: C++ +o For the FLTK interface: FLTK 1.3 + +A GNU zipped tar file is available from: + + http://www.syntax-k.de/projekte/teapot/ + +For more information, read the included manual in HTML or PDF format. + diff --git a/complete.c b/complete.c new file mode 100644 index 0000000..dabd5d1 --- /dev/null +++ b/complete.c @@ -0,0 +1,55 @@ +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include + +#include "complete.h" + +void completefile(char *file, char *pos, size_t size) +{ + struct dirent *direntp; + char *dir, *slash, *complete; + char next; + int required, atmost; + DIR *dirp; + + next = *pos; + *pos = 0; + + if (!(complete=strrchr(file,'/'))) { + dir = strdup("."); + complete = file; + + } else { + if (complete == file) { + dir = strdup("/"); + } else { + dir = strdup(file); + dir[complete-file] = 0; + } + complete++; + } + required = strlen(complete); + + if (!(dirp = opendir(dir))) { + free(dir); + return; + } + + while ((direntp = readdir(dirp))) { + if ((direntp->d_name[0] != '.' || complete[0] == '.') && !strncmp(complete, direntp->d_name, required)) { + if (!next && strlen(direntp->d_name) > required) { + strncpy(pos, direntp->d_name+required, size-(pos-file)); + file[size-1] = 0; + break; + } else if (direntp->d_name[required] == next && !strcmp(pos+1, direntp->d_name+required+1)) { + next = 0; + } + } + } + + closedir(dirp); + free(dir); +} diff --git a/complete.h b/complete.h new file mode 100644 index 0000000..4a2fb69 --- /dev/null +++ b/complete.h @@ -0,0 +1,6 @@ +#ifndef COMPLETE_H +#define COMPLETE_H + +void completefile(char *, char *, size_t size); + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..2a4e34f --- /dev/null +++ b/config.h @@ -0,0 +1,13 @@ +/* configuration values */ + + +#define VERSION "2.2.1" + +#define HELPFILE "/usr/local/share/doc/teapot/html/index.html" + +#define ENABLE_HELP +#define ENABLE_UTF8 +#define CURSES_HAVE_CURSES_H +/* #undef CURSES_HAVE_NCURSES_H */ +/* #undef CURSES_HAVE_NCURSES_NCURSES_H */ +/* #undef CURSES_HAVE_NCURSES_CURSES_H */ diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..d57b5aa --- /dev/null +++ b/config.h.in @@ -0,0 +1,13 @@ +/* configuration values */ + + +#define VERSION "@Teapot_VERSION_MAJOR@.@Teapot_VERSION_MINOR@.@Teapot_VERSION_PATCH@" + +#define HELPFILE "@HELPFILE@" + +#cmakedefine ENABLE_HELP +#cmakedefine ENABLE_UTF8 +#cmakedefine CURSES_HAVE_CURSES_H +#cmakedefine CURSES_HAVE_NCURSES_H +#cmakedefine CURSES_HAVE_NCURSES_NCURSES_H +#cmakedefine CURSES_HAVE_NCURSES_CURSES_H diff --git a/context.c b/context.c new file mode 100644 index 0000000..90f1c23 --- /dev/null +++ b/context.c @@ -0,0 +1,138 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include + + +#include "context.h" +#include "main.h" +#include "misc.h" +/*}}}*/ + +/* savecontext -- save as ConTeXt table */ /*{{{*/ +const char *savecontext(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count) +{ + /* variables */ /*{{{*/ + FILE *fp=(FILE*)0; /* cause runtime error */ + int x,y,z; + char buf[1024]; + char num[20]; + char fullname[PATH_MAX]; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sheet!=(Sheet*)0); + assert(name!=(const char*)0); + /*}}}*/ + *count=0; + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column"); + if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno); + for (z=z1; z<=z2; ++z) + { + if (body) + /* open new file */ /*{{{*/ + { + sprintf(num,".%d",z); + + fullname[sizeof(fullname)-strlen(num)-1]='\0'; + (void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + (void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno); + } + /*}}}*/ + else + /* print header */ /*{{{*/ + if (z==z1) + { + if (fputs_close("\\starttext\n",fp)==EOF) return strerror(errno); + } + else + { + if (fputs_close("\\page\n",fp)==EOF) return strerror(errno); + } + /*}}}*/ + + /* print bogus format */ /*{{{*/ + fprintf(fp,"\\starttable["); + for (x=x1; x<=x2; ++x) if (fputs_close("|l",fp)==EOF) return strerror(errno); + fprintf(fp,"|]\n"); + /*}}}*/ + for (y=y1; y<=y2; ++y) + /* print contents */ /*{{{*/ + { + for (x=x1; x<=x2; ) + { + int multicols; + char *bufp; + + if (x>x1 && fputs_close("\\NC",fp)==EOF) return strerror(errno); + for (multicols=x+1; multicolsdimx && shadowed(sheet,multicols,y,z); ++multicols); + multicols=multicols-x; + if (multicols>1) fprintf(fp,"\\use{%d}",multicols); + switch (getadjust(sheet,x,y,z)) + { + case LEFT: if (fputs_close("\\JustLeft ",fp)==EOF) return strerror(errno); break; + case RIGHT: if (fputs_close("\\JustRight ",fp)==EOF) return strerror(errno); break; + case CENTER: if (fputs_close("\\JustCenter ",fp)==EOF) return strerror(errno); break; + default: assert(0); + } + printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z); +/* if (fputs_close("}{",fp)==EOF) return strerror(errno);*/ + if (transparent(sheet,x,y,z)) + { + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + else for (bufp=buf; *bufp; ++bufp) switch (*bufp) + { + case '%': + case '$': + case '&': + case '#': + case '_': + case '{': + case '}': + case '~': + case '^': if (fputc_close('\\',fp)==EOF || fputc_close(*bufp,fp)==EOF) return strerror(errno); break; + case '\\': if (fputs_close("\\backslash ",fp)==EOF) return strerror(errno); break; + default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno); + } +/* if (fputc_close('}',fp)==EOF) return strerror(errno);*/ + x+=multicols; + ++*count; + } + if (fputs_close(y +#include +#include +#ifdef OLD_REALLOC +#define realloc(s,l) myrealloc(s,l) +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include "csv.h" + +static int semicol=0; + +/* csv_setopt -- set language */ /*{{{C}}}*//*{{{*/ +void csv_setopt(int sem) +{ + semicol=sem; +} +/*}}}*/ +#if 0 +/* csv_date -- convert string "yearmonthday" to date */ /*{{{*/ +static struct CSV_Date csv_date(const char *s, const char **end) +{ + struct CSV_Date date; + + assert(s!=(const char*)0); + assert(end!=(const char**)0); + *end=s; + if (*s=='"') + { + ++s; + if (isdigit(*s)) + { + for (date.year=0; isdigit(*s); date.year=10*date.year+(*s-'0'),++s); + if (*s) + { + ++s; + if (isdigit(*s)) + { + for (date.month=0; isdigit(*s); date.month=10*date.month+(*s-'0'),++s); + if (*s) + { + ++s; + if (isdigit(*s)) + { + for (date.day=0; isdigit(*s); date.day=10*date.day+(*s-'0'),++s); + if (*s=='"') + { + ++s; + *end=s; + } + } + } + } + } + } + } + return date; +} +/*}}}*/ +#endif +/* csv_separator -- convert string \t to nothing */ /*{{{*/ +void csv_separator(const char *s, const char **end) +{ + assert(s!=(const char*)0); + assert(end!=(const char**)0); + *end=s+(*s=='\t' || (semicol && *s==';') || (!semicol && *s==',')); +} +/*}}}*/ +/* csv_long -- convert string [0[x]]12345 to long */ /*{{{*/ +long csv_long(const char *s, const char **end) +{ + long value; + const char *t; + + assert(s!=(const char*)0); + assert(end!=(const char**)0); + if (*s=='\t') { *end=s; return 0L; }; + value=strtol(s,(char**)end,0); + if (s!=*end) + { + t=*end; csv_separator(t,end); + if (t!=*end || *t=='\0' || *t=='\n') return value; + } + *end=s; return 0L; +} +/*}}}*/ +/* csv_double -- convert string 123.4e5 to double */ /*{{{*/ +double csv_double(const char *s, const char **end) +{ + double value; + const char *t; + + assert(s!=(const char*)0); + assert(end!=(const char**)0); + if (*s=='\t') { *end=s; return 0.0; }; + value=strtod(s,(char**)end); + if (s!=*end) + { + t=*end; csv_separator(t,end); + if (t!=*end || *t=='\0' || *t=='\n') return value; + } + *end=s; return 0.0; +} +/*}}}*/ +/* csv_string -- convert almost any string to string */ /*{{{*/ +char *csv_string(const char *s, const char **end) +{ + static char *string; + static int strings,stringsz; + + assert(s!=(const char*)0); + assert(end!=(const char**)0); + strings=0; + stringsz=0; + string=(char*)0; + if (!isprint((int)*s) || (!semicol && *s==',') || (semicol && *s==';')) return (char*)0; + if (*s=='"') + { + ++s; + while (*s!='\0' && *s!='\n' && *s!='"') + { + if ((strings+2)>=stringsz) + { + string=realloc(string,stringsz+=32); + } + string[strings]=*s; + ++strings; + ++s; + } + if (*s=='"') ++s; + } + else + { + while (*s!='\0' && *s!='\n' && *s!='\t' && ((!semicol && *s!=',') || (semicol && *s!=';'))) + { + if ((strings+2)>=stringsz) + { + string=realloc(string,stringsz+=32); + } + string[strings]=*s; + ++strings; + ++s; + } + } + string[strings]='\0'; + *end=s; + csv_separator(s,end); + return string; +} +/*}}}*/ diff --git a/csv.h b/csv.h new file mode 100644 index 0000000..cf40e17 --- /dev/null +++ b/csv.h @@ -0,0 +1,17 @@ +#ifndef CSV_H +#define CSV_H + +struct CSV_Date +{ + int day; + int month; + int year; +}; + +void csv_setopt(int sem); +void csv_separator(const char *s, const char **end); +char *csv_string(const char *s, const char **end); +double csv_double(const char *s, const char **end); +long csv_long(const char *s, const char **end); + +#endif diff --git a/default.h b/default.h new file mode 100644 index 0000000..90777e0 --- /dev/null +++ b/default.h @@ -0,0 +1,38 @@ +#ifndef DEFAULT_H +#define DEFAULT_H + +#include + +/* default width of a column */ +#define DEF_COLUMNWIDTH 12 + +/* default precision of a printed value */ +#define DEF_PRECISION 2 + +/* default is no scientific notation for numbers */ +#define DEF_SCIENTIFIC 0 + +/* character attribute for cell and row numbers */ +#define DEF_NUMBER A_BOLD + +/* character attribute for cell cursor */ +#define DEF_CELLCURSOR A_REVERSE + +/* character attribute for selected menu choice */ +#define DEF_MENU A_REVERSE + +/* maximum number of sort keys */ +#define MAX_SORTKEYS 8 + +/* maximum number of eval() nesting */ +#define MAX_EVALNEST 32 + +/* define if testing with EletricFence */ +#ifdef THE_ELECTRIC_FENCE +#undef malloc +#undef free +#undef realloc +#undef calloc +#endif + +#endif diff --git a/display.c b/display.c new file mode 100644 index 0000000..4d41d82 --- /dev/null +++ b/display.c @@ -0,0 +1,1110 @@ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#include +#include +#include +#include +#include +#ifdef ENABLE_UTF8 +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NEED_BCOPY +#define memmove(dst,src,len) bcopy(src,dst,len) +#endif +#ifdef OLD_REALLOC +#define realloc(s,l) myrealloc(s,l) +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + + +#include "complete.h" +#include "default.h" +#include "display.h" +#include "eval.h" +#include "main.h" +#include "misc.h" +#include "sheet.h" +#include "utf8.h" + +static Key wgetc(void); + +/* redraw -- redraw whole screen */ +static void redraw(void) +{ + (void)touchwin(curscr); + (void)wrefresh(curscr); +} + + +/* do_attribute -- set cell attributes */ +static int do_attribute(Sheet *cursheet) +{ + + MenuChoice mainmenu[5]; + MenuChoice adjmenu[11]; + int c; + + /* create menus */ + adjmenu[0].str=mystrmalloc(_("lL)eft")); adjmenu[0].c='\0'; + adjmenu[1].str=mystrmalloc(_("rR)ight")); adjmenu[1].c='\0'; + adjmenu[2].str=mystrmalloc(_("cC)entered")); adjmenu[2].c='\0'; + adjmenu[3].str=mystrmalloc(_("11).23e1 <-> 12.3")); adjmenu[3].c='\0'; + adjmenu[4].str=mystrmalloc(_("pP)recision")); adjmenu[4].c='\0'; + adjmenu[5].str=mystrmalloc(_("sS)hadow")); adjmenu[5].c='\0'; + adjmenu[6].str=mystrmalloc(_("bB)old")); adjmenu[6].c='\0'; + adjmenu[7].str=mystrmalloc(_("uU)nderline")); adjmenu[7].c='\0'; + adjmenu[8].str=mystrmalloc(_("oO)utput special characters")); adjmenu[8].c='\0'; + adjmenu[9].str=(char*)0; + + mainmenu[0].str=mystrmalloc(_("rR)epresentation")); mainmenu[0].c='\0'; + mainmenu[1].str=mystrmalloc(_("lL)abel")); mainmenu[1].c='\0'; + mainmenu[2].str=mystrmalloc(_("oLo)ck")); mainmenu[2].c='\0'; + mainmenu[3].str=mystrmalloc(_("iI)gnore")); mainmenu[3].c='\0'; + mainmenu[4].str=(char*)0; + + do + { + c = line_menu(cursheet->mark1x==-1 ? _("Cell attribute:") : _("Block attribute:"),mainmenu,0); + if (cursheet->mark1x==-1 && c!=2 && locked(cursheet,cursheet->curx,cursheet->cury,cursheet->curz)) line_msg(_("Cell attribute:"),_("Cell is locked")); + else + { + switch (c) + { + case -2: + case -1: c = KEY_CANCEL; break; + case 0: + { + switch (c=line_menu(cursheet->mark1x==-1 ? _("Cell attribute:") : _("Block attribute:"),adjmenu,0)) + { + case -2: + case -1: c = K_INVALID; break; + case 0: c = ADJUST_LEFT; break; + case 1: c = ADJUST_RIGHT; break; + case 2: c = ADJUST_CENTER; break; + case 3: c = ADJUST_SCIENTIFIC; break; + case 4: c = ADJUST_PRECISION; break; + case 5: c = ADJUST_SHADOW; break; + case 6: c = ADJUST_BOLD; break; + case 7: c = ADJUST_UNDERLINE; break; + case 8: c = ADJUST_TRANSPARENT; break; + default: assert(0); + } + break; + } + case 1: c = ADJUST_LABEL; break; + case 2: c = ADJUST_LOCK; break; + case 3: c = ADJUST_IGNORE; break; + default: assert(0); + } + } + } while (c == K_INVALID); + if (c == KEY_CANCEL) c = K_INVALID; + + /* free menus */ + free(mainmenu[0].str); + free(mainmenu[1].str); + free(mainmenu[2].str); + free(mainmenu[3].str); + free(adjmenu[0].str); + free(adjmenu[1].str); + free(adjmenu[2].str); + free(adjmenu[3].str); + free(adjmenu[4].str); + free(adjmenu[5].str); + free(adjmenu[6].str); + free(adjmenu[7].str); + free(adjmenu[8].str); + + return c; +} +/* do_file -- file menu */ +static int do_file(Sheet *cursheet) +{ + + MenuChoice menu[4]; + int c; + + + menu[0].str=mystrmalloc(_("lL)oad")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("sS)ave")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("nN)ame")); menu[2].c='\0'; + menu[3].str=(char*)0; + c=0; + do + { + switch (c=line_menu(_("File:"),menu,0)) + { + case -2: + case -1: c = KEY_CANCEL; break; + case 0: c = K_LOADMENU; break; + case 1: c = K_SAVEMENU; break; + case 2: c = K_NAME; break; + default: assert(0); + } + } while (c == K_INVALID); + if (c == KEY_CANCEL) c = K_INVALID; + free(menu[0].str); + free(menu[1].str); + free(menu[2].str); + return c; +} + + +/* do_shell -- spawn a shell */ +static int do_shell(void) +{ + pid_t pid; + struct sigaction interrupt; + + refresh(); + interrupt.sa_flags=0; + sigemptyset(&interrupt.sa_mask); + interrupt.sa_handler=SIG_IGN; + sigaction(SIGINT,&interrupt,(struct sigaction *)0); + sigaction(SIGQUIT,&interrupt,(struct sigaction *)0); + switch (pid=fork()) + { + /* -1 */ + case -1: line_msg(_("Spawn sub shell"),strerror(errno)); break; + + /* 0 */ + case 0: + { + const char *shell; + + if ((shell=getenv("SHELL"))==(const char*)0) + { + struct passwd *pwd; + + if ((pwd=getpwuid(getuid()))==(struct passwd*)0) + { + shell="/bin/sh"; + } + else + { + shell=pwd->pw_shell; + } + } + line_msg((const char*)0,_("Sub shell started")); + move(LINES-1,0); + curs_set(1); + refresh(); + reset_shell_mode(); + puts("\n"); + interrupt.sa_handler=SIG_DFL; + sigaction(SIGINT,&interrupt,(struct sigaction *)0); + sigaction(SIGQUIT,&interrupt,(struct sigaction *)0); + execl(shell,shell,(const char*)0); + exit(127); + break; + } + + /* default */ + default: + { + pid_t r; + int status; + + while ((r=wait(&status))!=-1 && r!=pid); + reset_prog_mode(); + interrupt.sa_handler=SIG_DFL; + sigaction(SIGINT,&interrupt,(struct sigaction *)0); + sigaction(SIGQUIT,&interrupt,(struct sigaction *)0); + clear(); + refresh(); + curs_set(0); + redraw(); + } + + } + return -1; +} + + +/* do_block -- block menu */ +static int do_block(Sheet *cursheet) +{ + MenuChoice block[9]; + int c; + + block[0].str=mystrmalloc(_("ecle)ar")); block[0].c='\0'; + block[1].str=mystrmalloc(_("iI)nsert")); block[1].c='\0'; + block[2].str=mystrmalloc(_("dD)elete")); block[2].c='\0'; + block[3].str=mystrmalloc(_("mM)ove")); block[3].c='\0'; + block[4].str=mystrmalloc(_("cC)opy")); block[4].c='\0'; + block[5].str=mystrmalloc(_("fF)ill")); block[5].c='\0'; + block[6].str=mystrmalloc(_("sS)ort")); block[6].c='\0'; + block[7].str=mystrmalloc(_("rMir)ror")); block[7].c='\0'; + block[8].str=(char*)0; + c=0; + do + { + switch (c=line_menu(_("Block menu:"),block,0)) + { + case -2: + case -1: c = KEY_CANCEL; break; + case 0: c = BLOCK_CLEAR; break; + case 1: c = BLOCK_INSERT; break; + case 2: c = BLOCK_DELETE; break; + case 3: c = BLOCK_MOVE; break; + case 4: c = BLOCK_COPY; break; + case 5: c = BLOCK_FILL; break; + case 6: c = BLOCK_SORT; break; + case 7: c = BLOCK_MIRROR; break; + } + } while (c == K_INVALID); + if (c == KEY_CANCEL) c = K_INVALID; + free(block[0].str); + free(block[1].str); + free(block[2].str); + free(block[3].str); + free(block[4].str); + free(block[5].str); + free(block[6].str); + free(block[7].str); + return c; +} + + +int show_menu(Sheet *cursheet) +{ + + MenuChoice menu[9]; + int c = K_INVALID; + + + menu[0].str=mystrmalloc(_("aA)ttributes")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("wW)idth")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("bB)lock")); menu[2].c='\0'; + menu[3].str=mystrmalloc(_("fF)ile")); menu[3].c='\0'; + menu[4].str=mystrmalloc(_("gG)oto")); menu[4].c='\0'; + menu[5].str=mystrmalloc(_("sS)hell")); menu[5].c='\0'; + menu[6].str=mystrmalloc(_("vV)ersion")); menu[6].c='\0'; + menu[7].str=mystrmalloc(_("qQ)uit")); menu[7].c='\0'; + menu[8].str=(char*)0; + + do + { + switch (c=line_menu(_("Main menu:"),menu,0)) + { + case -2: + case -1: c = KEY_CANCEL; break; + case 0: c = do_attribute(cursheet); break; + case 1: c = K_COLWIDTH; break; + case 2: c = do_block(cursheet); break; + case 3: c = do_file(cursheet); break; + case 4: c = K_GOTO; break; + case 5: do_shell(); c = KEY_CANCEL; break; + case 6: c = K_ABOUT; break; + case 7: c = K_QUIT; break; + default: assert(0); + } + } while (c == K_INVALID); + if (c == KEY_CANCEL) c = K_INVALID; + + free(menu[0].str); + free(menu[1].str); + free(menu[2].str); + free(menu[3].str); + free(menu[4].str); + free(menu[5].str); + free(menu[6].str); + free(menu[7].str); + + return c; +} + +/* do_bg -- background teapot */ +static void do_bg(void) +{ + struct termios t; + + if (tcgetattr(0,&t)==0 && t.c_cc[VSUSP]!=_POSIX_VDISABLE) + { + line_msg((const char*)0,_("Teapot stopped")); + move(LINES-1,0); + curs_set(1); + refresh(); + reset_shell_mode(); + puts("\n"); + kill(getpid(),SIGSTOP); + clear(); + refresh(); + reset_prog_mode(); + curs_set(0); + } + else line_msg((const char*)0,_("The susp character is undefined")); +} + + + +void display_main(Sheet *cursheet) +{ + Key k; + int quit = 0; + + cursheet->maxx=COLS; + cursheet->maxy=LINES-1; + + do + { + quit = 0; + redraw_sheet(cursheet); + k=wgetc(); + wmove(stdscr,LINES-1,0); + wclrtoeol(stdscr); + switch ((int)k) + { + case KEY_SUSPEND: + case '\032': do_bg(); k = K_INVALID; break; + case '\014': redraw(); k = K_INVALID; break; + case KEY_F(0): + case KEY_F(10): k = show_menu(cursheet); break; + } + } while (k == K_INVALID || !do_sheetcmd(cursheet,k,0) || doanyway(cursheet,_("Sheet modified, leave anyway?"))!=1); +} + +void display_init(Sheet *cursheet, int always_redraw) +{ + initscr(); + curs_set(0); + noecho(); + raw(); + nonl(); + keypad(stdscr,TRUE); + clear(); + refresh(); +#ifdef HAVE_TYPEAHEAD + if (always_redraw) typeahead(-1); +#endif +} + +void display_end(void) +{ + curs_set(1); + echo(); + noraw(); + refresh(); + endwin(); +} + +void redraw_cell(Sheet *sheet, int x, int y, int z) +{ + redraw_sheet(sheet); +} + +/* redraw_sheet -- draw a sheet with cell cursor */ +void redraw_sheet(Sheet *sheet) +{ + + int width,col,x,y,again; + char pbuf[80]; + char *buf=malloc(128); + size_t bufsz=128; + const char *label; + char *err; + char moveonly; + + assert(sheet!=(Sheet*)0); + assert(sheet->curx>=0); + assert(sheet->cury>=0); + assert(sheet->curz>=0); + assert(sheet->offx>=0); + assert(sheet->offy>=0); + + /* correct offsets to keep cursor visible */ + while (shadowed(sheet,sheet->curx,sheet->cury,sheet->curz)) + { + --(sheet->curx); + assert(sheet->curx>=0); + } + if (sheet->cury-sheet->offy>(sheet->maxy-2-header)) sheet->offy=sheet->cury-sheet->maxy+2+header; + if (sheet->curyoffy) sheet->offy=sheet->cury; + if (sheet->curxoffx) sheet->offx=sheet->curx; + do + { + again=0; + for (width=4*header,x=sheet->offx,col=0; width<=sheet->maxx; width+=columnwidth(sheet,x,sheet->curz),++x,++col); + --col; + sheet->width=col; + if (sheet->curx!=sheet->offx) + { + if (col==0) { ++sheet->offx; again=1; } + else if (sheet->curx-sheet->offx>=col) { ++sheet->offx; again=1; } + } + } while (again); + + if (header) { + (void)wattron(stdscr,DEF_NUMBER); + + /* draw x numbers */ + for (width=4; widthmaxx; ++width) mvwaddch(stdscr,0+sheet->oriy,sheet->orix+width,(chtype)(unsigned char)' '); + for (width=4,x=sheet->offx; widthmaxx; width+=col,++x) + { + col=columnwidth(sheet,x,sheet->curz); + if (bufsz<(size_t)(col*UTF8SZ+1)) buf=realloc(buf,bufsz=(size_t)(col*UTF8SZ+1)); + snprintf(buf,bufsz,"%d",x); + if (mbslen(buf)>col) { + buf[col-1]='$'; + buf[col]='\0'; + } + adjust(CENTER,buf,(size_t)col); + assert(sheet->maxx>=width); + if ((sheet->maxx-width)maxx-width]='\0'; + mvwaddstr(stdscr,sheet->oriy,sheet->orix+width,buf); + } + + /* draw y numbers */ + for (y=1; y<(sheet->maxy-1); ++y) (void)mvwprintw(stdscr,sheet->oriy+y,sheet->orix,"%-4d",y-1+sheet->offy); + + (void)wattroff(stdscr,DEF_NUMBER); + + /* draw z number */ + (void)mvwprintw(stdscr,sheet->oriy,sheet->orix,"%3d",sheet->curz); + } + + /* draw elements */ + for (y=header; ymaxy-1; ++y) for (width=4*header,x=sheet->offx; widthmaxx; width+=columnwidth(sheet,x,sheet->curz),++x) + { + size_t size,realsize,fill,cutoff; + int realx; + + realx=x; + cutoff=0; + if (x==sheet->offx) while (shadowed(sheet,realx,y-header+sheet->offy,sheet->curz)) + { + --realx; + cutoff+=columnwidth(sheet,realx,sheet->curz); + } + if ((size=cellwidth(sheet,realx,y-header+sheet->offy,sheet->curz))) + { + int invert; + + if (bufsz<(size*UTF8SZ+1)) buf=realloc(buf,bufsz=(size*UTF8SZ+1)); + printvalue(buf,(size*UTF8SZ+1),size,quote,getscientific(sheet,realx,y-header+sheet->offy,sheet->curz),getprecision(sheet,realx,y-header+sheet->offy,sheet->curz),sheet,realx,y-header+sheet->offy,sheet->curz); + adjust(getadjust(sheet,realx,y-header+sheet->offy,sheet->curz),buf,size); + assert(size>=cutoff); + if (width+((int)(size-cutoff))>=sheet->maxx) + { + *(buf+cutoff+sheet->maxx-width)='\0'; + realsize=sheet->maxx-width+cutoff; + } + else realsize=size; + invert= + ( + (sheet->mark1x!=-1) && + ((x>=sheet->mark1x && x<=sheet->mark2x) || (x>=sheet->mark2x && x<=sheet->mark1x)) && + ((y-header+sheet->offy>=sheet->mark1y && y-header+sheet->offy<=sheet->mark2y) || (y-header+sheet->offy>=sheet->mark2y && y-header+sheet->offy<=sheet->mark1y)) && + ((sheet->curz>=sheet->mark1z && sheet->curz<=sheet->mark2z) || (sheet->curz>=sheet->mark2z && sheet->curz<=sheet->mark1z)) + ); + if (x==sheet->curx && (y-header+sheet->offy)==sheet->cury) invert=(sheet->marking ? 1 : 1-invert); + if (invert) (void)wattron(stdscr,DEF_CELLCURSOR); + if (isbold(sheet,realx,y-header+sheet->offy,sheet->curz)) wattron(stdscr,A_BOLD); + if (underlined(sheet,realx,y-header+sheet->offy,sheet->curz)) wattron(stdscr,A_UNDERLINE); + (void)mvwaddstr(stdscr,sheet->oriy+y,sheet->orix+width,buf+cutoff); + for (fill=mbslen(buf+cutoff); fillmaxx*UTF8SZ+1)) buf=realloc(buf,bufsz=(sheet->maxx*UTF8SZ+1)); + label=getlabel(sheet,sheet->curx,sheet->cury,sheet->curz); + assert(label!=(const char*)0); + moveonly=sheet->moveonly ? *_("V") : *_("E"); + if (*label=='\0') sprintf(pbuf,"%c @(%d,%d,%d)=",moveonly,sheet->curx,sheet->cury,sheet->curz); + else sprintf(pbuf,"%c @(%s)=",moveonly,label); + (void)strncpy(buf,pbuf,bufsz); + buf[bufsz-1] = 0; + if ((err=geterror(sheet,sheet->curx,sheet->cury,sheet->curz))!=(const char*)0) + { + (void)strncpy(buf, err, bufsz); + free(err); + } + else + { + print(buf+strlen(buf),bufsz-strlen(buf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,getcont(sheet,sheet->curx,sheet->cury,sheet->curz,0)); + if (getcont(sheet,sheet->curx,sheet->cury,sheet->curz,1) && mbslen(buf) < (size_t)(sheet->maxx+1-4)) + { + strcat(buf," -> "); + print(buf+strlen(buf),bufsz-strlen(buf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,getcont(sheet,sheet->curx,sheet->cury,sheet->curz,1)); + } + } + *mbspos(buf, sheet->maxx) = 0; + (void)mvwaddstr(stdscr,sheet->oriy+sheet->maxy-1,sheet->orix,buf); + for (col=mbslen(buf); colmaxx; ++col) (void)waddch(stdscr,' '); +} + + +/* line_file -- line editor function for file name entry */ +const char *line_file(const char *file, const char *pattern, const char *title, int create) +{ + static char buf[PATH_MAX] = ""; + int rc; + size_t dummy1 = 0, dummy2 = 0; + + if (file) strncpy(buf, file, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + rc = line_edit((Sheet*)0, buf, sizeof(buf), title, &dummy1, &dummy2); + if (rc < 0) return NULL; + return buf; +} + + +/* line_edit -- line editor function */ +int line_edit(Sheet *sheet, char *buf, size_t size, const char *prompt, size_t *x, size_t *offx) +{ + size_t promptlen; + char *src, *dest; + int i,mx,my,insert; + chtype c; + + assert(buf!=(char*)0); + assert(prompt!=(char*)0); + assert(x!=(size_t*)0); + assert(offx!=(size_t*)0); + + (void)curs_set(1); + mx=COLS; + my=LINES; + promptlen=mbslen(prompt)+1; + (void)mvwaddstr(stdscr,LINES-1,0,prompt); (void)waddch(stdscr,(chtype)(unsigned char)' '); + insert=1; + + do { + /* correct offx to cursor stays visible */ + if (*x<*offx) *offx=*x; + if ((*x-*offx)>(mx-promptlen-1)) *offx=*x-mx+promptlen+1; + + /* display buffer */ + (void)wmove(stdscr,LINES-1,(int)promptlen); + src = mbspos(buf, *offx); + dest = mbspos(buf, *offx+COLS-promptlen); + for (; *src && src < dest; src++) (void)waddch(stdscr,(chtype)(unsigned char)(*src)); + if (i!=mx) (void)wclrtoeol(stdscr); + + /* show cursor */ + (void)wmove(stdscr,LINES-1,(int)(*x-*offx+promptlen)); + + src = dest = mbspos(buf, *x); + c=wgetc(); + if (sheet!=(Sheet*)0 && sheet->moveonly) switch (c) { + /* ^o -- switch back to line editor */ + case '\t': + case '\017': sheet->moveonly=0; break; + + /* v -- insert value of current cell */ + case 'v': { + char valbuf[1024]; + + printvalue(valbuf,sizeof(valbuf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,sheet,sheet->curx,sheet->cury,sheet->curz); + if (strlen(buf)+strlen(valbuf) >= (size-1)) break; + (void)memmove(src+strlen(valbuf), src, strlen(src)); + (void)memcpy(src, valbuf, strlen(valbuf)); + (*x) += mbslen(valbuf); + break; + } + + /* p -- insert position of current cell */ + case 'p': { + char valbuf[1024]; + + sprintf(valbuf,"(%d,%d,%d)",sheet->curx,sheet->cury,sheet->curz); + if (strlen(buf)+strlen(valbuf) >= (size-1)) break; + (void)memmove(src+strlen(valbuf), src, strlen(src)); + (void)memcpy(src, valbuf, strlen(valbuf)); + (*x) += mbslen(valbuf); + break; + } + + /* default -- move around in sheet */ + default: + (void)do_sheetcmd(sheet,c,1); + redraw_sheet(sheet); + break; + + } else switch (c) { + /* UP */ + case K_UP: break; + + /* LEFT */ + case K_LEFT: if (*x > 0) (*x)--; break; + + /* RIGHT */ + case K_RIGHT: if (*x < mbslen(buf)) (*x)++; break; + + /* BACKSPACE */ + case K_BACKSPACE: + if (*x > 0) { + memmove(mbspos(src, -1), src, strlen(src)+1); + (*x)--; + } + break; + + /* C-i -- file name completion */ + case '\t': + completefile(buf, src, size); + break; + + /* DC */ + case K_DC: + src = mbspos(dest, 1); + if (*x < strlen(buf)) memmove(dest, src, strlen(src)+1); + break; + + /* HOME */ + case K_HOME: + *x = 0; + break; + + /* END */ + case K_END: + *x = mbslen(buf); + break; + + /* IC */ + case KEY_IC: + insert=1-insert; + break; + + /* EIC */ + case KEY_EIC: + insert=0; + break; + + /* control t */ + case '\024': + if (*x > 0) { + char c, *end; + + dest = mbspos(src, -1); + if (*x == mbslen(buf)) { + src = dest; + dest = mbspos(src, -1); + (*x)--; + } + end = mbspos(src, 1); + + while (src != end) { + c = *src; + memmove(dest+1, dest, src-dest); + *dest = c; + src++; + dest++; + } + (*x)++; + } + break; + + /* control backslash */ + case '\034': { + int level; + char open = 0, close = 0, dir = 1; + + switch (*dest) { + case ')': dir = -1; + case '(': open = '('; close = ')'; break; + case '}': dir = -1; + case '{': open = '{'; close = '}'; break; + case ']': dir = -1; + case '[': open = '['; close = ']'; break; + default: break; + } + + level = dir; + while (*dest && level) { + dest += dir; + if (*dest == open) level--; + else if (*dest == close) level++; + } + if (!level) *x = mbslen(buf)-mbslen(dest); + break; + } + + /* DL */ + case KEY_DL: + *src = '\0'; + break; + + /* control o */ + case '\017': + if (sheet!=(Sheet*)0) sheet->moveonly=1; + break; + + /* default */ + default: + if (((unsigned int)c) < ' ' || ((unsigned int)c) >= 256) break; + if (strlen(buf) >= (size-1)) { + if (is_mbcont(c)) { + dest = mbspos(src, -1); + memmove(dest, src, strlen(src)+1); + } + break; + } + if (insert || is_mbcont(c)) memmove(src+1, src, strlen(src)+1); + else { + if (is_mbchar(*src)) memmove(src+1, mbspos(src, 1), strlen(mbspos(src, 1))+1); + if (!*src) *(src+1) = '\0'; + } + *src = (char)c; + if (!is_mbcont(c)) (*x)++; + break; + } + + } while (c != K_ENTER && c != KEY_CANCEL && (c != K_UP || (sheet!=(Sheet*)0 && sheet->moveonly))); + + if (sheet) sheet->moveonly=0; + (void)curs_set(0); + (void)wmove(stdscr,LINES-1,0); + (void)wclrtoeol(stdscr); + + switch (c) { + case KEY_CANCEL: return -1; + case K_UP: return -2; + default: return 0; + } +} + +/* line_ok -- one line yes/no menu */ +int line_ok(const char *prompt, int curx) +{ + + MenuChoice menu[3]; + int result; + + + + assert(curx==0 || curx==1); + + menu[0].str=mystrmalloc(_("nN)o")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("yY)es")); menu[1].c='\0'; + menu[2].str=(char*)0; + result=line_menu(prompt,menu,curx); + free(menu[0].str); + free(menu[1].str); + return (result); +} + +/* line_menu -- one line menu */ +/* Notes */ +/* + +The choices are terminated by the last element having a (const char*)str +field. Each item can be chosen by tolower(*str) or by the key stored in +the c field. Use a space as first character of str, if you only want the +function key to work. + +*/ + +int line_menu(const char *prompt, const MenuChoice *choice, int curx) +{ + + int maxx,x,width,offx; + chtype c; + size_t promptlen; + + + + assert(prompt!=(const char*)0); + assert(choice!=(const MenuChoice*)0); + assert(curx>=0); + + mvwaddstr(stdscr,LINES-1,0,prompt); + promptlen = mbslen(prompt); + for (maxx=0; (choice+maxx)->str!=(const char*)0; ++maxx); + offx=0; + do + { + (void)wmove(stdscr,LINES-1,(int)promptlen); + /* correct offset so choice is visible */ + if (curx<=offx) offx=curx; + else do + { + for (width=promptlen,x=offx; xstr+1))+1<=COLS; width+=((int)(mbslen((choice+x)->str)))+1,++x); + --x; + if (xstr+1))+1<=COLS; width+=mbslen((choice+x)->str+1)+1,++x) + { + (void)waddch(stdscr,(chtype)(unsigned char)' '); + if (x==curx) (void)wattron(stdscr,DEF_MENU); + (void)waddstr(stdscr,(char*)(choice+x)->str+1); + if (x==curx) (void)wattroff(stdscr,DEF_MENU); + } + + if (width0) --curx; else curx=maxx-1; break; + + /* Space, Tab, KEY_RIGHT -- move to next item */ + case ' ': + case '\t': + case K_RIGHT: if (curx<(maxx-1)) ++curx; else curx=0; break; + + /* default -- search choice keys */ + default: + { + int i; + + for (i=0; (choice+i)->str!=(const char*)0; ++i) + { + if ((c<256 && tolower(c)==*((choice+i)->str)) || (choice+i)->c==c) + { + c=K_ENTER; + curx=i; + } + } + } + + } + } + while (c!=K_ENTER && c!=K_DOWN && c!=KEY_CANCEL && c!=K_UP); + (void)wmove(stdscr,LINES-1,0); + (void)wclrtoeol(stdscr); + switch (c) + { + case KEY_CANCEL: return -1; + case K_UP: return -2; + default: return curx; + } +} + +/* line_msg -- one line message which will be cleared by someone else */ +void line_msg(const char *prompt, const char *msg) +{ + + int width; + + + + assert(msg!=(const char*)0); + if (!*msg) msg = _("Use F0, F10 or / for menu"); + + if (!batch) + { + width=1; + mvwaddch(stdscr,LINES-1,0,(chtype)(unsigned char)'['); + if (prompt!=(const char*)0) + { + for (; width -- FPAGE */ + case KEY_NPAGE: + case '>': return K_FPAGE; + + /* C-x C-c -- QUIT */ + case '\03': return K_QUIT; + + /* C-x C-s -- SAVE */ + case '\023': return K_SAVE; + + /* C-x C-r -- LOAD */ + case '\022': return K_LOAD; + + /* default -- INVALID, general invalid value */ + default: return K_INVALID; + + } + } + + /* ESC, get one more key */ + case '\033': + { + switch (wgetch(stdscr)) + { + /* M-v -- PPAGE */ + case 'v': return K_PPAGE; + + /* M-Enter -- MENTER */ + case KEY_ENTER: + case '\r': + case '\n': return K_MENTER; + + /* M-z -- SAVEQUIT */ + case 'z': return K_SAVEQUIT; + + /* default -- INVALID, general invalid value */ + default: return K_INVALID; + + } + } + + /* _("Load sheet file format:") */ + case KEY_F(2): return K_LOADMENU; + + /* _("Save sheet file format:") */ + case KEY_F(3): return K_SAVEMENU; + + /* default */ + default: return c; + + } +} + + +void find_helpfile(char *buf, int size, const char *argv0) +{ + strncpy(buf, HELPFILE, size); + buf[size-1] = 0; +} diff --git a/display.h b/display.h new file mode 100644 index 0000000..96ff19b --- /dev/null +++ b/display.h @@ -0,0 +1,38 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include "main.h" +#include "sheet.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + Key c; + char *str; +} MenuChoice; + +void display_main(Sheet *cursheet); +void display_init(Sheet *cursheet, int always_redraw); +void display_end(void); +void redraw_cell(Sheet *sheet, int x, int y, int z); +void redraw_sheet(Sheet *sheet); +const char *line_file(const char *file, const char *pattern, const char *title, int create); +int line_edit(Sheet *sheet, char *buf, size_t size, const char *prompt, size_t *x, size_t *offx); +int line_ok(const char *prompt, int curx); +void line_msg(const char *prompt, const char *msg); +int keypressed(void); +void show_text(const char *text); + +Key show_menu(Sheet *cursheet); +int line_menu(const char *prompt, const MenuChoice *choice, int curx); +void find_helpfile(char *buf, int size, const char *argv0); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/doc/.latex2html-init b/doc/.latex2html-init new file mode 100644 index 0000000..7e66514 --- /dev/null +++ b/doc/.latex2html-init @@ -0,0 +1,198 @@ +#LaTeX2HTML Version 96.1 : dot.latex2html-init +# +### Command Line Argument Defaults ####################################### + +$MAX_SPLIT_DEPTH = 5; # Stop making separate files at this depth + +$MAX_LINK_DEPTH = 4; # Stop showing child nodes at this depth + +$NOLATEX = 0; # 1 = do not pass unknown environments to Latex + +$EXTERNAL_IMAGES = 0; # 1 = leave the images outside the document + +$ASCII_MODE = 0; # 1 = do not use any icons or internal images + +$LOCAL_ICONS = 1; + +# 1 = use links to external postscript images rather than inlined bitmap +# images. +$PS_IMAGES = 0; + +$TITLE = "fteapot manual"; # The default is "No Title" + +$DESTDIR = 'html'; # Put the result in this directory + +# When this is set, the generated HTML files will be placed in the +# current directory. If set to 0 the default behaviour is to create (or reuse) +# another file directory. +$NO_SUBDIR = 0; + + +# Supply your own string if you don't like the default +$ADDRESS = ""; + +$NO_NAVIGATION = 0; # 1 = do not put a navigation panel at the top of each page + +# Put navigation links at the top of each page. If the page exceeds +# $WORDS_IN_PAGE number of words then put one at the bottom of the page. +$AUTO_NAVIGATION = 1; + +# Put a link to the index page in the navigation panel +$INDEX_IN_NAVIGATION = 0; + +# Put a link to the table of contents in the navigation panel +$CONTENTS_IN_NAVIGATION = 1; + +# Put a link to the next logical page in the navigation panel +$NEXT_PAGE_IN_NAVIGATION = 1; + +# Put a link to the previous logical page in the navigation panel +$PREVIOUS_PAGE_IN_NAVIGATION = 1; + +$INFO = 1; # 0 = do not make a "About this document..." section + +# Reuse images generated during previous runs +$REUSE = 2; + +# When this is 1, the section numbers are shown. The section numbers should +# then match those that would have bee produced by LaTeX. +# The correct section numbers are obtained from the $FILE.aux file generated +# by LaTeX. +# Hiding the seciton numbers encourages use of particular sections +# as standalone documents. In this case the cross reference to a section +# is shown using the default symbol rather than the section number. +$SHOW_SECTION_NUMBERS = 0; + +### Other global variables ############################################### +$CHILDLINE = "

\n"; + +# This is the line width measured in pixels and it is used to right justify +# equations and equation arrays; +$LINE_WIDTH = 500; + +# Used in conjunction with AUTO_NAVIGATION +$WORDS_IN_PAGE = 300; + +# Affects ONLY the way accents are processed +$default_language = 'english'; + +# The value of this variable determines how many words to use in each +# title that is added to the navigation panel (see below) +# +$WORDS_IN_NAVIGATION_PANEL_TITLES = 5; + +# This number will determine the size of the equations, special characters, +# and anything which will be converted into an inlined image +# *except* "image generating environments" such as "figure", "table" +# or "minipage". +# Effective values are those greater than 0. +# Sensible values are between 0.1 - 4. +$MATH_SCALE_FACTOR = 2; + +# This number will determine the size of +# image generating environments such as "figure", "table" or "minipage". +# Effective values are those greater than 0. +# Sensible values are between 0.1 - 4. +$FIGURE_SCALE_FACTOR = 2; + + +# If this is set then intermediate files are left for later inspection. +# This includes $$_images.tex and $$_images.log created during image +# conversion. +# Caution: Intermediate files can be *enormous*. +$DEBUG = 0; + +# If both of the following two variables are set then the "Up" button +# of the navigation panel in the first node/page of a converted document +# will point to $EXTERNAL_UP_LINK. $EXTERNAL_UP_TITLE should be set +# to some text which describes this external link. +$EXTERNAL_UP_LINK = ""; +$EXTERNAL_UP_TITLE = ""; + +# If this is set then the resulting HTML will look marginally better if viewed +# with Netscape. +$NETSCAPE_HTML = 0; + +# Valid paper sizes are "letter", "legal", "a4","a3","a2" and "a0" +# Paper sizes has no effect other than in the time it takes to create inlined +# images and in whether large images can be created at all ie +# - larger paper sizes *MAY* help with large image problems +# - smaller paper sizes are quicker to handle +$PAPERSIZE = "a5"; + +# Replace "english" with another language in order to tell LaTeX2HTML that you +# want some generated section titles (eg "Table of Contents" or "References") +# to appear in a different language. Currently only "english" and "french" +# is supported but it is very easy to add your own. See the example in the +# file "latex2html.config" +$TITLES_LANGUAGE = "english"; + +### Navigation Panel ########################################################## +# +# The navigation panel is constructed out of buttons and section titles. +# These can be configured in any combination with arbitrary text and +# HTML tags interspersed between them. +# The buttons available are: +# $PREVIOUS - points to the previous section +# $UP - points up to the "parent" section +# $NEXT - points to the next section +# $NEXT_GROUP - points to the next "group" section +# $PREVIOUS_GROUP - points to the previous "group" section +# $CONTENTS - points to the contents page if there is one +# $INDEX - points to the index page if there is one +# +# If the corresponding section exists the button will contain an +# active link to that section. If the corresponding section does +# not exist the button will be inactive. +# +# Also for each of the $PREVIOUS $UP $NEXT $NEXT_GROUP and $PREVIOUS_GROUP +# buttons there are equivalent $PREVIOUS_TITLE, $UP_TITLE, etc variables +# which contain the titles of their corresponding sections. +# Each title is empty if there is no corresponding section. +# +# The subroutine below constructs the navigation panels in each page. +# Feel free to mix and match buttons, titles, your own text, your logos, +# and arbitrary HTML (the "." is the Perl concatenation operator). +sub top_navigation_panel { + + # Now add a few buttons with a space between them + "  $PREVIOUS   $UP   $NEXT   -   $CONTENTS   $INDEX   $CUSTOM_BUTTONS" . + + #"
\n" . # Line break + + # ... and the ``previous'' title + #($PREVIOUS_TITLE ? "<< $PREVIOUS_TITLE\n" : undef) . + + # Similarly with the ``up'' title ... + #($UP_TITLE ? "^ $UP_TITLE ^\n" : undef) . + + # If ``next'' section exists, add its title to the navigation panel + #($NEXT_TITLE ? "$NEXT_TITLE >>\n" : undef) + + # Line Break, horizontal rule (3-d dividing line) and new paragraph + #"
\n" + "" +} + +sub bot_navigation_panel { + + # Start with a horizontal rule (3-d dividing line) + #"
". + + # Now add a few buttons with a space between them + "  $PREVIOUS   $UP   $NEXT   -   $CONTENTS   $INDEX   $CUSTOM_BUTTONS" . + + "
\n" . # Line break + + # ... and the ``previous'' title + ($PREVIOUS_TITLE ? "<< $PREVIOUS_TITLE\n" : undef) . + + # Similarly with the ``up'' title ... + ($UP_TITLE ? "^ $UP_TITLE ^\n" : undef) . + + # If ``next'' section exists, add its title to the navigation panel + ($NEXT_TITLE ? "$NEXT_TITLE >>\n" : undef) + +} + +1; # This must be the last line diff --git a/doc/Ein neues Leben für Teapot.mbox b/doc/Ein neues Leben für Teapot.mbox new file mode 100644 index 0000000..882fa92 --- /dev/null +++ b/doc/Ein neues Leben für Teapot.mbox @@ -0,0 +1,43 @@ +From michael@moria.de Mon Jan 26 11:41:49 2009 +Return-path: +Envelope-to: info@syntax-k.de +Delivery-date: Mon, 26 Jan 2009 11:42:30 +0100 +Received: from [194.97.106.210] (helo=fangorn.moria.de) + by mercenary.garni.ch with esmtps (TLS-1.0:DHE_RSA_AES_256_CBC_SHA1:32) + (Exim 4.67) + (envelope-from ) + id 1LROv8-0007ub-Oi + for info@syntax-k.de; Mon, 26 Jan 2009 11:42:30 +0100 +Received: from michael by fangorn.moria.de with local (ID michael) (Exim 4.69 #20) + id 1LROub-0002Gz-Ge + for info@syntax-k.de; Mon, 26 Jan 2009 11:41:49 +0100 +Date: Mon, 26 Jan 2009 11:41:49 +0100 +To: info@syntax-k.de +Subject: Re: Ein neues Leben =?iso-8859-1?Q?f=FCr?= Teapot +References: <200901260214.44366.info@syntax-k.de> +In-Reply-To: <200901260214.44366.info@syntax-k.de> +User-Agent: Heirloom mailx 12.1 6/15/06 +MIME-Version: 1.0 +Content-Type: text/plain; + charset=iso-8859-1 +Content-Transfer-Encoding: 8bit +Message-Id: +From: Michael Haardt +X-Spam-Level: -- +X-Spam-Status: NO, score=-2.5, required=7.0 +X-Antivirus-Scanner: Clean mail. You should still use a local antivirus program. +X-Length: 1754 +X-UID: 1 + +> Ich war auf der Suche nach einer einfachen Tabellenkalkulation, die +> universell (also auch embedded, per SSH usw.) eingesetzt werden kann. +> Entdeckt habe ich Teapot, das dank Curses-UI wahrlich universell ist. Um +> es aber auf grafischen Systemen bequemer benutzen zu können, habe ich +> einfach mal ein GTK+-Frontend dafür geschrieben. + +Klingt ganz nett, aber ich mache nichts mehr an Teapot und GTK ist mir +ein zu veraenderliches Ding, um mich auf fremde Patches zu verlassen. +Magst Du teapot als Projekt uebernehmen? Ich wuerde es unter GPL gerne +an jemand abgeben, der am Code arbeitet. + +Michael diff --git a/doc/contents.png b/doc/contents.png new file mode 100644 index 0000000..fdd415b Binary files /dev/null and b/doc/contents.png differ diff --git a/doc/next.png b/doc/next.png new file mode 100644 index 0000000..cbed900 Binary files /dev/null and b/doc/next.png differ diff --git a/doc/next_g.png b/doc/next_g.png new file mode 100644 index 0000000..3d56307 Binary files /dev/null and b/doc/next_g.png differ diff --git a/doc/prev.png b/doc/prev.png new file mode 100644 index 0000000..96f1708 Binary files /dev/null and b/doc/prev.png differ diff --git a/doc/prev_g.png b/doc/prev_g.png new file mode 100644 index 0000000..6dcfaad Binary files /dev/null and b/doc/prev_g.png differ diff --git a/doc/teapot.lyx b/doc/teapot.lyx new file mode 100644 index 0000000..fc9a9ac --- /dev/null +++ b/doc/teapot.lyx @@ -0,0 +1,6417 @@ +#LyX 1.6.7 created this file. For more info see http://www.lyx.org/ +\lyxformat 345 +\begin_document +\begin_header +\textclass scrartcl +\begin_preamble +\usepackage{tikz} +\usepackage{pgffor} +\end_preamble +\options DIV=11 +\use_default_options false +\language english +\inputencoding auto +\font_roman newcent +\font_sans default +\font_typewriter default +\font_default_family default +\font_sc false +\font_osf false +\font_sf_scale 100 +\font_tt_scale 100 + +\graphics default +\paperfontsize default +\spacing single +\use_hyperref true +\pdf_title "Teapot User Guide" +\pdf_author "Michael Haardt, Jörg Walter" +\pdf_bookmarks true +\pdf_bookmarksnumbered true +\pdf_bookmarksopen true +\pdf_bookmarksopenlevel 2 +\pdf_breaklinks false +\pdf_pdfborder true +\pdf_colorlinks false +\pdf_backref false +\pdf_pdfusetitle true +\papersize default +\use_geometry false +\use_amsmath 1 +\use_esint 1 +\cite_engine basic +\use_bibtopic false +\paperorientation portrait +\secnumdepth 3 +\tocdepth 3 +\paragraph_separation indent +\defskip medskip +\quotes_language english +\papercolumns 1 +\papersides 1 +\paperpagestyle fancy +\tracking_changes false +\output_changes false +\author "" +\author "" +\end_header + +\begin_body + +\begin_layout Title +Teapot User Guide +\end_layout + +\begin_layout Author +Michael Haardt, Jörg Walter +\end_layout + +\begin_layout Date +\begin_inset CommandInset href +LatexCommand href +name " http://www.syntax-k.de/projekte/teapot" +target "http://www.syntax-k.de/projekte/teapot" + +\end_inset + + +\end_layout + +\begin_layout Abstract +For ages, spread sheet programs have been closely associated with financial + calculations done by typical end-users. + But it has shown that there is also hacker's work which can be done with + them, like calculate monitor timings for various resolutions, produce convincin +g time statistics which justify the lack of documentation or the need for + a budget increase to your employer. + This first part of this user guide explains how the various functions of + teapot are used, whereas the second part gives an introduction to spread + sheets and explains the expression evaluator and its functions. +\end_layout + +\begin_layout Standard +\begin_inset CommandInset toc +LatexCommand tableofcontents + +\end_inset + + +\end_layout + +\begin_layout Standard +\begin_inset Newpage newpage +\end_inset + + +\end_layout + +\begin_layout Section +Copyright, Contributors and License +\end_layout + +\begin_layout Standard + +\noun on +teapot +\noun default + (Table Editor And Planner, Or: Teapot), is copyrighted 1995--2006 by Michael + Haardt, and 2009--2010 by Jörg Walter. +\end_layout + +\begin_layout Standard +The implementation of clocked expressions is modelled after the description + of clocked evaluation in the PhD work of Jörg Wittenberger at the University + of Technology in Dresden, Germany. + The trigonometric functions were inspired by Koniorczyk Mátyás. + The context output format was contributed by Marko Schuetz. +\end_layout + +\begin_layout Standard +The (currently unused) message catalogs were contributed by Guido Müsch, + Wim van Dorst, and Volodymyr M. + Lisivka. +\end_layout + +\begin_layout Standard +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 3 of the License, or (at your option) + any later version. + +\end_layout + +\begin_layout Standard +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. + +\end_layout + +\begin_layout Standard +You should have received a copy of the GNU General Public License along + with this program. + If not, see < +\begin_inset CommandInset href +LatexCommand href +target "http://www.gnu.org/licenses/" + +\end_inset + +>. +\end_layout + +\begin_layout Section +Introduction to Spread Sheets +\end_layout + +\begin_layout Subsection +General Introduction +\end_layout + +\begin_layout Standard +A spread sheet consists of cells formed by rows and columns. + Additionally, in many spread sheets you have a third dimension, which you + can imagine as various sheets laying on top of each other. + The third dimension allows you to hide intermediate results, show you additiona +l results you do not want to appear in the +\begin_inset Quotes eld +\end_inset + +official +\begin_inset Quotes erd +\end_inset + + tables, keep sheets per time period (like 12 sheets for each month in a + year) while allowing you to make calculations over the entire time interval + and much more. + Figure +\begin_inset CommandInset ref +LatexCommand ref +reference "fig:Three-Dimensional-Spread-Sheet" + +\end_inset + + shows the three dimensions: +\end_layout + +\begin_layout Standard +\begin_inset Float figure +placement h +wide false +sideways false +status open + +\begin_layout Plain Layout +\align center +\begin_inset ERT +status open + +\begin_layout Plain Layout + +{ +\backslash +color{white} +\backslash +hrule} +\end_layout + +\begin_layout Plain Layout + +\end_layout + +\begin_layout Plain Layout + + +\backslash +begin{tikzpicture}[scale=.1,anchor=west] +\end_layout + +\begin_layout Plain Layout + + +\backslash +foreach +\backslash +z in {0,2,4,6} +\backslash +filldraw[fill=white,ystep=1,xstep=3,shift={(0,0, +\backslash +z)}] (0,0) rectangle (60,10) (0,0) grid (60,10); +\end_layout + +\begin_layout Plain Layout + + +\backslash +draw[->] (70,10,10) -- +(30,0,0) node{x}; +\end_layout + +\begin_layout Plain Layout + + +\backslash +draw[->] (70,10,10) -- +(0,-10,0) node{y}; +\end_layout + +\begin_layout Plain Layout + + +\backslash +draw[->] (70,10,10) -- +(0,0,-10) node{z}; +\end_layout + +\begin_layout Plain Layout + + +\backslash +end{tikzpicture} +\end_layout + +\begin_layout Plain Layout + +{ +\backslash +color{white} +\backslash +hrule} +\end_layout + +\end_inset + + +\end_layout + +\begin_layout Plain Layout +\begin_inset Caption + +\begin_layout Plain Layout +\begin_inset CommandInset label +LatexCommand label +name "fig:Three-Dimensional-Spread-Sheet" + +\end_inset + +Three-Dimensional Spread Sheet Layout +\end_layout + +\end_inset + + +\end_layout + +\end_inset + + +\end_layout + +\begin_layout Standard +You can think of cells as variables, which value is the value of an associated + expression. + The expression may be constant, like 1.23, or it may be a function of other + cell values. + The advantage compared to a programmable calculator is that if you change + a number, you directly see all changes in other cells caused by that. + Often this allows you to get a feeling how much you may change basic sizes + with still getting satisfying results without having to solve the problem + analytically. + +\end_layout + +\begin_layout Standard +Spread sheets offer many editing functions in order to modify, clear, copy + and move cells or blocks of cells. + Besides the usual mathematical functions, there are functions which work + on blocks of cells, like calculating the sum of a block or counting all + non-empty elements. + Further there are functions working on character strings, because most + likely you also want text besides numbers. + The next section will introduce you to some of these by examples. + +\end_layout + +\begin_layout Standard +teapot is a traditional spread sheet and a typical UNIX program, because + it does just one thing: Calculations. + It does not include any graphics functions and never will, but it allows + to export data in many formats, so you can use your favourite graphics + software. + +\end_layout + +\begin_layout Subsection +The First Steps +\end_layout + +\begin_layout Standard +Now that you should have an idea, it is probably a good time to make your + first steps. + This section will show you how to create and save a sheet which contains + two numbers and their sum. + Start the program without any arguments +\begin_inset Foot +status collapsed + +\begin_layout Plain Layout +If you are using the graphical version of teapot, please see section +\begin_inset CommandInset ref +LatexCommand vref +reference "sub:Differences-Between-User" + +\end_inset + +. +\end_layout + +\end_inset + +: +\end_layout + +\begin_layout Quote + +\family typewriter +teapot +\end_layout + +\begin_layout Standard +You see an empty sheet with the cell cursor being at the upper left corner. + Further, the status line tells you that this cell is really empty: +\end_layout + +\begin_layout Quote + +\family typewriter +E @(0,0,0)= +\family default + +\end_layout + +\begin_layout Standard +The +\family typewriter +E +\family default + means that you can edit the sheet. + A +\family typewriter +V +\family default + would mean that you could only view its contents. + The meaning of +\family typewriter +@() +\family default + will be explained soon. + You are now in the command mode of teapot. + Press the +\series bold +Enter +\series default + key to edit this cell. + A complete list of command mode functions will be given later. + A prompt will appear below the status line: +\end_layout + +\begin_layout Quote + +\family typewriter +Cell contents: +\family default + +\series bold +1 +\series default + +\end_layout + +\begin_layout Standard +Now the cell at position 0,0,0 has the integer constant 1. + The status line shows you the cell contents, whereas in the sheet you see + its value. + Since constants are identical with their values, both are 1. + Now move the cell cursor down one row and edit that cell, giving it the + integer constant +\family typewriter +41 +\family default +. + +\end_layout + +\begin_layout Standard +Now that you have two numbers, move the cell cursor to cell 0,2,0 and give + that cell the following contents: +\end_layout + +\begin_layout Quote + +\family typewriter +Cell contents: +\family default + +\series bold +@(0,0,0)+@(0,1,0) +\series default + +\end_layout + +\begin_layout Standard +If you were confused about the difference between contents and value of + a cell, it should become more clear now: The status line shows the contents, + which is the arithmetic expression to calculate the sum of two cells, whereas + in the sheet you see the value of that expression: 42, which was to be + expected. + +\family typewriter +@( +\family default +\shape italic +x +\family typewriter +\shape default +, +\family default +\shape italic +y +\family typewriter +\shape default +, +\family default +\shape italic +z +\family typewriter +\shape default +) +\family default + is a function which takes three coordinates and returns the value of the + cell at the given position. + +\end_layout + +\begin_layout Standard +As you can see, the arithmetic expression is not too readable. + If you would move cells around, it would not even work any more. + For these reasons, you can use symbolic names instead of coordinates, called + labels. + When used in an expression, a label is like a pointer to a cell, its data + type is +\emph on +location +\emph default +. + Move to cell 0,0,0 and use +\series bold +/ +\series default + (slash) in command mode to get into the main menu. + Depending on your screen size, you may not see all of it. + In this case, move the highlighted block right (or left) to scroll through + it and to see all items. + Now change its label attribute: A)ttributes, L)abel: +\end_layout + +\begin_layout Quote + +\family typewriter +Cell label: +\family default + +\series bold +Paper +\end_layout + +\begin_layout Standard +Then go one cell down and change its label to +\series bold +Tapes +\series default +. + After, move again one cell down and change the expression to: +\end_layout + +\begin_layout Quote + +\family typewriter +Cell contents: +\family default + +\series bold +@(Paper)+@(Tapes) +\end_layout + +\begin_layout Standard +As you see, you can call the function +\family typewriter +@ +\family default + with three integer values or with one location value. + Now the expression is more understandable, at least to you. + To someone else, the sheet only contained three numbers, so a little text + should be added. + To accomplish that, a new column needs to be inserted: B)lock, I)insert, + C)olumn, W)hole column. + The last menu item means that you want to insert a whole new column, not + only a partial column. + If you move the cursor around, you will see that everything is still fine, + because you used labels. + Go to cell 0,0,0 and edit it: +\end_layout + +\begin_layout Quote + +\family typewriter +Cell contents: +\family default + +\series bold +"Paper:" +\end_layout + +\begin_layout Standard +This is how you enter strings. + A string is a data type on its own, don't confuse this with labels. + If you feel like it, leave the quotes and the colon away, and you will + see the difference, because the result will not be a string, but the value + of the label +\family typewriter +Paper +\family default +, which is +\family typewriter +&(1,0,0) +\family default +. + Now change the cells below to +\series bold +"Tapes:" +\series default + and +\series bold +"Result:" +\series default +. + This is something that is understandable to others, too. + +\end_layout + +\begin_layout Standard +As the last step, save your work sheet to a file: F)ile, S)ave. + The native file format is XDR, so choose that. + Up to now, your sheet does not have a name, so you will be prompted for + one: +\end_layout + +\begin_layout Quote + +\family typewriter +New file name: +\family default + +\series bold +firststep +\end_layout + +\begin_layout Standard +Unless you see an error message after, your sheet is written to a file. + +\end_layout + +\begin_layout Standard +If you have come this far, quit (from the main menu) and you have successfully + completed your first steps on using teapot. + Now you know cells, the difference between contents and values, you learned + that labels are a good thing and you can do simple cell modifications as + well as saving your work. + This is enough for most applications. + If the capabilities described in the next section confuse you, then it + is unlikely that you need them really. + Just skip that section and don't worry about it. + +\end_layout + +\begin_layout Standard +You may wonder what happens if you have circular dependencies, i.e. + you have a cell which evaluates to its own value plus one. + Well, the answer is that it depends on the order in which you create this + cell. + If you first give it the value 1 and after edit it to contain the expression + which refers to itself plus 1, then you will find that each recalculation, + like after editing other cells, will increase the value. + While this may be funny, it is certainly not useful as you can not reset + the cell and you have little control of its development. + +\end_layout + +\begin_layout Standard +What you really want is a base value and an iterative expression along with + a way to control the recalculations. + teapot supports this by allowing two expressions per cell. + The expressions you have used so far are the ones which evaluate to the + base values. + Each time you edit a cell, the whole sheet will be reset, which means that + all results are recalculated using the base values. + After, you can clock the sheet, which is why the iterative part is also + called clocked expression. + A clock is an atomic operation, which means that all cell results will + be recalculated in a way that the new result will only show after the entire + recalculation. + +\end_layout + +\begin_layout Standard +An examples will demonstrate how to make use of this feature. + The notation +\shape italic +x +\shape default + -> +\shape italic +y +\shape default + means that +\shape italic +x +\shape default + is the base expression and +\shape italic +y +\shape default + is the clocked expression. + Don't let this confuse you, as both are entered separately: teapot does + not have an -> operator, but it displays the cell contents this way for + increased overview. + So, give the cell a base expression of +\family typewriter +1 +\family default + and a clocked expression of +\family typewriter +@(0,0,0)+1 +\family default + (using +\series bold +ESC-Enter +\series default + or +\series bold +Meta-Enter +\series default +) and you will see: +\end_layout + +\begin_layout Quote + +\family typewriter +@(0,0,0)=1 -> @(0,0,0)+1 +\end_layout + +\begin_layout Standard +The sheet is currently in reset condition and the result is 1. + Now clock it and you will see how the value increases. + +\end_layout + +\begin_layout Standard +After this introductional chapter, you should be familiar with the basic + concepts in spread sheets. + The next chapters explain all functions available in detail. + You should read them to get an overview of the possibilities offered by + teapot. + Finally, we will come back to using teapot by showing some common problems + and their solutions. + +\end_layout + +\begin_layout Subsection +\begin_inset CommandInset label +LatexCommand label +name "sub:Differences-Between-User" + +\end_inset + +Differences Between User Interfaces +\end_layout + +\begin_layout Standard + +\noun on +teapot +\noun default + comes in two flavours: A mouse-and-keyboard operated graphical application + and a traditional console-based program. + Large parts of this manual were written when the GUI version didn't exist, + so there may be occasional inconsistencies. +\end_layout + +\begin_layout Standard +Most notably, a few key bindings don't exist. + If something doesn't work as described in here, refer to the pull-down + menus, where all functionality can be found. + In addition to the common keys, the GUI variant has extended mouse and + keyboard bindings that work similarly to other GUI applications. + There are not yet documented, but should +\begin_inset Quotes eld +\end_inset + +just work +\begin_inset Quotes erd +\end_inset + + as expected. +\end_layout + +\begin_layout Section +Command Mode +\end_layout + +\begin_layout Standard +Right after starting teapot, you are in the command mode. + Many functions from the command mode are also available from menus, but + using keys is faster and some things, like moving the cell cursor, are + only available through keys. + Table +\begin_inset CommandInset ref +LatexCommand vref +reference "tab:Key-Bindings-in" + +\end_inset + + lists all available key bindings +\begin_inset Foot +status collapsed + +\begin_layout Plain Layout +If you are using the graphical version of teapot, please see section +\begin_inset CommandInset ref +LatexCommand vref +reference "sub:Differences-Between-User" + +\end_inset + +. +\end_layout + +\end_inset + +. +\end_layout + +\begin_layout Standard +\begin_inset Float table +wide false +sideways false +status open + +\begin_layout Plain Layout +\begin_inset Tabular + + + + + + + +\begin_inset Text + +\begin_layout Plain Layout +Function Key +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +ASCII Key +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Function +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Next Line +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-N +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor down +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Previous Line +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-P +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor up +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Begin +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-A +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to column 0 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +End +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-E +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to last column +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout ++ +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to next layer +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +- (Dash) +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to previous layer +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +< +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to line 0 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +> +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to last line +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +_ (Underscore) +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to layer 0 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +* +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Cursor to last layer +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-X < +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +One page left +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-X > +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +One page right +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +F10 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +/ +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Main menu +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +F2 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Save menu +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +F3 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Load menu +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-X Ctrl-R +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Load file +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Enter +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-J, Ctrl-M +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Edit cell contents +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +\begin_inset Quotes eld +\end_inset + +, @, +\emph on +digit +\emph default +, +\emph on +letter +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Overwrite cell contents +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Meta-Enter +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Esc Ctrl-J, Esc Ctrl-M +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Edit clocked cell contents +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Backspace +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-H +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Edit cell contents +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +. + (Period) +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Mark block +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-L +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Redraw screen +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-Y +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Paste block +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-R +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Reset sheet +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +F9 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-S +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Clock sheet +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Esc z +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Save and quit +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-X Ctrl-C +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Quit +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Next Page +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-V +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +One page down +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Previous Page +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Meta-V +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +One page up +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Cancel +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-G, Ctrl-C +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Abort current action +\end_layout + +\end_inset + + + + +\end_inset + + +\end_layout + +\begin_layout Plain Layout +\begin_inset Caption + +\begin_layout Plain Layout +\begin_inset CommandInset label +LatexCommand label +name "tab:Key-Bindings-in" + +\end_inset + +Key Bindings in Command Mode +\end_layout + +\end_inset + + +\end_layout + +\end_inset + + +\end_layout + +\begin_layout Standard +. + (Period) marks blocks: The first time it marks the beginning of a block, + which is then extended by moving the cell cursor. + The next time, it marks the end of the block which lets you move the cell + cursor after without changing the block. + The third time, it removes the block marks again. + +\end_layout + +\begin_layout Section +The Line Editor +\end_layout + +\begin_layout Standard +Many functions in teapot require editing a line of text, e.g. + editing cell contents, typing file names and the line. + Similar to the command mode, all things can be reached by control codes + and most by function keys. + Table +\begin_inset CommandInset ref +LatexCommand vref +reference "tab:Key-Bindings-forLineEditor" + +\end_inset + + lists all available key bindings +\begin_inset Foot +status collapsed + +\begin_layout Plain Layout +These only apply to the console version. + The GUI version has input fields that work like all other input fields. +\end_layout + +\end_inset + +. +\end_layout + +\begin_layout Standard +\begin_inset Float table +wide false +sideways false +status open + +\begin_layout Plain Layout +\begin_inset Tabular + + + + + + + +\begin_inset Text + +\begin_layout Plain Layout +Function Key +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +ASCII Key +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Function +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Previous Character +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-B +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Move cursor left +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Next Character +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-F +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Move cursor right +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Begin +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-A +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Move cursor to column 0 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +End +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-E +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Move cursor to last column +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Enter +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-J, Ctrl-M +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Finish editing +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-L +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Redraw screen +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-T +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Transpose characters +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl- +\backslash + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Go to matching paren +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Cancel +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-G, Ctrl-C +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Abort editing +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Backspace +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-H +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Delete previous character +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Delete +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Ctrl-?, Ctrl-D +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Delete current character +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +Insert +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Toggle insert mode +\end_layout + +\end_inset + + + + +\end_inset + + +\end_layout + +\begin_layout Plain Layout +\begin_inset Caption + +\begin_layout Plain Layout +\begin_inset CommandInset label +LatexCommand label +name "tab:Key-Bindings-forLineEditor" + +\end_inset + +Key Bindings for the line editor +\end_layout + +\end_inset + + +\end_layout + +\end_inset + + +\end_layout + +\begin_layout Standard +Besides the regular line editor functions, you may use Ctrl-O (Tab in the + GUI version) to temporarily leave the editor in order to move around in + the sheet if you are editing cell contents. + Another Ctrl-O (resp. + Tab) brings you back to the line editor. + While moving around in the sheet, you can insert the value (v) or position + (p) at the cursor position in the edited cell. + Clicking on a cell while editing has the same effect as moving to that + cell and pressing (p). +\end_layout + +\begin_layout Standard +Aborting line editing means that you will get right back to command mode, + whatever you started doing will have no effect. + +\end_layout + +\begin_layout Section +Interactive Functions +\end_layout + +\begin_layout Standard +Most actions are available through the menu. + Most of these will be applied to all cells within a block if a block of + cells is marked. +\end_layout + +\begin_layout Subsection +\begin_inset CommandInset label +LatexCommand label +name "sub:Cell-Attributes" + +\end_inset + +Cell Attributes +\end_layout + +\begin_layout Standard +Cells can have several attributes: +\end_layout + +\begin_layout Itemize +A cell label, which is useful because it avoids to directly address cells + by their position. + A cell label must be different from function names. + +\end_layout + +\begin_layout Itemize +The cell adjustment, which determines if the cell value is printed left + adjusted, right adjusted or centered. + +\end_layout + +\begin_layout Itemize +The precision for the output of floating point values. + The default is 2 digits after the dot. + +\end_layout + +\begin_layout Itemize +If floating point numbers should be printed in scientific notation (0.123e1) + or as decimal number (1.23). + It only affects the output, if the cell value is a floating point number. + +\end_layout + +\begin_layout Itemize +If the cell is shadowed by its left neighbour. + This means that the left neighbour cell additionally uses the room of the + shadowed cell. + +\end_layout + +\begin_layout Itemize +If the cell is locked which prevents to accidentally edit or clear it. + Note that block operations override this attribute, because when you deal + with blocks, you usually know what you are doing. + +\end_layout + +\begin_layout Itemize +If special characters for e.g. + roff and LaTeX should be quoted (default) or not. + Not quoting them allows special effects (if you know roff or LaTeX), but + is of course not portable. + +\end_layout + +\begin_layout Subsubsection +Precision +\end_layout + +\begin_layout Standard +The precision only changes what is printed, teapot always uses the maximum + precision for calculations. + It also only affects the output if the cell value is a floating point number. + Entering an empty precision means to set it to the default value. + +\end_layout + +\begin_layout Subsubsection +Exponential / Decimal +\end_layout + +\begin_layout Standard +Forces exponential notation for numbers in a cell. + Decimal mode will prefer plain decimal numbers unless the result is very + big or very small. +\end_layout + +\begin_layout Subsubsection +Adjustment +\end_layout + +\begin_layout Standard +Cells contents can be aligned to the left, right or centered. + By default, text is left adjusted and numbers are right adjusted. +\end_layout + +\begin_layout Subsubsection +Label +\end_layout + +\begin_layout Standard +This function lets you edit the cell label of the current cell. + Further it changes all occurences of it in the cell contents to the new + value, unless you erased the cell label. + If a block has been marked by the time you edit the cell label, all occurences + of the label in contents of cells in that block will be changed. + +\end_layout + +\begin_layout Subsubsection +Lock +\end_layout + +\begin_layout Standard +You can lock cells to protect them from accidental editing. + Note that this protects you from modifying single cells. + If you modify a block of cells which contains locked cells, those will + be modified as well. + This has been done because when using block commands, you usually know + what you are doing. + +\end_layout + +\begin_layout Subsubsection +Ignore +\end_layout + +\begin_layout Standard +Ignored cells will be completely ignored. + They appear as empty cells on screen and during calculations. + This is useful for temporarily disabling parts of your calculation, as + the former content reappears when the ignore is removed again. +\end_layout + +\begin_layout Subsubsection +Transparent +\end_layout + +\begin_layout Standard +Usually, values are quoted as needed so that you get the exact same output + as on screen. + Transparent cells will be exported as-is into display-oriented file formats + (LaTeX, etc.) so that you can embed commands for subsequent processing in + cell values. +\end_layout + +\begin_layout Subsubsection +Shadow +\end_layout + +\begin_layout Standard +Shadowed cells are effectively nonexistant. + Instead, their left neighbour cell extends into the shadowed cell, so that + longer text can be displayed. + You may think of shadowing as a way to get multi-column cells. +\end_layout + +\begin_layout Subsubsection +Column Width +\end_layout + +\begin_layout Standard +The column width only affects the screen display, not the formatting of + the final output (except formatted text files). + It is intended to let you make better usage of the screen for more overview. + If the width is too small to display the cell value, a placeholder will + be displayed. + +\end_layout + +\begin_layout Subsection +Block Functions +\end_layout + +\begin_layout Subsubsection +Copy/Move +\end_layout + +\begin_layout Standard +To copy a block of cells, mark it, then move the cell cursor to where the + upper left corner of the copy should be and issue the copy command. + Moving works similar, just use the move command. + Of course you can mark three-dimensional blocks and copy them anywhere + in the three-dimensional sheet, but doing so requires a good three-dimensional + imagination to get what you want. + +\end_layout + +\begin_layout Subsubsection +Fill +\end_layout + +\begin_layout Standard +To fill a block of cells, first mark a the block it should be filled with. + This may be just one cell! Then move the cell cursor to where the upper + left corner of the block to be filled should be and issue the fill command. + You will be prompted for how often the marked block should be repeated + in each dimension. + For example, you may to repeat a cell 9 times below. + Mark it, then move down one row. + Issue the fill command and answer 1 to the number of column repetitions, + 9 to rows and 1 to layers. + +\end_layout + +\begin_layout Subsubsection +Clear +\end_layout + +\begin_layout Standard +Clearing means to delete the cell contents and set all attributes to the + default value. + If you want to preserve the attributes, just edit the contents of a cell + and delete them. + +\end_layout + +\begin_layout Subsubsection +Insert +\end_layout + +\begin_layout Standard +Since work sheets can be three-dimensional, you can insert cells in all + three dimensions, too. + The inserted cells will be empty and their attributes have the default + values. + Cells will always be moved away from the front upper left corner to make + room for the inserted cells. + If no block is marked, you will be asked if you really only want to insert + a cell of if you want to insert a whole row, line or sheet. + +\end_layout + +\begin_layout Subsubsection +Delete +\end_layout + +\begin_layout Standard +Deleting works contrary to inserting. + The deleted cells will be filled by moving neighbour cells to their positions. + You will be prompted for the direction from where those cells will be taken. + Deleting an entire column column-wise is done by marking the column, use + the delete command and chose X direction. + +\end_layout + +\begin_layout Subsubsection +\begin_inset CommandInset label +LatexCommand label +name "sub:Sort" + +\end_inset + +Sort +\end_layout + +\begin_layout Standard +Marked blocks of cells can be sorted after one or multiple keys, either + column-wise, row-wise or depth-wise. + Sorting a two dimensional block row-wise will sort lines, but if a three + dimensional block is sorted row-wise, then horizontal layers will be sorted. + The sort key is specified as vector which is orthogonal to the sorted elements, + either in ascending or descending order. + The following example illustrates the sort function. + The upper left part of the screen should look like this: +\end_layout + +\begin_layout Standard +\align center +\begin_inset Tabular + + + + + + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +one +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +2 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +two +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +2 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +3 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +three +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +3 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +4 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +four +\end_layout + +\end_inset + + + + +\end_inset + + +\end_layout + +\begin_layout Standard +The box shows you which block to mark. + Now this block should be sorted row-wise, with the sort key being the numbers + in descending order, i.e. + we want the lines being numbered 4,3,2,1. + Go to the block menu, then select sort. + Use +\family typewriter +R)ow +\family default +, because that is how we want to sort this block. + The X position of the sort key vector is 0, because the column 0 contains + the numbers. + The Z position is 0, too, because those numbers are on sheet 0. + Now chose +\family typewriter +D)escending +\family default + as direction. + At this point, you could add a secondary key or decide to sort the block + by the keys entered so far. + Use +\family typewriter +S)ort region +\family default + to sort it. + That's it, the screen should look like this now: +\end_layout + +\begin_layout Standard +\align center +\begin_inset Tabular + + + + + + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +4 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +four +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +3 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +three +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +2 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +2 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +two +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +3 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +one +\end_layout + +\end_inset + + + + +\end_inset + + +\end_layout + +\begin_layout Subsubsection +Mirror +\end_layout + +\begin_layout Standard +Mirroring a marked block of cells can be done in three directions: Left/right, + upside/down and front/back. + +\end_layout + +\begin_layout Subsection +Saving and Loading +\end_layout + +\begin_layout Subsubsection +File names +\end_layout + +\begin_layout Standard +Usually, you want to overwrite the loaded file. + For this reason, the loaded file name is remembered. + If the sheet doesn't have a file name, like after starting an empty sheet, + you will be asked for a name when saving. +\end_layout + +\begin_layout Standard +Occasionally, you may want to rename a sheet, like before making critical + changes or when you load an existing sheet to have a start for making a + new one. + The Save As function allows you to save the file under a new name. +\end_layout + +\begin_layout Subsubsection +File Formats +\end_layout + +\begin_layout Paragraph +XDR (.tp) +\end_layout + +\begin_layout Standard +XDR (eXternal Data Representation) is a standard invented by Sun Microsystems + which defines a canonical way of storing/transporting data on external + media. + Its advantage is that it is widely available and that it defines a portable + floating point number format. + The native teapot file format uses XDR so it is portable across different + machine architectures and operating systems. + The advantage of this over the portable ASCII format is that due to the + (usually) missing conversion calculations any floating point constants + will be saved/loaded exactly without conversion errors. +\end_layout + +\begin_layout Paragraph +ASCII (.tpa) +\end_layout + +\begin_layout Standard +The ASCII file format allows easy generation/modification of saved sheets + by shell scripts. + Due to binary/ASCII conversion, there may be conversion errors in floating + point constants. + The default extension is +\family typewriter +. +\family default +tpa. + +\end_layout + +\begin_layout Paragraph +CSV (.csv) +\end_layout + +\begin_layout Standard +CSV (comma seperated value) files only contain the data, not the expressions + calculating it. + Many spread sheets can generate this file format and many graphics programs + like gnuplot(1) can read it. + The field separator usually is a tab or comma, strings may be enclosed + in double quotes and decimal numbers have a dot to mark the fractional + part. + One popular variation uses semicolons for separating fields and a decimal + comma instead of a decimal point, which teapot tries to autodetect. +\end_layout + +\begin_layout Standard +On load, strings without quotes and with a +\emph on +0x +\emph default + prefix followed by hexadecimal digits will be converted to integers. + When loading CSV files, the sheet will not be cleared and the data will + be load relative to the current cursor position. +\end_layout + +\begin_layout Paragraph +SC SpreadsheetCalculator (.sc) +\end_layout + +\begin_layout Standard +teapot can load simple SC sheets to convert them to teapot's native format. + While loading, teapot converts all references to absolute cell positions + to labels. + This allows to insert and delete in such sheets without screwing the whole + sheet up. + teapot can not save sheets in SC format, because SC lacks many features. + For now, only the most basic SC features are supported. +\end_layout + +\begin_layout Paragraph +Lotus 1-2-3 (.wk1) +\end_layout + +\begin_layout Standard +teapot can load simple WK1 sheets to convert them to teapot's native format. + By default, 1-2-3 cell references are relative, so don't be surprised by + a big amount of relative references in the resulting teapot sheet. + For now, only the most basic 1-2-3 features are supported. +\end_layout + +\begin_layout Paragraph +Formatted ASCII (.txt) +\end_layout + +\begin_layout Standard +The generated formatted ASCII files contain about what you see on the screen. + If your sheet has more than one layer, then the various layers will be + saved separated by form feeds. +\end_layout + +\begin_layout Paragraph +Troff tbl (.tbl) +\end_layout + +\begin_layout Standard +teapot can generate tbl(1) table bodies in single files which are supposed + to be used like this: +\end_layout + +\begin_layout Quote + +\family typewriter +.TS +\begin_inset Newline newline +\end_inset + + +\family default +\shape italic +options +\family typewriter +\shape default +; +\begin_inset Newline newline +\end_inset + +.so +\family default + +\shape italic +filename +\begin_inset Newline newline +\end_inset + + +\family typewriter +\shape default +.TE +\end_layout + +\begin_layout Standard +You will have to use soelim(1) to eliminate the +\family typewriter +.so +\family default + requests before the tbl run. + The +\shape italic +options +\family typewriter +\shape default +; +\family default + are optional. + If you use GNU roff, you will need to eliminate +\family typewriter +.lf +\family default + requests, because this GNU roff extension confuses GNU tbl: +\end_layout + +\begin_layout Quote + +\family typewriter +soelim +\family default + +\shape italic +file +\shape default + +\family typewriter +| grep -v '^ +\backslash +.lf' +\end_layout + +\begin_layout Standard +Alternatively, you can generate a stand-alone document, which needs no further + operations to format and print. + Note: If no block is marked, the whole sheet will be saved. +\end_layout + +\begin_layout Paragraph +LaTeX (.latex) +\end_layout + +\begin_layout Standard +If you generate LaTeX2e tables in single files, you include them in documents + using the +\family typewriter + +\backslash +include +\family default + command. + Alternatively, you can generate a stand-alone document, which needs no + further operations to format and print. + Note: If no block is marked, the whole sheet will be saved. +\end_layout + +\begin_layout Paragraph +ConTeXt (.tex) +\end_layout + +\begin_layout Standard +Analogous to LaTeX output, this generates input suitable to the ConTeXt + macro package. +\end_layout + +\begin_layout Paragraph +HTML (.html) +\end_layout + +\begin_layout Standard +You can generate html table bodies in single files which could be used in + combination with server-side includes. + This feature differs between the various servers, so refer to the manual + for your web server for details, please. + +\end_layout + +\begin_layout Standard +Alternatively, you can generate a stand-alone document. + Note: If no block is marked, the whole sheet will be saved. +\end_layout + +\begin_layout Subsection +Other Functions +\end_layout + +\begin_layout Subsubsection +Goto Location +\end_layout + +\begin_layout Standard +Sometimes, you directly want to go to a specific position, either to change + its contents to see which cell a location expression refers to. + This function lets you enter an expression, which must evaluate to a value + of the type location. + If so, the cursor is positioned to that location. + For example, you could enter +\family typewriter +&(10,2) +\family default + to go to cell 10,2 of the current layer or you could enter the name of + a label you want to go to. + Relative movements are no problem, either. + +\end_layout + +\begin_layout Subsubsection +Shell +\end_layout + +\begin_layout Standard +Start a sub shell. + Exiting from that sub shell will bring you back into teapot. + This function does not exist in the GUI version. +\end_layout + +\begin_layout Subsubsection +Version +\end_layout + +\begin_layout Standard +teapot will display its version number and copyright statement. +\end_layout + +\begin_layout Subsubsection +Help +\end_layout + +\begin_layout Standard +If teapot was built with the integrated help viewer, you can access this + manual from within teapot itself. +\end_layout + +\begin_layout Section +Batch functions +\end_layout + +\begin_layout Standard +Besides interactive facilities, teapot has a batch mode. + Using this batch mode, shell scripts can generate output from teapot sheets. + This is handy if you use make(1) to generate a bigger document containing + tables, because you don't have to generate a tbl or LaTeX file each time + you modified a sheet: make will do so. + In batch mode, teapot reads batch commands from standard input. + The following commands are available: +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +goto +\family default +\shape italic + +\begin_inset space ~ +\end_inset + +location +\series default +\shape default + Go to the specified +\shape italic +location +\shape default +. + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +from +\family default +\shape italic + +\begin_inset space ~ +\end_inset + +location +\series default +\shape default + Start marking a block. + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +to +\family default +\shape italic + +\begin_inset space ~ +\end_inset + +location +\series default +\shape default + End marking a block. + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +sort-x +\family default + +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +y +\begin_inset space ~ +\end_inset + +z +\emph default + +\begin_inset space ~ +\end_inset + +[ +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +y +\begin_inset space ~ +\end_inset + +z +\emph default + +\begin_inset space ~ +\end_inset + +... +\begin_inset space ~ +\end_inset + +] +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +sort- +\family default +y +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +x +\begin_inset space ~ +\end_inset + +z +\emph default + +\begin_inset space ~ +\end_inset + +[ +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +x +\begin_inset space ~ +\end_inset + +z +\emph default + +\begin_inset space ~ +\end_inset + +... +\begin_inset space ~ +\end_inset + +] +\series default + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +sort- +\family default +z +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +x +\begin_inset space ~ +\end_inset + + +\emph default +y +\begin_inset space ~ +\end_inset + +[ +\begin_inset space ~ +\end_inset + +d|a +\emph on + +\begin_inset space ~ +\end_inset + +x +\begin_inset space ~ +\end_inset + + +\emph default +y +\begin_inset space ~ +\end_inset + +... +\begin_inset space ~ +\end_inset + +] +\series default + Sorts the marked block as described in section +\begin_inset CommandInset ref +LatexCommand vref +reference "sub:Sort" + +\end_inset + +, column-wise, row-wise or depth-wise, respectivly. + "d" or "a" specify the sort order to be descending or ascending. + +\emph on +x +\emph default +, +\emph on +y +\emph default + and +\emph on +z +\emph default + specify the position of the sort key +\family typewriter +relative +\family default + to the first cell of the marked block. + Up to eight sort keys can be specified. + This example reproduces the result from section +\begin_inset CommandInset ref +LatexCommand ref +reference "sub:Sort" + +\end_inset + +: +\end_layout + +\begin_deeper +\begin_layout Quote + +\family typewriter +echo " +\begin_inset Newline newline +\end_inset + +from &(1,1,0) +\begin_inset Newline newline +\end_inset + +to &(2,4,0) +\begin_inset Newline newline +\end_inset + +sort-y d 0 0 +\begin_inset Newline newline +\end_inset + +save-csv result_num.txt +\begin_inset Newline newline +\end_inset + +" | teapot -b doc/unsorted +\end_layout + +\end_deeper +\begin_layout Description + +\family typewriter +\series bold +save-tbl +\family default +\emph on + +\begin_inset space ~ +\end_inset + +file +\series default +\emph default + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +save-csv +\family default +\emph on + +\begin_inset space ~ +\end_inset + +file +\series default +\emph default + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +save-latex +\family default +\emph on + +\begin_inset space ~ +\end_inset + +file +\series default +\emph default + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +save-context +\family default +\emph on + +\begin_inset space ~ +\end_inset + +file +\series default +\emph default + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +save-html +\begin_inset space ~ +\end_inset + + +\family default +\emph on +file +\series default +\emph default + Save the marked block in the specified format as +\emph on +file +\emph default +. + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +load-csv +\begin_inset space ~ +\end_inset + + +\family default +\emph on +file +\series default +\emph default + Load +\emph on +file +\emph default + in the specified format to the last +\family typewriter +goto +\family default + location. + This is the same functionality as the interactive load described in subsection + +\emph on +5.12.4 +\emph default +. + +\end_layout + +\begin_layout Section +Expressions +\end_layout + +\begin_layout Standard +Cells consist of a base (reset) expression, a clocked expression, and a + current value. + If the sheet is currently in the reset state (the default), all cells display + their base value as current value. +\end_layout + +\begin_layout Standard +When the sheet is clocked (see Table +\begin_inset CommandInset ref +LatexCommand ref +reference "tab:Key-Bindings-in" + +\end_inset + +), the clocked expression is evaluated, using the current value of referenced + cells. + The new current value is the result of that evaluation. +\end_layout + +\begin_layout Subsection +Data Types +\end_layout + +\begin_layout Standard +In teapot, each value has an associated data type. + The following data types exist: +\end_layout + +\begin_layout Description +Empty Empty cells have 0, 0.0 or +\begin_inset Quotes eld +\end_inset + + +\begin_inset Quotes erd +\end_inset + + as value, depending on context. + +\end_layout + +\begin_layout Description +String A string is a sequence of characters enclosed by double quotes: +\family typewriter +"This is a string" +\family default +. + A double quote can be part of the string, if it is quoted using a backslash: + +\family typewriter + +\begin_inset Quotes eld +\end_inset + + +\backslash + +\family default + +\begin_inset Quotes erd +\end_inset + + +\begin_inset Quotes erd +\end_inset + +. + If you want the backslash to appear in the output instead of quoting the + next character, use it to quote itself: +\family typewriter + +\begin_inset Quotes eld +\end_inset + + +\backslash + +\backslash + +\family default + +\begin_inset Quotes erd +\end_inset + +. +\end_layout + +\begin_layout Description +Floating +\begin_inset space ~ +\end_inset + +Point Floating point values are inexact, their precision and range depends + on the implementation of the C type double on your system. + An example is: +\family typewriter +42.0 +\family default + +\end_layout + +\begin_layout Description +Integer Integer values are exact, their range depends on the C type long + on your system. + An example is: +\family typewriter +42 +\family default + +\end_layout + +\begin_layout Description +Location Cell labels and the +\family typewriter +&() +\family default + function have this type, but there are no location constant literals. +\end_layout + +\begin_layout Description +Error Syntactical or semantical (type mismatch) errors cause this value, + as well as division by 0 and the function +\family typewriter +error() +\family default +. + An error always has an assigned error message. + Functions and operators, when applied to a value of the type error, evaluate + to just that value. + That way, the first error which was found deep inside a complicated expression + will be shown. + +\end_layout + +\begin_layout Subsection +Operators +\end_layout + +\begin_layout Standard +Unlike other spread sheets, the operators in teapot check the type of the + values they are applied to, which means the try to add a string to a floating + point number will result in an type error. + The following operators are available, listed in ascending precendence: + +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +< +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is less than +\emph on +y +\emph default +. + If +\emph on +x +\emph default + or +\emph on +y +\emph default + are empty, they are considered to be 0 if the other is an integer number, + 0.0 if it is a floating point number and the empty string if it is a string. + +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +<= +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is less than or equal to +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +>= +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is greater than or equal to +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +> +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is greater than +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +== +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is equal to +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default += +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if the floating point value +\emph on +x +\emph default + is almost equal to the floating point value +\emph on +y +\emph default +. + Almost equal means, the numbers are at most neighbours. + +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +!= +\family default +\emph on +y +\series default +\emph default + evaluates to 1 if +\emph on +x +\emph default + is not equal to +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default ++ +\family default +\emph on +y +\series default +\emph default + evaluates to the sum if +\emph on +x +\emph default + and +\emph on +y +\emph default + are numbers. + If +\emph on +x +\emph default + and +\emph on +y +\emph default + are strings, the result is the concatenated string. + There is no dedicated logical +\noun on +or +\noun default + operation, so use +\family typewriter ++ +\family default + for that. + +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +- +\family default +\emph on +y +\series default +\emph default + evaluates to the difference if +\emph on +x +\emph default + and +\emph on +y +\emph default + are numbers. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +* +\family default +\emph on +y +\series default +\emph default + evaluates to the product if +\emph on +x +\emph default + and +\emph on +y +\emph default + are numbers. + There is no dedicated logical +\noun on +and +\noun default + operation, so use +\family typewriter +* +\family default + for that. + +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +/ +\family default +\emph on +y +\series default +\emph default + evaluates to the quotient if +\emph on +x +\emph default + and +\emph on +y +\emph default + are numbers. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x +\family typewriter +\emph default +% +\family default +\emph on +y +\series default +\emph default + evaluates to the remainder of the division if +\emph on +x +\emph default + and +\emph on +y +\emph default + are numbers. +\end_layout + +\begin_layout Description + +\series bold +\emph on +x^y +\series default +\emph default + evaluates to +\emph on +x +\emph default + to the power of +\emph on +y +\emph default +. +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +- +\family default +\emph on +x +\series default +\emph default + evaluates to +\family typewriter +- +\family default +\emph on +x +\emph default + if +\emph on +x +\emph default + is a number. + If +\emph on +x +\emph default + is empty, the result will be empty, too. + +\end_layout + +\begin_layout Description + +\family typewriter +\series bold +( +\family default +\emph on +expression +\family typewriter +\emph default +) +\family default +\series default + evaluates to the expression. + +\end_layout + +\begin_layout Description + +\series bold +\emph on +function +\family typewriter +\emph default +( +\family default +\emph on +argument +\family typewriter +\emph default +, +\family default +... +\family typewriter +) +\family default +\series default + evaluates to the value of the function applied to the values resulting + from evaluating the argument expressions. + +\end_layout + +\begin_layout Subsection +Functions +\end_layout + +\begin_layout Standard +This section documents all available functions in alphabetical order. + The functions are given in a C-like notation, so use +\family typewriter +@(0,0,0) +\family default + instead of +\family typewriter +@(integer 0, integer 0, integer 0) +\family default +. + If no type is given for the result of a function, it means the result type + depends on the arguments. + Brackets mark optional arguments. + +\end_layout + +\begin_layout Description +@ +\series medium +([int +\begin_inset space ~ +\end_inset + + +\emph on +x +\emph default +][,[int +\emph on + +\begin_inset space ~ +\end_inset + +y +\emph default +][,[int +\emph on + +\begin_inset space ~ +\end_inset + +z +\emph default +]]]) +\end_layout + +\begin_layout Description +@ +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l +\emph default +) +\series default +returns the value of the cell at position +\emph on +x +\emph default +, +\emph on +y +\emph default +, +\emph on +z +\emph default +. + If any of +\emph on +x +\emph default +, +\emph on +y +\emph default + or +\emph on +z +\emph default + is omitted, the coordinate of the cell is used. + +\end_layout + +\begin_layout Description + +\series medium +location +\begin_inset space ~ +\end_inset + + +\series default +& +\series medium +([int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +][, +\begin_inset space ~ +\end_inset + +[int +\emph on + +\begin_inset space ~ +\end_inset + +y +\emph default +][, +\begin_inset space ~ +\end_inset + +[int +\emph on + +\begin_inset space ~ +\end_inset + +z +\emph default +]]]) +\series default +returns a pointer to the cell at location +\emph on +x +\emph default +, +\emph on +y +\emph default +, +\emph on +z +\emph default +. + If +\emph on +z +\emph default + is omitted, the +\emph on +z +\emph default + position of the current cell is used. + If +\emph on +y +\emph default + is missing as well, the +\emph on +y +\emph default + position (row) of the cell is used. + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +$ +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +env +\emph default +) +\series default +evaluates to the contents of the specified environment variable. + If the variable does not exist, then an empty string will be returned. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +abs +\series medium +(float +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +abs +\series medium +(int +\begin_inset space ~ +\end_inset + + +\emph on +x +\emph default +) +\series default +evaluates to the absolute value of +\emph on +x +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +acos +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default +evaluates to the arc cosine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +arcosh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the arc hyperbolic cosine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +arsinh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the arc hyperbolic sine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +artanh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the arc hyperbolic tangent of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +asin +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the arc sine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +atan +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the arc tangent of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description +clock +\series medium +(integer +\emph on + +\begin_inset space ~ +\end_inset + +condition +\emph default +,[location[,location]) +\series default + conditionally clocks the specified cell if the condition is not 0. + If two locations are given, all cells in that range will be clocked. + The return value of clock is empty. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +cos +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default +evaluates to the cosine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +cosh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the hyperbolic cosine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +deg2rad +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the degrees that are equivalent to +\emph on +x +\emph default + radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +e +\series medium +() +\series default +evaluates to the Euler constant +\emph on +e +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +error +\begin_inset space ~ +\end_inset + + +\series default +error +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +message +\emph default +) +\series default + evaluates to an error with the specified message. + +\end_layout + +\begin_layout Description +eval +\series medium +(location) +\series default + evaluates to the value of the expression in the cell at the given +\emph on +location +\emph default +, but evaluated in the context of the cell using eval(). + This function may not be used nested any deeper than 32 times. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +float +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +s +\emph default +) +\series default + converts the given string into a floating point number. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +frac +\series medium +(float +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the fractional part of +\emph on +x +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +int +\series medium +(float +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +[,integer +\emph on + +\begin_inset space ~ +\end_inset + +neg +\emph default +, +\begin_inset space ~ +\end_inset + +integer +\emph on + +\begin_inset space ~ +\end_inset + +pos +\emph default +]) +\series default +converts +\emph on +x +\emph default + to an integer value by cutting off the fractional part. + If given, the value of +\emph on +neg +\emph default + and +\emph on +pos +\emph default + determines how negative and non-negative numbers will be converted: +\end_layout + +\begin_layout Quote +\begin_inset Tabular + + + + + + +\begin_inset Text + +\begin_layout Plain Layout + +\emph on +neg +\emph default +/ +\emph on +pos +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Result +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +< -1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +next smaller integer value +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +-1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +round downward +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +0 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +cut fractional part off (default) +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +round upward +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +> 1 +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +next larger integer value +\end_layout + +\end_inset + + + + +\end_inset + + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +int +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +s +\emph default +) +\series default + converts +\emph on +s +\emph default + to an integer number. + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +len +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +s +\emph default +) +\series default + evaluates to the length of +\emph on +s +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +log +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +[,float|int +\emph on + +\begin_inset space ~ +\end_inset + +y +\emph default +) +\series default + evaluates to the logarithm of +\emph on +x +\emph default +. + If +\emph on +y +\emph default + is specified, the result will be the natural logarithm, otherwise it will + be the logarithm to the base of +\emph on +y +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +location +\begin_inset space ~ +\end_inset + + +\series default +max +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l1 +\emph default +, +\begin_inset space ~ +\end_inset + +location +\emph on + +\begin_inset space ~ +\end_inset + +l2 +\emph default +) +\series default + evaluates to the maximum in the same way min does for the minimum. + +\end_layout + +\begin_layout Description + +\series medium +location +\begin_inset space ~ +\end_inset + + +\series default +min +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l1 +\emph default +, +\begin_inset space ~ +\end_inset + +location +\emph on + +\begin_inset space ~ +\end_inset + +l2 +\emph default +) +\series default + evaluates to the location of the minimum of all values in the block marked + by the corners pointed to by +\emph on +l1 +\emph default + and +\emph on +l2 +\emph default +. + Note that the empty cell is equal to 0, 0.0 and "", so if the first minimum + is an empty cell, the result will be a pointer to this cell, too. + If you are not interested in the location of the minimum but the value + itself, use @(min( +\emph on +l1 +\emph default +, +\emph on +l2 +\emph default +)). + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +n +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l1 +\emph default +, +\begin_inset space ~ +\end_inset + +location +\emph on + +\begin_inset space ~ +\end_inset + +l2 +\emph default +) +\series default + evaluates to the number of non-empty cells in the block marked by the corners + pointed to by +\emph on +l1 +\emph default + and +\emph on +l2 +\emph default +. + +\end_layout + +\begin_layout Description +poly +\series medium +(float|integer +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +, +\begin_inset space ~ +\end_inset + +float|integer +\emph on + +\begin_inset space ~ +\end_inset + +cn +\emph default +[, +\begin_inset space ~ +\end_inset + +...]) +\series default + evaluates the polynome +\emph on + +\begin_inset Formula $c_{n}\cdot x^{n}+\ldots+c_{0}\cdot x^{0}$ +\end_inset + + +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +rad2deg +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default +evaluates to the radians that are equivalent to +\emph on +x +\emph default + degrees. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +rnd +\series medium +() +\series default + evaluates to a pseudo-random number between 0.0 and 1.0. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +sin +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the sine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +sinh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the hyperbolic sine of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +strftime +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +f +\emph default + +\begin_inset space ~ +\end_inset + +[, +\begin_inset space ~ +\end_inset + +integer +\emph on + +\begin_inset space ~ +\end_inset + +t +\emph default +]) +\series default +evaluates to the time +\emph on +t +\emph default + formatted according to the format specified in +\emph on +f +\emph default +. + Times in +\emph on +t +\emph default + are counted in seconds since epoch (1970-1-1 0:00). + If +\emph on +t +\emph default + is empty or 0, the actual time is used. + For the format specifications consult the man page of your c library, strftime + (3). + Example: +\emph on +@(now)=int(strftime("%s")) +\emph default + sets the field with label now to the actual time. + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +string +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l +\emph default +) +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +string +\series medium +(integer +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +string +\series medium +(float +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +[, +\begin_inset space ~ +\end_inset + +[integer +\emph on + +\begin_inset space ~ +\end_inset + +precision +\emph default +][, +\begin_inset space ~ +\end_inset + +integer +\emph on + +\begin_inset space ~ +\end_inset + +scientific +\emph default +]]) +\series default + evaluates to a string containing the current value of the given cell at + location +\emph on +l +\emph default +, or to the numeric value +\emph on +x +\emph default + with the given +\emph on +precision +\emph default +. + The +\emph on +scientific +\emph default + flag determines if decimal (0) or scientific (unequal 0) representation + is used. + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +strptime +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +f +\emph default +, +\begin_inset space ~ +\end_inset + +string +\emph on + +\begin_inset space ~ +\end_inset + +datetime +\emph default +) +\series default + evaluates to the seconds since epoch (1970-1-1 0:00) of the +\emph on +datetime +\emph default + string, parsed according to the format specified in +\emph on +f +\emph default +. + For the format specifications consult the man page of your c library, strptime + (3). + +\end_layout + +\begin_layout Description + +\series medium +string +\begin_inset space ~ +\end_inset + + +\series default +substr +\series medium +(string +\emph on + +\begin_inset space ~ +\end_inset + +s, +\emph default +integer +\emph on + +\begin_inset space ~ +\end_inset + +x, +\emph default +integer +\emph on + +\begin_inset space ~ +\end_inset + +y +\emph default +) +\series default +evaluates to the substring of +\emph on +s +\emph default + between +\emph on +x +\emph default + and +\emph on +y +\emph default +, which start at 0. + +\end_layout + +\begin_layout Description +sum +\series medium +(location +\emph on + +\begin_inset space ~ +\end_inset + +l1 +\emph default +, +\begin_inset space ~ +\end_inset + +location +\emph on + +\begin_inset space ~ +\end_inset + +l2 +\emph default +) +\series default +evaluates to the sum of all values in the block marked by the corners pointed + to by +\emph on +l1 +\emph default + and +\emph on +l2 +\emph default +. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +tan +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default +evaluates to the tangent of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +float +\begin_inset space ~ +\end_inset + + +\series default +tanh +\series medium +(float|int +\emph on + +\begin_inset space ~ +\end_inset + +x +\emph default +) +\series default + evaluates to the hyperbolic tangent of +\emph on +x +\emph default +, where +\emph on +x +\emph default + is given in radians. + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +x +\series medium +([location +\emph on + +\begin_inset space ~ +\end_inset + +l +\emph default +]) +\series default + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +y +\series medium +([location +\emph on + +\begin_inset space ~ +\end_inset + +l +\emph default +]) +\series default + +\end_layout + +\begin_layout Description + +\series medium +int +\begin_inset space ~ +\end_inset + + +\series default +z +\series medium +([location +\emph on + +\begin_inset space ~ +\end_inset + +l +\emph default +]) +\series default + evaluate to the +\emph on +x +\emph default +, +\emph on +y +\emph default + and +\emph on +z +\emph default + position of the given location, of the currently updated cell if none is + given. + These functions are usually used in combination with the @ function for + relative relations to other cells. + +\end_layout + +\begin_layout Subsection +Expression Grammar +\end_layout + +\begin_layout Description +digit::= +\family typewriter +0 +\family default + | .. + | +\family typewriter +9 +\end_layout + +\begin_layout Description +hex_digit::= +\family typewriter +0 +\family default + | .. + | +\family typewriter +9 +\family default + | +\family typewriter +a +\family default + | .. + | +\family typewriter +f +\end_layout + +\begin_layout Description +octal_digit::= +\family typewriter +0 +\family default + | .. + | +\family typewriter +7 +\end_layout + +\begin_layout Description +decimal_integer::= +\emph on +digit +\emph default + { +\emph on +digit +\emph default + } +\end_layout + +\begin_layout Description +hex_integer::= +\family typewriter +0x +\family default + +\emph on +hex_digit +\emph default + { +\emph on +hexdigit +\emph default + } +\end_layout + +\begin_layout Description +octal_integer::= +\family typewriter +0 +\family default + +\emph on +octal_digit +\emph default + { +\emph on +octdigit +\emph default + } +\end_layout + +\begin_layout Description +integer::= +\emph on +decimal_integer +\emph default + | +\emph on +hex_integer +\emph default + | +\emph on +octal_integer +\end_layout + +\begin_layout Description +float::= +\emph on +digit +\emph default + { +\emph on +digit +\emph default + } [ +\family typewriter +. + +\family default + ] { +\emph on +digit +\emph default + } [ +\family typewriter +e +\family default + | +\family typewriter +E +\family default + [ +\family typewriter ++ +\family default + | +\family typewriter +- +\family default + ] +\emph on +digit +\emph default + { +\emph on +digit +\emph default + } ] +\end_layout + +\begin_layout Description +quoted_character::= +\emph on + +\backslash + any_character +\end_layout + +\begin_layout Description +character::= +\emph on +any_character +\emph default + | +\emph on +quoted_character +\end_layout + +\begin_layout Description +string::= +\family typewriter +" +\family default + { +\emph on +character +\emph default + } +\family typewriter +" +\end_layout + +\begin_layout Description +identifier_character::= +\family typewriter +_ +\family default + | +\family typewriter +@ +\family default + | +\family typewriter +& +\family default + | +\family typewriter +. + +\family default + | +\family typewriter +$ +\family default + | +\emph on +alpha_character +\end_layout + +\begin_layout Description +identifier::= +\emph on +identifier_character +\emph default + { +\emph on +identifier_character +\emph default + | +\emph on +digit +\emph default + } +\end_layout + +\begin_layout Description +function::= +\emph on +identifier +\emph default + +\family typewriter +( +\family default + [ +\emph on +term +\emph default + ] { +\family typewriter +, +\family default + [ +\emph on +term +\emph default + ] } +\family typewriter +) +\end_layout + +\begin_layout Description +label::= +\emph on +identifier +\end_layout + +\begin_layout Description +parenterm::= +\family typewriter +( +\family default + +\emph on +term +\emph default + +\family typewriter +) +\end_layout + +\begin_layout Description +negterm::= +\family typewriter +- +\family default + +\emph on +primary +\end_layout + +\begin_layout Description +primary::= +\emph on +function +\emph default + | +\emph on +label +\emph default + | +\emph on +parenterm +\emph default + | +\emph on +negterm +\end_layout + +\begin_layout Description +powterm::= +\emph on +primary +\emph default + { +\family typewriter +‸ +\family default + +\emph on +primary +\emph default + } +\end_layout + +\begin_layout Description +mathterm::= +\emph on +powterm +\emph default + { +\family typewriter +/ +\family default + | +\family typewriter +* +\family default + | +\family typewriter +% +\family default + +\emph on +powterm +\emph default + } +\end_layout + +\begin_layout Description +factor::= +\emph on +mathterm +\emph default + { +\family typewriter ++ +\family default + | +\family typewriter +- +\family default + +\emph on +mathterm +\emph default + } +\end_layout + +\begin_layout Description +term::= +\emph on +factor +\emph default + { +\family typewriter +< +\family default + | +\family typewriter +<= +\family default + | +\family typewriter +>= +\family default + | +\family typewriter +> +\family default + | +\family typewriter +== +\family default + | +\family typewriter +!= +\family default + +\emph on +factor +\emph default + } +\end_layout + +\begin_layout Section +Frequently Asked Questions +\end_layout + +\begin_layout Subsection +Why is 1.0 unequal 1.0? +\end_layout + +\begin_layout Standard +If your machine uses binary floating point arithmetic, and chances are that + it does, you may eventually find yourself in the following situation: +\end_layout + +\begin_layout Quote +0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 +\end_layout + +\begin_layout Standard +You expect to see 1.0 as result, and indeed that is what you get. + Now you compare this result to the constant 1.0, but surprisingly for many + users, the result is 0. + Appearantly, 1.0 is unequal 1.0 for teapot. + +\end_layout + +\begin_layout Standard +This is not a bug in teapot, in fact it is not a bug at all. + The problem is, that 0.1 (1.0/10.0) does not have an exact representation + in binary floating point arithmetic, similar to how 1.0/3.0 does not have + an exact representation in decimal arithmetic (or binary, for that matter). + As such, a value very close to 0.1 is used, but when displaying it, it will + be rounded to 0.1. + The result is obvious, adding this number which is a little different from + 0.1 ten times leads to a result very close to but not quite 1.0. + Since it is so close, displaying it rounded to only a few digits precision + shows 1.0. + +\end_layout + +\begin_layout Standard +To solve the comparison problem, teapot has the operator +\family typewriter += +\family default + (in contrast to the operator ==), which compares two floating point values + apart from the last significant bit. + Use this operator to compare the two values from above and the result will + be 1, meaning they are about equal. + Don't assume that a number which can be expressed with a finite number + of decimal digits will be represented exactly in binary floating point + arithmetic. + +\end_layout + +\begin_layout Subsection +How do I hide intermediate results? +\end_layout + +\begin_layout Standard +If you used flat, two-dimensional spread sheets before, you are probably + used to hidden cells which contain intermediate results, global constants, + scratch areas and the like. + teapot has no way to hide cells, but you have three dimensions. + Just use one or more layers for such cells and give each cell a label in + order to reference and find it easily. + +\end_layout + +\begin_layout Subsection +Why is there no conditional evaluation? +\end_layout + +\begin_layout Standard +There is no special operator or function for conditional evaluation. + I could add one easily, but then next someone would ask for loops and someone + else for user-defined functions, variables and so on. + If you need a programming language, you know where to find it. + +\end_layout + +\begin_layout Standard +But don't worry. + The answer is, that conditional evaluation comes for free with teapot's + orthogonal cell addressing. + As an example, depending on the cell labelled +\family typewriter +X +\family default + being negative or not, you want the result to be the string +\family typewriter +"BAD +\family default + or +\family typewriter +"GOOD" +\family default +. + This is the solution: +\end_layout + +\begin_layout Quote +eval(&((@(X)>=0)+x(BAD),y(BAD),z(BAD))) +\end_layout + +\begin_layout Standard +The cell labelled +\family typewriter +BAD +\family default + contains the string +\family typewriter +"BAD" +\family default +, its right neighbour contains the string +\family typewriter +"GOOD" +\family default +. + If you have nested conditions, you could weight them with 1, 2, 4 and so + on to address a bigger range of cells. + Alternatively, you could make use of all three dimensions for nested conditions. + +\end_layout + +\end_body +\end_document diff --git a/doc/up.png b/doc/up.png new file mode 100644 index 0000000..b62699b Binary files /dev/null and b/doc/up.png differ diff --git a/doc/up_g.png b/doc/up_g.png new file mode 100644 index 0000000..f913451 Binary files /dev/null and b/doc/up_g.png differ diff --git a/eval.c b/eval.c new file mode 100644 index 0000000..6783a40 --- /dev/null +++ b/eval.c @@ -0,0 +1,1137 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "default.h" +#include "eval.h" +#include "func.h" +#include "main.h" +#include "misc.h" +#include "scanner.h" +#include "sheet.h" +/*}}}*/ + +/* tcopy -- return copy of token */ /*{{{*/ +Token tcopy(Token n) +{ + /* result */ /*{{{*/ + Token result; + /*}}}*/ + + result=n; + if (result.type==STRING) result.u.string=strcpy(malloc(strlen(n.u.string)+1),n.u.string); + else if (result.type==EEK) result.u.err=strcpy(malloc(strlen(n.u.err)+1),n.u.err); + else if (result.type==LIDENT) result.u.lident=strcpy(malloc(strlen(n.u.lident)+1),n.u.lident); + return result; +} +/*}}}*/ +/* tfree -- free dynamic data of token */ /*{{{*/ +void tfree(Token *n) +{ + if (n->type==STRING) + { + free(n->u.string); + n->u.string=(char*)0; + } + else if (n->type==EEK) + { + free(n->u.err); + n->u.err=(char*)0; + } + else if (n->type==LIDENT) + { + free(n->u.lident); + n->u.lident=(char*)0; + } +} +/*}}}*/ +/* tvecfree -- free a vector of pointer to tokens entirely */ /*{{{*/ +void tvecfree(Token **tvec) +{ + if (tvec!=(Token**)0) + { + /* variables */ /*{{{*/ + Token **t; + /*}}}*/ + + for (t=tvec; *t!=(Token*)0; ++t) + { + tfree(*t); + free(*t); + } + free(tvec); + } +} +/*}}}*/ +/* tadd -- + operator */ /*{{{*/ +Token tadd(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) + /* return left error */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (r.type==EEK) + /* return right error */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if (l.type==INT && r.type==INT) + /* result is int sum of two ints */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer+r.u.integer; + } + /*}}}*/ + else if (l.type==STRING && r.type==STRING) + /* result is concatenated strings */ /*{{{*/ + { + result.type=STRING; + result.u.string=malloc(strlen(l.u.string)+strlen(r.u.string)+1); + (void)strcpy(result.u.string,l.u.string); + (void)strcat(result.u.string,r.u.string); + } + /*}}}*/ + else if (l.type==EMPTY && (r.type==INT || r.type==STRING || r.type==FLOAT || r.type==EMPTY)) + /* return right argument */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if ((l.type==INT || l.type==STRING || l.type==FLOAT) && r.type==EMPTY) + /* return left argument */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) + /* result is float sum of int and float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=((double)l.u.integer)+r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) + /* result is float sum of float and int */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt+((double)r.u.integer); + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) + /* result is float sum of float and float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt+r.u.flt; + } + /*}}}*/ + else if (l.type==EMPTY && r.type==EMPTY) + /* result is emty */ /*{{{*/ + { + result.type=EMPTY; + } + /*}}}*/ + else + /* result is type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong types for + operator"))+1),_("wrong types for + operator")); + } + /*}}}*/ + if (result.type==FLOAT && (msg=dblfinite(result.u.flt))!=(const char*)0) + /* result is error */ /*{{{*/ + { + result.type=EEK; + result.u.err=malloc(strlen(msg)+4); + (void)strcpy(result.u.err,"+: "); + (void)strcat(result.u.err,msg); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tsub -- binary - operator */ /*{{{*/ +Token tsub(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) + /* return left error */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (r.type==EEK) + /* return right error */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if (l.type==INT && r.type==INT) + /* result is int difference between left int and right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer-r.u.integer; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) + /* result is float difference between left float and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt-r.u.flt; + } + /*}}}*/ + else if (l.type==EMPTY) + /* return negated right argument */ /*{{{*/ + return tneg(r); + /*}}}*/ + else if ((l.type==INT || l.type==FLOAT) && r.type==EMPTY) + /* return left argument */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) + /* result is float difference of left integer and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=((double)l.u.integer)-r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) + /* result is float difference between left float and right integer */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt-((double)r.u.integer); + } + /*}}}*/ + else + /* result is difference type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong types for - operator"))+1),_("wrong types for - operator")); + } + /*}}}*/ + if (result.type==FLOAT && (msg=dblfinite(result.u.flt))!=(const char*)0) + /* result is error */ /*{{{*/ + { + result.type=EEK; + result.u.err=malloc(strlen(msg)+4); + (void)strcpy(result.u.err,"-: "); + (void)strcat(result.u.err,msg); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tdiv -- / operator */ /*{{{*/ +Token tdiv(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) + /* return left error */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (r.type==EEK) + /* return right error */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if ((r.type==INT && r.u.integer==0) || (r.type==FLOAT && r.u.flt==0.0) || (r.type==EMPTY)) + /* result is division by 0 error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("division by 0"))+1),_("division by 0")); + } + /*}}}*/ + else if (l.type==INT && r.type==INT) + /* result is quotient of left int and right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer/r.u.integer; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) + /* result is quotient of left float and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt/r.u.flt; + } + /*}}}*/ + else if (l.type==EMPTY && r.type==INT) + /* result is 0 */ /*{{{*/ + { + result.type=INT; + result.u.integer=0; + } + /*}}}*/ + else if (l.type==EMPTY && r.type==FLOAT) + /* result is 0.0 */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=0.0; + } + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) + /* result is float quotient of left int and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=((double)l.u.integer)/r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) + /* result is float quotient of left float and right int */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt/((double)r.u.integer); + } + /*}}}*/ + else + /* result is quotient type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong types for / operator"))+1),_("wrong types for / operator")); + } + /*}}}*/ + if (result.type==FLOAT && (msg=dblfinite(result.u.flt))!=(const char*)0) + /* result is error */ /*{{{*/ + { + result.type=EEK; + result.u.err=malloc(strlen(msg)+4); + (void)strcpy(result.u.err,"/: "); + (void)strcat(result.u.err,msg); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tmod -- % operator */ /*{{{*/ +Token tmod(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) /* return left error */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (r.type==EEK) /* return right error */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if ((r.type==INT && r.u.integer==0) || (r.type==FLOAT && r.u.flt==0.0) || (r.type==EMPTY)) /* result is modulo 0 error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("modulo 0"))+1),_("modulo 0")); + } + /*}}}*/ + else if (l.type==INT && r.type==INT) /* result is remainder of left int and right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer%r.u.integer; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) /* result is remainder of left float and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=fmod(l.u.flt,r.u.flt); + } + /*}}}*/ + else if (l.type==EMPTY && r.type==INT) /* result is 0 */ /*{{{*/ + { + result.type=INT; + result.u.integer=0; + } + /*}}}*/ + else if (l.type==EMPTY && r.type==FLOAT) /* result is 0.0 */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=0.0; + } + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) /* result is float remainder of left int and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=fmod((double)l.u.integer,r.u.flt); + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) /* result is float remainder of left float and right int */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=fmod(l.u.flt,(double)r.u.integer); + } + /*}}}*/ + else /* result is remainder type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong types for % operator"))+1),_("wrong types for % operator")); + } + /*}}}*/ + if (result.type==FLOAT && (msg=dblfinite(result.u.flt))!=(const char*)0) /* result is error */ /*{{{*/ + { + result.type=EEK; + result.u.err=malloc(strlen(msg)+4); + (void)strcpy(result.u.err,"%: "); + (void)strcat(result.u.err,msg); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tmul -- * operator */ /*{{{*/ +Token tmul(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) + /* return left error */ /*{{{*/ + result=tcopy(l); + /*}}}*/ + else if (r.type==EEK) + /* return right error */ /*{{{*/ + result=tcopy(r); + /*}}}*/ + else if (l.type==INT && r.type==INT) + /* result is int product of left int and right int */ /*{{{*/ + { + result=l; + result.u.integer=l.u.integer*r.u.integer; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) + /* result is float product of left float and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt*r.u.flt; + } + /*}}}*/ + else if ((l.type==EMPTY && r.type==INT) || (l.type==INT && r.type==EMPTY)) + /* result is 0 */ /*{{{*/ + { + result.type=INT; + result.u.integer=0; + } + /*}}}*/ + else if ((l.type==EMPTY && r.type==FLOAT) || (l.type==FLOAT && r.type==EMPTY)) + /* result is 0.0 */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=0.0; + } + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) + /* result is float product of left int and right float */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=((double)l.u.integer)*r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) + /* result is float product of left float and right int */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=l.u.flt*((double)r.u.integer); + } + /*}}}*/ + else if (l.type==EMPTY && r.type==EMPTY) + /* result is empty */ /*{{{*/ + { + result.type=EMPTY; + } + /*}}}*/ + else + /* result is product type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong types for * operator"))+1),_("wrong types for * operator")); + } + /*}}}*/ + if (result.type==FLOAT && (msg=dblfinite(result.u.flt))!=(const char*)0) + /* result is error */ /*{{{*/ + { + result.type=EEK; + result.u.err=malloc(strlen(msg)+4); + (void)strcpy(result.u.err,"*: "); + (void)strcat(result.u.err,msg); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tneg -- monadic - operator */ /*{{{*/ +Token tneg(Token x) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (x.type==EEK) + /* return error */ /*{{{*/ + return tcopy(x); + /*}}}*/ + else if (x.type==INT) + /* result is negated int argument */ /*{{{*/ + { + result.type=INT; + result.u.integer=-x.u.integer; + } + /*}}}*/ + else if (x.type==FLOAT) + /* result is negated float argument */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=-x.u.flt; + } + /*}}}*/ + else if (x.type==EMPTY) + /* result is argument itself */ /*{{{*/ + { + result=tcopy(x); + } + /*}}}*/ + else + /* result is negation error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("wrong type for - operator"))+1),_("wrong type for - operator")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tpow -- ^ operator */ /*{{{*/ +Token tpow(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + const char *msg; + /*}}}*/ + + if (l.type==EEK) /* return left error */ /*{{{*/ + return tcopy(l); + /*}}}*/ + else if (r.type==EEK) /* return right error */ /*{{{*/ + return tcopy(r); + /*}}}*/ + else if ((l.type==INT || l.type==FLOAT || l.type==EMPTY) && (r.type==INT || r.type==FLOAT || l.type==EMPTY)) /* do the real work */ /*{{{*/ + { + if ((l.type==INT || l.type==EMPTY) && ((r.type==INT && r.u.integer>=0) || r.type==EMPTY)) + /* int^int, return int or error if 0^0 */ /*{{{*/ + { + long x,y; + + if (l.type==EMPTY) x=0; + else x=l.u.integer; + if (r.type==EMPTY) y=0; + else y=r.u.integer; + if (x==0 && y==0) + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("0^0 is not defined"))+1),_("0^0 is not defined")); + } + else + { + long i; + + result.type=INT; + if (x==0) result.u.integer=0; + else if (y==0) result.u.integer=1; + else for (result.u.integer=x,i=1; iu.fident].func(argc, argv); +} +/*}}}*/ +/* tlt -- < operator */ /*{{{*/ +Token tlt(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + static char empty[]=""; + /*}}}*/ + + if (l.type==EEK) /* return left error argument */ /*{{{*/ + return tcopy(l); + /*}}}*/ + if (r.type==EEK) /* return right error argument */ /*{{{*/ + return tcopy(r); + /*}}}*/ + if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case INT: l.u.integer=0; break; + case FLOAT: l.u.flt=0.0; break; + case STRING: l.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case INT: r.u.integer=0; break; + case FLOAT: r.u.flt=0.0; break; + case STRING: r.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (l.type==INT && r.type==INT) /* return left int < right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer= operator */ /*{{{*/ +Token tge(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + static char empty[]=""; + /*}}}*/ + + if (l.type==EEK) /* return left error argument */ /*{{{*/ + return tcopy(l); + /*}}}*/ + if (r.type==EEK) /* return right error argument */ /*{{{*/ + return tcopy(r); + /*}}}*/ + if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case INT: l.u.integer=0; break; + case FLOAT: l.u.flt=0.0; break; + case STRING: l.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case INT: r.u.integer=0; break; + case FLOAT: r.u.flt=0.0; break; + case STRING: r.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (l.type==INT && r.type==INT) /* result is left int >= right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer>=r.u.integer; + } + /*}}}*/ + else if (l.type==STRING && r.type==STRING) /* return left string >= right string */ /*{{{*/ + { + result.type=INT; + result.u.integer=(strcmp(l.u.string,r.u.string)>=0); + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) /* return left float >= right float */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.flt>=r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) /* return left float >= (double) right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.flt>=((double)r.u.integer); + } + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) /* return (double) left int >= right float */ /*{{{*/ + { + result.type=INT; + result.u.integer=((double)l.u.integer)>=r.u.flt; + } + /*}}}*/ + else /* return >= type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* tgt -- <= operator */ /*{{{*/ +Token tgt(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + static char empty[]=""; + /*}}}*/ + + if (l.type==EEK) /* return left error argument */ /*{{{*/ + return tcopy(l); + /*}}}*/ + if (r.type==EEK) /* return right error argument */ /*{{{*/ + return tcopy(r); + /*}}}*/ + if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case INT: l.u.integer=0; break; + case FLOAT: l.u.flt=0.0; break; + case STRING: l.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case INT: r.u.integer=0; break; + case FLOAT: r.u.flt=0.0; break; + case STRING: r.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (l.type==INT && r.type==INT) /* result is left int > right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.integer>r.u.integer; + } + /*}}}*/ + else if (l.type==STRING && r.type==STRING) /* result is left string > right string */ /*{{{*/ + { + result.type=INT; + result.u.integer=(strcmp(l.u.string,r.u.string)>0); + } + /*}}}*/ + else if (l.type==FLOAT && r.type==FLOAT) /* result is left float > right float */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.flt>r.u.flt; + } + /*}}}*/ + else if (l.type==FLOAT && r.type==INT) /* result is left float > (double) right int */ /*{{{*/ + { + result.type=INT; + result.u.integer=l.u.flt>((double)r.u.integer); + } + /*}}}*/ + else if (l.type==INT && r.type==FLOAT) /* result is left int > right float */ /*{{{*/ + { + result.type=INT; + result.u.integer=((double)l.u.integer)>r.u.flt; + } + /*}}}*/ + else /* result is relation op type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("type mismatch for relational operator")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* teq -- == operator */ /*{{{*/ +Token teq(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + static char empty[]=""; + /*}}}*/ + + if (l.type==EEK) return tcopy(l); + else if (r.type==EEK) return tcopy(r); + if (l.type==EMPTY) + /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case INT: l.u.integer=0; break; + case FLOAT: l.u.flt=0.0; break; + case STRING: l.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) + /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case INT: r.u.integer=0; break; + case FLOAT: r.u.flt=0.0; break; + case STRING: r.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (l.type==FLOAT && r.type==INT) + { + result.type=INT; + result.u.integer=l.u.flt==((double)r.u.integer); + } + else if (l.type==INT && r.type==FLOAT) + { + result.type=INT; + result.u.integer=((double)l.u.integer)==r.u.flt; + } + else if (l.type!=r.type) + { + result.type=INT; + result.u.integer=0; + } + else if (l.type==INT) + { + result.type=INT; + result.u.integer=l.u.integer==r.u.integer; + } + else if (l.type==STRING) + { + result.type=INT; + result.u.integer=(strcmp(l.u.string,r.u.string)==0); + } + else if (l.type==FLOAT) + { + result.type=INT; + result.u.integer=l.u.flt==r.u.flt; + } + else if (l.type==EMPTY) + { + result.type=INT; + result.u.integer=1; + } + else + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); + } + return result; +} +/*}}}*/ +/* tabouteq -- ~= operator */ /*{{{*/ +Token tabouteq(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (l.type==EEK) return tcopy(l); + else if (r.type==EEK) return tcopy(r); + if (l.type==EMPTY) + /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case FLOAT: l.u.flt=0.0; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) + /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case FLOAT: r.u.flt=0.0; break; + default: ; + } + } + /*}}}*/ + if (l.type==FLOAT && r.type==FLOAT) + { + result.type=INT; + result.u.integer=(fabs(l.u.flt-r.u.flt)<=DBL_EPSILON); + } + else + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); + } + return result; +} +/*}}}*/ +/* tne -- != operator */ /*{{{*/ +Token tne(Token l, Token r) +{ + /* variables */ /*{{{*/ + Token result; + static char empty[]=""; + /*}}}*/ + + if (l.type==EEK) return tcopy(l); + else if (r.type==EEK) return tcopy(r); + if (l.type==EMPTY) + /* try to assign 0 element of r.type */ /*{{{*/ + { + l.type=r.type; + switch (r.type) + { + case INT: l.u.integer=0; break; + case FLOAT: l.u.flt=0.0; break; + case STRING: l.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (r.type==EMPTY) + /* try to assign 0 element of l.type */ /*{{{*/ + { + r.type=l.type; + switch (l.type) + { + case INT: r.u.integer=0; break; + case FLOAT: r.u.flt=0.0; break; + case STRING: r.u.string=empty; break; + default: ; + } + } + /*}}}*/ + if (l.type==FLOAT && r.type==INT) + { + result.type=INT; + result.u.integer=l.u.flt!=((double)r.u.integer); + } + else if (l.type==INT && r.type==FLOAT) + { + result.type=INT; + result.u.integer=((double)l.u.integer)!=r.u.flt; + } + else if (l.type!=r.type) + { + result.type=INT; + result.u.integer=1; + } + else if (l.type==INT) + { + result.type=INT; + result.u.integer=l.u.integer!=r.u.integer; + } + else if (l.type==STRING) + { + result.type=INT; + result.u.integer=(strcmp(l.u.string,r.u.string)!=0); + } + else if (l.type==FLOAT) + { + result.type=INT; + result.u.integer=l.u.flt!=r.u.flt; + } + else if (l.type==EMPTY) + { + result.type=INT; + result.u.integer=0; + } + else + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); + } + return result; +} +/*}}}*/ + + diff --git a/eval.h b/eval.h new file mode 100644 index 0000000..9f3cc87 --- /dev/null +++ b/eval.h @@ -0,0 +1,25 @@ +#ifndef THE_EVAL_H +#define THE_EVAL_H + +#include "scanner.h" + +Token tcopy(Token n); +void tfree(Token *n); +void tvecfree(Token **tvec); +Token tpow(Token l, Token r); +Token tdiv(Token l, Token r); +Token tmod(Token l, Token r); +Token tmul(Token l, Token r); +Token tadd(Token l, Token r); +Token tsub(Token l, Token r); +Token tneg(Token x); +Token tfuncall(Token *ident, int argc, Token argv[]); +Token tlt(Token l, Token r); +Token tle(Token l, Token r); +Token tge(Token l, Token r); +Token tgt(Token l, Token r); +Token teq(Token l, Token r); +Token tabouteq(Token l, Token r); +Token tne(Token l, Token r); + +#endif diff --git a/examples/asqrt.README b/examples/asqrt.README new file mode 100644 index 0000000..46b3650 --- /dev/null +++ b/examples/asqrt.README @@ -0,0 +1,3 @@ +This sheet calculates the square root of x automatically, as soon as +you enter a number in cell X. The calculation will be iterated until +the square of the square root is as close as 0.1. diff --git a/examples/asqrt.tp b/examples/asqrt.tp new file mode 100644 index 0000000..60a9c4f Binary files /dev/null and b/examples/asqrt.tp differ diff --git a/examples/blink.tp b/examples/blink.tp new file mode 100644 index 0000000..ed4de42 Binary files /dev/null and b/examples/blink.tp differ diff --git a/examples/counter.README b/examples/counter.README new file mode 100644 index 0000000..860fd82 --- /dev/null +++ b/examples/counter.README @@ -0,0 +1,3 @@ +This sheet shows how to use clock() to model a counter which +automatically counts up to 1000 once the sheet gets clocked with +control-s. diff --git a/examples/counter.tp b/examples/counter.tp new file mode 100644 index 0000000..9fbd299 Binary files /dev/null and b/examples/counter.tp differ diff --git a/examples/graphtest.sh b/examples/graphtest.sh new file mode 100755 index 0000000..741b699 --- /dev/null +++ b/examples/graphtest.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +( echo This is the same data, once as a pie graph and once as a box graph. +echo .PS +../graph -w 2 <graphtest.ps diff --git a/examples/life.README b/examples/life.README new file mode 100644 index 0000000..8d123c9 --- /dev/null +++ b/examples/life.README @@ -0,0 +1,4 @@ +This is a sheet which contains the well known game of life. Load it and +clock the sheet with control-s a few times, and you will see the glider +moving. Layer 0 contains the grid and layer 1 contains some help +formulas. diff --git a/examples/life.tp b/examples/life.tp new file mode 100644 index 0000000..754b1dc Binary files /dev/null and b/examples/life.tp differ diff --git a/examples/load_save_time.README b/examples/load_save_time.README new file mode 100644 index 0000000..ed18ef9 --- /dev/null +++ b/examples/load_save_time.README @@ -0,0 +1,6 @@ +This sheet demonstrates the use of the time functions strftime and +strptime. +The associated shell script shows how to use teapot in batch mode, +with the definitions and output formulas in level 0, and variable data +loaded to level 1. + diff --git a/examples/load_save_time.sh b/examples/load_save_time.sh new file mode 100755 index 0000000..125cb1c --- /dev/null +++ b/examples/load_save_time.sh @@ -0,0 +1,12 @@ +#!/bin/sh +echo 11.9.2001,2.2.2002 >load_save_time.txt +teapot -b load_save_time.tp<} {private global +} + +decl {\#include } {private global +} + +decl {\#include } {private global +} + +decl {\#define shadow _shadow} {private global +} + +decl {\#define transparent _transparent} {private global +} + +decl {\#define MenuChoice _MenuChoice} {private global +} + +decl {\#define Cell _Cell} {private global +} + +decl {\#include } {private local +} + +decl {\#include } {private global +} + +decl {\#include } {private global +} + +decl {\#include } {private global +} + +decl {\#include } {private global +} + +decl {\#include } {public global +} + +decl {\#include } {public global +} + +decl {\#include } {public global +} + +decl {\#undef shadow} {private global +} + +decl {\#undef transparent} {private global +} + +decl {\#undef MenuChoice} {private global +} + +decl {\#undef Cell} {private global +} + +decl {\#include "misc.h"} {private global +} + +decl {\#include "display.h"} {public global +} + +decl {\#define SHEET(s,x,y,z) (*(s->sheet+(x)*s->dimz*s->dimy+(y)*s->dimz+(z)))} {private global +} + +decl {\#define SHADOWED(sheet,x,y,z) (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->shadowed)} {private global +} + +class TeapotTable {open : {public Fl_Table} +} { + decl {Sheet *cursheet;} {protected local + } + decl {bool cut, updating;} {protected local + } + decl {static const TableContext ACTION = (TableContext)(1<<8);} {public local + } + decl {static const TableContext REFRESH = (TableContext)(1<<9);} {public local + } + Function {TeapotTable(int x, int y, int w, int h, const char *l=0) : Fl_Table(x, y, w, h, l), cut(false), updating(false)} {open + } { + code {end(); +col_resize_min(10); +col_resize(true); +col_header(header); +row_resize(false); +row_header(header); +set_visible_focus(); +table_box(FL_THIN_UP_FRAME);} {} + } + Function {~TeapotTable()} {} { + code {} {} + } + Function {sheet(Sheet *s)} {open return_type void + } { + code {cursheet = s; +s->display = (void *)this; +clear(); +update_table(); +do_callback(CONTEXT_NONE, 0, 0);} {} + } + Function {sheet()} {return_type {Sheet *} + } { + code {return cursheet;} {} + } + Function {draw_cell(TableContext context, int R, int C, int X, int Y, int W, int H)} {open return_type void + } { + code {char s[1024]; +//printf("DRAW: %i @%i,%i - (%i,%i) %ix%i\\n", context, C,R, X,Y, W,H); +switch (context) { +case CONTEXT_ENDPAGE: + W = X-x()-2; + H = Y-y()-2; + X = x()+2; + Y = y()+2; + fl_font(FL_HELVETICA | FL_BOLD, 14); + fl_push_clip(X, Y, W, H); + fl_draw_box(FL_DIAMOND_UP_BOX, X, Y, W, H, col_header_color()); + fl_color(FL_INACTIVE_COLOR); + sprintf(s, "%d", cursheet->curz); + fl_draw(s, X, Y, W, H, FL_ALIGN_CENTER); + fl_pop_clip(); + break; + +case CONTEXT_STARTPAGE: + adjust_outside(); + if (Fl::event_button1()) update_sheet(); + break; + +case CONTEXT_COL_HEADER: + fl_font(FL_HELVETICA | FL_BOLD, 14); + fl_push_clip(X, Y, W, H); + fl_draw_box(FL_THIN_UP_BOX, X, Y, W, H, col_header_color()); + fl_color(FL_FOREGROUND_COLOR); + sprintf(s, "%d", C); + fl_draw(s, X, Y, W, H, FL_ALIGN_CENTER); + fl_pop_clip(); + return; + +case CONTEXT_ROW_HEADER: + fl_font(FL_HELVETICA | FL_BOLD, 14); + fl_push_clip(X, Y, W, H); + fl_draw_box(FL_THIN_UP_BOX, X, Y, W, H, row_header_color()); + fl_color(FL_FOREGROUND_COLOR); + sprintf(s, "%d", R); + fl_draw(s, X, Y, W, H, FL_ALIGN_CENTER); + fl_pop_clip(); + return; + +case CONTEXT_CELL: { + while (SHADOWED(cursheet, C, R, cursheet->curz)) X -= W = col_width(--C); + int x = C+1; + while (SHADOWED(cursheet,x,R,cursheet->curz)) W += col_width(x), x++; + + fl_push_clip(X, Y, W, H); + bool selected = false; + if (cursheet->mark1x >= 0) { + int x1 = cursheet->mark1x, x2 = cursheet->mark2x, y1 = cursheet->mark1y, y2 = cursheet->mark2y, z1 = cursheet->mark1z, z2 = cursheet->mark2z; + posorder(&x1, &x2); + posorder(&y1, &y2); + posorder(&z1, &z2); + selected = cursheet->mark1x >= 0 && C >= x1 && C <= x2 && R >= y1 && R <= y2 && cursheet->curz >= z1 && cursheet->curz <= z2; + } + fl_draw_box(C == cursheet->curx && R == cursheet->cury?FL_BORDER_BOX:FL_THIN_DOWN_BOX, X, Y, W, H, selected?FL_SELECTION_COLOR:FL_BACKGROUND2_COLOR); + if (Fl::focus() == this && C == cursheet->curx && R == cursheet->cury) draw_focus(FL_BORDER_BOX, X, Y, W, H); + fl_pop_clip(); + + fl_push_clip(X+3, Y+3, W-6, H-6); + fl_color(FL_FOREGROUND_COLOR); + fl_font(FL_HELVETICA | (isbold(cursheet, C, R, cursheet->curz)? FL_BOLD:0), 14); + + printvalue(s, sizeof(s), 0, 0, getscientific(cursheet, C, R, cursheet->curz), getprecision(cursheet, C, R, cursheet->curz), cursheet, C, R, cursheet->curz); + int ww = 0, hh = 0; + fl_measure(s, ww, hh, 0); + if (ww > W-6) for (int i = 0; s[i]; i++) s[i] = '\#'; + + int adj = getadjust(cursheet, C, R, cursheet->curz); + fl_draw(s, X+3, Y+3, W-6, H-6, adj == RIGHT?FL_ALIGN_RIGHT:adj == LEFT?FL_ALIGN_LEFT:FL_ALIGN_CENTER); + if (underlined(cursheet, C, R, cursheet->curz)) fl_xyline(X, Y+H-7, X+W); + fl_pop_clip(); + + return; +} + +default: + return; +}} {selected + } + } + Function {update_table()} {open return_type void + } { + code {if (updating) return; + +updating = true; +//printf("update_table: %ix%i@%i,%i; %i[%i], %i[%i]\\n", cursheet->dimx, cursheet->dimy, cursheet->curx, cursheet->cury, cursheet->offx, cursheet->maxx, cursheet->offy, cursheet->maxy); + +if (cursheet->dimx > cols()) cols(cursheet->dimx); +if (cursheet->dimy > rows()) rows(cursheet->dimy); +adjust_outside(); + +for (int x = 0; x < cursheet->dimx; x++) { + int w = columnwidth(cursheet, x, cursheet->curz)*10; + if (col_width(x) != w) col_width(x, w); +} +col_position(cursheet->offx); +row_position(cursheet->offy); +set_selection(cursheet->cury, cursheet->curx, cursheet->cury, cursheet->curx); +move_cursor(0,0); +updating = false;} {} + } + Function {update_sheet()} {return_type void + } { + code {int x1, x2, y1, y2; + +if (updating) return; +updating = true; + +get_selection(y1, x1, y2, x2); +if (x1 != x2 || y1 != y2) { + cursheet->mark1x = x1; + cursheet->mark1y = y1; + cursheet->mark2x = x2; + cursheet->mark2y = y2; + cursheet->mark1z = cursheet->mark2z = cursheet->curz; + cursheet->marking = 0; +} +moveto(cursheet, current_col, current_row, -1); +visible_cells(cursheet->offy, cursheet->maxy, cursheet->offx, cursheet->maxx); +cursheet->maxx -= cursheet->offx; +cursheet->maxy -= cursheet->offy; + +if (is_interactive_resize()) { + for (int C = 0; C < cursheet->dimx; C++) { + int w = (col_width(C) + 5)/10; + if (w != columnwidth(cursheet, C, cursheet->curz)) setwidth(cursheet, C, cursheet->curz, w); + } +} + + +updating = false; +//printf("update_sheet: %ix%i@%i,%i; %i[%i], %i[%i] (%i,%i)-(%i,%i)\\n", cols(), rows(), cursheet->curx, cursheet->cury, cursheet->offx, cursheet->maxx, cursheet->offy, cursheet->maxy, x1, y1, x2, y2);} {} + } + Function {handle(int event)} {open return_type int + } { + code {if (event == FL_KEYDOWN) { + int ctrl = Fl::event_ctrl(); + int alt = Fl::event_alt(); + int shift = Fl::event_shift(); + Key k = (Key)Fl::event_key(); + + switch ((unsigned int)k) { + case FL_Escape: k = K_INVALID; break; + case FL_BackSpace: k = K_BACKSPACE; break; + case FL_F+1: k = ctrl?K_ABOUT:K_HELP; break; + case FL_F+8: k = ctrl?K_RECALC:K_CLOCK; break; + case FL_F+9: k = ctrl?K_RECALC:K_CLOCK; break; + case FL_F+10: k = (Key)'/'; break; + case FL_Enter: + case FL_KP_Enter: k = alt?K_MENTER:K_ENTER; break; + case 'c': if (ctrl) { do_mark(cursheet, 2); cut = false; k = K_NONE; } break; + case 'v': k = ctrl?(cut?BLOCK_MOVE:BLOCK_COPY):(Key)'v'; break; + case 'x': if (ctrl) { do_mark(cursheet, 2); cut = true, k = K_NONE; } break; + case FL_Insert: if (ctrl) { do_mark(cursheet, 2); cut = false; } k = !shift?K_NONE:cut?BLOCK_MOVE:BLOCK_COPY; break; + case FL_Delete: if (shift) { do_mark(cursheet, 2); cut = true; } k = shift?K_NONE:BLOCK_CLEAR; break; + case FL_Home: k = ctrl?K_FIRSTL:shift?K_FSHEET:K_HOME; break; + case FL_End: k = ctrl?K_LASTL:shift?K_LSHEET:K_END; break; + case FL_Up: if (shift && !cursheet->marking) do_mark(cursheet, 1); k = ctrl?K_PPAGE:K_UP; break; + case FL_Down: if (shift && !cursheet->marking) do_mark(cursheet, 1); k = ctrl?K_NPAGE:K_DOWN; break; + case FL_Right: if (shift && !cursheet->marking) do_mark(cursheet, 1); k = ctrl?K_FPAGE:K_RIGHT; break; + case FL_Left: if (shift && !cursheet->marking) do_mark(cursheet, 1); k = ctrl?K_BPAGE:K_LEFT; break; + case FL_Page_Down: k = shift?K_NSHEET:ctrl?K_LASTL :K_NPAGE; break; + case FL_Page_Up: k = shift?K_PSHEET:ctrl?K_FIRSTL:K_PPAGE; break; + } + + if (k > 0 && (ctrl || alt)) return 0; + + // Quick and dirty upper-case fix, fails for international chars on keyboards... + if (shift && !alt && !ctrl && k >= 'a' && k <= 'z') k = (Key)(k - 'a' + 'A'); + + do_sheetcmd(cursheet, k, cursheet->moveonly); + do_callback(ACTION, 0, 0); + redraw(); +} else if (event == FL_FOCUS) { + do_callback(REFRESH, 0, 0); + return 1; +} else if (event == FL_PUSH) { + int ex = Fl::event_x() - x(), ey = Fl::event_y() - y(); + if (ex >= row_header_width() || ey >= col_header_height()) { + int rc = Fl_Table::handle(event); + do_callback(ACTION, 0, 0); + return rc; + } + if (ex < row_header_width()/2) relmoveto(cursheet, 0, 0, -1); + else relmoveto(cursheet, 0, 0, 1); + return 1; +} else if (event != FL_KEYUP) { + return Fl_Table::handle(event); +} + +return 1;} {} + } + Function {adjust_outside()} {open protected return_type void + } { + code {int x1, x2, y1, y2; + +if (!cols() || !rows()) { + cols(1); + rows(1); +} + +visible_cells(y1, y2, x1, x2); +//printf("adj: (%i,%i)-(%i,%i) %ix%i\\n", x1, y1, x2, y2, cols(), rows()); +if (x2+2 < cols() && cols() > cursheet->dimx) cols(x2+2 < cursheet->dimx?cursheet->dimx:x2+2); +else if (x2+1 == cols()) { + int xpos = col_scroll_position(cols()); + int w = col_width(cols()-1); + x2 += (tow + hscrollbar->value() - xpos) / w + 2; + //printf(" : t: %i, w: %i, p: %i, r: %i\\n", tow, w, xpos, x2+1); + cols(x2+1); +} + +if (y2+2 < rows() && rows() > cursheet->dimy) rows(y2+2 < cursheet->dimy?cursheet->dimy:y2+2); +else if (y2+1 == rows()) { + int ypos = row_scroll_position(rows()); + int h = row_height(rows()-1); + y2 += (toh + vscrollbar->value() - ypos) / h + 2; + rows(y2+1); +}} {} + } +} + +class MainWindow {open +} { + decl {static MainWindow *current;} {protected local + } + decl {int edit_rc;} {private local + } + Function {MainWindow(Sheet *sheet)} {open + } { + Fl_Window window { + label teapot + callback {if (Fl::event_key(FL_Escape)) table->take_focus(); +else if (do_sheetcmd(table->sheet(), K_QUIT, 0) && doanyway(table->sheet(), _("Sheet modified, leave anyway?"))) { + line_label->deactivate(); + window->hide(); +}} open + protected xywh {866 342 800 600} type Double when 0 hide resizable + } { + Fl_Menu_Bar menu { + callback {Sheet *sheet = table->sheet(); +Key action = (Key)(intptr_t)o->mvalue()->user_data(); + +if (do_sheetcmd(sheet, action, sheet->moveonly) && doanyway(sheet, _("Sheet modified, leave anyway?"))) { + window->hide(); + return; +} + +table->update_table(); +table->redraw();} open + xywh {0 0 800 25} + class Fl_Sys_Menu_Bar + } { + Submenu {} { + label {&File} + xywh {25 25 67 24} + } { + MenuItem {} { + label {&Open...} + user_data K_LOADMENU + xywh {5 5 30 20} shortcut 0x4006f + } + MenuItem {} { + label {&Save} + user_data K_SAVE + xywh {0 0 30 20} shortcut 0x40073 + } + MenuItem {} { + label {Save &As...} + user_data K_NAME + xywh {0 0 30 20} shortcut 0x50053 divider + } + MenuItem {} { + label {&Quit} + user_data K_QUIT + xywh {0 0 30 20} shortcut 0x40071 + } + } + Submenu {} { + label {&Block} + xywh {25 25 67 24} + } { + MenuItem {} { + label {&Insert} + user_data BLOCK_INSERT + xywh {0 0 30 20} shortcut 0x90069 + } + MenuItem {} { + label {&Delete} + user_data BLOCK_DELETE + xywh {0 0 30 20} shortcut 0x90064 divider + } + MenuItem {} { + label {&Move} + user_data BLOCK_MOVE + xywh {0 0 30 20} shortcut 0x9006d + } + MenuItem {} { + label {&Copy} + user_data BLOCK_COPY + xywh {0 0 36 21} shortcut 0x90063 divider + } + MenuItem {} { + label {&Fill} + user_data BLOCK_FILL + xywh {0 0 36 21} shortcut 0x90066 + } + MenuItem {} { + label {C&lear} + user_data BLOCK_CLEAR + xywh {0 0 36 21} shortcut 0x9006c divider + } + MenuItem {} { + label {&Sort} + user_data BLOCK_SORT + xywh {0 0 36 21} shortcut 0x90073 + } + MenuItem {} { + label {Mi&rror} + user_data BLOCK_MIRROR + xywh {0 0 36 21} shortcut 0x90072 + } + } + Submenu {} { + label {&View} + xywh {0 0 70 21} + } { + MenuItem {} { + label {Column &Width...} + user_data K_COLWIDTH + xywh {0 0 36 21} shortcut 0x80077 + } + MenuItem {} { + label {&Goto} + user_data K_GOTO + xywh {0 0 36 21} shortcut 0x40067 + } + } + Submenu {} { + label {F&ormat} open + xywh {5 5 70 21} + } { + MenuItem {} { + label {L&abel...} + user_data ADJUST_LABEL + xywh {0 0 36 21} shortcut 0x80061 divider + } + MenuItem bold { + label {&Bold} + user_data ADJUST_BOLD + protected xywh {0 0 34 21} shortcut 0x80062 + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem underline { + label {&Underline} + user_data ADJUST_UNDERLINE + protected xywh {0 0 34 21} shortcut 0x80075 divider + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem left { + label {&Left} + user_data ADJUST_LEFT + protected xywh {0 0 36 21} shortcut 0x8006c + code0 {o->flags |= FL_MENU_RADIO;} + } + MenuItem right { + label {&Right} + user_data ADJUST_RIGHT + protected xywh {0 0 36 21} shortcut 0x80072 + code0 {o->flags |= FL_MENU_RADIO;} + } + MenuItem center { + label {&Center} + user_data ADJUST_CENTER + protected xywh {0 0 36 21} shortcut 0x80063 divider + code0 {o->flags |= FL_MENU_RADIO;} + } + MenuItem {} { + label {&Precision...} + user_data ADJUST_PRECISION + xywh {0 0 36 21} shortcut 0x80070 divider + } + MenuItem sci { + label {&Scientific} + user_data ADJUST_SCIENTIFIC + protected xywh {0 0 36 21} shortcut 0x80073 + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem shadow { + label {Shadow&ed} + user_data ADJUST_SHADOW + protected xywh {0 0 36 21} shortcut 0x80065 + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem transparent { + label {&Transparent} + user_data ADJUST_TRANSPARENT + protected xywh {0 0 36 21} shortcut 0x80074 divider + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem lock { + label {Lo&ck} + user_data ADJUST_LOCK + protected xywh {0 0 36 21} shortcut 0x80063 + code0 {o->flags |= FL_MENU_TOGGLE;} + } + MenuItem ignore { + label {&Ignore} + user_data ADJUST_IGNORE + protected xywh {0 0 36 21} shortcut 0x80069 + code0 {o->flags |= FL_MENU_TOGGLE;} + } + } + Submenu {} { + label {&Help} open + xywh {25 25 67 24} + } { + MenuItem {} { + label {&Manual} + user_data K_HELP + xywh {0 0 30 20} shortcut 0xffbe + } + MenuItem {} { + label {&About} + user_data K_ABOUT + xywh {0 0 30 20} + } + } + } + Fl_Group line_label { + label { Input:} open + protected xywh {0 25 800 25} box ROUND_UP_BOX align 20 deactivate + } { + Fl_Input line_input { + callback {bool enterkey = Fl::event_key(FL_Enter) || Fl::event_key(FL_KP_Enter); +if (Fl::focus() && (Fl::focus() != table || enterkey || Fl::event_key(FL_Escape))) { + if (enterkey) edit_rc = 0; + line_label->deactivate(); +}} + protected xywh {75 27 723 21} box ROUND_DOWN_BOX labeltype NO_LABEL align 20 when 6 deactivate + } + } + Fl_Box table { + callback {Sheet *sheet = table->sheet(); +table->update_sheet(); + +const char *label = getlabel(sheet,sheet->curx,sheet->cury,sheet->curz); +char moveonly=sheet->moveonly ? *_("V") : *_("E"); + +char buf[1024]; +if (*label == 0) snprintf(buf, sizeof(buf), "%c @@(%d,%d,%d)=", moveonly, sheet->curx, sheet->cury, sheet->curz); +else snprintf(buf, sizeof(buf), "%c @@(%s)=", moveonly, label); + +if (moveonly && table->callback_context() == TeapotTable::ACTION) { + char valbuf[1024] = ""; + if (Fl::event_key() == 'p' || Fl::event_button1()) sprintf(valbuf, "(%i,%i,%i)", sheet->curx, sheet->cury, sheet->curz); + else if (Fl::event_key() == 'v') printvalue(valbuf,sizeof(valbuf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,sheet,sheet->curx,sheet->cury,sheet->curz); + else if (Fl::event_key(FL_Tab)) line_input->take_focus(); + if (valbuf[0]) { + line_input->insert(valbuf); + line_input->take_focus(); + } +} + + +char *err; +char val[1024]; +if ((err = geterror(sheet, sheet->curx, sheet->cury, sheet->curz))) { + strncpy(val,err,sizeof(val)); + free(err); + val[sizeof(val)-1] = 0; +} else { + print(val,sizeof(val),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,getcont(sheet,sheet->curx,sheet->cury,sheet->curz,0)); + if (getcont(sheet,sheet->curx,sheet->cury,sheet->curz,1)) { + snprintf(val+strlen(val),sizeof(val)-strlen(val)," -> "); + print(val+strlen(val),sizeof(val)-strlen(val),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,getcont(sheet,sheet->curx,sheet->cury,sheet->curz,1)); + } +} + +line_edit(sheet, val, 0, buf, 0, 0); +int x = sheet->curx, y = sheet->cury, z = sheet->curz; +int adj = getadjust(sheet, x, y, z); +if (adj == LEFT) left->setonly(); +else if (adj == RIGHT) right->setonly(); +else if (adj == CENTER) center->setonly(); +if (shadowed(sheet, x+1, y, z)) shadow->set(); +else shadow->clear(); +if (::transparent(sheet, x, y, z)) transparent->set(); +else transparent->clear(); +if (locked(sheet, x, y, z)) lock->set(); +else lock->clear(); +if (ignored(sheet, x, y, z)) ignore->set(); +else ignore->clear(); +if (isbold(sheet, x, y, z)) bold->set(); +else bold->clear(); +if (underlined(sheet, x, y, z)) underline->set(); +else underline->clear(); +if (getscientific(sheet, x, y, z)) sci->set(); +else sci->clear();} + protected xywh {0 50 800 525} box DOWN_FRAME labeltype NO_LABEL resizable + code0 {table->sheet(sheet);} + class TeapotTable + } + Fl_Box status { + label {teapot ready.} + protected xywh {0 575 800 25} box GTK_ROUND_DOWN_BOX align 20 + } + } + code {current = this;} {} + } + Function {line_edit(Sheet *sheet, char *buf, size_t size, const char *prompt, size_t *x, size_t *offx)} {open return_type int + } { + code {if (line_label->active()) { + if (x) line_msg(NULL, "Action not possible at this time."); + return -1; +} + +line_label->copy_label(prompt); + +int ww = 0, hh = 0; +line_label->measure_label(ww, hh); +line_input->resize(line_label->x()+ww+5, line_input->y(), line_label->w()-ww-7, line_input->h()); +line_input->value(buf); + +if (!x) return 0; + +line_input->maximum_size(size); +line_input->position(*x, *x); +line_label->activate(); +line_input->activate(); +table->sheet()->moveonly = 1; +line_input->take_focus(); + +edit_rc = -1; +while (line_label->active()) Fl::wait(); + +memcpy(buf, line_input->value(), size); +line_input->deactivate(); +table->sheet()->moveonly = 0; +table->take_focus(); +return edit_rc;} {} + } + Function {line_msg(const char *prompt, const char *msg)} {return_type void + } { + code {char label[1024]; +snprintf(label, sizeof(label), "%s%s%s", prompt?prompt:"", prompt?" ":"", msg); +status->copy_label(label);} {} + } + decl {friend void line_msg(const char*, const char*);} {private local + } +} + +Function {line_file(const char *file, const char *pattern, const char *title, int create)} {open C return_type {const char *} +} { + code {static char buf[PATH_MAX]; +Fl_Native_File_Chooser chooser; + +chooser.title(title); +chooser.type(create?Fl_Native_File_Chooser::BROWSE_SAVE_FILE:Fl_Native_File_Chooser::BROWSE_FILE); +chooser.filter(pattern); +chooser.options((create?Fl_Native_File_Chooser::NEW_FOLDER:0)|Fl_Native_File_Chooser::SAVEAS_CONFIRM); + +if (file) { + fl_filename_absolute(buf, sizeof(buf), file); + char *p = (char *)fl_filename_name(buf); + *p = 0; + chooser.directory(buf); +} +if (chooser.show()) return NULL; + +strncpy(buf, chooser.filename(), sizeof(buf)); +buf[sizeof(buf)-1] = 0; +return buf;} {} +} + +Function {line_edit(Sheet *sheet, char *buf, size_t size, const char *prompt, size_t *x, size_t *offx)} {C return_type int +} { + code {if (sheet) return ((MainWindow*)((TeapotTable*)sheet->display)->parent()->user_data())->line_edit(sheet, buf, size, prompt, x, offx); + +const char *val = fl_input("%s", buf, prompt); +if (val) { + strncpy(buf, val, size); + buf[size-1] = 0; +} + +return !val;} {} +} + +Function {line_ok(const char *prompt, int curx)} {C return_type int +} { + code {int rc = !!fl_choice("%s", "&No", NULL, "&Yes", prompt); +if (Fl::event_key(FL_Escape)) return -1; +return rc;} {} +} + +Function {line_msg(const char *prompt, const char *msg)} {C return_type void +} { + code {MainWindow::current->line_msg(prompt, msg);} {} +} + +Function {keypressed()} {open C return_type int +} { + code {while (Fl::wait(.01)) if (Fl::event_key(FL_Escape)) return 1; +return 0;} {} +} + +Function {line_menu(const char *prompt, const MenuChoice *choice, int curx)} {C return_type int +} { + Fl_Window line_menu_menu { + label {Please Choose} open + xywh {706 58 250 245} type Double hide resizable modal + } { + Fl_Group {} { + label {Please Choose:} open + xywh {0 0 250 200} box ENGRAVED_BOX align 21 + } { + Fl_Browser line_menu_browser { + callback {line_menu_menu->hide();} + xywh {5 25 240 170} + class Fl_Select_Browser + } + } + Fl_Button {} { + label Cancel + callback {line_menu_menu->hide();} + xywh {5 205 240 35} + } + } + code {line_menu_browser->clear(); +while (choice->str) { + line_menu_browser->add(choice->str+1); + choice++; +} + +line_menu_menu->show(); +while (line_menu_menu->shown()) Fl::wait(); + +return line_menu_browser->value()-1;} {} +} + +Function {redraw_sheet(Sheet *sheet)} {C return_type void +} { + code {TeapotTable *t = (TeapotTable*)sheet->display; +t->update_table(); +t->redraw();} {} +} + +Function {redraw_cell(Sheet *sheet, int x, int y, int z)} {C return_type void +} { + code {redraw_sheet(sheet);} {} +} + +Function {display_init(Sheet *sheet, int always_redraw)} {open C return_type void +} { + code {Fl::get_system_colors(); +\#ifdef ENABLE_HELP +Fl_File_Icon::load_system_icons(); +\#endif +Fl::scheme("gtk+"); +int ch = sheet->changed; +resize(sheet, 1, 1, 1); +sheet->changed = ch; +new MainWindow(sheet);} {} +} + +Function {display_end()} {C return_type void +} { + code {} {} +} + +Function {display_main(Sheet *cursheet)} {C return_type void +} { + code {((TeapotTable *)cursheet->display)->parent()->show(); +Fl::run();} {} +} + +Function {show_menu(Sheet *sheet)} {C return_type Key +} { + code {return K_NONE;} {} +} + +decl {MainWindow *MainWindow::current;} {private global +} + +declblock {\#ifdef ENABLE_HELP} {after {\#endif} +} { + decl {\#include } {private global + } + Function {show_text(const char *text)} {open C return_type void + } { + code {Fl_Help_Dialog *d = new Fl_Help_Dialog(); +if (strchr(text, '<')) { + d->value(text); +} else { + d->load(text); +} +d->resize(d->x(), d->y(), d->w()*3/2, d->h()*3/2); +d->show();} {} + } +} + +declblock {\#ifndef ENABLE_HELP} {after {\#endif} +} { + Function {show_text(const char *text)} {open C return_type void + } { + code {char *txt = striphtml(text); +fl_message("%s", txt); +free(txt);} {} + } +} + +Function {find_helpfile(char *buf, int size, const char *argv0)} {open C return_type void +} { + code {fl_filename_absolute(buf, size, argv0); +char *p = (char *)fl_filename_name(buf); +strncpy(p, "../share/doc/teapot/html/index.html", buf+size-p); +buf[size-1] = 0; + +// Check if help exists in default installed location, fallback value is valid for build directory +int test = open(buf, O_RDONLY); +if (test < 0) strncpy(p, "html/index.html", buf+size-p); +else close(test); +buf[size-1] = 0; + +// Try the configure-time determined value +test = open(buf, O_RDONLY); +if (test < 0) strncpy(buf, HELPFILE, size); +else close(test); +buf[size-1] = 0; + +// Fall back to a sane value for unixoid systems +test = open(buf, O_RDONLY); +if (test < 0) strncpy(buf, "/usr/share/doc/teapot/html/index.html", size); +else close(test); +buf[size-1] = 0; + +} {} +} diff --git a/func.c b/func.c new file mode 100644 index 0000000..62a2b28 --- /dev/null +++ b/func.c @@ -0,0 +1,1414 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#define _XOPEN_SOURCE /* glibc2 needs this */ + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +extern double strtod(const char *nptr, char **endptr); /* SunOS 4 hack */ +#include +#include +#include + + +#include "default.h" +#include "eval.h" +#include "func.h" +#include "main.h" +#include "misc.h" +#include "parser.h" +#include "scanner.h" +#include "sheet.h" +/*}}}*/ +/* #defines */ /*{{{*/ +/* There is a BSD extensions, but they are possibly more exact. */ +#ifdef M_E +#define CONST_E M_E +#else +#define CONST_E ((double)2.7182818284590452354) +#endif +#ifdef M_PI +#define CONST_PI M_PI +#else +#define CONST_PI ((double)3.14159265358979323846) +#endif +/*}}}*/ + +#ifdef WIN32 +// This strptime implementation Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Implement strptime under windows +static const char* kWeekFull[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; + +static const char* kWeekAbbr[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" +}; + +static const char* kMonthFull[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char* kMonthAbbr[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char* _parse_num(const char* s, int low, int high, int* value) { + const char* p = s; + for (*value = 0; *p && isdigit(*p); ++p) { + *value = (*value) * 10 + *p - '0'; + } + + if (p == s || *value < low || *value > high) return NULL; + return p; +} + +static char* _strptime(const char *s, const char *format, struct tm *tm) { + int i, len; + while (*format && *s) { + if (*format != '%') { + if (*s != *format) return NULL; + + ++format; + ++s; + continue; + } + + ++format; + len = 0; + switch (*format) { + // weekday name. + case 'a': + case 'A': + tm->tm_wday = -1; + for (i = 0; i < 7; ++i) { + len = strlen(kWeekAbbr[i]); + if (strnicmp(kWeekAbbr[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + + len = strlen(kWeekFull[i]); + if (strnicmp(kWeekFull[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + } + if (tm->tm_wday == -1) return NULL; + s += len; + break; + + // month name. + case 'b': + case 'B': + case 'h': + tm->tm_mon = -1; + for (i = 0; i < 12; ++i) { + len = strlen(kMonthAbbr[i]); + if (strnicmp(kMonthAbbr[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + + len = strlen(kMonthFull[i]); + if (strnicmp(kMonthFull[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + } + if (tm->tm_mon == -1) return NULL; + s += len; + break; + + // month [1, 12]. + case 'm': + s = _parse_num(s, 1, 12, &tm->tm_mon); + if (s == NULL) return NULL; + --tm->tm_mon; + break; + + // day [1, 31]. + case 'd': + case 'e': + s = _parse_num(s, 1, 31, &tm->tm_mday); + if (s == NULL) return NULL; + break; + + // hour [0, 23]. + case 'H': + s = _parse_num(s, 0, 23, &tm->tm_hour); + if (s == NULL) return NULL; + break; + + // minute [0, 59] + case 'M': + s = _parse_num(s, 0, 59, &tm->tm_min); + if (s == NULL) return NULL; + break; + + // seconds [0, 60]. 60 is for leap year. + case 'S': + s = _parse_num(s, 0, 60, &tm->tm_sec); + if (s == NULL) return NULL; + break; + + // year [1900, 9999]. + case 'Y': + s = _parse_num(s, 1900, 9999, &tm->tm_year); + if (s == NULL) return NULL; + tm->tm_year -= 1900; + break; + + // year [0, 99]. + case 'y': + s = _parse_num(s, 0, 99, &tm->tm_year); + if (s == NULL) return NULL; + if (tm->tm_year <= 68) { + tm->tm_year += 100; + } + break; + + // arbitray whitespace. + case 't': + case 'n': + while (isspace(*s)) ++s; + break; + + // '%'. + case '%': + if (*s != '%') return NULL; + ++s; + break; + + // All the other format are not supported. + default: + return NULL; + } + ++format; + } + + if (*format) { + return NULL; + } else { + return (char *)s; + } +} + +char* strptime(const char *buf, const char *fmt, struct tm *tm) { + return _strptime(buf, fmt, tm); +} +#endif + +/* sci_func -- map a double to a double */ /*{{{*/ +static Token sci_func(int argc, const Token argv[], double (*func)(double), const char *func_name) +{ + Token result; + + if (argc==1 && argv[0].type==FLOAT) + { + result.type=FLOAT; + errno=0; + result.u.flt=(func)(argv[0].u.flt); + if (errno) + { + result.type=EEK; + result.u.err=malloc(strlen(func_name)+2+strlen(strerror(errno))+1); + sprintf(result.u.err,"%s: %s",func_name,strerror(errno)); + } + } + else + { + if (argc==1 && argv[0].type==INT) + { + result.type=FLOAT; + errno=0; + result.u.flt=(func)((double)argv[0].u.integer); + if (errno) + { + result.type=EEK; + result.u.err=malloc(strlen(func_name)+2+strlen(strerror(errno))+1); + sprintf(result.u.err,"%s: %s",func_name,strerror(errno)); + } + } + else + { + result.type=EEK; + /* This is actually too much, but always enough for %s formats. */ + result.u.err=malloc(strlen(_("Usage: %s(float)"))+strlen(func_name)+1); + sprintf(result.u.err,_("Usage: %s(float)"),func_name); + } + } + return result; +} +/*}}}*/ +/* arsinh */ /*{{{*/ +static double arsinh(double x) +{ + return log(x+sqrt(x*x+1.0)); +} +/*}}}*/ +/* arcosh */ /*{{{*/ +static double arcosh(double x) +{ + if (x>=1.0) return log(x+sqrt(x*x-1.0)); + else { errno=EDOM; return 0.0; } +} +/*}}}*/ +/* artanh */ /*{{{*/ +static double artanh(double x) +{ + if (x>-1.0 && x<1.0) return 0.5*log((1.0+x)/(1.0-x)); + else { errno=EDOM; return 0.0; } +} +/*}}}*/ +/* rad2deg */ /*{{{*/ +static double rad2deg(double x) +{ + return (360.0/(2.0*CONST_PI))*x; +} +/*}}}*/ +/* deg2rad */ /*{{{*/ +static double deg2rad(double x) +{ + return (2.0*CONST_PI/360.0)*x; +} +/*}}}*/ + +/* @ */ /*{{{*/ +static Token at_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(argv!=(Token*)0); + /*}}}*/ + if (argc==0) + /* return value at current location */ /*{{{*/ + return (getvalue(upd_sheet,upd_x,upd_y,upd_z)); + /*}}}*/ + if (argc==1 && argv[0].type==LOCATION) + /* return value at location pointed to by argument */ /*{{{*/ + return (getvalue(upd_sheet,argv[0].u.location[0],argv[0].u.location[1],argv[0].u.location[2])); + /*}}}*/ + else if (argc==1 && (argv[0].type==INT || argv[0].type==EMPTY)) + /* return value at x on current y,z */ /*{{{*/ + return (getvalue(upd_sheet,argv[0].type==INT ? argv[0].u.integer : upd_x,upd_y,upd_z)); + /*}}}*/ + else if (argc==2 && (argv[0].type==INT || argv[0].type==EMPTY) && (argv[1].type==INT || argv[1].type==EMPTY)) + /* return value at x,y on current z */ /*{{{*/ + return (getvalue(upd_sheet,argv[0].type==INT ? argv[0].u.integer : upd_x,argv[1].type==INT ? argv[1].u.integer : upd_y,upd_z)); + /*}}}*/ + else if (argc==3 && (argv[0].type==INT || argv[0].type==EMPTY) && (argv[1].type==INT || argv[1].type==EMPTY) && (argv[2].type==INT || argv[2].type==EMPTY)) + /* return value at x,y,z */ /*{{{*/ + return (getvalue(upd_sheet,argv[0].type==INT ? argv[0].u.integer : upd_x,argv[1].type==INT ? argv[1].u.integer : upd_y,argv[2].type==INT ? argv[2].u.integer : upd_z)); + /*}}}*/ + else + /* return error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: @([integer x][,[integer y][,[integer z]]]) or @(location)"))+1),_("Usage: @([integer x][,[integer y][,[integer z]]]) or @(location)")); + return result; + } + /*}}}*/ +} +/*}}}*/ +/* & */ /*{{{*/ +static Token adr_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(argv!=(Token*)0); + /*}}}*/ + if (argc==3 && (argv[0].type==INT || argv[0].type==EMPTY) && (argv[1].type==INT || argv[1].type==EMPTY) && (argv[2].type==INT || argv[2].type==EMPTY)) + /* result is location of the given position */ /*{{{*/ + { + result.type=LOCATION; + result.u.location[0]=(argv[0].type==INT ? argv[0].u.integer : upd_x); + result.u.location[1]=(argv[1].type==INT ? argv[1].u.integer : upd_y); + result.u.location[2]=(argv[2].type==INT ? argv[2].u.integer : upd_z); + } + /*}}}*/ + else if (argc==2 && (argv[0].type==INT || argv[0].type==EMPTY) && (argv[1].type==INT || argv[1].type==EMPTY)) + /* result is location of the given position in the current z layer */ /*{{{*/ + { + result.type=LOCATION; + result.u.location[0]=(argv[0].type==INT ? argv[0].u.integer : upd_x); + result.u.location[1]=(argv[1].type==INT ? argv[1].u.integer : upd_y); + result.u.location[2]=upd_z; + } + /*}}}*/ + else if (argc==1 && (argv[0].type==INT || argv[0].type==EMPTY)) + /* result is location of the given position in the current y,z layer */ /*{{{*/ + { + result.type=LOCATION; + result.u.location[0]=(argv[0].type==INT ? argv[0].u.integer : upd_x); + result.u.location[1]=upd_y; + result.u.location[2]=upd_z; + } + /*}}}*/ + else if (argc==0) + /* result is location of the current position */ /*{{{*/ + { + result.type=LOCATION; + result.u.location[0]=upd_x; + result.u.location[1]=upd_y; + result.u.location[2]=upd_z; + } + /*}}}*/ + else + /* result is type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: &([integer x][,[integer y][,[integer z]]])"))+1),_("Usage: &([integer x][,[integer y][,[integer z]]])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* x */ /*{{{*/ +static Token x_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==0) + /* result is currently updated x position */ /*{{{*/ + { + result.type=INT; + result.u.integer=upd_x; + } + /*}}}*/ + else if (argc==1 && argv[0].type==LOCATION) + /* return x component of location */ /*{{{*/ + { + result.type=INT; + result.u.integer=argv[0].u.location[0]; + } + /*}}}*/ + else + /* x type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: x([location])"))+1),_("Usage: x([location])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* y */ /*{{{*/ +static Token y_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==0) + /* result is currently updated y position */ /*{{{*/ + { + result.type=INT; + result.u.integer=upd_y; + } + /*}}}*/ + else if (argc==1 && argv[0].type==LOCATION) + /* return y component of location */ /*{{{*/ + { + result.type=INT; + result.u.integer=argv[0].u.location[1]; + } + /*}}}*/ + else + /* y type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: y([location])"))+1),_("Usage: y([location])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* z */ /*{{{*/ +static Token z_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==0) + /* result is currently updated z position */ /*{{{*/ + { + result.type=INT; + result.u.integer=upd_z; + } + /*}}}*/ + else if (argc==1 && argv[0].type==LOCATION) + /* return z component of location */ /*{{{*/ + { + result.type=INT; + result.u.integer=argv[0].u.location[2]; + } + /*}}}*/ + else + /* result is z type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: z([location])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* e */ /*{{{*/ +static Token e_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==0) /* result is constant e */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=CONST_E; + } + /*}}}*/ + else /* result is e type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: e()")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* eval */ /*{{{*/ +static Token eval_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + --max_eval; + if (max_eval<0) + /* nesting error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("nested eval()")); + } + /*}}}*/ + else if (argc==1 && argv[0].type==LOCATION) + /* evaluate expression in cell at given position */ /*{{{*/ + { + Token **contents; + + contents=getcont(upd_sheet,argv[0].u.location[0],argv[0].u.location[1],argv[0].u.location[2],2); + if (contents==(Token**)0) result.type=EMPTY; + else result=eval(contents); + } + /*}}}*/ + else + /* eval type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: eval(location)"))+1),_("Usage: eval(location)")); + } + /*}}}*/ + ++max_eval; + return result; +} +/*}}}*/ +/* error */ /*{{{*/ +static Token error_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(argv!=(Token*)0); + /*}}}*/ + result.type=EEK; + if (argc!=1 || argv[0].type!=STRING) + /* result is type error */ /*{{{*/ + result.u.err=strcpy(malloc(strlen(_("Usage: error(string message)"))+1),_("Usage: error(string message)")); + /*}}}*/ + else + /* result is user defined error */ /*{{{*/ + result.u.err=strcpy(malloc(strlen(argv[0].u.string)+1),argv[0].u.string); + /*}}}*/ + return result; +} +/*}}}*/ +/* string */ /*{{{*/ +static Token string_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + char *buf; + size_t size; + /*}}}*/ + + if (argc==1 && argv[0].type==LOCATION) /* cell to string */ /*{{{*/ + { + buf=(char*)0; + size=0; + do + { + if (buf!=(char*)0) free(buf); + size+=16; + buf=malloc(size); + printvalue(buf,size,0,0,getscientific(upd_sheet,argv[0].u.location[0],argv[0].u.location[1],argv[0].u.location[2]),getprecision(upd_sheet,argv[0].u.location[0],argv[0].u.location[1],argv[0].u.location[2]),upd_sheet,argv[0].u.location[0],argv[0].u.location[1],argv[0].u.location[2]); + } while (strlen(buf)==size-1); + result.type=STRING; + result.u.string=buf; + } + /*}}}*/ + else if (argc==1 && argv[0].type==FLOAT) /* float to string */ /*{{{*/ + { + result.u.string=malloc(def_precision+10); + sprintf(result.u.string,DEF_SCIENTIFIC ? "%.*e" : "%.*f", def_precision,argv[0].u.flt); + result.type=STRING; + } + /*}}}*/ + else if (argc==1 && argv[0].type==INT) /* int to string */ /*{{{*/ + { + int length=2; + int n=argv[0].u.integer; + + while (n!=0) n/=10; + result.u.string=malloc(length); + sprintf(result.u.string,"%ld",argv[0].u.integer); + result.type=STRING; + } + /*}}}*/ + else if (argc==2 && argv[0].type==FLOAT && argv[1].type==INT) /* float to string */ /*{{{*/ + { + result.u.string=malloc(argv[1].u.integer==-1 ? def_precision+10 : argv[1].u.integer+10); + sprintf(result.u.string,DEF_SCIENTIFIC ? "%.*e" : "%.*f",argv[1].u.integer==-1 ? def_precision : (int)(argv[1].u.integer), argv[0].u.flt); + result.type=STRING; + } + /*}}}*/ + else if (argc==3 && argv[0].type==FLOAT && (argv[1].type==INT || argv[1].type==EMPTY) && argv[2].type==INT) /* float to string */ /*{{{*/ + { + result.u.string=malloc((argv[1].type==INT ? argv[1].u.integer : def_precision)+10); + sprintf(result.u.string,argv[2].u.integer ? "%.*e" : "%.*f",argv[1].type==INT && argv[1].u.integer>=0 ? (int)argv[1].u.integer : def_precision, argv[0].u.flt); + result.type=STRING; + } + /*}}}*/ + else /* return string type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: string(location) or string(float[,[integer][,integer]])"))+1),_("Usage: string(location) or string(float[,[integer][,integer]])")); + return result; + } + /*}}}*/ + return result; +} +/*}}}*/ +/* sum */ /*{{{*/ +static Token sum_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==2 && argv[0].type==LOCATION && argv[1].type==LOCATION) /* result is sum */ /*{{{*/ + { + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + Token tmp; + /*}}}*/ + + x1=argv[0].u.location[0]; x2=argv[1].u.location[0]; posorder(&x1,&x2); + y1=argv[0].u.location[1]; y2=argv[1].u.location[1]; posorder(&y1,&y2); + z1=argv[0].u.location[2]; z2=argv[1].u.location[2]; posorder(&z1,&z2); + result.type=EMPTY; + for (x=x1; x<=x2; ++x) + for (y=y1; y<=y2; ++y) + for (z=z1; z<=z2; ++z) + { + Token t; + + tmp=tadd(result,t=getvalue(upd_sheet,x,y,z)); + tfree(&t); + tfree(&result); + result=tmp; + if (result.type==EEK) return result; + } + } + /*}}}*/ + else /* result is sum type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: sum(location,location)"))+1),_("Usage: sum(location,location)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* n */ /*{{{*/ +static Token n_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==2 && argv[0].type==LOCATION && argv[1].type==LOCATION) + /* result is number of elements */ /*{{{*/ + { + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + Token tmp; + int n; + /*}}}*/ + + x1=argv[0].u.location[0]; x2=argv[1].u.location[0]; posorder(&x1,&x2); + y1=argv[0].u.location[1]; y2=argv[1].u.location[1]; posorder(&y1,&y2); + z1=argv[0].u.location[2]; z2=argv[1].u.location[2]; posorder(&z1,&z2); + n=0; + for (x=x1; x<=x2; ++x) + for (y=y1; y<=y2; ++y) + for (z=z1; z<=z2; ++z) + { + tmp=getvalue(upd_sheet,x,y,z); + if (tmp.type!=EMPTY) ++n; + tfree(&tmp); + } + result.type=INT; + result.u.integer=n; + } + /*}}}*/ + else + /* result is n type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: n(location,location)"))+1),_("Usage: n(location,location)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* int */ /*{{{*/ +static Token int_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==1 && argv[0].type==FLOAT) + /* result is integer with cutoff fractional part */ /*{{{*/ + { + result.type=INT; + result.u.integer=(long)(argv[0].u.flt); + } + /*}}}*/ + else if (argc==3 && argv[0].type==FLOAT && argv[1].type==INT && argv[2].type==INT) + /* result is integer with given conversion */ /*{{{*/ + { + result.type=INT; + if (argv[0].u.flt<0) + { + if (argv[1].u.integer<-1) result.u.integer=(long)floor(argv[0].u.flt); + else if (argv[1].u.integer==-1) result.u.integer=(long)(argv[0].u.flt-0.5); + else if (argv[1].u.integer==0) result.u.integer=(long)(argv[0].u.flt); + else if (argv[1].u.integer==1) result.u.integer=(long)(argv[0].u.flt+0.5); + else result.u.integer=(long)ceil(argv[0].u.flt); + } + else + { + if (argv[2].u.integer<-1) result.u.integer=(long)floor(argv[0].u.flt); + else if (argv[2].u.integer==-1) result.u.integer=(long)(argv[0].u.flt-0.5); + else if (argv[2].u.integer==0) result.u.integer=(long)(argv[0].u.flt); + else if (argv[2].u.integer==1) result.u.integer=(long)(argv[0].u.flt+0.5); + else result.u.integer=(long)ceil(argv[0].u.flt); + } + } + /*}}}*/ + else if (argc==1 && argv[0].type==STRING) + /* result is integer */ /*{{{*/ + { + char *s; + + errno=0; + result.u.integer=strtol(argv[0].u.string,&s,10); + if (s==(char*)0 || *s) + { + result.type=EEK; + result.u.err=mystrmalloc(_("int(string): invalid string")); + } + else if (errno==ERANGE && (result.u.integer==LONG_MAX || result.u.integer==LONG_MIN)) + { + result.type=EEK; + result.u.err=mystrmalloc(_("int(string): domain error")); + } + else result.type=INT; + } + /*}}}*/ + else + /* result is int type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: int(float[,integer,integer])"))+1),_("Usage: int(float[,integer,integer])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* frac */ /*{{{*/ +static Token frac_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + double foo; + /*}}}*/ + + if (argc==1 && argv[0].type==FLOAT) + /* result is fractional part */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=modf(argv[0].u.flt,&foo); + } + /*}}}*/ + else + /* result is frac type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("Usage: frac(float)"))+1),_("Usage: frac(float)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* len */ /*{{{*/ +static Token len_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==1 && argv[0].type==STRING) + /* result is length */ /*{{{*/ + { + result.type=INT; + result.u.integer=strlen(argv[0].u.string); + } + /*}}}*/ + else + /* result is frac type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: len(string)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* log */ /*{{{*/ +static Token log_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + double x=-1.0,y=-1.0; + Token result; + /*}}}*/ + + /* set x and y to first two arguments */ /*{{{*/ + if (argc>=1) + { + if (argv[0].type==FLOAT) x=argv[0].u.flt; + else if (argv[0].type==INT) x=(double)argv[0].u.integer; + } + if (argc==2) + { + if (argv[1].type==FLOAT) y=argv[1].u.flt; + else if (argv[1].type==INT) y=(double)argv[1].u.integer; + } + /*}}}*/ + if (argc==1 && (argv[0].type==FLOAT || argv[0].type==INT)) /* result is ln(x) */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=log(x); + } + /*}}}*/ + else if (argc==2 && (argv[0].type==FLOAT || argv[0].type==INT) && (argv[1].type==FLOAT || argv[1].type==INT)) /* result is ln(x)/ln(y) */ /*{{{*/ + { + result.type=FLOAT; + if (y==CONST_E) result.u.flt=log(x); + else if (y==10.0) result.u.flt=log10(x); + else result.u.flt=log(x)/log(y); + } + /*}}}*/ + else /* result is log type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: log(float[,float])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* minmax */ /*{{{*/ +static Token minmax_func(int argc, const Token argv[], int min) +{ + /* variables */ /*{{{*/ + Token result; + int resultx,resulty,resultz; + /*}}}*/ + + if (argc==2 && argv[0].type==LOCATION && argv[1].type==LOCATION) + /* result is min/max */ /*{{{*/ + { + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + Token tmp; + /*}}}*/ + + x1=argv[0].u.location[0]; x2=argv[1].u.location[0]; posorder(&x1,&x2); + y1=argv[0].u.location[1]; y2=argv[1].u.location[1]; posorder(&y1,&y2); + z1=argv[0].u.location[2]; z2=argv[1].u.location[2]; posorder(&z1,&z2); + result=getvalue(upd_sheet,x1,y1,z1); + resultx=x1; + resulty=y1; + resultz=z1; + for (x=x1; x<=x2; ++x) + for (y=y1; y<=y2; ++y) + for (z=z1; z<=z2; ++z) + { + Token t; + + tmp=(min ? tle(result,t=getvalue(upd_sheet,x,y,z)) : tge(result,t=getvalue(upd_sheet,x,y,z))); + if (tmp.type==INT) + /* successful comparison */ /*{{{*/ + { + tfree(&tmp); + if (tmp.u.integer==0) + { + tfree(&result); + result=t; + resultx=x; + resulty=y; + resultz=z; + } + else tfree(&t); + } + /*}}}*/ + else + /* successless comparison, return with error */ /*{{{*/ + { + tfree(&result); + tfree(&t); + return tmp; + } + /*}}}*/ + } + tfree(&result); + result.type=LOCATION; + result.u.location[0]=resultx; + result.u.location[1]=resulty; + result.u.location[2]=resultz; + return result; + } + /*}}}*/ + else + /* result is min/max type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(min ? _("Usage: min(location,location)") : _("Usage: max(location,location)")); + return result; + } + /*}}}*/ +} +/*}}}*/ +/* min */ /*{{{*/ +static Token min_func(int argc, const Token argv[]) +{ + return minmax_func(argc,argv,1); +} +/*}}}*/ +/* max */ /*{{{*/ +static Token max_func(int argc, const Token argv[]) +{ + return minmax_func(argc,argv,0); +} +/*}}}*/ +/* abs */ /*{{{*/ +static Token abs_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==1 && argv[0].type==FLOAT) + /* result is absolute floating point number */ /*{{{*/ + { + result.type=FLOAT; + result.u.flt=fabs(argv[0].u.flt); + } + /*}}}*/ + else if (argc==1 && argv[0].type==INT) + /* result is absolute integer number */ /*{{{*/ + { + result.type=INT; + result.u.integer=(argv[0].u.integer<0 ? -argv[0].u.integer : argv[0].u.integer); + } + /*}}}*/ + else + /* result is abs type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: abs(float|integer)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* $ */ /*{{{*/ +static Token env_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==1 && argv[0].type==STRING) + { + const char *e; + + if ((e=getenv(argv[0].u.string))==(char*)0) e=""; + result.type=STRING; + result.u.string=mystrmalloc(e); + } + else + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: $(string)")); + } + return result; +} +/*}}}*/ +/* float */ /*{{{*/ +static Token float_func(int argc, const Token argv[]) +{ + Token result; + + if (argc==1 && argv[0].type==STRING) + /* convert string to float */ /*{{{*/ + { + char *p; + + result.u.flt=strtod(argv[0].u.string,&p); + if (p!=argv[0].u.string && *p=='\0' && dblfinite(result.u.flt)==(const char*)0) + { + result.type=FLOAT; + } + else + { + result.type=EEK; + result.u.err=mystrmalloc(_("Not a (finite) floating point number")); + } + } + /*}}}*/ + else + /* float type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: float(string)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* strftime */ /*{{{*/ +static Token strftime_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==1 && argv[0].type==STRING) /* format and return string */ /*{{{*/ + { + time_t t; + struct tm *tm; + char s[1024]; + + t=time((time_t*)0); + tm=localtime(&t); + strftime(s,sizeof(s),argv[0].u.string,tm); + s[sizeof(s)-1]='\0'; + result.u.string=mystrmalloc(s); + result.type=STRING; + } + /*}}}*/ + else if (argc==2 && argv[0].type==STRING && argv[1].type==INT) /* format and return string */ /*{{{*/ + { + time_t t; + struct tm *tm; + char s[1024]; + + t=argv[1].u.integer; + tm=localtime(&t); + strftime(s,sizeof(s),argv[0].u.string,tm); + s[sizeof(s)-1]='\0'; + result.u.string=mystrmalloc(s); + result.type=STRING; + } + /*}}}*/ + else /* strftime type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: strftime(string[,integer])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* clock */ /*{{{*/ +static Token clock_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==2 && argv[0].type==INT && argv[1].type==LOCATION) /* clock(condition,location) */ /*{{{*/ + { + if (argv[0].u.integer) clk(upd_sheet,argv[1].u.location[0],argv[1].u.location[1],argv[1].u.location[2]); + result.type=EMPTY; + } + /*}}}*/ + else if (argc==3 && argv[0].type==INT && argv[1].type==LOCATION && argv[2].type==LOCATION) /* clock(condition,location,location) */ /*{{{*/ + { + if (argv[0].u.integer) + { + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + /*}}}*/ + + x1=argv[1].u.location[0]; x2=argv[2].u.location[0]; posorder(&x1,&x2); + y1=argv[1].u.location[1]; y2=argv[2].u.location[1]; posorder(&y1,&y2); + z1=argv[1].u.location[2]; z2=argv[2].u.location[2]; posorder(&z1,&z2); + for (x=x1; x<=x2; ++x) + for (y=y1; y<=y2; ++y) + for (z=z1; z<=z2; ++z) clk(upd_sheet,x,y,z); + } + result.type=EMPTY; + } + /*}}}*/ + else /* wrong usage */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: clock(condition,location[,location])")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* poly */ /*{{{*/ +static Token poly_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + int i; + /*}}}*/ + + for (i=0; i l ) b = l; + if( e > l ) e = l; + n = e - b + 1; + if( n >= 1024 ) n = 1024 - 1; + if(n > 0) { + ss[n] = '\0'; + strncpy(ss, argv[0].u.string + b, n); + result.type=STRING; + result.u.string=mystrmalloc(ss); + } + else { + result.type=EMPTY; + } + } + else + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: substr(string,integer,integer)")); + } + return result; +} +/*}}}*/ +/* strptime */ /*{{{*/ +static Token strptime_func(int argc, const Token argv[]) +{ + /* variables */ /*{{{*/ + Token result; + /*}}}*/ + + if (argc==2 && argv[0].type==STRING && argv[1].type==STRING) /* format and return string */ /*{{{*/ + { + time_t t; + struct tm tm; + + t=time((time_t*)0); + tm=*localtime(&t); + strptime(argv[1].u.string,argv[0].u.string,&tm); + result.u.integer=mktime(&tm); + result.type=INT; + } + /*}}}*/ + else /* strftime type error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: strptime(string,string)")); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* time */ /*{{{*/ +static Token time_func(int argc, const Token argv[]) +{ + Token result; + + if (argc==0) + { + result.type=INT; + result.u.integer=time((time_t*)0); + } + else + { + result.type=EEK; + result.u.err=mystrmalloc(_("Usage: time()")); + } + return result; +} +/*}}}*/ + +/* table of functions */ /*{{{*/ +/* The order of these entries has no influence on performance, but to stay + compatible, new entries should be appended. */ +Tfunc tfunc[]= +{ + { "@", at_func }, + { "&", adr_func }, + { "x", x_func }, + { "y", y_func }, + { "z", z_func }, + { "eval", eval_func }, + { "error", error_func }, + { "string", string_func }, + { "sum", sum_func }, + { "n", n_func }, + { "int", int_func }, + { "frac", frac_func }, + { "len", len_func }, + { "min", min_func }, + { "max", max_func }, + { "abs", abs_func }, + { "$", env_func }, + { "float", float_func }, + { "strftime", strftime_func }, + { "clock", clock_func }, + { "poly", poly_func }, + { "e", e_func }, + { "log", log_func }, + { "sin", sin_func }, + { "cos", cos_func }, + { "tan", tan_func }, + { "sinh", sinh_func }, + { "cosh", cosh_func }, + { "tanh", tanh_func }, + { "asin", asin_func }, + { "acos", acos_func }, + { "atan", atan_func }, + { "arsinh", arsinh_func }, + { "arcosh", arcosh_func }, + { "artanh", artanh_func }, + { "deg2rad", deg2rad_func }, + { "rad2deg", rad2deg_func }, + { "rnd", rnd_func }, + { "substr", substr_func }, + { "strptime", strptime_func }, + { "time", time_func }, + { "", (Token (*)(int, const Token[]))0 } +}; +/*}}}*/ diff --git a/func.h b/func.h new file mode 100644 index 0000000..14c0277 --- /dev/null +++ b/func.h @@ -0,0 +1,14 @@ +#ifndef FUNC_H +#define FUNC_H + +#include "scanner.h" + +typedef struct +{ + const char name[20]; + Token (*func)(int, const Token[]); +} Tfunc; + +extern Tfunc tfunc[]; + +#endif diff --git a/graph.c b/graph.c new file mode 100644 index 0000000..de660ff --- /dev/null +++ b/graph.c @@ -0,0 +1,247 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +double strtod(const char *nptr, char **endptr); /* SunOS 4 hack */ +#ifdef OLD_REALLOC +#define realloc(s,l) myrealloc(s,l) +#endif +extern char *optarg; +extern int optind,opterr,optopt; +int getopt(int argc, char * const *argv, const char *optstring); + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include "csv.h" +/*}}}*/ + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + struct Pair + { + const char *label; + double value; + int special; + } *pair; + int pairs; + double height,width; + int box; + int nl; + /*}}}*/ + + /* parse options */ /*{{{*/ + { + int c; + height=width=2.0; + box=0; + nl=1; + while ((c=getopt(argc,argv,"bnw:h:?"))!=EOF) switch (c) + { + /* w width */ /*{{{*/ + case 'w': + { + char *end; + + width=strtod(optarg,&end); + if (*end!='\0') + { + fprintf(stderr,"graph: invalid width\n"); + exit(1); + } + break; + } + /*}}}*/ + /* h height */ /*{{{*/ + case 'h': + { + char *end; + + height=strtod(optarg,&end); + if (*end!='\0') + { + fprintf(stderr,"graph: invalid height\n"); + exit(1); + } + break; + } + /*}}}*/ + /* n */ + case 'n': + { + nl=0; + break; + } + /* b */ + case 'b': + { + box=1; + break; + } + default: + { + fprintf(stderr,"Usage: graph [-b][-h height][-w width]\n"); + exit(1); + } + } + } + /*}}}*/ + /* get data */ /*{{{*/ + { + char ln[256]; + int line,pairsz; + + pairs=pairsz=0; + pair=(struct Pair*)0; + for (line=1; fgets(ln,sizeof(ln),stdin); ++line) + { + const char *s; + const char *end; + + if (pairs==pairsz) + { + if ((pair=realloc(pair,sizeof(struct Pair)*(pairsz+=128)))==(struct Pair*)0) + { + fprintf(stderr,"graph:%d:out of memory\n",line); + exit(1); + } + } + s=ln; + pair[pairs].label=csv_string(s,&end); + if (s==end) fprintf(stderr,"graph:%d:invalid string, ignoring record\n",line); + else + { + s=end; + pair[pairs].value=csv_double(s,&end); + if (s==end) fprintf(stderr,"graph:%d:invalid value, ignoring record\n",line); + else + { + s=end; + pair[pairs].special=csv_double(s,&end); + if (s==end && *s!='\n') fprintf(stderr,"graph:%d:invalid mark value, ignoringrecord\n",line); + else + { + s=end; + if (*s!='\n') fprintf(stderr,"graph:%d:trailing garbage\n",line); + } + ++pairs; + } + } + } + } + /*}}}*/ + + if (box) /* make box graph */ /*{{{*/ + { + double height,boxwid,min,max,scale; + int i; + + height=2; + boxwid=width/pairs; + for (min=max=0.0,i=0; imax) max=pair[i].value; + } + scale=height/(max-min); + printf("[\nFRAME: box invis wid %f ht %f\n",width,height); + for (i=0; i0 ? "sw" : "nw", + i*boxwid, + -min*scale, + pair[i].label + ); + } + printf("]"); if (nl) printf("\n"); + } + /*}}}*/ + else /* make pie graph */ /*{{{*/ + { + int anyspecial,i; + double sum,scale,extra,arc; + + for (sum=0.0,i=0,anyspecial=0; i +#include +#include +#include +#include +#include + + +#include "htmlio.h" +#include "main.h" +#include "misc.h" +/*}}}*/ + +/* savehtml -- save as HTML table */ /*{{{*/ +const char *savehtml(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count) +{ + /* variables */ /*{{{*/ + FILE *fp=(FILE*)0; /* cause runtime error */ + int x,y,z; + char buf[1024]; + char num[20]; + char fullname[PATH_MAX]; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sheet!=(Sheet*)0); + assert(name!=(const char*)0); + /*}}}*/ + *count=0; + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column"); + if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno); + for (z=z1; z<=z2; ++z) + { + if (body) /* open new file */ /*{{{*/ + { + sprintf(num,".%d",z); + + fullname[sizeof(fullname)-strlen(num)-1]='\0'; + (void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + (void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno); + } + /*}}}*/ + else /* print header */ /*{{{*/ + if (fputs_close("\n\n\n\n\n\n",fp)==EOF) return strerror(errno); + /*}}}*/ + + if (fputs_close("\n",fp)==EOF) return strerror(errno); + for (y=y1; y<=y2; ++y) /* print contents */ /*{{{*/ + { + if (fputs_close("",fp)==EOF) return strerror(errno); + for (x=x1; x<=x2; ) + { + int multicols; + char *bufp; + + for (multicols=x+1; multicolsdimx && shadowed(sheet,multicols,y,z); ++multicols); + multicols=multicols-x; + if (multicols>1) fprintf(fp,"",fp)==EOF) return strerror(errno); + x+=multicols; + ++*count; + } + if (fputs_close("\n",fp)==EOF) return strerror(errno); + } + /*}}}*/ + if (fputs_close("
",fp)==EOF) return strerror(errno); break; + case RIGHT: if (fputs_close(" align=right>",fp)==EOF) return strerror(errno); break; + case CENTER: if (fputs_close(" align=center>",fp)==EOF) return strerror(errno); break; + default: assert(0); + } + printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z); + if (transparent(sheet,x,y,z)) + { + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + else for (bufp=buf; *bufp; ++bufp) switch (*bufp) + { + case '<': if (fputs_close("<",fp)==EOF) return strerror(errno); break; + case '>': if (fputs_close(">",fp)==EOF) return strerror(errno); break; + case '&': if (fputs_close("&",fp)==EOF) return strerror(errno); break; + case '"': if (fputs_close(""",fp)==EOF) return strerror(errno); break; + default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno); + } + if (fputs_close("
\n",fp)==EOF) return strerror(errno); + if (body) + { + if (fclose(fp)==EOF) return strerror(errno); + } + } + if (!body) + { + if (fputs_close("\n\n",fp)==EOF) return strerror(errno); + if (fclose(fp)==EOF) return strerror(errno); + } + return (const char*)0; +} +/*}}}*/ diff --git a/htmlio.h b/htmlio.h new file mode 100644 index 0000000..248fd4f --- /dev/null +++ b/htmlio.h @@ -0,0 +1,8 @@ +#ifndef HTML_H +#define HTML_H + +#include "sheet.h" + +const char *savehtml(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count); + +#endif diff --git a/latex.c b/latex.c new file mode 100644 index 0000000..0c2462d --- /dev/null +++ b/latex.c @@ -0,0 +1,138 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include + + +#include "latex.h" +#include "main.h" +#include "misc.h" +/*}}}*/ + +/* savelatex -- save as LaTeX table */ /*{{{*/ +const char *savelatex(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count) +{ + /* variables */ /*{{{*/ + FILE *fp=(FILE*)0; /* cause runtime error */ + int x,y,z; + char buf[1024]; + char num[20]; + char fullname[PATH_MAX]; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sheet!=(Sheet*)0); + assert(name!=(const char*)0); + /*}}}*/ + *count=0; + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column"); + if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno); + for (z=z1; z<=z2; ++z) + { + if (body) + /* open new file */ /*{{{*/ + { + sprintf(num,".%d",z); + + fullname[sizeof(fullname)-strlen(num)-1]='\0'; + (void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + (void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno); + } + /*}}}*/ + else + /* print header */ /*{{{*/ + if (z==z1) + { + if (fputs_close("\\documentclass{article}\n\\usepackage{longtable}\n\\begin{document}\n\\setlongtables\n\\vfill\n",fp)==EOF) return strerror(errno); + } + else + { + if (fputs_close("\\clearpage\n\\vfill\n",fp)==EOF) return strerror(errno); + } + /*}}}*/ + + /* print bogus format */ /*{{{*/ + fprintf(fp,"\\begin{longtable}{"); + for (x=x1; x<=x2; ++x) if (fputc_close('l',fp)==EOF) return strerror(errno); + fprintf(fp,"}\n"); + /*}}}*/ + for (y=y1; y<=y2; ++y) + /* print contents */ /*{{{*/ + { + for (x=x1; x<=x2; ) + { + int multicols; + char *bufp; + + if (x>x1 && fputc_close('&',fp)==EOF) return strerror(errno); + for (multicols=x+1; multicolsdimx && shadowed(sheet,multicols,y,z); ++multicols); + multicols=multicols-x; + fprintf(fp,"\\multicolumn{%d}{",multicols); + switch (getadjust(sheet,x,y,z)) + { + case LEFT: if (fputc_close('l',fp)==EOF) return strerror(errno); break; + case RIGHT: if (fputc_close('r',fp)==EOF) return strerror(errno); break; + case CENTER: if (fputc_close('c',fp)==EOF) return strerror(errno); break; + default: assert(0); + } + printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z); + if (fputs_close("}{",fp)==EOF) return strerror(errno); + if (transparent(sheet,x,y,z)) + { + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + else for (bufp=buf; *bufp; ++bufp) switch (*bufp) + { + case '%': + case '$': + case '&': + case '#': + case '_': + case '{': + case '}': + case '~': + case '^': if (fputc_close('\\',fp)==EOF || fputc_close(*bufp,fp)==EOF) return strerror(errno); break; + case '\\': if (fputs_close("\\backslash ",fp)==EOF) return strerror(errno); break; + default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno); + } + if (fputc_close('}',fp)==EOF) return strerror(errno); + x+=multicols; + ++*count; + } + if (fputs_close(y +#include +#include +#include +#include +#include +#include +#include +extern char *optarg; +extern int optind,opterr,optopt; +int getopt(int argc, char * const *argv, const char *optstring); +#include +#include + + +#include "default.h" +#include "display.h" +#include "eval.h" +#include "htmlio.h" +#include "latex.h" +#include "context.h" +#include "main.h" +#include "misc.h" +#include "sc.h" +#include "scanner.h" +#include "utf8.h" +#include "parser.h" +#include "sheet.h" +#include "wk1.h" +/*}}}*/ + +/* variables */ /*{{{*/ +char helpfile[PATH_MAX]; +int batch=0; +unsigned int batchln=0; +int def_precision=DEF_PRECISION; +int quote=0; +int header=1; +static int usexdr=1; +/*}}}*/ + +static void get_mark(Sheet *sheet, int *x1, int *x2, int *y1, int *y2, int *z1, int *z2, int everything) +{ + if (sheet->marking) { + posorder(&sheet->mark1x, &sheet->mark2x); + posorder(&sheet->mark1y, &sheet->mark2y); + posorder(&sheet->mark1z, &sheet->mark2z); + sheet->marking = 0; + } + if (x1) { + if (sheet->mark1x >= 0) { + *x1 = sheet->mark1x; + *x2 = sheet->mark2x; + *y1 = sheet->mark1y; + *y2 = sheet->mark2y; + *z1 = sheet->mark1z; + *z2 = sheet->mark2z; + } else if (everything) { + *x1 = 0; + *x2 = sheet->dimx-1; + *y1 = 0; + *y2 = sheet->dimy-1; + *z1 = 0; + *z2 = sheet->dimz-1; + } else { + *x1 = *x2 = sheet->curx; + *y1 = *y2 = sheet->cury; + *z1 = *z2 = sheet->curz; + } + } +} + +void moveto(Sheet *sheet, int x, int y, int z) +{ + int need_redraw = 0; + int xdir = x > sheet->curx?1:-1; + + if (x >= 0) sheet->curx = x; + if (y >= 0) sheet->cury = y; + if (z >= 0) need_redraw++, sheet->curz = z; + while (sheet->curx > 0 && shadowed(sheet, sheet->curx, sheet->cury, sheet->curz)) sheet->curx += xdir; + + if (sheet->marking) { + sheet->mark2x = sheet->curx; + sheet->mark2y = sheet->cury; + sheet->mark2z = sheet->curz; + } + + if (sheet->curx <= sheet->offx && sheet->offx) need_redraw++, sheet->offx = (sheet->curx?sheet->curx-1:0); + if (sheet->cury <= sheet->offy && sheet->offy) need_redraw++, sheet->offy = (sheet->cury?sheet->cury-1:0); + if (sheet->curx >= sheet->offx+sheet->maxx) need_redraw++, sheet->offx = sheet->curx-sheet->maxx+2; + if (sheet->cury >= sheet->offy+sheet->maxy) need_redraw++, sheet->offy = sheet->cury-sheet->maxy+2; + + if (need_redraw) redraw_sheet(sheet); + else if (x != sheet->curx || y != sheet->cury || z != sheet->curz) redraw_cell(sheet, sheet->curx, sheet->cury, sheet->curz); +} + +void relmoveto(Sheet *sheet, int x, int y, int z) +{ + moveto(sheet, sheet->curx+x, sheet->cury+y, (z?sheet->curz+z:-1)); +} + +/* line_numedit -- number line editor function */ /*{{{*/ +static int line_numedit(int *n, const char *prompt, size_t *x, size_t *offx) +{ + /* variables */ /*{{{*/ + char buf[20]; + const char *s; + Token **t; + int c; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(prompt!=(char*)0); + assert(x!=(size_t*)0); + assert(offx!=(size_t*)0); + /*}}}*/ + t=(Token**)0; + sprintf(buf,"%d",*n); + s=buf+strlen(buf); + do + { + tvecfree(t); + *x=s-buf; + if ((c=line_edit((Sheet*)0,buf,sizeof(buf),prompt,x,offx))<0) return c; + s=buf; + t=scan(&s); + } while ((*s!='\0' && t==(Token**)0) || !(t!=(Token**)0 && ((*t)==(Token*)0 || ((*t)->type==INT && (*t)->u.integer>=0 && *(t+1)==(Token*)0)))); + if (t==(Token**)0 || *t==(Token*)0) *n=-1; + else *n=(*t)->u.integer; + tvecfree(t); + return 0; +} +/*}}}*/ +/* line_lidedit -- label identifier line editor function */ /*{{{*/ +static int line_idedit(char *ident, size_t size, const char *prompt, size_t *x, size_t *offx) +{ + /* variables */ /*{{{*/ + const char *s; + Token **t; + int c; + /*}}}*/ + + t=(Token**)0; + s=ident+strlen(ident); + do + { + tvecfree(t); + *x=s-ident; + if ((c=line_edit((Sheet*)0,ident,size,prompt,x,offx))<0) return c; + s=ident; + t=scan(&s); + } while ((*s!='\0' && t==(Token**)0) || !(t!=(Token**)0 && ((*t)==(Token*)0 || ((*t)->type==LIDENT && *(t+1)==(Token*)0)))); + tvecfree(t); + return 0; +} +/*}}}*/ +/* doanyway -- ask if action should be done despite unsaved changes */ /*{{{*/ +int doanyway(Sheet *sheet, const char *msg) +{ + int result; + + if (sheet->changed) { + result=line_ok(msg,0); + if (result < 0) return 0; + return result; + } + return 1; +} +/*}}}*/ + +/* do_edit -- set or modify cell contents */ /*{{{*/ +static int do_edit(Sheet *cursheet, Key c, const char *expr, int clocked) +{ + /* variables */ /*{{{*/ + char buf[1024]; + const char *s; + size_t x,offx; + int curx,cury,curz; + Token **t; + /*}}}*/ + + if (locked(cursheet,cursheet->curx,cursheet->cury,cursheet->curz)) line_msg(_("Edit cell:"),_("Cell is locked")); + else + { + curx=cursheet->curx; + cury=cursheet->cury; + curz=cursheet->curz; + if (expr) + { + s=expr; + t=scan(&s); + if (*s!='\0' && t==(Token**)0) line_msg(clocked ? _("Clocked cell contents:") : _("Cell contents:"),"XXX invalid expression"); + } + else + { + offx=0; + if (c==K_NONE) + { + print(buf,sizeof(buf),0,1,getscientific(cursheet,cursheet->curx,cursheet->cury,cursheet->curz),-1,getcont(cursheet,cursheet->curx,cursheet->cury,cursheet->curz,clocked)); + s=buf+strlen(buf); + } + else if (c==K_BACKSPACE) + { + print(buf,sizeof(buf),0,1,getscientific(cursheet,cursheet->curx,cursheet->cury,cursheet->curz),-1,getcont(cursheet,cursheet->curx,cursheet->cury,cursheet->curz,clocked)); + if (strlen(buf)) *mbspos(buf+strlen(buf),-1)='\0'; + s=buf+strlen(buf); + } + else if (c==K_DC) + { + print(buf,sizeof(buf),0,1,getscientific(cursheet,cursheet->curx,cursheet->cury,cursheet->curz),-1,getcont(cursheet,cursheet->curx,cursheet->cury,cursheet->curz,clocked)); + memmove(buf,mbspos(buf,1),strlen(mbspos(buf,1))+1); + s=buf; + } + else if (isalpha(c)) + { + buf[0] = '"'; + buf[1] = c; + buf[2] = 0; + s=buf+2; + } + else + { + if (c < 256) buf[0]=c; + else buf[0] = 0; + buf[1]='\0'; + s=buf+1; + } + do + { + int r; + + x=mbslen(buf)-mbslen(s); + if ((r=line_edit(cursheet,buf,sizeof(buf),clocked ? _("Clocked cell contents:") : _("Cell contents:"),&x,&offx))<0) return r; + s=buf; + if (buf[0] == '"' && buf[strlen(buf)-1] != '"' && strlen(buf)+1 < sizeof(buf)) { + buf[strlen(buf)+1] = 0; + buf[strlen(buf)] = '"'; + } + t=scan(&s); + } while (*s!='\0' && t==(Token**)0); + } + if (t!=(Token**)0 && *t==(Token*)0) { free(t); t=(Token**)0; } + moveto(cursheet,curx,cury,curz); + putcont(cursheet,cursheet->curx,cursheet->cury,cursheet->curz,t,clocked); + forceupdate(cursheet); + } + return 0; +} +/*}}}*/ +/* do_label -- modify cell label */ /*{{{*/ +static int do_label(Sheet *sheet) +{ + /* variables */ /*{{{*/ + char buf[1024],oldlabel[1024]; + size_t edx,offx,ok; + Token t; + int x,y,z,x1,y1,z1,x2,y2,z2; + int c; + /*}}}*/ + + assert(sheet!=(Sheet*)0); + if (sheet->mark1x==-1 && locked(sheet,sheet->curx,sheet->cury,sheet->curz)) line_msg(_("Cell label:"),_("Cell is locked")); + else + { + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + ok=edx=offx=0; + (void)strcpy(buf,getlabel(sheet,sheet->curx,sheet->cury,sheet->curz)); + (void)strcpy(oldlabel,buf); + do + { + if ((c=line_idedit(buf,sizeof(buf),_("Cell label:"),&edx,&offx))<0) return c; + if (buf[0]=='\0') ok=1; + else + { + ok=((t=findlabel(sheet,buf)).type==EEK || (t.type==LOCATION && t.u.location[0]==sheet->curx && t.u.location[1]==sheet->cury && t.u.location[2]==sheet->curz)); + tfree(&t); + } + } while (!ok); + setlabel(sheet,sheet->curx,sheet->cury,sheet->curz,buf,1); + if (buf[0]!='\0') for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) relabel(sheet,oldlabel,buf,x,y,z); + } + return -1; +} +/*}}}*/ +/* do_columnwidth -- set the column width */ /*{{{*/ +static int do_columnwidth(Sheet *cursheet) +{ + /* variables */ /*{{{*/ + size_t edx,offx; + int n; + int x,x1,x2,z,z1,z2; + int c; + /*}}}*/ + + offx=0; + edx=0; + n=columnwidth(cursheet,cursheet->curx,cursheet->curz); + do if ((c=line_numedit(&n,_("Column width:"),&edx,&offx))<0) return c; while (n<=0); + if (cursheet->mark1x==-1) + /* range is the current cell */ /*{{{*/ + { + x1=x2=cursheet->curx; + z1=z2=cursheet->curz; + } + /*}}}*/ + else + /* range is the marked cube */ /*{{{*/ + { + x1=cursheet->mark1x; x2=cursheet->mark2x; + z1=cursheet->mark1z; z2=cursheet->mark2z; + } + /*}}}*/ + for (x=x1; x<=x2; ++x) for (z=z1; z<=z2; ++z) setwidth(cursheet,x,z,n); + return -1; +} +/*}}}*/ +/* do_attribute -- set cell attributes */ /*{{{*/ +static void do_attribute(Sheet *cursheet, Key action) +{ + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + int c = 0; + /*}}}*/ + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + + if (action != ADJUST_LOCK && cursheet->mark1x==-1 && action != ADJUST_LOCK && locked(cursheet,cursheet->curx,cursheet->cury,cursheet->curz)) + { + line_msg(_("Cell attribute:"),_("Cell is locked")); + return; + } + switch ((int)action) + { + /* 0 -- adjust left */ /*{{{*/ + case ADJUST_LEFT: + { + if (cursheet->mark1x != -1 && line_ok(_("Make block left-adjusted:"), 0) <= 0) break; + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) setadjust(cursheet,x,y,z,LEFT); + break; + } + /*}}}*/ + /* 1 -- adjust right */ /*{{{*/ + case ADJUST_RIGHT: + { + if (cursheet->mark1x != -1 && line_ok(_("Make block right-adjusted:"), 0) <= 0) break; + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) setadjust(cursheet,x,y,z,RIGHT); + break; + } + /*}}}*/ + /* 2 -- adjust centered */ /*{{{*/ + case ADJUST_CENTER: + { + if (cursheet->mark1x != -1 && line_ok(_("Make block centered:"), 0) <= 0) break; + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) setadjust(cursheet,x,y,z,CENTER); + break; + } + /*}}}*/ + /* 3 -- set scientific notation flag */ /*{{{*/ + case ADJUST_SCIENTIFIC: + { + int n; + + if (cursheet->mark1x==-1) n = !getscientific(cursheet,x1,y1,z1); + else n = line_ok(_("Make block notation scientific:"), getscientific(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) setscientific(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* 5 -- set precision */ /*{{{*/ + case ADJUST_PRECISION: + { + size_t ex,offx; + int n; + + offx=0; + ex=0; + n=getprecision(cursheet,x1,y1,z1); + do if (line_numedit(&n,cursheet->mark1x==-1 ? _("Precision for cell:") : _("Precision for block:"),&ex,&offx)==-1) return; while (n!=-1 && (n==0 || n>20)); + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) setprecision(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* 6 -- shadow */ /*{{{*/ + case ADJUST_SHADOW: + { + int n; + + if (cursheet->mark1x==-1) n = !shadowed(cursheet,x1,y1,z1); + else n = line_ok(_("Shadow block:"), shadowed(cursheet,x1,y1,z1)); + if (x1 == 0 && n == 1) { + line_msg(_("Shadow cell:"),_("You can not shadow cells in column 0")); + break; + } + + if (n >= 0) { + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) + { + int rx; + + if (n==0) for (rx=x+1; shadowed(cursheet,rx,y,z); ++rx) shadow(cursheet,rx,y,z,0); + else if (x>0) shadow(cursheet,x,y,z,1); + } + } + break; + } + /*}}}*/ + /* 7 -- transparent */ /*{{{*/ + case ADJUST_TRANSPARENT: + { + int n; + + if (cursheet->mark1x==-1) n = !transparent(cursheet,x1,y1,z1); + else n = line_ok(_("Make block transparent:"), transparent(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) maketrans(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* 8 -- bold */ /*{{{*/ + case ADJUST_BOLD: + { + int n; + + if (cursheet->mark1x==-1) n = !isbold(cursheet,x1,y1,z1); + else n = line_ok(_("Make block bold:"), isbold(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) bold(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* 9 -- underline */ /*{{{*/ + case ADJUST_UNDERLINE: + { + int n; + + if (cursheet->mark1x==-1) n = !underlined(cursheet,x1,y1,z1); + else n = line_ok(_("Make block underlined:"), underlined(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) underline(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* 1 -- edit label and goto end */ /*{{{*/ + case ADJUST_LABEL: + { + do_label(cursheet); + return; + } + /*}}}*/ + /* 2 -- lock */ /*{{{*/ + case ADJUST_LOCK: + { + int n; + + if (cursheet->mark1x==-1) n = !locked(cursheet,x1,y1,z1); + else n = line_ok(_("Lock block:"), locked(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) lockcell(cursheet,x,y,z,n); + + break; + } + /*}}}*/ + /* 3 -- ignore */ /*{{{*/ + case ADJUST_IGNORE: + { + int n; + + if (cursheet->mark1x==-1) n = !ignored(cursheet,x1,y1,z1); + else n = line_ok(_("Ignore values of all cells in this block:"), ignored(cursheet,x1,y1,z1)); + if (n >= 0) for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) igncell(cursheet,x,y,z,n); + break; + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + if (c>=0) + { + if (cursheet->mark1x==-1) redraw_cell(cursheet, cursheet->curx, cursheet->cury, cursheet->curz); + else redraw_sheet(cursheet); + } + forceupdate(cursheet); + return; +} +/*}}}*/ +/* do_savexdr -- save sheet as XDR file */ /*{{{*/ +static int do_savexdr(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + unsigned int count; + + if (!name) { + name = cursheet->name; + + if (strcmp(name+strlen(name)-3,".tp")) { + snprintf(buf, sizeof(buf), "%s.tp", name); + name = buf; + } + } + + if ((msg = savexdr(cursheet, name, &count))) { + line_msg(_("Save sheet to XDR file:"), msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save sheet to XDR file:"),buf); + return -1; +} +/*}}}*/ +/* do_saveport -- save sheet as portable ASCII file */ /*{{{*/ +static int do_saveport(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + unsigned int count; + + if (!name) name = cursheet->name; + + if ((msg = saveport(cursheet, name, &count))) { + line_msg(_("Save sheet to ASCII file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save sheet to ASCII file:"),buf); + return -1; +} +/*}}}*/ +/* do_savetbl -- save sheet as tbl file */ /*{{{*/ +static int do_savetbl(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int standalone=0; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) { + name = cursheet->name; + if ((standalone=line_ok(_("Save as stand-alone document:"),1))<0) return standalone; + } + + if ((msg = savetbl(cursheet, name, !standalone, x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in tbl format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in tbl format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_savelatex -- save sheet as LaTeX file */ /*{{{*/ +static int do_savelatex(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int standalone=0; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) { + name = cursheet->name; + if ((standalone=line_ok(_("Save as stand-alone document:"),1))<0) return standalone; + } + + if ((msg = savelatex(cursheet, name, !standalone, x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in LaTeX format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in LaTeX format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_savecontext -- save sheet as ConTeXt file */ /*{{{*/ +static int do_savecontext(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int standalone=0; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) { + name = cursheet->name; + if ((standalone=line_ok(_("Save as stand-alone document:"),1))<0) return standalone; + } + + if ((msg = savecontext(cursheet, name, !standalone, x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in ConTeXt format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in ConTeXt format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_savehtml -- save sheet as HTML file */ /*{{{*/ +static int do_savehtml(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int standalone=0; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) { + name = cursheet->name; + if ((standalone=line_ok(_("Save as stand-alone document:"),1))<0) return standalone; + } + + if ((msg = savehtml(cursheet, name, !standalone, x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in HTML format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in HTML format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_savetext -- save sheet as formatted text file */ /*{{{*/ +static int do_savetext(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) name = cursheet->name; + + if ((msg = savetext(cursheet, name, x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in plain text format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in plain text format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_savecsv -- save sheet as CSV file */ /*{{{*/ +static int do_savecsv(Sheet *cursheet, const char *name) +{ + char buf[PATH_MAX]; + const char *msg; + int x1,y1,z1,x2,y2,z2; + unsigned int count; + int sep = 0; + const char seps[4] = ",;\t"; + + get_mark(cursheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + if (!name) { + MenuChoice menu[4]; + name = cursheet->name; + + menu[0].str=mystrmalloc(_("cC)omma (,)")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("sS)emicolon (;)")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("tT)ab (\\t)")); menu[2].c='\0'; + menu[3].str=(char*)0; + sep=line_menu(_("Choose separator:"),menu,0); + if (sep < 0) return sep; + } + + if ((msg = savecsv(cursheet, name, seps[sep], x1, y1, z1, x2, y2, z2, &count))) { + line_msg(_("Save in CSV format to file:"),msg); + return -2; + } + + snprintf(buf, sizeof(buf), _("%u cells written"), count); + if (!batch) line_msg(_("Save in CSV format to file:"), buf); + return -1; +} +/*}}}*/ +/* do_loadxdr -- load sheet from XDR file */ /*{{{*/ +static int do_loadxdr(Sheet *cursheet) +{ + const char *msg; + + if ((msg=loadxdr(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from XDR file:"),msg); + return -1; +} +/*}}}*/ +/* do_loadport -- load sheet from portable ASCII file */ /*{{{*/ +static int do_loadport(Sheet *cursheet) +{ + const char *msg; + /*}}}*/ + + if ((msg=loadport(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from ASCII file:"),msg); + return -1; +} +/*}}}*/ +/* do_loadsc -- load sheet from SC file */ /*{{{*/ +static int do_loadsc(Sheet *cursheet) +{ + const char *msg; + + if ((msg=loadsc(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from SC file:"),msg); + return -1; +} +/*}}}*/ +/* do_loadwk1 -- load sheet from WK1 file */ /*{{{*/ +static int do_loadwk1(Sheet *cursheet) +{ + const char *msg; + + if ((msg=loadwk1(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from WK1 file:"),msg); + return -1; +} +/*}}}*/ +/* do_loadcsv -- load/merge sheet from CSV file */ /*{{{*/ +static int do_loadcsv(Sheet *cursheet) +{ + const char *msg; + + if ((msg=loadcsv(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from CSV file:"),msg); + return -1; +} +/*}}}*/ +/* do_mark -- set mark */ /*{{{*/ +void do_mark(Sheet *cursheet, int force) +{ + if (force==0) + { + if (!cursheet->marking && cursheet->mark1x==-1) force=1; + else if (cursheet->marking) force=2; + else force=3; + } + switch (force) + { + case 1: + { + cursheet->mark1x=cursheet->mark2x=cursheet->curx; + cursheet->mark1y=cursheet->mark2y=cursheet->cury; + cursheet->mark1z=cursheet->mark2z=cursheet->curz; + cursheet->marking=1; + break; + } + case 2: + { + cursheet->marking=0; + break; + } + case 3: + { + cursheet->mark1x=-1; + break; + } + default: assert(0); + } +} +/*}}}*/ +static int do_name(Sheet *cursheet); +/* do_save -- save sheet */ /*{{{*/ +static int do_save(Sheet *cursheet) +{ + const char *ext = cursheet->name; + if (ext==(char*)0) return do_name(cursheet); + + ext += strlen(ext)-1; + + if (!strcmp(ext-3, ".tpa")) return do_saveport(cursheet, NULL); + if (!strcmp(ext-3, ".tbl")) return do_savetbl(cursheet, NULL); + if (!strcmp(ext-5, ".latex")) return do_savelatex(cursheet, NULL); + if (!strcmp(ext-4, ".html")) return do_savehtml(cursheet, NULL); + if (!strcmp(ext-3, ".csv")) return do_savecsv(cursheet, NULL); + if (!strcmp(ext-3, ".txt")) return do_savetext(cursheet, NULL); + if (!strcmp(ext-3, ".tex")) return do_savecontext(cursheet, NULL); + return do_savexdr(cursheet, NULL); +} +/*}}}*/ +/* do_name -- (re)name sheet */ /*{{{*/ +static int do_name(Sheet *cursheet) +{ + const char *name; + + name = line_file(cursheet->name, _("Teapot \t*.tp\nTeapot ASCII \t*.tpa\ntbl \t*.tbl\nLaTeX \t*.latex\nHTML \t*.html\nCSV \t*.csv\nFormatted ASCII \t*.txt\nConTeXt \t*.tex"), _("New file name:"), 1); + if (!name) return -1; + + if (cursheet->name!=(char*)0) free(cursheet->name); + cursheet->name=strdup(name); + return do_save(cursheet); +} +/*}}}*/ +/* do_load -- load sheet */ /*{{{*/ +static int do_load(Sheet *cursheet) +{ + const char *name, *ext; + + if (doanyway(cursheet, _("Sheet modified, load new file anyway?")) != 1) return -1; + + name = line_file(cursheet->name, _("Teapot \t*.tp\nTeapot ASCII \t*.tpa\nSC Spreadsheet Calculator \t*.sc\nLotus 1-2-3 \t*.wk1\nCSV \t*.csv"), _("Load sheet:"), 0); + if (!name) return -1; + if (cursheet->name!=(char*)0) free(cursheet->name); + cursheet->name=strdup(name); + + ext = name+strlen(name)-1; + if (!strcmp(ext-3, ".tpa")) return do_loadport(cursheet); + if (!strcmp(ext-2, ".sc")) return do_loadsc(cursheet); + if (!strcmp(ext-3, ".wk1")) return do_loadwk1(cursheet); + if (!strcmp(ext-3, ".csv")) return do_loadcsv(cursheet); + return do_loadxdr(cursheet); +} +/*}}}*/ + +/* do_clear -- clear block */ /*{{{*/ +static int do_clear(Sheet *sheet) +{ + /* variables */ /*{{{*/ + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + int c; + /*}}}*/ + + if (sheet->mark1x==-1 && locked(sheet,sheet->curx,sheet->cury,sheet->curz)) line_msg(_("Clear cell:"),_("Cell is locked")); + else + { + if (sheet->mark1x!=-1) + { + if ((c=line_ok(_("Clear block:"),0))<0) return c; + else if (c!=1) return -1; + } + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + for (x=x1; x<=x2; ++x) for (y=y1; y<=y2; ++y) for (z=z1; z<=z2; ++z) freecell(sheet,x,y,z); + cachelabels(sheet); + forceupdate(sheet); + } + return -1; +} +/*}}}*/ +/* do_insert -- insert block */ /*{{{*/ +static int do_insert(Sheet *sheet) +{ + /* variables */ /*{{{*/ + int x1,y1,z1,x2,y2,z2,reply; + /*}}}*/ + + /* ask for direction of insertation */ /*{{{*/ + { + MenuChoice menu[4]; + + menu[0].str=mystrmalloc(_("cC)olumn")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("rR)ow")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("dD)epth")); menu[2].c='\0'; + menu[3].str=(char*)0; + reply=line_menu(_("Insert:"),menu,0); + free(menu[0].str); + free(menu[1].str); + free(menu[2].str); + if (reply<0) return reply; + } + /*}}}*/ + if (sheet->mark1x==-1) + /* ask if current cell or whole dimension should be used */ /*{{{*/ + { + /* variables */ /*{{{*/ + MenuChoice menu[3]; + int r; + /*}}}*/ + + /* show menu */ /*{{{*/ + switch (reply) + { + case 0: menu[0].str=mystrmalloc(_("wW)hole column")); break; + case 1: menu[0].str=mystrmalloc(_("wW)hole line")); break; + case 2: menu[0].str=mystrmalloc(_("wW)hole sheet")); break; + default: assert(0); + } + menu[1].str=mystrmalloc(_("sS)ingle cell")); menu[1].c='\0'; + menu[2].str=(char*)0; + r=line_menu(_("Insert:"),menu,0); + free(menu[0].str); + free(menu[1].str); + /*}}}*/ + switch (r) + { + /* 0 -- use whole dimension */ /*{{{*/ + case 0: + { + switch (reply) + { + /* 0 -- use whole column */ /*{{{*/ + case 0: + { + x1=x2=sheet->curx; + y1=0; y2=sheet->dimy; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* 1 -- use whole line */ /*{{{*/ + case 1: + { + x1=0; x2=sheet->dimx; + y1=y2=sheet->cury; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* 2 -- use whole layer */ /*{{{*/ + case 2: + { + x1=0; x2=sheet->dimx; + y1=0; y2=sheet->dimy; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + break; + } + /*}}}*/ + /* 1 -- use current cell */ /*{{{*/ + case 1: + { + x1=x2=sheet->curx; + y1=y2=sheet->cury; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* -2,-1 -- go up or abort */ /*{{{*/ + case -2: + case -1: return r; + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + } + /*}}}*/ + else + /* range is the marked cube */ /*{{{*/ + { + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + } + /*}}}*/ + switch (reply) + { + /* 0 -- columns */ /*{{{*/ + case 0: insertcube(sheet,x1,y1,z1,x2,y2,z2,IN_X); break; + /*}}}*/ + /* 1 -- rows */ /*{{{*/ + case 1: insertcube(sheet,x1,y1,z1,x2,y2,z2,IN_Y); break; + /*}}}*/ + /* 2 -- depth */ /*{{{*/ + case 2: insertcube(sheet,x1,y1,z1,x2,y2,z2,IN_Z); break; + /*}}}*/ + } + return 0; +} +/*}}}*/ +/* do_delete -- delete block */ /*{{{*/ +static int do_delete(Sheet *sheet) +{ + /* variables */ /*{{{*/ + int x1,y1,z1,x2,y2,z2,reply; + /*}}}*/ + + firstmenu: + /* ask for direction of deletion */ /*{{{*/ + { + MenuChoice menu[4]; + + menu[0].str=mystrmalloc(_("cC)olumn")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("rR)ow")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("dD)epth")); menu[2].c='\0'; + menu[3].str=(char*)0; + reply=line_menu(_("Delete:"),menu,0); + free(menu[0].str); + free(menu[1].str); + free(menu[2].str); + if (reply<0) return reply; + } + /*}}}*/ + if (sheet->mark1x==-1) + /* ask if range is the current cell or whole dimension should be used */ /*{{{*/ + { + /* variables */ /*{{{*/ + MenuChoice menu[3]; + int r; + /*}}}*/ + + /* show menu */ /*{{{*/ + switch (reply) + { + case 0: menu[0].str=mystrmalloc(_("wW)hole column")); break; + case 1: menu[0].str=mystrmalloc(_("wW)hole line")); break; + case 2: menu[0].str=mystrmalloc(_("wW)hole sheet")); break; + default: assert(0); + } + menu[1].str=mystrmalloc(_("sS)ingle cell")); menu[1].c='\0'; + menu[2].str=(char*)0; + r=line_menu(_("Delete:"),menu,0); + free(menu[0].str); + free(menu[1].str); + /*}}}*/ + switch (r) + { + /* 0 -- use whole dimension */ /*{{{*/ + case 0: + { + switch (reply) + { + /* 0 -- use whole column */ /*{{{*/ + case 0: + { + x1=x2=sheet->curx; + y1=0; y2=sheet->dimy; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* 1 -- use whole line */ /*{{{*/ + case 1: + { + x1=0; x2=sheet->dimx; + y1=y2=sheet->cury; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* 2 -- use whole layer */ /*{{{*/ + case 2: + { + x1=0; x2=sheet->dimx; + y1=0; y2=sheet->dimy; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + break; + } + /*}}}*/ + /* 1 -- use current cell */ /*{{{*/ + case 1: + { + x1=x2=sheet->curx; + y1=y2=sheet->cury; + z1=z2=sheet->curz; + break; + } + /*}}}*/ + /* -1 -- abort */ /*{{{*/ + case -1: return -1; + /*}}}*/ + /* -2 -- go back to previous menu */ /*{{{*/ + case -2: goto firstmenu; + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + } + /*}}}*/ + else + /* range is the marked cube */ /*{{{*/ + { + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + } + /*}}}*/ + switch(reply) + { + /* 0 -- columns */ /*{{{*/ + case 0: deletecube(sheet,x1,y1,z1,x2,y2,z2,IN_X); break; + /*}}}*/ + /* 1 -- rows */ /*{{{*/ + case 1: deletecube(sheet,x1,y1,z1,x2,y2,z2,IN_Y); break; + /*}}}*/ + /* 2 -- depth */ /*{{{*/ + case 2: deletecube(sheet,x1,y1,z1,x2,y2,z2,IN_Z); break; + /*}}}*/ + } + return -1; +} +/*}}}*/ +/* do_move -- copy or move a block */ /*{{{*/ +static int do_move(Sheet *sheet, int copy, int force) +{ + int c; + + c=-1; + if (sheet->mark1x==-1) line_msg(copy ? _("Copy block:") : _("Move block:"),_("No block marked")); + else if (force || (c=line_ok(copy ? _("Copy block:") : _("Move block:"),0))==1) + { + int x1,y1,z1; + int x2,y2,z2; + + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + moveblock(sheet,x1,y1,z1,x2,y2,z2,sheet->curx,sheet->cury,sheet->curz,copy); + if (!copy) sheet->mark1x=-1; + } + if (c<0) return c; else return -1; +} +/*}}}*/ +/* do_fill -- fill a block */ /*{{{*/ +static int do_fill(Sheet *sheet) +{ + /* variables */ /*{{{*/ + size_t offx,edx; + int cols,rows,layers; + int x,y,z; + int x1,y1,z1; + int x2,y2,z2; + int c; + /*}}}*/ + + if (sheet->mark1x==-1) line_msg(_("Fill block:"),_("No block marked")); + else + { + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 0); + cols=rows=layers=1; + firstmenu: + offx=0; + edx=0; + do if ((c=line_numedit(&cols,_("Number of column-wise repetitions:"),&edx,&offx))<0) return c; while (cols<=0); + secondmenu: + offx=0; + edx=0; + do + { + c=line_numedit(&rows,_("Number of row-wise repetitions:"),&edx,&offx); + if (c==-1) return -1; + else if (c==-2) goto firstmenu; + } while (rows<=0); + offx=0; + edx=0; + do + { + c=line_numedit(&layers,_("Number of depth-wise repetitions:"),&edx,&offx); + if (c==-1) return -1; + else if (c==-2) goto secondmenu; + } while (layers<=0); + for (x=0; xcurx+x*(x2-x1+1),sheet->cury+y*(y2-y1+1),sheet->curz+z*(z2-z1+1),1); + } + return -1; +} +/*}}}*/ +/* do_sort -- sort block */ /*{{{*/ +static int do_sort(Sheet *sheet) +{ + /* variables */ /*{{{*/ + MenuChoice menu1[4],menu2[3],menu3[3]; + Sortkey sk[MAX_SORTKEYS]; + unsigned int key; + size_t x,offx; + const char *msg; + Direction in_dir=(Direction)-2; /* cause run time error */ + int x1,y1,z1,x2,y2,z2; + int doit=-1; + int c; + int last; + /*}}}*/ + + /* note and order block coordinates */ /*{{{*/ + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + /*}}}*/ + /* build menues */ /*{{{*/ + menu1[0].str=mystrmalloc(_("cC)olumn")); menu1[0].c='\0'; + menu1[1].str=mystrmalloc(_("rR)ow")); menu1[1].c='\0'; + menu1[2].str=mystrmalloc(_("dD)epth")); menu1[2].c='\0'; + menu1[3].str=(char*)0; + menu2[0].str=mystrmalloc(_("sS)ort region")); menu2[0].c='\0'; + menu2[1].str=mystrmalloc(_("aA)dd key")); menu2[1].c='\0'; + menu2[2].str=(char*)0; + menu3[0].str=mystrmalloc(_("aA)scending")); menu3[0].c='\0'; + menu3[1].str=mystrmalloc(_("dD)escending")); menu3[0].c='\0'; + menu3[2].str=(char*)0; + /*}}}*/ + + last=-1; + /* ask for sort direction */ /*{{{*/ + zero: switch (c=line_menu(_("Sort block:"),menu1,0)) + { + /* 0 -- in X */ /*{{{*/ + case 0: in_dir=IN_X; break; + /*}}}*/ + /* 1 -- in Y */ /*{{{*/ + case 1: in_dir=IN_Y; break; + /*}}}*/ + /* 2 -- in Z */ /*{{{*/ + case 2: in_dir=IN_Z; break; + /*}}}*/ + /* -2,-1 -- abort */ /*{{{*/ + case -2: + case -1: goto greak; + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + last=0; + /*}}}*/ + key=0; + do + { + /* ask for positions */ /*{{{*/ + one: if (in_dir==IN_X) sk[key].x=0; else /* ask for x position */ /*{{{*/ + { + x=0; + offx=0; + sk[key].x=0; + do + { + c=line_numedit(&(sk[key].x),_("X position of key vector:"),&x,&offx); + if (c==-1) goto greak; + else if (c==-2) switch (last) + { + case -1: goto greak; + case 0: goto zero; + case 2: goto two; + case 3: goto three; + case 5: goto five; + } + } while (sk[key].x<0); + last=1; + } + /*}}}*/ + two: if (in_dir==IN_Y) sk[key].y=0; else /* ask for y position */ /*{{{*/ + { + x=0; + offx=0; + sk[key].y=0; + do + { + c=line_numedit(&(sk[key].y),_("Y position of key vector:"),&x,&offx); + if (c==-1) goto greak; + else if (c==-2) switch (last) + { + case -1: goto greak; + case 0: goto zero; + case 1: goto one; + case 3: goto three; + case 5: goto five; + default: assert(0); + } + } while (sk[key].y<0); + last=2; + } + /*}}}*/ + three: if (in_dir==IN_Z) sk[key].z=0; else /* ask for z position */ /*{{{*/ + { + x=0; + offx=0; + sk[key].z=0; + do + { + c=line_numedit(&(sk[key].z),_("Z position of key vector:"),&x,&offx); + if (c==-1) goto greak; + else if (c==-2) switch (last) + { + case -1: goto greak; + case 0: goto zero; + case 1: goto one; + case 2: goto two; + case 5: goto five; + default: assert(0); + } + } while (sk[key].z<0); + last=3; + } + /*}}}*/ + /*}}}*/ + /* ask for sort key */ /*{{{*/ + four: sk[key].sortkey=0; + switch (c=line_menu(_("Sort block:"),menu3,0)) + { + /* 0 -- ascending */ /*{{{*/ + case 0: sk[key].sortkey|=ASCENDING; break; + /*}}}*/ + /* 1 -- descending */ /*{{{*/ + case 1: sk[key].sortkey&=~ASCENDING; break; + /*}}}*/ + /* -1 -- abort */ /*{{{*/ + case -1: goto greak; + /*}}}*/ + /* -2 -- go to first menu */ /*{{{*/ + case -2: switch (last) + { + case -1: goto greak; + case 1: goto one; + case 2: goto two; + case 3: goto three; + default: assert(0); + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + last=4; + /*}}}*/ + ++key; + five: + if (key==MAX_SORTKEYS) /* ask for sort comfirmation */ /*{{{*/ + { + c=line_ok(_("Sort block:"),0); + if (c==-1) goto greak; + else if (c==-2) goto four; + else if (c==0) doit=1; + } + /*}}}*/ + else /* ask for sort or adding another key */ /*{{{*/ + switch (line_menu(_("Sort block:"),menu2,0)) + { + /* 0 -- sort it */ /*{{{*/ + case 0: doit=1; break; + /*}}}*/ + /* 1 -- add another key */ /*{{{*/ + case 1: doit=0; break; + /*}}}*/ + /* -1 -- abort */ /*{{{*/ + case -1: goto greak; + /*}}}*/ + case -2: goto four; + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + /*}}}*/ + last=5; + } while (!doit); + c=-1; + if ((msg=sortblock(sheet,x1,y1,z1,x2,y2,z2,in_dir,sk,key))!=(const char*)0) line_msg(_("Sort block:"),msg); + greak: + /* free menues */ /*{{{*/ + free((char*)menu1[0].str); + free((char*)menu1[1].str); + free((char*)menu1[2].str); + free((char*)menu2[0].str); + free((char*)menu2[1].str); + free((char*)menu2[2].str); + free((char*)menu3[0].str); + free((char*)menu3[1].str); + free((char*)menu3[2].str); + /*}}}*/ + return c; +} +/*}}}*/ +/* do_batchsort -- sort block in a batch*/ /*{{{*/ +static void do_batchsort(Sheet *sheet, Direction dir, char* arg) +{ + Sortkey sk[MAX_SORTKEYS]; + int x1,y1,z1,x2,y2,z2; + unsigned int key = 0; + char* next; + while( *arg != '\0' ) + { + while (isspace((int)*arg)) arg++; + sk[key].x=sk[key].y=sk[key].z=sk[key].sortkey=0; + switch (*arg) + { + case 'a': sk[key].sortkey|=ASCENDING; arg++; break; + case 'd': sk[key].sortkey&=~ASCENDING; arg++; break; + } + if ( *arg != '\0' && dir != IN_X ) { sk[key].x=strtol(arg, &next, 10); arg = next; } + if ( *arg != '\0' && dir != IN_Y ) { sk[key].y=strtol(arg, &next, 10); arg = next; } + if ( *arg != '\0' && dir != IN_Z ) { sk[key].z=strtol(arg, &next, 10); arg = next; } + key++; + } + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + sortblock(sheet, x1, y1, z1, x2, y2, z2, dir, sk, key); +} +/*}}}*/ +/* do_mirror -- mirror block */ /*{{{*/ +static int do_mirror(Sheet *sheet) +{ + /* variables */ /*{{{*/ + int x1,y1,z1,x2,y2,z2,reply; + /*}}}*/ + + /* note and order block coordinates */ /*{{{*/ + get_mark(sheet, &x1, &x2, &y1, &y2, &z1, &z2, 1); + /*}}}*/ + /* ask for direction of mirroring */ /*{{{*/ + { + MenuChoice menu[4]; + + menu[0].str=mystrmalloc(_("lL)eft-right")); menu[0].c='\0'; + menu[1].str=mystrmalloc(_("uU)pside-down")); menu[1].c='\0'; + menu[2].str=mystrmalloc(_("fF)ront-back")); menu[2].c='\0'; + menu[3].str=(char*)0; + reply=line_menu(_("Mirror block:"),menu,0); + free(menu[0].str); + free(menu[1].str); + free(menu[2].str); + if (reply<0) return reply; + } + /*}}}*/ + switch (reply) + { + /* 0 -- left-right */ /*{{{*/ + case 0: mirrorblock(sheet,x1,y1,z1,x2,y2,z2,IN_X); break; + /*}}}*/ + /* 1 -- upside-down */ /*{{{*/ + case 1: mirrorblock(sheet,x1,y1,z1,x2,y2,z2,IN_Y); break; + /*}}}*/ + /* 2 -- front-back */ /*{{{*/ + case 2: mirrorblock(sheet,x1,y1,z1,x2,y2,z2,IN_Z); break; + /*}}}*/ + default: assert(0); + } + return 0; +} +/*}}}*/ +/* do_goto -- go to a specific cell */ /*{{{*/ +static int do_goto(Sheet *sheet, const char *expr) +{ + /* variables */ /*{{{*/ + char buf[1024]; + const char *s; + size_t x,offx; + Token **t; + int c; + /*}}}*/ + + assert(sheet!=(Sheet*)0); + buf[0]='\0'; + x=offx=0; + if (expr) strcpy(buf,expr); + else if ((c=line_edit(sheet,buf,sizeof(buf),_("Go to location:"),&x,&offx))<0) return c; + s=buf; + t=scan(&s); + if (t!=(Token**)0) + { + Token value; + + upd_x=sheet->curx; + upd_y=sheet->cury; + upd_z=sheet->curz; + upd_sheet=sheet; + value=eval(t); + tvecfree(t); + if (value.type==LOCATION && value.u.location[0]>=0 && value.u.location[1]>=0 && value.u.location[2]>=0) + moveto(sheet,value.u.location[0],value.u.location[1],value.u.location[2]); + else + line_msg(_("Go to location:"),_("Not a valid location")); + tfree(&value); + } + return -1; +} +/*}}}*/ + +/* do_sheetcmd -- process one key press */ /*{{{*/ +int do_sheetcmd(Sheet *cursheet, Key c, int moveonly) +{ + switch ((int)c) + { + case K_GOTO: do_goto(cursheet, (const char *)0); break; + case K_COLWIDTH: do_columnwidth(cursheet); break; + case BLOCK_CLEAR: do_clear(cursheet); redraw_sheet(cursheet); break; + case BLOCK_INSERT: do_insert(cursheet); redraw_sheet(cursheet); break; + case BLOCK_DELETE: do_delete(cursheet); redraw_sheet(cursheet); break; + case BLOCK_MOVE: do_move(cursheet,0,0); redraw_sheet(cursheet); break; + case BLOCK_COPY: do_move(cursheet,1,0); redraw_sheet(cursheet); break; + case BLOCK_FILL: do_fill(cursheet); redraw_sheet(cursheet); break; + case BLOCK_SORT: do_sort(cursheet); redraw_sheet(cursheet); break; + case BLOCK_MIRROR: do_mirror(cursheet); redraw_sheet(cursheet); break; + case ADJUST_LEFT: + case ADJUST_RIGHT: + case ADJUST_CENTER: + case ADJUST_SCIENTIFIC: + case ADJUST_PRECISION: + case ADJUST_SHADOW: + case ADJUST_BOLD: + case ADJUST_UNDERLINE: + case ADJUST_TRANSPARENT: + case ADJUST_LABEL: + case ADJUST_LOCK: + case ADJUST_IGNORE: do_attribute(cursheet, c); break; + /* UP -- move up */ /*{{{*/ + case K_UP: + { + relmoveto(cursheet, 0, -1, 0); + break; + } + /*}}}*/ + /* DOWN -- move down */ /*{{{*/ + case K_DOWN: + { + relmoveto(cursheet, 0, 1, 0); + break; + } + /*}}}*/ + /* LEFT -- move left */ /*{{{*/ + case K_LEFT: + { + relmoveto(cursheet, -1, 0, 0); + break; + } + /*}}}*/ + /* RIGHT -- move right */ /*{{{*/ + case K_RIGHT: + { + relmoveto(cursheet, 1, 0, 0); + break; + } + /*}}}*/ + /* FIRSTL -- move to first line */ /*{{{*/ + case K_FIRSTL: + case '<': + { + moveto(cursheet, -1, 0, -1); + break; + } + /*}}}*/ + /* LASTL -- move to last line */ /*{{{*/ + case K_LASTL: + case '>': + { + moveto(cursheet, -1, (cursheet->dimy ? cursheet->dimy-1 : 0), -1); + break; + } + /*}}}*/ + /* HOME -- move to beginning of line */ /*{{{*/ + case K_HOME: + { + moveto(cursheet, 0, -1, -1); + break; + } + /*}}}*/ + /* END -- move to end of line */ /*{{{*/ + case K_END: + { + moveto(cursheet, (cursheet->dimx ? cursheet->dimx-1 : 0), -1, -1); + break; + } + /*}}}*/ + /* + -- move one sheet down */ /*{{{*/ + case K_NSHEET: + case '+': + { + relmoveto(cursheet, 0, 0, 1); + break; + } + /*}}}*/ + /* - -- move one sheet up */ /*{{{*/ + case K_PSHEET: + case '-': + { + relmoveto(cursheet, 0, 0, -1); + break; + } + /*}}}*/ + /* * -- move to bottom sheet */ /*{{{*/ + case K_LSHEET: + case '*': + { + moveto(cursheet, -1, -1, (cursheet->dimz ? cursheet->dimz-1 : 0)); + break; + } + /*}}}*/ + /* _ -- move to top sheet */ /*{{{*/ + case K_FSHEET: + case '_': + { + moveto(cursheet, -1, -1, 0); + break; + } + /*}}}*/ + /* ENTER -- edit current cell */ /*{{{*/ + case K_ENTER: if (moveonly) break; do_edit(cursheet,'\0',(const char*)0,0); break; + /*}}}*/ + /* MENTER -- edit current clocked cell */ /*{{{*/ + case K_MENTER: if (moveonly) break; do_edit(cursheet,'\0',(const char*)0,1); break; + /*}}}*/ + /* ", @, digit -- edit current cell with character already in buffer */ /*{{{*/ + case K_BACKSPACE: + case K_DC: + case '"': + case '@': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': if (moveonly) break; do_edit(cursheet,c,(const char*)0,0); break; + /*}}}*/ + /* MARK -- toggle block marking */ /*{{{*/ + case '.': + case K_MARK: if (moveonly) break; do_mark(cursheet,0); break; + /*}}}*/ + /* _("Save sheet file format:") -- save menu */ /*{{{*/ + case K_SAVEMENU: if (moveonly) break; do_save(cursheet); break; + /*}}}*/ + /* _("Load sheet file format:") -- load menu */ /*{{{*/ + case K_LOAD: + case K_LOADMENU: if (moveonly) break; do_load(cursheet); break; + /*}}}*/ + /* _("nN)ame") -- set name */ /*{{{*/ + case K_NAME: if (moveonly) break; do_name(cursheet); break; + /*}}}*/ +#ifdef ENABLE_HELP + case K_HELP: show_text(helpfile); break; +#else + case K_HELP: show_text(_("Sorry, manual is not installed.")); break; +#endif + case K_ABOUT: show_text(_("About teapot
\n\n"
+        "               ` ',`    '  '                   \n"
+        "                `   '  ` ' '                   \n"
+        "                 `' '   '`'                    \n"
+        "                 ' `   ' '`                    \n"
+        "    '           '` ' ` '`` `                   \n"
+        "    `.   Table Editor And Planner, or:         \n"
+        "      ,         . ,   ,  . .                   \n"
+        "                ` '   `  ' '                   \n"
+        "     `::\\    /:::::::::::::::::\\   ___         \n"
+        "      `::\\  /:::::::::::::::::::\\,'::::\\       \n"
+        "       :::\\/:::::::::::::::::::::\\/   \\:\\      \n"
+        "       :::::::::::::::::::::::::::\\    :::     \n"
+        "       ::::::::::::::::::::::::::::;  /:;'     \n"
+        "       `::::::::::::::::::::::::::::_/:;'      \n"
+        "         `::::::::::::::::::::::::::::'        \n"
+        "          `////////////////////////'           \n"
+        "           `:::::::::::::::::::::'             \n"
+        "
\n" + "

Teapot " VERSION "

\n" + "\n" + "

Original Version: Michael Haardt
\n" + "Current Maintainer: Joerg Walter
\n" + "Home Page: http://www.syntax-k.de/projekte/teapot/

\n" + "\n" + "

Copyright 1995-2006 Michael Haardt,
\n" + "Copyright 2009-2010 Joerg Walter (info@syntax-k.de)

\n" + "\f" + "

This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.

\n" + "\n" + "

This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.

\n" + "\n" + "

You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see http://www.gnu.org/licenses/.

" + "")); + break; + /* MENU, / -- main menu */ /*{{{*/ + case '/': if (!moveonly && do_sheetcmd(cursheet, show_menu(cursheet), 0)) return 1; break; + /*}}}*/ + /* _("sS)ave") -- save in current native format */ /*{{{*/ + case K_SAVE: do_save(cursheet); break; + /*}}}*/ + /* _("cC)opy") -- copy block */ /*{{{*/ + case K_COPY: if (moveonly) break; do_move(cursheet,1,1); break; + /*}}}*/ + /* RECALC -- recalculate */ /*{{{*/ + case K_RECALC: if (moveonly) break; forceupdate(cursheet); break; + /*}}}*/ + /* _("Usage: clock(condition,location[,location])") -- clock */ /*{{{*/ + case K_CLOCK: + { + int x,y,z; + + for (x=0; xdimx; ++x) + for (y=0; ydimy; ++y) + for (z=0; zdimz; ++z) + clk(cursheet,x,y,z); + update(cursheet); + break; + } + /*}}}*/ + /* NPAGE -- page down */ /*{{{*/ + case K_NPAGE: + { + cursheet->offy+=(cursheet->maxy-3); + relmoveto(cursheet, 0, cursheet->maxy-3, 0); + break; + } + /*}}}*/ + /* PPAGE -- page up */ /*{{{*/ + case K_PPAGE: + { + cursheet->offy = (cursheet->offy>=(cursheet->maxy-3) ? cursheet->offy-(cursheet->maxy-3) : 0); + relmoveto(cursheet, 0, (cursheet->cury>=(cursheet->maxy-3) ? -(cursheet->maxy-3) : -cursheet->cury), 0); + break; + } + /*}}}*/ + /* FPAGE -- page right */ /*{{{*/ + case K_FPAGE: + { + cursheet->offx+=cursheet->width; + relmoveto(cursheet, cursheet->width, 0, 0); + break; + } + /*}}}*/ + /* BPAGE -- page left */ /*{{{*/ + case K_BPAGE: + { + cursheet->offx=(cursheet->offx>=cursheet->width ? cursheet->offx-cursheet->width : 0); + relmoveto(cursheet, (cursheet->curx>=cursheet->width ? -cursheet->width : -cursheet->curx), 0, 0); + break; + } + /*}}}*/ + /* SAVEQUIT -- save and quit */ /*{{{*/ + case K_SAVEQUIT: + { + if (moveonly) break; + if (do_save(cursheet)!=-2) return 1; + break; + } + /*}}}*/ + /* _("qQ)uit") -- quit */ /*{{{*/ + case K_QUIT: if (moveonly) break; return 1; + /*}}}*/ + default: + if (isalpha(c) && !moveonly) do_edit(cursheet,c,(const char*)0,0); + break; + } + return 0; +} +/*}}}*/ + +/* main */ /*{{{*/ +int main(int argc, char *argv[]) +{ + /* variables */ /*{{{*/ + Sheet sheet,*cursheet; + int o; + const char *loadfile; + int always_redraw=0; + char ln[1024]; + /*}}}*/ + + setlocale(LC_ALL, ""); + find_helpfile(helpfile, sizeof(helpfile), argv[0]); + + /* parse options */ /*{{{*/ + while ((o=getopt(argc,argv,"abhnrqHp:?"))!=EOF) switch (o) + { + /* a -- use ascii as default */ /*{{{*/ + case 'a': + { + usexdr=0; + break; + } + /*}}}*/ + /* b -- run batch */ /*{{{*/ + case 'b': batch=1; break; + /*}}}*/ + /* n -- no quoted strings */ /*{{{*/ + case 'n': quote=0; break; + /*}}}*/ + /* q -- force quoted strings */ /*{{{*/ + case 'q': quote=1; break; + /*}}}*/ + /* H -- no row/column headers */ /*{{{*/ + case 'H': header=0; break; + /*}}}*/ + /* r -- always redraw */ /*{{{*/ + case 'r': + { + always_redraw=1; + break; + } + /*}}}*/ + /* p -- precision */ /*{{{*/ + case 'p': + { + long n; + char *end; + + n=strtol(optarg,&end,0); + if (*end || n<0 || n>DBL_DIG) + { + fprintf(stderr,_("teapot: precision must be between 0 and %d.\n"),DBL_DIG); + exit(1); + } + def_precision=n; + break; + } + /*}}}*/ + /* default -- includes ? and h */ /*{{{*/ + default: + { + fprintf(stderr,_( + "Usage: %s [-a] [-b] [-n] [-H] [-r] [-p digits] [file]\n" + " -a: use ASCII file format as default\n" + " -b: batch mode\n" + " -q: display strings in quotes\n" + " -H: hide row/column headers\n" + " -r: redraw more often\n" + " -p: set decimal precision\n" + ), argv[0]); + exit(1); + } + /*}}}*/ + } + loadfile=(optindcurx=cursheet->cury=cursheet->curz=0; + cursheet->offx=cursheet->offy=0; + cursheet->dimx=cursheet->dimy=cursheet->dimz=0; + cursheet->sheet=(Cell**)0; + cursheet->column=(int*)0; + cursheet->orix=0; + cursheet->oriy=0; + cursheet->maxx=0; + cursheet->maxy=0; + cursheet->name=(char*)0; + cursheet->mark1x=-1; + cursheet->marking=0; + cursheet->changed=0; + cursheet->moveonly=0; + cursheet->clk=0; + (void)memset(cursheet->labelcache,0,sizeof(cursheet->labelcache)); + /*}}}*/ + /* start display */ /*{{{*/ + if (!batch) { + display_init(&sheet, always_redraw); + line_msg((const char*)0,""); + } + /*}}}*/ + if (loadfile) /* load given sheet */ /*{{{*/ + { + const char *msg; + + cursheet->name=mystrmalloc(loadfile); + if (usexdr) + { + if ((msg=loadxdr(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from XDR file:"),msg); + } + else + { + if ((msg=loadport(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from ASCII file:"),msg); + } + } + /*}}}*/ + if (batch) /* process batch */ /*{{{*/ + while (fgets(ln,sizeof(ln),stdin)!=(char*)0) + { + /* variables */ /*{{{*/ + size_t len; + char *cmd,*arg; + /*}}}*/ + + /* set cmd and arg */ /*{{{*/ + ++batchln; + len=strlen(ln); + if (len && ln[len-1]=='\n') ln[len-1]='\0'; + cmd=ln; while (isspace((int)*cmd)) ++cmd; + arg=cmd; + while (*arg && !isspace((int)*arg)) ++arg; + while (isspace((int)*arg)) *arg++='\0'; + /*}}}*/ + + /* goto location */ /*{{{*/ + if (strcmp(cmd,"goto")==0) do_goto(cursheet,arg); + /*}}}*/ + /* from location */ /*{{{*/ + else if (strcmp(cmd,"from")==0) + { + do_goto(cursheet,arg); + do_mark(cursheet,1); + } + /*}}}*/ + /* to location */ /*{{{*/ + else if (strcmp(cmd,"to")==0) + { + do_goto(cursheet,arg); + do_mark(cursheet,2); + } + /*}}}*/ + /* save-tbl file */ /*{{{*/ + else if (strcmp(cmd,"save-tbl")==0) do_savetbl(cursheet,arg); + /*}}}*/ + /* save-latex file */ /*{{{*/ + else if (strcmp(cmd,"save-latex")==0) do_savelatex(cursheet,arg); + /*}}}*/ + /* save-context file */ /*{{{*/ + else if (strcmp(cmd,"save-context")==0) do_savecontext(cursheet,arg); + /*}}}*/ + /* save-csv file */ /*{{{*/ + else if (strcmp(cmd,"save-csv")==0) do_savecsv(cursheet,arg); + /*}}}*/ + /* save-html file */ /*{{{*/ + else if (strcmp(cmd,"save-html")==0) do_savehtml(cursheet,arg); + /*}}}*/ + /* load-csv file */ /*{{{*/ + else if (strcmp(cmd,"load-csv")==0) { loadcsv(cursheet,arg); forceupdate(cursheet); } + /*}}}*/ + /* sort in x direction */ /*{{{*/ + else if (strcmp(cmd,"sort-x")==0) do_batchsort(cursheet, IN_X, arg); + /*}}}*/ + /* sort in y direction */ /*{{{*/ + else if (strcmp(cmd,"sort-y")==0) do_batchsort(cursheet, IN_Y, arg); + /*}}}*/ + /* sort in z direction */ /*{{{*/ + else if (strcmp(cmd,"sort-z")==0) do_batchsort(cursheet, IN_Z, arg); + /*}}}*/ + /* this is an unknown command */ /*{{{*/ + else line_msg(_("Unknown batch command:"),cmd); + /*}}}*/ + } + /*}}}*/ + else /* process interactive input */ /*{{{*/ + { + display_main(cursheet); + display_end(); + } + /*}}}*/ + freesheet(cursheet,1); + fclose(stdin); + return 0; +} +/*}}}*/ diff --git a/main.h b/main.h new file mode 100644 index 0000000..0f38923 --- /dev/null +++ b/main.h @@ -0,0 +1,94 @@ +#ifndef MAIN_H +#define MAIN_H + +#include "config.h" + +#define _(x) (x) + +#include "sheet.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int batch; +extern int def_precision; +extern int quote; +extern int header; +extern unsigned int batchln; + +/* A variable of type Key is either a special symbol from this enum, representing + actions the user has triggered (by whatever means available), or a Unicode + character that was entered +*/ +typedef enum { + K_NONE = 0, + K_INVALID = -1, + K_BACKSPACE = -2, + K_BPAGE = -3, + K_CLOCK = -4, + K_COPY = -5, + K_DC = -6, + K_DOWN = -7, + K_END = -8, + K_ENTER = -9, + K_FIRSTL = -10, + K_FPAGE = -11, + K_FSHEET = -12, + K_HOME = -13, + K_LASTL = -14, + K_LEFT = -15, + K_LOAD = -16, + K_LOADMENU = -17, + K_LSHEET = -18, + K_MARK = -19, + K_MENTER = -20, + K_NPAGE = -21, + K_NSHEET = -22, + K_PPAGE = -23, + K_PSHEET = -24, + K_QUIT = -25, + K_RECALC = -26, + K_RIGHT = -27, + K_SAVE = -28, + K_SAVEMENU = -29, + K_SAVEQUIT = -30, + K_UP = -31, + K_GOTO = -32, + K_NAME = -33, + ADJUST_LEFT = -34, + ADJUST_RIGHT = -35, + ADJUST_CENTER = -36, + ADJUST_SCIENTIFIC = -37, + ADJUST_BOLD = -38, + ADJUST_PRECISION = -39, + ADJUST_SHADOW = -40, + ADJUST_TRANSPARENT = -41, + ADJUST_LABEL = -42, + ADJUST_LOCK = -43, + ADJUST_IGNORE = -44, + K_COLWIDTH = -45, + BLOCK_CLEAR = -46, + BLOCK_INSERT = -47, + BLOCK_DELETE = -48, + BLOCK_MOVE = -49, + BLOCK_COPY = -50, + BLOCK_FILL = -51, + BLOCK_SORT = -52, + BLOCK_MIRROR = -53, + K_ABOUT = -54, + K_HELP = -55, + ADJUST_UNDERLINE = -56 +} Key; + +extern int do_sheetcmd(Sheet *cursheet, Key c, int moveonly); +extern int doanyway(Sheet *sheet, const char *msg); +extern void moveto(Sheet *sheet, int x, int y, int z); +extern void relmoveto(Sheet *sheet, int x, int y, int z); +extern void do_mark(Sheet *cursheet, int force); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..ef28123 --- /dev/null +++ b/misc.c @@ -0,0 +1,249 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NEED_BCOPY +#define memmove(dst,src,len) bcopy(src,dst,len) +#endif + + +#include "default.h" +#include "main.h" +#include "misc.h" +#include "utf8.h" +/*}}}*/ + +/* posnumber -- match positive integer */ /*{{{*/ +long int posnumber(const char *s, const char **endptr) +{ + unsigned int base=10; + unsigned char c; + register const char *nptr = s; + long int result = 0L; + int saw_a_digit = 0; + + if (*nptr == '0') + { + if ((c = *++nptr) == 'x' || c == 'X') + { + ++nptr; + base = 16; + } + else + { + saw_a_digit = 1; + base = 8; + } + } + + --nptr; + while ((c=*++nptr)!='\0') + { + if (isdigit(c)) c -= '0'; + else if (isupper(c)) c -= ('A'-10); + else if (islower(c)) c -= ('a'-10); + else break; + if (c>=base) break; + saw_a_digit = 1; + result = result*base+c; + } + + *endptr=(saw_a_digit ? nptr : s); + return result; +} +/*}}}*/ +/* posorder -- sort two integers */ /*{{{*/ +void posorder(int *x, int *y) +{ + /* variables */ /*{{{*/ + int t; + /*}}}*/ + + assert(x!=(int*)0); + assert(*x>=0); + assert(y!=(int*)0); + assert(*y>=0); + if (*x>*y) + { + t=*x; + *x=*y; + *y=t; + } +} +/*}}}*/ +/* mystrmalloc -- return malloced copy of string */ /*{{{*/ +char *mystrmalloc(const char *str) +{ + return (strcpy(malloc(strlen(str)+1),str)); +} +/*}}}*/ +/* finite -- return error message about number or null */ /*{{{*/ +static volatile int caughtfpe; + +static void catchfpe(int n) +{ + caughtfpe=1; +} + +const char *dblfinite(double x) +{ + /*struct sigaction act; + + caughtfpe=0; + act.sa_handler=catchfpe; + act.sa_flags=0; + (void)sigemptyset(&act.sa_mask); + (void)sigaction(SIGFPE,&act,(struct sigaction*)0);*/ + signal(SIGFPE, catchfpe); + if (x==0.0) + { + if (caughtfpe) return _("Not a (finite) floating point number"); /* who knows */ + else return (const char*)0; + } + else + { + if (caughtfpe) return _("Not a (finite) floating point number"); + /* If one comparison was allowed, more won't hurt either. */ + if (x<0.0) + { + if (x<-DBL_MAX) return _("Not a (finite) floating point number"); /* -infinite */ + else return (const char*)0; + } + else if (x>0.0) + { + if (x>DBL_MAX) return _("Not a (finite) floating point number"); /* +infinite */ + else return (const char*)0; + } + else return _("Not a (finite) floating point number"); /* NaN */ + } +} +/*}}}*/ +/* fputc_close -- error checking fputc which closes stream on error */ /*{{{*/ +int fputc_close(char c, FILE *fp) +{ + int e; + + if ((e=fputc(c,fp))==EOF) + { + int oerrno; + + oerrno=errno; + (void)fclose(fp); + errno=oerrno; + } + return e; +} + +/* fputs_close -- error checking fputs which closes stream on error */ /*{{{*/ +int fputs_close(const char *s, FILE *fp) +{ + int e; + + if ((e=fputs(s,fp))==EOF) + { + int oerrno; + + oerrno=errno; + (void)fclose(fp); + errno=oerrno; + } + return e; +} + +/* adjust -- readjust a left adjusted string in a buffer */ /*{{{*/ +void adjust(Adjust a, char *s, size_t n) +{ + assert(s!=(char*)0); + assert(mbslen(s)<=n); + switch (a) + { + /* LEFT */ /*{{{*/ + case LEFT: break; + /*}}}*/ + /* RIGHT */ /*{{{*/ + case RIGHT: + { + size_t len; + + len=mbslen(s); + if (len < n) + { + memmove(s+n-len, s, strlen(s)+1); + memset(s, ' ', n-len); + } + break; + } + /*}}}*/ + /* CENTER */ /*{{{*/ + case CENTER: + { + size_t len,pad; + + len=mbslen(s); + pad=(n-len)/2; + assert((pad+len)<=n); + memmove(s+pad, s, strlen(s)+1); + memset(s, ' ', pad); + //*(s+strlen(s)+n-pad-len)='\0'; + //(void)memset(s+strlen(s),' ',n-pad-len-1); + break; + } + /*}}}*/ + /* default */ /*{{{*/ + default: assert(0); + /*}}}*/ + } +} +/*}}}*/ +/* strerror -- strerror(3) */ /*{{{*/ +#ifdef NEED_STRERROR +extern int sys_nerr; +extern const char *sys_errlist[]; + +const char *strerror(int errno) +{ + return (errno>=0 && errno'); + } + if (in) strcpy(out, in); + else *out = 0; + return stripped; +} diff --git a/misc.h b/misc.h new file mode 100644 index 0000000..db0d24b --- /dev/null +++ b/misc.h @@ -0,0 +1,25 @@ +#ifndef MISC_H +#define MISC_H + +#include + +#include "sheet.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void posorder(int *x, int *y); +long int posnumber(const char *s, const char **endptr); +char *mystrmalloc(const char *str); +const char *dblfinite(double x); +int fputc_close(char c, FILE *fp); +int fputs_close(const char *s, FILE *fp); +void adjust(Adjust a, char *s, size_t n); +char *striphtml(const char *in); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/parser.c b/parser.c new file mode 100644 index 0000000..58095c3 --- /dev/null +++ b/parser.c @@ -0,0 +1,316 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include + + +#include "eval.h" +#include "main.h" +#include "misc.h" +#include "parser.h" +#include "scanner.h" +#include "sheet.h" +/*}}}*/ +/* #defines */ /*{{{*/ +#define MAXARGC 16 +/*}}}*/ + +/* prototypes */ /*{{{*/ +static Token term(Token *n[], int *i); +/*}}}*/ + +/* primary -- parse and evaluate a primary term */ /*{{{*/ +static Token primary(Token *n[], int *i) +{ + /* variables */ /*{{{*/ + int argc,j; + Token *ident,argv[MAXARGC],result; + /*}}}*/ + + if (n[*i]==(Token*)0) + /* error */ /*{{{*/ + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("missing operator"))+1),_("missing operator")); + return result; + } + /*}}}*/ + else switch (n[*i]->type) + { + /* STRING, FLOAT, INT */ /*{{{*/ + case STRING: + case FLOAT: + case INT: + { + return tcopy(*n[(*i)++]); + } + /*}}}*/ + /* OPERATOR */ /*{{{*/ + case OPERATOR: + { + if (n[*i]->u.op==OP) + /* return paren term */ /*{{{*/ + { + ++(*i); + result=term(n,i); + if (result.type==EEK) return result; + if (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==CP) + { + ++(*i); + return result; + } + tfree(&result); + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_(") expected"))+1),_(") expected")); + return result; + } + /*}}}*/ + else if (n[*i]->u.op==MINUS) + /* return negated term */ /*{{{*/ + { + ++(*i); + return(tneg(primary(n,i))); + } + /*}}}*/ + else + /* return error, value expected */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("value expected")); + return result; + } + /*}}}*/ + } + /*}}}*/ + /* LIDENT */ /*{{{*/ + case LIDENT: + { + ident=n[*i]; + ++(*i); + return findlabel(upd_sheet,ident->u.lident); + } + /*}}}*/ + /* FIDENT */ /*{{{*/ + case FIDENT: + { + ident=n[*i]; + ++(*i); + if (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==OP) + /* parse arguments and closing paren of function call, return its value */ /*{{{*/ + { + ++(*i); + argc=0; + if (!(n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==CP)) + /* parse at least one argument */ /*{{{*/ + { + if (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==COMMA) + /* empty argument */ /*{{{*/ + { + argv[argc].type=EMPTY; + } + /*}}}*/ + else argv[argc]=term(n,i); + if (argv[argc].type==EEK) return argv[argc]; + ++argc; + while (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==COMMA) + /* parse the following argument */ /*{{{*/ + { + ++(*i); + if (argc<=MAXARGC) + { + if (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && (n[*i]->u.op==COMMA || n[*i]->u.op==CP)) + { + argv[argc].type=EMPTY; + } + else argv[argc]=term(n,i); + } + else + { + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("too many arguments"))+1),_("too many arguments")); + for (j=0; j<=argc; ++j) tfree(&argv[j]); + return result; + } + ++argc; + } + /*}}}*/ + } + /*}}}*/ + if (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op==CP) + /* eval function */ /*{{{*/ + { + ++(*i); + result=tfuncall(ident,argc,argv); + for (j=0; jtype==OPERATOR && n[*i]->u.op==POW) + { + Token result,r; + + ++(*i); + r=primary(n,i); + result=tpow(l,r); + tfree(&l); + tfree(&r); + if (result.type==EEK) return result; + l=result; + } + return l; +} +/*}}}*/ +/* piterm -- parse and evaluate a product/division/modulo term */ /*{{{*/ +static Token piterm(Token *n[], int *i) +{ + Token l; + + l=powterm(n,i); + if (l.type==EEK) return l; + while (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && (n[*i]->u.op==DIV || n[*i]->u.op==MUL || n[*i]->u.op==MOD)) + { + Operator op; + Token result,r; + + op=n[*i]->u.op; + ++(*i); + r=powterm(n,i); + switch (op) + { + case MUL: result=tmul(l,r); break; + case DIV: result=tdiv(l,r); break; + case MOD: result=tmod(l,r); break; + default: assert(0); + } + tfree(&l); + tfree(&r); + if (result.type==EEK) return result; + l=result; + } + return l; +} +/*}}}*/ +/* factor -- parse and evaluate a factor of sums/differences */ /*{{{*/ +static Token factor(Token *n[], int *i) +{ + Token l; + + l=piterm(n,i); + if (l.type==EEK) return l; + while (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && (n[*i]->u.op==PLUS || n[*i]->u.op==MINUS)) + { + Operator op; + Token result,r; + + op=n[*i]->u.op; + ++(*i); + r=piterm(n,i); + result=(op==PLUS ? tadd(l,r) : tsub(l,r)); + tfree(&l); + tfree(&r); + if (result.type==EEK) return result; + l=result; + } + return l; +} +/*}}}*/ +/* term -- parse and evaluate a relational term */ /*{{{*/ +static Token term(Token *n[], int *i) +{ + Token l; + + l=factor(n,i); + if (l.type==EEK) return l; + while (n[*i]!=(Token*)0 && n[*i]->type==OPERATOR && n[*i]->u.op>=LT && n[*i]->u.op<=NE) + { + Operator op; + Token result,r; + + op=n[*i]->u.op; + ++(*i); + r=factor(n,i); + switch (op) + { + case LT: result=tlt(l,r); break; + case LE: result=tle(l,r); break; + case GE: result=tge(l,r); break; + case GT: result=tgt(l,r); break; + case ISEQUAL: result=teq(l,r); break; + case ABOUTEQ: result=tabouteq(l,r); break; + case NE: result=tne(l,r); break; + default: assert(0); + } + tfree(&l); + tfree(&r); + if (result.type==EEK) return result; + l=result; + } + return l; +} +/*}}}*/ + +/* eval -- parse and evaluate token sequence */ /*{{{*/ +Token eval(Token **n) +{ + Token result; + int i; + + assert(upd_sheet!=(Sheet*)0); + i=0; + result=term(n,&i); + if (result.type==EEK) return result; + if (n[i]!=(Token*)0) + { + tfree(&result); + result.type=EEK; + result.u.err=strcpy(malloc(strlen(_("parse error after term"))+1),_("parse error after term")); + return result; + } + return result; +} +/*}}}*/ diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..89653f2 --- /dev/null +++ b/parser.h @@ -0,0 +1,8 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "scanner.h" + +Token eval(Token *n[]); + +#endif diff --git a/sc.c b/sc.c new file mode 100644 index 0000000..179fb59 --- /dev/null +++ b/sc.c @@ -0,0 +1,320 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include + + +#include "eval.h" +#include "main.h" +#include "sheet.h" +#include "sc.h" +/*}}}*/ + +static const char *s2t_s; +static char *s2t_t; + +static int s2t_term(Sheet *sheet); + +/* s2t_loc */ /*{{{*/ +static int s2t_loc(Sheet *sheet, const char **s, char **t) +{ + int x,y; + char label[10],*l; + + l=label; + if (**s>='A' && **s<='Z') + { + *l++=**s; + *(*t)++=**s; + x=*(*s)++-'A'; + } + else return 0; + if (**s>='A' && **s<='Z') + { + *l++=**s; + *(*t)++=**s; + x=x*26+(*(*s)++-'A'); + } + if (**s>='0' && **s<='9') + { + *l++=**s; + y=**s-'0'; + *(*t)++=*(*s)++; + } + else return 0; + while (**s>='0' && **s<='9') + { + *l++=**s; + y=y*10+(**s-'0'); + *(*t)++=*(*s)++; + } + *l='\0'; + if (*getlabel(sheet,x,y,0)=='\0') setlabel(sheet,x,y,0,label,0); + return 1; +} +/*}}}*/ +/* s2t_range */ /*{{{*/ +static int s2t_range(Sheet *sheet) +{ + if (s2t_loc(sheet,&s2t_s,&s2t_t)==0) return 0; + if (*s2t_s==':') + { + s2t_s++; *s2t_t++=','; + if (s2t_loc(sheet,&s2t_s,&s2t_t)==0) return 0; + return 1; + } + else return 0; +} +/*}}}*/ +/* s2t_primary */ /*{{{*/ +static int s2t_primary(Sheet *sheet) +{ + if (*s2t_s=='@') + /* @function */ /*{{{*/ + { + ++s2t_s; + if (strncmp(s2t_s,"sum(",4)==0) + /* @sum(range) -> sum(range) */ /*{{{*/ + { + s2t_s+=4; + *s2t_t++='s'; *s2t_t++='u'; *s2t_t++='m'; *s2t_t++='('; + if (s2t_range(sheet)==0) return 0; + *s2t_t++=')'; + return 1; + } + /*}}}*/ + else if (strncmp(s2t_s,"rnd(",4)==0) + /* @rnd(e) -> int(e,-1,1) */ /*{{{*/ + { + s2t_s+=4; + *s2t_t++='i'; *s2t_t++='n'; *s2t_t++='t'; *s2t_t++='('; + if (s2t_term(sheet)==0) return 0; + *s2t_t++=','; *s2t_t++='-'; *s2t_t++='1'; *s2t_t++=','; *s2t_t++='1'; *s2t_t++=')'; + return 1; + } + /*}}}*/ + else if (strncmp(s2t_s,"floor(",6)==0) + /* @floor(e) -> int(e,-2,-2) */ /*{{{*/ + { + s2t_s+=6; + *s2t_t++='i'; *s2t_t++='n'; *s2t_t++='t'; *s2t_t++='('; + if (s2t_term(sheet)==0) return 0; + *s2t_t++=','; *s2t_t++='-'; *s2t_t++='2'; *s2t_t++=','; *s2t_t++='-'; *s2t_t++='2'; *s2t_t++=')'; + return 1; + } + /*}}}*/ + else if (strncmp(s2t_s,"ceil(",5)==0) + /* @ceil(e) -> int(e,2,2) */ /*{{{*/ + { + s2t_s+=5; + *s2t_t++='i'; *s2t_t++='n'; *s2t_t++='t'; *s2t_t++='('; + if (s2t_term(sheet)==0) return 0; + *s2t_t++=','; *s2t_t++='2'; *s2t_t++=','; *s2t_t++='2'; *s2t_t++=')'; + return 1; + } + /*}}}*/ + else return 0; + } + /*}}}*/ + else if ((*s2t_s>='0' && *s2t_s<='9') || *s2t_s=='.') + /* number */ /*{{{*/ + { + if (*s2t_s=='.') *s2t_t++='0'; else *s2t_t++=*s2t_s++; + while (*s2t_s>='0' && *s2t_s<='9') *s2t_t++=*s2t_s++; + if (*s2t_s=='.') + { + *s2t_t++=*s2t_s++; + while (*s2t_s>='0' && *s2t_s<='9') *s2t_t++=*s2t_s++; + } + else + { + *s2t_t++='.'; *s2t_t++='0'; + } + return 1; + } + /*}}}*/ + else if (*s2t_s>='A' && *s2t_s<='Z') + /* cell value */ /*{{{*/ + { + *s2t_t++='@'; *s2t_t++='('; + if (s2t_loc(sheet,&s2t_s,&s2t_t)==0) return 0; + *s2t_t++=')'; + return 1; + } + /*}}}*/ + else if (*s2t_s) return 0; + else return 1; +} +/*}}}*/ +/* s2t_powterm */ /*{{{*/ +static int s2t_powterm(Sheet *sheet) +{ + if (s2t_primary(sheet)==0) return 0; + while (*s2t_s=='^') + { + *s2t_t++=*s2t_s++; + if (s2t_primary(sheet)==0) return 0; + } + return 1; +} +/*}}}*/ +/* s2t_piterm */ /*{{{*/ +static int s2t_piterm(Sheet *sheet) +{ + if (s2t_powterm(sheet)==0) return 0; + while (*s2t_s=='*' || *s2t_s=='/') + { + *s2t_t++=*s2t_s++; + if (s2t_powterm(sheet)==0) return 0; + } + return 1; +} +/*}}}*/ +/* s2t_term */ /*{{{*/ +static int s2t_term(Sheet *sheet) +{ + if (s2t_piterm(sheet)==0) return 0; + while (*s2t_s=='+' || *s2t_s=='-') + { + *s2t_t++=*s2t_s++; + if (s2t_piterm(sheet)==0) return 0; + } + return 1; +} +/*}}}*/ + +/* loadsc */ /*{{{*/ +const char *loadsc(Sheet *sheet, const char *name) +{ + /* variables */ /*{{{*/ + static char errbuf[80]; + FILE *fp; + char buf[256]; + int line; + size_t width; + const char *err; + int x,y; + /*}}}*/ + + if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno); + freesheet(sheet,0); + err=(const char*)0; + line=1; + while (fgets(buf,sizeof(buf),fp)!=(char*)0) + { + /* remove nl */ /*{{{*/ + width=strlen(buf); + if (width>0 && buf[width-1]=='\n') buf[width-1]='\0'; + /*}}}*/ + if (buf[0] && buf[0]!='#') + { + if (strncmp(buf,"format ",7)==0) /* format col width precision whoknows */ /*{{{*/ + { + char colstr[3]; + int col,colwidth,precision,whoknows; + + sscanf(buf+7,"%s %d %d %d",colstr,&colwidth,&precision,&whoknows); + col=(colstr[0]-'A'); if (colstr[1]) col=col*26+(colstr[1]-'A'); + initcell(sheet,col,0,0); + SHEET(sheet,col,0,0)->adjust=RIGHT; + SHEET(sheet,col,0,0)->precision=precision; + setwidth(sheet,col,0,colwidth); + } + /*}}}*/ + else if (strncmp(buf,"leftstring ",11)==0 || strncmp(buf,"rightstring ",12)==0) /* rightstring/leftstring cell = "string" */ /*{{{*/ + { + int x,y; + const char *s; + Token **contents; + + if (strncmp(buf,"leftstring ",11)==0) s=buf+11; else s=buf+12; + x=*s++-'A'; if (*s>='A' && *s<='Z') x=x*26+(*s++-'A'); + y=*s++-'0'; while (*s>='0' && *s<='9') y=10*y+(*s++-'0'); + s+=3; + contents=scan(&s); + if (contents==(Token**)0) + { + tvecfree(contents); + sprintf(errbuf,_("Expression syntax error in line %d"),line); + err=errbuf; + goto eek; + } + initcell(sheet,x,y,0); + SHEET(sheet,x,y,0)->adjust=strncmp(buf,"leftstring ",11) ? RIGHT : LEFT; + SHEET(sheet,x,y,0)->contents=contents; + } + /*}}}*/ + else if (strncmp(buf,"let ",4)==0) /* let cell = expression */ /*{{{*/ + { + /* variables */ /*{{{*/ + const char *s; + Token **contents; + char newbuf[512]; + /*}}}*/ + + s=buf+4; + x=*s++-'A'; if (*s>='A' && *s<='Z') x=x*26+(*s++-'A'); + y=*s++-'0'; while (*s>='0' && *s<='9') y=10*y+(*s++-'0'); + if (getcont(sheet,x,y,0,0)==(Token**)0) + { + s+=3; + s2t_s=s; s2t_t=newbuf; + if (s2t_term(sheet)==0) + { + *s2t_t='\0'; + if (err==(const char*)0) + { + sprintf(errbuf,_("Unimplemented SC feature in line %d"),line); + err=errbuf; + } + } + *s2t_t='\0'; + s=newbuf; + contents=scan(&s); + if (contents==(Token**)0) + { + tvecfree(contents); + sprintf(errbuf,_("Expression syntax error in line %d"),line); + err=errbuf; + goto eek; + } + initcell(sheet,x,y,0); + SHEET(sheet,x,y,0)->adjust=RIGHT; + SHEET(sheet,x,y,0)->contents=contents; + } + } + /*}}}*/ + } + ++line; + } + /* set precisions for each column */ /*{{{*/ + for (x=0; xdimx; ++x) + { + int prec; + + prec=getprecision(sheet,x,0,0)==def_precision ? 2 : getprecision(sheet,x,0,0); + for (y=1; ydimy; ++y) if (SHEET(sheet,x,y,0)) SHEET(sheet,x,y,0)->precision=prec; + } + /*}}}*/ + eek: + if (fclose(fp)==EOF && err==(const char*)0) err=strerror(errno); + sheet->changed=0; + cachelabels(sheet); + forceupdate(sheet); + return err; +} +/*}}}*/ diff --git a/sc.h b/sc.h new file mode 100644 index 0000000..9e37911 --- /dev/null +++ b/sc.h @@ -0,0 +1,8 @@ +#ifndef SC_H +#define SC_H + +#include "sheet.h" + +const char *loadsc(Sheet *sheet, const char *name); + +#endif diff --git a/scanner.c b/scanner.c new file mode 100644 index 0000000..aec7bae --- /dev/null +++ b/scanner.c @@ -0,0 +1,362 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include +extern double strtod(const char *nptr, char **endptr); /* SunOS 4 hack */ +#include + + +#include "default.h" +#include "func.h" +#include "main.h" +#include "misc.h" +#include "scanner.h" +/*}}}*/ + +/* identcode -- return number of identifier */ /*{{{*/ +int identcode(const char *s, size_t len) +{ + Tfunc *p; + int fident; + + for (p=tfunc,fident=0; p->name[0]!='\0' && (len!=strlen(p->name) || strncmp(s,p->name,len)); ++p,++fident); + if (p->name[0]=='\0') return -1; + else return fident; +} +/*}}}*/ + +/* charstring -- match quoted string and return token */ /*{{{*/ +static Token *charstring(const char **s) +{ + const char *r; + + r=*s; + if (**s=='"') + { + ++(*s); + while (**s!='\0' && **s!='"') if (**s=='\\' && *((*s)+1)!='\0') (*s)+=2; else ++(*s); + if (**s=='\0') { *s=r; return 0; } + else + { + Token *n; + char *t; + + ++(*s); + n=malloc(sizeof(Token)); + n->type=STRING; + t=n->u.string=malloc((size_t)(*s-r)); + /* Clean string of quotes. This may waste a few bytes, so? */ + ++r; + while (r<(*s-1)) if (*r=='\\') { *t++=*(r+1); r+=2; } else *t++=*r++; + *t='\0'; + return n; + } + } + else return (Token*)0; +} +/*}}}*/ +/* integer -- match an unsigned integer and return token */ /*{{{*/ +static Token *integer(const char **s) +{ + const char *r; + long i; + + r=*s; + i=posnumber(r,s); + if (*s!=r && **s!='.' && **s!='e') + { + Token *n; + + n=malloc(sizeof(Token)); + n->type=INT; + n->u.integer=i; + return n; + } + else { *s=r; return (Token*)0; } +} +/*}}}*/ +/* flt -- match a floating point number */ /*{{{*/ +static Token *flt(const char **s) +{ + /* variables */ /*{{{*/ + const char *t; + char *end; + Token *n; + double x; + /*}}}*/ + + t=*s; + x=strtod(t,&end); + *s = end; + if (t!=*s && dblfinite(x)==(const char*)0) + { + n=malloc(sizeof(Token)); + n->type=FLOAT; + n->u.flt=x; + return n; + } + else + { + *s=t; + return (Token*)0; + } +} +/*}}}*/ +/* op -- match an op and return token */ /*{{{*/ +static Token *op(const char **s) +{ + Token *n; + Operator op; + + switch (**s) + { + case '+': op=PLUS; break; + case '-': op=MINUS; break; + case '*': op=MUL; break; + case '/': op=DIV; break; + case '%': op=MOD; break; + case '(': op=OP; break; + case ')': op=CP; break; + case ',': op=COMMA; break; + case '^': op=POW; break; + case '<': if (*(*s+1)=='=') { ++(*s); op=LE; } else op=LT; break; + case '>': if (*(*s+1)=='=') { ++(*s); op=GE; } else op=GT; break; + case '=': if (*(*s+1)=='=') { ++(*s); op=ISEQUAL; } else return (Token*)0; break; + case '~': if (*(*s+1)=='=') { ++(*s); op=ABOUTEQ; } else return (Token*)0; break; + case '!': if (*(*s+1)=='=') { ++(*s); op=NE; } else return (Token*)0; break; + default: return (Token*)0; + } + n=malloc(sizeof(Token)); + n->type=OPERATOR; + n->u.op=op; + ++(*s); + return n; +} +/*}}}*/ +/* ident -- match an identifier and return token */ /*{{{*/ +static Token *ident(const char **s) +{ + const char *begin; + Token *result; + + if (isalpha((int)**s) || **s=='_' || **s=='@' || **s=='&' || **s=='.' || **s=='$') + { + int fident; + + begin=*s; ++(*s); + while (isalpha((int)**s) || **s=='_' || **s=='@' || **s=='&' || **s=='.' || **s=='$' || isdigit((int)**s)) ++(*s); + result=malloc(sizeof(Token)); + if ((fident=identcode(begin,(size_t)(*s-begin)))==-1) + { + result->type=LIDENT; + result->u.lident=malloc((size_t)(*s-begin+1)); + (void)strncpy(result->u.lident,begin,(size_t)(*s-begin)); + result->u.lident[*s-begin]='\0'; + } + else + { + result->type=FIDENT; + result->u.fident=fident; + } + return result; + } + return (Token*)0; +} +/*}}}*/ + +/* scan -- scan string into tokens */ /*{{{*/ +Token **scan(const char **s) +{ + /* variables */ /*{{{*/ + Token **na,*n; + const char *r; + int i,j; + /*}}}*/ + + /* compute number of tokens */ /*{{{*/ + r=*s; + while (*r==' ') ++r; + for (i=0; *r!='\0'; ++i) + { + const char *or; + + or=r; + while (*r==' ') ++r; + n=charstring(&r); + if (n==(Token*)0) n=op(&r); + if (n==(Token*)0) n=integer(&r); + if (n==(Token*)0) n=flt(&r); + if (n==(Token*)0) n=ident(&r); + if (or==r) { *s=r; return (Token**)0; } + } + /*}}}*/ + /* allocate token space */ /*{{{*/ + na=malloc(sizeof(Token*)*(i+1)); + /*}}}*/ + /* store tokens */ /*{{{*/ + r=*s; + while (*r==' ') ++r; + for (j=0; jtype) + { + /* EMPTY */ /*{{{*/ + case EMPTY: assert(cur==0); *(s+cur)='\0'; ++cur; break; + /*}}}*/ + /* STRING */ /*{{{*/ + case STRING: + { + char *str=(*n)->u.string; + + if (quote) + { + *(s+cur)='"'; + ++cur; + } + while (curu.integer); + assert(buflenu.flt); + assert(lenbuf && *p=='0' && *(p-1)!='.') { *p='\0'; --p; --len; } + } + p=buf+len; + while (*--p==' ') { *p='\0'; --len; } + (void)strncpy(s+cur,buf,size-cur-1); + cur+=len; + break; + } + /*}}}*/ + /* OPERATOR */ /*{{{*/ + case OPERATOR: + { + static const char *ops[]={ "+", "-", "*", "/", "(", ")", ",", "<", "<=", ">=", ">", "==", "~=", "!=", "^", "%" }; + + if ((size-cur)>1) *(s+cur++)=*ops[(*n)->u.op]; + if (*(ops[(*n)->u.op]+1) && (size-cur)>1) *(s+cur++)=*(ops[(*n)->u.op]+1); + break; + } + /*}}}*/ + /* LIDENT */ /*{{{*/ + case LIDENT: + { + size_t identlen; + + identlen=strlen((*n)->u.lident); + if ((cur+identlen+1)<=size) strcpy(s+cur,(*n)->u.lident); + else (void)strncpy(s+cur,(*n)->u.lident,size-cur-1); + cur+=identlen; + break; + } + /*}}}*/ + /* FIDENT */ /*{{{*/ + case FIDENT: + { + size_t identlen; + + identlen=strlen(tfunc[(*n)->u.fident].name); + if ((cur+identlen+1)<=size) strcpy(s+cur,tfunc[(*n)->u.fident].name); + else (void)strncpy(s+cur,tfunc[(*n)->u.fident].name,size-cur-1); + cur+=identlen; + break; + } + /*}}}*/ + /* LOCATION */ /*{{{*/ + case LOCATION: + { + char buf[60]; + + sprintf(buf,"&(%d,%d,%d)",(*n)->u.location[0],(*n)->u.location[1],(*n)->u.location[2]); + (void)strncpy(s+cur,buf,size-cur-1); + cur+=strlen(buf); + break; + } + /*}}}*/ + /* EEK */ /*{{{*/ + case EEK: + { + (void)strncpy(s+cur,_("ERROR"),size-cur-1); + cur+=5; + break; + } + /*}}}*/ + /* default */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + if (cur chars) { + for (cur=0; cur < chars; ++cur) s[cur] = '#'; + s[cur] = 0; + } +} +/*}}}*/ diff --git a/scanner.h b/scanner.h new file mode 100644 index 0000000..f7cb0e8 --- /dev/null +++ b/scanner.h @@ -0,0 +1,43 @@ +#ifndef SCANNER_H +#define SCANNER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + EMPTY +#ifndef __cplusplus + , STRING, FLOAT, INT, OPERATOR, LIDENT, FIDENT, LOCATION, EEK +#endif +} Type; + +typedef enum { PLUS, MINUS, MUL, DIV, OP, CP, COMMA, LT, LE, GE, GT, ISEQUAL, ABOUTEQ, NE, POW, MOD } Operator; + +typedef struct +{ + Type type; + union + { + char *string; + double flt; + long integer; + Operator op; + char *lident; + int fident; + int location[3]; + char *err; + } u; +} Token; + +int identcode(const char *s, size_t len); +Token **scan(const char **s); +void print(char *s, size_t size, size_t chars, int quote, int scientific, int precision, Token **n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sheet.c b/sheet.c new file mode 100644 index 0000000..6cd7625 --- /dev/null +++ b/sheet.c @@ -0,0 +1,2014 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include +#include +#include + + +#include "csv.h" +#include "default.h" +#include "display.h" +#include "eval.h" +#include "main.h" +#include "misc.h" +#include "parser.h" +#include "scanner.h" +#include "sheet.h" +#include "utf8.h" +#include "xdr.h" + +/*}}}*/ +/* #defines */ /*{{{*/ +#define SHEET(s,x,y,z) (*(s->sheet+(x)*s->dimz*s->dimy+(y)*s->dimz+(z))) + +#define HASH(x,s) \ +{ \ + const unsigned char *S=(const unsigned char*)s; \ + \ + x=0; \ + while (*S) { x=(x<<5)+((x>>27)^*S); ++S; } \ + x%=LABEL_CACHE; \ +} + +#define SHADOWED(sheet,x,y,z) (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->shadowed) +/*}}}*/ + +/* variables */ /*{{{*/ +static int upd_clock; /* evaluate clocked expressions */ +/* Used during evaluation of a cell to specify the currently updated cell */ +Sheet *upd_sheet; +int upd_x; +int upd_y; +int upd_z; +int max_eval; +/*}}}*/ + +/* copycell -- copy a cell */ /*{{{*/ +static void copycell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2) +{ + /* variables */ /*{{{*/ + Token **run; + int i,len; + /*}}}*/ + + assert(sheet1!=(Sheet*)0); + assert(sheet2!=(Sheet*)0); + if (x1dimx && y1dimy && z1dimz) + { + sheet2->changed=1; + if (SHEET(sheet1,x1,y1,z1)==(Cell*)0) freecell(sheet2,x2,y2,z2); + else + /* copy first cell to second */ /*{{{*/ + { + freecell(sheet2,x2,y2,z2); + initcell(sheet2,x2,y2,z2); + memcpy(SHEET(sheet2,x2,y2,z2),SHEET(sheet1,x1,y1,z1),sizeof(Cell)); + if (SHEET(sheet1,x1,y1,z1)->contents!=(Token**)0) + { + for (len=1,run=SHEET(sheet1,x1,y1,z1)->contents; *run!=(Token*)0; ++len,++run); + SHEET(sheet2,x2,y2,z2)->contents=malloc(len*sizeof(Token*)); + for (i=0; icontents+i)==(Token*)0) *(SHEET(sheet2,x2,y2,z2)->contents+i)=(Token*)0; + else + { + *(SHEET(sheet2,x2,y2,z2)->contents+i)=malloc(sizeof(Token)); + **(SHEET(sheet2,x2,y2,z2)->contents+i)=tcopy(**(SHEET(sheet1,x1,y1,z1)->contents+i)); + } + } + } + if (SHEET(sheet1,x1,y1,z1)->ccontents!=(Token**)0) + { + for (len=1,run=SHEET(sheet1,x1,y1,z1)->ccontents; *run!=(Token*)0; ++len,++run); + SHEET(sheet2,x2,y2,z2)->ccontents=malloc(len*sizeof(Token*)); + for (i=0; iccontents+i)==(Token*)0) *(SHEET(sheet2,x2,y2,z2)->ccontents+i)=(Token*)0; + else + { + *(SHEET(sheet2,x2,y2,z2)->ccontents+i)=malloc(sizeof(Token)); + **(SHEET(sheet2,x2,y2,z2)->ccontents+i)=tcopy(**(SHEET(sheet1,x1,y1,z1)->ccontents+i)); + } + } + } + if (SHEET(sheet1,x1,y1,z1)->label!=(char*)0) SHEET(sheet2,x2,y2,z2)->label=strcpy(malloc(strlen(SHEET(sheet1,x1,y1,z1)->label)+1),SHEET(sheet1,x1,y1,z1)->label); + SHEET(sheet2,x2,y2,z2)->value.type=EMPTY; + } + /*}}}*/ + } + else freecell(sheet2,x2,y2,z2); +} +/*}}}*/ +/* swapblock -- swap two non-overlapping blocks of cells */ /*{{{*/ +static void swapblock(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int xdist, int ydist, int zdist) +{ + int xoff, yoff, zoff; + + assert(sheet1!=(Sheet*)0); + assert(sheet2!=(Sheet*)0); + resize(sheet1,x1+xdist-1,y1+ydist-1,z1+zdist-1); + resize(sheet2,x2+xdist-1,y2+ydist-1,z2+zdist-1); + for (xoff=0; xoffchanged=1; + sheet2->changed=1; +} +/*}}}*/ +/* cmpcell -- compare to cells with given order flags */ /*{{{*/ +/* Notes */ /*{{{*/ +/* +Compare the _values_ of two cells. The result is -1 if first is smaller +than second, 0 if they are equal and 1 if the first is bigger than the +second. A result of 2 means they are not comparable. +*/ +/*}}}*/ +static int cmpcell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int sortkey) +{ + assert(sheet1!=(Sheet*)0); + assert(sheet2!=(Sheet*)0); + /* empty cells are smaller than any non-empty cell */ /*{{{*/ + if (x1>=sheet1->dimx || y1>=sheet1->dimy || z1>=sheet1->dimz || SHEET(sheet1,x1,y1,z1)==(Cell*)0 || SHEET(sheet1,x1,y1,z1)->value.type==EMPTY) + { + if (x2>=sheet2->dimx || y2>=sheet2->dimy || z2>=sheet2->dimz || SHEET(sheet2,x2,y2,z2)==(Cell*)0 || SHEET(sheet2,x2,y2,z2)->value.type==EMPTY) return 0; + else return (sortkey&ASCENDING ? -1 : 1); + } + if (x2>=sheet2->dimx || y2>=sheet2->dimy || z2>=sheet2->dimz || SHEET(sheet2,x2,y2,z2)==(Cell*)0 || SHEET(sheet2,x2,y2,z2)->value.type==EMPTY) return (sortkey&ASCENDING ? 1 : -1); + /*}}}*/ + switch (SHEET(sheet1,x1,y1,z1)->value.type) + { + /* STRING */ /*{{{*/ + case STRING: + { + if (SHEET(sheet2,x2,y2,z2)->value.type==STRING) + { + int r; + + r=strcmp(SHEET(sheet1,x1,y1,z1)->value.u.string,SHEET(sheet2,x2,y2,z2)->value.u.string); + if (r<0) return (sortkey&ASCENDING ? -1 : 1); + else if (r==0) return 0; + else return (sortkey&ASCENDING ? 1 : -1); + } + return 2; + } + /*}}}*/ + /* FLOAT */ /*{{{*/ + case FLOAT: + { + if (SHEET(sheet2,x2,y2,z2)->value.type==FLOAT) + { + if (SHEET(sheet1,x1,y1,z1)->value.u.fltvalue.u.flt) return (sortkey&ASCENDING ? -1 : 1); + else if (SHEET(sheet1,x1,y1,z1)->value.u.flt==SHEET(sheet2,x2,y2,z2)->value.u.flt) return 0; + else return (sortkey&ASCENDING ? 1 : -1); + } + if (SHEET(sheet2,x2,y2,z2)->value.type==INT) + { + if (SHEET(sheet1,x1,y1,z1)->value.u.fltvalue.u.integer) return (sortkey&ASCENDING ? -1 : 1); + else if (SHEET(sheet1,x1,y1,z1)->value.u.flt==SHEET(sheet2,x2,y2,z2)->value.u.integer) return 0; + else return (sortkey&ASCENDING ? 1 : -1); + } + return 2; + } + /*}}}*/ + /* INT */ /*{{{*/ + case INT: + { + if (SHEET(sheet2,x2,y2,z2)->value.type==INT) + { + if (SHEET(sheet1,x1,y1,z1)->value.u.integervalue.u.integer) return (sortkey&ASCENDING ? -1 : 1); + else if (SHEET(sheet1,x1,y1,z1)->value.u.integer==SHEET(sheet2,x2,y2,z2)->value.u.integer) return 0; + else return (sortkey&ASCENDING ? 1 : -1); + } + if (SHEET(sheet2,x2,y2,z2)->value.type==FLOAT) + { + if (SHEET(sheet1,x1,y1,z1)->value.u.integervalue.u.flt) return (sortkey&ASCENDING ? -1 : 1); + else if (SHEET(sheet1,x1,y1,z1)->value.u.integer==SHEET(sheet2,x2,y2,z2)->value.u.flt) return 0; + else return (sortkey&ASCENDING ? 1 : -1); + } + return 2; + } + /*}}}*/ + default: return 2; + } +} +/*}}}*/ + +/* resize -- check if sheet needs to be resized in any dimension */ /*{{{*/ +void resize(Sheet *sheet, int x, int y, int z) +{ + assert(x>=0); + assert(y>=0); + assert(z>=0); + assert(sheet!=(Sheet*)0); + if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz) + { + /* variables */ /*{{{*/ + Cell **newsheet; + int *newcolumn; + unsigned int ndimx,ndimy,ndimz; + /*}}}*/ + + sheet->changed=1; + ndimx=(x>=sheet->dimx ? x+1 : sheet->dimx); + ndimy=(y>=sheet->dimy ? y+1 : sheet->dimy); + ndimz=(z>=sheet->dimz ? z+1 : sheet->dimz); + /* allocate new sheet */ /*{{{*/ + newsheet=malloc(ndimx*ndimy*ndimz*sizeof(Cell*)); + for (x=0; xdimx && ydimy && zdimz) *(newsheet+x*ndimz*ndimy+y*ndimz+z)=SHEET(sheet,x,y,z); + else *(newsheet+x*ndimz*ndimy+y*ndimz+z)=(Cell*)0; + } + if (sheet->sheet!=(Cell**)0) free(sheet->sheet); + sheet->sheet=newsheet; + /*}}}*/ + /* allocate new columns */ /*{{{*/ + if (x>sheet->dimx || z>=sheet->dimz) + { + newcolumn=malloc(ndimx*ndimz*sizeof(int)); + for (x=0; xdimx && zdimz) *(newcolumn+x*ndimz+z)=*(sheet->column+x*sheet->dimz+z); + else *(newcolumn+x*ndimz+z)=DEF_COLUMNWIDTH; + } + if (sheet->column!=(int*)0) free(sheet->column); + sheet->column=newcolumn; + } + /*}}}*/ + sheet->dimx=ndimx; + sheet->dimy=ndimy; + sheet->dimz=ndimz; + } +} +/*}}}*/ +/* initcell -- initialise new cell, if it does not exist yet */ /*{{{*/ +void initcell(Sheet *sheet, int x, int y, int z) +{ + assert(x>=0); + assert(y>=0); + assert(z>=0); + resize(sheet,x,y,z); + if (SHEET(sheet,x,y,z)==(Cell*)0) + { + sheet->changed=1; + SHEET(sheet,x,y,z)=malloc(sizeof(Cell)); + SHEET(sheet,x,y,z)->contents=(Token**)0; + SHEET(sheet,x,y,z)->ccontents=(Token**)0; + SHEET(sheet,x,y,z)->label=(char*)0; + SHEET(sheet,x,y,z)->adjust=AUTOADJUST; + SHEET(sheet,x,y,z)->precision=-1; + SHEET(sheet,x,y,z)->shadowed=0; + SHEET(sheet,x,y,z)->bold=0; + SHEET(sheet,x,y,z)->underline=0; + SHEET(sheet,x,y,z)->scientific=DEF_SCIENTIFIC; + SHEET(sheet,x,y,z)->value.type=EMPTY; + SHEET(sheet,x,y,z)->resvalue.type=EMPTY; + SHEET(sheet,x,y,z)->locked=0; + SHEET(sheet,x,y,z)->ignored=0; + SHEET(sheet,x,y,z)->clock_t0=0; + SHEET(sheet,x,y,z)->clock_t1=0; + SHEET(sheet,x,y,z)->clock_t2=0; + } +} +/*}}}*/ +/* cachelabels -- create new label cache */ /*{{{*/ +void cachelabels(Sheet *sheet) +{ + int i,x,y,z; + + if (sheet==(Sheet*)0) return; + for (i=0; ilabelcache[i]; run!=(struct Label*)0;) + { + struct Label *runnext; + + runnext=run->next; + free(run); + run=runnext; + } + sheet->labelcache[i]=(struct Label*)0; + } + /*}}}*/ + for (x=0; xdimx; ++x) for (y=0; ydimy; ++y) for (z=0; zdimz; ++z) + /* cache all labels */ /*{{{*/ + { + const char *l; + + l=getlabel(sheet,x,y,z); + if (*l) + { + unsigned long hx; + struct Label **run; + + HASH(hx,l); + for (run=&sheet->labelcache[(unsigned int)hx]; *run!=(struct Label*)0 && strcmp(l,(*run)->label); run=&((*run)->next)); + if (*run==(struct Label*)0) + { + *run=malloc(sizeof(struct Label)); + (*run)->next=(struct Label*)0; + (*run)->label=l; + (*run)->x=x; + (*run)->y=y; + (*run)->z=z; + } + /* else we have a duplicate label, which _can_ happen under */ + /* unfortunate conditions. Don't tell anybody. */ + } + } + /*}}}*/ +} +/*}}}*/ +/* freesheet -- free all cells of an entire spread sheet */ /*{{{*/ +void freesheet(Sheet *sheet, int all) +{ + /* variables */ /*{{{*/ + int x,y,z; + /*}}}*/ + + assert(sheet!=(Sheet*)0); + sheet->changed=0; + for (x=0; xdimx; ++x) for (y=0; ydimy; ++y) for (z=0; zdimz; ++z) + { + freecell(sheet,x,y,z); + } + if (all) + { + int i; + + for (i=0; ilabelcache[i]; run!=(struct Label*)0;) + { + struct Label *runnext; + + runnext=run->next; + free(run); + run=runnext; + } + } + /*}}}*/ + if (sheet->sheet) free(sheet->sheet); + if (sheet->column) free(sheet->column); + if (sheet->name) free(sheet->name); + } + else + { + for (x=0; xdimx; ++x) for (z=0; zdimz; ++z) + { + *(sheet->column+x*sheet->dimz+z)=DEF_COLUMNWIDTH; + } + cachelabels(sheet); + forceupdate(sheet); + } +} +/*}}}*/ +/* forceupdate -- clear all clock and update flags */ /*{{{*/ +void forceupdate(Sheet *sheet) +{ + int i; + + assert(sheet!=(Sheet*)0); + for (i=0; idimx*sheet->dimy*sheet->dimz; ++i) if (*(sheet->sheet+i)!=(Cell*)0) + { + (*(sheet->sheet+i))->updated=0; + (*(sheet->sheet+i))->clock_t0=0; + (*(sheet->sheet+i))->clock_t1=0; + (*(sheet->sheet+i))->clock_t2=0; + } + update(sheet); +} +/*}}}*/ +/* freecell -- free one cell */ /*{{{*/ +void freecell(Sheet *sheet, int x, int y, int z) +{ + assert(sheet!=(Sheet*)0); + if (sheet->sheet!=(Cell**)0 && xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0) + { + tvecfree(SHEET(sheet,x,y,z)->contents); + tvecfree(SHEET(sheet,x,y,z)->ccontents); + tfree(&(SHEET(sheet,x,y,z)->value)); + free(SHEET(sheet,x,y,z)); + SHEET(sheet,x,y,z)=(Cell*)0; + sheet->changed=1; + } +} +/*}}}*/ +/* columnwidth -- get width of column */ /*{{{*/ +int columnwidth(Sheet *sheet, int x, int z) +{ + assert(sheet!=(Sheet*)0); + if (xdimx && zdimz) return (*(sheet->column+x*sheet->dimz+z)); + else return DEF_COLUMNWIDTH; +} +/*}}}*/ +/* setwidth -- set width of column */ /*{{{*/ +void setwidth(Sheet *sheet, int x, int z, int width) +{ + assert(sheet!=(Sheet*)0); + resize(sheet,x,1,z); + sheet->changed=1; + *(sheet->column+x*sheet->dimz+z)=width; +} +/*}}}*/ +/* cellwidth -- get width of a cell */ /*{{{*/ +int cellwidth(Sheet *sheet, int x, int y, int z) +{ + int width; + + if (SHADOWED(sheet,x,y,z)) return 0; + width=columnwidth(sheet,x,z); + for (++x; SHADOWED(sheet,x,y,z); width+=columnwidth(sheet,x,z),++x); + return width; +} +/*}}}*/ +/* putcont -- assign new contents */ /*{{{*/ +void putcont(Sheet *sheet, int x, int y, int z, Token **t, int c) +{ + assert(sheet!=(Sheet*)0); + sheet->changed=1; + resize(sheet,x,y,z); + initcell(sheet,x,y,z); + if (c) + { + tvecfree(SHEET(sheet,x,y,z)->ccontents); + SHEET(sheet,x,y,z)->ccontents=t; + } + else + { + tvecfree(SHEET(sheet,x,y,z)->contents); + SHEET(sheet,x,y,z)->contents=t; + } + redraw_cell(sheet, x, y, z); +} +/*}}}*/ +/* getcont -- get contents */ /*{{{*/ +Token **getcont(Sheet *sheet, int x, int y, int z, int c) +{ + if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz) return (Token**)0; + else if (SHEET(sheet,x,y,z)==(Cell*)0) return (Token**)0; + else if (c==2) return (SHEET(sheet,x,y,z)->clock_t0 && SHEET(sheet,x,y,z)->ccontents ? SHEET(sheet,x,y,z)->ccontents : SHEET(sheet,x,y,z)->contents); + else return (c ? SHEET(sheet,x,y,z)->ccontents : SHEET(sheet,x,y,z)->contents); +} +/*}}}*/ +/* getvalue -- get tcopy()ed value */ /*{{{*/ +Token getvalue(Sheet *sheet, int x, int y, int z) +{ + /* variables */ /*{{{*/ + Token result; + int orig_upd_clock; + /*}}}*/ + + assert(sheet!=(Sheet*)0); + if (x<0 || y<0 || z<0) + /* return error */ /*{{{*/ + { + result.type=EEK; + result.u.err=mystrmalloc(_("Negative index")); + } + /*}}}*/ + else if (getcont(sheet,x,y,z,2)==(Token**)0) + /* return empty value */ /*{{{*/ + result.type=EMPTY; + /*}}}*/ + else + /* update value of this cell if needed and return it */ /*{{{*/ + { + orig_upd_clock=upd_clock; + if (SHEET(sheet,x,y,z)->ignored) + { + /* variables */ /*{{{*/ + Token oldvalue; + /*}}}*/ + + oldvalue=SHEET(sheet,x,y,z)->value; + SHEET(sheet,x,y,z)->updated=1; + SHEET(sheet,x,y,z)->value.type=EMPTY; + tfree(&oldvalue); + } + else if (SHEET(sheet,x,y,z)->updated==0) + { + /* variables */ /*{{{*/ + Sheet *old_sheet; + int old_x,old_y,old_z,old_max_eval; + Token oldvalue; + /*}}}*/ + + old_sheet=upd_sheet; + old_x=upd_x; + old_y=upd_y; + old_z=upd_z; + old_max_eval=max_eval; + upd_sheet=sheet; + upd_x=x; + upd_y=y; + upd_z=z; + max_eval=MAX_EVALNEST; + if (SHEET(sheet,x,y,z)->clock_t1==0) + { + SHEET(sheet,x,y,z)->updated=1; + oldvalue=SHEET(sheet,x,y,z)->value; + upd_clock=0; + SHEET(sheet,x,y,z)->value=eval(getcont(sheet,x,y,z,2)); + tfree(&oldvalue); + } + else if (upd_clock) + { + SHEET(sheet,x,y,z)->updated=1; + upd_clock=0; + SHEET(sheet,x,y,z)->resvalue=eval(getcont(sheet,x,y,z,2)); + } + upd_sheet=old_sheet; + upd_x=old_x; + upd_y=old_y; + upd_z=old_z; + max_eval=old_max_eval; + } + if (!orig_upd_clock) result=tcopy(SHEET(sheet,x,y,z)->value); + } + /*}}}*/ + return result; +} +/*}}}*/ +/* update -- update all cells that need it */ /*{{{*/ +void update(Sheet *sheet) +{ + int x,y,z,kp,iterating; + + assert(sheet!=(Sheet*)0); + kp=0; + iterating=0; + do + { + sheet->clk=0; + if (iterating==1) + { + line_msg((const char*)0,_("Calculating running, press Escape to abort it")); + ++iterating; + } + else if (iterating==0) ++iterating; + for (x=0; xdimx; ++x) for (y=0; ydimy; ++y) for (z=0; zdimz; ++z) + { + if (SHEET(sheet,x,y,z) && SHEET(sheet,x,y,z)->clock_t2) + { + SHEET(sheet,x,y,z)->updated=0; + SHEET(sheet,x,y,z)->clock_t0=1; + SHEET(sheet,x,y,z)->clock_t1=1; + SHEET(sheet,x,y,z)->clock_t2=0; + } + } + for (x=0; xdimx; ++x) for (y=0; ydimy; ++y) for (z=0; zdimz; ++z) + { + upd_clock=1; + getvalue(sheet,x,y,z); + } + for (x=0; xdimx; ++x) for (y=0; ydimy; ++y) for (z=0; zdimz; ++z) + { + if (SHEET(sheet,x,y,z) && SHEET(sheet,x,y,z)->clock_t1) + { + tfree(&(SHEET(sheet,x,y,z)->value)); + SHEET(sheet,x,y,z)->value=SHEET(sheet,x,y,z)->resvalue; + SHEET(sheet,x,y,z)->clock_t1=0; + } + } + upd_clock=0; + } while (sheet->clk && !(kp=keypressed())); + if (iterating==2) line_msg((const char*)0,kp ? _("Calculation aborted") : _("Calculation finished")); + sheet->clk=0; + redraw_sheet(sheet); +} +/*}}}*/ +/* geterror -- get malloc()ed error string */ /*{{{*/ +char *geterror(Sheet *sheet, int x, int y, int z) +{ + Token v; + + assert(sheet!=(Sheet*)0); + if ((v=getvalue(sheet,x,y,z)).type!=EEK) + { + tfree(&v); + return (char*)0; + } + else + { + return (v.u.err); + } +} +/*}}}*/ +/* printvalue -- get ASCII representation of value */ /*{{{*/ +void printvalue(char *s, size_t size, size_t chars, int quote, int scientific, int precision, Sheet *sheet, int x, int y, int z) +{ + Token *tv[2],t; + + assert(sheet!=(Sheet*)0); + t=getvalue(sheet,x,y,z); tv[0]=&t; + tv[1]=(Token*)0; + print(s,size,chars,quote,scientific,precision,tv); + tfree(&t); +} +/*}}}*/ +/* getadjust -- get cell adjustment */ /*{{{*/ +Adjust getadjust(Sheet *sheet, int x, int y, int z) +{ + assert(sheet!=(Sheet*)0); + if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0) + { + return LEFT; + } + else if (SHEET(sheet,x,y,z)->adjust==AUTOADJUST) return (SHEET(sheet,x,y,z)->value.type==INT || SHEET(sheet,x,y,z)->value.type==FLOAT ? RIGHT : LEFT); + else return (SHEET(sheet,x,y,z)->adjust); +} +/*}}}*/ +/* setadjust -- set cell adjustment */ /*{{{*/ +void setadjust(Sheet *sheet, int x, int y, int z, Adjust adjust) +{ + assert(sheet!=(Sheet*)0); + sheet->changed=1; + resize(sheet,x,y,z); + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->adjust=adjust; +} +/*}}}*/ + +/* shadow -- shadow cell by left neighbour */ /*{{{*/ +void shadow(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->shadowed=yep; +} +/*}}}*/ +/* shadowed -- is cell shadowed? */ /*{{{*/ +int shadowed(Sheet *sheet, int x, int y, int z) +{ + return (SHADOWED(sheet,x,y,z)); +} +/*}}}*/ +/* bold -- bold font */ /*{{{*/ +void bold(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->bold=yep; +} +/*}}}*/ +/* isbold -- is cell bold? */ /*{{{*/ +int isbold(Sheet *sheet, int x, int y, int z) +{ + return (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->bold); +} +/*}}}*/ +/* underline -- underline */ /*{{{*/ +void underline(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->underline=yep; +} +/*}}}*/ +/* isunderline -- is cell underlined? */ /*{{{*/ +int underlined(Sheet *sheet, int x, int y, int z) +{ + return (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->underline); +} +/*}}}*/ +/* lockcell -- lock cell */ /*{{{*/ +void lockcell(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->locked=yep; +} +/*}}}*/ +/* locked -- is cell locked? */ /*{{{*/ +int locked(Sheet *sheet, int x, int y, int z) +{ + return (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->locked); +} +/*}}}*/ +/* transparent -- is cell transparent? */ /*{{{*/ +int transparent(Sheet *sheet, int x, int y, int z) +{ + return (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->transparent); +} +/*}}}*/ +/* maketrans -- make cell transparent */ /*{{{*/ +void maketrans(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->transparent=yep; +} +/*}}}*/ +/* igncell -- ignore cell */ /*{{{*/ +void igncell(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->ignored=yep; +} +/*}}}*/ +/* ignored -- is cell ignored? */ /*{{{*/ +int ignored(Sheet *sheet, int x, int y, int z) +{ + return (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->ignored); +} +/*}}}*/ +/* clk -- clock cell */ /*{{{*/ +void clk(Sheet *sheet, int x, int y, int z) +{ + assert(sheet!=(Sheet*)0); + assert(x>=0 && xdimx); + assert(y>=0 && ydimy); + assert(z>=0 && zdimz); + if (SHEET(sheet,x,y,z)) + { + SHEET(sheet,x,y,z)->clock_t2=1; + sheet->clk=1; + } +} +/*}}}*/ +/* setscientific -- cell value should be displayed in scientific notation */ /*{{{*/ +void setscientific(Sheet *sheet, int x, int y, int z, int yep) +{ + sheet->changed=1; + resize(sheet,x,y,z); + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->scientific=yep; +} +/*}}}*/ +/* getscientific -- should value be displayed in scientific notation? */ /*{{{*/ +int getscientific(Sheet *sheet, int x, int y, int z) +{ + if (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0) return SHEET(sheet,x,y,z)->scientific; + else return DEF_SCIENTIFIC; +} +/*}}}*/ +/* setprecision -- set cell precision */ /*{{{*/ +void setprecision(Sheet *sheet, int x, int y, int z, int precision) +{ + sheet->changed=1; + resize(sheet,x,y,z); + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->precision=precision; +} +/*}}}*/ +/* getprecision -- get cell precision */ /*{{{*/ +int getprecision(Sheet *sheet, int x, int y, int z) +{ + if (xdimx && ydimy && zdimz && SHEET(sheet,x,y,z)!=(Cell*)0) return (SHEET(sheet,x,y,z)->precision==-1 ? def_precision : SHEET(sheet,x,y,z)->precision); + else return def_precision; +} +/*}}}*/ +/* getlabel -- get cell label */ /*{{{*/ +const char *getlabel(Sheet *sheet, int x, int y, int z) +{ + if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0 || SHEET(sheet,x,y,z)->label==(char*)0) return ""; + else return (SHEET(sheet,x,y,z)->label); +} +/*}}}*/ +/* setlabel -- set cell label */ /*{{{*/ +void setlabel(Sheet *sheet, int x, int y, int z, const char *buf, int update) +{ + sheet->changed=1; + resize(sheet,x,y,z); + initcell(sheet,x,y,z); + if (SHEET(sheet,x,y,z)->label!=(char*)0) free(SHEET(sheet,x,y,z)->label); + if (*buf!='\0') SHEET(sheet,x,y,z)->label=strcpy(malloc(strlen(buf)+1),buf); + else SHEET(sheet,x,y,z)->label=(char*)0; + if (update) + { + cachelabels(sheet); + forceupdate(sheet); + } +} +/*}}}*/ +/* findlabel -- return cell location for a given label */ /*{{{*/ +Token findlabel(Sheet *sheet, const char *label) +{ + /* variables */ /*{{{*/ + Token result; + unsigned long hx; + struct Label *run; + /*}}}*/ + + assert(sheet!=(Sheet*)0); +/* + if (sheet==(Sheet*)0) run=(struct Label*)0; + else +*/ + { + HASH(hx,label); + for (run=sheet->labelcache[(unsigned int)hx]; run!=(struct Label*)0 && strcmp(label,run->label); run=run->next); + } + if (run) + { + result.type=LOCATION; + result.u.location[0]=run->x; + result.u.location[1]=run->y; + result.u.location[2]=run->z; + } + else + { + result.type=EEK; + result.u.err=mystrmalloc(_("No such label")); + } + return result; +} +/*}}}*/ +/* relabel -- search and replace for labels */ /*{{{*/ +void relabel(Sheet *sheet, const char *oldlabel, const char *newlabel, int x, int y, int z) +{ + /* variables */ /*{{{*/ + Token **run; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sheet!=(Sheet*)0); + assert(oldlabel!=(const char*)0); + assert(newlabel!=(const char*)0); + assert(x>=0); + assert(y>=0); + assert(z>=0); + /*}}}*/ + if (!(x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0 || SHEET(sheet,x,y,z)->contents==(Token**)0)) + { + for (run=SHEET(sheet,x,y,z)->contents; *run!=(Token*)0; ++run) + { + if ((*run)->type==LIDENT && strcmp((*run)->u.lident,oldlabel)==0) + { + free((*run)->u.lident); + (*run)->u.lident=mystrmalloc(newlabel); + } + } + } + if (!(x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0 || SHEET(sheet,x,y,z)->ccontents==(Token**)0)) + { + for (run=SHEET(sheet,x,y,z)->ccontents; *run!=(Token*)0; ++run) + { + if ((*run)->type==LIDENT && strcmp((*run)->u.lident,oldlabel)==0) + { + free((*run)->u.lident); + (*run)->u.lident=mystrmalloc(newlabel); + } + } + } + cachelabels(sheet); + forceupdate(sheet); +} +/*}}}*/ +/* savexdr -- save a spread sheet in XDR */ /*{{{*/ +const char *savexdr(Sheet *sheet, const char *name, unsigned int *count) +{ + /* variables */ /*{{{*/ + FILE *fp; + XDR xdrs; + int x,y,z; + /*}}}*/ + + *count=0; + if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno); + xdrstdio_create(&xdrs,fp,XDR_ENCODE); + if (!xdr_magic(&xdrs)) + { + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(errno); + } + for (x=sheet->dimx-1; x>=0; --x) for (z=sheet->dimz-1; z>=0; --z) + { + int width; + int u; + + width=columnwidth(sheet,x,z); + if (width!=DEF_COLUMNWIDTH) + { + u=0; + if (xdr_int(&xdrs,&u)==0 || xdr_column(&xdrs,&x,&z,&width)==0) + { + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(errno); + } + } + for (y=sheet->dimy-1; y>=0; --y) + { + if (SHEET(sheet,x,y,z)!=(Cell*)0) + { + u=1; + if (xdr_int(&xdrs,&u)==0 || xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0 || xdr_cell(&xdrs,SHEET(sheet,x,y,z))==0) + { + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(errno); + } + ++*count; + } + } + } + xdr_destroy(&xdrs); + if (fclose(fp)==EOF) return strerror(errno); + sheet->changed=0; + return (const char*)0; +} +/*}}}*/ +/* savetbl -- save as tbl tyble */ /*{{{*/ +const char *savetbl(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count) +{ + /* variables */ /*{{{*/ + FILE *fp=(FILE*)0; /* cause run time error */ + int x,y,z; + char buf[1024]; + char num[20]; + char fullname[PATH_MAX]; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sheet!=(Sheet*)0); + assert(name!=(const char*)0); + /*}}}*/ + *count=0; + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column"); + if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno); + for (z=z1; z<=z2; ++z) + { + if (body) + /* open new file */ /*{{{*/ + { + sprintf(num,".%d",z); + fullname[sizeof(fullname)-strlen(num)-1]='\0'; + (void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + (void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1); + fullname[sizeof(fullname)-1]='\0'; + if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno); + } + /*}}}*/ + else if (fputs_close(".TS\n",fp)==EOF) return strerror(errno); + for (y=y1; y<=y2; ++y) + { + /* print format */ /*{{{*/ + if (y>y1 && fputs_close(".T&\n",fp)==EOF) return strerror(errno); + for (x=x1; x<=x2; ++x) + { + if (x>x1 && fputc_close(' ',fp)==EOF) return strerror(errno); + if (shadowed(sheet,x,y,z)) + { + if (fputc_close('s',fp)==EOF) return strerror(errno); + } + if (isbold(sheet,x,y,z)) + { + if (fputc_close('b',fp)==EOF) return strerror(errno); + } + if (underlined(sheet,x,y,z)) + { + if (fputc_close('u',fp)==EOF) return strerror(errno); + } + else switch (getadjust(sheet,x,y,z)) + { + case LEFT: if (fputc_close('l',fp)==EOF) return strerror(errno); break; + case RIGHT: if (fputc_close('r',fp)==EOF) return strerror(errno); break; + case CENTER: if (fputc_close('c',fp)==EOF) return strerror(errno); break; + default: assert(0); + } + } + if (fputs_close(".\n",fp)==EOF) return strerror(errno); + /*}}}*/ + /* print contents */ /*{{{*/ + for (x=x1; x<=x2; ++x) + { + if (!shadowed(sheet,x,y,z)) + { + if (x>x1 && fputc_close('\t',fp)==EOF) return strerror(errno); + if (SHEET(sheet,x,y,z)!=(Cell*)0) + { + char *bufp; + + printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z); + if (transparent(sheet,x,y,z)) + { + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + else for (bufp=buf; *bufp; ++bufp) switch (*bufp) + { + case '\\': + { + if (fputc_close('\\',fp)==EOF || fputc_close('e',fp)==EOF) return strerror(errno); + break; + } + case '_': + { + if (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF || fputc_close('_',fp)==EOF) return strerror(errno); + break; + } + case '.': + { + if (x==x1 && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno); + if (fputc_close('.',fp)==EOF) return strerror(errno); + break; + } + case '\'': + { + if (x==x1 && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno); + if (fputc_close('\'',fp)==EOF) return strerror(errno); + break; + } + case '-': + { + if (*(bufp+1)=='-') + { + if (fputc_close('-',fp)==EOF) return strerror(errno); + else ++bufp; + } + else if (fputs_close("\\-",fp)==EOF) return strerror(errno); + break; + } + default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno); + } + } + } + } + if (fputc_close('\n',fp)==EOF) return strerror(errno); + /*}}}*/ + ++*count; + } + if (!body) + { + if (fputs_close(".TE\n",fp)==EOF) return strerror(errno); + if (zx1) if (fputc_close(sep,fp)==EOF) return strerror(errno); + if (SHEET(sheet,x,y,z)!=(Cell*)0) + { + char *buf,*s; + + buf=malloc(255*UTF8SZ+1); + printvalue(buf,255*UTF8SZ+1,255,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z); + if (SHEET(sheet,x,y,z)->value.type==STRING && fputc_close('"',fp)==EOF) + { + free(buf); + return strerror(errno); + } + for (s=buf; *s; ++s) + { + if (fputc_close(*s,fp)==EOF || (*s=='"' && fputc_close(*s,fp)==EOF)) + { + free(buf); + return strerror(errno); + } + } + free(buf); + if (SHEET(sheet,x,y,z)->value.type==STRING && fputc_close('"',fp)==EOF) return strerror(errno); + } + ++*count; + } + if (fputc_close('\n',fp)==EOF) return strerror(errno); + } + if (zdimz-1; z>=0; --z) + { + for (y=sheet->dimy-1; y>=0; --y) + { + for (x=sheet->dimx-1; x>=0; --x) + { + if (y==0) if (columnwidth(sheet,x,z)!=DEF_COLUMNWIDTH) fprintf(fp,"W%d %d %d\n",x,z,columnwidth(sheet,x,z)); + if (SHEET(sheet,x,y,z)!=(Cell*)0) + { + fprintf(fp,"C%d %d %d ",x,y,z); + if (SHEET(sheet,x,y,z)->adjust!=AUTOADJUST) fprintf(fp,"A%c ","lrc"[SHEET(sheet,x,y,z)->adjust]); + if (SHEET(sheet,x,y,z)->label) fprintf(fp,"L%s ",SHEET(sheet,x,y,z)->label); + if (SHEET(sheet,x,y,z)->precision!=-1) fprintf(fp,"P%d ",SHEET(sheet,x,y,z)->precision); + if (SHEET(sheet,x,y,z)->shadowed) fprintf(fp,"S "); + if (SHEET(sheet,x,y,z)->bold) fprintf(fp,"B "); + if (SHEET(sheet,x,y,z)->underline) fprintf(fp,"U "); + if (SHEET(sheet,x,y,z)->scientific!=DEF_SCIENTIFIC) fprintf(fp,"E "); + if (SHEET(sheet,x,y,z)->locked) fprintf(fp,"C "); + if (SHEET(sheet,x,y,z)->transparent) fprintf(fp,"T "); + if (SHEET(sheet,x,y,z)->contents) + { + char buf[4096]; + + if (fputc_close(':',fp)==EOF) return strerror(errno); + print(buf,sizeof(buf),0,1,SHEET(sheet,x,y,z)->scientific,SHEET(sheet,x,y,z)->precision,SHEET(sheet,x,y,z)->contents); + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + if (SHEET(sheet,x,y,z)->ccontents) + { + char buf[4096]; + + if (fputs_close("\\\n",fp)==EOF) return strerror(errno); + print(buf,sizeof(buf),0,1,SHEET(sheet,x,y,z)->scientific,SHEET(sheet,x,y,z)->precision,SHEET(sheet,x,y,z)->ccontents); + if (fputs_close(buf,fp)==EOF) return strerror(errno); + } + if (fputc_close('\n',fp)==EOF) return strerror(errno); + ++*count; + } + } + } + } + if (fclose(fp)==EOF) return strerror(errno); + return (const char*)0; +} +/*}}}*/ +/* loadport -- load from portable text */ /*{{{*/ +const char *loadport(Sheet *sheet, const char *name) +{ + /* variables */ /*{{{*/ + static char errbuf[80]; + FILE *fp; + int x,y,z; + char buf[4096]; + int line; + const char *ns,*os; + const char *err; + int precision; + char *label; + Adjust adjust; + int shadowed; + int bold; + int underline; + int scientific; + int locked; + int transparent; + int ignored; + Token **contents,**ccontents; + int width; + /*}}}*/ + + if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno); + freesheet(sheet,0); + err=(const char*)0; + line=1; + while (fgets(buf,sizeof(buf),fp)!=(char*)0) + { + /* remove nl */ /*{{{*/ + width=strlen(buf); + if (width>0 && buf[width-1]=='\n') buf[--width]='\0'; + /*}}}*/ + switch (buf[0]) + { + /* C -- parse cell */ /*{{{*/ + case 'C': + { + int cc=0; + + if (width>0 && buf[width-1]=='\\') { buf[--width]='\0'; cc=1; } + adjust=AUTOADJUST; + precision=-1; + label=(char*)0; + contents=(Token**)0; + ccontents=(Token**)0; + shadowed=0; + bold=0; + underline=0; + scientific=DEF_SCIENTIFIC; + locked=0; + transparent=0; + ignored=0; + /* parse x y and z */ /*{{{*/ + os=ns=buf+1; + x=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for x position in line %d"),line); + err=errbuf; + goto eek; + } + while (*ns==' ') ++ns; + os=ns; + y=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for y position in line %d"),line); + err=errbuf; + goto eek; + } + while (*ns==' ') ++ns; + os=ns; + z=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for z position in line %d"),line); + err=errbuf; + goto eek; + } + /*}}}*/ + /* parse optional attributes */ /*{{{*/ + do + { + while (*ns==' ') ++ns; + switch (*ns) + { + /* A -- adjustment */ /*{{{*/ + case 'A': + { + ++ns; + switch (*ns) + { + case 'l': adjust=LEFT; ++ns; break; + case 'r': adjust=RIGHT; ++ns; break; + case 'c': adjust=CENTER; ++ns; break; + default: sprintf(errbuf,_("Parse error for adjustment in line %d"),line); err=errbuf; goto eek; + } + break; + } + /*}}}*/ + /* L -- label */ /*{{{*/ + case 'L': + { + char buf[1024],*p; + + p=buf; + ++ns; + while (*ns && *ns!=' ') { *p=*ns; ++p; ++ns; } + *p='\0'; + label=mystrmalloc(buf); + break; + } + /*}}}*/ + /* P -- precision */ /*{{{*/ + case 'P': + { + os=++ns; + precision=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for precision in line %d"),line); + err=errbuf; + goto eek; + } + break; + } + /*}}}*/ + /* S -- shadowed */ /*{{{*/ + case 'S': + { + if (x==0) + { + sprintf(errbuf,_("Trying to shadow cell (%d,%d,%d) in line %d"),x,y,z,line); + err=errbuf; + goto eek; + } + ++ns; + shadowed=1; + break; + } + /*}}}*/ + /* U -- underline */ /*{{{*/ + case 'U': + { + if (x==0) + { + sprintf(errbuf,_("Trying to underline cell (%d,%d,%d) in line %d"),x,y,z,line); + err=errbuf; + goto eek; + } + ++ns; + underline=1; + break; + } + /*}}}*/ + /* B -- bold */ /*{{{*/ + case 'B': + { + if (x==0) + { + sprintf(errbuf,_("Trying to bold cell (%d,%d,%d) in line %d"),x,y,z,line); + err=errbuf; + goto eek; + } + ++ns; + bold=1; + break; + } + /*}}}*/ + /* E -- scientific */ /*{{{*/ + case 'E': + { + ++ns; + scientific=1; + break; + } + /*}}}*/ + /* O -- locked */ /*{{{*/ + case 'O': + { + ++ns; + locked=1; + break; + } + /*}}}*/ + /* T -- transparent */ /*{{{*/ + case 'T': + { + ++ns; + transparent=1; + break; + } + /*}}}*/ + /* I -- ignored */ /*{{{*/ + case 'I': + { + ++ns; + ignored=1; + break; + } + /*}}}*/ + /* : \0 -- do nothing */ /*{{{*/ + case ':': + case '\0': break; + /*}}}*/ + /* default -- error */ /*{{{*/ + default: sprintf(errbuf,_("Invalid option %c in line %d"),*ns,line); err=errbuf; goto eek; + /*}}}*/ + } + } while (*ns!=':' && *ns!='\0'); + /*}}}*/ + /* convert remaining string into token sequence */ /*{{{*/ + if (*ns) + { + ++ns; + contents=scan(&ns); + if (contents==(Token**)0) + { + tvecfree(contents); + sprintf(errbuf,_("Expression syntax error in line %d"),line); + err=errbuf; + goto eek; + } + } + /*}}}*/ + /* convert remaining string into token sequence */ /*{{{*/ + if (cc && fgets(buf,sizeof(buf),fp)!=(char*)0) + { + ++line; + /* remove nl */ /*{{{*/ + width=strlen(buf); + if (width>0 && buf[width-1]=='\n') buf[width-1]='\0'; + /*}}}*/ + ns=buf; + ccontents=scan(&ns); + if (ccontents==(Token**)0) + { + tvecfree(ccontents); + sprintf(errbuf,_("Expression syntax error in line %d"),line); + err=errbuf; + goto eek; + } + } + /*}}}*/ + initcell(sheet,x,y,z); + SHEET(sheet,x,y,z)->adjust=adjust; + SHEET(sheet,x,y,z)->label=label; + SHEET(sheet,x,y,z)->precision=precision; + SHEET(sheet,x,y,z)->shadowed=shadowed; + SHEET(sheet,x,y,z)->bold=bold; + SHEET(sheet,x,y,z)->underline=underline; + SHEET(sheet,x,y,z)->scientific=scientific; + SHEET(sheet,x,y,z)->locked=locked; + SHEET(sheet,x,y,z)->transparent=transparent; + SHEET(sheet,x,y,z)->ignored=ignored; + SHEET(sheet,x,y,z)->contents=contents; + SHEET(sheet,x,y,z)->ccontents=ccontents; + break; + } + /*}}}*/ + /* W -- column width */ /*{{{*/ + case 'W': + { + /* parse x and z */ /*{{{*/ + os=ns=buf+1; + x=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for x position in line %d"),line); + err=errbuf; + goto eek; + } + while (*ns==' ') ++ns; + os=ns; + z=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for z position in line %d"),line); + err=errbuf; + goto eek; + } + /*}}}*/ + /* parse width */ /*{{{*/ + while (*ns==' ') ++ns; + os=ns; + width=posnumber(os,&ns); + if (os==ns) + { + sprintf(errbuf,_("Parse error for width in line %d"),line); + err=errbuf; + goto eek; + } + /*}}}*/ + setwidth(sheet,x,z,width); + break; + } + /*}}}*/ + /* # -- comment */ /*{{{*/ + case '#': break; + /*}}}*/ + /* default -- error */ /*{{{*/ + default: + { + sprintf(errbuf,_("Unknown tag %c in line %d"),buf[0],line); + err=errbuf; + goto eek; + } + /*}}}*/ + } + ++line; + } + eek: + if (fclose(fp)==EOF && err==(const char*)0) err=strerror(errno); + sheet->changed=0; + cachelabels(sheet); + forceupdate(sheet); + return err; +} +/*}}}*/ +/* loadxdr -- load a spread sheet in XDR */ /*{{{*/ +const char *loadxdr(Sheet *sheet, const char *name) +{ + /* variables */ /*{{{*/ + FILE *fp; + XDR xdrs; + int x,y,z; + int width; + int u; + int olderror; + /*}}}*/ + + if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno); + xdrstdio_create(&xdrs,fp,XDR_DECODE); + if (!xdr_magic(&xdrs)) + { +#if 0 + xdr_destroy(&xdrs); + fclose(fp); + return _("This is not a teapot worksheet in XDR format"); +#else + xdr_destroy(&xdrs); + rewind(fp); + xdrstdio_create(&xdrs,fp,XDR_DECODE); +#endif + } + freesheet(sheet,0); + while (xdr_int(&xdrs,&u)) switch (u) + { + /* 0 -- column width element */ /*{{{*/ + case 0: + { + if (xdr_column(&xdrs,&x,&z,&width)==0) + { + olderror=errno; + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(olderror); + } + setwidth(sheet,x,z,width); + break; + } + /*}}}*/ + /* 1 -- cell element */ /*{{{*/ + case 1: + { + if (xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0) + { + olderror=errno; + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(olderror); + } + initcell(sheet,x,y,z); + if (xdr_cell(&xdrs,SHEET(sheet,x,y,z))==0) + { + freecell(sheet,x,y,z); + olderror=errno; + xdr_destroy(&xdrs); + (void)fclose(fp); + return strerror(olderror); + } + break; + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: + { + xdr_destroy(&xdrs); + fclose(fp); + sheet->changed=0; + cachelabels(sheet); + forceupdate(sheet); + return _("Invalid record, loading aborted"); + } + /*}}}*/ + } + xdr_destroy(&xdrs); + if (fclose(fp)==EOF) return strerror(errno); + sheet->changed=0; + cachelabels(sheet); + forceupdate(sheet); + redraw_sheet(sheet); + return (const char*)0; +} +/*}}}*/ +/* loadcsv -- load/merge CSVs */ /*{{{*/ +const char *loadcsv(Sheet *sheet, const char *name) +{ + /* variables */ /*{{{*/ + FILE *fp; + Token **t; + const char *err; + int line,x; + char ln[4096]; + const char *str; + double value; + long lvalue; + int separator = 0; + /*}}}*/ + + if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno); + err=(const char*)0; + for (x=0,line=1; fgets(ln,sizeof(ln),fp); ++line) + { + const char *s; + const char *cend; + + if (!separator) { /* FIXME: find a better way to autodetect */ + int ccnt = 0, scnt = 0; + char *pos = ln; + while ((pos = strchr(pos, ','))) pos++, ccnt++; + pos = ln; + while ((pos = strchr(pos, ';'))) pos++, scnt++; + if (ccnt || scnt) separator = 1; + csv_setopt(scnt > ccnt); + } + + s=cend=ln; + x=0; + do + { + t=malloc(2*sizeof(Token*)); + t[0]=malloc(sizeof(Token)); + t[1]=(Token*)0; + lvalue=csv_long(s,&cend); + if (s!=cend) /* ok, it is a integer */ /*{{{*/ + { + t[0]->type=INT; + t[0]->u.integer=lvalue; + putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0); + } + /*}}}*/ + else + { + value=csv_double(s,&cend); + if (s!=cend) /* ok, it is a double */ /*{{{*/ + { + t[0]->type=FLOAT; + t[0]->u.flt=value; + putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0); + } + /*}}}*/ + else + { + str=csv_string(s,&cend); + if (s!=cend) /* ok, it is a string */ /*{{{*/ + { + t[0]->type=STRING; + t[0]->u.string=mystrmalloc(str); + putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0); + } + /*}}}*/ + else + { + tvecfree(t); + csv_separator(s,&cend); + while (s==cend && *s && *s!='\n') + { + err=_("unknown values ignored"); + csv_separator(++s,&cend); + } + /* else it is nothing, which does not need to be stored :) */ + } + } + } + } while (s!=cend ? s=cend,++x,1 : 0); + } + fclose(fp); + return err; +} +/*}}}*/ +/* insertcube -- insert a block */ /*{{{*/ +void insertcube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction ins) +{ + /* variables */ /*{{{*/ + int x,y,z; + /*}}}*/ + + switch (ins) + { + /* IN_X */ /*{{{*/ + case IN_X: + { + int right; + + right=sheet->dimx+x2-x1; + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=right; x>x2; --x) + { + resize(sheet,x,y,z); + SHEET(sheet,x,y,z)=SHEET(sheet,x-(x2-x1+1),y,z); + SHEET(sheet,x-(x2-x1+1),y,z)=(Cell*)0; + } + break; + } + /*}}}*/ + /* IN_Y */ /*{{{*/ + case IN_Y: + { + int down; + + down=sheet->dimy+y2-y1; + for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=down; y>y2; --y) + { + resize(sheet,x,y,z); + SHEET(sheet,x,y,z)=SHEET(sheet,x,y-(y2-y1+1),z); + SHEET(sheet,x,y-(y2-y1+1),z)=(Cell*)0; + } + break; + } + /*}}}*/ + /* IN_Z */ /*{{{*/ + case IN_Z: + { + int bottom; + + bottom=sheet->dimz+z2-z1; + for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=bottom; z>z2; --z) + { + resize(sheet,x,y,z); + SHEET(sheet,x,y,z)=SHEET(sheet,x,y,z-(z2-z1+1)); + SHEET(sheet,x,y,z-(z2-z1+1))=(Cell*)0; + } + break; + } + /*}}}*/ + /* default */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + sheet->changed=1; + cachelabels(sheet); + forceupdate(sheet); +} +/*}}}*/ +/* deletecube -- delete a block */ /*{{{*/ +void deletecube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction del) +{ + /* variables */ /*{{{*/ + int x,y,z; + /*}}}*/ + + /* free cells in marked block */ /*{{{*/ + for (x=x1; x<=x2; ++x) + for (y=y1; y<=y2; ++y) + for (z=z1; z<=z2; ++z) + freecell(sheet,x,y,z); + /*}}}*/ + switch (del) + { + /* IN_X */ /*{{{*/ + case IN_X: + { + for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=x1; x<=sheet->dimx-(x2-x1+1); ++x) + { + if (x+(x2-x1+1)dimx && ydimy && zdimz) + { + SHEET(sheet,x,y,z)=SHEET(sheet,x+(x2-x1+1),y,z); + SHEET(sheet,x+(x2-x1+1),y,z)=(Cell*)0; + } + } + break; + } + /*}}}*/ + /* IN_Y */ /*{{{*/ + case IN_Y: + { + for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=y1; y<=sheet->dimy-(y2-y1+1); ++y) + { + if (xdimx && y+(y2-y1+1)dimy && zdimz) + { + SHEET(sheet,x,y,z)=SHEET(sheet,x,y+(y2-y1+1),z); + SHEET(sheet,x,y+(y2-y1+1),z)=(Cell*)0; + } + } + break; + } + /*}}}*/ + /* IN_Z */ /*{{{*/ + case IN_Z: + { + for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=z1; z<=sheet->dimz-(z2-z1+1); ++z) + { + if (xdimx && ydimy && z+(z2-z1+1)dimz) + { + SHEET(sheet,x,y,z)=SHEET(sheet,x,y,z+(z2-z1+1)); + SHEET(sheet,x,y,z+(z2-z1+1))=(Cell*)0; + } + } + break; + } + /*}}}*/ + /* default */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + sheet->changed=1; + cachelabels(sheet); + forceupdate(sheet); +} +/*}}}*/ +/* moveblock -- move a block */ /*{{{*/ +void moveblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int copy) +{ + /* variables */ /*{{{*/ + int dirx, diry, dirz; + int widx, widy, widz; + int x, y, z; + int xf, xt, yf, yt, zf, zt; + /*}}}*/ + + if (x1==x3 && y1==y3 && z1==z3) return; + widx=(x2-x1); + widy=(y2-y1); + widz=(z2-z1); + if (x3>x1) { dirx=-1; xf=widx; xt=-1; } else { dirx=1; xf=0; xt=widx+1; } + if (y3>y1) { diry=-1; yf=widy; yt=-1; } else { diry=1; yf=0; yt=widy+1; } + if (z3>z1) { dirz=-1; zf=widz; zt=-1; } else { dirz=1; zf=0; zt=widz+1; } + for (x=xf; x!=xt; x+=dirx) + for (y=yf; y!=yt; y+=diry) + for (z=zf; z!=zt; z+=dirz) + { + if (copy) + { + copycell(sheet,x1+x,y1+y,z1+z,sheet,x3+x,y3+y,z3+z); + } + else + { + if (x1+xdimx && y1+ydimy && z1+zdimz) + { + resize(sheet,x3+x,y3+y,z3+z); + SHEET(sheet,x3+x,y3+y,z3+z)=SHEET(sheet,x1+x,y1+y,z1+z); + SHEET(sheet,x1+x,y1+y,z1+z)=(Cell*)0; + } + else + { + freecell(sheet,x3+x,y3+y,z3+z); + } + } + } + sheet->changed=1; + cachelabels(sheet); + forceupdate(sheet); +} +/*}}}*/ +/* sortblock -- sort a block */ /*{{{*/ +/* Notes */ /*{{{*/ +/* +The idea is to sort a block of cells in one direction by swapping the +planes which are canonical to the sort key vectors. An example is to +sort a two dimensional block line-wise with one column as sort key. +You can have multiple sort keys which all have the same direction and +you can sort a cube plane-wise. +*/ +/*}}}*/ +const char *sortblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir, Sortkey *sk, size_t sklen) +{ + /* variables */ /*{{{*/ + int x,y,z; + int incx=0,incy=0,incz=0; + int distx,disty,distz; + int i,r=-3,norel,work; + /*}}}*/ + + /* asserts */ /*{{{*/ + assert(sklen>0); + assert(x1>=0); + assert(x2>=0); + assert(y1>=0); + assert(y2>=0); + assert(z1>=0); + assert(z2>=0); + /*}}}*/ + norel=0; + posorder(&x1,&x2); + posorder(&y1,&y2); + posorder(&z1,&z2); + distx=(x2-x1+1); + disty=(y2-y1+1); + distz=(z2-z1+1); + switch (dir) + { + case IN_X: incx=1; --x2; incy=0; incz=0; distx=1; break; + case IN_Y: incx=0; incy=1; --y2; incz=0; disty=1; break; + case IN_Z: incx=0; incy=0; incz=1; --z2; distz=1; break; + default: assert(0); + } + assert(incx || incy || incz); + do + { + work=0; + for (x=x1,y=y1,z=z1; x<=x2&&y<=y2&&z<=z2; x+=incx,y+=incy,z+=incz) + { + for (i=0; ichanged=1; + if (norel) return _("uncomparable elements"); + else return (const char*)0; +} +/*}}}*/ +/* mirrorblock -- mirror a block */ /*{{{*/ +void mirrorblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir) +{ + switch (dir) + { + case IN_X: /* left-right */ /*{{{*/ + { + int x,middle=(x2-x1+1)/2; + for (x=0; xchanged=1; + cachelabels(sheet); + forceupdate(sheet); +} +/*}}}*/ diff --git a/sheet.h b/sheet.h new file mode 100644 index 0000000..1ee5898 --- /dev/null +++ b/sheet.h @@ -0,0 +1,140 @@ +#ifndef SHEET_H +#define SHEET_H + +#include "scanner.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHEET(s,x,y,z) (*(s->sheet+(x)*s->dimz*s->dimy+(y)*s->dimz+(z))) + +typedef enum { LEFT=0, RIGHT=1, CENTER=2, AUTOADJUST=3 } Adjust; + +typedef enum { IN_X, IN_Y, IN_Z } Direction; + +/* must be a prime */ +#define LABEL_CACHE 29 + +#define ASCENDING 001 + +typedef struct +{ + int x; + int y; + int z; + int sortkey; /* OR-ed value of the above constants */ +} Sortkey; + +typedef struct +{ + Token **contents; + Token **ccontents; + char *label; + Token value; + Token resvalue; + Adjust adjust; + int precision; + unsigned int updated:1; + unsigned int shadowed:1; + unsigned int scientific:1; + unsigned int locked:1; + unsigned int transparent:1; + unsigned int ignored:1; + unsigned int clock_t0:1; + unsigned int clock_t1:1; + unsigned int clock_t2:1; + unsigned int bold:1; + unsigned int underline:1; +} Cell; + +struct Label +{ + const char *label; + int x,y,z; + struct Label *next; +}; + +typedef struct +{ + struct Label *labelcache[LABEL_CACHE]; + int curx, cury, curz; + int mark1x, mark1y, mark1z; + int mark2x, mark2y, mark2z; + int marking; + int offx, offy; + Cell **sheet; + int *column; + int dimx, dimy, dimz; + int orix, oriy, maxx, maxy; + int width; + char *name; + unsigned int changed:1; + unsigned int moveonly:1; + unsigned int clk:1; + void *display; +} Sheet; + +extern Sheet *upd_sheet; +extern int upd_x; +extern int upd_y; +extern int upd_z; +extern int max_eval; + +void resize(Sheet *sheet, int x, int y, int z); +void initcell(Sheet *sheet, int x, int y, int z); +void cachelabels(Sheet *sheet); +void freesheet(Sheet *sheet, int all); +void forceupdate(Sheet *sheet); +void freecell(Sheet *sheet, int x, int y, int z); +int columnwidth(Sheet *sheet, int x, int z); +void setwidth(Sheet *sheet, int x, int z, int width); +int cellwidth(Sheet *sheet, int x, int y, int z); +void putcont(Sheet *sheet, int x, int y, int z, Token **t, int c); +Token **getcont(Sheet *sheet, int x, int y, int z, int c); +Token getvalue(Sheet *sheet, int x, int y, int z); +void update(Sheet *sheet); +char *geterror(Sheet *sheet, int x, int y, int z); +void printvalue(char *s, size_t size, size_t chars, int quote, int scientific, int precision, Sheet *sheet, int x, int y, int z); +Adjust getadjust(Sheet *sheet, int x, int y, int z); +void setadjust(Sheet *sheet, int x, int y, int z, Adjust adjust); +void shadow(Sheet *sheet, int x, int y, int z, int yep); +int shadowed(Sheet *sheet, int x, int y, int z); +void bold(Sheet *sheet, int x, int y, int z, int yep); +int isbold(Sheet *sheet, int x, int y, int z); +void underline(Sheet *sheet, int x, int y, int z, int yep); +int underlined(Sheet *sheet, int x, int y, int z); +void lockcell(Sheet *sheet, int x, int y, int z, int yep); +int locked(Sheet *sheet, int x, int y, int z); +int transparent(Sheet *sheet, int x, int y, int z); +void maketrans(Sheet *sheet, int x, int y, int z, int yep); +void igncell(Sheet *sheet, int x, int y, int z, int yep); +int ignored(Sheet *sheet, int x, int y, int z); +void clk(Sheet *sheet, int x, int y, int z); +void setscientific(Sheet *sheet, int x, int y, int z, int yep); +int getscientific(Sheet *sheet, int x, int y, int z); +void setprecision(Sheet *sheet, int x, int y, int z, int precision); +int getprecision(Sheet *sheet, int x, int y, int z); +const char *getlabel(Sheet *sheet, int x, int y, int z); +void setlabel(Sheet *sheet, int x, int y, int z, const char *buf, int update); +Token findlabel(Sheet *sheet, const char *label); +void relabel(Sheet *sheet, const char *oldlabel, const char *newlabel, int x, int y, int z); +const char *savexdr(Sheet *sheet, const char *name, unsigned int *count); +const char *savetbl(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count); +const char *savetext(Sheet *sheet, const char *name, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count); +const char *savecsv(Sheet *sheet, const char *name, char sep, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count); +const char *saveport(Sheet *sheet, const char *name, unsigned int *count); +const char *loadxdr(Sheet *sheet, const char *name); +const char *loadport(Sheet *sheet, const char *name); +const char *loadcsv(Sheet *sheet, const char *name); +void insertcube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction ins); +void deletecube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction ins); +void moveblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int copy); +const char *sortblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir, Sortkey *sk, size_t sklen); +void mirrorblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/teapot.1 b/teapot.1 new file mode 100644 index 0000000..3a0dbc7 --- /dev/null +++ b/teapot.1 @@ -0,0 +1,84 @@ +.\" teapot.1: teapot manual +.\" Copyright(c) 2010 by wave++ "Yuri D'Elia" +.\" Distributed under GNU GPL WITHOUT ANY WARRANTY. +.\" +.Dd Dicember 8, 2010 +.Dt TEAPOT 1 +.\" +.\" +.Sh NAME +.Nm teapot +.Nd Table Editor And Planner, Or: Teapot! +.\" +.\" +.Sh SYNOPSIS +.Nm +.Op Fl abHqr +.Op Fl p Ar digits +.Op Ar file +.\" +.\" +.Sh DESCRIPTION +.Nm +(Table Editor And Planner, Or Teapot!) is a compact spreadsheet program, featuring three +dimensions with linear addressing, relative references, formula references, +type safety of computations, iterative expressions and platform independent +file format. +.Pp +This document only describes command-line arguments. Please refer to the +complete manual that can be found under +.Pa /usr/share/doc/teapot +in both html and pdf format. +The manual can also be viewed from within the graphical version, fteapot, +using the Help menu. +.\" +.\" +.Sh OPTIONS +.Bl -tag -compact -width " \-p digits " +.It Fl a +Use ASCII instead of XDR as default file format when loading or saving. +.It Fl b +Read batch commands from standard input. +.It Fl H +Hide row and column headers. +.It Fl q +Display strings quoted. +.It Fl r +Redraw the terminal window more often. +.It Fl p Ar digits +Set default precision of displayed numbers. +.El +.\" +.\" +.Sh ENVIRONMENT +.Ev DISPLAY See Xr X 7 . +.\" +.\" +.Sh SEE ALSO +.Pa /usr/share/doc/teapot/ +.\" +.\" +.Sh LICENSE +Copyright (C) 1995-2006 +.An "Michael Haardt" +.Pp +Copyright (C) 2009-2012 +.Mt info@syntax-k.de +J\(:org Walter +.Me +.Pp +Manual page written by +.Mt wavexx@users.sf.net +Yuri D'Elia +.Me +.Pp +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 3 of the License, or (at your option) any later version. +.Pp +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. +.Pp +You should have received a copy of the GNU General Public License along with this program. +If not, see +.Ur http://www.gnu.org/licenses/ +http://www.gnu.org/licenses/ +.Ue . diff --git a/utf8.c b/utf8.c new file mode 100644 index 0000000..402af49 --- /dev/null +++ b/utf8.c @@ -0,0 +1,70 @@ +/* + UTF-8 wrapper for teapot + Copyright (C) 2010 Joerg Walter + + 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 3 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, see . +*/ + +#include "utf8.h" + +#include + +#ifdef ENABLE_UTF8 + +size_t mbslen(const char *s) +{ + const unsigned char *str = (unsigned char *)s; + size_t chars = 0; + + while (*str) { + chars++; + if (is_mbchar(*str++)) while (is_mbcont(*str)) str++; + } + + return chars; +} + +char *mbspos(const char *s, int ch) +{ + const unsigned char *str = (unsigned char *)s; + int chars = 0; + + if (ch < 0) { + while (chars > ch) { + chars--; + while (is_mbcont(*--str)); + } + } else { + while (chars < ch && *str) { + chars++; + if (is_mbchar(*str++)) while (is_mbcont(*str)) str++; + } + } + + return (char *)(void *)str; +} + +#else + +size_t mbslen(const char *str) +{ + return strlen(str); +} + +char *mbspos(const char *str, int ch) +{ + return (char *)(void *)(str+ch); +} + +#endif diff --git a/utf8.h b/utf8.h new file mode 100644 index 0000000..23683c6 --- /dev/null +++ b/utf8.h @@ -0,0 +1,64 @@ +/* + UTF-8 wrapper for teapot + Copyright (C) 2010 Joerg Walter + + 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 3 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, see . +*/ + +#ifndef UTF8_H +#define UTF8_H + +#include +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +size_t mbslen(const char *str); +char *mbspos(const char *str, int ch); + +#ifdef ENABLE_UTF8 + +#define UTF8SZ 4 +static inline int is_mbcont(unsigned char c) +{ + return (c >= 0x80 && c < 0xc0); +} + +static inline int is_mbchar(unsigned char c) +{ + return (c >= 0x80); +} + +#else + +#define UTF8SZ 1 +static inline int is_mbcont(unsigned char c) +{ + return 0; +} + +static inline int is_mbchar(unsigned char c) +{ + return 0; +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wk1.c b/wk1.c new file mode 100644 index 0000000..72574cf --- /dev/null +++ b/wk1.c @@ -0,0 +1,1163 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#ifndef NO_POSIX_SOURCE +#undef _POSIX_SOURCE +#define _POSIX_SOURCE 1 +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 +#endif + +#include +#include +#include +#include +#include +#include +#include +#ifdef OLD_REALLOC +#define realloc(s,l) myrealloc(s,l) +#endif + +#ifdef DMALLOC +#include "dmalloc.h" +#endif + + +#include "eval.h" +#include "main.h" +#include "misc.h" +#include "sheet.h" +#include "wk1.h" +/*}}}*/ +/* #defines */ /*{{{*/ +#define WK1DEBUG 0 +#define LOTUS123 0x0404 +#define SYMPHONY 0x0405 +/* Applix uses that, too */ +#define EXCEL 0x0406 +/*}}}*/ + +static int unrpn(const char *s, const int *offset, int top, Token **t, int *tokens); + +#if WK1DEBUG +static FILE *se; +#endif + +/* it -- convert string to int */ /*{{{*/ +static int it(const char *s) +{ + return (((unsigned int)((const unsigned char)*s))|(*(s+1)<<8)); +} +/*}}}*/ +/* dbl -- convert string to double */ /*{{{*/ +static double dbl(const unsigned char *s) +{ + double x; + int sg,e,i; + + x=0.0; + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[0]&i); + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[1]&i); + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[2]&i); + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[3]&i); + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[4]&i); + for (i=1; i<256; i<<=1) x=x/2.0+!!(s[5]&i); + x=x/2.0+!!(s[6]&0x01); + x=x/2.0+!!(s[6]&0x02); + x=x/2.0+!!(s[6]&0x04); + x=x/2.0+!!(s[6]&0x08); + x=x/2.0+1.0; + if ((e=((s[6]>>4)+((s[7]&0x7f)<<4))-1023)==-1023) + { + x=0.0; + e=0; + } + if (s[7]&0x80) sg=-1; else sg=1; +#if WK1DEBUG + fprintf(se,"%02x %02x %02x %02x %02x %02x %02x %02x ",s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7]); + fprintf(se,"%f (exp 2^%d)\r\n",sg*ldexp(x,e),e); +#endif + return (sg*ldexp(x,e)); +} +/*}}}*/ +/* format -- convert string into format */ /*{{{*/ +static void format(unsigned char s, Cell *cell) +{ +#if WK1DEBUG + fprintf(se,", format 0x%02x",s); + if (s&0x80) fprintf(se,", locked"); +#endif + switch (((unsigned int)(s&0x70))>>4) + { + case 0: /* fixed with given precision */ /*{{{*/ + { + cell->precision=s&0x0f; + cell->scientific=0; + break; + } + /*}}}*/ + case 1: /* scientifix with given presision */ /*{{{*/ + { + cell->precision=s&0x0f; + cell->scientific=1; + break; + } + /*}}}*/ + case 2: /* currency with given precision */ /*{{{*/ + { + cell->precision=s&0x0f; + break; + } + /*}}}*/ + case 3: /* percent with given precision */ /*{{{*/ + { + cell->precision=s&0x0f; + break; + } + /*}}}*/ + case 4: /* comma with given precision */ /*{{{*/ + { + cell->precision=s&0x0f; + break; + } + /*}}}*/ + case 5: /* unused */ break; + case 6: /* unused */ break; + case 7: + { + switch (s&0x0f) + { + case 0: /* +/- */; break; + case 1: /* general */; break; + case 2: /* day-month-year */; break; + case 3: /* day-month */; break; + case 4: /* month-year */; break; + case 5: /* text */; break; + case 6: /* hidden */; break; + case 7: /* date;hour-min-sec */; break; + case 8: /* date;hour-min */; break; + case 9: /* date;intnt'l1 */; break; + case 10: /* date;intnt'l2 */; break; + case 11: /* time;intnt'l1 */; break; + case 12: /* time;intnt'l2 */; break; + case 13: /* unused13 */; break; + case 14: /* unused14 */; break; + case 15: /* default special format */; break; + } + break; + } + } +} +/*}}}*/ +/* pair -- convert coordinate pair */ /*{{{*/ +static int pair(const char *s, Token **t, int *tokens) +{ + int x,y; + + x=it(s); y=it(s+2); + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=OP; +#if WK1DEBUG + fprintf(se,"[("); +#endif + } + if (tokens) ++(*tokens); + switch (x&0xc000) + { + case 0x0000: /* MSB -> 0 0 */ /*{{{*/ + { + x=x&0x2000 ? x|0xc000 : x&0x3fff; + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=INT; + t[*tokens]->u.integer=x; +#if WK1DEBUG + fprintf(se,"%d",x); +#endif + } + if (tokens) ++(*tokens); + break; + } + /*}}}*/ + case 0x4000: assert(0); break; + case 0x8000: /* MSB -> 1 0 */ /*{{{*/ + { + x=x&0x2000 ? x|0xc000 : x&0x3fff; + if (x!=0) + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+2]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FIDENT; + t[*tokens]->u.fident=identcode("x",1); + t[*tokens+1]->type=OPERATOR; + t[*tokens+1]->u.op=OP; + t[*tokens+2]->type=OPERATOR; + t[*tokens+2]->u.op=CP; +#if WK1DEBUG + fprintf(se,"x()"); +#endif + } + if (tokens) *tokens+=3; + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens+1]->type=INT; + if (x<0) + { + t[*tokens]->u.op=MINUS; + t[*tokens+1]->u.integer=-x; +#if WK1DEBUG + fprintf(se,"-%d",-x); +#endif + } + else + { + t[*tokens]->u.op=PLUS; + t[*tokens+1]->u.integer=x; +#if WK1DEBUG + fprintf(se,"+%d",x); +#endif + } + } + if (tokens) *tokens+=2; + } + break; + } + /*}}}*/ + case 0xc000: assert(0); break; + } + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=COMMA; +#if WK1DEBUG + fprintf(se,","); +#endif + } + if (tokens) ++(*tokens); + switch (y&0xc000) + { + case 0x0000: /* MSB -> 0 0 */ /*{{{*/ + { + y=y&0x2000 ? y|0xc000 : y&0x3fff; + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=INT; + t[*tokens]->u.integer=y; +#if WK1DEBUG + fprintf(se,"%d",y); +#endif + } + if (tokens) ++(*tokens); + break; + } + /*}}}*/ + case 0x4000: assert(0); break; + case 0x8000: /* MSB -> 1 0 */ /*{{{*/ + { + y=y&0x2000 ? y|0xc000 : y&0x3fff; + if (y) + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+2]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FIDENT; + t[*tokens]->u.fident=identcode("y",1); + t[*tokens+1]->type=OPERATOR; + t[*tokens+1]->u.op=OP; + t[*tokens+2]->type=OPERATOR; + t[*tokens+2]->u.op=CP; +#if WK1DEBUG + fprintf(se,"y()"); +#endif + } + if (tokens) *tokens+=3; + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens+1]->type=INT; + if (y<0) + { + if (t) + { + t[*tokens]->u.op=MINUS; + t[*tokens+1]->u.integer=-y; +#if WK1DEBUG + fprintf(se,"-%d",-y); +#endif + } + } + else + { + if (t) + { + t[*tokens]->u.op=PLUS; + t[*tokens+1]->u.integer=y; +#if WK1DEBUG + fprintf(se,"+%d",y); +#endif + } + } + } + if (tokens) *tokens+=2; + } + break; + } + /*}}}*/ + case 0xc000: assert(0); break; + } + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=CP; +#if WK1DEBUG + fprintf(se,")]"); +#endif + } + if (tokens) ++(*tokens); + return 0; +} +/*}}}*/ +/* sumup -- sum up arguments */ /*{{{*/ +static int sumup(const char *s, const int *offset, int top, Token **t, int *tokens, int argc) +{ + int low; + + if (top<0) return -1; + if (argc>1) + { + low=unrpn(s,offset,top,(Token**)0,(int*)0); + if (low<0) return -1; + sumup(s,offset,low-1,t,tokens,argc-1); + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=PLUS; +#if WK1DEBUG + fprintf(se,"[+]"); +#endif + } + if (tokens) ++(*tokens); + } + if (s[offset[top]]==2) + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FIDENT; + t[*tokens]->u.fident=identcode("sum",3); + t[*tokens+1]->type=OPERATOR; + t[*tokens+1]->u.op=OP; +#if WK1DEBUG + fprintf(se,"[sum(]"); +#endif + } + if (tokens) *tokens+=2; + } + low=unrpn(s,offset,top,t,tokens); + if (s[offset[top]]==2) + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=CP; +#if WK1DEBUG + fprintf(se,"[)]"); +#endif + } + if (tokens) ++(*tokens); + } + return low; +} +/*}}}*/ +/* unrpn -- convert RPN expression to infix */ /*{{{*/ +static int unrpn(const char *s, const int *offset, int top, Token **t, int *tokens) +{ + int low; + + if (top<0) return -1; + switch (s[offset[top]]) + { + case 0: /* double constant */ /*{{{*/ + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FLOAT; + t[*tokens]->u.flt=dbl((const unsigned char*)s+offset[top]+1); +#if WK1DEBUG + fprintf(se,"[constant %f]",dbl((const unsigned char*)s+offset[top]+1)); +#endif + } + if (tokens) ++(*tokens); + low=top; + break; + } + /*}}}*/ + case 1: /* variable */ /*{{{*/ + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FIDENT; + t[*tokens]->u.fident=identcode("@",1); + } + if (tokens) ++(*tokens); + if (pair(s+offset[top]+1,t,tokens)==-1) low=-1; else low=top; + break; + } + /*}}}*/ + case 2: /* range */ /*{{{*/ + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=FIDENT; + t[*tokens]->u.fident=identcode("&",1); +#if WK1DEBUG + fprintf(se,"[&]"); +#endif + } + if (tokens) ++(*tokens); + pair(s+offset[top]+1,t,tokens); + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0 || (t[*tokens+1]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.fident=COMMA; + t[*tokens+1]->type=FIDENT; + t[*tokens+1]->u.op=identcode("&",1); +#if WK1DEBUG + fprintf(se,"[,&]"); +#endif + } + if (tokens) *tokens+=2; + pair(s+offset[top]+5,t,tokens); + low=top; + break; + } + /*}}}*/ + case 3: /* return */ /*{{{*/ + { + low=unrpn(s,offset,top-1,t,tokens); + if (t) + { + t[*tokens]=(Token*)0; +#if WK1DEBUG + fprintf(se,"[RETURN]"); +#endif + } + if (tokens) ++(*tokens); + break; + } + /*}}}*/ + case 4: /* paren */ /*{{{*/ + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=OP; +#if WK1DEBUG + fprintf(se,"[(]"); +#endif + } + if (tokens) ++(*tokens); + low=unrpn(s,offset,top-1,t,tokens); + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=CP; +#if WK1DEBUG + fprintf(se,"[)]"); +#endif + } + if (tokens) ++(*tokens); + break; + } + /*}}}*/ + case 5: /* int constant */ /*{{{*/ + { + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=INT; + t[*tokens]->u.integer=it(s+offset[top]+1); +#if WK1DEBUG + fprintf(se,"[constant %d]",it(s+offset[top]+1)); +#endif + } + if (tokens) ++(*tokens); + low=top; + break; + } + /*}}}*/ + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: /* +, -, *, /, ^, -, !=, <=, >=, <, > */ /*{{{*/ + { + if (t) + { + low=unrpn(s,offset,top-1,(Token**)0,(int*)0); + low=unrpn(s,offset,low-1,t,tokens); + if (low<0) return -1; + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + switch (s[offset[top]]) + { + case 9: /* + */ /*{{{*/ + { + t[*tokens]->u.op=PLUS; +#if WK1DEBUG + fprintf(se,"[+]"); +#endif + break; + } + /*}}}*/ + case 10: /* - */ /*{{{*/ + { + t[*tokens]->u.op=MINUS; +#if WK1DEBUG + fprintf(se,"[-]"); +#endif + break; + } + /*}}}*/ + case 11: /* * */ /*{{{*/ + { + t[*tokens]->u.op=MUL; +#if WK1DEBUG + fprintf(se,"[*]"); +#endif + break; + } + /*}}}*/ + case 12: /* / */ /*{{{*/ + { + t[*tokens]->u.op=DIV; +#if WK1DEBUG + fprintf(se,"[/]"); +#endif + break; + } + /*}}}*/ + case 13: /* ^ */ /*{{{*/ + { + t[*tokens]->u.op=POW; +#if WK1DEBUG + fprintf(se,"[^]"); +#endif + break; + } + /*}}}*/ + case 14: /* == */ /*{{{*/ + { + t[*tokens]->u.op=ISEQUAL; +#if WK1DEBUG + fprintf(se,"[==]"); +#endif + break; + } + /*}}}*/ + case 15: /* != */ /*{{{*/ + { + t[*tokens]->u.op=NE; +#if WK1DEBUG + fprintf(se,"[!=]"); +#endif + break; + } + /*}}}*/ + case 16: /* <= */ /*{{{*/ + { + t[*tokens]->u.op=LE; +#if WK1DEBUG + fprintf(se,"[<=]"); +#endif + break; + } + /*}}}*/ + case 17: /* >= */ /*{{{*/ + { + t[*tokens]->u.op=GE; +#if WK1DEBUG + fprintf(se,"[>=]"); +#endif + break; + } + /*}}}*/ + case 18: /* < */ /*{{{*/ + { + t[*tokens]->u.op=LT; +#if WK1DEBUG + fprintf(se,"[<]"); +#endif + break; + } + /*}}}*/ + case 19: /* > */ /*{{{*/ + { + t[*tokens]->u.op=GT; +#if WK1DEBUG + fprintf(se,"[>]"); +#endif + break; + } + /*}}}*/ + default: assert(0); + } + if (tokens) ++(*tokens); + unrpn(s,offset,top-1,t,tokens); + } + else + { + low=unrpn(s,offset,top-1,(Token**)0,tokens); + if (tokens) ++(*tokens); + low=unrpn(s,offset,low-1,(Token**)0,tokens); + } + break; + } + /*}}}*/ + case 23: /* unary + */ /*{{{*/ + { + low=unrpn(s,offset,top-1,t,tokens); + break; + } + /*}}}*/ + case 80: /* sum */ /*{{{*/ + { + int argc; + + argc=s[offset[top]+1]; +#if WK1DEBUG + if (t) fprintf(se,"[sum argc=%d]",argc); +#endif + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=OP; +#if WK1DEBUG + fprintf(se,"[(]"); +#endif + } + if (tokens) ++(*tokens); + low=sumup(s,offset,top-1,t,tokens,argc); + if (t) + { + if ((t[*tokens]=malloc(sizeof(Token)))==(Token*)0) return -1; + t[*tokens]->type=OPERATOR; + t[*tokens]->u.op=CP; +#if WK1DEBUG + fprintf(se,"[)]"); +#endif + } + if (tokens) ++(*tokens); + break; + } + /*}}}*/ + default: assert(0); low=-1; + } + return (low<0 ? -1 : low); +} +/*}}}*/ + +/* loadwk1 -- load WK1 file */ /*{{{*/ +const char *loadwk1(Sheet *sheet, const char *name) +{ + /* variables */ /*{{{*/ + FILE *fp; + const char *err; + int head[4]; + char *body=(char*)0,*newbody; + size_t bodymaxlen=0; + size_t bodylen; + int found_bof=0,found_eof=0; + /*}}}*/ + +#if WK1DEBUG + se=fopen("/dev/tty","w"); assert(se!=(FILE*)0); fprintf(se,"\r\n"); +#endif + if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno); + err=(const char*)0; + while (1) + { + /* read header */ /*{{{*/ + if ((head[0]=getc(fp))==EOF) break; + if ((head[1]=getc(fp))==EOF) { err=_("The record header appears to be truncated"); goto ouch; } + if ((head[2]=getc(fp))==EOF) { err=_("The record header appears to be truncated"); goto ouch; } + if ((head[3]=getc(fp))==EOF) { err=_("The record header appears to be truncated"); goto ouch; } + bodylen=head[2]|(head[3]<<8); + /*}}}*/ + /* read body */ /*{{{*/ + if (bodylen>bodymaxlen) + { + newbody=realloc(body,bodymaxlen=bodylen); + if (newbody==(char*)0) { err=_("Out of memory"); goto ouch; } + else body=newbody; + } + if (bodylen) if (fread(body,bodylen,1,fp)!=1) { err=_("The record body appears to be truncated"); goto ouch; } + /*}}}*/ + /* process record */ /*{{{*/ +#if WK1DEBUG + fprintf(se,"bodylen %d, type %04x\r\n",bodylen,head[0]|(head[1]<<8)); +#endif + switch (head[0]|(head[1]<<8)) + { + /* BOF -- Beginning of file */ /*{{{*/ + case 0x0: + { + if (bodylen!=2) { err=_("Invalid record body length"); goto ouch; } + if (!found_bof) + { + freesheet(sheet,0); + found_bof=it(body); + } + break; + } + /*}}}*/ + /* EOF -- End of file */ /*{{{*/ + case 0x1: + { + if (bodylen!=0) { err=_("Invalid record body length"); goto ouch; } + found_eof=1; + break; + } + /*}}}*/ + /* CALCMODE -- Calculation mode */ /*{{{*/ + case 0x2: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned char)body[0] means: */ + /* 0 -- manual */ + /* 0xff -- automatic */ + break; + } + /*}}}*/ + /* CALCORDER -- Calculation order */ /*{{{*/ + case 0x3: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned char)body[0] means: */ + /* 0 -- natural */ + /* 1 -- by column */ + /* 0xff -- by row */ + break; + } + /*}}}*/ + /* SPLIT -- Split window type */ /*{{{*/ + case 0x4: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned)body[0] means: */ + /* 0: not split */ + /* 1: vertical split */ + /* 0xff: horizontal split */ + break; + } + /*}}}*/ + /* SYNC -- Split window sync */ /*{{{*/ + case 0x5: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned)body[0] means: */ + /* 0: not synchronized */ + /* 0xff: synchronized */ + break; + } + /*}}}*/ + /* RANGE -- Active worksheet range */ /*{{{*/ + case 0x6: + { + if (bodylen!=8) { err=_("Invalid record body length"); goto ouch; } + resize(sheet,it(body+4),it(body+6),0); + /* range is from &(it(body),it(body+2)) to &(it(body+4),it(body+6)) */ + break; + } + /*}}}*/ + /* WINDOW1 -- Window 1 record */ /*{{{*/ + case 0x7: + { + /* 31 is the specification, but Applix generates 32 while claiming to be Excel */ + if (bodylen!=31 && (found_bof!=EXCEL && bodylen!=32)) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* COLW1 -- Column width, window 1 */ /*{{{*/ + case 0x8: + { + if (bodylen!=3) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* WINTWO -- Window 2 record */ /*{{{*/ + case 0x9: + { + if (bodylen!=31) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* COLW2 -- Column width, window 2 */ /*{{{*/ + case 0xA: + { + if (bodylen!=3) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* _("nN)ame") -- Named range */ /*{{{*/ + case 0xB: + { + if (bodylen!=24) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* BLANK -- Blank cell */ /*{{{*/ + case 0xC: + { + if (bodylen!=5) { err=_("Invalid record body length"); goto ouch; } + initcell(sheet,it(body+1),it(body+3),0); + format((unsigned char)body[0],SHEET(sheet,it(body+1),it(body+3),0)); + break; + } + /*}}}*/ + /* INTEGER -- Integer number cell */ /*{{{*/ + case 0xD: + { + Token **t; + + assert(bodylen==7); + initcell(sheet,it(body+1),it(body+3),0); + t=malloc(2*sizeof(Token*)); + t[0]=malloc(sizeof(Token)); + t[1]=(Token*)0; + t[0]->type=INT; + t[0]->u.integer=it(body+5); + putcont(sheet,it(body+1),it(body+3),0,t,0); + format((unsigned char)body[0],SHEET(sheet,it(body+1),it(body+3),0)); + break; + } + /*}}}*/ + /* NUMBER -- Floating point number */ /*{{{*/ + case 0xE: + { + Token **t; + + assert(bodylen==13); + initcell(sheet,it(body+1),it(body+3),0); + t=malloc(2*sizeof(Token*)); + t[0]=malloc(sizeof(Token)); + t[1]=(Token*)0; + t[0]->type=FLOAT; + t[0]->u.flt=dbl((unsigned char*)body+5); + putcont(sheet,it(body+1),it(body+3),0,t,0); + format((unsigned char)body[0],SHEET(sheet,it(body+1),it(body+3),0)); + break; + } + /*}}}*/ + /* _("lL)abel") -- Label cell */ /*{{{*/ + case 0xF: + { + Token **t; + + assert(bodylen>=6 && bodylen<=245); + initcell(sheet,it(body+1),it(body+3),0); + t=malloc(2*sizeof(Token*)); + t[0]=malloc(sizeof(Token)); + t[1]=(Token*)0; + t[0]->type=STRING; + t[0]->u.string=mystrmalloc(body+6); + putcont(sheet,it(body+1),it(body+3),0,t,0); + format((unsigned char)body[0],SHEET(sheet,it(body+1),it(body+3),0)); + break; + } + /*}}}*/ + /* FORMULA -- Formula cell */ /*{{{*/ + case 0x10: + { + int i,j,size; + int *offset; + int tokens; + Token **t; + + assert(bodylen>15); + if ((offset=malloc(it(body+13)*sizeof(int)))==0) { err=_("Out of memory"); goto ouch; } +#if WK1DEBUG + fprintf(se,"FORMULA: &(%d,%d)=",it(body+1),it(body+3)); +#endif + for (i=15,size=it(body+13)+15,j=0; ivalue.type=FLOAT; + SHEET(sheet,it(body+1),it(body+3),0)->value.u.flt=dbl((unsigned char*)body+5); + putcont(sheet,it(body+1),it(body+3),0,t,0); + } + break; + } + /*}}}*/ + /* TABLE -- Data table range */ /*{{{*/ + case 0x18: assert(bodylen==25); break; + /*}}}*/ + /* ORANGE/QRANGE -- Query range */ /*{{{*/ + case 0x19: assert(bodylen==25); break; + /*}}}*/ + /* PRANGE -- Print range */ /*{{{*/ + case 0x1A: assert(bodylen==8); break; + /*}}}*/ + /* SRANGE -- Sort range */ /*{{{*/ + case 0x1B: assert(bodylen==8); break; + /*}}}*/ + /* FRANGE -- Fill range */ /*{{{*/ + case 0x1C: assert(bodylen==8); break; + /*}}}*/ + /* KRANGE1 -- Primary sort key range */ /*{{{*/ + case 0x1D: assert(bodylen==9); break; + /*}}}*/ + /* HRANGE -- Distribution range */ /*{{{*/ + case 0x20: assert(bodylen==16); break; + /*}}}*/ + /* KRANGE2 -- Secondary sort key range */ /*{{{*/ + case 0x23: assert(bodylen==9); break; + /*}}}*/ + /* PROTEC -- Global protection */ /*{{{*/ + case 0x24: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned)body[0] means: */ + /* 0: off */ + /* 0xff: on */ + break; + } + /*}}}*/ + /* FOOTER -- Print footer */ /*{{{*/ + case 0x25: + { + if (body[bodylen-1]!='\0' || bodylen<1 || bodylen>243) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* HEADER -- Print header */ /*{{{*/ + case 0x26: + { + if (body[bodylen-1]!='\0' || bodylen<1 || bodylen>243) { err=_("Invalid record body length"); goto ouch; } + break; + } + /*}}}*/ + /* SETUP -- Print setup */ /*{{{*/ + case 0x27: assert(bodylen==40); break; + /*}}}*/ + /* MARGINS -- Print margins code */ /*{{{*/ + case 0x28: assert(bodylen==10); break; + /*}}}*/ + /* LABELFMT -- Label alignment */ /*{{{*/ + case 0x29: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned char)body[0] means: */ + /* 0x22: right aligned labels */ + /* 0x27: left aligned labels */ + /* 0x5e: centered labels */ + break; + } + /*}}}*/ + /* TITLES -- Print borders */ /*{{{*/ + case 0x2A: assert(bodylen==16); break; + /*}}}*/ + /* GRAPH -- Current graph settings */ /*{{{*/ + case 0x2D: + { + /* The specification says bodylen is 437, Excel 5 says it are */ + /* 443 bytes. We better silently ignore this. */ + break; + } + /*}}}*/ + /* NGRAPH -- Named graph settings */ /*{{{*/ + case 0x2E: assert(bodylen==453); break; + /*}}}*/ + /* CALCCOUNT -- Iteration count */ /*{{{*/ + case 0x2F: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* Do up to %d Iterations */ + break; + } + /*}}}*/ + /* UNFORMATTED -- Formatted/unformatted print */ /*{{{*/ + case 0x30: assert(bodylen==1); break; + /*}}}*/ + /* CURSORW12 -- Cursor location */ /*{{{*/ + case 0x31: + { + if (bodylen!=1) { err=_("Invalid record body length"); goto ouch; } + /* (unsigned)body[0] means cursor in window: */ + /* 1: 1 */ + /* 2: 2 */ + break; + } + /*}}}*/ + /* WINDOW -- Symphony window settings */ /*{{{*/ + case 0x32: assert(bodylen==144); break; + /*}}}*/ + /* STRING -- Value of string formula */ /*{{{*/ + case 0x33: + { + Token **t; + + assert(bodylen>=6 && bodylen<=245); + initcell(sheet,it(body+1),it(body+3),0); + t=malloc(2*sizeof(Token*)); + t[0]=malloc(sizeof(Token)); + t[1]=(Token*)0; + t[0]->type=STRING; + t[0]->u.string=mystrmalloc(body+5); + putcont(sheet,it(body+1),it(body+3),0,t,0); + format((unsigned char)body[0],SHEET(sheet,it(body+1),it(body+3),0)); + break; + } + /*}}}*/ + /* PASSWORD -- File lockout (CHKSUM) */ /*{{{*/ + case 0x37: assert(bodylen==4); break; + /*}}}*/ + /* LOCKED -- Lock flag */ /*{{{*/ + case 0x38: assert(bodylen==1); break; + /*}}}*/ + /* QUERY -- Symphony query settings */ /*{{{*/ + case 0x3C: assert(bodylen==127); break; + /*}}}*/ + /* QUERYNAME -- Query name */ /*{{{*/ + case 0x3D: assert(bodylen==16); break; + /*}}}*/ + /* PRINT -- Symphony print record */ /*{{{*/ + case 0x3E: assert(bodylen==679); break; + /*}}}*/ + /* PRINTNAME -- Print record name */ /*{{{*/ + case 0x3F: assert(bodylen==16); break; + /*}}}*/ + /* GRAPH2 -- Symphony graph record */ /*{{{*/ + case 0x40: assert(bodylen==499); break; + /*}}}*/ + /* GRAPHNAME -- Graph record name */ /*{{{*/ + case 0x41: assert(bodylen==16); break; + /*}}}*/ + /* ZOOM -- Orig coordinates expanded window */ /*{{{*/ + case 0x42: assert(bodylen==9); break; + /*}}}*/ + /* SYMSPLIT -- No. of split windows */ /*{{{*/ + case 0x43: assert(bodylen==2); break; + /*}}}*/ + /* NSROWS -- No. of screen rows */ /*{{{*/ + case 0x44: assert(bodylen==2); break; + /*}}}*/ + /* NSCOLS -- No. of screen columns */ /*{{{*/ + case 0x45: assert(bodylen==2); break; + /*}}}*/ + /* RULER -- Named ruler range */ /*{{{*/ + case 0x46: assert(bodylen==25); break; + /*}}}*/ + /* NNAME -- Named sheet range */ /*{{{*/ + case 0x47: assert(bodylen==25); break; + /*}}}*/ + /* ACOMM -- Autoload.comm code */ /*{{{*/ + case 0x48: assert(bodylen==65); break; + /*}}}*/ + /* AMACRO -- Autoexecute macro address */ /*{{{*/ + case 0x49: assert(bodylen==8); break; + /*}}}*/ + /* PARSE -- Query parse information */ /*{{{*/ + case 0x4A: assert(bodylen==16); break; + /*}}}*/ + } + /*}}}*/ + if (!found_bof) { err=_("This is not a WK1 file"); goto ouch; } + } + if (!found_eof) err=_("File truncated"); + ouch: + if (body) free(body); + if (fclose(fp)==EOF && err==(const char*)0) err=strerror(errno); + sheet->changed=0; + cachelabels(sheet); + forceupdate(sheet); + return err; +} +/*}}}*/ diff --git a/wk1.h b/wk1.h new file mode 100644 index 0000000..e2b89e6 --- /dev/null +++ b/wk1.h @@ -0,0 +1,8 @@ +#ifndef WK1_H +#define WK1_H + +#include "sheet.h" + +const char *loadwk1(Sheet *sheet, const char *name); + +#endif diff --git a/xdr.c b/xdr.c new file mode 100644 index 0000000..a57f587 --- /dev/null +++ b/xdr.c @@ -0,0 +1,209 @@ +/* Notes */ /*{{{C}}}*//*{{{*/ +/* + +xdr_enum() is unusable, because enum_t may have a different size than +an enum. The construction + + int_value=*enum_value; + result=xdr_int(xdrs,&int_value); + *enum_value=int_value; + return result; + +solves the problem and works for both encoding and decoding. +Unfortunately, I could not yet find such a solution for a variable sized +array terminated by a special element. + +*/ +/*}}}*/ +/* #includes */ /*{{{*/ +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +#include +#include +#include +#include + +#include "sheet.h" +#include "xdr.h" +/*}}}*/ + +/* xdr_token */ /*{{{*/ +static bool_t xdr_token(XDR *xdrs, Token *t) +{ + int x,result; + + if (xdrs->x_op==XDR_DECODE) (void)memset(t,0,sizeof(Token)); + x=t->type; + if (t->type==OPERATOR) x|=t->u.op<<8; + result=xdr_int(xdrs,&x); + if ((x&0xff)==OPERATOR) t->u.op=(x>>8)&0xff; + t->type=x&0xff; + if (result==0) return result; + switch (t->type) + { + /* EMPTY */ /*{{{*/ + case EMPTY: + { + return 1; + } + /*}}}*/ + /* STRING */ /*{{{*/ + case STRING: + { + return xdr_wrapstring(xdrs,&(t->u.string)); + } + /*}}}*/ + /* FLOAT */ /*{{{*/ + case FLOAT: + { + return xdr_double(xdrs,&(t->u.flt)); + } + /*}}}*/ + /* INT */ /*{{{*/ + case INT: + { + return xdr_long(xdrs,&(t->u.integer)); + } + /*}}}*/ + /* OPERATOR */ /*{{{*/ + case OPERATOR: + { + return 1; /* since op is encoded in type */ + } + /*}}}*/ + /* LIDENT */ /*{{{*/ + case LIDENT: + { + return xdr_wrapstring(xdrs,&(t->u.lident)); + } + /*}}}*/ + /* FIDENT */ /*{{{*/ + case FIDENT: + { + return xdr_int(xdrs,&(t->u.fident)); + } + /*}}}*/ + /* LOCATION */ /*{{{*/ + case LOCATION: + { + return (xdr_int(xdrs,&(t->u.location[0])) && xdr_int(xdrs,&(t->u.location[1])) && xdr_int(xdrs,&(t->u.location[2]))); + } + /*}}}*/ + /* EEK */ /*{{{*/ + case EEK: + { + return xdr_wrapstring(xdrs,&(t->u.err)); + } + /*}}}*/ + /* default -- should not happen */ /*{{{*/ + default: assert(0); + /*}}}*/ + } + return 0; +} +/*}}}*/ +/* xdr_tokenptr */ /*{{{*/ +static bool_t xdr_tokenptr(XDR *xdrs, Token **t) +{ + return xdr_pointer(xdrs,(char**)t,sizeof(Token),(xdrproc_t)xdr_token); +} +/*}}}*/ +/* xdr_tokenptrvec */ /*{{{*/ +static bool_t xdr_tokenptrvec(XDR *xdrs, Token ***t) +{ + unsigned int len; + int result; + + assert(t!=(Token***)0); + if (xdrs->x_op!=XDR_DECODE) + { + Token **run; + + if (*t==(Token**)0) len=0; + else for (len=1,run=*t; *run!=(Token*)0; ++len,++run); + } + result=xdr_array(xdrs,(char**)t,&len,65536,sizeof(Token*),(xdrproc_t)xdr_tokenptr); + if (len==0) *t=(Token**)0; + return result; +} +/*}}}*/ +/* xdr_mystring */ /*{{{*/ +static bool_t xdr_mystring(XDR *xdrs, char **str) +{ + static struct xdr_discrim arms[3]= + { + { 0, (xdrproc_t)xdr_void }, + { 1, (xdrproc_t)xdr_wrapstring }, + { -1, (xdrproc_t)0 } + }; + enum_t x; + int res; + + x=(*str!=(char*)0); + res=xdr_union(xdrs, &x, (char*)str, arms, (xdrproc_t)0); + if (!x) *str=(char*)0; + return res; +} +/*}}}*/ + +/* Notes */ /*{{{*/ +/* + +The saved sheet consists of three xdr_int()s which specify x, y and z +position of the cell saved with xdr_cell(). Perhaps xdr_cell could be +given those as parameters, which would be more correct concerning the +purpose of the xdr_functions. Then again, reading the position may +fail (eof), whereas after the position has been read, xdr_cell() must +not fail when loading a sheet. + +*/ +/*}}}*/ +/* xdr_cell */ /*{{{*/ +bool_t xdr_cell(XDR *xdrs, Cell *cell) +{ + int result,x; + + assert(cell!=(Cell*)0); + if (!(xdr_tokenptrvec(xdrs, &(cell->contents)) && xdr_tokenptrvec(xdrs, &(cell->ccontents)) /* && xdr_token(xdrs, &(cell->value)) */ )) return 0; + if (xdr_mystring(xdrs, &(cell->label))==0) return 0; + x=cell->adjust; + result=xdr_int(xdrs, &x); + cell->adjust=x; + if (result==0) return 0; + if (xdr_int(xdrs, &(cell->precision))==0) return 0; + x=(cell->updated&1)|((cell->shadowed&1)<<1)|((cell->scientific&1)<<2)|((cell->locked&1)<<3)|((cell->transparent&1)<<4)|((cell->ignored&1)<<5)|((cell->bold&1)<<6)|((cell->underline&1)<<7); + result=xdr_int(xdrs, &x); + cell->updated=((x&(1))!=0); + cell->shadowed=((x&(1<<1))!=0); + cell->scientific=((x&(1<<2))!=0); + cell->locked=((x&(1<<3))!=0); + cell->transparent=((x&(1<<4))!=0); + cell->ignored=((x&(1<<5))!=0); + cell->bold=((x&(1<<6))!=0); + cell->underline=((x&(1<<7))!=0); + return result; +} +/*}}}*/ +/* xdr_column */ /*{{{*/ +bool_t xdr_column(XDR *xdrs, int *x, int *z, int *width) +{ + return (xdr_int(xdrs, x) && xdr_int(xdrs, z) && xdr_int(xdrs, width)); +} +/*}}}*/ +/* xdr_magic */ /*{{{*/ +#define MAGIC0 (('#'<<24)|('!'<<16)|('t'<<8)|'e') +#define MAGIC1 (('a'<<24)|('p'<<16)|('o'<<8)|'t') +#define MAGIC2 (('\n'<<24)|('x'<<16)|('d'<<8)|'r') + +bool_t xdr_magic(XDR *xdrs) +{ + long m0,m1,m2; + + m0=MAGIC0; + m1=MAGIC1; + m2=MAGIC2; + return (xdr_long(xdrs,&m0) && m0==MAGIC0 && xdr_long(xdrs,&m1) && m1==MAGIC1 && xdr_long(xdrs,&m2) && m2==MAGIC2); +} +/*}}}*/ diff --git a/xdr.h b/xdr.h new file mode 100644 index 0000000..403ff4c --- /dev/null +++ b/xdr.h @@ -0,0 +1,20 @@ +#ifndef XDR_H +#define XDR_H + +/* Thanks to curses. */ +#if 0 +#undef TRUE +#undef FALSE +#endif + +#include +#include +#include + +#include "sheet.h" + +bool_t xdr_cell(XDR *xdrs, Cell *cell); +bool_t xdr_column(XDR *xdrs, int *x, int *z, int *width); +bool_t xdr_magic(XDR *xdrs); + +#endif