From fe3d4ce758822d65a0a5d617b7b77df2dbc972d8 Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Sun, 16 Mar 2014 14:55:57 -0700 Subject: Implement new magic frontend using sexpr --- .travis.yml | 8 +- COPYING | 5 +- Makefile.in | 5 +- agpl-3.0.txt | 661 ++++++++++++++++ src/io/line.cpp | 43 +- src/io/line.hpp | 21 +- src/io/line_test.cpp | 47 ++ src/map/atcommand.cpp | 18 +- src/map/itemdb.cpp | 5 +- src/map/itemdb.hpp | 3 +- src/map/magic-expr-eval.cpp | 3 + src/map/magic-expr.cpp | 33 +- src/map/magic-interpreter-aux.cpp | 3 + src/map/magic-interpreter-lexer.hpp | 1 - src/map/magic-interpreter-lexer.lpp | 152 ---- src/map/magic-interpreter-parser.ypp | 1441 ---------------------------------- src/map/magic-interpreter.cpp | 3 + src/map/magic-interpreter.hpp | 3 - src/map/magic-interpreter.py | 224 ++++++ src/map/magic-v2.cpp | 1245 +++++++++++++++++++++++++++++ src/map/magic-v2.hpp | 28 + src/map/magic.cpp | 2 - src/map/main.cpp | 2 + src/map/map.cpp | 3 +- src/map/npc.cpp | 2 +- src/map/script.cpp | 14 +- src/sexpr/lexer.cpp | 228 ++++++ src/sexpr/lexer.hpp | 73 ++ src/sexpr/lexer_test.cpp | 113 +++ src/sexpr/main.cpp | 130 +++ src/sexpr/parser.cpp | 79 ++ src/sexpr/parser.hpp | 80 ++ src/sexpr/parser.py | 25 + src/sexpr/parser_test.cpp | 89 +++ src/spell-convert/ast.cpp | 260 ++++++ src/spell-convert/ast.hpp | 432 ++++++++++ src/spell-convert/lexer.lpp | 117 +++ src/spell-convert/main.cpp | 7 + src/spell-convert/parser.ypp | 882 +++++++++++++++++++++ 39 files changed, 4857 insertions(+), 1633 deletions(-) create mode 100644 agpl-3.0.txt create mode 100644 src/map/magic-expr-eval.cpp create mode 100644 src/map/magic-interpreter-aux.cpp delete mode 100644 src/map/magic-interpreter-lexer.hpp delete mode 100644 src/map/magic-interpreter-lexer.lpp delete mode 100644 src/map/magic-interpreter-parser.ypp create mode 100644 src/map/magic-interpreter.cpp create mode 100644 src/map/magic-interpreter.py create mode 100644 src/map/magic-v2.cpp create mode 100644 src/map/magic-v2.hpp create mode 100644 src/sexpr/lexer.cpp create mode 100644 src/sexpr/lexer.hpp create mode 100644 src/sexpr/lexer_test.cpp create mode 100644 src/sexpr/main.cpp create mode 100644 src/sexpr/parser.cpp create mode 100644 src/sexpr/parser.hpp create mode 100644 src/sexpr/parser.py create mode 100644 src/sexpr/parser_test.cpp create mode 100644 src/spell-convert/ast.cpp create mode 100644 src/spell-convert/ast.hpp create mode 100644 src/spell-convert/lexer.lpp create mode 100644 src/spell-convert/main.cpp create mode 100644 src/spell-convert/parser.ypp diff --git a/.travis.yml b/.travis.yml index 02f837e..3dc9847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,12 +49,12 @@ script: - cd build - ../configure --dev CPPFLAGS=-DQUIET - make -R -k -j2 - - make -R format - - git diff --exit-code + - make -R -k -j2 test TESTER='valgrind --error-exitcode=1 --track-fds=yes' ## Do something after the main test script after_script: - - make -R test TESTER='valgrind --error-exitcode=1 --track-fds=yes' + - make -R -k -j2 format + - git diff --exit-code ### The rest of the file creates a build matrix ### containing gcc-4.6 through gcc-4.8 and clang-3.1 through clang-3.3 @@ -84,6 +84,8 @@ matrix: env: REAL_CC=clang-3.2 REAL_CXX=clang++-3.2 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.2 - compiler: clang env: REAL_CC=clang-3.3 REAL_CXX=clang++-3.3 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.3 + - compiler: clang + env: REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.4 - compiler: gcc env: REAL_CC=gcc-4.6 REAL_CXX=g++-4.6 PPA= PACKAGE=g++-4.6 - compiler: gcc diff --git a/COPYING b/COPYING index a5bc77a..70323b9 100644 --- a/COPYING +++ b/COPYING @@ -1,7 +1,8 @@ TmwAthena - an MMORPG server -This software is available under GPL3+. See gpl-3.0.txt for specifics. - +This software is available under a combination of GPL3+ and AGPL3+. +This combination is explicitly allowed by section 13 of the licenses. +See gpl-3.0.txt or agpl-3.0.txt for specific terms. History: diff --git a/Makefile.in b/Makefile.in index 9871843..6321489 100644 --- a/Makefile.in +++ b/Makefile.in @@ -169,6 +169,7 @@ GEN_SOURCES := \ $(patsubst %.lpp,%.cpp,${LEXERS}) \ $(patsubst %.ypp,%.cpp,${PARSERS}) GEN_HEADERS := \ + $(patsubst %.lpp,%.hpp,${LEXERS}) \ $(patsubst %.ypp,%.hpp,${PARSERS}) REAL_SOURCES := $(shell cd ${SRC_DIR}; find src/ -name '*.cpp') REAL_HEADERS := $(shell cd ${SRC_DIR}; find src/ -name '*.hpp' -o -name '*.tcc') @@ -287,9 +288,9 @@ distclean: clean gen-clean gen-clean: $c rm -f ${GEN_SOURCES} ${GEN_HEADERS} -%.cpp: %.lpp +%.cpp %.hpp: %.lpp $(MKDIR_FIRST) - $c ${FLEX} -o $@ $< + $c ${FLEX} --header-file=$*.hpp -o $*.cpp $< %.cpp %.hpp: %.ypp $(MKDIR_FIRST) $c ${BISON} --defines=$*.hpp -o $*.cpp $< diff --git a/agpl-3.0.txt b/agpl-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/agpl-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/src/io/line.cpp b/src/io/line.cpp index fb73f45..8173398 100644 --- a/src/io/line.cpp +++ b/src/io/line.cpp @@ -32,7 +32,7 @@ namespace io { - AString Line::message_str(ZString cat, ZString msg) + AString Line::message_str(ZString cat, ZString msg) const { MString out; if (column) @@ -46,7 +46,7 @@ namespace io return AString(out); } - void Line::message(ZString cat, ZString msg) + void Line::message(ZString cat, ZString msg) const { if (column) FPRINTF(stderr, "%s:%u:%u: %s: %s\n", @@ -58,6 +58,45 @@ namespace io FPRINTF(stderr, "%*c\n", column, '^'); } + AString LineSpan::message_str(ZString cat, ZString msg) const + { + assert (begin.column); + assert (end.column); + assert (begin.column <= end.column); + + MString out; + if (begin.line == end.line) + { + out += STRPRINTF("%s:%u:%u: %s: %s\n", + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n", begin.text); + out += STRPRINTF("%*c", begin.column, '^'); + for (unsigned c = begin.column; c != end.column; ++c) + out += '~'; + out += '\n'; + } + else + { + out += STRPRINTF("%s:%u:%u: %s: %s\n", + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n", begin.text); + out += STRPRINTF("%*c", begin.column, '^'); + for (unsigned c = begin.column; c != begin.text.size(); ++c) + out += '~'; + out += " ...\n"; + out += STRPRINTF("%s\n", end.text); + for (unsigned c = 0; c != end.column; ++c) + out += '~'; + out += '\n'; + } + return AString(out); + } + + void LineSpan::message(ZString cat, ZString msg) const + { + FPRINTF(stderr, "%s", message_str(cat, msg)); + } + LineReader::LineReader(ZString name) : filename(name), line(0), column(0), rf(name) {} diff --git a/src/io/line.hpp b/src/io/line.hpp index 321cdf7..a9e8944 100644 --- a/src/io/line.hpp +++ b/src/io/line.hpp @@ -40,11 +40,11 @@ namespace io // 1-based uint16_t line, column; - AString message_str(ZString cat, ZString msg); - void message(ZString cat, ZString msg); - void note(ZString msg) { message("note", msg); } - void warning(ZString msg) { message("warning", msg); } - void error(ZString msg) { message("error", msg); } + AString message_str(ZString cat, ZString msg) const; + void message(ZString cat, ZString msg) const; + void note(ZString msg) const { message("note", msg); } + void warning(ZString msg) const { message("warning", msg); } + void error(ZString msg) const { message("error", msg); } }; // psst, don't tell anyone @@ -59,6 +59,17 @@ namespace io } }; + struct LineSpan + { + LineChar begin, end; + + AString message_str(ZString cat, ZString msg) const; + void message(ZString cat, ZString msg) const; + void note(ZString msg) const { message("note", msg); } + void warning(ZString msg) const { message("warning", msg); } + void error(ZString msg) const { message("error", msg); } + }; + class LineReader { protected: diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp index ae316c6..6f0706f 100644 --- a/src/io/line_test.cpp +++ b/src/io/line_test.cpp @@ -348,3 +348,50 @@ TEST(io, linechar5) lr.adv(); EXPECT_FALSE(lr.get(c)); } + +TEST(io, linespan) +{ + io::LineCharReader lr("", string_pipe("Hello\nWorld\n")); + io::LineSpan span; + do + { + lr.get(span.begin); + lr.adv(); + } + while (span.begin.ch() != 'e'); + do + { + lr.get(span.end); + lr.adv(); + } + while (span.end.ch() != 'o'); + EXPECT_EQ(span.message_str("info", "meh"), + ":1:2: info: meh\n" + "Hello\n" + " ^~~~\n" + ); + do + { + lr.get(span.end); + lr.adv(); + } + while (span.end.ch() != 'r'); + + EXPECT_EQ(span.begin.message_str("note", "foo"), + ":1:2: note: foo\n" + "Hello\n" + " ^\n" + ); + EXPECT_EQ(span.end.message_str("warning", "bar"), + ":2:3: warning: bar\n" + "World\n" + " ^\n" + ); + EXPECT_EQ(span.message_str("error", "qux"), + ":1:2: error: qux\n" + "Hello\n" + " ^~~~ ...\n" + "World\n" + "~~~\n" + ); +} diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 29bf0b1..5455671 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -1290,7 +1290,7 @@ static ATCE atcommand_item(Session *s, dumb_ptr sd, ZString message) { - ItemName item_name; + XString item_name; int number = 0, item_id; struct item_data *item_data = NULL; int get_count, i; @@ -1306,9 +1306,12 @@ ATCE atcommand_item(Session *s, dumb_ptr sd, number = 1; item_id = 0; - if ((item_data = itemdb_searchname(item_name)) != NULL || - (item_data = itemdb_exists(atoi(item_name.c_str()))) != NULL) + if ((item_data = itemdb_searchname(item_name)) != NULL) + item_id = item_data->nameid; + else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != NULL) item_id = item_data->nameid; + else + item_id = 0; if (item_id >= 500) { @@ -3635,7 +3638,7 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr sd, ZString message) { CharName character; - ItemName item_name; + XString item_name; int i, number = 0, item_id, item_position, count; struct item_data *item_data; @@ -3643,9 +3646,12 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr sd, return ATCE::USAGE; item_id = 0; - if ((item_data = itemdb_searchname(item_name)) != NULL || - (item_data = itemdb_exists(atoi(item_name.c_str()))) != NULL) + if ((item_data = itemdb_searchname(item_name)) != NULL) + item_id = item_data->nameid; + else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != NULL) item_id = item_data->nameid; + else + item_id = 0; if (item_id > 500) { diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 528c81f..1822b8a 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -48,8 +48,11 @@ void itemdb_searchname_sub(struct item_data *item, ItemName str, struct item_dat * 名前で検索 *------------------------------------------ */ -struct item_data *itemdb_searchname(ItemName str) +struct item_data *itemdb_searchname(XString str_) { + ItemName str = stringish(str_); + if (XString(str) != str_) + return nullptr; struct item_data *item = NULL; for (auto& pair : item_db) itemdb_searchname_sub(&pair.second, str, &item); diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index 4c07303..624030e 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -33,7 +33,8 @@ struct random_item_data int per; }; -struct item_data *itemdb_searchname(ItemName name); +struct item_data *itemdb_searchname(ItemName name) = delete; +struct item_data *itemdb_searchname(XString name); struct item_data *itemdb_search(int nameid); struct item_data *itemdb_exists(int nameid); diff --git a/src/map/magic-expr-eval.cpp b/src/map/magic-expr-eval.cpp new file mode 100644 index 0000000..57f9c32 --- /dev/null +++ b/src/map/magic-expr-eval.cpp @@ -0,0 +1,3 @@ +#include "magic-expr-eval.hpp" + +#include "../poison.hpp" diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp index 279ba56..1be1b0b 100644 --- a/src/map/magic-expr.cpp +++ b/src/map/magic-expr.cpp @@ -358,7 +358,7 @@ int fun_neg(dumb_ptr, val_t *result, const_array args) static int fun_gte(dumb_ptr, val_t *result, const_array args) { - if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING) + if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING) { stringify(&args[0], 1); stringify(&args[1], 1); @@ -373,6 +373,14 @@ int fun_gte(dumb_ptr, val_t *result, const_array args) return 0; } +static +int fun_lt(dumb_ptr env, val_t *result, const_array args) +{ + fun_gte(env, result, args); + RESULTINT = !RESULTINT; + return 0; +} + static int fun_gt(dumb_ptr, val_t *result, const_array args) { @@ -391,6 +399,14 @@ int fun_gt(dumb_ptr, val_t *result, const_array args) return 0; } +static +int fun_lte(dumb_ptr env, val_t *result, const_array args) +{ + fun_gt(env, result, args); + RESULTINT = !RESULTINT; + return 0; +} + static int fun_eq(dumb_ptr, val_t *result, const_array args) { @@ -423,6 +439,14 @@ int fun_eq(dumb_ptr, val_t *result, const_array args) return 0; } +static +int fun_ne(dumb_ptr env, val_t *result, const_array args) +{ + fun_eq(env, result, args); + RESULTINT = !RESULTINT; + return 0; +} + static int fun_bitand(dumb_ptr, val_t *result, const_array args) { @@ -753,7 +777,7 @@ magic_find_item(const_array args, int index, struct item *item_, int *sta if (ARG_TYPE(index) == TYPE::INT) item_data = itemdb_exists(ARGINT(index)); else if (ARG_TYPE(index) == TYPE::STRING) - item_data = itemdb_searchname(stringish(ARGSTR(index))); + item_data = itemdb_searchname(ARGSTR(index)); else return -1; @@ -1250,9 +1274,12 @@ std::map functions = MAGIC_FUNCTION("%", "ii", 'i', fun_mod), MAGIC_FUNCTION("||", "ii", 'i', fun_or), MAGIC_FUNCTION("&&", "ii", 'i', fun_and), + MAGIC_FUNCTION("<", "..", 'i', fun_lt), MAGIC_FUNCTION(">", "..", 'i', fun_gt), + MAGIC_FUNCTION("<=", "..", 'i', fun_lte), MAGIC_FUNCTION(">=", "..", 'i', fun_gte), - MAGIC_FUNCTION("=", "..", 'i', fun_eq), + MAGIC_FUNCTION("==", "..", 'i', fun_eq), + MAGIC_FUNCTION("!=", "..", 'i', fun_ne), MAGIC_FUNCTION("|", "..", 'i', fun_bitor), MAGIC_FUNCTION("&", "ii", 'i', fun_bitand), MAGIC_FUNCTION("^", "ii", 'i', fun_bitxor), diff --git a/src/map/magic-interpreter-aux.cpp b/src/map/magic-interpreter-aux.cpp new file mode 100644 index 0000000..e293e78 --- /dev/null +++ b/src/map/magic-interpreter-aux.cpp @@ -0,0 +1,3 @@ +#include "magic-interpreter-aux.hpp" + +#include "../poison.hpp" diff --git a/src/map/magic-interpreter-lexer.hpp b/src/map/magic-interpreter-lexer.hpp deleted file mode 100644 index 8c7e195..0000000 --- a/src/map/magic-interpreter-lexer.hpp +++ /dev/null @@ -1 +0,0 @@ -// dummy header to make Make dependencies work diff --git a/src/map/magic-interpreter-lexer.lpp b/src/map/magic-interpreter-lexer.lpp deleted file mode 100644 index 38b7b1e..0000000 --- a/src/map/magic-interpreter-lexer.lpp +++ /dev/null @@ -1,152 +0,0 @@ -%{ -#include "magic-interpreter-lexer.hpp" - -#include "magic-interpreter-parser.hpp" - -#include "../io/cxxstdio.hpp" - -#ifdef HEADING -# error "what platform is this? please tell me who #defined HEADING" -#endif - -#define FIXLOC magic_frontend_lloc.first_line = magic_frontend_lineno - -#define HEADING(dir) { magic_frontend_lval.i = dir; FIXLOC; return DIR; } -%} - -%option yylineno -%option noyywrap -%option prefix="magic_frontend_" -%option nounput -%option noinput - -%% - -"S" HEADING(0); -"SW" HEADING(1); -"W" HEADING(2); -"NW" HEADING(3); -"N" HEADING(4); -"NE" HEADING(5); -"E" HEADING(6); -"SE" HEADING(7); -"=" {FIXLOC; return '=';} -"==" {FIXLOC; return EQ;} -"<>" {FIXLOC; return NEQ;} -"!=" {FIXLOC; return NEQ;} -">" {FIXLOC; return '>';} -"<" {FIXLOC; return '<';} -">=" {FIXLOC; return GTE;} -"<=" {FIXLOC; return LTE;} -"(" {FIXLOC; return '(';} -")" {FIXLOC; return ')';} -"+" {FIXLOC; return '+';} -"-" {FIXLOC; return '-';} -"*" {FIXLOC; return '*';} -"/" {FIXLOC; return '/';} -"%" {FIXLOC; return '%';} -"&&" {FIXLOC; return ANDAND;} -"||" {FIXLOC; return OROR;} -";" {FIXLOC; return ';';} -":" {FIXLOC; return ':';} -"," {FIXLOC; return ',';} -"@" {FIXLOC; return '@';} -"|" {FIXLOC; return '|';} -"[" {FIXLOC; return '[';} -"]" {FIXLOC; return ']';} -"&" {FIXLOC; return '&';} -"^" {FIXLOC; return '^';} -"." {FIXLOC; return '.';} -"<<" {FIXLOC; return SHL;} -">>" {FIXLOC; return SHR;} -"PROCEDURE" {FIXLOC; return PROCEDURE;} -"CALL" {FIXLOC; return CALL;} -"OR" {FIXLOC; return OR;} -"TO" {FIXLOC; return TO;} -"TOWARDS" {FIXLOC; return TOWARDS;} -"TELEPORT-ANCHOR" {FIXLOC; return TELEPORT_ANCHOR;} -"SILENT" {FIXLOC; return SILENT;} -"LOCAL" {FIXLOC; return LOCAL;} -"NONMAGIC" {FIXLOC; return NONMAGIC;} -"SPELL" {FIXLOC; return SPELL;} -"LET" {FIXLOC; return LET;} -"IN" {FIXLOC; return IN;} -"END" {FIXLOC; return END;} -"=>" {FIXLOC; return DARROW;} -"STRING" {FIXLOC; return STRING_TY;} -"REQUIRE" {FIXLOC; return REQUIRE;} -"CATALYSTS" {FIXLOC; return CATALYSTS;} -"COMPONENTS" {FIXLOC; return COMPONENTS;} -"MANA" {FIXLOC; return MANA;} -"CASTTIME" {FIXLOC; return CASTTIME;} -"SKIP" {FIXLOC; return SKIP;} -"ABORT" {FIXLOC; return ABORT;} -"BREAK" {FIXLOC; return BREAK;} -"EFFECT" {FIXLOC; return EFFECT_;} -"ATEND" {FIXLOC; return ATEND;} -"ATTRIGGER" {FIXLOC; return ATTRIGGER;} -"CONST" {FIXLOC; return CONST;} -"PC" {FIXLOC; return PC_F;} -"NPC" {FIXLOC; return NPC_F;} -"MOB" {FIXLOC; return MOB_F;} -"ENTITY" {FIXLOC; return ENTITY_F;} -"TARGET" {FIXLOC; return TARGET_F;} -"IF" {FIXLOC; return IF;} -"THEN" {FIXLOC; return THEN;} -"ELSE" {FIXLOC; return ELSE;} -"FOREACH" {FIXLOC; return FOREACH;} -"FOR" {FIXLOC; return FOR;} -"DO" {FIXLOC; return DO;} -"WAIT" {FIXLOC; return SLEEP;} - -\{([^\}]|\\.)*\} { - magic_frontend_lval.s = dumb_string::copy(yytext); - FIXLOC; - return SCRIPT_DATA; -} - -\"([^\"]|\\.)*\" { - dumb_string string = dumb_string::copy(yytext + 1); - const char *src = string.c_str(); - char *dst = &string[0]; - while (*src && *src != '"') - { - if (*src == '\\') - { - *dst++ = src[1]; - src += 2; - } - else - *dst++ = *src++; - } - *dst = '\0'; /* terminate */ - magic_frontend_lval.s = string; - FIXLOC; - return STRING; -} - -"-"?[0-9]+ { - magic_frontend_lval.i = atoi(yytext); - FIXLOC; - return INT; - } - -"0x"[0-9a-fA-F]+ { - magic_frontend_lval.i = strtol(yytext + 2, NULL, 16); - FIXLOC; - return INT; - } - -[a-zA-Z][-_a-zA-Z0-9]* { - magic_frontend_lval.s = dumb_string::copy(yytext); - FIXLOC; - return ID; -} - -"#".*$ /* Ignore comments */ -"//".*$ /* Ignore comments */ -[ \n\t\r] /* ignore whitespace */ -. FPRINTF(stderr, "%s: Unexpected character in line %d\n", current_magic_filename, magic_frontend_lineno); - -%% -// nothing to see here, move along diff --git a/src/map/magic-interpreter-parser.ypp b/src/map/magic-interpreter-parser.ypp deleted file mode 100644 index ef8b159..0000000 --- a/src/map/magic-interpreter-parser.ypp +++ /dev/null @@ -1,1441 +0,0 @@ -%code requires -{ -#include "magic-interpreter.hpp" - -extern -AString current_magic_filename; -} // %code requires - -%code -{ -#include "magic-interpreter-parser.hpp" - -#include -#include // exception to "no va_list" rule, even after cxxstdio - -#include "../strings/rstring.hpp" -#include "../strings/astring.hpp" -#include "../strings/zstring.hpp" - -#include "../generic/const_array.hpp" - -#include "../io/cxxstdio.hpp" - -#include "itemdb.hpp" -#include "magic-expr.hpp" - -AString current_magic_filename; - -// can't use src/warnings.hpp in generated code -#pragma GCC diagnostic warning "-Wall" -#pragma GCC diagnostic warning "-Wextra" -#pragma GCC diagnostic warning "-Wformat" -#ifndef __clang__ -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -static -size_t intern_id(ZString id_name); - -static -dumb_ptr fun_expr(AString name, const_array> argv, int line, int column); - -static -dumb_ptr dot_expr(dumb_ptr lhs, int id); - -static -void BIN_EXPR(dumb_ptr& x, AString name, dumb_ptr arg1, dumb_ptr arg2, int line, int column) -{ - dumb_ptr e[2]; - e[0] = arg1; - e[1] = arg2; - x = fun_expr(name, const_array>(e, 2), line, column); -} - -static -int failed_flag = 0; - -static -void magic_frontend_error(const char *msg); - -static __attribute__((format(printf, 3, 4))) -void fail(int line, int column, const char *fmt, ...); - -static -dumb_ptr new_spell(dumb_ptr guard); - -static -dumb_ptr spellguard_implication(dumb_ptr a, dumb_ptr b); - -static -dumb_ptr new_spellguard(SPELLGUARD ty); - -static -dumb_ptr new_effect(EFFECT ty); - -static -dumb_ptr set_effect_continuation(dumb_ptr src, dumb_ptr continuation); - -static -void add_spell(dumb_ptr spell, int line_nr); - -static -void add_teleport_anchor(dumb_ptr anchor, int line_nr); - -static -dumb_ptr op_effect(AString name, const_array> argv, int line, int column); - -// in magic-interpreter-lexer.cpp -int magic_frontend_lex(void); - -static -void install_proc(dumb_ptr proc); - -static -dumb_ptr call_proc(ZString name, dumb_ptr>> argvp, int line_nr, int column); - -static -void bind_constant(RString name, val_t *val, int line_nr); - -static -val_t *find_constant(RString name); - -} // %code - -%name-prefix="magic_frontend_" - -%locations - -%union -{ - int i; - SPELL_FLAG spell_flags; - SPELLARG spell_arg; - FOREACH_FILTER foreach_filter; - dumb_string s; - int op; - // magic_conf_t *magic_conf; - val_t value; - dumb_ptr expr; - e_location_t location; - e_area_t area; - args_rec_t arg_list; - dumb_ptr> letdefvp; - dumb_ptr spell; - struct { int id; SPELLARG ty; } spellarg_def; - letdef_t vardef; - dumb_ptr spellguard; - dumb_ptr components; - struct { int id, count; } component; - dumb_ptr effect; - dumb_ptr proc; - - // evil hackery - YYSTYPE() { really_memzero_this(this); } - ~YYSTYPE() = default; - YYSTYPE(const YYSTYPE& rhs) = default; - YYSTYPE& operator = (const YYSTYPE& rhs) = default; -} // %union - -%expect 7 - -%token INT -%token STRING -%token ID -%token DIR - -%token '=' -%token '<' -%token '>' -%token '+' -%token '-' -%token '*' -%token '/' -%token '%' -%token '@' -%token ',' -%token '.' -%token ':' -%token ';' -%token '|' -%token '[' -%token ']' -%token '&' -%token '^' - -%token CONST -%token PROCEDURE -%token CALL -%token SILENT -%token LOCAL -%token NONMAGIC -%token SHL -%token SHR -%token EQ -%token NEQ -%token GTE -%token LTE -%token ANDAND -%token OROR -%token SCRIPT_DATA -%token TO -%token TOWARDS -%token TELEPORT_ANCHOR -%token SPELL -%token LET -%token IN -%token END -%token DARROW -%token STRING_TY -%token REQUIRE -%token CATALYSTS -%token COMPONENTS -%token MANA -%token CASTTIME -%token SKIP -%token ABORT -%token BREAK -%token EFFECT_ -%token ATEND -%token ATTRIGGER -%token PC_F -%token NPC_F -%token MOB_F -%token ENTITY_F -%token TARGET_F -%token IF -%token THEN -%token ELSE -%token FOREACH -%token FOR -%token DO -%token SLEEP - -%type value -%type location -%type area -%type arg_list -%type arg_list_ne -%type defs -%type spelldef -%type argopt -%type def -%type spellbody_list -%type spellbody -%type spellguard -%type spellguard_list -%type prereq -%type item -%type items -%type item_list -%type item_name -%type selection; -%type effect -%type effect_list -%type maybe_trigger -%type maybe_end -%type spell_flags; - -%type expr -%type arg_ty -%type proc_formals_list -%type proc_formals_list_ne - -%left OROR -%left ANDAND -%left '<' '>' GTE LTE NEQ EQ -%left '+' '-' -%left '*' '/' '%' -%left SHL SHR '&' '^' '|' -%right '=' -%left OR -%left DARROW -%left '.' - -%% - -spellconf - -: /* empty */ -{} - -| spellconf_option semicolons spellconf -{} - -; - - -semicolons - -: /* empty */ -{} - -| semicolons ';' -{} - -; - - -proc_formals_list - -: /* empty */ -{ - $$ = dumb_ptr::make(); -} - -| proc_formals_list_ne -{ - $$ = $1; -} - -; - - -proc_formals_list_ne - -: ID -{ - $$ = dumb_ptr::make(); - $$->argv.push_back(intern_id($1)); - $1.delete_(); -} - -| proc_formals_list_ne ',' ID -{ - $$ = $1; - $$->argv.push_back(intern_id($3)); - $3.delete_(); -} - -; - - -spellconf_option - -: ID '=' expr -{ - if (find_constant($1.str())) - { - fail(@1.first_line, 0, "Attempt to redefine constant `%s' as global\n", $1.c_str()); - } - else - { - int var_id = intern_id($1); - magic_eval(dumb_ptr(&magic_default_env), &magic_conf.varv[var_id].val, $3); - } - $1.delete_(); -} - -| CONST ID '=' expr -{ - val_t var; - magic_eval(dumb_ptr(&magic_default_env), &var, $4); - bind_constant($2.str(), &var, @1.first_line); - $2.delete_(); -} - -| TELEPORT_ANCHOR ID ':' expr '=' expr -{ - auto anchor = dumb_ptr::make(); - anchor->name = $2.str(); - $2.delete_(); - anchor->invocation = magic_eval_str(dumb_ptr(&magic_default_env), $4); - anchor->location = $6; - - if (!failed_flag) - add_teleport_anchor(anchor, @1.first_line); - else - anchor.delete_(); - failed_flag = 0; -} - -| PROCEDURE ID '(' proc_formals_list ')' '=' effect_list -{ - dumb_ptr proc = $4; - proc->name = $2.str(); - $2.delete_(); - proc->body = $7; - if (!failed_flag) - install_proc(proc); - proc.delete_(); - failed_flag = 0; -} - -| spell_flags SPELL ID argopt ':' expr '=' spelldef -{ - dumb_ptr spell = $8; - spell->name = $3.str(); - $3.delete_(); - spell->invocation = magic_eval_str(dumb_ptr(&magic_default_env), $6); - spell->arg = $4.id; - spell->spellarg_ty = $4.ty; - spell->flags = $1; - if (!failed_flag) - add_spell(spell, @1.first_line); - failed_flag = 0; -} - -; - - -spell_flags - -: /* empty */ -{ - $$ = SPELL_FLAG::ZERO; -} - -| LOCAL spell_flags -{ - if (bool($2 & SPELL_FLAG::LOCAL)) - fail(@1.first_line, @1.first_column, "`LOCAL' specified more than once"); - $$ = $2 | SPELL_FLAG::LOCAL; -} - -| NONMAGIC spell_flags -{ - if (bool($2 & SPELL_FLAG::NONMAGIC)) - fail(@1.first_line, @1.first_column, "`NONMAGIC' specified more than once"); - $$ = $2 | SPELL_FLAG::NONMAGIC; -} - -| SILENT spell_flags -{ - if (bool($2 & SPELL_FLAG::SILENT)) - fail(@1.first_line, @1.first_column, "`SILENT' specified more than once"); - $$ = $2 | SPELL_FLAG::SILENT; -} - -; - - -argopt - -: /* empty */ -{ - $$.ty = SPELLARG::NONE; -} - -| '(' ID ':' arg_ty ')' -{ - $$.id = intern_id($2); - $2.delete_(); - $$.ty = $4; -} - -; - - -arg_ty - -: PC_F -{ - $$ = SPELLARG::PC; -} - -| STRING_TY -{ - $$ = SPELLARG::STRING; -} - -; - - -value - -: DIR -{ - $$.ty = TYPE::DIR; - $$.v.v_int = $1; -} - -| INT -{ - $$.ty = TYPE::INT; - $$.v.v_int = $1; -} - -| STRING -{ - $$.ty = TYPE::STRING; - $$.v.v_string = $1; -} - -; - - -expr - -: value -{ - $$ = magic_new_expr(EXPR::VAL); - $$->e.e_val = $1; -} - -| ID -{ - val_t *val = find_constant($1.str()); - if (val) - { - $$ = magic_new_expr(EXPR::VAL); - $$->e.e_val = *val; - } - else - { - $$ = magic_new_expr(EXPR::ID); - $$->e.e_id = intern_id($1); - } - $1.delete_(); -} - -| area -{ - $$ = magic_new_expr(EXPR::AREA); - $$->e.e_area = $1; -} - -| expr '+' expr -{ - BIN_EXPR($$, "+", $1, $3, @1.first_line, @1.first_column); -} - -| expr '-' expr -{ - BIN_EXPR($$, "-", $1, $3, @1.first_line, @1.first_column); -} - -| expr '*' expr -{ - BIN_EXPR($$, "*", $1, $3, @1.first_line, @1.first_column); -} - -| expr '%' expr -{ - BIN_EXPR($$, "%", $1, $3, @1.first_line, @1.first_column); -} - -| expr '/' expr -{ - BIN_EXPR($$, "/", $1, $3, @1.first_line, @1.first_column); -} - -| expr '<' expr -{ - BIN_EXPR($$, ">", $3, $1, @1.first_line, @1.first_column); -} - -| expr '>' expr -{ - BIN_EXPR($$, ">", $1, $3, @1.first_line, @1.first_column); -} - -| expr '&' expr -{ - BIN_EXPR($$, "&", $1, $3, @1.first_line, @1.first_column); -} - -| expr '^' expr -{ - BIN_EXPR($$, "^", $1, $3, @1.first_line, @1.first_column); -} - -| expr '|' expr -{ - BIN_EXPR($$, "|", $1, $3, @1.first_line, @1.first_column); -} - -| expr SHL expr -{ - BIN_EXPR($$, "<<", $1, $3, @1.first_line, @1.first_column); -} - -| expr SHR expr -{ - BIN_EXPR($$, ">>", $1, $3, @1.first_line, @1.first_column); -} - -| expr LTE expr -{ - BIN_EXPR($$, ">=", $3, $1, @1.first_line, @1.first_column); -} - -| expr GTE expr -{ - BIN_EXPR($$, ">=", $1, $3, @1.first_line, @1.first_column); -} - -| expr ANDAND expr -{ - BIN_EXPR($$, "&&", $1, $3, @1.first_line, @1.first_column); -} - -| expr OROR expr -{ - BIN_EXPR($$, "||", $1, $3, @1.first_line, @1.first_column); -} - -| expr EQ expr -{ - BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); -} - -| expr '=' expr -{ - BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); -} - -| expr NEQ expr -{ - BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); - $$ = fun_expr("not", const_array>(&$$, 1), @1.first_line, @1.first_column); -} - -| ID '(' arg_list ')' -{ - $$ = fun_expr($1.str(), *$3.argvp, @1.first_line, @1.first_column); - $3.argvp.delete_(); - $1.delete_(); // allocated from m-i-lexer.lpp -} - -| '(' expr ')' -{ - $$ = $2; -} - -| expr '.' ID -{ - $$ = dot_expr($1, intern_id($3)); - $3.delete_(); -} - -; - - -arg_list - -: /* empty */ -{ - $$.argvp.new_(); -} - -| arg_list_ne -{ - $$ = $1; -} - -; - - -arg_list_ne - -: expr -{ - $$.argvp.new_(); - $$.argvp->push_back($1); -} - -| arg_list_ne ',' expr -{ - // yikes! Fate is officially banned from ever touching my code again. - $$ = $1; - $$.argvp->push_back($3); -} - -; - - -location - -: '@' '(' expr ',' expr ',' expr ')' -{ - $$.m = $3; - $$.x = $5; - $$.y = $7; -} - -; - - -area - -: location -{ - $$.ty = AREA::LOCATION; - $$.a.a_loc = $1; -} - -| location '@' '+' '(' expr ',' expr ')' -{ - $$.ty = AREA::RECT; - $$.a.a_rect.loc = $1; - $$.a.a_rect.width = $5; - $$.a.a_rect.height = $7; -} - -| location TOWARDS expr ':' '(' expr ',' expr ')' -{ - $$.ty = AREA::BAR; - $$.a.a_bar.loc = $1; - $$.a.a_bar.width = $6; - $$.a.a_bar.depth = $8; - $$.a.a_bar.dir = $3; -} - -; - - -spelldef - -: spellbody_list -{ - $$ = new_spell($1); -} - -| LET defs IN spellbody_list -{ - $$ = new_spell($4); - $$->letdefv = std::move(*$2); - $2.delete_(); - $$->spellguard = $4; -} - -; - - -defs - -: semicolons -{ - $$.new_(); -} - -| defs def semicolons -{ - $$ = $1; - $$->push_back($2); -} - -; - - -def - -: ID '=' expr -{ - if (find_constant($1.str())) - { - fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' as LET-bound variable.\n", $1.c_str()); - } - else - { - $$.id = intern_id($1); - $$.expr = $3; - } - $1.delete_(); -} - -; - - -spellbody_list - -: spellbody -{ - $$ = $1; -} - -| spellbody '|' spellbody_list -{ - dumb_ptr sg = new_spellguard(SPELLGUARD::CHOICE); - sg->next = $1; - sg->s.s_alt = $3; - $$ = sg; -} - -; - - -spellbody - -: spellguard DARROW spellbody -{ - $$ = spellguard_implication($1, $3); -} - -| '(' spellbody_list ')' -{ - $$ = $2; -} - -| EFFECT_ effect_list maybe_trigger maybe_end -{ - dumb_ptr sg = new_spellguard(SPELLGUARD::EFFECT); - sg->s.s_effect.effect = $2; - sg->s.s_effect.at_trigger = $3; - sg->s.s_effect.at_end = $4; - $$ = sg; -} - -; - - -maybe_trigger - -: /* empty */ -{ - $$ = NULL; -} - -| ATTRIGGER effect_list -{ - $$ = $2; -} - -; - - -maybe_end - -: /* empty */ -{ - $$ = NULL; -} - -| ATEND effect_list -{ - $$ = $2; -} - -; - - -spellguard - -: prereq -{ - $$ = $1; -} - -| spellguard OR spellguard -{ - dumb_ptr sg = new_spellguard(SPELLGUARD::CHOICE); - sg->next = $1; - sg->s.s_alt = $3; - $$ = sg; -} - -| '(' spellguard_list ')' -{ - $$ = $2; -} - -; - - -spellguard_list - -: spellguard -{ - $$ = $1; -} - -| spellguard ',' spellguard_list -{ - $$ = spellguard_implication($1, $3); -} - -; - - -prereq - -: REQUIRE expr -{ - $$ = new_spellguard(SPELLGUARD::CONDITION); - $$->s.s_condition = $2; -} - -| CATALYSTS items -{ - $$ = new_spellguard(SPELLGUARD::CATALYSTS); - $$->s.s_catalysts = $2; -} - -| COMPONENTS items -{ - $$ = new_spellguard(SPELLGUARD::COMPONENTS); - $$->s.s_components = $2; -} - -| MANA expr -{ - $$ = new_spellguard(SPELLGUARD::MANA); - $$->s.s_mana = $2; -} - -| CASTTIME expr -{ - $$ = new_spellguard(SPELLGUARD::CASTTIME); - $$->s.s_casttime = $2; -} - -; - - -items - -: '[' item_list ']' -{ - $$ = $2; -} - -; - - -item_list - -: item -{ - $$ = NULL; - magic_add_component(&$$, $1.id, $1.count); -} - -| item_list ',' item -{ - $$ = $1; - magic_add_component(&$$, $3.id, $3.count); -} - -; - - -item - -: INT '*' item_name -{ - $$.id = $3; - $$.count = $1; -} - -| item_name -{ - $$.id = $1; - $$.count = 1; -} - -; - - -item_name - -: STRING -{ - struct item_data *item = itemdb_searchname(stringish(ZString($1))); - if (!item) - { - fail(@1.first_line, @1.first_column, "Unknown item `%s'\n", $1.c_str()); - $$ = 0; - } - else - $$ = item->nameid; - $1.delete_(); -} - -| INT -{ - $$ = $1; -} - -; - - -selection - -: PC_F -{ - $$ = FOREACH_FILTER::PC; -} - -| MOB_F -{ - $$ = FOREACH_FILTER::MOB; -} - -| ENTITY_F -{ - $$ = FOREACH_FILTER::ENTITY; -} - -| SPELL -{ - $$ = FOREACH_FILTER::SPELL; -} - -| TARGET_F -{ - $$ = FOREACH_FILTER::TARGET; -} - -| NPC_F -{ - $$ = FOREACH_FILTER::NPC; -} - -; - - -effect - -: '(' effect_list ')' -{ - $$ = $2; -} - -| SKIP ';' -{ - $$ = new_effect(EFFECT::SKIP); -} - -| ABORT ';' -{ - $$ = new_effect(EFFECT::ABORT); -} - -| END ';' -{ - $$ = new_effect(EFFECT::END); -} - -| BREAK ';' -{ - $$ = new_effect(EFFECT::BREAK); -} - -| ID '=' expr ';' -{ - if (find_constant($1.str())) - { - fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' in assignment.", $1.c_str()); - } - else - { - $$ = new_effect(EFFECT::ASSIGN); - $$->e.e_assign.id = intern_id($1); - $$->e.e_assign.expr = $3; - } - $1.delete_(); -} - -| FOREACH selection ID IN expr DO effect -{ - $$ = new_effect(EFFECT::FOREACH); - $$->e.e_foreach.id = intern_id($3); - $3.delete_(); - $$->e.e_foreach.area = $5; - $$->e.e_foreach.body = $7; - $$->e.e_foreach.filter = $2; -} - -| FOR ID '=' expr TO expr DO effect -{ - $$ = new_effect(EFFECT::FOR); - $$->e.e_for.id = intern_id($2); - $2.delete_(); - $$->e.e_for.start = $4; - $$->e.e_for.stop = $6; - $$->e.e_for.body = $8; -} - -| IF expr THEN effect ELSE effect -{ - $$ = new_effect(EFFECT::IF); - $$->e.e_if.cond = $2; - $$->e.e_if.true_branch = $4; - $$->e.e_if.false_branch = $6; -} - -| IF expr THEN effect -{ - $$ = new_effect(EFFECT::IF); - $$->e.e_if.cond = $2; - $$->e.e_if.true_branch = $4; - $$->e.e_if.false_branch = new_effect(EFFECT::SKIP); -} - -| SLEEP expr ';' -{ - $$ = new_effect(EFFECT::SLEEP); - $$->e.e_sleep = $2; -} - -| ID '(' arg_list ')' ';' -{ - $$ = op_effect($1.str(), *$3.argvp, @1.first_line, @1.first_column); - $1.delete_(); -} - -| SCRIPT_DATA -{ - $$ = new_effect(EFFECT::SCRIPT); - $$->e.e_script = dumb_ptr(parse_script(ZString($1), @1.first_line).release()); - $1.delete_(); - if ($$->e.e_script == NULL) - fail(@1.first_line, @1.first_column, "Failed to compile script\n"); -} - -| CALL ID '(' arg_list ')' ';' -{ - $$ = call_proc($2, $4.argvp, @1.first_line, @1.first_column); - $2.delete_(); -} - -; - - -effect_list - -: /* empty */ -{ - $$ = new_effect(EFFECT::SKIP); -} - -| effect semicolons effect_list -{ - $$ = set_effect_continuation($1, $3); -} - -; - - -%% - -size_t intern_id(ZString id_name) -{ - size_t i; - for (i = 0; i < magic_conf.varv.size(); i++) - if (id_name == magic_conf.varv[i].name) - return i; - - // i = magic_conf.varv.size(); - /* Must add new */ - magic_conf_t::mcvar new_var {}; - new_var.name = id_name; - new_var.val.ty = TYPE::UNDEF; - magic_conf.varv.push_back(new_var); - - return i; -} - -void add_spell(dumb_ptr spell, int line_nr) -{ - auto pair1 = magic_conf.spells_by_name.insert({spell->name, spell}); - if (!pair1.second) - { - fail(line_nr, 0, "Attempt to redefine spell `%s'\n", spell->name.c_str()); - return; - } - - auto pair2 = magic_conf.spells_by_invocation.insert({spell->invocation, spell}); - if (!pair2.second) - { - fail(line_nr, 0, "Attempt to redefine spell invocation `%s' between spells `%s' and `%s'\n", - spell->invocation.c_str(), pair1.first->second->name.c_str(), spell->name.c_str()); - magic_conf.spells_by_name.erase(pair1.first); - return; - } -} - -void add_teleport_anchor(dumb_ptr anchor, int line_nr) -{ - auto pair1 = magic_conf.anchors_by_name.insert({anchor->name, anchor}); - if (!pair1.second) - { - fail(line_nr, 0, "Attempt to redefine teleport anchor `%s'\n", anchor->name.c_str()); - return; - } - - auto pair2 = magic_conf.anchors_by_invocation.insert({anchor->name, anchor}); - if (!pair2.second) - { - fail(line_nr, 0, "Attempt to redefine anchor invocation `%s' between anchors `%s' and `%s'\n", - anchor->invocation.c_str(), pair1.first->second->name.c_str(), anchor->name.c_str()); - magic_conf.anchors_by_name.erase(pair1.first); - return; - } -} - - -void fail(int line, int column, const char *fmt, ...) -{ - va_list ap; - FPRINTF(stderr, "[magic-init] L%d:%d: ", line, column); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - failed_flag = 1; -} - -dumb_ptr dot_expr(dumb_ptr expr, int id) -{ - dumb_ptr retval = magic_new_expr(EXPR::SPELLFIELD); - retval->e.e_field.id = id; - retval->e.e_field.expr = expr; - - return retval; -} - -dumb_ptr fun_expr(AString name, const_array> argv, int line, int column) -{ - dumb_ptr expr; - fun_t *fun = magic_get_fun(name); - - if (!fun) - fail(line, column, "Unknown function `%s'\n", name.c_str()); - else if (fun->signature.size() != argv.size()) - { - fail(line, column, "Incorrect number of arguments to function `%s': Expected %zu, found %zu\n", - name.c_str(), fun->signature.size(), argv.size()); - fun = NULL; - } - - if (fun) - { - expr = magic_new_expr(EXPR::FUNAPP); - expr->e.e_funapp.line_nr = line; - expr->e.e_funapp.column = column; - expr->e.e_funapp.funp = fun; - - assert (argv.size() <= MAX_ARGS); - expr->e.e_funapp.args_nr = argv.size(); - - std::copy(argv.begin(), argv.end(), expr->e.e_funapp.args); - } - else - { - /* failure */ - expr = magic_new_expr(EXPR::VAL); - expr->e.e_val.ty = TYPE::FAIL; - } - - return expr; -} - -dumb_ptr new_spell(dumb_ptr guard) -{ - auto retval = dumb_ptr::make(); - retval->spellguard = guard; - return retval; -} - -dumb_ptr new_spellguard(SPELLGUARD ty) -{ - dumb_ptr retval = dumb_ptr::make(); - retval->ty = ty; - return retval; -} - -dumb_ptr spellguard_implication(dumb_ptr a, dumb_ptr b) -{ - dumb_ptr retval = a; - - if (a == b) - { - /* This can happen due to reference sharing: - * e.g., - * (R0 -> (R1 | R2)) => (R3) - * yields - * (R0 -> (R1 -> R3 | R2 -> R3)) - * - * So if we now add => R4 to that, we want - * (R0 -> (R1 -> R3 -> R4 | R2 -> R3 -> R4)) - * - * but we only need to add it once, because the R3 reference is shared. - */ - return retval; - } - - /* If the premise is a disjunction, b is the continuation of _all_ branches */ - if (a->ty == SPELLGUARD::CHOICE) - spellguard_implication(a->s.s_alt, b); - if (a->next) - spellguard_implication(a->next, b); - else - a->next = b; - - return retval; -} - -dumb_ptr new_effect(EFFECT ty) -{ - auto effect = dumb_ptr::make(); - effect->ty = ty; - return effect; -} - -dumb_ptr set_effect_continuation(dumb_ptr src, dumb_ptr continuation) -{ - dumb_ptr retval = src; - /* This function is completely analogous to `spellguard_implication' above; read the control flow implications above first before pondering it. */ - - if (src == continuation) - return retval; - - /* For FOR and FOREACH, we use special stack handlers and thus don't have to set - * the continuation. It's only IF that we need to handle in this fashion. */ - if (src->ty == EFFECT::IF) - { - set_effect_continuation(src->e.e_if.true_branch, continuation); - set_effect_continuation(src->e.e_if.false_branch, continuation); - } - if (src->next) - set_effect_continuation(src->next, continuation); - else - src->next = continuation; - - return retval; -} - -dumb_ptr op_effect(AString name, const_array> argv, int line, int column) -{ - dumb_ptr effect; - op_t *op = magic_get_op(name); - - if (!op) - fail(line, column, "Unknown operation `%s'\n", name.c_str()); - else if (op->signature.size() != argv.size()) - { - fail(line, column, "Incorrect number of arguments to operation `%s': Expected %zu, found %zu\n", - name.c_str(), op->signature.size(), argv.size()); - op = NULL; - } - - if (op) - { - effect = new_effect(EFFECT::OP); - effect->e.e_op.line_nr = line; - effect->e.e_op.column = column; - effect->e.e_op.opp = op; - assert (argv.size() <= MAX_ARGS); - effect->e.e_op.args_nr = argv.size(); - - std::copy(argv.begin(), argv.end(), effect->e.e_op.args); - } - else /* failure */ - effect = new_effect(EFFECT::SKIP); - - return effect; -} - - -std::map procs; - -// I think this was a memory leak (or undefined behavior) -void install_proc(dumb_ptr proc) -{ - procs.insert({proc->name, std::move(*proc)}); -} - -dumb_ptr call_proc(ZString name, dumb_ptr>> argvp, int line_nr, int column) -{ - auto pi = procs.find(name); - if (pi == procs.end()) - { - fail(line_nr, column, "Unknown procedure `%s'\n", name.c_str()); - return new_effect(EFFECT::SKIP); - } - - proc_t *p = &pi->second; - - if (p->argv.size() != argvp->size()) - { - fail(line_nr, column, "Procedure %s/%zu invoked with %zu parameters\n", - name.c_str(), p->argv.size(), argvp->size()); - return new_effect(EFFECT::SKIP); - } - - dumb_ptr retval = new_effect(EFFECT::CALL); - retval->e.e_call.body = p->body; - retval->e.e_call.formalv = &p->argv; - retval->e.e_call.actualvp = argvp; - return retval; -} - -std::map const_defm; - -void bind_constant(RString name, val_t *val, int line_nr) -{ - if (!const_defm.insert({name, *val}).second) - { - fail(line_nr, 0, "Redefinition of constant `%s'\n", name.c_str()); - } -} - -val_t *find_constant(RString name) -{ - auto it = const_defm.find(name); - if (it != const_defm.end()) - return &it->second; - - return NULL; -} - - -static -int error_flag; - -inline -void INTERN_ASSERT(ZString name, int id) -{ - int zid = intern_id(name); - if (zid != id) - { - FPRINTF(stderr, - "[magic-conf] INTERNAL ERROR: Builtin special var %s interned to %d, not %d as it should be!\n", - name, zid, id); - error_flag = 1; - } -} - -extern FILE *magic_frontend_in; - -bool magic_init0() -{ - error_flag = 0; - - INTERN_ASSERT("min_casttime", VAR_MIN_CASTTIME); - INTERN_ASSERT("obscure_chance", VAR_OBSCURE_CHANCE); - INTERN_ASSERT("caster", VAR_CASTER); - INTERN_ASSERT("spellpower", VAR_SPELLPOWER); - INTERN_ASSERT("self_spell", VAR_SPELL); - INTERN_ASSERT("self_invocation", VAR_INVOCATION); - INTERN_ASSERT("target", VAR_TARGET); - INTERN_ASSERT("script_target", VAR_SCRIPTTARGET); - INTERN_ASSERT("location", VAR_LOCATION); - - return !error_flag; -} - -// must be called after itemdb initialisation -bool magic_init1(ZString conffile) -{ - current_magic_filename = conffile; - magic_frontend_in = fopen(conffile.c_str(), "r"); - if (!magic_frontend_in) - { - FPRINTF(stderr, "[magic-conf] Magic configuration file `%s' not found -> no magic.\n", conffile); - return false; - } - magic_frontend_parse(); - - PRINTF("[magic-conf] Magic initialised. %zu spells, %zu teleport anchors.\n", - magic_conf.spells_by_name.size(), magic_conf.anchors_by_name.size()); - - return !error_flag; -} - -extern int magic_frontend_lineno; - -void magic_frontend_error(const char *msg) -{ - FPRINTF(stderr, "[magic-conf] Parse error: %s at line %d\n", msg, magic_frontend_lineno); - failed_flag = 1; -} diff --git a/src/map/magic-interpreter.cpp b/src/map/magic-interpreter.cpp new file mode 100644 index 0000000..526d549 --- /dev/null +++ b/src/map/magic-interpreter.cpp @@ -0,0 +1,3 @@ +#include "magic-interpreter.hpp" + +#include "../poison.hpp" diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp index 6864844..c6abb81 100644 --- a/src/map/magic-interpreter.hpp +++ b/src/map/magic-interpreter.hpp @@ -445,9 +445,6 @@ struct proc_t {} }; -bool magic_init0(); -// must be called after itemdb initialisation -bool magic_init1(ZString filename); void spell_update_location(dumb_ptr invocation); #endif // TMWA_MAP_MAGIC_INTERPRETER_HPP diff --git a/src/map/magic-interpreter.py b/src/map/magic-interpreter.py new file mode 100644 index 0000000..8170f27 --- /dev/null +++ b/src/map/magic-interpreter.py @@ -0,0 +1,224 @@ +class area_t(object): + ''' print an area_t + ''' + __slots__ = ('_value') + name = 'area_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + yield 'size', v['size'] + ty = v['ty'] + yield 'ty', ty + a = v['a'] + if ty == 0: + yield 'a.a_loc', a['a_loc'] + elif ty == 1: + yield 'a.a_union', a['a_union'] + elif ty == 2: + yield 'a.a_rect', a['a_rect'] + elif ty == 3: + yield 'a.a_bar', a['a_bar'] + + +class val_t(object): + ''' print a val_t + ''' + __slots__ = ('_value') + name = 'val_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + ty = v['ty'] + yield 'ty', ty + u = v['v'] + if ty == 1: + yield 'v.v_int', u['v_int'] + elif ty == 2: + yield 'v.v_dir', u['v_dir'] + elif ty == 3: + yield 'v.v_string', u['v_string'] + elif ty == 4: + yield 'v.v_int', u['v_int'] + yield 'v.v_entity', u['v_entity'] + elif ty == 5: + yield 'v.v_location', u['v_location'] + elif ty == 6: + yield 'v.v_area', u['v_area'] + elif ty == 7: + yield 'v.v_spell', u['v_spell'] + elif ty == 8: + yield 'v.v_int', u['v_int'] + yield 'v.v_invocation', u['v_invocation'] + + +class e_area_t(object): + ''' print an e_area_t + ''' + __slots__ = ('_value') + name = 'e_area_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + ty = v['ty'] + yield 'ty', ty + a = v['a'] + if ty == 0: + yield 'a.a_loc', a['a_loc'] + elif ty == 1: + yield 'a.a_union', a['a_union'] + elif ty == 2: + yield 'a.a_rect', a['a_rect'] + elif ty == 3: + yield 'a.a_bar', a['a_bar'] + + +class expr_t(object): + ''' print an expr_t + ''' + __slots__ = ('_value') + name = 'expr_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + ty = v['ty'] + yield 'ty', ty + u = v['e'] + if ty == 0: + yield 'e.e_val', u['e_val'] + elif ty == 1: + yield 'e.e_location', u['e_location'] + elif ty == 2: + yield 'e.e_area', u['e_area'] + elif ty == 3: + yield 'e.e_funapp', u['e_funapp'] + elif ty == 4: + yield 'e.e_id', u['e_id'] + elif ty == 5: + yield 'e.e_field', u['e_field'] + + +class effect_t(object): + ''' print an effect_t + ''' + __slots__ = ('_value') + name = 'effect_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + yield 'next', v['next'] + ty = v['ty'] + yield 'ty', ty + u = v['e'] + if ty == 2: + yield 'e.e_assign', u['e_assign'] + elif ty == 3: + yield 'e.e_foreach', u['e_foreach'] + elif ty == 4: + yield 'e.e_for', u['e_for'] + elif ty == 5: + yield 'e.e_if', u['e_if'] + elif ty == 6: + yield 'e.e_sleep', u['e_sleep'] + elif ty == 7: + yield 'e.e_script', u['e_script'] + elif ty == 9: + yield 'e.e_op', u['e_op'] + elif ty == 11: + yield 'e.e_call', u['e_call'] + + +class spellguard_t(object): + ''' print a spellguard_t + ''' + __slots__ = ('_value') + name = 'spellguard_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + yield 'next', v['next'] + ty = v['ty'] + yield 'ty', ty + u = v['s'] + if ty == 0: + yield 's.s_condition', u['s_condition'] + elif ty == 1: + yield 's.s_components', u['s_components'] + elif ty == 2: + yield 's.s_catalysts', u['s_catalysts'] + elif ty == 3: + yield 's.s_alt', u['s_alt'] + elif ty == 4: + yield 's.s_mana', u['s_mana'] + elif ty == 5: + yield 's.s_casttime', u['s_casttime'] + elif ty == 6: + yield 's.s_effect', u['s_effect'] + + +class cont_activation_record_t(object): + ''' print a cont_activation_record_t + ''' + __slots__ = ('_value') + name = 'cont_activation_record_t' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + yield 'return_location', v['return_location'] + ty = v['ty'] + yield 'ty', ty + u = v['c'] + if ty == 0: + yield 'c.c_foreach', u['c_foreach'] + elif ty == 1: + yield 'c.c_for', u['c_for'] + elif ty == 2: + yield 'c.c_proc', u['c_proc'] diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp new file mode 100644 index 0000000..b10df3f --- /dev/null +++ b/src/map/magic-v2.cpp @@ -0,0 +1,1245 @@ +#include "magic-v2.hpp" +// magic-v2.cpp - second generation magic parser +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// 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 + +#include "../sexpr/parser.hpp" + +#include "../mmo/dumb_ptr.hpp" + +#include "itemdb.hpp" +#include "magic-expr.hpp" + +#include "../poison.hpp" + +namespace magic_v2 +{ + static + std::map procs; + static + std::map const_defm; + + static + size_t intern_id(ZString id_name) + { + // TODO use InternPool + size_t i; + for (i = 0; i < magic_conf.varv.size(); i++) + if (id_name == magic_conf.varv[i].name) + return i; + + // i = magic_conf.varv.size(); + /* Must add new */ + magic_conf_t::mcvar new_var {}; + new_var.name = id_name; + new_var.val.ty = TYPE::UNDEF; + magic_conf.varv.push_back(new_var); + + return i; + } + inline + bool INTERN_ASSERT(ZString name, int id) + { + int zid = intern_id(name); + if (zid != id) + { + FPRINTF(stderr, + "[magic-conf] INTERNAL ERROR: Builtin special var %s interned to %d, not %d as it should be!\n", + name, zid, id); + } + return zid == id; + } + + static + bool init0() + { + bool ok = true; + + ok &= INTERN_ASSERT("min_casttime", VAR_MIN_CASTTIME); + ok &= INTERN_ASSERT("obscure_chance", VAR_OBSCURE_CHANCE); + ok &= INTERN_ASSERT("caster", VAR_CASTER); + ok &= INTERN_ASSERT("spellpower", VAR_SPELLPOWER); + ok &= INTERN_ASSERT("self_spell", VAR_SPELL); + ok &= INTERN_ASSERT("self_invocation", VAR_INVOCATION); + ok &= INTERN_ASSERT("target", VAR_TARGET); + ok &= INTERN_ASSERT("script_target", VAR_SCRIPTTARGET); + ok &= INTERN_ASSERT("location", VAR_LOCATION); + + return ok; + } + + + static + bool bind_constant(io::LineSpan span, RString name, val_t *val) + { + if (!const_defm.insert({name, *val}).second) + { + span.error(STRPRINTF("Redefinition of constant '%s'", name)); + return false; + } + return true; + } + static + val_t *find_constant(RString name) + { + auto it = const_defm.find(name); + if (it != const_defm.end()) + return &it->second; + + return NULL; + } + static + dumb_ptr new_effect(EFFECT ty) + { + auto effect = dumb_ptr::make(); + effect->ty = ty; + return effect; + } + static + dumb_ptr set_effect_continuation(dumb_ptr src, dumb_ptr continuation) + { + dumb_ptr retval = src; + /* This function is completely analogous to `spellguard_implication' above; read the control flow implications above first before pondering it. */ + + if (src == continuation) + return retval; + + /* For FOR and FOREACH, we use special stack handlers and thus don't have to set + * the continuation. It's only IF that we need to handle in this fashion. */ + if (src->ty == EFFECT::IF) + { + set_effect_continuation(src->e.e_if.true_branch, continuation); + set_effect_continuation(src->e.e_if.false_branch, continuation); + } + + if (src->next) + set_effect_continuation(src->next, continuation); + else + src->next = continuation; + + return retval; + } + static + dumb_ptr new_spellguard(SPELLGUARD ty) + { + dumb_ptr retval = dumb_ptr::make(); + retval->ty = ty; + return retval; + } + static + dumb_ptr spellguard_implication(dumb_ptr a, dumb_ptr b) + { + dumb_ptr retval = a; + + if (a == b) + { + /* This can happen due to reference sharing: + * e.g., + * (R0 -> (R1 | R2)) => (R3) + * yields + * (R0 -> (R1 -> R3 | R2 -> R3)) + * + * So if we now add => R4 to that, we want + * (R0 -> (R1 -> R3 -> R4 | R2 -> R3 -> R4)) + * + * but we only need to add it once, because the R3 reference is shared. + */ + return retval; + } + + /* If the premise is a disjunction, b is the continuation of _all_ branches */ + if (a->ty == SPELLGUARD::CHOICE) + spellguard_implication(a->s.s_alt, b); + + if (a->next) + spellguard_implication(a->next, b); + else + // this is the important bit + a->next = b; + + return retval; + } + + + static + bool add_spell(io::LineSpan span, dumb_ptr spell) + { + auto pair1 = magic_conf.spells_by_name.insert({spell->name, spell}); + if (!pair1.second) + { + span.error(STRPRINTF("Attempt to redefine spell '%s'", spell->name)); + return false; + } + + auto pair2 = magic_conf.spells_by_invocation.insert({spell->invocation, spell}); + if (!pair2.second) + { + span.error(STRPRINTF("Attempt to redefine spell invocation '%s'", spell->invocation)); + magic_conf.spells_by_name.erase(pair1.first); + return false; + } + return true; + } + static + bool add_teleport_anchor(io::LineSpan span, dumb_ptr anchor) + { + auto pair1 = magic_conf.anchors_by_name.insert({anchor->name, anchor}); + if (!pair1.second) + { + span.error(STRPRINTF("Attempt to redefine teleport anchor '%s'", anchor->name)); + return false; + } + + auto pair2 = magic_conf.anchors_by_invocation.insert({anchor->name, anchor}); + if (!pair2.second) + { + span.error(STRPRINTF("Attempt to redefine anchor invocation '%s'", anchor->invocation)); + magic_conf.anchors_by_name.erase(pair1.first); + return false; + } + return true; + } + + static + bool install_proc(io::LineSpan span, dumb_ptr proc) + { + RString name = proc->name; + if (!procs.insert({name, std::move(*proc)}).second) + { + span.error("procedure already exists"); + return false; + } + return true; + } + static + bool call_proc(io::LineSpan span, ZString name, dumb_ptr>> argvp, dumb_ptr& retval) + { + auto pi = procs.find(name); + if (pi == procs.end()) + { + span.error(STRPRINTF("Unknown procedure '%s'", name)); + return false; + } + + proc_t *p = &pi->second; + + if (p->argv.size() != argvp->size()) + { + span.error(STRPRINTF("Procedure %s/%zu invoked with %zu parameters", + name, p->argv.size(), argvp->size())); + return false; + } + + retval = new_effect(EFFECT::CALL); + retval->e.e_call.body = p->body; + retval->e.e_call.formalv = &p->argv; + retval->e.e_call.actualvp = argvp; + return true; + } + static + bool op_effect(io::LineSpan span, ZString name, const_array> argv, dumb_ptr& effect) + { + op_t *op = magic_get_op(name); + if (!op) + { + span.error(STRPRINTF("Unknown operation '%s'", name)); + return false; + } + if (op->signature.size() != argv.size()) + { + span.error(STRPRINTF("Incorrect number of arguments to operation '%s': Expected %zu, found %zu", + name, op->signature.size(), argv.size())); + return false; + } + + effect = new_effect(EFFECT::OP); + effect->e.e_op.line_nr = span.begin.line; + effect->e.e_op.column = span.begin.column; + effect->e.e_op.opp = op; + assert (argv.size() <= MAX_ARGS); + effect->e.e_op.args_nr = argv.size(); + + std::copy(argv.begin(), argv.end(), effect->e.e_op.args); + return true; + } + + static + dumb_ptr dot_expr(dumb_ptr expr, int id) + { + dumb_ptr retval = magic_new_expr(EXPR::SPELLFIELD); + retval->e.e_field.id = id; + retval->e.e_field.expr = expr; + + return retval; + } + static + bool fun_expr(io::LineSpan span, ZString name, const_array> argv, dumb_ptr& expr) + { + fun_t *fun = magic_get_fun(name); + if (!fun) + { + span.error(STRPRINTF("Unknown function '%s'", name)); + return false; + } + if (fun->signature.size() != argv.size()) + { + span.error(STRPRINTF("Incorrect number of arguments to function '%s': Expected %zu, found %zu", + name, fun->signature.size(), argv.size())); + return false; + } + expr = magic_new_expr(EXPR::FUNAPP); + expr->e.e_funapp.line_nr = span.begin.line; + expr->e.e_funapp.column = span.begin.column; + expr->e.e_funapp.funp = fun; + + assert (argv.size() <= MAX_ARGS); + expr->e.e_funapp.args_nr = argv.size(); + + std::copy(argv.begin(), argv.end(), expr->e.e_funapp.args); + return true; + } + static + dumb_ptr BIN_EXPR(io::LineSpan span, ZString name, dumb_ptr left, dumb_ptr right) + { + dumb_ptr e[2]; + e[0] = left; + e[1] = right; + dumb_ptr rv; + if (!fun_expr(span, name, const_array>(e, 2), rv)) + abort(); + return rv; + } + + static + bool fail(const sexpr::SExpr& s, ZString msg) + { + s._span.error(msg); + return false; + } +} + +namespace magic_v2 +{ + using sexpr::SExpr; + + static + bool parse_expression(const SExpr& x, dumb_ptr& out); + static + bool parse_effect(const SExpr& s, dumb_ptr& out); + static + bool parse_spellguard(const SExpr& s, dumb_ptr& out); + static + bool parse_spellbody(const SExpr& s, dumb_ptr& out); + + // Note: anything with dumb_ptr leaks memory on failure + // once this is all done, we can convert it to unique_ptr + // (may require bimaps somewhere) + static + bool is_comment(const SExpr& s) + { + if (s._type == sexpr::STRING) + return true; + if (s._type != sexpr::LIST) + return false; + if (s._list.empty()) + return false; + if (s._list[0]._type != sexpr::TOKEN) + return false; + return s._list[0]._str == "DISABLED"; + } + + static + bool parse_loc(const SExpr& s, e_location_t& loc) + { + if (s._type != sexpr::LIST) + return fail(s, "loc not list"); + if (s._list.size() != 4) + return fail(s, "loc not 3 args"); + if (s._list[0]._type != sexpr::TOKEN) + return fail(s._list[0], "loc cmd not tok"); + if (s._list[0]._str != "@") + return fail(s._list[0], "loc cmd not cmd"); + return parse_expression(s._list[1], loc.m) + && parse_expression(s._list[2], loc.x) + && parse_expression(s._list[3], loc.y); + } + + static + bool parse_expression(const SExpr& x, dumb_ptr& out) + { + switch (x._type) + { + case sexpr::INT: + { + val_t val; + val.ty = TYPE::INT; + val.v.v_int = x._int; + if (val.v.v_int != x._int) + return fail(x, "integer too large"); + + out = magic_new_expr(EXPR::VAL); + out->e.e_val = val; + return true; + } + case sexpr::STRING: + { + val_t val; + val.ty = TYPE::STRING; + val.v.v_string = dumb_string::copys(x._str); + + out = magic_new_expr(EXPR::VAL); + out->e.e_val = val; + return true; + } + case sexpr::TOKEN: + { + ZString dirs[8] = { + "S", "SW", "W", "NW", "N", "NE", "E", "SE", + }; + auto begin = std::begin(dirs); + auto end = std::end(dirs); + auto it = std::find(begin, end, x._str); + if (it != end) + { + val_t val; + val.ty = TYPE::DIR; + val.v.v_dir = static_cast(it - begin); + + out = magic_new_expr(EXPR::VAL); + out->e.e_val = val; + return true; + } + } + { + if (val_t *val = find_constant(x._str)) + { + out = magic_new_expr(EXPR::VAL); + out->e.e_val = *val; + return true; + } + else + { + out = magic_new_expr(EXPR::ID); + out->e.e_id = intern_id(x._str); + return true; + } + } + break; + case sexpr::LIST: + if (x._list.empty()) + return fail(x, "empty list"); + { + if (x._list[0]._type != sexpr::TOKEN) + return fail(x._list[0], "op not token"); + ZString op = x._list[0]._str; + // area + if (op == "@") + { + e_location_t loc; + if (!parse_loc(x, loc)) + return false; + out = magic_new_expr(EXPR::AREA); + out->e.e_area.ty = AREA::LOCATION; + out->e.e_area.a.a_loc = loc; + return true; + } + if (op == "@+") + { + e_location_t loc; + dumb_ptr width; + dumb_ptr height; + if (!parse_loc(x._list[1], loc)) + return false; + if (!parse_expression(x._list[2], width)) + return false; + if (!parse_expression(x._list[3], height)) + return false; + out = magic_new_expr(EXPR::AREA); + out->e.e_area.ty = AREA::RECT; + out->e.e_area.a.a_rect.loc = loc; + out->e.e_area.a.a_rect.width = width; + out->e.e_area.a.a_rect.height = height; + return true; + } + if (op == "TOWARDS") + { + e_location_t loc; + dumb_ptr dir; + dumb_ptr width; + dumb_ptr depth; + if (!parse_loc(x._list[1], loc)) + return false; + if (!parse_expression(x._list[2], dir)) + return false; + if (!parse_expression(x._list[3], width)) + return false; + if (!parse_expression(x._list[4], depth)) + return false; + out = magic_new_expr(EXPR::AREA); + out->e.e_area.ty = AREA::BAR; + out->e.e_area.a.a_bar.loc = loc; + out->e.e_area.a.a_bar.dir = dir; + out->e.e_area.a.a_bar.width = width; + out->e.e_area.a.a_bar.depth = depth; + return true; + } + if (op == ".") + { + if (x._list.size() != 3) + return fail(x, ". not 2"); + dumb_ptr expr; + if (!parse_expression(x._list[1], expr)) + return false; + if (x._list[2]._type != sexpr::TOKEN) + return fail(x._list[2], ".elem not name"); + ZString elem = x._list[2]._str; + out = dot_expr(expr, intern_id(elem)); + return true; + } + static + std::set ops = + { + "<", ">", "<=", ">=", "==", "!=", + "+", "-", "*", "%", "/", + "&", "^", "|", "<<", ">>", + "&&", "||", + }; + // TODO implement unary operators + if (ops.count(op)) + { + // operators are n-ary and left-associative + if (x._list.size() < 3) + return fail(x, "operator not at least 2 args"); + auto begin = x._list.begin() + 1; + auto end = x._list.end(); + if (!parse_expression(*begin, out)) + return false; + ++begin; + for (; begin != end; ++begin) + { + dumb_ptr tmp; + if (!parse_expression(*begin, tmp)) + return false; + out = BIN_EXPR(x._span, op, out, tmp); + } + return true; + } + std::vector> argv; + for (auto it = x._list.begin() + 1, end = x._list.end(); it != end; ++it) + { + dumb_ptr expr; + if (!parse_expression(*it, expr)) + return false; + argv.push_back(expr); + } + return fun_expr(x._span, op, argv, out); + } + break; + } + abort(); + } + + static + bool parse_item(const SExpr& s, int& id, int& count) + { + if (s._type == sexpr::STRING) + { + count = 1; + + item_data *item = itemdb_searchname(s._str); + if (!item) + return fail(s, "no such item"); + id = item->nameid; + return true; + } + if (s._type != sexpr::LIST) + return fail(s, "item not string or list"); + if (s._list.size() != 2) + return fail(s, "item list is not pair"); + if (s._list[0]._type != sexpr::INT) + return fail(s._list[0], "item pair first not int"); + count = s._list[0]._int; + if (s._list[1]._type != sexpr::STRING) + return fail(s._list[1], "item pair second not name"); + + item_data *item = itemdb_searchname(s._list[1]._str); + if (!item) + return fail(s, "no such item"); + id = item->nameid; + return true; + } + + static + bool parse_spellguard(const SExpr& s, dumb_ptr& out) + { + if (s._type != sexpr::LIST) + return fail(s, "not list"); + if (s._list.empty()) + return fail(s, "empty list"); + if (s._list[0]._type != sexpr::TOKEN) + return fail(s._list[0], "not token"); + ZString cmd = s._list[0]._str; + if (cmd == "OR") + { + auto begin = s._list.begin() + 1; + auto end = s._list.end(); + if (begin == end) + return fail(s, "missing arguments"); + if (!parse_spellguard(*begin, out)) + return false; + ++begin; + for (; begin != end; ++begin) + { + dumb_ptr alt; + if (!parse_spellguard(*begin, alt)) + return false; + dumb_ptr choice = new_spellguard(SPELLGUARD::CHOICE); + choice->next = out; + choice->s.s_alt = alt; + out = choice; + } + return true; + } + if (cmd == "GUARD") + { + auto begin = s._list.begin() + 1; + auto end = s._list.end(); + while (is_comment(end[-1])) + --end; + if (begin == end) + return fail(s, "missing arguments"); + if (!parse_spellguard(end[-1], out)) + return false; + --end; + for (; begin != end; --end) + { + if (is_comment(end[-1])) + continue; + dumb_ptr implier; + if (!parse_spellguard(end[-1], implier)) + return false; + out = spellguard_implication(implier, out); + } + return true; + } + if (cmd == "REQUIRE") + { + if (s._list.size() != 2) + return fail(s, "not one argument"); + dumb_ptr condition; + if (!parse_expression(s._list[1], condition)) + return false; + out = new_spellguard(SPELLGUARD::CONDITION); + out->s.s_condition = condition; + return true; + } + if (cmd == "MANA") + { + if (s._list.size() != 2) + return fail(s, "not one argument"); + dumb_ptr mana; + if (!parse_expression(s._list[1], mana)) + return false; + out = new_spellguard(SPELLGUARD::MANA); + out->s.s_mana = mana; + return true; + } + if (cmd == "CASTTIME") + { + if (s._list.size() != 2) + return fail(s, "not one argument"); + dumb_ptr casttime; + if (!parse_expression(s._list[1], casttime)) + return false; + out = new_spellguard(SPELLGUARD::CASTTIME); + out->s.s_casttime = casttime; + return true; + } + if (cmd == "CATALYSTS") + { + dumb_ptr items = nullptr; + for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) + { + int id, count; + if (!parse_item(*it, id, count)) + return false; + magic_add_component(&items, id, count); + } + out = new_spellguard(SPELLGUARD::CATALYSTS); + out->s.s_catalysts = items; + return true; + } + if (cmd == "COMPONENTS") + { + dumb_ptr items = nullptr; + for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) + { + int id, count; + if (!parse_item(*it, id, count)) + return false; + magic_add_component(&items, id, count); + } + out = new_spellguard(SPELLGUARD::COMPONENTS); + out->s.s_components = items; + return true; + } + return fail(s._list[0], "unknown guard"); + } + + static + bool build_effect_list(std::vector::const_iterator begin, + std::vector::const_iterator end, dumb_ptr& out) + { + // these backward lists could be forward by keeping the reference + // I know this is true because Linus said so + out = new_effect(EFFECT::SKIP); + while (end != begin) + { + const SExpr& s = *--end; + if (is_comment(s)) + continue; + dumb_ptr chain; + if (!parse_effect(s, chain)) + return false; + out = set_effect_continuation(chain, out); + } + return true; + } + + static + bool parse_effect(const SExpr& s, dumb_ptr& out) + { + if (s._type != sexpr::LIST) + return fail(s, "not list"); + if (s._list.empty()) + return fail(s, "empty list"); + if (s._list[0]._type != sexpr::TOKEN) + return fail(s._list[0], "not token"); + ZString cmd = s._list[0]._str; + if (cmd == "BLOCK") + { + return build_effect_list(s._list.begin() + 1, s._list.end(), out); + } + if (cmd == "SET") + { + if (s._list.size() != 3) + return fail(s, "not 2 args"); + if (s._list[1]._type != sexpr::TOKEN) + return fail(s._list[1], "not token"); + ZString name = s._list[1]._str; + if (find_constant(name)) + return fail(s._list[1], "assigning to constant"); + dumb_ptr expr; + if (!parse_expression(s._list[2], expr)) + return false; + + out = new_effect(EFFECT::ASSIGN); + out->e.e_assign.id = intern_id(name); + out->e.e_assign.expr = expr; + return true; + } + if (cmd == "SCRIPT") + { + if (s._list.size() != 2) + return fail(s, "not 1 arg"); + if (s._list[1]._type != sexpr::STRING) + return fail(s._list[1], "not string"); + ZString body = s._list[1]._str; + std::unique_ptr script = parse_script(body, s._list[1]._span.begin.line); + if (!script) + return fail(s._list[1], "script does not compile"); + out = new_effect(EFFECT::SCRIPT); + out->e.e_script = dumb_ptr(script.release()); + return true; + } + if (cmd == "SKIP") + { + if (s._list.size() != 1) + return fail(s, "not 0 arg"); + out = new_effect(EFFECT::SKIP); + return true; + } + if (cmd == "ABORT") + { + if (s._list.size() != 1) + return fail(s, "not 0 arg"); + out = new_effect(EFFECT::ABORT); + return true; + } + if (cmd == "END") + { + if (s._list.size() != 1) + return fail(s, "not 0 arg"); + out = new_effect(EFFECT::END); + return true; + } + if (cmd == "BREAK") + { + if (s._list.size() != 1) + return fail(s, "not 0 arg"); + out = new_effect(EFFECT::BREAK); + return true; + } + if (cmd == "FOREACH") + { + if (s._list.size() != 5) + return fail(s, "not 4 arg"); + if (s._list[1]._type != sexpr::TOKEN) + return fail(s._list[1], "foreach type not token"); + ZString type = s._list[1]._str; + FOREACH_FILTER filter; + if (type == "PC") + filter = FOREACH_FILTER::PC; + else if (type == "MOB") + filter = FOREACH_FILTER::MOB; + else if (type == "ENTITY") + filter = FOREACH_FILTER::ENTITY; + else if (type == "SPELL") + filter = FOREACH_FILTER::SPELL; + else if (type == "TARGET") + filter = FOREACH_FILTER::TARGET; + else if (type == "NPC") + filter = FOREACH_FILTER::NPC; + else + return fail(s._list[1], "unknown foreach filter"); + if (s._list[2]._type != sexpr::TOKEN) + return fail(s._list[2], "foreach var not token"); + ZString var = s._list[2]._str; + dumb_ptr area; + dumb_ptr effect; + if (!parse_expression(s._list[3], area)) + return false; + if (!parse_effect(s._list[4], effect)) + return false; + out = new_effect(EFFECT::FOREACH); + out->e.e_foreach.id = intern_id(var); + out->e.e_foreach.area = area; + out->e.e_foreach.body = effect; + out->e.e_foreach.filter = filter; + return true; + } + if (cmd == "FOR") + { + if (s._list.size() != 5) + return fail(s, "not 4 arg"); + if (s._list[1]._type != sexpr::TOKEN) + return fail(s._list[1], "for var not token"); + ZString var = s._list[1]._str; + dumb_ptr low; + dumb_ptr high; + dumb_ptr effect; + if (!parse_expression(s._list[2], low)) + return false; + if (!parse_expression(s._list[3], high)) + return false; + if (!parse_effect(s._list[4], effect)) + return false; + out = new_effect(EFFECT::FOR); + out->e.e_for.id = intern_id(var); + out->e.e_for.start = low; + out->e.e_for.stop = high; + out->e.e_for.body = effect; + return true; + } + if (cmd == "IF") + { + if (s._list.size() != 3 && s._list.size() != 4) + return fail(s, "not 2 or 3 args"); + dumb_ptr cond; + dumb_ptr if_true; + dumb_ptr if_false; + if (!parse_expression(s._list[1], cond)) + return false; + if (!parse_effect(s._list[2], if_true)) + return false; + if (s._list.size() == 4) + { + if (!parse_effect(s._list[3], if_false)) + return false; + } + else + if_false = new_effect(EFFECT::SKIP); + out = new_effect(EFFECT::IF); + out->e.e_if.cond = cond; + out->e.e_if.true_branch = if_true; + out->e.e_if.false_branch = if_false; + return true; + } + if (cmd == "WAIT") + { + if (s._list.size() != 2) + return fail(s, "not 1 arg"); + dumb_ptr expr; + if (!parse_expression(s._list[1], expr)) + return false; + out = new_effect(EFFECT::SLEEP); + out->e.e_sleep = expr; + return true; + } + if (cmd == "CALL") + { + if (s._list.size() < 2) + return fail(s, "call what?"); + if (s._list[1]._type != sexpr::TOKEN) + return fail(s._list[1], "call token please"); + ZString func = s._list[1]._str; + auto argvp = dumb_ptr>>::make(); + for (auto it = s._list.begin() + 2, end = s._list.end(); it != end; ++it) + { + dumb_ptr expr; + if (!parse_expression(*it, expr)) + return false; + argvp->push_back(expr); + } + return call_proc(s._span, func, argvp, out); + } + auto argv = std::vector>(); + for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) + { + dumb_ptr expr; + if (!parse_expression(*it, expr)) + return false; + argv.push_back(expr); + } + return op_effect(s._span, cmd, argv, out); + } + + static + bool parse_spellbody(const SExpr& s, dumb_ptr& out) + { + if (s._type != sexpr::LIST) + return fail(s, "not list"); + if (s._list.empty()) + return fail(s, "empty list"); + if (s._list[0]._type != sexpr::TOKEN) + return fail(s._list[0], "not token"); + ZString cmd = s._list[0]._str; + if (cmd == "=>") + { + if (s._list.size() != 3) + return fail(s, "list does not have exactly 2 arguments"); + dumb_ptr guard; + if (!parse_spellguard(s._list[1], guard)) + return false; + dumb_ptr body; + if (!parse_spellbody(s._list[2], body)) + return false; + out = spellguard_implication(guard, body); + return true; + } + if (cmd == "|") + { + if (s._list.size() == 1) + return fail(s, "spellbody choice empty"); + auto begin = s._list.begin() + 1; + auto end = s._list.end(); + if (!parse_spellbody(*begin, out)) + return false; + ++begin; + for (; begin != end; ++begin) + { + dumb_ptr alt; + if (!parse_spellbody(*begin, alt)) + return false; + auto tmp = out; + out = new_spellguard(SPELLGUARD::CHOICE); + out->next = tmp; + out->s.s_alt = alt; + } + return true; + } + if (cmd == "EFFECT") + { + auto begin = s._list.begin() + 1; + auto end = s._list.end(); + + dumb_ptr effect, attrig, atend; + + // decreasing end can never pass begin, since we know that + // begin[-1] is token EFFECT + while (is_comment(end[-1])) + --end; + if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty() + && end[-1]._list[0]._type == sexpr::TOKEN + && end[-1]._list[0]._str == "ATEND") + { + auto atb = end[-1]._list.begin() + 1; + auto ate = end[-1]._list.end(); + if (!build_effect_list(atb, ate, atend)) + return false; + --end; + + while (is_comment(end[-1])) + --end; + } + else + { + atend = nullptr; + } + if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty() + && end[-1]._list[0]._type == sexpr::TOKEN + && end[-1]._list[0]._str == "ATTRIGGER") + { + auto atb = end[-1]._list.begin() + 1; + auto ate = end[-1]._list.end(); + if (!build_effect_list(atb, ate, attrig)) + return false; + --end; + } + else + { + attrig = nullptr; + } + if (!build_effect_list(begin, end, effect)) + return false; + out = new_spellguard(SPELLGUARD::EFFECT); + out->s.s_effect.effect = effect; + out->s.s_effect.at_trigger = attrig; + out->s.s_effect.at_end = atend; + return true; + } + return fail(s._list[0], "unknown spellbody"); + } + + static + bool parse_top_set(const std::vector& in) + { + if (in.size() != 3) + return fail(in[0], "not 2 arguments"); + ZString name = in[1]._str; + dumb_ptr expr; + if (!parse_expression(in[2], expr)) + return false; + if (find_constant(name)) + return fail(in[1], "assign constant"); + size_t var_id = intern_id(name); + magic_eval(dumb_ptr(&magic_default_env), &magic_conf.varv[var_id].val, expr); + return true; + } + static + bool parse_const(io::LineSpan span, const std::vector& in) + { + if (in.size() != 3) + return fail(in[0], "not 2 arguments"); + if (in[1]._type != sexpr::TOKEN) + return fail(in[1], "not token"); + ZString name = in[1]._str; + dumb_ptr expr; + if (!parse_expression(in[2], expr)) + return false; + val_t tmp; + magic_eval(dumb_ptr(&magic_default_env), &tmp, expr); + return bind_constant(span, name, &tmp); + } + static + bool parse_anchor(io::LineSpan span, const std::vector& in) + { + if (in.size() != 4) + return fail(in[0], "not 3 arguments"); + auto anchor = dumb_ptr::make(); + if (in[1]._type != sexpr::TOKEN) + return fail(in[1], "not token"); + anchor->name = in[1]._str; + if (in[2]._type != sexpr::STRING) + return fail(in[2], "not string"); + anchor->invocation = in[2]._str; + dumb_ptr expr; + if (!parse_expression(in[3], expr)) + return false; + anchor->location = expr; + return add_teleport_anchor(span, anchor); + } + static + bool parse_proc(io::LineSpan span, const std::vector& in) + { + if (in.size() < 4) + return fail(in[0], "not at least 3 arguments"); + auto proc = dumb_ptr::make(); + if (in[1]._type != sexpr::TOKEN) + return fail(in[1], "name not token"); + proc->name = in[1]._str; + if (in[2]._type != sexpr::LIST) + return fail(in[2], "args not list"); + for (const SExpr& arg : in[2]._list) + { + if (arg._type != sexpr::TOKEN) + return fail(arg, "arg not token"); + proc->argv.push_back(intern_id(arg._str)); + } + if (!build_effect_list(in.begin() + 3, in.end(), proc->body)) + return false; + return install_proc(span, proc); + } + static + bool parse_spell(io::LineSpan span, const std::vector& in) + { + if (in.size() < 6) + return fail(in[0], "not at least 5 arguments"); + if (in[1]._type != sexpr::LIST) + return fail(in[1], "flags not list"); + + auto spell = dumb_ptr::make(); + + for (const SExpr& s : in[1]._list) + { + if (s._type != sexpr::TOKEN) + return fail(s, "flag not token"); + SPELL_FLAG flag = SPELL_FLAG::ZERO; + if (s._str == "LOCAL") + flag = SPELL_FLAG::LOCAL; + else if (s._str == "NONMAGIC") + flag = SPELL_FLAG::NONMAGIC; + else if (s._str == "SILENT") + flag = SPELL_FLAG::SILENT; + else + return fail(s, "unknown flag"); + if (bool(spell->flags & flag)) + return fail(s, "duplicate flag"); + spell->flags |= flag; + } + if (in[2]._type != sexpr::TOKEN) + return fail(in[2], "name not token"); + spell->name = in[2]._str; + if (in[3]._type != sexpr::STRING) + return fail(in[3], "invoc not string"); + spell->invocation = in[3]._str; + if (in[4]._type != sexpr::LIST) + return fail(in[4], "spellarg not list"); + if (in[4]._list.size() == 0) + { + spell->spellarg_ty = SPELLARG::NONE; + } + else + { + if (in[4]._list.size() != 2) + return fail(in[4], "spellarg not empty list or pair"); + if (in[4]._list[0]._type != sexpr::TOKEN) + return fail(in[4]._list[0], "spellarg type not token"); + if (in[4]._list[1]._type != sexpr::TOKEN) + return fail(in[4]._list[1], "spellarg name not token"); + ZString ty = in[4]._list[0]._str; + if (ty == "PC") + spell->spellarg_ty = SPELLARG::PC; + else if (ty == "STRING") + spell->spellarg_ty = SPELLARG::STRING; + else + return fail(in[4]._list[0], "unknown spellarg type"); + ZString an = in[4]._list[1]._str; + spell->arg = intern_id(an); + } + std::vector::const_iterator it = in.begin() + 5; + for (;; ++it) + { + if (it == in.end()) + return fail(it[-1], "end of list scanning LET defs"); + if (is_comment(*it)) + continue; + if (it->_type != sexpr::LIST || it->_list.empty()) + break; + if (it->_list[0]._type != sexpr::TOKEN || it->_list[0]._str != "LET") + break; + + if (it->_list[1]._type != sexpr::TOKEN) + return fail(it->_list[1], "let name not token"); + ZString name = it->_list[1]._str; + if (find_constant(name)) + return fail(it->_list[1], "constant exists"); + dumb_ptr expr; + if (!parse_expression(it->_list[2], expr)) + return false; + letdef_t let; + let.id = intern_id(name); + let.expr = expr; + spell->letdefv.push_back(let); + } + if (it + 1 != in.end()) + return fail(*it, "expected only one body entry besides LET"); + + // formally, 'guard' only refers to the first argument of '=>' + // but internally, spellbodies use the same thing + dumb_ptr guard; + if (!parse_spellbody(*it, guard)) + return false; + spell->spellguard = guard; + return add_spell(span, spell); + } + + static + bool parse_top(io::LineSpan span, const std::vector& vs) + { + if (vs.empty()) + { + span.error("Empty list at top"); + return false; + } + if (vs[0]._type != sexpr::TOKEN) + return fail(vs[0], "top not token"); + ZString cmd = vs[0]._str; + if (cmd == "CONST") + return parse_const(span, vs); + if (cmd == "PROCEDURE") + return parse_proc(span, vs); + if (cmd == "SET") + return parse_top_set(vs); + if (cmd == "SPELL") + return parse_spell(span, vs); + if (cmd == "TELEPORT-ANCHOR") + return parse_anchor(span, vs); + return fail(vs[0], "Unknown top-level command"); + } + + static + bool loop(sexpr::Lexer& in) + { + SExpr s; + while (sexpr::parse(in, s)) + { + if (is_comment(s)) + continue; + if (s._type != sexpr::LIST) + return fail(s, "top-level entity not a list or comment"); + if (!parse_top(s._span, s._list)) + return false; + } + // handle low-level errors + if (in.peek() != sexpr::TOK_EOF) + { + in.span().error("parser gave up before end of file"); + return false; + } + return true; + } +} // namespace magic_v2 + +bool magic_init0() +{ + return magic_v2::init0(); +} + +bool load_magic_file_v2(ZString filename) +{ + sexpr::Lexer in(filename); + bool rv = magic_v2::loop(in); + if (!rv) + { + in.span().error(STRPRINTF("next token: %s '%s'", sexpr::token_name(in.peek()), in.val_string())); + } + return rv; +} diff --git a/src/map/magic-v2.hpp b/src/map/magic-v2.hpp new file mode 100644 index 0000000..d9140dc --- /dev/null +++ b/src/map/magic-v2.hpp @@ -0,0 +1,28 @@ +#ifndef TMWA_MAP_MAGIC_V2_HPP +#define TMWA_MAP_MAGIC_V2_HPP +// magic-v2.hpp - second generation magic parser +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// 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 "../strings/zstring.hpp" + +bool magic_init0(); +// must be called after itemdb initialization +bool load_magic_file_v2(ZString filename); + +#endif // TMWA_MAP_MAGIC_V2_HPP diff --git a/src/map/magic.cpp b/src/map/magic.cpp index 2e521b2..56705fe 100644 --- a/src/map/magic.cpp +++ b/src/map/magic.cpp @@ -10,8 +10,6 @@ #include "magic-expr.hpp" #include "magic-interpreter-base.hpp" -#include "magic-interpreter-lexer.hpp" -#include "src/map/magic-interpreter-parser.hpp" #include "magic-stmt.hpp" #include "magic.hpp" diff --git a/src/map/main.cpp b/src/map/main.cpp index f0e3517..de1ca3c 100644 --- a/src/map/main.cpp +++ b/src/map/main.cpp @@ -1,4 +1,6 @@ // dummy file to make Make dependencies work #include "map.hpp" +#include "magic-v2.hpp" + #include "../poison.hpp" diff --git a/src/map/map.cpp b/src/map/map.cpp index d9dd9cc..e5a3341 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -41,6 +41,7 @@ #include "itemdb.hpp" #include "magic.hpp" #include "magic-interpreter.hpp" +#include "magic-v2.hpp" #include "mob.hpp" #include "npc.hpp" #include "party.hpp" @@ -1654,7 +1655,7 @@ bool map_confs(XString key, ZString value) if (key == "skill_db") return skill_readdb(value); if (key == "magic_conf") - return magic_init1(value); + return load_magic_file_v2(value); if (key == "resnametable") return load_resnametable(value); diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 1e164a5..bfba45f 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -1060,7 +1060,7 @@ bool extract(XString xs, npc_item_list *itv) if (extract(name_or_id, &itv->nameid) && itv->nameid > 0) goto return_true; - id = itemdb_searchname(stringish(name_or_id.rstrip())); + id = itemdb_searchname(name_or_id.rstrip()); if (id == NULL) return false; itv->nameid = id->nameid; diff --git a/src/map/script.cpp b/src/map/script.cpp index fe42e44..9884aae 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -1832,7 +1832,7 @@ void builtin_countitem(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); if (item_data != NULL) nameid = item_data->nameid; @@ -1872,7 +1872,7 @@ void builtin_checkweight(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); if (item_data) nameid = item_data->nameid; @@ -1916,7 +1916,7 @@ void builtin_getitem(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); nameid = 727; //Default to iten if (item_data != NULL) @@ -1969,7 +1969,7 @@ void builtin_makeitem(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); nameid = 512; //Apple Item ID if (item_data) @@ -2015,7 +2015,7 @@ void builtin_delitem(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); //nameid=512; if (item_data) @@ -2960,7 +2960,7 @@ void builtin_getareadropitem(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); struct item_data *item_data = itemdb_searchname(name); item = 512; if (item_data) @@ -3376,7 +3376,7 @@ void builtin_getitemname(ScriptState *st) get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { - ItemName name = stringish(ZString(conv_str(st, data))); + ZString name = ZString(conv_str(st, data)); i_data = itemdb_searchname(name); } else diff --git a/src/sexpr/lexer.cpp b/src/sexpr/lexer.cpp new file mode 100644 index 0000000..8c1c380 --- /dev/null +++ b/src/sexpr/lexer.cpp @@ -0,0 +1,228 @@ +#include "lexer.hpp" +// lexer.cpp - tokenize a stream of S-expressions +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "../strings/mstring.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../poison.hpp" + +namespace sexpr +{ + Lexeme Lexer::_adv() + { + XString whitespace = " \t\n\r\v\f"; + while (true) + { + if (!_in.get(_span.begin)) + { + if (!_depth.empty()) + { + _depth.back().error("Unmatched '('"); + return TOK_ERROR; + } + return TOK_EOF; + } + char co = _span.begin.ch(); + if (!whitespace.contains(co)) + break; + _in.adv(); + } + + char co = _span.begin.ch(); + _in.adv(); + _span.end = _span.begin; + switch (co) + { + case '(': + _string = "("; + _depth.push_back(_span.end); + return TOK_OPEN; + case ')': + _string = ")"; + if (_depth.empty()) + { + _span.end.error("Unmatched ')'"); + return TOK_ERROR; + } + _depth.pop_back(); + return TOK_CLOSE; + case '"': + { + MString collect; + // read until " and consume it + // but handle \s + while (true) + { + if (!_in.get(_span.end)) + { + _span.error("EOF in string literal"); + return TOK_ERROR; + } + char ch = _span.end.ch(); + _in.adv(); + if (ch == '"') + break; + + if (ch != '\\') + { + collect += ch; + continue; + } + + if (!_in.get(_span.end)) + { + _span.end.error("EOF at backslash in string"); + return TOK_ERROR; + } + ch = _span.end.ch(); + _in.adv(); + switch (ch) + { + default: + _span.end.error("Unknown backslash sequence"); + return TOK_ERROR; + case 'a': collect += '\a'; break; + case 'b': collect += '\b'; break; + case 'e': collect += '\e'; break; + case 'f': collect += '\f'; break; + case 'n': collect += '\n'; break; + case 'r': collect += '\r'; break; + case 't': collect += '\t'; break; + case 'v': collect += '\v'; break; + case '\\': collect += '\\'; break; + case '\"': collect += '\"'; break; + case 'x': + { + unsigned char tmp = 0; + for (int i = 0; i < 2; ++i) + { + tmp *= 16; + if (!_in.get(_span.end)) + { + _span.end.error("EOF after \\x in string"); + return TOK_ERROR; + } + char cx = _span.end.ch(); + _in.adv(); + if ('0' <= cx && cx <= '9') + tmp += cx - '0'; + else if ('A' <= cx && cx <= 'F') + tmp += cx - 'A' + 10; + else if ('a' <= cx && cx <= 'a') + tmp += cx - 'a' + 10; + else + { + _span.end.error("Non-hex char after \\x"); + return TOK_ERROR; + } + } + collect += tmp; + } + } + } + _string = AString(collect); + return TOK_STRING; + } + case '\'': + case '\\': + _span.end.error("forbidden character"); + return TOK_ERROR; + default: + // this includes integers - they are differentiated in parsing + { + MString collect; + collect += co; + // read until whitespace, (, ), ", or EOF + io::LineChar tmp; + while (_in.get(tmp)) + { + char ct = tmp.ch(); + if (ct == '\'' || ct == '\\') + // error later + break; + if (ct == '(' || ct == ')' || ct == '"') + break; + if (whitespace.contains(ct)) + break; + collect += ct; + _span.end = tmp; + _in.adv(); + } + _string = AString(collect); + if (!_string.is_print()) + _span.error("String is not entirely printable"); + return TOK_TOKEN; + } + } + } + + VString<4> escape(char c) + { + switch (c) + { + case '\a': return {"\\a"}; + case '\b': return {"\\b"}; + case '\e': return {"\\e"}; + case '\f': return {"\\f"}; + //case '\n': return {"\\n"}; + case '\r': return {"\\r"}; + case '\t': return {"\\t"}; + case '\v': return {"\\v"}; + case '\\': return {"\\\\"}; + case '\"': return {"\\\""}; + default: + if (c == '\n') + return c; + if (' ' <= c && c <= '~') + return c; + else + return STRNPRINTF(5, "\\x%02x", static_cast(c)); + } + } + AString escape(XString s) + { + MString m; + m += '"'; + for (char c : s) + m += escape(c); + m += '"'; + return AString(m); + } + + ZString token_name(Lexeme tok) + { + switch (tok) + { + case TOK_EOF: + return ZString("EOF"); + case TOK_OPEN: + return ZString("OPEN"); + case TOK_CLOSE: + return ZString("CLOSE"); + case TOK_STRING: + return ZString("STRING"); + case TOK_TOKEN: + return ZString("TOKEN"); + default: + return ZString("ERROR"); + } + } +} // namespace sexpr diff --git a/src/sexpr/lexer.hpp b/src/sexpr/lexer.hpp new file mode 100644 index 0000000..7bce620 --- /dev/null +++ b/src/sexpr/lexer.hpp @@ -0,0 +1,73 @@ +#ifndef TMWA_SEXPR_LEXER_HPP +#define TMWA_SEXPR_LEXER_HPP +// lexer.hpp - tokenize a stream of S-expressions +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +# include "../sanity.hpp" + +# include "../strings/astring.hpp" +# include "../strings/vstring.hpp" +# include "../strings/xstring.hpp" +# include "../strings/zstring.hpp" + +# include "../io/line.hpp" + +namespace sexpr +{ + enum Lexeme + { + TOK_EOF = 0, + TOK_ERROR, + TOK_OPEN, + TOK_CLOSE, + TOK_STRING, + TOK_TOKEN, + }; + + class Lexer + { + io::LineCharReader _in; + Lexeme _current; + AString _string; + io::LineSpan _span; + std::vector _depth; + private: + Lexeme _adv(); + public: + explicit + Lexer(ZString filename) + : _in(filename), _current(TOK_EOF), _span(), _depth() + { adv(); } + // for unit tests + Lexer(ZString fake, io::FD fd) + : _in(fake, fd), _current(TOK_EOF), _span(), _depth() + { adv(); } + Lexeme peek() { return _current; } + void adv() { _current = _adv(); } + ZString val_string() { return _string; } + io::LineSpan span() { return _span; } + }; + + VString<4> escape(char c); + AString escape(XString s); + + ZString token_name(Lexeme tok); +} // namespace sexpr + +#endif // TMWA_SEXPR_LEXER_HPP diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp new file mode 100644 index 0000000..7a2dc09 --- /dev/null +++ b/src/sexpr/lexer_test.cpp @@ -0,0 +1,113 @@ +#include "lexer.hpp" + +#include + +static +io::FD string_pipe(ZString sz) +{ + io::FD rfd, wfd; + if (-1 == io::FD::pipe(rfd, wfd)) + return io::FD(); + if (sz.size() != wfd.write(sz.c_str(), sz.size())) + { + rfd.close(); + wfd.close(); + return io::FD(); + } + wfd.close(); + return rfd; +} + +TEST(sexpr, escape) +{ + EXPECT_EQ(sexpr::escape('\0'), "\\x00"); + EXPECT_EQ(sexpr::escape('\x1f'), "\\x1f"); + EXPECT_EQ(sexpr::escape('\x20'), " "); + EXPECT_EQ(sexpr::escape('\x7e'), "~"); + EXPECT_EQ(sexpr::escape('\x7f'), "\\x7f"); + EXPECT_EQ(sexpr::escape('\x80'), "\\x80"); + EXPECT_EQ(sexpr::escape('\xff'), "\\xff"); + EXPECT_EQ(sexpr::escape('\a'), "\\a"); + EXPECT_EQ(sexpr::escape('\b'), "\\b"); + EXPECT_EQ(sexpr::escape('\e'), "\\e"); + EXPECT_EQ(sexpr::escape('\f'), "\\f"); + //EXPECT_EQ(sexpr::escape('\n'), "\\n"); + EXPECT_EQ(sexpr::escape('\n'), "\n"); + EXPECT_EQ(sexpr::escape('\r'), "\\r"); + EXPECT_EQ(sexpr::escape('\t'), "\\t"); + EXPECT_EQ(sexpr::escape('\v'), "\\v"); + EXPECT_EQ(sexpr::escape('\\'), "\\\\"); + EXPECT_EQ(sexpr::escape('\"'), "\\\""); + + EXPECT_EQ(sexpr::escape("\x1f\x20\x7e\x7f\x80\xff\a\b\e\f\r\t\v\\\""), + "\"\\x1f ~\\x7f\\x80\\xff\\a\\b\\e\\f\\r\\t\\v\\\\\\\"\""); +} + +TEST(sexpr, lexer) +{ + io::LineSpan span; + sexpr::Lexer lexer("", string_pipe(" foo( ) 123\"\" \n")); + EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN); + EXPECT_EQ(lexer.val_string(), "foo"); + EXPECT_EQ(lexer.span().message_str("error", "test"), + ":1:2: error: test\n" + " foo( ) 123\"\" \n" + " ^~~\n" + ); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN); + EXPECT_EQ(lexer.span().message_str("error", "test"), + ":1:5: error: test\n" + " foo( ) 123\"\" \n" + " ^\n" + ); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_CLOSE); + EXPECT_EQ(lexer.span().message_str("error", "test"), + ":1:7: error: test\n" + " foo( ) 123\"\" \n" + " ^\n" + ); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN); + EXPECT_EQ(lexer.val_string(), "123"); + EXPECT_EQ(lexer.span().message_str("error", "test"), + ":1:9: error: test\n" + " foo( ) 123\"\" \n" + " ^~~\n" + ); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_STRING); + EXPECT_EQ(lexer.val_string(), ""); + EXPECT_EQ(lexer.span().message_str("error", "test"), + ":1:12: error: test\n" + " foo( ) 123\"\" \n" + " ^~\n" + ); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF); +} + +TEST(sexpr, lexbad) +{ + { + io::LineSpan span; + sexpr::Lexer lexer("", string_pipe("(\n")); + EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN); + lexer.adv(); + EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); + } + for (ZString bad : { + ZString(")\n"), + ZString("\"\n"), + ZString("'\n"), + ZString("\\\n"), + ZString("\"\\"), + ZString("\"\\z\""), + }) + { + io::LineSpan span; + sexpr::Lexer lexer("", string_pipe(bad)); + EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); + } +} diff --git a/src/sexpr/main.cpp b/src/sexpr/main.cpp new file mode 100644 index 0000000..7d63ddf --- /dev/null +++ b/src/sexpr/main.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include "../io/cxxstdio.hpp" + +#include "lexer.hpp" +#include "parser.hpp" + +#include "../poison.hpp" + +enum Spacing +{ + LINES, + SIMPLE, + SPACES, + SPACES_1, + SPACES_2, + SPACES_3, + SPACES_4, +}; + +static +void do_spacing(bool& first, Spacing& sp, int depth) +{ + if (first) + { + first = false; + return; + } + switch (sp) + { + case LINES: + PRINTF("\n%*s", (depth - 1) * 4, ""); + return; + case SPACES: + case SIMPLE: + PRINTF(" "); + return; + case SPACES_1: + PRINTF(" "); + sp = LINES; + return; + case SPACES_2: + PRINTF(" "); + sp = SPACES_1; + return; + case SPACES_3: + PRINTF(" "); + sp = SPACES_2; + return; + case SPACES_4: + PRINTF(" "); + sp = SPACES_3; + return; + } +} + +static +void adjust_spacing(Spacing& sp, ZString val) +{ + std::map spaces = + { + {"BLOCK", LINES}, + {"GUARD", LINES}, + {"DISABLED", LINES}, + {"PROCEDURE", SPACES_2}, + {"SPELL", SPACES_4}, + {"IF", SPACES_1}, + {"set_script_variable", SPACES_2}, + }; + auto it = spaces.find(val); + if (it != spaces.end()) + sp = it->second; +} + +int main() +{ + if (1 == 1) + { + sexpr::Lexer lexer("/dev/stdin"); + sexpr::SExpr sexpr; + while (sexpr::parse(lexer, sexpr)) + { + PRINTF(""); + } + if (lexer.peek() != sexpr::TOK_EOF) + { + lexer.span().error(STRPRINTF("Incomplete: %s: %s\n", + sexpr::token_name(lexer.peek()), lexer.val_string())); + } + return 0; + } + + std::stack spacing; + spacing.push(LINES); + sexpr::Lexer lexer("/dev/stdin"); + bool first = true; + while (sexpr::Lexeme tok = lexer.peek()) + { + switch (tok) + { + case sexpr::TOK_OPEN: + if (spacing.top() == SIMPLE) + spacing.top() = LINES; + do_spacing(first, spacing.top(), spacing.size()); + PRINTF("("); + spacing.push(SIMPLE); + first = true; + break; + case sexpr::TOK_CLOSE: + PRINTF(")"); + spacing.pop(); + first = false; + break; + case sexpr::TOK_STRING: + do_spacing(first, spacing.top(), spacing.size()); + PRINTF("%s", sexpr::escape(lexer.val_string())); + break; + case sexpr::TOK_TOKEN: + do_spacing(first, spacing.top(), spacing.size()); + PRINTF("%s", lexer.val_string()); + adjust_spacing(spacing.top(), lexer.val_string()); + break; + default: + abort(); + } + lexer.adv(); + } + PRINTF("\n"); +} diff --git a/src/sexpr/parser.cpp b/src/sexpr/parser.cpp new file mode 100644 index 0000000..2068565 --- /dev/null +++ b/src/sexpr/parser.cpp @@ -0,0 +1,79 @@ +#include "parser.hpp" +// parser.cpp - build a tree of S-expressions +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "../poison.hpp" + +namespace sexpr +{ + bool token_is_int(ZString s, int64_t& out, bool& ok) + { + if (!s) + return false; + if (s.startswith('-') || s.xslice_h(1).is_digit10()) + { + const char *z = s.c_str(); + char *end = nullptr; + errno = 0; + out = strtoll(z, &end, 0); + if (errno) + ok = false; + return !*end; + } + return false; + } + + bool parse(Lexer& lex, SExpr& out) + { + out._list.clear(); + out._str = RString(); + + bool rv = true; + out._span.begin = lex.span().begin; + switch (lex.peek()) + { + default: + return false; + case TOK_STRING: + out._type = STRING; + out._str = lex.val_string(); + break; + case TOK_TOKEN: + out._type = TOKEN; + out._str = lex.val_string(); + if (token_is_int(out._str, out._int, rv)) + out._type = INT; + break; + case TOK_OPEN: + out._type = LIST; + lex.adv(); + while (lex.peek() != TOK_CLOSE) + { + SExpr tmp; + if (!parse(lex, tmp)) + return false; + out._list.push_back(std::move(tmp)); + } + break; + } + out._span.end = lex.span().end; + lex.adv(); + return rv; + } +} // namespace sexpr diff --git a/src/sexpr/parser.hpp b/src/sexpr/parser.hpp new file mode 100644 index 0000000..6097f78 --- /dev/null +++ b/src/sexpr/parser.hpp @@ -0,0 +1,80 @@ +#ifndef TMWA_SEXPR_PARSER_HPP +#define TMWA_SEXPR_PARSER_HPP +// parser.hpp - build a tree of S-expressions +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +# include "../sanity.hpp" + +# include "../strings/zstring.hpp" + +# include "../io/line.hpp" + +# include "lexer.hpp" + +namespace sexpr +{ + enum Type + { + LIST, + INT, + STRING, + TOKEN, + }; + + struct SExpr + { + Type _type; + int64_t _int; + RString _str; + std::vector _list; + + io::LineSpan _span; + + SExpr() : _type(), _int(), _str(), _list(), _span() {} + }; + + inline + bool operator == (const SExpr& l, const SExpr& r) + { + if (l._type != r._type) + return false; + switch (l._type) + { + case LIST: + return l._list == r._list; + case INT: + return l._int == r._int; + case STRING: + case TOKEN: + return l._str == r._str; + } + abort(); + } + inline + bool operator != (const SExpr& l, const SExpr& r) + { + return !(l == r); + } + + bool token_is_int(ZString s, int64_t& out, bool& ok); + /// return false on error or eof, check lex.peek() == TOK_EOF to see + bool parse(Lexer& lex, SExpr& out); +} // namespace sexpr + +#endif // TMWA_SEXPR_PARSER_HPP diff --git a/src/sexpr/parser.py b/src/sexpr/parser.py new file mode 100644 index 0000000..d638259 --- /dev/null +++ b/src/sexpr/parser.py @@ -0,0 +1,25 @@ +class SExpr(object): + ''' print a SExpr + ''' + __slots__ = ('_value') + name = 'sexpr::SExpr' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return None + + def children(self): + v = self._value + t = v['_type'] + if t == 0: + yield '(list)', v['_list'] + if t == 1: + yield '(int)', v['_int'] + if t == 2: + yield '(str)', v['_str'] + if t == 3: + yield '(token)', v['_str'] + yield '_span', v['_span'] diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp new file mode 100644 index 0000000..a752313 --- /dev/null +++ b/src/sexpr/parser_test.cpp @@ -0,0 +1,89 @@ +#include "parser.hpp" + +#include + +static +io::FD string_pipe(ZString sz) +{ + io::FD rfd, wfd; + if (-1 == io::FD::pipe(rfd, wfd)) + return io::FD(); + if (sz.size() != wfd.write(sz.c_str(), sz.size())) + { + rfd.close(); + wfd.close(); + return io::FD(); + } + wfd.close(); + return rfd; +} + +TEST(sexpr, parser) +{ + sexpr::SExpr s; + io::LineSpan span; + sexpr::Lexer lexer("", string_pipe(" foo( ) 123\"\" \n")); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::TOKEN); + EXPECT_EQ(s._str, "foo"); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::LIST); + EXPECT_EQ(s._list, std::vector()); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::INT); + EXPECT_EQ(s._int, 123); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::STRING); + EXPECT_EQ(s._str, ""); + + EXPECT_FALSE(sexpr::parse(lexer, s)); + EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF); +} + +TEST(sexpr, parselist) +{ + sexpr::SExpr s; + sexpr::Lexer lexer("", string_pipe("(foo)(bar)\n")); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::LIST); + EXPECT_EQ(s._list.size(), 1); + EXPECT_EQ(s._list[0]._type, sexpr::TOKEN); + EXPECT_EQ(s._list[0]._str, "foo"); + + EXPECT_TRUE(sexpr::parse(lexer, s)); + EXPECT_EQ(s._type, sexpr::LIST); + EXPECT_EQ(s._list.size(), 1); + EXPECT_EQ(s._list[0]._type, sexpr::TOKEN); + EXPECT_EQ(s._list[0]._str, "bar"); + + EXPECT_FALSE(sexpr::parse(lexer, s)); + EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF); +} + +TEST(sexpr, parsebad) +{ + for (ZString bad : { + ZString("(\n"), + ZString(")\n"), + ZString("\"\n"), + ZString("'\n"), + ZString("\\\n"), + ZString("\"\\"), + ZString("\"\\z\""), + ZString("(()\n"), + ZString("((\n"), + ZString("((\"\n"), + }) + { + sexpr::SExpr s; + io::LineSpan span; + sexpr::Lexer lexer("", string_pipe(bad)); + EXPECT_FALSE(sexpr::parse(lexer, s)); + EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); + } +} diff --git a/src/spell-convert/ast.cpp b/src/spell-convert/ast.cpp new file mode 100644 index 0000000..eb66752 --- /dev/null +++ b/src/spell-convert/ast.cpp @@ -0,0 +1,260 @@ +#include "ast.hpp" + +#include "../io/cxxstdio.hpp" + +void Constant::dump() +{ + PRINTF("(CONST %s\n", name); + body->show(); + PRINTF(")\n"); +} +void Teleport::dump() +{ + PRINTF("(TELEPORT-ANCHOR %s\n", name); + ident->show(); + body->show(); + PRINTF(")\n"); +} +void Procedure::dump() +{ + PRINTF("(PROCEDURE %s\n", name); + PRINTF("("); + for (RString& a : *args) + PRINTF(" %s ", a); + PRINTF(")\n"); + for (Effect *f : *body) + { + f->print(); + } + PRINTF(")\n"); +} +void Spell::dump() +{ + PRINTF("(SPELL \n"); + PRINTF("("); + for (RString fl : *flags) + PRINTF(" %s ", fl); + PRINTF(")"); + PRINTF("%s", name); + ident->show(); + PRINTF("( %s %s )", arg->vartype, arg->varname); + for (Assignment *a : *body->lets) + { + PRINTF("(LET %s ", a->name); + a->body->show(); + PRINTF(")\n"); + } + for (SpellBod *b : *body->body) + { + b->say(); + } + PRINTF(")\n"); +} +void Assignment::dump() +{ + PRINTF("(SET %s\n", name); + body->show(); + PRINTF(")\n"); +} + +void EffectList::print() +{ + PRINTF("(BLOCK\n"); + for (Effect *ef : *body) + ef->print(); + PRINTF(")\n"); +} +void SimpleEffect::print() +{ + PRINTF("( %s )", text); +} +void ScriptEffect::print() +{ + PRINTF("(SCRIPT %s )", text); +} +void Assignment::print() +{ + PRINTF("(SET %s\n", name); + body->show(); + PRINTF(")\n"); +} +void ForeachEffect::print() +{ + PRINTF("(FOREACH %s %s ", selection, var); + expr->show(); + PRINTF("\n"); + effect->print(); + PRINTF(")"); +} +void ForEffect::print() +{ + PRINTF("(FOR %s", var); + low->show(); + high->show(); + effect->print(); + PRINTF(")"); +} +void IfEffect::print() +{ + PRINTF("(IF "); + cond->show(); + if_true->print(); + if (if_false_maybe) + if_false_maybe->print(); + PRINTF(")"); +} +void ExplicitCallEffect::print() +{ + PRINTF("(CALL %s ", userfunc); + for (Expression *x : *args) + x->show(); + PRINTF(")"); +} +void SleepEffect::print() +{ + PRINTF("(WAIT "); + time->show(); + PRINTF(")"); +} +void CallExpr::print() +{ + PRINTF("(%s ", func); + for (Expression *x : *args) + x->show(); + PRINTF(")"); +} + +void SimpleExpr::show() +{ + PRINTF(" %s ", content); +} +void BinExpr::show() +{ + PRINTF("(%s ", op); + left->show(); + right->show(); + PRINTF(")"); +} +void CallExpr::show() +{ + PRINTF("(%s ", func); + for (Expression *x : *args) + x->show(); + PRINTF(")"); +} +void AreaLoc::show() +{ + PRINTF("(@ "); + loc->map->show(); + loc->x->show(); + loc->y->show(); + PRINTF(")"); +} +void AreaRect::show() +{ + PRINTF("(@+ "); + AreaLoc{loc}.show(); + width->show(); + height->show(); + PRINTF(")"); +} +void AreaBar::show() +{ + PRINTF("(TOWARD "); + AreaLoc{loc}.show(); + dir->show(); + width->show(); + depth->show(); + PRINTF(")"); +} + +void SpellBodGuarded::say() +{ + PRINTF("(=> "); + guard->declare(); + body->say(); + PRINTF(")"); +} +void SpellBodList::say() +{ + PRINTF("(|\n"); + for (SpellBod *b : *body) + b->say(); + PRINTF(")"); +} +void SpellBodEffect::say() +{ + PRINTF("(EFFECT\n"); + for (Effect *f : *body) + f->print(); + if (maybe_trigger) + { + PRINTF("(ATTRIGGER\n"); + for (Effect *f : *maybe_trigger) + f->print(); + PRINTF(")"); + } + if (maybe_end) + { + PRINTF("(ATEND\n"); + for (Effect *f : *maybe_end) + f->print(); + PRINTF(")"); + } + PRINTF(")"); +} + +void SpellGuardOr::declare() +{ + PRINTF("(OR\n"); + for (SpellGuard *sg : *any) + sg->declare(); + PRINTF(")"); +} +void SpellGuardList::declare() +{ + PRINTF("(GUARD\n"); + for (SpellGuard *sg : *all) + sg->declare(); + PRINTF(")"); +} +void SpellGuardRequire::declare() +{ + PRINTF("(REQUIRE "); + expr->show(); + PRINTF(")"); +} +static +void do_item(Item *itm) +{ + if (itm->count) + PRINTF("( %s %s )", itm->count, itm->item); + else + PRINTF(" %s ", itm->item); +} +void SpellGuardCatalysts::declare() +{ + PRINTF("(CATALYSTS "); + for (Item *itm : *items) + do_item(itm); + PRINTF(")"); +} +void SpellGuardComponents::declare() +{ + PRINTF("(COMPONENTS "); + for (Item *itm : *items) + do_item(itm); + PRINTF(")"); +} +void SpellGuardMana::declare() +{ + PRINTF("(MANA "); + sp->show(); + PRINTF(")"); +} +void SpellGuardCasttime::declare() +{ + PRINTF("(CASTTIME "); + time->show(); + PRINTF(")"); +} diff --git a/src/spell-convert/ast.hpp b/src/spell-convert/ast.hpp new file mode 100644 index 0000000..e5319fc --- /dev/null +++ b/src/spell-convert/ast.hpp @@ -0,0 +1,432 @@ +#ifndef AST_HPP +#define AST_HPP + +# include +# include + +# include "../strings/rstring.hpp" + +// We just leak +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +struct TopLevel; +struct Constant; +struct Teleport; +struct Procedure; +struct Spell; +struct SpellArg; +struct Effect; +struct EffectList; +struct SimpleEffect; +struct ScriptEffect; +struct Assignment; +struct ForeachEffect; +struct ForEffect; +struct IfEffect; +struct ExplicitCallEffect; +struct SleepEffect; +struct SpellDef; +struct SpellBod; +struct SpellBodGuarded; +struct SpellBodList; +struct SpellBodEffect; +struct SpellGuard; +struct SpellGuardOr; +struct SpellGuardList; +struct SpellGuardRequire; +struct SpellGuardCatalysts; +struct SpellGuardComponents; +struct SpellGuardMana; +struct SpellGuardCasttime; +struct Item; +struct Expression; +struct SimpleExpr; +struct BinExpr; +struct CallExpr; +struct Location; +struct AreaLoc; +struct AreaRect; +struct AreaBar; + + +struct TopLevel +{ + virtual void dump() = 0; +}; + +struct Constant : TopLevel +{ + RString name; + Expression *body; + + Constant(RString n, Expression *b) + : name(n), body(b) + {} + + virtual void dump() override; +}; + +struct Teleport : TopLevel +{ + RString name; + Expression *ident; + Expression *body; + + Teleport(RString n, Expression *i, Expression *b) + : name(n), ident(i), body(b) + {} + + virtual void dump() override; +}; + +struct Procedure : TopLevel +{ + RString name; + std::vector *args; + std::deque *body; + + Procedure(RString n, std::vector *a, std::deque *b) + : name(n), args(a), body(b) + {} + + virtual void dump() override; +}; + +struct Spell : TopLevel +{ + std::vector *flags; + RString name; + SpellArg *arg; + Expression *ident; + SpellDef *body; + + Spell(std::vector *f, RString n, SpellArg *a, Expression *i, SpellDef *b) + : flags(f), name(n), arg(a), ident(i), body(b) + {} + + virtual void dump() override; +}; + +struct SpellArg +{ + RString varname; + RString vartype; +}; + +struct Effect +{ + virtual void print() = 0; +}; + +struct EffectList : Effect +{ + std::deque *body; + + EffectList(std::deque *b) + : body(b) + {} + + virtual void print() override; +}; +struct SimpleEffect : Effect +{ + RString text; + + SimpleEffect(RString t) : text(t) {} + + virtual void print() override; +}; +struct ScriptEffect : Effect +{ + RString text; + + ScriptEffect(RString t) : text(t) {} + + virtual void print() override; +}; + +struct Assignment : TopLevel, Effect +{ + RString name; + Expression *body; + + Assignment(RString n, Expression *b) + : name(n), body(b) + {} + + // toplevel + virtual void dump() override; + // effect + virtual void print() override; +}; + +struct ForeachEffect : Effect +{ + RString selection; + RString var; + Expression *expr; + Effect *effect; + + ForeachEffect(RString s, RString v, Expression *x, Effect *f) + : selection(s), var(v), expr(x), effect(f) + {} + + virtual void print() override; +}; + +struct ForEffect : Effect +{ + RString var; + Expression *low; + Expression *high; + Effect *effect; + + ForEffect(RString v, Expression *l, Expression *h, Effect *f) + : var(v), low(l), high(h), effect(f) + {} + + virtual void print() override; +}; + +struct IfEffect : Effect +{ + Expression *cond; + Effect *if_true; + Effect *if_false_maybe; + + IfEffect(Expression *c, Effect *t, Effect *f=nullptr) + : cond(c), if_true(t), if_false_maybe(f) + {} + + virtual void print() override; +}; + +struct ExplicitCallEffect : Effect +{ + RString userfunc; + std::vector *args; + + ExplicitCallEffect(RString f, std::vector *a) + : userfunc(f), args(a) + {} + + virtual void print() override; +}; + +struct SleepEffect : Effect +{ + Expression *time; + + SleepEffect(Expression *t) + : time(t) + {} + + virtual void print() override; +}; + +struct SpellDef +{ + std::vector *lets; + std::vector *body; +}; + +struct SpellBod +{ + virtual void say() = 0; +}; + +struct SpellBodGuarded : SpellBod +{ + SpellGuard *guard; + SpellBod *body; + + SpellBodGuarded(SpellGuard *g, SpellBod *b) + : guard(g), body(b) + {} + + virtual void say() override; +}; + +struct SpellBodList : SpellBod +{ + std::vector *body; + + SpellBodList(std::vector *b) + : body(b) + {} + + virtual void say() override; +}; + +struct SpellBodEffect : SpellBod +{ + std::deque *body; + std::deque *maybe_trigger; + std::deque *maybe_end; + + SpellBodEffect(std::deque *b, std::deque *t, std::deque *e) + : body(b), maybe_trigger(t), maybe_end(e) + {} + + virtual void say() override; +}; + +struct SpellGuard +{ + virtual void declare() = 0; +}; + +struct SpellGuardOr : SpellGuard +{ + std::vector *any; + + SpellGuardOr(std::vector *a) : any(a) {} + SpellGuardOr(SpellGuard *left, SpellGuard *right) + : any(new std::vector({left, right})) + {} + + virtual void declare() override; +}; +struct SpellGuardList : SpellGuard +{ + std::vector *all; + + SpellGuardList(std::vector *a) : all(a) {} + + virtual void declare() override; +}; +struct SpellGuardRequire : SpellGuard +{ + Expression *expr; + + SpellGuardRequire(Expression *x) : expr(x) {} + + virtual void declare() override; +}; +struct SpellGuardCatalysts : SpellGuard +{ + std::vector *items; + + SpellGuardCatalysts(std::vector *i) : items(i) {} + + virtual void declare() override; +}; +struct SpellGuardComponents : SpellGuard +{ + std::vector *items; + + SpellGuardComponents(std::vector *i) : items(i) {} + + virtual void declare() override; +}; +struct SpellGuardMana : SpellGuard +{ + Expression *sp; + + SpellGuardMana(Expression *x) : sp(x) {} + + virtual void declare() override; +}; +struct SpellGuardCasttime : SpellGuard +{ + Expression *time; + + SpellGuardCasttime(Expression *x) : time(x) {} + + virtual void declare() override; +}; + +struct Item +{ + RString count; + RString item; +}; + +struct Expression +{ + virtual void show() = 0; +}; + +struct SimpleExpr : Expression +{ + RString content; + + SimpleExpr(RString c) : content(c) {} + + virtual void show() override; +}; + +struct BinExpr : Expression +{ + Expression *left; + RString op; + Expression *right; + + BinExpr(Expression *l, RString o, Expression *r) + : left(l), op(o), right(r) + {} + + virtual void show() override; +}; + +struct CallExpr : Expression, Effect +{ + RString func; + std::vector *args; + + CallExpr(RString f, std::vector *a) + : func(f), args(a) + {} + + // expression + virtual void show() override; + // effect + virtual void print() override; +}; + +struct Location +{ + Expression *map; + Expression *x; + Expression *y; +}; + +struct AreaLoc : Expression +{ + Location *loc; + + AreaLoc(Location *l) + : loc(l) + {} + + virtual void show() override; +}; + +struct AreaRect : Expression +{ + Location *loc; + Expression *width; + Expression *height; + + AreaRect(Location *l, Expression *w, Expression *h) + : loc(l), width(w), height(h) + {} + + virtual void show() override; +}; + +struct AreaBar : Expression +{ + Location *loc; + Expression *dir; + Expression *width; + Expression *depth; + + AreaBar(Location *l, Expression *a, Expression *w, Expression *d) + : loc(l), dir(a), width(w), depth(d) + {} + + virtual void show() override; +}; + +#endif // AST_HPP diff --git a/src/spell-convert/lexer.lpp b/src/spell-convert/lexer.lpp new file mode 100644 index 0000000..92acf48 --- /dev/null +++ b/src/spell-convert/lexer.lpp @@ -0,0 +1,117 @@ +%{ +/* vim: set ft=lex: */ +//#include "lexer.hpp" + +#include "../strings/rstring.hpp" +#include "../strings/zstring.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../sexpr/lexer.hpp" + +#include "parser.hpp" + +#define yylval spell_converterlval + +RString *str(const char *s) +{ + return new RString(ZString(strings::really_construct_from_a_pointer, s, nullptr)); +} +%} + +%option noyywrap +%option prefix="spell_converter" +%option nounput +%option noinput + +%% + +"S" { yylval.s = str(yytext); return DIR; } +"SW" { yylval.s = str(yytext); return DIR; } +"W" { yylval.s = str(yytext); return DIR; } +"NW" { yylval.s = str(yytext); return DIR; } +"N" { yylval.s = str(yytext); return DIR; } +"NE" { yylval.s = str(yytext); return DIR; } +"E" { yylval.s = str(yytext); return DIR; } +"SE" { yylval.s = str(yytext); return DIR; } +"=" { return '='; } +"==" { return EQ; } +"<>" { return NEQ; } +"!=" { return NEQ; } +">" { return '>'; } +"<" { return '<'; } +">=" { return GTE; } +"<=" { return LTE; } +"(" { return '('; } +")" { return ')'; } +"+" { return '+'; } +"-" { return '-'; } +"*" { return '*'; } +"/" { return '/'; } +"%" { return '%'; } +"&&" { return ANDAND; } +"||" { return OROR; } +";" { return ';'; } +":" { return ':'; } +"," { return ','; } +"@" { return '@'; } +"|" { return '|'; } +"[" { return '['; } +"]" { return ']'; } +"&" { return '&'; } +"^" { return '^'; } +"." { return '.'; } +"<<" { return SHL; } +">>" { return SHR; } +"PROCEDURE" { return PROCEDURE; } +"CALL" { return CALL; } +"OR" { return OR; } +"TO" { return TO; } +"TOWARDS" { return TOWARDS; } +"TELEPORT-ANCHOR" { return TELEPORT_ANCHOR; } +"SILENT" { return SILENT; } +"LOCAL" { return LOCAL; } +"NONMAGIC" { return NONMAGIC; } +"SPELL" { return SPELL; } +"LET" { return LET; } +"IN" { return IN; } +"END" { return END; } +"=>" { return DARROW; } +"STRING" { return STRING_TY; } +"REQUIRE" { return REQUIRE; } +"CATALYSTS" { return CATALYSTS; } +"COMPONENTS" { return COMPONENTS; } +"MANA" { return MANA; } +"CASTTIME" { return CASTTIME; } +"SKIP" { return SKIP; } +"ABORT" { return ABORT; } +"BREAK" { return BREAK; } +"EFFECT" { return EFFECT_; } +"ATEND" { return ATEND; } +"ATTRIGGER" { return ATTRIGGER; } +"CONST" { return CONST; } +"PC" { return PC_F; } +"NPC" { return NPC_F; } +"MOB" { return MOB_F; } +"ENTITY" { return ENTITY_F; } +"TARGET" { return TARGET_F; } +"IF" { return IF; } +"THEN" { return THEN; } +"ELSE" { return ELSE; } +"FOREACH" { return FOREACH; } +"FOR" { return FOR; } +"DO" { return DO; } +"WAIT" { return SLEEP; } + +\{([^\}]|\\.)*\} { yylval.s = str(yytext); return SCRIPT_DATA; } +\"([^\"]|\\.)*\" { yylval.s = str(yytext); return STRING; } +"-"?[0-9]+ { yylval.s = str(yytext); return INT; } +"0x"[0-9a-fA-F]+ { yylval.s = str(yytext); return INT; } +[a-zA-Z][-_a-zA-Z0-9]* { yylval.s = str(yytext); return ID; } +"#".*$ { PRINTF("%s\n", sexpr::escape(*str(yytext + 1))); } +"//".*$ { PRINTF("%s\n", sexpr::escape(*str(yytext + 2))); } +[ \n\t\r] /* ignore whitespace */ +. { abort(); } + +%% +// nothing to see here, move along diff --git a/src/spell-convert/main.cpp b/src/spell-convert/main.cpp new file mode 100644 index 0000000..a6f0d76 --- /dev/null +++ b/src/spell-convert/main.cpp @@ -0,0 +1,7 @@ +#include "src/spell-convert/lexer.hpp" +#include "src/spell-convert/parser.hpp" + +int main() +{ + spell_converterparse(); +} diff --git a/src/spell-convert/parser.ypp b/src/spell-convert/parser.ypp new file mode 100644 index 0000000..822727d --- /dev/null +++ b/src/spell-convert/parser.ypp @@ -0,0 +1,882 @@ +%code requires +{ +/* vim: set ft=yacc: */ +#include "../strings/rstring.hpp" + +#include "ast.hpp" + +#undef YYERROR_VERBOSE +#define YYERROR_VERBOSE 1 +} // %code requires + +%code +{ +//#include "parser.hpp" +#include "lexer.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../sexpr/lexer.hpp" + +void yyerror(const char *msg) { FPRINTF(stderr, "Fatal: %s\n", msg); abort(); } +} // %code + +%name-prefix "spell_converter" + +%union +{ + RString *s; + std::vector *vs; + Effect *e; + std::deque *ve; + SpellDef *spelldef; + SpellArg *spellarg; + TopLevel *top; + Expression *expr; + std::vector *vx; + Location *loc; + Item *it; + std::vector *vit; + Assignment *a; + std::vector *va; + SpellBod *b; + std::vector *vb; + SpellGuard *g; + std::vector *vg; +} // %union + +%expect 7 + +%token INT +%token STRING +%token ID +%token DIR + +%token '=' +%token '<' +%token '>' +%token '+' +%token '-' +%token '*' +%token '/' +%token '%' +%token '@' +%token ',' +%token '.' +%token ':' +%token ';' +%token '|' +%token '[' +%token ']' +%token '&' +%token '^' + +%token CONST +%token PROCEDURE +%token CALL +%token SILENT +%token LOCAL +%token NONMAGIC +%token SHL +%token SHR +%token EQ +%token NEQ +%token GTE +%token LTE +%token ANDAND +%token OROR +%token SCRIPT_DATA +%token TO +%token TOWARDS +%token TELEPORT_ANCHOR +%token SPELL +%token LET +%token IN +%token END +%token DARROW +%token STRING_TY +%token REQUIRE +%token CATALYSTS +%token COMPONENTS +%token MANA +%token CASTTIME +%token SKIP +%token ABORT +%token BREAK +%token EFFECT_ +%token ATEND +%token ATTRIGGER +%token PC_F +%token NPC_F +%token MOB_F +%token ENTITY_F +%token TARGET_F +%token IF +%token THEN +%token ELSE +%token FOREACH +%token FOR +%token DO +%token SLEEP + +%type value +%type location +%type area +%type arg_list +%type arg_list_ne +%type defs +%type spelldef +%type argopt +%type def +%type spellbody_list +%type spellbody +%type spellguard +%type spellguard_list +%type prereq +%type item +%type items +%type item_list +%type item_name +%type selection; +%type effect +%type effect_list +%type maybe_trigger +%type maybe_end +%type spell_flags; + +%type expr +%type arg_ty +%type proc_formals_list +%type proc_formals_list_ne + +%type spellconf_option + +%left OROR +%left ANDAND +%left '<' '>' GTE LTE NEQ EQ +%left '+' '-' +%left '*' '/' '%' +%left SHL SHR '&' '^' '|' +%right '=' +%left OR +%left DARROW +%left '.' + +%% + +spellconf + +: /* empty */ + +| spellconf semicolons spellconf_option +{ + $3->dump(); +} + +; + + +semicolons + +: /* empty */ + +| semicolons ';' + +; + + +proc_formals_list + +: /* empty */ +{ + $$ = new std::vector(); +} + +| proc_formals_list_ne +{ + $$ = $1; +} + +; + + +proc_formals_list_ne + +: ID +{ + $$ = new std::vector(); + $$->push_back(*$1); +} + +| proc_formals_list_ne ',' ID +{ + $$ = $1; + $$->push_back(*$3); +} + +; + + +spellconf_option + +: ID '=' expr +{ + $$ = new Assignment{*$1, $3}; +} + +| CONST ID '=' expr +{ + $$ = new Constant{*$2, $4}; +} + +| TELEPORT_ANCHOR ID ':' expr '=' expr +{ + $$ = new Teleport{*$2, $4, $6}; +} + +| PROCEDURE ID '(' proc_formals_list ')' '=' effect_list +{ + $$ = new Procedure{*$2, $4, $7}; +} + +| spell_flags SPELL ID argopt ':' expr '=' spelldef +{ + $$ = new Spell{$1, *$3, $4, $6, $8}; +} + +; + + +spell_flags + +: /* empty */ +{ + $$ = new std::vector(); +} + +| spell_flags LOCAL +{ + $$ = $1; + $$->push_back("LOCAL"); +} + +| spell_flags NONMAGIC +{ + $$ = $1; + $$->push_back("NONMAGIC"); +} + +| spell_flags SILENT +{ + $$ = $1; + $$->push_back("SILENT"); +} + +; + + +argopt + +: /* empty */ +{ + $$ = new SpellArg{}; +} + +| '(' ID ':' arg_ty ')' +{ + $$ = new SpellArg{*$2, *$4}; +} + +; + + +arg_ty + +: PC_F +{ + $$ = new RString("PC"); +} + +| STRING_TY +{ + $$ = new RString("STRING"); +} + +; + + +value + +: DIR +{ + $$ = $1; +} + +| INT +{ + $$ = $1; +} + +| STRING +{ + $$ = $1; +} + +; + + +expr + +: value +{ + $$ = new SimpleExpr{*$1}; +} + +| ID +{ + $$ = new SimpleExpr{*$1}; +} + +| area +{ + $$ = $1; +} + +| expr '+' expr +{ + $$ = new BinExpr{$1, "+", $3}; +} + +| expr '-' expr +{ + $$ = new BinExpr{$1, "-", $3}; +} + +| expr '*' expr +{ + $$ = new BinExpr{$1, "*", $3}; +} + +| expr '%' expr +{ + $$ = new BinExpr{$1, "%", $3}; +} + +| expr '/' expr +{ + $$ = new BinExpr{$1, "/", $3}; +} + +| expr '<' expr +{ + $$ = new BinExpr{$1, "<", $3}; +} + +| expr '>' expr +{ + $$ = new BinExpr{$1, ">", $3}; +} + +| expr '&' expr +{ + $$ = new BinExpr{$1, "&", $3}; +} + +| expr '^' expr +{ + $$ = new BinExpr{$1, "^", $3}; +} + +| expr '|' expr +{ + $$ = new BinExpr{$1, "|", $3}; +} + +| expr SHL expr +{ + $$ = new BinExpr{$1, "<<", $3}; +} + +| expr SHR expr +{ + $$ = new BinExpr{$1, ">>", $3}; +} + +| expr LTE expr +{ + $$ = new BinExpr{$1, "<=", $3}; +} + +| expr GTE expr +{ + $$ = new BinExpr{$1, ">=", $3}; +} + +| expr ANDAND expr +{ + $$ = new BinExpr{$1, "&&", $3}; +} + +| expr OROR expr +{ + $$ = new BinExpr{$1, "||", $3}; +} + +| expr EQ expr +{ + $$ = new BinExpr{$1, "==", $3}; +} + +| expr '=' expr +{ + // convert to == + $$ = new BinExpr{$1, "==", $3}; +} + +| expr NEQ expr +{ + $$ = new BinExpr{$1, "!=", $3}; +} + +| ID '(' arg_list ')' +{ + $$ = new CallExpr{*$1, $3}; +} + +| '(' expr ')' +{ + $$ = $2; +} + +| expr '.' ID +{ + $$ = new BinExpr{$1, ".", new SimpleExpr(*$3)}; +} + +; + + +arg_list + +: /* empty */ +{ + $$ = new std::vector(); +} + +| arg_list_ne +{ + $$ = $1; +} + +; + + +arg_list_ne + +: expr +{ + $$ = new std::vector(); + $$->push_back($1); +} + +| arg_list_ne ',' expr +{ + $$ = $1; + $$->push_back($3); +} + +; + + +location + +: '@' '(' expr ',' expr ',' expr ')' +{ + $$ = new Location{$3, $5, $7}; +} + +; + + +area + +: location +{ + $$ = new AreaLoc{$1}; +} + +| location '@' '+' '(' expr ',' expr ')' +{ + $$ = new AreaRect{$1, $5, $7}; +} + +| location TOWARDS expr ':' '(' expr ',' expr ')' +{ + $$ = new AreaBar{$1, $3, $6, $8}; +} + +; + + +spelldef + +: spellbody_list +{ + $$ = new SpellDef{new std::vector{}, $1}; +} + +| LET defs IN spellbody_list +{ + $$ = new SpellDef{$2, $4}; +} + +; + + +defs + +: semicolons +{ + $$ = new std::vector(); +} + +| defs def semicolons +{ + $$ = $1; + $$->push_back($2); +} + +; + + +def + +: ID '=' expr +{ + $$ = new Assignment{*$1, $3}; +} + +; + + +spellbody_list + +: spellbody +{ + $$ = new std::vector(); + $$->push_back($1); +} + +| spellbody_list '|' spellbody +{ + $$ = $1; + $$->push_back($3); +} + +; + + +spellbody + +: spellguard DARROW spellbody +{ + $$ = new SpellBodGuarded{$1, $3}; +} + +| '(' spellbody_list ')' +{ + $$ = new SpellBodList{$2}; +} + +| EFFECT_ effect_list maybe_trigger maybe_end +{ + $$ = new SpellBodEffect{$2, $3, $4}; +} + +; + + +maybe_trigger + +: /* empty */ +{ + $$ = nullptr; +} + +| ATTRIGGER effect_list +{ + $$ = $2; +} + +; + + +maybe_end + +: /* empty */ +{ + $$ = nullptr; +} + +| ATEND effect_list +{ + $$ = $2; +} + +; + + +spellguard + +: prereq +{ + $$ = $1; +} + +| spellguard OR spellguard +{ + $$ = new SpellGuardOr($1, $3); +} + +| '(' spellguard_list ')' +{ + $$ = new SpellGuardList{$2}; +} + +; + + +spellguard_list + +: spellguard +{ + $$ = new std::vector(); + $$->push_back($1); +} + +| spellguard_list ',' spellguard +{ + $$ = $1; + $$->push_back($3); +} + +; + + +prereq + +: REQUIRE expr +{ + $$ = new SpellGuardRequire{$2}; +} + +| CATALYSTS items +{ + $$ = new SpellGuardCatalysts{$2}; +} + +| COMPONENTS items +{ + $$ = new SpellGuardComponents{$2}; +} + +| MANA expr +{ + $$ = new SpellGuardMana{$2}; +} + +| CASTTIME expr +{ + $$ = new SpellGuardCasttime{$2}; +} + +; + + +items + +: '[' item_list ']' +{ + $$ = $2; +} + +; + + +item_list + +: item +{ + $$ = new std::vector(); + $$->push_back($1); +} + +| item_list ',' item +{ + $$ = $1; + $$->push_back($3); +} + +; + + +item + +: INT '*' item_name +{ + $$ = new Item{*$1, *$3}; +} + +| item_name +{ + $$ = new Item{RString(), *$1}; +} + +; + + +item_name + +: STRING +{ + $$ = $1; +} + +| INT +{ + $$ = $1; +} + +; + + +selection + +: PC_F +{ + $$ = new RString{"PC"}; +} + +| MOB_F +{ + $$ = new RString{"MOB"}; +} + +| ENTITY_F +{ + $$ = new RString{"ENTITY"}; +} + +| SPELL +{ + $$ = new RString{"SPELL"}; +} + +| TARGET_F +{ + $$ = new RString{"TARGET"}; +} + +| NPC_F +{ + $$ = new RString{"NPC"}; +} + +; + + +effect + +: '(' effect_list ')' +{ + $$ = new EffectList{$2}; +} + +| SKIP ';' +{ + $$ = new SimpleEffect{"SKIP"}; +} + +| ABORT ';' +{ + $$ = new SimpleEffect{"ABORT"}; +} + +| END ';' +{ + $$ = new SimpleEffect{"END"}; +} + +| BREAK ';' +{ + $$ = new SimpleEffect{"BREAK"}; +} + +| ID '=' expr ';' +{ + $$ = new Assignment(*$1, $3); +} + +| FOREACH selection ID IN expr DO effect +{ + $$ = new ForeachEffect{*$2, *$3, $5, $7}; +} + +| FOR ID '=' expr TO expr DO effect +{ + $$ = new ForEffect{*$2, $4, $6, $8}; +} + +| IF expr THEN effect ELSE effect +{ + $$ = new IfEffect{$2, $4, $6}; +} + +| IF expr THEN effect +{ + $$ = new IfEffect{$2, $4}; +} + +| SLEEP expr ';' +{ + $$ = new SleepEffect{$2}; +} + +| ID '(' arg_list ')' ';' +{ + $$ = new CallExpr{*$1, $3}; +} + +| SCRIPT_DATA +{ + AString tmp = sexpr::escape(*$1); + $$ = new ScriptEffect{RString(tmp)}; +} + +| CALL ID '(' arg_list ')' ';' +{ + $$ = new ExplicitCallEffect{*$2, $4}; +} + +; + + +effect_list + +: /* empty */ +{ + $$ = new std::deque(); +} + +| effect semicolons effect_list +{ + // because of grammar problems, doing this right generates reduce/reduce conflicts + $$ = $3; + $$->push_front($1); +} + +; + + +%% +// Nothing to see here, move along -- cgit v1.2.3-60-g2f50