├── .gitignore ├── README.md ├── epl-v10.html ├── license.txt ├── project.clj ├── src └── name │ └── choi │ └── joshua │ ├── fnparse.clj │ └── fnparse │ └── json.clj └── test └── name └── choi └── joshua └── fnparse ├── test_parse.clj └── test_parse_json.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | classes 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Farewell to FnParse 2 | 3 | This library is **deprecated**. You’re free to fork it as per the [CPL](https://en.wikipedia.org/wiki/Common_Public_License), but don’t use it for production. For general-purpose parsing in Clojure, use an alternative, living library such as: 4 | 5 | * [clojure.spec by Rich Hickey et al.](http://clojure.org/about/spec) 6 | * [Instaparse by Mark Engelberg](https://github.com/Engelberg/instaparse) 7 | * [pex by Ghadi Shayban](https://github.com/ghadishayban/pex) 8 | * [sequex by Jonathan Claggett](https://github.com/jclaggett/seqex) 9 | * [squarepeg by Eric Normand](https://github.com/ericnormand/squarepeg) 10 | 11 | Long ago, while Clojure was still young, I wrote FnParse as an exercise in monadic parsing. I was merely a college pre-medical student, but programming was a fun, rewarding activity that helped me in my research. But in the years since, my life has really changed, and my time for programming has been sadly very scarce. It is with a heavy heart, then, that I explicitly deprecate FnParse today. 12 | 13 | Of course, any users left of this library have long known this; the most-recent previous commit was in 2010. In the interim, I’ve thought often about FnParse with much guilt. It’s freely licensed open source, of course, and people can copy, fork, and adapt it freely for their use. And life has been very, very busy. But it was still embarrassing. I wanted to come out with a better version before I would publicly publish any more code. But that time won’t be for a while, and this, for now, is for the best. 14 | 15 | I still want to make a better rewrite, under a new name—but in the meantime, I’ll keep this repository up for historical purposes. My apologies to everyone ever inconvenienced, especially those who contributed any pull requests or issues. 16 | 17 | J S C 18 | 2016-06-07 19 | 20 | *** 21 | 22 | FnParse is a library for creating functional parsers in the Clojure programming 23 | language. It presents an easy, functional way to create parsers from EBNF rules and 24 | was inspired by the paper Using Functional Parsing to Achieve Quality in Software 25 | Maintenance (http://citeseer.ist.psu.edu/148293.html). 26 | 27 | FnParse's distribution has src and test folders. To use FnParse, download this 28 | distribution and include its src folder in your program's classpath—for instance, 29 | java -cp $CLOJURE_PATH:path-to-FnParse-folder/src/ ... 30 | 31 | FnParse's namespace is name.choi.joshua.fnparse. 32 | 33 | FnParse's unit tests are stored in the tests folder and use the test-is library 34 | from clojure-contrib, the Clojure standard library. 35 | 36 | For documentation, go to: http://github.com/joshua-choi/fnparse/wikis 37 | 38 | * FnParse 39 | * Copyright (c) 2009 Joshua Choi. All rights reserved. 40 | * The use and distribution terms for this software are covered by the 41 | * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 42 | * which can be found in the file epl-v10.html at the root of this distribution. 43 | * By using this software in any fashion, you are agreeing to be bound by 44 | * the terms of this license. 45 | * You must not remove this notice, or any other, from this software. 46 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |Eclipse Public License - v 1.0
31 | 32 |THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.
36 | 37 |1. DEFINITIONS
38 | 39 |"Contribution" means:
40 | 41 |a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and
43 |b) in the case of each subsequent Contributor:
44 |i) changes to the Program, and
45 |ii) additions to the Program;
46 |where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.
54 | 55 |"Contributor" means any person or entity that distributes 56 | the Program.
57 | 58 |"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.
61 | 62 |"Program" means the Contributions distributed in accordance 63 | with this Agreement.
64 | 65 |"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.
67 | 68 |2. GRANT OF RIGHTS
69 | 70 |a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.
76 | 77 |b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.
88 | 89 |c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.
101 | 102 |d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.
105 | 106 |3. REQUIREMENTS
107 | 108 |A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:
110 | 111 |a) it complies with the terms and conditions of this 112 | Agreement; and
113 | 114 |b) its license agreement:
115 | 116 |i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;
120 | 121 |ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;
124 | 125 |iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and
128 | 129 |iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.
133 | 134 |When the Program is made available in source code form:
135 | 136 |a) it must be made available under this Agreement; and
137 | 138 |b) a copy of this Agreement must be included with each 139 | copy of the Program.
140 | 141 |Contributors may not remove or alter any copyright notices contained 142 | within the Program.
143 | 144 |Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.
147 | 148 |4. COMMERCIAL DISTRIBUTION
149 | 150 |Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.
172 | 173 |For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.
183 | 184 |5. NO WARRANTY
185 | 186 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.
197 | 198 |6. DISCLAIMER OF LIABILITY
199 | 200 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 | 209 |7. GENERAL
210 | 211 |If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.
216 | 217 |If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.
223 | 224 |All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.
232 | 233 |Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.
252 | 253 |This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.
258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Common Public License Version 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 6 | 1. DEFINITIONS 7 | 8 | "Contribution" means: 9 | 10 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 19 | 20 | "Contributor" means any person or entity that distributes the Program. 21 | 22 | "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 23 | 24 | "Program" means the Contributions distributed in accordance with this Agreement. 25 | 26 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 27 | 28 | 29 | 2. GRANT OF RIGHTS 30 | 31 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 32 | 33 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 34 | 35 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 36 | 37 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 38 | 39 | 40 | 3. REQUIREMENTS 41 | 42 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 43 | 44 | a) it complies with the terms and conditions of this Agreement; and 45 | 46 | b) its license agreement: 47 | 48 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 49 | 50 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 51 | 52 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 53 | 54 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 55 | 56 | When the Program is made available in source code form: 57 | 58 | a) it must be made available under this Agreement; and 59 | 60 | b) a copy of this Agreement must be included with each copy of the Program. 61 | 62 | Contributors may not remove or alter any copyright notices contained within the Program. 63 | 64 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 65 | 66 | 67 | 4. COMMERCIAL DISTRIBUTION 68 | 69 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 70 | 71 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 72 | 73 | 74 | 5. NO WARRANTY 75 | 76 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 77 | 78 | 79 | 6. DISCLAIMER OF LIABILITY 80 | 81 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 82 | 83 | 84 | 7. GENERAL 85 | 86 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 87 | 88 | If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 89 | 90 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 91 | 92 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 93 | 94 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 95 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject fnparse "2.2.8-SNAPSHOT" 2 | :description "A library for creating functional parsers in Clojure." 3 | :aot [name.choi.joshua.fnparse] 4 | :dependencies [[org.clojure/clojure "1.3.0"] 5 | [slingshot "0.8.0"] 6 | [org.clojure/algo.monads "0.1.0"]] 7 | :dev-dependencies [[lein-clojars "0.5.0"]]) 8 | 9 | -------------------------------------------------------------------------------- /src/name/choi/joshua/fnparse.clj: -------------------------------------------------------------------------------- 1 | (ns name.choi.joshua.fnparse 2 | [:use clojure.set clojure.algo.monads]) 3 | 4 | ; A rule is a delay object that contains a function that: 5 | ; - Takes a collection of tokens. 6 | ; - If the token sequence is valid, it returns a (0) vector containing the (1) 7 | ; consumed symbols' products and (2) a state data object, usually a map. The 8 | ; state contains the (3) sequence of remaining tokens, usually with the key 9 | ; *remainder-accessor*. 10 | ; - If the given token sequence is invalid, then the rule Fails, meaning that it either 11 | ; simply returns nil. 12 | 13 | ; - (0) is called the rule's Result. 14 | ; - (1) is called the rule's Product. 15 | ; - (2) is called the rule's State. 16 | ; - (3) is called the rule's Remainder. 17 | 18 | (defn call-parser-maybe-fn [state] 19 | (fn [parser] 20 | (try (parser state) 21 | (catch Exception e nil)))) 22 | 23 | (def parser-m (state-t maybe-m)) 24 | 25 | (def 26 | #^{:doc "The function, symbol, or other callable object that is used to access 27 | the remainder inside a state object. In other words, 28 | (*remainder-accessor* a-state) has to return the remainder inside a-state. 29 | By default, the remainder-accessor is :remainder (meaning that FnParse's 30 | default states are maps containing :remainder). But the accessor is 31 | rebindable, so that you can use different kinds of state objects in your 32 | parsing application. Myself, I usually put a struct-map accessor for 33 | :remainder in here."} 34 | ^:dynamic 35 | *remainder-accessor* 36 | :remainder) 37 | (def 38 | #^{:doc "The function, symbol, or other callable object that is used to change 39 | the remainder inside a state object. In other words, 40 | (*remainder-setter* a-state new-remainder) has to return the remainder 41 | inside a-state. By default, the remainder-accessor is 42 | #(assoc %1 :remainder %2), which means that FnParse's default states are 43 | maps containing :remainder. But the accessor is rebindable, so that you can 44 | use different kinds of state objects in your parsing application. Myself, I 45 | usually leave this variable alone."} 46 | ^:dynamic 47 | *remainder-setter* 48 | #(assoc %1 :remainder %2)) 49 | 50 | (defmacro complex 51 | [steps & product-expr] 52 | `(domonad parser-m ~steps ~@product-expr)) 53 | 54 | (def 55 | #^{:doc "A rule that consumes no tokens. Its product is the entire current 56 | state. 57 | [Equivalent to the result of fetch-state from clojure.contrib.monads.]"} 58 | get-state (fetch-state)) 59 | (defn get-info 60 | "Creates a rule that consumes no tokens. The new rule's product is the value of the given 61 | key in the current state. 62 | [Equivalent to fetch-val from clojure.contrib.monads.]" 63 | [key] 64 | (fetch-val key)) 65 | (def 66 | #^{:doc "A rule that consumes no tokens. Its product is the sequence of the remaining 67 | tokens. 68 | (Equivalent to the result of (fetch-val *remainder-accessor*) from 69 | clojure.contrib.monads.)"} 70 | get-remainder (fetch-val *remainder-accessor*)) 71 | (defn set-info 72 | "Creates a rule that consumes no tokens. The new rule directly changes the current state 73 | by associating the given key with the given value. The product is the old value of the 74 | changed key. 75 | 76 | [Equivalent to set-val from clojure.contrib.monads.]" 77 | [key value] 78 | (set-val key value)) 79 | (defn update-info 80 | "Creates a rule that consumes no tokens. The new rule changes the current state by 81 | associating the given key with the evaluated result of applying the given updating 82 | function to the key's current value. The product is the old value of the changed key. 83 | [Equivalent to update-val from clojure.contrib.monads.]" 84 | [key val-update-fn] 85 | (update-val key val-update-fn)) 86 | 87 | (with-monad parser-m 88 | (def 89 | #^{:doc "A rule that matches emptiness--that is, it always matches with every given 90 | token sequence, and it always returns [nil tokens]. 91 | (def a emptiness) would be equivalent to the EBNF a = ; 92 | This rule's product is always nil, and it therefore always returns [nil tokens]."} 93 | emptiness (m-result nil))) 94 | 95 | (defn anything 96 | "A rule that matches anything--that is, it matches the first token of the 97 | tokens it is given. 98 | This rule's product is the first token it receives. It fails if there are no 99 | tokens left." 100 | ; [{tokens *remainder-accessor*, :as state}] 101 | [state] 102 | (if-let [tokens (*remainder-accessor* state)] 103 | [(first tokens) (*remainder-setter* state (next tokens))])) 104 | 105 | (defn validate 106 | "Creates a rule from attaching a product-validating function to the given subrule--that 107 | is, any products of the subrule must fulfill the validator function. 108 | (def a (validate b validator)) says that the rule a succeeds only when b succeeds and 109 | also when the evaluated value of (validator b-product) is true. The new rule's product 110 | would be b-product." 111 | [subrule validator] 112 | (complex [subproduct subrule, :when (validator subproduct)] 113 | subproduct)) 114 | 115 | (defn term 116 | "Creates a rule that is a terminal rule of the given validator--that is, it accepts only 117 | tokens for whom (validator token) is true. 118 | (def a (term validator)) would be equivalent to the EBNF 119 | a = ? (validator %) evaluates to true ?; 120 | The new rule's product would be the first token, if it fulfills the validator." 121 | [validator] 122 | (validate anything validator)) 123 | 124 | (defn lit 125 | "Creates a rule that is the terminal rule of the given literal token--that is, it accepts 126 | only tokens that are equal to the given literal token. 127 | (def a (lit \"...\")) would be equivalent to the EBNF 128 | a = \"...\"; 129 | The new rule's product would be the first token, if it equals the given literal token." 130 | [literal-token] 131 | (term (partial = literal-token))) 132 | 133 | (defn re-term 134 | "Creates a rule that is the terminal rule of the given regex--that is, it accepts only 135 | tokens that match the given regex. 136 | (def a (re-term #\"...\")) would be equivalent to the EBNF 137 | a = ? (re-matches #\"...\" %) evaluates to true ?; 138 | The new rule's product would be the first token, if it matches the given regex." 139 | [token-re] 140 | (term (partial re-matches token-re))) 141 | 142 | (defn followed-by 143 | "Creates a rule that does not consume any tokens, but fails when the given subrule fails. 144 | The new rule's product would be the subrule's product." 145 | [subrule] 146 | (complex [state get-state, subproduct subrule, _ (set-state state)] 147 | subproduct)) 148 | 149 | (defn not-followed-by 150 | "Creates a rule that does not consume any tokens, but fails when the given subrule 151 | succeeds. On success, the new rule's product is always true." 152 | [subrule] 153 | (fn [state] 154 | (if (nil? (subrule state)) 155 | [true state]))) 156 | 157 | (defn semantics 158 | "Creates a rule with a semantic hook: basically a simple version of a complex rule. The 159 | semantic hook is a function that takes one argument: the product of the subrule." 160 | [subrule semantic-hook] 161 | (complex [subproduct subrule] 162 | (semantic-hook subproduct))) 163 | 164 | (defn constant-semantics 165 | "Creates a rule with a constant semantic hook. Its product is always the given constant." 166 | [subrule semantic-value] 167 | (complex [subproduct subrule] 168 | semantic-value)) 169 | 170 | (def 171 | #^{:doc "A rule that does not consume any tokens. Its product is the very next token in 172 | the remainder."} 173 | remainder-peek 174 | (complex [remainder get-remainder] 175 | (first remainder))) 176 | 177 | (defmacro conc 178 | "Creates a rule that is the concatenation of the given subrules. Basically a simple 179 | version of complex, each subrule consumes tokens in order, and if any fail, the entire 180 | rule fails. 181 | (def a (conc b c d)) would be equivalent to the EBNF: 182 | a = b, c, d; 183 | This macro is almost equivalent to m-seq for the parser-m monad. The difference is that 184 | it defers evaluation of whatever variables it receives, so that it accepts expressions 185 | containing unbound variables that are defined later." 186 | [& subrules] 187 | `(with-monad parser-m 188 | (fn [state#] 189 | ((m-seq ~(vec subrules)) state#)))) 190 | 191 | (defmacro alt 192 | "Creates a rule that is the alternation of the given subrules. It succeeds when 193 | any of its subrules succeed, and fails when none do. Its result is that of the first 194 | subrule that succeeds, so the order of the subrules that this function receives 195 | matters. 196 | (def a (alt b c d)) would be equivalent to the EBNF: 197 | a = b | c | d; 198 | This macro is almost equivalent to m-plus for the parser-m monad. The difference is that 199 | it defers evaluation of whatever variables it receives, so that it accepts expressions 200 | containing unbound variables that are defined later." 201 | [& subrules] 202 | `(with-monad parser-m 203 | (fn [state#] 204 | ((~'m-plus ~@subrules) state#)))) 205 | 206 | (defn opt 207 | "Creates a rule that is the optional form of the subrule. It always succeeds. Its result 208 | is either the subrule's (if the subrule succeeds), or else its product is nil, and the 209 | rule acts as the emptiness rule. 210 | (def a (opt b)) would be equivalent to the EBNF: 211 | a = b?;" 212 | [subrule] 213 | (with-monad parser-m 214 | (m-plus subrule emptiness))) 215 | 216 | (defmacro invisi-conc 217 | "Like conc, only that the product is the first subrule's product only, not a vector of 218 | all the products of the subrules--effectively hiding the products of the other subrules. 219 | The rest of the subrules consume tokens too; their products simply aren't accessible. 220 | This is useful for applying set-info and update-info to a rule, without having to deal 221 | with set-info or update-info's products." 222 | [first-subrule & rest-subrules] 223 | `(semantics (conc ~first-subrule ~@rest-subrules) first)) 224 | 225 | (defn lit-conc-seq 226 | "A convenience function: it creates a rule that is the concatenation of the literals 227 | formed from the given sequence of literal tokens. 228 | (def a (lit-conc-seq [\"a\" \"b\" \"c\"])) would be equivalent to the EBNF: 229 | a = \"a\", \"b\", \"c\"; 230 | The function has an optional argument: a rule-making function. By default it is the lit 231 | function. This is the function that is used to create the literal rules from each element 232 | in the given token sequence." 233 | ([token-seq] 234 | (lit-conc-seq token-seq lit)) 235 | ([token-seq rule-maker] 236 | (with-monad parser-m 237 | (m-seq (map rule-maker token-seq))))) 238 | 239 | (defn lit-alt-seq 240 | "A convenience function: it creates a rule that is the alternation of the literals 241 | formed from the given sequence of literal tokens. 242 | (def a (lit-alt-seq [\"a\" \"b\" \"c\"])) would be equivalent to the EBNF: 243 | a = \"a\" | \"b\" | \"c\";" 244 | ([token-seq] 245 | (lit-alt-seq token-seq lit)) 246 | ([token-seq rule-maker] 247 | (with-monad parser-m 248 | (apply m-plus (map rule-maker token-seq))))) 249 | 250 | (declare rep+) 251 | 252 | (defn rep* 253 | "Creates a rule that is the zero-or-more greedy repetition of the given subrule. It 254 | always succeeds. It consumes tokens with its subrule until its subrule fails. 255 | Its result is the sequence of results from the subrule's repetitions, (or nil if the 256 | subrule fails immediately). 257 | (def a (rep* b)) is equivalent to the EBNF: 258 | a = {b}; 259 | The new rule's products would be either the vector [b-product ...] for how many matches 260 | of b were found, or nil if there was no match. (Note that this means that, in the latter 261 | case, the result would be [nil given-state].) The new rule can never simply return nil." 262 | [subrule] 263 | (fn [state] 264 | (loop [cur-product [], cur-state state] 265 | (if-let [[subproduct substate] (subrule cur-state)] 266 | (if (seq (*remainder-accessor* substate)) 267 | (recur (conj cur-product subproduct) substate) 268 | [(conj cur-product subproduct) substate]) 269 | [(if (not= cur-product []) cur-product) cur-state])))) 270 | ; The following code was used until I found that the mutually recursive calls to rep+ 271 | ; resulted in an easily inflated function call stack. 272 | ; (opt (rep+ subrule))) 273 | 274 | (defn rep+ 275 | "Creates a rule that is the zero-or-more greedy repetition of the given subrule. It 276 | fails only when its subrule fails immediately. It consumes tokens with its subrule until 277 | its subrule fails. Its result is the sequence of results from the subrule's repetitions. 278 | (def a (rep* b)) is equivalent to the EBNF: 279 | a = {b}-; 280 | The new rule's products would be the vector [b-product ...] for how many matches 281 | of b were found. If there was no match, then the rule fails." 282 | [subrule] 283 | (complex [first-product subrule, rest-products (rep* subrule)] 284 | (vec (cons first-product rest-products)))) 285 | ; See note at rep*. 286 | ; (complex [cur-remainder get-remainder 287 | ; :when (seq cur-remainder) 288 | ; first-subproduct subrule 289 | ; rest-subproducts (rep* subrule)] 290 | ; (cons first-subproduct rest-subproducts))) 291 | 292 | (defn except 293 | "Creates a rule that is the exception from the first given subrules with the second given 294 | subrule--that is, it accepts only tokens that fulfill the first subrule but fails the 295 | second of the subrules. 296 | (def a (except b c)) would be equivalent to the EBNF 297 | a = b - c; 298 | The new rule's products would be b-product. If b fails or c succeeds, then nil is simply 299 | returned." 300 | [minuend subtrahend] 301 | (complex [state (fetch-state) 302 | minuend-product minuend 303 | :when (not (subtrahend state))] 304 | minuend-product)) 305 | 306 | (defn rep-predicate 307 | "Like the rep* function, only that the number of times that the subrule is fulfilled must 308 | fulfill the given factor-predicate function." 309 | [factor-predicate subrule] 310 | (validate (rep* subrule) (comp factor-predicate count))) 311 | 312 | (defn rep= 313 | "Creates a rule that is the greedy repetition of the given subrule by the given factor (a 314 | positive integer)--that is, it eats up all the tokens that fulfill the subrule, and it 315 | then succeeds only if the number of times the subrule was fulfilled is equal to the 316 | given factor, no more and no less. 317 | (rep= 3 :a) would eat the first three tokens of [:a :a :a :b] and return: 318 | [[:a :a :a] (list :a :b)]. 319 | (rep= 3 :a) would eat the first four tokens of [:a :a :a :a :b] and fail." 320 | [factor subrule] 321 | (rep-predicate (partial = factor) subrule)) 322 | 323 | (defn rep< 324 | "A similiar function to rep=, only that the instead the new rule succeeds if the number 325 | of times that the subrule is fulfilled is less than and not equal to the given factor." 326 | [factor subrule] 327 | (rep-predicate (partial > factor) subrule)) 328 | 329 | (defn rep<= 330 | "A similiar function to rep=, only that the instead the new rule succeeds if the number 331 | of times that the subrule is fulfilled is less than or equal to the given factor." 332 | [factor subrule] 333 | (rep-predicate (partial >= factor) subrule)) 334 | 335 | (defn factor= 336 | "Creates a rule that is the syntactic factor (that is, a non-greedy repetition) of the 337 | given subrule by a given integer--that is, it is equivalent to the subrule replicated by 338 | 1, 2, etc. times and then concatenated. 339 | (def a (factor= n b)) would be equivalent to the EBNF 340 | a = n * b; 341 | The new rule's products would be b-product. If b fails below n times, then nil is simply 342 | returned. 343 | (factor= 3 :a) would eat the first three tokens [:a :a :a :a :b] and return: 344 | [[:a :a :a] (list :a :b)]. 345 | (factor= 3 :a) would eat the first three tokens [:a :a :b] and fail." 346 | [factor subrule] 347 | (with-monad parser-m 348 | (m-seq (replicate factor subrule)))) 349 | 350 | (defn factor< 351 | "Same as the factor= function, except that the new rule eats up tokens only until the 352 | given subrule is fulfilled one less times than the factor. The new rule would never fail. 353 | (factor< 3 :a) would eat the first two tokens [:a :a :a :a :b] and return: 354 | [[:a :a] (list :a :a :b)]. 355 | (factor< 3 :a) would eat the first three tokens [:b] and return: 356 | [nil (list :b)]" 357 | [factor subrule] 358 | (alt (factor= (dec factor) subrule) (rep< factor subrule))) 359 | 360 | (defn factor<= 361 | "Same as the factor= function, except that the new rule always succeeds, consuming tokens 362 | until the subrule is fulfilled the same amount of times as the given factor. The new rule 363 | would never fail. 364 | (factor<= 3 :a) would eat the first two tokens [:a :a :a :a :b] and return: 365 | [[:a :a :a] (list :a :b)]. 366 | (factor<= 3 :a) would eat the first three tokens [:b] and return: 367 | [nil (list :b)]" 368 | [factor subrule] 369 | (alt (factor= factor subrule) (rep< factor subrule))) 370 | 371 | (defn failpoint 372 | "Creates a rule that applies a failpoint to a subrule. When the subrule fails—i.e., it 373 | returns nil—then the failure hook function is called with one argument, the state at time 374 | of failure." 375 | [subrule failure-hook] 376 | (fn [state] 377 | (if-let [result (subrule state)] 378 | result 379 | (failure-hook (*remainder-accessor* state) state)))) 380 | 381 | (defmacro effects 382 | "Creates a rule that calls the lists given in its body for side effects. It does not 383 | consume any tokens or modify the state in any other way." 384 | [& effect-body] 385 | `(fn [state#] 386 | [((fn [] ~@effect-body)) state#])) 387 | 388 | (defn intercept 389 | "This rule is intended for intercepting and continuing exceptions and errors. 390 | It creates a rule that calls the intercept hook. The intercept hook is a function that 391 | receives only one argument: a function to be called with no arguments that calls the 392 | subrule with the current state. If you don't call this argument in the intercept hook, the 393 | subrule will not be called at all. The result of the whole rule will be directly what the 394 | product of the intercept-hook is. Here's an example of intended usage: 395 | intercept-rule (p/intercept subrule-that-can-throw-an-exception 396 | (fn [rule-call] 397 | (try (rule-call) 398 | (catch Exception e (throw another-exception)))))" 399 | [subrule intercept-hook] 400 | (fn [state] (intercept-hook (partial subrule state)))) 401 | 402 | (defn validate-state 403 | "Creates a rule from attaching a state-validating function to the given subrule--that 404 | is, any products of the subrule must fulfill the validator function. 405 | (def a (validate-state b validator)) says that the rule a succeeds only when b succeeds 406 | and also when the evaluated value of (validator b-state) is true. The new rule's product 407 | would be b-product." 408 | [subrule validator] 409 | (complex [subproduct subrule, substate get-state, :when (validator substate)] 410 | subproduct)) 411 | 412 | (defn validate-remainder 413 | "Creates a rule from attaching a remainder-validating function to the given subrule--that 414 | is, any products of the subrule must fulfill the validator function. 415 | (def a (validate-remainder b validator)) says that the rule a succeeds only when b 416 | succeeds and also when the evaluated value of (validator b-remainder) is true. The new 417 | rule's product would be b-product." 418 | [subrule validator] 419 | (complex [subproduct subrule, subremainder get-remainder, :when (validator subremainder)] 420 | subproduct)) 421 | 422 | (defn rule-match 423 | "Creates a function that tries to completely match the given rule to the given state, with 424 | no remainder left. 425 | - If (rule given-state) fails, then (failure-fn given-state) is called. 426 | - If the remainder of (rule given-state) is not empty, then 427 | (incomplete-fn given-state new-state-after-rule) is called. 428 | - If the new remainder is empty, then the product of the rule is returned." 429 | [rule failure-fn incomplete-fn state] 430 | (if-let [[product new-state] (rule state)] 431 | (if (empty? (*remainder-accessor* new-state)) 432 | product 433 | (incomplete-fn state new-state)) 434 | (failure-fn state))) 435 | 436 | (defn rule-matcher 437 | "DEPRECATED: Use rule-match instead. 438 | Creates a function that tries to completely match the given rule to the given state, with 439 | no remainder left. 440 | - If (rule given-state) fails, then (failure-fn given-state) is called. 441 | - If the remainder of (rule given-state) is not empty, then 442 | (incomplete-fn given-state new-state-after-rule) is called. 443 | - If the new remainder is empty, then the product of the rule is returned." 444 | [rule failure-fn incomplete-fn] 445 | (partial rule-match rule failure-fn incomplete-fn)) 446 | -------------------------------------------------------------------------------- /src/name/choi/joshua/fnparse/json.clj: -------------------------------------------------------------------------------- 1 | (ns name.choi.joshua.fnparse.json 2 | (:use name.choi.joshua.fnparse clojure.contrib.error-kit)) 3 | 4 | ;; These are some functions that the rules will use. A lot of these are 5 | ;; optional. 6 | 7 | ; A JSON node, which what the parsing will return in the end. 8 | (defstruct node-s :kind :content) 9 | 10 | ; The parsing state data structure. The remaining tokens are stored 11 | ; in :remainder, and the current column and line are stored in their 12 | ; respective fields. 13 | (defstruct state-s :remainder :column :line) 14 | 15 | (def remainder-a 16 | (accessor state-s :remainder)) 17 | 18 | (def make-node 19 | (partial struct node-s)) 20 | 21 | (def make-scalar-node 22 | (partial make-node :scalar)) 23 | 24 | (def make-array-node 25 | (partial make-node :array)) 26 | 27 | (def make-object-node 28 | (partial make-node :object)) 29 | 30 | (def apply-str 31 | (partial apply str)) 32 | 33 | ;; These two functions are given a rule and make it so that it 34 | ;; increments the current column (or the current line). 35 | 36 | (defn- nb-char [subrule] 37 | (invisi-conc subrule (update-info :column inc))) 38 | 39 | (def nb-char-lit 40 | (comp nb-char lit)) ; lit is a FnParse function that creates a literal 41 | ; rule. 42 | 43 | (defn- b-char [subrule] 44 | (invisi-conc subrule (update-info :line inc))) 45 | 46 | ;; A couple of parse errors have been put here and there. It's nowhere 47 | ;; near complete, but rather it's to show examples of how to implement 48 | ;; errors. 49 | 50 | (deferror parse-error [] [state message message-args] 51 | {:msg (str (format "JSON error at line %s, column %s: " 52 | (:line state) (:column state)) 53 | (apply format message message-args)) 54 | :unhandled (throw-msg Exception)}) 55 | 56 | (defn- expectation-error-fn [expectation] 57 | (fn [remainder state] 58 | (raise parse-error state "%s expected where \"%s\" is" 59 | [expectation (or (first remainder) "the end of the file")]))) 60 | 61 | ;; And here are where this parser's rules are defined. 62 | 63 | (def string-delimiter 64 | (nb-char-lit \")) 65 | 66 | (def escape-indicator 67 | (nb-char-lit \\)) 68 | 69 | (def false-lit 70 | (constant-semantics (lit-conc-seq "false" nb-char-lit) 71 | (make-scalar-node false))) 72 | 73 | (def true-lit 74 | (constant-semantics (lit-conc-seq "true" nb-char-lit) 75 | (make-scalar-node true))) 76 | 77 | (def null-lit 78 | (constant-semantics (lit-conc-seq "null" nb-char-lit) 79 | (make-scalar-node nil))) 80 | 81 | (def keyword-lit (alt false-lit true-lit null-lit)) 82 | 83 | (def space (nb-char-lit \space)) 84 | 85 | (def tab (nb-char-lit \tab)) 86 | 87 | (def newline-lit (lit \newline)) 88 | 89 | (def return-lit (lit \return)) 90 | 91 | (def line-break (b-char (rep+ (alt newline-lit return-lit)))) 92 | 93 | (def json-char (alt line-break (nb-char anything))) 94 | 95 | (def ws (constant-semantics (rep* (alt space tab line-break)) :ws)) 96 | 97 | (def begin-array 98 | (constant-semantics (conc ws (nb-char-lit \[) ws) :begin-array)) 99 | (def end-array 100 | (constant-semantics (conc ws (nb-char-lit \]) ws) :end-array)) 101 | 102 | (def begin-object 103 | (constant-semantics (conc ws (nb-char-lit \{) ws) :begin-object)) 104 | 105 | (def end-object 106 | (constant-semantics (conc ws (nb-char-lit \}) ws) :end-object)) 107 | 108 | (def name-separator 109 | (constant-semantics (conc ws (nb-char-lit \:) ws) :name-separator)) 110 | 111 | (def value-separator 112 | (constant-semantics (conc ws (nb-char-lit \,) ws) :value-separator)) 113 | 114 | (def minus-sign (nb-char-lit \-)) 115 | 116 | (def plus-sign (nb-char-lit \+)) 117 | 118 | (def decimal-point (nb-char-lit \.)) 119 | 120 | (def exponential-sign (lit-alt-seq "eE" nb-char-lit)) 121 | 122 | (def zero-digit (nb-char-lit \0)) 123 | 124 | (def nonzero-decimal-digit (lit-alt-seq "123456789" nb-char-lit)) 125 | 126 | (def decimal-digit (alt zero-digit nonzero-decimal-digit)) 127 | 128 | (def fractional-part (conc decimal-point (rep* decimal-digit))) 129 | 130 | (def exponential-part 131 | (conc exponential-sign (opt (alt plus-sign minus-sign)) 132 | (failpoint (rep+ decimal-digit) 133 | (expectation-error-fn 134 | (str "in number literal, after an exponent sign, decimal" 135 | "digit"))))) 136 | 137 | (def number-lit 138 | (complex [minus (opt minus-sign) 139 | above-one (alt zero-digit (rep+ nonzero-decimal-digit)) 140 | below-one (opt fractional-part) 141 | power (opt exponential-part)] 142 | (-> [minus above-one below-one power] flatten apply-str 143 | Double/parseDouble 144 | ((if (or below-one power) identity int)) 145 | make-scalar-node))) 146 | 147 | (def hexadecimal-digit 148 | (alt decimal-digit (lit-alt-seq "ABCDEF" nb-char-lit))) 149 | 150 | (def unescaped-char 151 | (except json-char (alt escape-indicator string-delimiter))) 152 | 153 | (def unicode-char-sequence 154 | (complex [_ (nb-char-lit \u) 155 | digits (factor= 4 156 | (failpoint hexadecimal-digit 157 | (expectation-error-fn "hexadecimal digit")))] 158 | (-> digits apply-str (Integer/parseInt 16) char))) 159 | 160 | (def escaped-characters 161 | {\\ \\, \/ \/, \b \backspace, \f \formfeed, \n \newline, \r \return, 162 | \t \tab}) 163 | 164 | (def normal-escape-sequence 165 | (semantics (lit-alt-seq (keys escaped-characters) nb-char-lit) 166 | escaped-characters)) 167 | 168 | (def escape-sequence 169 | (complex [_ escape-indicator 170 | character (alt unicode-char-sequence 171 | normal-escape-sequence)] 172 | character)) 173 | 174 | (def string-char 175 | (alt escape-sequence unescaped-char)) 176 | 177 | (def string-lit 178 | (complex [_ string-delimiter 179 | contents (rep* string-char) 180 | _ string-delimiter] 181 | (-> contents apply-str make-scalar-node))) 182 | 183 | (declare array) 184 | 185 | (declare object) 186 | 187 | (def value (alt string-lit number-lit keyword-lit array object)) 188 | 189 | (def additional-value 190 | (complex [_ value-separator, content value] content)) 191 | 192 | (def array-contents 193 | (complex [first-value value, rest-values (rep* additional-value)] 194 | (cons first-value rest-values))) 195 | 196 | (def array 197 | (complex [_ begin-array 198 | contents (opt array-contents) 199 | _ (failpoint end-array 200 | (expectation-error-fn "an array is unclosed; \"]\""))] 201 | (-> contents vec make-array-node))) 202 | 203 | (def entry 204 | (complex [entry-key string-lit, _ name-separator, entry-val value] 205 | [entry-key entry-val])) 206 | 207 | (def additional-entry 208 | (complex [_ value-separator, content entry] 209 | content)) 210 | 211 | (def object-contents 212 | (complex [first-entry entry, rest-entries (rep* additional-entry)] 213 | (cons first-entry rest-entries))) 214 | 215 | (def object 216 | (complex [_ begin-object 217 | contents object-contents 218 | _ (failpoint end-object 219 | (expectation-error-fn 220 | (str "either \"}\" or another object entry (which " 221 | "always starts with a string)")))] 222 | (struct node-s :object (into {} contents)))) 223 | 224 | (def text (alt object array)) ; The root rule 225 | 226 | ;; The functions below uses the rules to parse strings. 227 | 228 | (defn parse [tokens] 229 | (binding [*remainder-accessor* remainder-a] ; this is completely 230 | ; optional 231 | (rule-match text 232 | #(raise parse-error % "invalid document \"%s\"" 233 | (apply-str (remainder-a %))) 234 | #(raise parse-error %2 "leftover data after a valid node \"%s\"" 235 | (apply-str (remainder-a %2))) 236 | (struct state-s tokens 0 0)))) 237 | ; The call to rule-match above is equivalent to the stuff below: 238 | ; (let [[product state :as result] 239 | ; (text (struct state-s tokens 0 0))] 240 | ; (if (nil? result) 241 | ; (raise parse-error "invalid document \"%s\"" 242 | ; (apply-str tokens)) 243 | ; (if-let [remainder (seq (remainder-a state))] 244 | ; product 245 | ; (raise parse-error "leftover data after a valid node \"%s\"" 246 | ; (apply-str remainder))))) 247 | 248 | ;; The functions below just convert JSON nodes into Clojure strings, 249 | ;; vectors, and maps. 250 | 251 | (defmulti represent :kind) 252 | 253 | (defmethod represent :object [node] 254 | (into {} 255 | (map #(vector (represent (key %)) (represent (val %))) 256 | (:content node)))) 257 | 258 | (defmethod represent :array [node] 259 | (vec (map #(represent %) (:content node)))) 260 | 261 | (defmethod represent :scalar [node] 262 | (:content node)) 263 | 264 | (def load-stream (comp represent parse)) 265 | -------------------------------------------------------------------------------- /test/name/choi/joshua/fnparse/test_parse.clj: -------------------------------------------------------------------------------- 1 | (ns name.choi.joshua.fnparse.test-parse 2 | (:use clojure.test clojure.algo.monads) 3 | (:require [name.choi.joshua.fnparse :as p])) 4 | 5 | (defstruct state-s :remainder :column) 6 | (def make-state (partial struct state-s)) 7 | 8 | (defn throw-arg [fmt & args] 9 | (throw (IllegalArgumentException. (apply format fmt args)))) 10 | 11 | (deftest emptiness 12 | (is (= (p/emptiness {:remainder (list "A" "B" "C")}) 13 | [nil {:remainder (list "A" "B" "C")}]) 14 | "emptiness rule matches emptiness")) 15 | 16 | (deftest anything 17 | (is (= (p/anything {:remainder "ABC"}) 18 | [\A {:remainder (seq "BC")}]) 19 | "anything rule matches first token") 20 | (is (nil? (p/anything (make-state nil))) 21 | "anything rule fails with no tokens left") 22 | (is (= ((p/rep* p/anything) (make-state "ABCD")) 23 | [(seq "ABCD") (make-state nil)]) 24 | "repeated anything rule does not create infinite loop")) 25 | 26 | (deftest term 27 | (is (= ((p/term (partial = "true")) {:remainder ["true" "THEN"]}) 28 | ["true" {:remainder (list "THEN")}]) 29 | "created terminal rule works when first token fulfills validator") 30 | (is (nil? ((p/term (partial = "true")) {:remainder ["false" "THEN"]})) 31 | "created terminal rule fails when first token fails validator") 32 | (is (= ((p/term (partial = "true")) {:remainder ["true"]}) 33 | ["true" {:remainder nil}]) 34 | "created terminal rule works when no remainder")) 35 | 36 | (deftest lit 37 | (is (= ((p/lit "true") {:remainder ["true" "THEN"]}) 38 | ["true" {:remainder (list "THEN")}]) 39 | "created literal rule works when literal token present") 40 | (is (nil? ((p/lit "true") {:remainder ["false" "THEN"]})) 41 | "created literal rule fails when literal token not present")) 42 | 43 | (deftest re-term 44 | (is (= ((p/re-term #"\s*true\s*") {:remainder [" true" "THEN"]}) 45 | [" true" {:remainder (list "THEN")}]) 46 | "created re-term rule works when first token matches regex") 47 | (is (nil? ((p/re-term #"\s*true\s*") {:remainder ["false" "THEN"]})) 48 | "created re-term rule fails when first token does not match regex") 49 | (is (nil? ((p/re-term #"\s*true\s*") {:remainder nil})) 50 | "created re-term rule fails when no tokens are left")) 51 | 52 | (deftest followed-by 53 | (is (= ((p/followed-by (p/lit \a)) {:remainder "abc"}) [\a {:remainder "abc"}])) 54 | (is (nil? ((p/followed-by (p/lit \a)) {:remainder "bcd"})))) 55 | 56 | (deftest not-followed-by 57 | (is (= ((p/not-followed-by (p/lit \a)) {:remainder "bcd"}) [true {:remainder "bcd"}])) 58 | (is (nil? ((p/not-followed-by (p/lit \a)) {:remainder "abc"})))) 59 | 60 | (deftest complex 61 | (is (= ((p/complex [a (p/lit "hi")] (str a "!")) {:remainder ["hi" "THEN"]}) 62 | ["hi!" {:remainder (list "THEN")}]) 63 | "created complex rule applies semantic hook to valid result of given rule") 64 | (is (nil? ((p/complex [a (p/lit "hi")] (str a \!)) {:remainder ["RST"]})) 65 | "created complex rule fails when a given subrule fails") 66 | (is (= ((p/complex [a (p/lit "hi")] (str a \!)) {:remainder ["hi" "THEN"], :a "hi"}) 67 | ["hi!" {:remainder (list "THEN"), :a "hi"}]) 68 | "created complex rule passes rest of state to subrule") 69 | (is (= ((p/complex [a (p/lit "hi") b (p/lit "THEN")] [(str a "!") b]) 70 | {:remainder ["hi" "THEN" "bye"]}) 71 | [["hi!" "THEN"] {:remainder (list "bye")}]) 72 | "created complex rule succeeds when all subrules fulfilled in order") 73 | (is (nil? ((p/complex [a (p/lit "hi") b (p/lit "THEN")] [(str a "!") b]) 74 | {:remainder ["hi" "bye" "boom"]})) 75 | "created complex rule fails when one subrule fails")) 76 | 77 | (deftest semantics 78 | (is (= ((p/semantics (p/lit "hi") #(str % "!")) {:remainder ["hi" "THEN"]}) 79 | ["hi!" {:remainder (list "THEN")}]) 80 | "created simple semantic rule applies semantic hook to valid result of given rule")) 81 | 82 | (deftest constant-semantics 83 | (is (= ((p/constant-semantics (p/lit "hi") {:a 1}) {:remainder ["hi" "THEN"]}) 84 | [{:a 1} {:remainder (list "THEN")}]) 85 | "created constant sem rule returns constant value when given subrule does not fail")) 86 | 87 | (deftest validate 88 | (is (= ((p/validate (p/lit "hi") (partial = "hi")) {:remainder ["hi" "THEN"]}) 89 | ["hi" {:remainder (list "THEN")}]) 90 | "created validator rule succeeds when given subrule and validator succeed") 91 | (is (nil? ((p/validate (p/lit "hi") (partial = "RST")) {:remainder ["RST"]})) 92 | "created validator rule fails when given subrule fails") 93 | (is (nil? ((p/validate (p/lit "hi") (partial = "hi")) {:remainder "hi"})) 94 | "created validator rule fails when given validator fails")) 95 | 96 | (deftest get-remainder 97 | (is (= ((p/complex [remainder p/get-remainder] remainder) {:remainder ["hi" "THEN"]}) 98 | [["hi" "THEN"] {:remainder ["hi" "THEN"]}]))) 99 | 100 | (deftest remainder-peek 101 | (is (= (p/remainder-peek {:remainder (seq "ABC")}) 102 | [\A {:remainder (seq "ABC")}]))) 103 | 104 | (deftest conc 105 | (is (= ((p/conc (p/lit "hi") (p/lit "THEN")) {:remainder ["hi" "THEN" "bye"]}) 106 | [["hi" "THEN"] {:remainder (list "bye")}]) 107 | "created concatenated rule succeeds when all subrules fulfilled in order") 108 | (is (nil? ((p/conc (p/lit "hi") (p/lit "THEN")) {:remainder ["hi" "bye" "boom"]})) 109 | "created concatenated rule fails when one subrule fails")) 110 | 111 | (deftest alt 112 | (let [literal-true (p/lit "true") 113 | literal-false (p/lit "false") 114 | literal-boolean (p/alt literal-true literal-false)] 115 | (is (= (literal-boolean {:remainder ["false" "THEN"]}) 116 | ["false" {:remainder (list "THEN")}]) 117 | "created alternatives rule works with first valid rule product") 118 | (is (nil? (literal-boolean {:remainder ["aRSTIR"]})) 119 | "created alternatives rule fails when no valid rule product present"))) 120 | 121 | (deftest update-info 122 | (is (= ((p/update-info :column inc) (make-state [\a] 3)) 123 | [3 (make-state [\a] 4)]))) 124 | 125 | (deftest invisi-conc 126 | (is (= ((p/invisi-conc (p/lit \a) (p/update-info :column inc)) (make-state "abc" 3)) 127 | [\a (make-state (seq "bc") 4)]))) 128 | 129 | (deftest lit-conc-seq 130 | (is (= ((p/lit-conc-seq "THEN") {:remainder "THEN print 42;"}) 131 | [(vec "THEN") {:remainder (seq " print 42;")}]) 132 | "created literal-sequence rule is based on sequence of given token sequencible") 133 | (is (= ((p/lit-conc-seq "THEN" (fn [lit-token] 134 | (p/invisi-conc (p/lit lit-token) 135 | (p/update-info :column inc)))) 136 | {:remainder "THEN print 42;", :column 1}) 137 | [(vec "THEN") {:remainder (seq " print 42;"), :column 5}]) 138 | "created literal-sequence rule uses given rule-maker")) 139 | 140 | (deftest lit-alt-seq 141 | (is (= ((p/lit-alt-seq "ABCD") {:remainder (seq "B 2")}) 142 | [\B {:remainder (seq " 2")}]) 143 | (str "created literal-alternative-sequence rule works when literal symbol present in" 144 | "sequence")) 145 | (is (nil? ((p/lit-alt-seq "ABCD") {:remainder (seq "E 2")})) 146 | (str "created literal-alternative-sequence rule fails when literal symbol not present" 147 | "in sequence")) 148 | (is (= ((p/lit-alt-seq "ABCD" (fn [lit-token] 149 | (p/invisi-conc (p/lit lit-token) 150 | (p/update-info :column inc)))) 151 | {:remainder "B 2", :column 1}) 152 | [\B {:remainder (seq " 2"), :column 2}]) 153 | "created literal-alternative-sequence rule uses given rule-maker")) 154 | 155 | (deftest opt 156 | (let [opt-true (p/opt (p/lit "true"))] 157 | (is (= (opt-true {:remainder ["true" "THEN"]}) 158 | ["true" {:remainder (list "THEN")}]) 159 | "created option rule works when symbol present") 160 | (is (= (opt-true {:remainder (list "THEN")}) 161 | [nil {:remainder (list "THEN")}]) 162 | "created option rule works when symbol absent"))) 163 | 164 | (deftest rep* 165 | (let [rep*-true (p/rep* (p/lit true)) 166 | rep*-untrue (p/rep* (p/except p/anything (p/lit true)))] 167 | (is (= (rep*-true {:remainder [true "THEN"], :a 3}) 168 | [[true] {:remainder (list "THEN"), :a 3}]) 169 | "created zero-or-more-repetition rule works when symbol present singularly") 170 | (is (= (rep*-true {:remainder [true true true "THEN"], :a 3}) 171 | [[true true true] {:remainder (list "THEN"), :a 3}]) 172 | "created zero-or-more-repetition rule works when symbol present multiply") 173 | (is (= (rep*-true {:remainder ["THEN"], :a 3}) 174 | [nil {:remainder (list "THEN"), :a 3}]) 175 | "created zero-or-more-repetition rule works when symbol absent") 176 | (is (= (rep*-true {:remainder [true true true]}) 177 | [[true true true] {:remainder nil}]) 178 | "created zero-or-more-repetition rule works with no remainder after symbols") 179 | (is (= (rep*-true {:remainder nil}) 180 | [nil {:remainder nil}]) 181 | "created zero-or-more-repetition rule works with no remainder") 182 | (is (= (rep*-untrue {:remainder [false false]}) 183 | [[false false] {:remainder nil}]) 184 | "created zero-or-more-repetition negative rule works consuming up to end") 185 | (is (= (rep*-untrue {:remainder [false false true]}) 186 | [[false false] {:remainder [true]}]) 187 | "created zero-or-more-repetition negative rule works consuming until exception") 188 | (is (= (rep*-untrue {:remainder nil}) 189 | [nil {:remainder nil}]) 190 | "created zero-or-more-repetition negative rule works with no remainder"))) 191 | 192 | (deftest rep+ 193 | (let [rep+-true (p/rep+ (p/lit true))] 194 | (is (= (rep+-true {:remainder [true "THEN"]}) 195 | [[true] {:remainder (list "THEN")}]) 196 | "created one-or-more-repetition rule works when symbol present singularly") 197 | (is (= (rep+-true {:remainder [true true true "THEN"]}) 198 | [[true true true] {:remainder (list "THEN")}]) 199 | "created one-or-more-repetition rule works when symbol present multiply") 200 | (is (nil? (rep+-true {:remainder (list "THEN")})) 201 | "created one-or-more-repetition rule fails when symbol absent"))) 202 | 203 | (deftest except 204 | (let [except-rule (p/except (p/lit-alt-seq "ABC") (p/alt (p/lit \B) (p/lit \C)))] 205 | (is (= (except-rule {:remainder (seq "ABC"), :a 1}) [\A {:remainder (seq "BC"), :a 1}]) 206 | "created exception rule works when symbol is not one of the syntatic exceptions") 207 | (is (nil? (except-rule {:remainder (seq "BAC")})) 208 | "created exception rule fails when symbol is one of the syntactic exceptions") 209 | (is (nil? (except-rule {:remainder (seq "DAB")})) 210 | "created exception rule fails when symbol does not fulfill subrule"))) 211 | 212 | (deftest factor= 213 | (let [tested-rule-3 (p/factor= 3 (p/lit "A")), tested-rule-0 (p/factor= 0 (p/lit "A"))] 214 | (is (= (tested-rule-3 {:remainder (list "A" "A" "A" "A" "C")}) 215 | [["A" "A" "A"] {:remainder (list "A" "C")}]) 216 | (str "created factor= rule works when symbol fulfills all subrule multiples and" 217 | "leaves strict remainder")) 218 | (is (= (tested-rule-3 {:remainder (list "A" "A" "A" "C")}) 219 | [["A" "A" "A"] {:remainder (list "C")}]) 220 | "created factor= rule works when symbol fulfills all subrule multiples only") 221 | (is (= (tested-rule-3 {:remainder (list "A" "A" "C")}) nil) 222 | "created factor= rule fails when symbol does not fulfill all subrule multiples") 223 | (is (= (tested-rule-3 {:remainder (list "D" "A" "B")}) nil) 224 | "created factor= rule fails when symbol does not fulfill subrule at all") 225 | (is (= (tested-rule-0 {:remainder (list "D" "A" "B")}) 226 | [[] {:remainder (list "D" "A" "B")}]) 227 | "created factor= rule works when symbol fulfils no multiples and factor is zero"))) 228 | 229 | (deftest rep-predicate 230 | (let [tested-rule-fn (p/rep-predicate (partial > 3) (p/lit "A")) 231 | infinity-rule (p/rep-predicate (partial > Double/POSITIVE_INFINITY) (p/lit "A"))] 232 | (is (= (tested-rule-fn {:remainder (list "A" "A" "C")}) 233 | [["A" "A"] {:remainder (list "C")}]) 234 | "created rep rule works when predicate returns true") 235 | (is (nil? (tested-rule-fn {:remainder (list "A" "A" "A")})) 236 | "created rep rule fails when predicate returns false") 237 | (is (= (tested-rule-fn {:remainder (list "D" "A" "B")}) 238 | [nil {:remainder (list "D" "A" "B")}]) 239 | "created rep rule succeeds when symbol does not fulfill subrule at all"))) 240 | 241 | (deftest rep= 242 | (let [tested-rule-fn (p/rep= 3 (p/lit \A))] 243 | (is (= (tested-rule-fn {:remainder (seq "AAAC")}) 244 | [[\A \A \A] {:remainder (seq "C")}]) 245 | "created rep= rule works when symbol only fulfills all subrule multiples") 246 | (is (nil? (tested-rule-fn {:remainder (seq "AAAA")})) 247 | "created rep= rule fails when symbol exceeds subrule multiples") 248 | (is (nil? (tested-rule-fn {:remainder (seq "AAC")})) 249 | "created rep= rule fails when symbol does not fulfill all subrule multiples") 250 | (is (nil? (tested-rule-fn {:remainder (seq "DAB")})) 251 | "created rep= rule fails when symbol does not fulfill subrule at all"))) 252 | 253 | (deftest factor< 254 | (let [tested-rule (p/factor< 3 (p/lit \A))] 255 | (is (= (tested-rule {:remainder (seq "AAAAC")}) 256 | [[\A \A] {:remainder (seq "AAC")}]) 257 | (str "created factor< rule works when symbol fulfills all subrule multiples and" 258 | "leaves strict remainder")) 259 | (is (= (tested-rule {:remainder (seq "AAAC")}) 260 | [[\A \A] {:remainder (seq "AC")}]) 261 | "created factor< rule works when symbol fulfills all subrule multiples only") 262 | (is (= (tested-rule {:remainder (seq "AAC")}) [[\A \A] {:remainder (seq "C")}]) 263 | "created factor< rule works when symbol does not fulfill all subrule multiples") 264 | (is (= (tested-rule {:remainder (seq "DAB")}) 265 | [nil {:remainder (seq "DAB")}]) 266 | "created factor< rule works when symbol does not fulfill subrule at all"))) 267 | 268 | (deftest failpoint 269 | (let [exception-rule (p/failpoint (p/lit "A") 270 | (fn [remainder state] 271 | (throw-arg "ERROR %s at line %s" 272 | (first remainder) (:line state))))] 273 | (is (= (exception-rule {:remainder ["A"], :line 3}) ["A" {:remainder nil, :line 3}]) 274 | "failing rules succeed when their subrules are fulfilled") 275 | (is (thrown-with-msg? IllegalArgumentException #"ERROR B at line 3" 276 | (exception-rule {:remainder ["B"], :line 3}) 277 | "failing rules fail with given exceptions when their subrules fail")))) 278 | 279 | (deftest intercept 280 | (let [parse-error-rule (p/semantics (p/lit \A) (fn [_] (throw (Exception.)))) 281 | intercept-rule (p/intercept parse-error-rule 282 | (fn [rule-call] (try (rule-call) (catch Exception e :error))))] 283 | (is (= (intercept-rule (make-state "ABC")) :error)))) 284 | 285 | (deftest effects 286 | (let [rule (p/complex [subproduct (p/lit "A") 287 | line-number (p/get-info :line) 288 | effects (p/effects (println "!" subproduct) 289 | (println "YES" line-number))] 290 | subproduct)] 291 | (is (= (with-out-str 292 | (is (= (rule {:remainder ["A" "B"], :line 3}) 293 | ["A" {:remainder (list "B"), :line 3}]) 294 | "pre-effect rules succeed when their subrules are fulfilled")) 295 | "! A\nYES 3\n") 296 | "effect rule should call their effect and return the same state"))) 297 | 298 | (deftest remainder-bindings 299 | (binding [p/*remainder-accessor* identity 300 | p/*remainder-setter* #(identity %2)] 301 | (is (= ((p/lit \a) "abc") [\a (seq "bc")])))) 302 | 303 | (deftest rule-match 304 | (let [rule (p/lit "A") 305 | matcher (partial p/rule-match rule identity vector)] 306 | (is (= (matcher (make-state ["A"])) "A")) 307 | (is (= (matcher (make-state ["B"])) (make-state ["B"]))) 308 | (is (= (matcher (make-state ["A" "B"])) [(make-state ["A" "B"]) (make-state ["B"])])))) 309 | 310 | (time (run-tests)) 311 | -------------------------------------------------------------------------------- /test/name/choi/joshua/fnparse/test_parse_json.clj: -------------------------------------------------------------------------------- 1 | (ns name.choi.joshua.fnparse.test-parse-json 2 | (:use clojure.test) 3 | (:require [name.choi.joshua.fnparse.json :as j])) 4 | 5 | (defstruct node-s :kind :content) 6 | (defstruct state-s :remainder :column :line) 7 | (def make-node (partial struct node-s)) 8 | (def make-state (partial struct state-s)) 9 | 10 | (deftest number-lit 11 | (is (= (j/number-lit (make-state "123]" 3 4)) 12 | [(make-node :scalar 123) (make-state (seq "]") 6 4)])) 13 | (is (= (j/number-lit (make-state "-123]" 3 4)) 14 | [(make-node :scalar -123) (make-state (seq "]") 7 4)])) 15 | (is (= (j/number-lit (make-state "-123e3]" 3 4)) 16 | [(make-node :scalar -123e3) (make-state (seq "]") 9 4)])) 17 | (is (= (j/number-lit (make-state "-123.9e3]" 3 4)) 18 | [(make-node :scalar -123.9e3) (make-state (seq "]") 11 4)]))) 19 | ; (is (thrown-with-msg? Exception 20 | ; #"JSON error at line 4, column 10: in number literal, after an exponent sign, decimal digit expected where \"e\" is" 21 | ; (j/number-lit (make-state "-123.9ee3]" 3 4))))) 22 | 23 | (deftest unicode-char-sequence 24 | (is (= (j/unicode-char-sequence (make-state "u11A3a\"]" 3 4)) 25 | [\u11A3 (make-state (seq "a\"]") 8 4)])) 26 | (is (thrown-with-msg? Exception 27 | #"JSON error at line 4, column 7: hexadecimal digit expected where \"T\" is" 28 | (j/unicode-char-sequence (make-state "u11ATa\"]" 3 4))))) 29 | 30 | (deftest escape-sequence 31 | (is (= (j/escape-sequence (make-state "\\\\a\"]" 3 4)) 32 | [\\ (make-state (seq "a\"]") 5 4)])) 33 | (is (= (j/escape-sequence (make-state "\\u1111a\"]" 3 4)) 34 | [\u1111 (make-state (seq "a\"]") 9 4)]))) 35 | 36 | (deftest string-lit 37 | (is (= (j/string-lit (make-state "\"hello\"]" 3 4)) 38 | [(make-node :scalar "hello") (make-state (seq "]") 10 4)])) 39 | (is (= (j/string-lit (make-state "\"hello\\u1111\"]" 3 4)) 40 | [(make-node :scalar "hello\u1111") (make-state (seq "]") 16 4)]))) 41 | 42 | (deftest entry 43 | (is (= (j/entry (make-state "\"hello\": 55}" 3 4)) 44 | [[(make-node :scalar "hello") (make-node :scalar 55)] 45 | (make-state (seq "}") 14 4)]))) 46 | 47 | (deftest additional-entry 48 | (is (= (j/additional-entry (make-state ", \"hello\": 55}" 3 4)) 49 | [[(make-node :scalar "hello") (make-node :scalar 55)] 50 | (make-state (seq "}") 16 4)]))) 51 | 52 | (deftest object 53 | (is (= (j/object (make-state "{\"hello\": 55}]" 3 4)) 54 | [(make-node :object {(make-node :scalar "hello") (make-node :scalar 55)}) 55 | (make-state (seq "]") 16 4)])) 56 | (is (= (j/object (make-state "{\"hello\": 55, \"B\": \"goodbye\"}]" 3 4)) 57 | [(make-node :object {(make-node :scalar "hello") (make-node :scalar 55) 58 | (make-node :scalar "B") (make-node :scalar "goodbye")}) 59 | (make-state (seq "]") 32 4)]))) 60 | 61 | (deftest load-stream 62 | (is (= (j/load-stream "[1, 2, 3]") [1 2 3]) 63 | "loading a flat array containing integers") 64 | ; (is (thrown-with-msg? Exception 65 | ; #"an array is unclosed; \"]\" expected where \"}\" is" 66 | ; (j/load-stream "[1, 2, 3}") [1 2 3]) 67 | ; "loading an improperly closed array") 68 | (is (= (j/load-stream "[\"a\", \"b\\n\", \"\\u1234\"]") 69 | ["a" "b\n" "\u1234"]) 70 | "loading a flat array containing strings") 71 | (is (= (j/load-stream "{\"a\": 1, \"b\\n\": 2, \"\\u1234\": 3}") 72 | {"a" 1, "b\n" 2, "\u1234" 3}) 73 | "loading a flat object containing strings and integers")) 74 | 75 | (time (run-tests)) 76 | --------------------------------------------------------------------------------