summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--COPYING5
-rw-r--r--Makefile.in5
-rw-r--r--agpl-3.0.txt661
-rw-r--r--src/io/line.cpp43
-rw-r--r--src/io/line.hpp21
-rw-r--r--src/io/line_test.cpp47
-rw-r--r--src/map/atcommand.cpp18
-rw-r--r--src/map/itemdb.cpp5
-rw-r--r--src/map/itemdb.hpp3
-rw-r--r--src/map/magic-expr-eval.cpp3
-rw-r--r--src/map/magic-expr.cpp33
-rw-r--r--src/map/magic-interpreter-aux.cpp3
-rw-r--r--src/map/magic-interpreter-lexer.hpp1
-rw-r--r--src/map/magic-interpreter-lexer.lpp152
-rw-r--r--src/map/magic-interpreter-parser.ypp1441
-rw-r--r--src/map/magic-interpreter.cpp3
-rw-r--r--src/map/magic-interpreter.hpp3
-rw-r--r--src/map/magic-interpreter.py224
-rw-r--r--src/map/magic-v2.cpp1245
-rw-r--r--src/map/magic-v2.hpp28
-rw-r--r--src/map/magic.cpp2
-rw-r--r--src/map/main.cpp2
-rw-r--r--src/map/map.cpp3
-rw-r--r--src/map/npc.cpp2
-rw-r--r--src/map/script.cpp14
-rw-r--r--src/sexpr/lexer.cpp228
-rw-r--r--src/sexpr/lexer.hpp73
-rw-r--r--src/sexpr/lexer_test.cpp113
-rw-r--r--src/sexpr/main.cpp130
-rw-r--r--src/sexpr/parser.cpp79
-rw-r--r--src/sexpr/parser.hpp80
-rw-r--r--src/sexpr/parser.py25
-rw-r--r--src/sexpr/parser_test.cpp89
-rw-r--r--src/spell-convert/ast.cpp260
-rw-r--r--src/spell-convert/ast.hpp432
-rw-r--r--src/spell-convert/lexer.lpp117
-rw-r--r--src/spell-convert/main.cpp7
-rw-r--r--src/spell-convert/parser.ypp882
39 files changed, 4857 insertions, 1633 deletions
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. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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
+<http://www.gnu.org/licenses/>.
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("<span>", 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"),
+ "<span>: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"),
+ "<span>:1:2: note: foo\n"
+ "Hello\n"
+ " ^\n"
+ );
+ EXPECT_EQ(span.end.message_str("warning", "bar"),
+ "<span>:2:3: warning: bar\n"
+ "World\n"
+ " ^\n"
+ );
+ EXPECT_EQ(span.message_str("error", "qux"),
+ "<span>: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<map_session_data> 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<map_session_data> 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<map_session_data> 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<map_session_data> 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<ItemName>(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<env_t>, val_t *result, const_array<val_t> args)
static
int fun_gte(dumb_ptr<env_t>, val_t *result, const_array<val_t> 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);
@@ -374,6 +374,14 @@ int fun_gte(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
}
static
+int fun_lt(dumb_ptr<env_t> env, val_t *result, const_array<val_t> args)
+{
+ fun_gte(env, result, args);
+ RESULTINT = !RESULTINT;
+ return 0;
+}
+
+static
int fun_gt(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
{
if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING)
@@ -392,6 +400,14 @@ int fun_gt(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
}
static
+int fun_lte(dumb_ptr<env_t> env, val_t *result, const_array<val_t> args)
+{
+ fun_gt(env, result, args);
+ RESULTINT = !RESULTINT;
+ return 0;
+}
+
+static
int fun_eq(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
{
if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING)
@@ -424,6 +440,14 @@ int fun_eq(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
}
static
+int fun_ne(dumb_ptr<env_t> env, val_t *result, const_array<val_t> args)
+{
+ fun_eq(env, result, args);
+ RESULTINT = !RESULTINT;
+ return 0;
+}
+
+static
int fun_bitand(dumb_ptr<env_t>, val_t *result, const_array<val_t> args)
{
RESULTINT = ARGINT(0) & ARGINT(1);
@@ -753,7 +777,7 @@ magic_find_item(const_array<val_t> 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<ItemName>(ARGSTR(index)));
+ item_data = itemdb_searchname(ARGSTR(index));
else
return -1;
@@ -1250,9 +1274,12 @@ std::map<ZString, fun_t> 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 <cassert>
-#include <cstdarg> // 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<expr_t> fun_expr(AString name, const_array<dumb_ptr<expr_t>> argv, int line, int column);
-
-static
-dumb_ptr<expr_t> dot_expr(dumb_ptr<expr_t> lhs, int id);
-
-static
-void BIN_EXPR(dumb_ptr<expr_t>& x, AString name, dumb_ptr<expr_t> arg1, dumb_ptr<expr_t> arg2, int line, int column)
-{
- dumb_ptr<expr_t> e[2];
- e[0] = arg1;
- e[1] = arg2;
- x = fun_expr(name, const_array<dumb_ptr<expr_t>>(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<spell_t> new_spell(dumb_ptr<spellguard_t> guard);
-
-static
-dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b);
-
-static
-dumb_ptr<spellguard_t> new_spellguard(SPELLGUARD ty);
-
-static
-dumb_ptr<effect_t> new_effect(EFFECT ty);
-
-static
-dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation);
-
-static
-void add_spell(dumb_ptr<spell_t> spell, int line_nr);
-
-static
-void add_teleport_anchor(dumb_ptr<teleport_anchor_t> anchor, int line_nr);
-
-static
-dumb_ptr<effect_t> op_effect(AString name, const_array<dumb_ptr<expr_t>> argv, int line, int column);
-
-// in magic-interpreter-lexer.cpp
-int magic_frontend_lex(void);
-
-static
-void install_proc(dumb_ptr<proc_t> proc);
-
-static
-dumb_ptr<effect_t> call_proc(ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> 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_t> expr;
- e_location_t location;
- e_area_t area;
- args_rec_t arg_list;
- dumb_ptr<std::vector<letdef_t>> letdefvp;
- dumb_ptr<spell_t> spell;
- struct { int id; SPELLARG ty; } spellarg_def;
- letdef_t vardef;
- dumb_ptr<spellguard_t> spellguard;
- dumb_ptr<component_t> components;
- struct { int id, count; } component;
- dumb_ptr<effect_t> effect;
- dumb_ptr<proc_t> 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 <i> INT
-%token <s> STRING
-%token <s> ID
-%token <i> 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 <s> 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> value
-%type <location> location
-%type <area> area
-%type <arg_list> arg_list
-%type <arg_list> arg_list_ne
-%type <letdefvp> defs
-%type <spell> spelldef
-%type <spellarg_def> argopt
-%type <vardef> def
-%type <spellguard> spellbody_list
-%type <spellguard> spellbody
-%type <spellguard> spellguard
-%type <spellguard> spellguard_list
-%type <spellguard> prereq
-%type <component> item
-%type <components> items
-%type <components> item_list
-%type <i> item_name
-%type <foreach_filter> selection;
-%type <effect> effect
-%type <effect> effect_list
-%type <effect> maybe_trigger
-%type <effect> maybe_end
-%type <spell_flags> spell_flags;
-
-%type <expr> expr
-%type <spell_arg> arg_ty
-%type <proc> proc_formals_list
-%type <proc> 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<proc_t>::make();
-}
-
-| proc_formals_list_ne
-{
- $$ = $1;
-}
-
-;
-
-
-proc_formals_list_ne
-
-: ID
-{
- $$ = dumb_ptr<proc_t>::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<env_t>(&magic_default_env), &magic_conf.varv[var_id].val, $3);
- }
- $1.delete_();
-}
-
-| CONST ID '=' expr
-{
- val_t var;
- magic_eval(dumb_ptr<env_t>(&magic_default_env), &var, $4);
- bind_constant($2.str(), &var, @1.first_line);
- $2.delete_();
-}
-
-| TELEPORT_ANCHOR ID ':' expr '=' expr
-{
- auto anchor = dumb_ptr<teleport_anchor_t>::make();
- anchor->name = $2.str();
- $2.delete_();
- anchor->invocation = magic_eval_str(dumb_ptr<env_t>(&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_t> 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_t> spell = $8;
- spell->name = $3.str();
- $3.delete_();
- spell->invocation = magic_eval_str(dumb_ptr<env_t>(&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<dumb_ptr<expr_t>>(&$$, 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<spellguard_t> 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<spellguard_t> 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<spellguard_t> 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<ItemName>(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<const ScriptBuffer>(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_t> 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<teleport_anchor_t> 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<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id)
-{
- dumb_ptr<expr_t> retval = magic_new_expr(EXPR::SPELLFIELD);
- retval->e.e_field.id = id;
- retval->e.e_field.expr = expr;
-
- return retval;
-}
-
-dumb_ptr<expr_t> fun_expr(AString name, const_array<dumb_ptr<expr_t>> argv, int line, int column)
-{
- dumb_ptr<expr_t> 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<spell_t> new_spell(dumb_ptr<spellguard_t> guard)
-{
- auto retval = dumb_ptr<spell_t>::make();
- retval->spellguard = guard;
- return retval;
-}
-
-dumb_ptr<spellguard_t> new_spellguard(SPELLGUARD ty)
-{
- dumb_ptr<spellguard_t> retval = dumb_ptr<spellguard_t>::make();
- retval->ty = ty;
- return retval;
-}
-
-dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b)
-{
- dumb_ptr<spellguard_t> 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<effect_t> new_effect(EFFECT ty)
-{
- auto effect = dumb_ptr<effect_t>::make();
- effect->ty = ty;
- return effect;
-}
-
-dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation)
-{
- dumb_ptr<effect_t> 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<effect_t> op_effect(AString name, const_array<dumb_ptr<expr_t>> argv, int line, int column)
-{
- dumb_ptr<effect_t> 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<RString, proc_t> procs;
-
-// I think this was a memory leak (or undefined behavior)
-void install_proc(dumb_ptr<proc_t> proc)
-{
- procs.insert({proc->name, std::move(*proc)});
-}
-
-dumb_ptr<effect_t> call_proc(ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> 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<effect_t> 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<RString, val_t> 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> 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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include <set>
+
+#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<RString, proc_t> procs;
+ static
+ std::map<RString, val_t> 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<effect_t> new_effect(EFFECT ty)
+ {
+ auto effect = dumb_ptr<effect_t>::make();
+ effect->ty = ty;
+ return effect;
+ }
+ static
+ dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation)
+ {
+ dumb_ptr<effect_t> 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<spellguard_t> new_spellguard(SPELLGUARD ty)
+ {
+ dumb_ptr<spellguard_t> retval = dumb_ptr<spellguard_t>::make();
+ retval->ty = ty;
+ return retval;
+ }
+ static
+ dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b)
+ {
+ dumb_ptr<spellguard_t> 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_t> 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<teleport_anchor_t> 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_t> 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<std::vector<dumb_ptr<expr_t>>> argvp, dumb_ptr<effect_t>& 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<dumb_ptr<expr_t>> argv, dumb_ptr<effect_t>& 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<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id)
+ {
+ dumb_ptr<expr_t> 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<dumb_ptr<expr_t>> argv, dumb_ptr<expr_t>& 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<expr_t> BIN_EXPR(io::LineSpan span, ZString name, dumb_ptr<expr_t> left, dumb_ptr<expr_t> right)
+ {
+ dumb_ptr<expr_t> e[2];
+ e[0] = left;
+ e[1] = right;
+ dumb_ptr<expr_t> rv;
+ if (!fun_expr(span, name, const_array<dumb_ptr<expr_t>>(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<expr_t>& out);
+ static
+ bool parse_effect(const SExpr& s, dumb_ptr<effect_t>& out);
+ static
+ bool parse_spellguard(const SExpr& s, dumb_ptr<spellguard_t>& out);
+ static
+ bool parse_spellbody(const SExpr& s, dumb_ptr<spellguard_t>& 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<expr_t>& 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<DIR>(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<expr_t> width;
+ dumb_ptr<expr_t> 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<expr_t> dir;
+ dumb_ptr<expr_t> width;
+ dumb_ptr<expr_t> 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_t> 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<ZString> 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<expr_t> tmp;
+ if (!parse_expression(*begin, tmp))
+ return false;
+ out = BIN_EXPR(x._span, op, out, tmp);
+ }
+ return true;
+ }
+ std::vector<dumb_ptr<expr_t>> argv;
+ for (auto it = x._list.begin() + 1, end = x._list.end(); it != end; ++it)
+ {
+ dumb_ptr<expr_t> 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<spellguard_t>& 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<spellguard_t> alt;
+ if (!parse_spellguard(*begin, alt))
+ return false;
+ dumb_ptr<spellguard_t> 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<spellguard_t> 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<expr_t> 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<expr_t> 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<expr_t> 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<component_t> 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<component_t> 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<SExpr>::const_iterator begin,
+ std::vector<SExpr>::const_iterator end, dumb_ptr<effect_t>& 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<effect_t> 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<effect_t>& 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_t> 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<const ScriptBuffer> 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<const ScriptBuffer>(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<expr_t> area;
+ dumb_ptr<effect_t> 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<expr_t> low;
+ dumb_ptr<expr_t> high;
+ dumb_ptr<effect_t> 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<expr_t> cond;
+ dumb_ptr<effect_t> if_true;
+ dumb_ptr<effect_t> 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_t> 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<std::vector<dumb_ptr<expr_t>>>::make();
+ for (auto it = s._list.begin() + 2, end = s._list.end(); it != end; ++it)
+ {
+ dumb_ptr<expr_t> expr;
+ if (!parse_expression(*it, expr))
+ return false;
+ argvp->push_back(expr);
+ }
+ return call_proc(s._span, func, argvp, out);
+ }
+ auto argv = std::vector<dumb_ptr<expr_t>>();
+ for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it)
+ {
+ dumb_ptr<expr_t> 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<spellguard_t>& 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<spellguard_t> guard;
+ if (!parse_spellguard(s._list[1], guard))
+ return false;
+ dumb_ptr<spellguard_t> 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<spellguard_t> 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_t> 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<SExpr>& in)
+ {
+ if (in.size() != 3)
+ return fail(in[0], "not 2 arguments");
+ ZString name = in[1]._str;
+ dumb_ptr<expr_t> 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<env_t>(&magic_default_env), &magic_conf.varv[var_id].val, expr);
+ return true;
+ }
+ static
+ bool parse_const(io::LineSpan span, const std::vector<SExpr>& 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_t> expr;
+ if (!parse_expression(in[2], expr))
+ return false;
+ val_t tmp;
+ magic_eval(dumb_ptr<env_t>(&magic_default_env), &tmp, expr);
+ return bind_constant(span, name, &tmp);
+ }
+ static
+ bool parse_anchor(io::LineSpan span, const std::vector<SExpr>& in)
+ {
+ if (in.size() != 4)
+ return fail(in[0], "not 3 arguments");
+ auto anchor = dumb_ptr<teleport_anchor_t>::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_t> 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<SExpr>& in)
+ {
+ if (in.size() < 4)
+ return fail(in[0], "not at least 3 arguments");
+ auto proc = dumb_ptr<proc_t>::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<SExpr>& 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<spell_t>::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<SExpr>::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_t> 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<spellguard_t> 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<SExpr>& 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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+# 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<ItemName>(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<ItemName>(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<ItemName>(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<ItemName>(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<ItemName>(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<ItemName>(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<ItemName>(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<ItemName>(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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#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<uint8_t>(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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+# 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<io::LineChar> _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 <gtest/gtest.h>
+
+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("<lexer-test1>", 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"),
+ "<lexer-test1>: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"),
+ "<lexer-test1>: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"),
+ "<lexer-test1>: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"),
+ "<lexer-test1>: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"),
+ "<lexer-test1>: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("<lexer-bad>", 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("<lexer-bad>", 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 <stack>
+#include <map>
+
+#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<ZString, Spacing> 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;
+ 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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#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 <b.r.longbons@gmail.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+# 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<SExpr> _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 <gtest/gtest.h>
+
+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("<parser-test1>", 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<sexpr::SExpr>());
+
+ 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("<parser-test1>", 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("<parse-bad>", 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 <deque>
+# include <vector>
+
+# 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<RString> *args;
+ std::deque<Effect *> *body;
+
+ Procedure(RString n, std::vector<RString> *a, std::deque<Effect *> *b)
+ : name(n), args(a), body(b)
+ {}
+
+ virtual void dump() override;
+};
+
+struct Spell : TopLevel
+{
+ std::vector<RString> *flags;
+ RString name;
+ SpellArg *arg;
+ Expression *ident;
+ SpellDef *body;
+
+ Spell(std::vector<RString> *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<Effect *> *body;
+
+ EffectList(std::deque<Effect *> *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<Expression *> *args;
+
+ ExplicitCallEffect(RString f, std::vector<Expression *> *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<Assignment *> *lets;
+ std::vector<SpellBod *> *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<SpellBod *> *body;
+
+ SpellBodList(std::vector<SpellBod *> *b)
+ : body(b)
+ {}
+
+ virtual void say() override;
+};
+
+struct SpellBodEffect : SpellBod
+{
+ std::deque<Effect *> *body;
+ std::deque<Effect *> *maybe_trigger;
+ std::deque<Effect *> *maybe_end;
+
+ SpellBodEffect(std::deque<Effect *> *b, std::deque<Effect *> *t, std::deque<Effect *> *e)
+ : body(b), maybe_trigger(t), maybe_end(e)
+ {}
+
+ virtual void say() override;
+};
+
+struct SpellGuard
+{
+ virtual void declare() = 0;
+};
+
+struct SpellGuardOr : SpellGuard
+{
+ std::vector<SpellGuard *> *any;
+
+ SpellGuardOr(std::vector<SpellGuard *> *a) : any(a) {}
+ SpellGuardOr(SpellGuard *left, SpellGuard *right)
+ : any(new std::vector<SpellGuard *>({left, right}))
+ {}
+
+ virtual void declare() override;
+};
+struct SpellGuardList : SpellGuard
+{
+ std::vector<SpellGuard *> *all;
+
+ SpellGuardList(std::vector<SpellGuard *> *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<Item *> *items;
+
+ SpellGuardCatalysts(std::vector<Item *> *i) : items(i) {}
+
+ virtual void declare() override;
+};
+struct SpellGuardComponents : SpellGuard
+{
+ std::vector<Item *> *items;
+
+ SpellGuardComponents(std::vector<Item *> *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<Expression *> *args;
+
+ CallExpr(RString f, std::vector<Expression *> *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<RString> *vs;
+ Effect *e;
+ std::deque<Effect *> *ve;
+ SpellDef *spelldef;
+ SpellArg *spellarg;
+ TopLevel *top;
+ Expression *expr;
+ std::vector<Expression *> *vx;
+ Location *loc;
+ Item *it;
+ std::vector<Item *> *vit;
+ Assignment *a;
+ std::vector<Assignment *> *va;
+ SpellBod *b;
+ std::vector<SpellBod *> *vb;
+ SpellGuard *g;
+ std::vector<SpellGuard *> *vg;
+} // %union
+
+%expect 7
+
+%token <s> INT
+%token <s> STRING
+%token <s> ID
+%token <s> 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 <s> 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 <s> value
+%type <loc> location
+%type <expr> area
+%type <vx> arg_list
+%type <vx> arg_list_ne
+%type <va> defs
+%type <spelldef> spelldef
+%type <spellarg> argopt
+%type <a> def
+%type <vb> spellbody_list
+%type <b> spellbody
+%type <g> spellguard
+%type <vg> spellguard_list
+%type <g> prereq
+%type <it> item
+%type <vit> items
+%type <vit> item_list
+%type <s> item_name
+%type <s> selection;
+%type <e> effect
+%type <ve> effect_list
+%type <ve> maybe_trigger
+%type <ve> maybe_end
+%type <vs> spell_flags;
+
+%type <expr> expr
+%type <s> arg_ty
+%type <vs> proc_formals_list
+%type <vs> proc_formals_list_ne
+
+%type <top> 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<RString>();
+}
+
+| proc_formals_list_ne
+{
+ $$ = $1;
+}
+
+;
+
+
+proc_formals_list_ne
+
+: ID
+{
+ $$ = new std::vector<RString>();
+ $$->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<RString>();
+}
+
+| 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<Expression *>();
+}
+
+| arg_list_ne
+{
+ $$ = $1;
+}
+
+;
+
+
+arg_list_ne
+
+: expr
+{
+ $$ = new std::vector<Expression *>();
+ $$->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<Assignment *>{}, $1};
+}
+
+| LET defs IN spellbody_list
+{
+ $$ = new SpellDef{$2, $4};
+}
+
+;
+
+
+defs
+
+: semicolons
+{
+ $$ = new std::vector<Assignment *>();
+}
+
+| defs def semicolons
+{
+ $$ = $1;
+ $$->push_back($2);
+}
+
+;
+
+
+def
+
+: ID '=' expr
+{
+ $$ = new Assignment{*$1, $3};
+}
+
+;
+
+
+spellbody_list
+
+: spellbody
+{
+ $$ = new std::vector<SpellBod *>();
+ $$->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<SpellGuard *>();
+ $$->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<Item *>();
+ $$->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 *>();
+}
+
+| 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