├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── atoms.go ├── atoms_test.go ├── composites.go ├── composites_test.go ├── examples ├── recur │ └── main.go ├── rule-engine │ └── main.go ├── scripted │ └── main.go ├── simple.lisp ├── simple │ └── main.go └── spec.lisp ├── func.go ├── func_test.go ├── go.mod ├── go.sum ├── reader.go ├── reader_test.go ├── reflect.go ├── reflect_test.go ├── repl ├── option.go └── repl.go ├── sabre.go ├── sabre_test.go ├── scope.go ├── scope_test.go ├── specials.go ├── specials_test.go ├── value.go └── value_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | bin/ 14 | .vscode/ 15 | .idea/ 16 | expt/ 17 | sabre.iml 18 | temp.lisp 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: '1.13' 3 | env: 4 | - GO111MODULE=on 5 | script: make clean test-verbose 6 | notifications: 7 | email: false 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.3.3 (2020-03-01) 4 | 5 | * Prevent special forms being passed as value at `Symbol.Eval` level. 6 | * Add checks for ambiguous arity detection in `MultiFn`. 7 | 8 | ## v0.3.2 (2020-02-28) 9 | 10 | * Expand macro-expansions in place during 'List.parse()' 11 | * Add `MacroExpand` public function 12 | * Prevent macros being passed as value at `Symbole.Eval` level. 13 | * All `Values` operations return `&List{}` as result instead of `Values`. 14 | 15 | ## v0.3.1 (2020-02-24) 16 | 17 | * Move `Slang` to separate [repository](https://github.com/spy16/slang) 18 | * Add support for member access using qualified symbols (i.e., `foo.Bar.Baz`). 19 | 20 | ## v0.3.0 (2020-02-22) 21 | 22 | * Add support for macros through `macro*` special form. 23 | * Use Macro support to add `defn` and `defmacro` macros. 24 | * Rewrite `core.lisp` with `defn` macro. 25 | 26 | ## v0.2.3 (2020-02-22) 27 | 28 | * Add support for custom special forms through `SpecialForm` type. 29 | * Update package documentation. 30 | * Remove all type specific functions in favour generic slang core. 31 | 32 | ## v0.2.2 (2020-02-21) 33 | 34 | * Add type init through Type.Invoke() method 35 | * Remove GoFunc in favor of Fn 36 | 37 | ## v0.2.1 (2020-02-19) 38 | 39 | * Add slang tests using lisp files (#8) 40 | * Added tests for function definitions 41 | * Improve function call reflection logic to handle error returns 42 | 43 | ## v0.2.0 (2020-02-18) 44 | 45 | * Add evaluation error with positional info 46 | * Add position info to Set, List, Vector, Symbol 47 | * Add slang runtime package, add generic repl package 48 | * Add support for variadic functions 49 | 50 | ## v0.1.3 (2020-02-04) 51 | 52 | * Add Values type and Seq types 53 | * Add let and throw special forms 54 | * Add support for multi-arity functions 55 | * Convert List, Set, Vector, Symbol types to struct 56 | * Modify List, Set, Vector types to embed Values type 57 | * Move special form functions into sabre root package 58 | * Add parent method to scope and modify def to apply at root scope 59 | 60 | ## v0.1.2 (2020-01-23) 61 | 62 | * Add working clojure style quote system 63 | * Move SpecialFn to sabre root package as GoFunc 64 | * remove redundant strictFn type 65 | 66 | ## v0.1.1 (2020-01-20) 67 | 68 | * Add error function and type functions 69 | * Add experimental Set implementation 70 | * Add special Nil type, add not & do core functions 71 | * Add type check and type init functions for all types 72 | * Add unit tests for all string and eval methods 73 | * Fix nested lambda binding issue 74 | * Split builtin functions into core package 75 | 76 | ## v0.1.0 (2020-01-18) 77 | 78 | * Fully working LISP reader. 79 | * Working Evaluation logic with no automatic type conversion. 80 | * Core functions `def`, `eval` and `fn` implemented. 81 | * Simple working REPL implemented. 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION="`git describe --abbrev=0 --tags`" 2 | COMMIT="`git rev-list -1 --abbrev-commit HEAD`" 3 | 4 | all: clean fmt test 5 | 6 | fmt: 7 | @echo "Formatting..." 8 | @goimports -l -w ./ 9 | 10 | clean: 11 | @echo "Cleaning up..." 12 | @go mod tidy -v 13 | 14 | test: 15 | @echo "Running tests..." 16 | @go test -cover ./... 17 | 18 | test-verbose: 19 | @echo "Running tests..." 20 | @go test -v -cover ./... 21 | 22 | benchmark: 23 | @echo "Running benchmarks..." 24 | @go test -benchmem -run="none" -bench="Benchmark.*" -v ./... 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sabre 2 | 3 | [![GoDoc](https://godoc.org/github.com/spy16/sabre?status.svg)](https://godoc.org/github.com/spy16/sabre) [![Go Report Card](https://goreportcard.com/badge/github.com/spy16/sabre)](https://goreportcard.com/report/github.com/spy16/sabre) [![Build Status](https://travis-ci.org/spy16/sabre.svg?branch=master)](https://travis-ci.org/spy16/sabre) 4 | 5 | **DEPRECATED**: *This repository is deprecated in favour much better [slurp](//github.com/spy16/slurp) project and will be archived/removed soon.* 6 | 7 | Sabre is highly customizable, embeddable LISP engine for Go. 8 | 9 | Check out [Slang](https://github.com/spy16/slang) for a tiny LISP written using *Sabre*. 10 | 11 | ## Features 12 | 13 | * Highly Customizable reader/parser through a read table (Inspired by Clojure) (See [Reader](#reader)) 14 | * Built-in data types: nil, bool, string, number, character, keyword, symbol, list, vector, set, 15 | hash-map and module. 16 | * Multiple number formats supported: decimal, octal, hexadecimal, radix and scientific notations. 17 | * Full unicode support. Symbols can include unicode characters (Example: `find-δ`, `π` etc.) 18 | and `🧠`, `🏃` etc. (yes, smileys too). 19 | * Character Literals with support for: 20 | 1. simple literals (e.g., `\a` for `a`) 21 | 2. special literals (e.g., `\newline`, `\tab` etc.) 22 | 3. unicode literals (e.g., `\u00A5` for `¥` etc.) 23 | * Clojure style built-in special forms: `fn*`, `def`, `if`, `do`, `throw`, `let*` 24 | * Simple interface `sabre.Value` and optional `sabre.Invokable`, `sabre.Seq` interfaces for 25 | adding custom data types. (See [Evaluation](#evaluation)) 26 | * A macro system. 27 | 28 | > Please note that Sabre is _NOT_ an implementation of a particular LISP dialect. It provides 29 | > pieces that can be used to build a LISP dialect or can be used as a scripting layer. 30 | 31 | ## Usage 32 | 33 | What can you use it for? 34 | 35 | 1. Embedded script engine to provide dynamic behavior without requiring re-compilation 36 | of your application. 37 | 2. Business rule engine by exposing very specific & composable rule functions. 38 | 3. To build your own LISP dialect. 39 | 40 | > Sabre requires Go 1.13 or higher. 41 | 42 | ### As Embedded Script Engine 43 | 44 | Sabre has concept of `Scope` which is responsible for maintaining bindings. You can bind 45 | any Go value and access it using LISP code, which makes it possible to expose parts of your 46 | API and make it scriptable or build your own LISP dialect. Also, See [Extending](#extending) 47 | for more information on customizing the reader or eval. 48 | 49 | ```go 50 | package main 51 | 52 | import "github.com/spy16/sabre" 53 | 54 | func main() { 55 | scope := sabre.NewScope(nil) 56 | _ = scope.BindGo("inc", func(v int) int { return v+1 }) 57 | 58 | result, _ := sabre.ReadEvalStr(scope, "(inc 10)") 59 | fmt.Printf("Result: %v\n", result) // should print "Result: 11" 60 | } 61 | ``` 62 | 63 | ### Expose through a REPL 64 | 65 | Sabre comes with a tiny `repl` package that is very flexible and easy to setup 66 | to expose your LISP through a read-eval-print-loop. 67 | 68 | ```go 69 | package main 70 | 71 | import ( 72 | "context" 73 | 74 | "github.com/spy16/sabre" 75 | "github.com/spy16/sabre/repl" 76 | ) 77 | 78 | func main() { 79 | scope := sabre.NewScope(nil) 80 | scope.BindGo("inc", func(v int) int { return v+1 }) 81 | 82 | repl.New(scope, 83 | repl.WithBanner("Welcome to my own LISP!"), 84 | repl.WithPrompts("=>", "|"), 85 | // many more options available 86 | ).Loop(context.Background()) 87 | } 88 | ``` 89 | 90 | ### Standalone 91 | 92 | Sabre has a small reference LISP dialect named ***Slang*** (short for *Sabre Lang*) for 93 | which a standalone binary is available. Check out [Slang](https://github.com/spy16/slang) 94 | for instructions on installing *Slang*. 95 | 96 | ## Extending 97 | 98 | ### Reader 99 | 100 | Sabre reader is inspired by Clojure reader and uses a _read table_. Reader supports 101 | following forms: 102 | 103 | * Numbers: 104 | * Integers use `int64` Go representation and can be specified using decimal, binary 105 | hexadecimal or radix notations. (e.g., 123, -123, 0b101011, 0xAF, 2r10100, 8r126 etc.) 106 | * Floating point numbers use `float64` Go representation and can be specified using 107 | decimal notation or scientific notation. (e.g.: 3.1412, -1.234, 1e-5, 2e3, 1.5e3 etc.) 108 | * Characters: Characters use `rune` or `uint8` Go representation and can be written in 3 ways: 109 | * Simple: `\a`, `\λ`, `\β` etc. 110 | * Special: `\newline`, `\tab` etc. 111 | * Unicode: `\u1267` 112 | * Boolean: `true` or `false` are converted to `Bool` type. 113 | * Nil: `nil` is represented as a zero-allocation empty struct in Go. 114 | * Keywords: Keywords are like symbols but start with `:` and evaluate to themselves. 115 | * Symbols: Symbols can be used to name a value and can contain any Unicode symbol. 116 | * Lists: Lists are zero or more forms contained within parenthesis. (e.g., `(1 2 3)`, `(1 [])`). 117 | Evaluating a list leads to an invocation. 118 | * Vectors: Vectors are zero or more forms contained within brackets. (e.g., `[]`, `[1 2 3]`) 119 | * Sets: Set is a container for zero or more unique forms. (e.g. `#{1 2 3}`) 120 | * HashMaps: HashMap is a container for key-value pairs (e.g., `{:name "Bob" :age 10}`) 121 | 122 | Reader can be extended to add new syntactical features by adding _reader macros_ 123 | to the _read table_. _Reader Macros_ are implementations of `sabre.ReaderMacro` 124 | function type. _Except numbers and symbols, everything else supported by the reader 125 | is implemented using reader macros_. 126 | 127 | ### Evaluation 128 | 129 | * `Keyword`, `String`, `Int`, `Float`, `Character`, `Bool`, `nil`, `MultiFn`, 130 | `Fn`, `Type` and `Any` evaluate to themselves. 131 | * `Symbol` is resolved as follows: 132 | * If symbol has no `.`, symbol is directly used to lookup in current `Scope` 133 | to find the value. 134 | * If symbol is qualified (i.e., contains `.`), symbol is split using `.` as 135 | delimiter and first field is resolved as per previous rule and rest of the 136 | fields are recursively resolved as members. (For example, `foo.Bar.Baz`: `foo` 137 | is resolved from scope, `Bar` should be member of value of `foo`. And `Baz` 138 | should be member of value resolved for `foo.Bar`) 139 | * Evaluating `HashMap`, `Vector` & `Set` simply yields new hashmap, vector and set 140 | whose values are evaluated values contained in the original hashmaap, vector and set. 141 | * Evaluating `Module` evaluates all the forms in the module and returns the result 142 | of last evaluation. Any error stops the evaluation process. 143 | * Empty `List` is returned as is. 144 | * Non empty `List` is an invocation and evaluated using following rules: 145 | * If the first argument resolves to a special-form (`SpecialForm` Go type), 146 | it is invoked and return value is cached in the list. This return value 147 | is used for evaluating the list. 148 | * If the first argument resolves to a Macro, macro is invoked with the rest 149 | of the list as arguments and return value replaces the list with `(do retval)` 150 | form. 151 | * If first value resolves to an `Invokable` value, `Invoke()` is called. Functions 152 | are implemented using `MultiFn` which implements `Invokable`. `Vector` also implements 153 | `Invokable` and provides index access. 154 | * It is an error. 155 | -------------------------------------------------------------------------------- /atoms.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // Nil represents a nil value. 10 | type Nil struct{} 11 | 12 | // Eval returns the underlying value. 13 | func (n Nil) Eval(_ Scope) (Value, error) { return n, nil } 14 | 15 | func (n Nil) String() string { return "nil" } 16 | 17 | // Bool represents a boolean value. 18 | type Bool bool 19 | 20 | // Eval returns the underlying value. 21 | func (b Bool) Eval(_ Scope) (Value, error) { return b, nil } 22 | 23 | func (b Bool) String() string { return fmt.Sprintf("%t", b) } 24 | 25 | // Float64 represents double precision floating point numbers represented 26 | // using decimal or scientific number formats. 27 | type Float64 float64 28 | 29 | // Eval simply returns itself since Floats evaluate to themselves. 30 | func (f64 Float64) Eval(_ Scope) (Value, error) { return f64, nil } 31 | 32 | func (f64 Float64) String() string { return fmt.Sprintf("%f", f64) } 33 | 34 | // Int64 represents integer values represented using decimal, octal, radix 35 | // and hexadecimal formats. 36 | type Int64 int64 37 | 38 | // Eval simply returns itself since Integers evaluate to themselves. 39 | func (i64 Int64) Eval(_ Scope) (Value, error) { return i64, nil } 40 | 41 | func (i64 Int64) String() string { return fmt.Sprintf("%d", i64) } 42 | 43 | // String represents double-quoted string literals. String Form represents 44 | // the true string value obtained from the reader. Escape sequences are not 45 | // applicable at this level. 46 | type String string 47 | 48 | // Eval simply returns itself since Strings evaluate to themselves. 49 | func (se String) Eval(_ Scope) (Value, error) { return se, nil } 50 | 51 | func (se String) String() string { return fmt.Sprintf("\"%s\"", string(se)) } 52 | 53 | // First returns the first character if string is not empty, nil otherwise. 54 | func (se String) First() Value { 55 | if len(se) == 0 { 56 | return Nil{} 57 | } 58 | 59 | return Character(se[0]) 60 | } 61 | 62 | // Next slices the string by excluding first character and returns the 63 | // remainder. 64 | func (se String) Next() Seq { return se.chars().Next() } 65 | 66 | // Cons converts the string to character sequence and adds the given value 67 | // to the beginning of the list. 68 | func (se String) Cons(v Value) Seq { return se.chars().Cons(v) } 69 | 70 | // Conj joins the given values to list of characters of the string and returns 71 | // the new sequence. 72 | func (se String) Conj(vals ...Value) Seq { return se.chars().Conj(vals...) } 73 | 74 | func (se String) chars() Values { 75 | var vals Values 76 | for _, r := range se { 77 | vals = append(vals, Character(r)) 78 | } 79 | return vals 80 | } 81 | 82 | // Character represents a character literal. For example, \a, \b, \1, \∂ etc 83 | // are valid character literals. In addition, special literals like \newline, 84 | // \space etc are supported by the reader. 85 | type Character rune 86 | 87 | // Eval simply returns itself since Chracters evaluate to themselves. 88 | func (char Character) Eval(_ Scope) (Value, error) { return char, nil } 89 | 90 | func (char Character) String() string { return fmt.Sprintf("\\%c", rune(char)) } 91 | 92 | // Keyword represents a keyword literal. 93 | type Keyword string 94 | 95 | // Eval simply returns itself since Keywords evaluate to themselves. 96 | func (kw Keyword) Eval(_ Scope) (Value, error) { return kw, nil } 97 | 98 | func (kw Keyword) String() string { return fmt.Sprintf(":%s", string(kw)) } 99 | 100 | // Invoke enables keyword lookup for maps. 101 | func (kw Keyword) Invoke(scope Scope, args ...Value) (Value, error) { 102 | if err := verifyArgCount([]int{1, 2}, args); err != nil { 103 | return nil, err 104 | } 105 | 106 | argVals, err := evalValueList(scope, args) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | hm, ok := argVals[0].(*HashMap) 112 | if !ok { 113 | return Nil{}, nil 114 | } 115 | 116 | def := Value(Nil{}) 117 | if len(argVals) == 2 { 118 | def = argVals[1] 119 | } 120 | 121 | return hm.Get(kw, def), nil 122 | } 123 | 124 | // Symbol represents a name given to a value in memory. 125 | type Symbol struct { 126 | Position 127 | Value string 128 | } 129 | 130 | // Eval returns the value bound to this symbol in current context. If the 131 | // symbol is in fully qualified form (i.e., separated by '.'), eval does 132 | // recursive member access. 133 | func (sym Symbol) Eval(scope Scope) (Value, error) { 134 | target, err := sym.resolveValue(scope) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | if _, isSpecial := target.(SpecialForm); isSpecial { 140 | return nil, fmt.Errorf("can't take value of special form '%s'", sym.Value) 141 | } 142 | 143 | if isMacro(target) { 144 | return nil, fmt.Errorf("can't take value of macro '%s'", sym.Value) 145 | } 146 | 147 | return target, nil 148 | } 149 | 150 | // Compare compares this symbol to the given value. Returns true if 151 | // the given value is a symbol with same data. 152 | func (sym Symbol) Compare(v Value) bool { 153 | other, ok := v.(Symbol) 154 | if !ok { 155 | return false 156 | } 157 | 158 | return other.Value == sym.Value 159 | } 160 | 161 | func (sym Symbol) String() string { return sym.Value } 162 | 163 | func (sym Symbol) resolveValue(scope Scope) (Value, error) { 164 | fields := strings.Split(sym.Value, ".") 165 | 166 | if sym.Value == "." { 167 | fields = []string{"."} 168 | } 169 | 170 | target, err := scope.Resolve(fields[0]) 171 | if len(fields) == 1 || err != nil { 172 | return target, err 173 | } 174 | 175 | rv := reflect.ValueOf(target) 176 | for i := 1; i < len(fields); i++ { 177 | if rv.Type() == reflect.TypeOf(Any{}) { 178 | rv = rv.Interface().(Any).V 179 | } 180 | 181 | rv, err = accessMember(rv, fields[i]) 182 | if err != nil { 183 | return nil, err 184 | } 185 | } 186 | 187 | if isKind(rv.Type(), reflect.Chan, reflect.Array, 188 | reflect.Func, reflect.Ptr) && rv.IsNil() { 189 | return Nil{}, nil 190 | } 191 | 192 | return ValueOf(rv.Interface()), nil 193 | } 194 | 195 | func resolveSpecial(scope Scope, v Value) (*SpecialForm, error) { 196 | sym, isSymbol := v.(Symbol) 197 | if !isSymbol { 198 | return nil, nil 199 | } 200 | 201 | v, err := sym.resolveValue(scope) 202 | if err != nil { 203 | return nil, nil 204 | } 205 | 206 | sf, ok := v.(SpecialForm) 207 | if !ok { 208 | return nil, nil 209 | } 210 | 211 | return &sf, nil 212 | } 213 | -------------------------------------------------------------------------------- /atoms_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/spy16/sabre" 9 | ) 10 | 11 | var _ sabre.Seq = sabre.String("") 12 | 13 | func TestBool_Eval(t *testing.T) { 14 | executeEvalTests(t, []evalTestCase{ 15 | { 16 | name: "Success", 17 | getScope: nil, 18 | value: sabre.Bool(true), 19 | want: sabre.Bool(true), 20 | }, 21 | }) 22 | } 23 | 24 | func TestNil_Eval(t *testing.T) { 25 | executeEvalTests(t, []evalTestCase{ 26 | { 27 | name: "Success", 28 | getScope: nil, 29 | value: sabre.Nil{}, 30 | want: sabre.Nil{}, 31 | }, 32 | }) 33 | } 34 | 35 | func TestString_Eval(t *testing.T) { 36 | executeEvalTests(t, []evalTestCase{ 37 | { 38 | name: "Success", 39 | getScope: nil, 40 | value: sabre.String("hello"), 41 | want: sabre.String("hello"), 42 | }, 43 | }) 44 | } 45 | 46 | func TestKeyword_Eval(t *testing.T) { 47 | executeEvalTests(t, []evalTestCase{ 48 | { 49 | name: "Success", 50 | getScope: nil, 51 | value: sabre.Keyword("hello"), 52 | want: sabre.Keyword("hello"), 53 | }, 54 | }) 55 | } 56 | 57 | func TestSymbol_Eval(t *testing.T) { 58 | executeEvalTests(t, []evalTestCase{ 59 | { 60 | name: "Success", 61 | getScope: func() sabre.Scope { 62 | scope := sabre.NewScope(nil) 63 | scope.Bind("hello", sabre.String("world")) 64 | 65 | return scope 66 | }, 67 | value: sabre.Symbol{Value: "hello"}, 68 | want: sabre.String("world"), 69 | }, 70 | }) 71 | } 72 | 73 | func TestCharacter_Eval(t *testing.T) { 74 | executeEvalTests(t, []evalTestCase{ 75 | { 76 | name: "Success", 77 | getScope: nil, 78 | value: sabre.Character('a'), 79 | want: sabre.Character('a'), 80 | }, 81 | }) 82 | } 83 | 84 | func TestNil_String(t *testing.T) { 85 | executeStringTestCase(t, []stringTestCase{ 86 | { 87 | value: sabre.Nil{}, 88 | want: "nil", 89 | }, 90 | }) 91 | } 92 | 93 | func TestInt64_String(t *testing.T) { 94 | executeStringTestCase(t, []stringTestCase{ 95 | { 96 | value: sabre.Int64(10), 97 | want: "10", 98 | }, 99 | { 100 | value: sabre.Int64(-10), 101 | want: "-10", 102 | }, 103 | }) 104 | } 105 | 106 | func TestFloat64_String(t *testing.T) { 107 | executeStringTestCase(t, []stringTestCase{ 108 | { 109 | value: sabre.Float64(10.3), 110 | want: "10.300000", 111 | }, 112 | { 113 | value: sabre.Float64(-10.3), 114 | want: "-10.300000", 115 | }, 116 | }) 117 | } 118 | 119 | func TestBool_String(t *testing.T) { 120 | executeStringTestCase(t, []stringTestCase{ 121 | { 122 | value: sabre.Bool(true), 123 | want: "true", 124 | }, 125 | { 126 | value: sabre.Bool(false), 127 | want: "false", 128 | }, 129 | }) 130 | } 131 | 132 | func TestKeyword_String(t *testing.T) { 133 | executeStringTestCase(t, []stringTestCase{ 134 | { 135 | value: sabre.Keyword("hello"), 136 | want: ":hello", 137 | }, 138 | }) 139 | } 140 | 141 | func TestSymbol_String(t *testing.T) { 142 | executeStringTestCase(t, []stringTestCase{ 143 | { 144 | value: sabre.Symbol{Value: "hello"}, 145 | want: "hello", 146 | }, 147 | }) 148 | } 149 | 150 | func TestCharacter_String(t *testing.T) { 151 | executeStringTestCase(t, []stringTestCase{ 152 | { 153 | value: sabre.Character('a'), 154 | want: "\\a", 155 | }, 156 | }) 157 | } 158 | 159 | func TestString_String(t *testing.T) { 160 | executeStringTestCase(t, []stringTestCase{ 161 | { 162 | value: sabre.String("hello world"), 163 | want: `"hello world"`, 164 | }, 165 | { 166 | value: sabre.String("hello\tworld"), 167 | want: `"hello world"`, 168 | }, 169 | }) 170 | } 171 | 172 | type stringTestCase struct { 173 | value sabre.Value 174 | want string 175 | } 176 | 177 | type evalTestCase struct { 178 | name string 179 | getScope func() sabre.Scope 180 | value sabre.Value 181 | want sabre.Value 182 | wantErr bool 183 | } 184 | 185 | func executeStringTestCase(t *testing.T, tests []stringTestCase) { 186 | t.Parallel() 187 | 188 | for _, tt := range tests { 189 | t.Run(reflect.TypeOf(tt.value).Name(), func(t *testing.T) { 190 | got := strings.TrimSpace(tt.value.String()) 191 | if got != tt.want { 192 | t.Errorf("String() got = %v, want %v", got, tt.want) 193 | } 194 | }) 195 | } 196 | } 197 | 198 | func executeEvalTests(t *testing.T, tests []evalTestCase) { 199 | t.Parallel() 200 | 201 | for _, tt := range tests { 202 | t.Run(tt.name, func(t *testing.T) { 203 | var scope sabre.Scope 204 | if tt.getScope != nil { 205 | scope = tt.getScope() 206 | } 207 | 208 | got, err := sabre.Eval(scope, tt.value) 209 | if (err != nil) != tt.wantErr { 210 | t.Errorf("Eval() error = %v, wantErr %v", err, tt.wantErr) 211 | return 212 | } 213 | if !reflect.DeepEqual(got, tt.want) { 214 | t.Errorf("Eval() got = %v, want %v", got, tt.want) 215 | } 216 | }) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /composites.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // List represents an list of forms/vals. Evaluating a list leads to a 10 | // function invocation. 11 | type List struct { 12 | Values 13 | Position 14 | 15 | special *Fn 16 | } 17 | 18 | // Eval performs an invocation. 19 | func (lf *List) Eval(scope Scope) (Value, error) { 20 | if lf.Size() == 0 { 21 | return lf, nil 22 | } 23 | 24 | err := lf.parse(scope) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | if lf.special != nil { 30 | return lf.special.Invoke(scope, lf.Values[1:]...) 31 | } 32 | 33 | target, err := Eval(scope, lf.Values[0]) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | invokable, ok := target.(Invokable) 39 | if !ok { 40 | return nil, fmt.Errorf( 41 | "cannot invoke value of type '%s'", reflect.TypeOf(target), 42 | ) 43 | } 44 | 45 | return invokable.Invoke(scope, lf.Values[1:]...) 46 | } 47 | 48 | func (lf List) String() string { 49 | return containerString(lf.Values, "(", ")", " ") 50 | } 51 | 52 | func (lf *List) parse(scope Scope) error { 53 | if lf.Size() == 0 { 54 | return nil 55 | } 56 | 57 | form, expanded, err := MacroExpand(scope, lf) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if expanded { 63 | lf.Values = Values{ 64 | Symbol{Value: "do"}, 65 | form, 66 | } 67 | } 68 | 69 | special, err := resolveSpecial(scope, lf.First()) 70 | if err != nil { 71 | return err 72 | } else if special == nil { 73 | return analyzeSeq(scope, lf.Values) 74 | } 75 | 76 | fn, err := special.Parse(scope, lf.Values[1:]) 77 | if err != nil { 78 | return fmt.Errorf("%s: %v", special.Name, err) 79 | } 80 | lf.special = fn 81 | return nil 82 | } 83 | 84 | // Vector represents a list of values. Unlike List type, evaluation of 85 | // vector does not lead to function invoke. 86 | type Vector struct { 87 | Values 88 | Position 89 | } 90 | 91 | // Eval evaluates each value in the vector form and returns the resultant 92 | // values as new vector. 93 | func (vf Vector) Eval(scope Scope) (Value, error) { 94 | vals, err := evalValueList(scope, vf.Values) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | return Vector{Values: vals}, nil 100 | } 101 | 102 | // Invoke of a vector performs a index lookup. Only arity 1 is allowed 103 | // and should be an integer value to be used as index. 104 | func (vf Vector) Invoke(scope Scope, args ...Value) (Value, error) { 105 | vals, err := evalValueList(scope, args) 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | if len(vals) != 1 { 111 | return nil, fmt.Errorf("call requires exactly 1 argument, got %d", len(vals)) 112 | } 113 | 114 | index, isInt := vals[0].(Int64) 115 | if !isInt { 116 | return nil, fmt.Errorf("key must be integer") 117 | } 118 | 119 | if int(index) >= len(vf.Values) { 120 | return nil, fmt.Errorf("index out of bounds") 121 | } 122 | 123 | return vf.Values[index], nil 124 | } 125 | 126 | func (vf Vector) String() string { 127 | return containerString(vf.Values, "[", "]", " ") 128 | } 129 | 130 | // Set represents a list of unique values. (Experimental) 131 | type Set struct { 132 | Values 133 | Position 134 | } 135 | 136 | // Eval evaluates each value in the set form and returns the resultant 137 | // values as new set. 138 | func (set Set) Eval(scope Scope) (Value, error) { 139 | vals, err := evalValueList(scope, set.Uniq()) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | return Set{Values: Values(vals).Uniq()}, nil 145 | } 146 | 147 | func (set Set) String() string { 148 | return containerString(set.Values, "#{", "}", " ") 149 | } 150 | 151 | // TODO: Remove this naive solution 152 | func (set Set) valid() bool { 153 | s := map[string]struct{}{} 154 | 155 | for _, v := range set.Values { 156 | str := v.String() 157 | if _, found := s[str]; found { 158 | return false 159 | } 160 | s[v.String()] = struct{}{} 161 | } 162 | 163 | return true 164 | } 165 | 166 | // HashMap represents a container for key-value pairs. 167 | type HashMap struct { 168 | Position 169 | Data map[Value]Value 170 | } 171 | 172 | // Eval evaluates all keys and values and returns a new HashMap containing 173 | // the evaluated values. 174 | func (hm *HashMap) Eval(scope Scope) (Value, error) { 175 | res := &HashMap{Data: map[Value]Value{}} 176 | for k, v := range hm.Data { 177 | key, err := k.Eval(scope) 178 | if err != nil { 179 | return nil, err 180 | } 181 | 182 | val, err := v.Eval(scope) 183 | if err != nil { 184 | return nil, err 185 | } 186 | 187 | res.Data[key] = val 188 | } 189 | 190 | return res, nil 191 | } 192 | 193 | func (hm *HashMap) String() string { 194 | var fields []Value 195 | for k, v := range hm.Data { 196 | fields = append(fields, k, v) 197 | } 198 | return containerString(fields, "{", "}", " ") 199 | } 200 | 201 | // Get returns the value associated with the given key if found. 202 | // Returns def otherwise. 203 | func (hm *HashMap) Get(key Value, def Value) Value { 204 | if !isHashable(key) { 205 | return def 206 | } 207 | 208 | v, found := hm.Data[key] 209 | if !found { 210 | return def 211 | } 212 | 213 | return v 214 | } 215 | 216 | // Set sets/updates the value associated with the given key. 217 | func (hm *HashMap) Set(key, val Value) error { 218 | if !isHashable(key) { 219 | return fmt.Errorf("value of type '%s' is not hashable", key) 220 | } 221 | 222 | hm.Data[key] = val 223 | return nil 224 | } 225 | 226 | // Keys returns all the keys in the hashmap. 227 | func (hm *HashMap) Keys() Values { 228 | var res []Value 229 | for k := range hm.Data { 230 | res = append(res, k) 231 | } 232 | return res 233 | } 234 | 235 | // Values returns all the values in the hashmap. 236 | func (hm *HashMap) Values() Values { 237 | var res []Value 238 | for _, v := range hm.Data { 239 | res = append(res, v) 240 | } 241 | return res 242 | } 243 | 244 | // Module represents a group of forms. Evaluating a module leads to evaluation 245 | // of each form in order and result will be the result of last evaluation. 246 | type Module []Value 247 | 248 | // Eval evaluates all the vals in the module body and returns the result of the 249 | // last evaluation. 250 | func (mod Module) Eval(scope Scope) (Value, error) { 251 | res, err := evalValueList(scope, mod) 252 | if err != nil { 253 | return nil, err 254 | } 255 | 256 | if len(res) == 0 { 257 | return Nil{}, nil 258 | } 259 | 260 | return res[len(res)-1], nil 261 | } 262 | 263 | // Compare returns true if the 'v' is also a module and all forms in the 264 | // module are equivalent. 265 | func (mod Module) Compare(v Value) bool { 266 | other, ok := v.(Module) 267 | if !ok { 268 | return false 269 | } 270 | 271 | if len(mod) != len(other) { 272 | return false 273 | } 274 | 275 | for i := range mod { 276 | if !Compare(mod[i], other[i]) { 277 | return false 278 | } 279 | } 280 | 281 | return true 282 | } 283 | 284 | func (mod Module) String() string { return containerString(mod, "", "\n", "\n") } 285 | 286 | func containerString(vals []Value, begin, end, sep string) string { 287 | parts := make([]string, len(vals)) 288 | for i, expr := range vals { 289 | parts[i] = fmt.Sprintf("%v", expr) 290 | } 291 | return begin + strings.Join(parts, sep) + end 292 | } 293 | -------------------------------------------------------------------------------- /composites_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/spy16/sabre" 9 | ) 10 | 11 | var ( 12 | _ sabre.Seq = &sabre.List{} 13 | _ sabre.Seq = sabre.Vector{} 14 | _ sabre.Seq = sabre.Set{} 15 | ) 16 | 17 | func TestList_Eval(t *testing.T) { 18 | executeEvalTests(t, []evalTestCase{ 19 | { 20 | name: "EmptyList", 21 | value: &sabre.List{}, 22 | want: &sabre.List{}, 23 | }, 24 | { 25 | name: "Invocation", 26 | value: &sabre.List{ 27 | Values: []sabre.Value{sabre.Symbol{Value: "greet"}, sabre.String("Bob")}, 28 | }, 29 | getScope: func() sabre.Scope { 30 | scope := sabre.NewScope(nil) 31 | scope.BindGo("greet", func(name sabre.String) string { 32 | return fmt.Sprintf("Hello %s!", string(name)) 33 | }) 34 | return scope 35 | }, 36 | want: sabre.String("Hello Bob!"), 37 | }, 38 | { 39 | name: "NonInvokable", 40 | value: &sabre.List{ 41 | Values: []sabre.Value{sabre.Int64(10), sabre.Keyword("hello")}, 42 | }, 43 | wantErr: true, 44 | }, 45 | { 46 | name: "EvalFailure", 47 | value: &sabre.List{ 48 | Values: []sabre.Value{sabre.Symbol{Value: "hello"}}, 49 | }, 50 | getScope: func() sabre.Scope { 51 | return sabre.NewScope(nil) 52 | }, 53 | wantErr: true, 54 | }, 55 | }) 56 | } 57 | 58 | func TestModule_Eval(t *testing.T) { 59 | executeEvalTests(t, []evalTestCase{ 60 | { 61 | name: "NilModule", 62 | value: sabre.Module(nil), 63 | want: sabre.Nil{}, 64 | }, 65 | { 66 | name: "EmptyModule", 67 | value: sabre.Module{}, 68 | want: sabre.Nil{}, 69 | }, 70 | { 71 | name: "SingleForm", 72 | value: sabre.Module{sabre.Int64(10)}, 73 | want: sabre.Int64(10), 74 | }, 75 | { 76 | name: "MultiForm", 77 | value: sabre.Module{ 78 | sabre.Int64(10), 79 | sabre.String("hello"), 80 | }, 81 | want: sabre.String("hello"), 82 | }, 83 | { 84 | name: "Failure", 85 | getScope: func() sabre.Scope { return sabre.NewScope(nil) }, 86 | value: sabre.Module{ 87 | sabre.Symbol{Value: "hello"}, 88 | }, 89 | wantErr: true, 90 | }, 91 | }) 92 | } 93 | 94 | func TestVector_Eval(t *testing.T) { 95 | executeEvalTests(t, []evalTestCase{ 96 | { 97 | name: "EmptyVector", 98 | value: sabre.Vector{}, 99 | want: sabre.Vector{}, 100 | }, 101 | { 102 | name: "EvalFailure", 103 | getScope: func() sabre.Scope { 104 | return sabre.NewScope(nil) 105 | }, 106 | value: sabre.Vector{Values: []sabre.Value{sabre.Symbol{Value: "hello"}}}, 107 | wantErr: true, 108 | }, 109 | }) 110 | } 111 | 112 | func TestSet_Eval(t *testing.T) { 113 | executeEvalTests(t, []evalTestCase{ 114 | { 115 | name: "Empty", 116 | value: sabre.Set{}, 117 | want: sabre.Set{}, 118 | }, 119 | { 120 | name: "ValidWithoutDuplicates", 121 | getScope: func() sabre.Scope { 122 | return sabre.NewScope(nil) 123 | }, 124 | value: sabre.Set{Values: []sabre.Value{sabre.String("hello")}}, 125 | want: sabre.Set{Values: []sabre.Value{sabre.String("hello")}}, 126 | }, 127 | { 128 | name: "ValidWithtDuplicates", 129 | getScope: func() sabre.Scope { 130 | return sabre.NewScope(nil) 131 | }, 132 | value: sabre.Set{Values: []sabre.Value{ 133 | sabre.String("hello"), 134 | sabre.String("hello"), 135 | }}, 136 | want: sabre.Set{Values: []sabre.Value{sabre.String("hello")}}, 137 | }, 138 | { 139 | name: "Failure", 140 | getScope: func() sabre.Scope { 141 | return sabre.NewScope(nil) 142 | }, 143 | value: sabre.Set{Values: []sabre.Value{sabre.Symbol{Value: "hello"}}}, 144 | wantErr: true, 145 | }, 146 | }) 147 | } 148 | 149 | func TestList_String(t *testing.T) { 150 | executeStringTestCase(t, []stringTestCase{ 151 | { 152 | value: &sabre.List{}, 153 | want: "()", 154 | }, 155 | { 156 | value: &sabre.List{ 157 | Values: []sabre.Value{sabre.Keyword("hello")}, 158 | }, 159 | want: "(:hello)", 160 | }, 161 | { 162 | value: &sabre.List{ 163 | Values: []sabre.Value{sabre.Keyword("hello"), &sabre.List{}}, 164 | }, 165 | want: "(:hello ())", 166 | }, 167 | { 168 | value: &sabre.List{ 169 | Values: []sabre.Value{sabre.Symbol{Value: "quote"}, sabre.Symbol{Value: "hello"}}, 170 | }, 171 | want: "(quote hello)", 172 | }, 173 | { 174 | value: &sabre.List{ 175 | Values: []sabre.Value{ 176 | sabre.Symbol{Value: "quote"}, 177 | &sabre.List{Values: []sabre.Value{sabre.Symbol{Value: "hello"}}}}, 178 | }, 179 | want: "(quote (hello))", 180 | }, 181 | }) 182 | } 183 | 184 | func TestVector_String(t *testing.T) { 185 | executeStringTestCase(t, []stringTestCase{ 186 | { 187 | value: sabre.Vector{}, 188 | want: "[]", 189 | }, 190 | { 191 | value: sabre.Vector{Values: []sabre.Value{sabre.Keyword("hello")}}, 192 | want: "[:hello]", 193 | }, 194 | { 195 | value: sabre.Vector{Values: []sabre.Value{sabre.Keyword("hello"), &sabre.List{}}}, 196 | want: "[:hello ()]", 197 | }, 198 | }) 199 | } 200 | 201 | func TestModule_String(t *testing.T) { 202 | executeStringTestCase(t, []stringTestCase{ 203 | { 204 | value: sabre.Module(nil), 205 | want: "", 206 | }, 207 | { 208 | value: sabre.Module{sabre.Symbol{Value: "hello"}}, 209 | want: "hello", 210 | }, 211 | { 212 | value: sabre.Module{sabre.Symbol{Value: "hello"}, sabre.Keyword("world")}, 213 | want: "hello\n:world", 214 | }, 215 | }) 216 | } 217 | 218 | func TestVector_Invoke(t *testing.T) { 219 | t.Parallel() 220 | 221 | vector := sabre.Vector{Values: []sabre.Value{sabre.Keyword("hello")}} 222 | 223 | table := []struct { 224 | name string 225 | getScope func() sabre.Scope 226 | args []sabre.Value 227 | want sabre.Value 228 | wantErr bool 229 | }{ 230 | { 231 | name: "NoArgs", 232 | args: []sabre.Value{}, 233 | wantErr: true, 234 | }, 235 | { 236 | name: "InvalidIndex", 237 | args: []sabre.Value{sabre.Int64(10)}, 238 | wantErr: true, 239 | }, 240 | { 241 | name: "ValidIndex", 242 | args: []sabre.Value{sabre.Int64(0)}, 243 | want: sabre.Keyword("hello"), 244 | wantErr: false, 245 | }, 246 | { 247 | name: "NonIntegerArg", 248 | args: []sabre.Value{sabre.Keyword("h")}, 249 | wantErr: true, 250 | }, 251 | { 252 | name: "EvalFailure", 253 | getScope: func() sabre.Scope { 254 | return sabre.NewScope(nil) 255 | }, 256 | args: []sabre.Value{sabre.Symbol{Value: "hello"}}, 257 | wantErr: true, 258 | }, 259 | } 260 | 261 | for _, tt := range table { 262 | t.Run(tt.name, func(t *testing.T) { 263 | var scope sabre.Scope 264 | if tt.getScope != nil { 265 | scope = tt.getScope() 266 | } 267 | 268 | got, err := vector.Invoke(scope, tt.args...) 269 | if (err != nil) != tt.wantErr { 270 | t.Errorf("Eval() error = %v, wantErr %v", err, tt.wantErr) 271 | return 272 | } 273 | if !reflect.DeepEqual(got, tt.want) { 274 | t.Errorf("Eval() got = %v, want %v", got, tt.want) 275 | } 276 | }) 277 | } 278 | } 279 | 280 | func TestHashMap_Eval(t *testing.T) { 281 | executeEvalTests(t, []evalTestCase{ 282 | { 283 | name: "Simple", 284 | value: &sabre.HashMap{ 285 | Data: map[sabre.Value]sabre.Value{ 286 | sabre.Keyword("name"): sabre.String("Bob"), 287 | }, 288 | }, 289 | want: &sabre.HashMap{ 290 | Data: map[sabre.Value]sabre.Value{ 291 | sabre.Keyword("name"): sabre.String("Bob"), 292 | }, 293 | }, 294 | }, 295 | }) 296 | } 297 | 298 | func TestHashMap_String(t *testing.T) { 299 | executeStringTestCase(t, []stringTestCase{ 300 | { 301 | value: &sabre.HashMap{ 302 | Data: map[sabre.Value]sabre.Value{ 303 | sabre.Keyword("name"): sabre.String("Bob"), 304 | }, 305 | }, 306 | want: `{:name "Bob"}`, 307 | }, 308 | }) 309 | } 310 | -------------------------------------------------------------------------------- /examples/recur/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | const rangeTco = ` 11 | (def range (fn* range [min max coll] 12 | (if (= min max) 13 | coll 14 | (recur (inc min) max (coll.Conj min))))) 15 | 16 | (print (range 0 10 '())) 17 | (range 0 1000 '()) 18 | ` 19 | 20 | const rangeNotTco = ` 21 | (def range (fn* range [min max coll] 22 | (if (= min max) 23 | coll 24 | (range (inc min) max (coll.Conj min))))) 25 | 26 | (print (range 0 10 '())) 27 | (range 0 1000 '()) 28 | ` 29 | 30 | func main() { 31 | scope := sabre.New() 32 | scope.BindGo("inc", inc) 33 | scope.BindGo("print", fmt.Println) 34 | scope.BindGo("=", sabre.Compare) 35 | 36 | initial := time.Now() 37 | _, err := sabre.ReadEvalStr(scope, rangeNotTco) 38 | if err != nil { 39 | panic(err) 40 | } 41 | final := time.Since(initial) 42 | fmt.Printf("no recur: %s\n", final) 43 | 44 | initial = time.Now() 45 | _, err = sabre.ReadEvalStr(scope, rangeTco) 46 | if err != nil { 47 | panic(err) 48 | } 49 | final = time.Since(initial) 50 | fmt.Printf("recur: %s\n", final) 51 | } 52 | 53 | func inc(val sabre.Int64) sabre.Int64 { 54 | return val + 1 55 | } 56 | -------------------------------------------------------------------------------- /examples/rule-engine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spy16/sabre" 5 | ) 6 | 7 | func main() { 8 | // Accept business rules from file, command-line, http request etc. 9 | // These rules can change as per business requirements and your 10 | // application doesn't have to change. 11 | ruleSrc := `(and (regular-user? current-user) 12 | (not-blacklisted? current-user))` 13 | 14 | shouldDiscount, err := runDiscountingRule(ruleSrc, "bob") 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | if shouldDiscount { 20 | // apply discount for the order 21 | } else { 22 | // don't apply discount 23 | } 24 | } 25 | 26 | func runDiscountingRule(rule string, user string) (bool, error) { 27 | // Define a scope with no bindings. (not even special forms) 28 | scope := sabre.NewScope(nil) 29 | 30 | // Define and expose your rules which ideally should have no 31 | // side effects. 32 | scope.BindGo("and", and) 33 | scope.BindGo("or", or) 34 | scope.BindGo("regular-user?", isRegularUser) 35 | scope.BindGo("minimum-cart-price?", isMinCartPrice) 36 | scope.BindGo("not-blacklisted?", isNotBlacklisted) 37 | 38 | // Bind current user name 39 | scope.BindGo("current-user", user) 40 | 41 | shouldDiscount, err := sabre.ReadEvalStr(scope, rule) 42 | return isTruthy(shouldDiscount), err 43 | } 44 | 45 | func isTruthy(v sabre.Value) bool { 46 | if v == nil || v == (sabre.Nil{}) { 47 | return false 48 | } 49 | if b, ok := v.(sabre.Bool); ok { 50 | return bool(b) 51 | } 52 | return true 53 | } 54 | 55 | func isNotBlacklisted(user string) bool { 56 | return user != "joe" 57 | } 58 | 59 | func isMinCartPrice(price float64) bool { 60 | return price >= 100 61 | } 62 | 63 | func isRegularUser(user string) bool { 64 | return user == "bob" 65 | } 66 | 67 | func and(rest ...bool) bool { 68 | if len(rest) == 0 { 69 | return true 70 | } 71 | result := rest[0] 72 | for _, r := range rest { 73 | result = result && r 74 | if !result { 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | 81 | func or(rest ...bool) bool { 82 | if len(rest) == 0 { 83 | return true 84 | } 85 | result := rest[0] 86 | for _, r := range rest { 87 | if result { 88 | return true 89 | } 90 | result = result || r 91 | } 92 | return false 93 | } 94 | -------------------------------------------------------------------------------- /examples/scripted/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | func main() { 11 | // Setup the environment available for your scripts. NewScope(nil) 12 | // starts with no bindings. 13 | scope := sabre.NewScope(nil) 14 | scope.BindGo("api", &API{name: "foo"}) 15 | scope.BindGo("console-print", printToConsole) 16 | scope.BindGo("value-of-pi", valueOfPi) 17 | 18 | // userProgram can be read from a file, command-line, a network socket 19 | // etc. and can contain calls that return/simply have side effects. 20 | userProgram := ` 21 | (api.SetName "Bob") 22 | (console-print (api.Name)) 23 | (value-of-pi) 24 | ` 25 | 26 | res, err := sabre.ReadEvalStr(scope, userProgram) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | fmt.Println(res) // should print 3.141200 32 | } 33 | 34 | func valueOfPi() float64 { 35 | return 3.1412 36 | } 37 | 38 | // You can expose control to your application through just functions 39 | // also. 40 | func printToConsole(msg string) { 41 | log.Printf("func-api called") 42 | } 43 | 44 | // API provides functions that allow your application behavior to be 45 | // controlled at runtime. 46 | type API struct { 47 | name string 48 | } 49 | 50 | // SetName can be used from the scripting layer to change name. 51 | func (api *API) SetName(name string) { 52 | api.name = name 53 | } 54 | 55 | // Name returns the current value of the name. 56 | func (api *API) Name() string { 57 | return api.name 58 | } 59 | -------------------------------------------------------------------------------- /examples/simple.lisp: -------------------------------------------------------------------------------- 1 | ; define a value binding 2 | (def π 3.1412) 3 | 4 | ; define a lambda function 5 | (def echo (fn* [msg] msg)) 6 | 7 | ; method access for Go values 8 | (def first (fn* [coll] (coll.First))) 9 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/spy16/sabre" 8 | "github.com/spy16/sabre/repl" 9 | ) 10 | 11 | const program = ` 12 | (def result (sum 1 2 3)) 13 | (printf "Sum of numbers is %s\n" result) 14 | ` 15 | 16 | func main() { 17 | scope := sabre.New() 18 | scope.BindGo("sum", sum) 19 | scope.BindGo("printf", fmt.Printf) 20 | 21 | repl.New(scope, 22 | repl.WithPrompts("=>", ">"), 23 | ).Loop(context.Background()) 24 | } 25 | 26 | func sum(nums ...float64) float64 { 27 | sum := 0.0 28 | for _, n := range nums { 29 | sum += n 30 | } 31 | 32 | return sum 33 | } 34 | -------------------------------------------------------------------------------- /examples/spec.lisp: -------------------------------------------------------------------------------- 1 | ; All the constructs supported by the Reader (with default read 2 | ; table.) 3 | 4 | ; strings -------------------------------------------------------- 5 | "Hello" ; simple string 6 | "Hello\tWorld" ; string with escape sequences 7 | 8 | "this is going 9 | to be a multi 10 | line string" ; a multi-line string 11 | 12 | ; characters ----------------------------------------------------- 13 | \a ; simple character literal representing 'a' 14 | \newline ; special character literal representing '\n' 15 | \u00A5 ; unicode character literal representing '¥' 16 | 17 | ; numbers -------------------------------------------------------- 18 | 1234 ; simple integer (int64) 19 | 3.142 ; simple double precision floating point (float64) 20 | -1.23445 ; negative floating point number 21 | 010 ; octal representation of 8 22 | -010 ; octal representation of -8 23 | 0xF ; hexadecimal representation of 15 24 | -0xAF ; negative hexadecimal number -175 25 | 1e3 ; scientific notation for 1x10^3 = 1000 26 | 1.5e3 ; scientific notation for 1.5x10^3 = 1500 27 | 10e-1 ; scientific notation with negative exponent 28 | 29 | ; keywords ------------------------------------------------------- 30 | :key ; simple ASCII keyword 31 | :find-Ψ ; a keyword with non non-ASCII 32 | 33 | ; symbols -------------------------------------------------------- 34 | ; do ; simple ASCII symbol 35 | ; λ ; symbol with non-ASCII 36 | 37 | ; quote/unquote -------------------------------------------------- 38 | 'hello ; quoted symbol 39 | '() ; quoted list 40 | 41 | ; lists ---------------------------------------------------------- 42 | () ; empty list 43 | (eval 10) ; function/macro/special form invocation 44 | (do, 1, 2) ; same as above, forms separated by "," 45 | 46 | ; vectors -------------------------------------------------------- 47 | [] ; empty vector 48 | [1 2 3 4] ; vector entries separated by space 49 | [1, 2, 3, 4] ; vector entries can be separated by "," as well 50 | 51 | ; sets ----------------------------------------------------------- 52 | #{} ; empty set 53 | #{1 2 []} ; a set 54 | ; #{1 1 2} ; invalid set 55 | -------------------------------------------------------------------------------- /func.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // MacroExpand expands the macro invocation form. 10 | func MacroExpand(scope Scope, form Value) (Value, bool, error) { 11 | list, ok := form.(*List) 12 | if !ok || list.Size() == 0 { 13 | return form, false, nil 14 | } 15 | 16 | symbol, ok := list.First().(Symbol) 17 | if !ok { 18 | return form, false, nil 19 | } 20 | 21 | target, err := symbol.resolveValue(scope) 22 | if err != nil || !isMacro(target) { 23 | return form, false, nil 24 | } 25 | 26 | mfn := target.(MultiFn) 27 | v, err := mfn.Expand(scope, list.Values[1:]) 28 | return v, true, err 29 | } 30 | 31 | // MultiFn represents a multi-arity function or macro definition. 32 | type MultiFn struct { 33 | Name string 34 | IsMacro bool 35 | Methods []Fn 36 | } 37 | 38 | // Eval returns the multiFn definition itself. 39 | func (multiFn MultiFn) Eval(_ Scope) (Value, error) { return multiFn, nil } 40 | 41 | func (multiFn MultiFn) String() string { 42 | var sb strings.Builder 43 | for _, fn := range multiFn.Methods { 44 | sb.WriteString("[" + strings.Trim(fn.String(), "()") + "] ") 45 | } 46 | 47 | s := multiFn.Name + " " + strings.TrimSpace(sb.String()) 48 | return "(" + strings.TrimSpace(s) + ")" 49 | } 50 | 51 | // Invoke dispatches the call to a method based on number of arguments. 52 | func (multiFn MultiFn) Invoke(scope Scope, args ...Value) (Value, error) { 53 | if multiFn.IsMacro { 54 | form, err := multiFn.Expand(scope, args) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return form.Eval(scope) 60 | } 61 | 62 | fn, err := multiFn.selectMethod(args) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | argVals, err := evalValueList(scope, args) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | result, err := fn.Invoke(scope, argVals...) 73 | 74 | if !isRecur(result) { 75 | return result, err 76 | } 77 | 78 | for isRecur(result) { 79 | args = result.(*List).Values[1:] 80 | result, err = fn.Invoke(scope, args...) 81 | } 82 | 83 | return result, err 84 | } 85 | 86 | func isRecur(value Value) bool { 87 | 88 | list, ok := value.(*List) 89 | if !ok { 90 | return false 91 | } 92 | 93 | sym, ok := list.First().(Symbol) 94 | if !ok { 95 | return false 96 | } 97 | 98 | if sym.Value != "recur" { 99 | return false 100 | } 101 | 102 | return true 103 | } 104 | 105 | // Expand executes the macro body and returns the result of the expansion. 106 | func (multiFn MultiFn) Expand(scope Scope, args []Value) (Value, error) { 107 | fn, err := multiFn.selectMethod(args) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | if !multiFn.IsMacro { 113 | return &fn, nil 114 | } 115 | 116 | return fn.Invoke(scope, args...) 117 | } 118 | 119 | // Compare returns true if 'v' is also a MultiFn and all methods are 120 | // equivalent. 121 | func (multiFn MultiFn) Compare(v Value) bool { 122 | other, ok := v.(MultiFn) 123 | if !ok { 124 | return false 125 | } 126 | 127 | sameHeader := (multiFn.Name == other.Name) && 128 | (multiFn.IsMacro == other.IsMacro) && 129 | (len(multiFn.Methods) == len(other.Methods)) 130 | if !sameHeader { 131 | return false 132 | } 133 | 134 | for i, fn1 := range multiFn.Methods { 135 | fn2 := other.Methods[i] 136 | if !fn1.Compare(&fn2) { 137 | return false 138 | } 139 | } 140 | 141 | return true 142 | } 143 | 144 | func (multiFn MultiFn) selectMethod(args []Value) (Fn, error) { 145 | for _, fn := range multiFn.Methods { 146 | if fn.matchArity(args) { 147 | return fn, nil 148 | } 149 | } 150 | 151 | return Fn{}, fmt.Errorf("wrong number of args (%d) to '%s'", 152 | len(args), multiFn.Name) 153 | } 154 | 155 | func (multiFn *MultiFn) validate() error { 156 | variadicAt := -1 157 | variadicArity := 0 158 | 159 | for idx, method := range multiFn.Methods { 160 | if method.Variadic { 161 | if variadicAt >= 0 { 162 | return fmt.Errorf("can't have multiple variadic overloads") 163 | } 164 | variadicAt = idx 165 | variadicArity = len(method.Args) 166 | } 167 | } 168 | 169 | fixedArities := map[int]struct{}{} 170 | for idx, method := range multiFn.Methods { 171 | if method.Variadic { 172 | continue 173 | } 174 | 175 | arity := method.minArity() 176 | if variadicAt >= 0 && idx != variadicAt && arity >= variadicArity { 177 | return fmt.Errorf("can't have fixed arity overload with more params than variadic") 178 | } 179 | 180 | if _, exists := fixedArities[arity]; exists { 181 | return fmt.Errorf("ambiguous arities defined for '%s'", multiFn.Name) 182 | } 183 | fixedArities[arity] = struct{}{} 184 | } 185 | 186 | return nil 187 | } 188 | 189 | // Fn represents a function or macro definition. 190 | type Fn struct { 191 | Args []string 192 | Variadic bool 193 | Body Value 194 | Func func(scope Scope, args []Value) (Value, error) 195 | } 196 | 197 | // Eval returns the function itself. 198 | func (fn *Fn) Eval(_ Scope) (Value, error) { return fn, nil } 199 | 200 | func (fn Fn) String() string { 201 | var sb strings.Builder 202 | 203 | for i, arg := range fn.Args { 204 | if i == len(fn.Args)-1 && fn.Variadic { 205 | sb.WriteString(" & " + arg) 206 | } else { 207 | sb.WriteString(arg + " ") 208 | } 209 | } 210 | 211 | return "(" + strings.TrimSpace(sb.String()) + ")" 212 | } 213 | 214 | // Invoke executes the function with given arguments. 215 | func (fn *Fn) Invoke(scope Scope, args ...Value) (Value, error) { 216 | if fn.Func != nil { 217 | return fn.Func(scope, args) 218 | } 219 | 220 | fnScope := NewScope(scope) 221 | 222 | for idx := range fn.Args { 223 | var argVal Value 224 | if idx == len(fn.Args)-1 && fn.Variadic { 225 | argVal = &List{ 226 | Values: args[idx:], 227 | } 228 | } else { 229 | argVal = args[idx] 230 | } 231 | 232 | _ = fnScope.Bind(fn.Args[idx], argVal) 233 | } 234 | 235 | if fn.Body == nil { 236 | return Nil{}, nil 237 | } 238 | 239 | return Eval(fnScope, fn.Body) 240 | } 241 | 242 | // Compare returns true if 'other' is also a function and has the same 243 | // signature and body. 244 | func (fn *Fn) Compare(v Value) bool { 245 | other, ok := v.(*Fn) 246 | if !ok || other == nil { 247 | return false 248 | } 249 | 250 | if !reflect.DeepEqual(fn.Args, other.Args) { 251 | return false 252 | } 253 | 254 | bothVariadic := (fn.Variadic == other.Variadic) 255 | noFunc := (fn.Func == nil && other.Func == nil) 256 | 257 | return bothVariadic && noFunc && Compare(fn.Body, other.Body) 258 | } 259 | 260 | func (fn Fn) minArity() int { 261 | if len(fn.Args) > 0 && fn.Variadic { 262 | return len(fn.Args) - 1 263 | } 264 | return len(fn.Args) 265 | } 266 | 267 | func (fn Fn) matchArity(args []Value) bool { 268 | argc := len(args) 269 | if fn.Variadic { 270 | return argc >= len(fn.Args)-1 271 | } 272 | return argc == len(fn.Args) 273 | } 274 | 275 | func (fn *Fn) parseArgSpec(spec Value) error { 276 | vec, isVector := spec.(Vector) 277 | if !isVector { 278 | return fmt.Errorf("argument spec must be a vector of symbols, not '%s'", 279 | reflect.TypeOf(spec)) 280 | } 281 | 282 | argNames, err := toArgNames(vec.Values) 283 | if err != nil { 284 | return err 285 | } 286 | 287 | fn.Variadic, err = checkVariadic(argNames) 288 | if err != nil { 289 | return err 290 | } 291 | 292 | if fn.Variadic { 293 | argc := len(argNames) 294 | fn.Args = append(argNames[:argc-2], argNames[argc-1]) 295 | } else { 296 | fn.Args = argNames 297 | } 298 | 299 | return nil 300 | } 301 | 302 | func checkVariadic(args []string) (bool, error) { 303 | for i, arg := range args { 304 | if arg != "&" { 305 | continue 306 | } 307 | 308 | if i > len(args)-2 { 309 | return false, fmt.Errorf("expecting one more symbol after '&'") 310 | } else if i < len(args)-2 { 311 | return false, fmt.Errorf("expecting only one symbol after '&'") 312 | } 313 | 314 | return true, nil 315 | } 316 | 317 | return false, nil 318 | } 319 | 320 | func toArgNames(vals []Value) ([]string, error) { 321 | var names []string 322 | 323 | for i, v := range vals { 324 | sym, isSymbol := v.(Symbol) 325 | if !isSymbol { 326 | return nil, fmt.Errorf( 327 | "expecting symbol at '%d', not '%s'", 328 | i, reflect.TypeOf(v), 329 | ) 330 | } 331 | 332 | names = append(names, sym.Value) 333 | } 334 | 335 | return names, nil 336 | } 337 | 338 | func isMacro(target Value) bool { 339 | multiFn, ok := target.(MultiFn) 340 | return ok && multiFn.IsMacro 341 | } 342 | -------------------------------------------------------------------------------- /func_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | func TestMultiFn_Eval(t *testing.T) { 11 | executeEvalTests(t, []evalTestCase{ 12 | { 13 | name: "Valid", 14 | value: sabre.MultiFn{}, 15 | want: sabre.MultiFn{}, 16 | }, 17 | }) 18 | } 19 | 20 | func TestMultiFn_String(t *testing.T) { 21 | executeStringTestCase(t, []stringTestCase{ 22 | { 23 | value: sabre.MultiFn{ 24 | Name: "hello", 25 | }, 26 | want: "(hello)", 27 | }, 28 | }) 29 | } 30 | 31 | func TestMultiFn_Invoke(t *testing.T) { 32 | t.Parallel() 33 | 34 | table := []struct { 35 | name string 36 | getScope func() sabre.Scope 37 | multiFn sabre.MultiFn 38 | args []sabre.Value 39 | want sabre.Value 40 | wantErr bool 41 | }{ 42 | { 43 | name: "WrongArity", 44 | multiFn: sabre.MultiFn{ 45 | Name: "arityOne", 46 | Methods: []sabre.Fn{ 47 | { 48 | Args: []string{"arg1"}, 49 | }, 50 | }, 51 | }, 52 | args: []sabre.Value{}, 53 | wantErr: true, 54 | }, 55 | { 56 | name: "VariadicArity", 57 | multiFn: sabre.MultiFn{ 58 | Name: "arityMany", 59 | Methods: []sabre.Fn{ 60 | { 61 | Args: []string{"args"}, 62 | Variadic: true, 63 | }, 64 | }, 65 | }, 66 | args: []sabre.Value{}, 67 | want: sabre.Nil{}, 68 | }, 69 | { 70 | name: "ArgEvalFailure", 71 | getScope: func() sabre.Scope { return sabre.NewScope(nil) }, 72 | multiFn: sabre.MultiFn{ 73 | Name: "arityOne", 74 | Methods: []sabre.Fn{ 75 | { 76 | Args: []string{"arg1"}, 77 | }, 78 | }, 79 | }, 80 | args: []sabre.Value{sabre.Symbol{Value: "argVal"}}, 81 | wantErr: true, 82 | }, 83 | { 84 | name: "Macro", 85 | getScope: func() sabre.Scope { 86 | scope := sabre.NewScope(nil) 87 | scope.Bind("argVal", sabre.String("hello")) 88 | return scope 89 | }, 90 | multiFn: sabre.MultiFn{ 91 | Name: "arityOne", 92 | IsMacro: true, 93 | Methods: []sabre.Fn{ 94 | { 95 | Args: []string{"arg1"}, 96 | Body: sabre.Int64(10), 97 | }, 98 | }, 99 | }, 100 | args: []sabre.Value{sabre.Symbol{Value: "argVal"}}, 101 | want: sabre.Int64(10), 102 | }, 103 | } 104 | 105 | for _, tt := range table { 106 | t.Run(tt.name, func(t *testing.T) { 107 | var scope sabre.Scope 108 | if tt.getScope != nil { 109 | scope = tt.getScope() 110 | } 111 | 112 | got, err := tt.multiFn.Invoke(scope, tt.args...) 113 | if (err != nil) != tt.wantErr { 114 | t.Errorf("Invoke() error = %v, wantErr %v", err, tt.wantErr) 115 | return 116 | } 117 | if !reflect.DeepEqual(got, tt.want) { 118 | t.Errorf("Invoke() got = %v, want %v", got, tt.want) 119 | } 120 | }) 121 | } 122 | } 123 | 124 | func TestFn_Invoke(t *testing.T) { 125 | t.Parallel() 126 | 127 | table := []struct { 128 | name string 129 | getScope func() sabre.Scope 130 | fn sabre.Fn 131 | args []sabre.Value 132 | want sabre.Value 133 | wantErr bool 134 | }{ 135 | { 136 | name: "GoFuncWrap", 137 | fn: sabre.Fn{ 138 | Func: func(scope sabre.Scope, args []sabre.Value) (sabre.Value, error) { 139 | return sabre.Int64(10), nil 140 | }, 141 | }, 142 | want: sabre.Int64(10), 143 | }, 144 | { 145 | name: "NoBody", 146 | fn: sabre.Fn{ 147 | Args: []string{"test"}, 148 | }, 149 | args: []sabre.Value{sabre.Bool(true)}, 150 | want: sabre.Nil{}, 151 | }, 152 | { 153 | name: "VariadicMatch", 154 | fn: sabre.Fn{ 155 | Args: []string{"test"}, 156 | Variadic: true, 157 | }, 158 | args: []sabre.Value{}, 159 | want: sabre.Nil{}, 160 | }, 161 | { 162 | name: "VariadicMatch", 163 | fn: sabre.Fn{ 164 | Args: []string{"test"}, 165 | Variadic: true, 166 | }, 167 | args: []sabre.Value{sabre.Int64(10), sabre.Bool(true)}, 168 | want: sabre.Nil{}, 169 | }, 170 | } 171 | 172 | for _, tt := range table { 173 | t.Run(tt.name, func(t *testing.T) { 174 | var scope sabre.Scope 175 | if tt.getScope != nil { 176 | scope = tt.getScope() 177 | } 178 | 179 | got, err := tt.fn.Invoke(scope, tt.args...) 180 | if (err != nil) != tt.wantErr { 181 | t.Errorf("Invoke() error = %v, wantErr %v", err, tt.wantErr) 182 | return 183 | } 184 | if !reflect.DeepEqual(got, tt.want) { 185 | t.Errorf("Invoke() got = %v, want %v", got, tt.want) 186 | } 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spy16/sabre 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spy16/sabre/ee083d4df6e9b5ddabf1ff16c129e205d6551891/go.sum -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "math" 10 | "net" 11 | "os" 12 | "reflect" 13 | "strconv" 14 | "strings" 15 | "unicode" 16 | ) 17 | 18 | const dispatchTrigger = '#' 19 | 20 | var ( 21 | // ErrSkip can be returned by reader macro to indicate a no-op form which 22 | // should be discarded (e.g., Comments). 23 | ErrSkip = errors.New("skip expr") 24 | 25 | // ErrEOF is returned when stream ends prematurely to indicate that more 26 | // data is needed to complete the current form. 27 | ErrEOF = errors.New("unexpected EOF") 28 | ) 29 | 30 | var ( 31 | escapeMap = map[rune]rune{ 32 | '"': '"', 33 | 'n': '\n', 34 | '\\': '\\', 35 | 't': '\t', 36 | 'a': '\a', 37 | 'f': '\a', 38 | 'r': '\r', 39 | 'b': '\b', 40 | 'v': '\v', 41 | } 42 | 43 | charLiterals = map[string]rune{ 44 | "tab": '\t', 45 | "space": ' ', 46 | "newline": '\n', 47 | "return": '\r', 48 | "backspace": '\b', 49 | "formfeed": '\f', 50 | } 51 | 52 | predefSymbols = map[string]Value{ 53 | "nil": Nil{}, 54 | "true": Bool(true), 55 | "false": Bool(false), 56 | } 57 | ) 58 | 59 | // NewReader returns a lisp reader instance which can read forms from rs. 60 | // Reader behavior can be customized by using SetMacro to override or remove 61 | // from the default read table. File name will be inferred from the reader 62 | // value and type information or can be set manually on the Reader. 63 | func NewReader(rs io.Reader) *Reader { 64 | return &Reader{ 65 | File: inferFileName(rs), 66 | rs: bufio.NewReader(rs), 67 | macros: defaultReadTable(), 68 | dispatch: defaultDispatchTable(), 69 | } 70 | } 71 | 72 | // ReaderMacro implementations can be plugged into the Reader to extend, override 73 | // or customize behavior of the reader. 74 | type ReaderMacro func(rd *Reader, init rune) (Value, error) 75 | 76 | // Reader provides functions to parse characters from a stream into symbolic 77 | // expressions or forms. 78 | type Reader struct { 79 | File string 80 | 81 | rs io.RuneReader 82 | buf []rune 83 | line, col int 84 | lastCol int 85 | macros map[rune]ReaderMacro 86 | dispatch map[rune]ReaderMacro 87 | dispatching bool 88 | } 89 | 90 | // All consumes characters from stream until EOF and returns a list of all the 91 | // forms parsed. Any no-op forms (e.g., comment) returned will not be included 92 | // in the result. 93 | func (rd *Reader) All() (Value, error) { 94 | var forms []Value 95 | 96 | for { 97 | form, err := rd.One() 98 | if err != nil { 99 | if err == io.EOF { 100 | break 101 | } 102 | 103 | return nil, err 104 | } 105 | 106 | forms = append(forms, form) 107 | } 108 | 109 | return Module(forms), nil 110 | } 111 | 112 | // One consumes characters from underlying stream until a complete form is 113 | // parsed and returns the form while ignoring the no-op forms like comments. 114 | // Except EOF, all errors will be wrapped with ReaderError type along with 115 | // the positional information obtained using Position(). 116 | func (rd *Reader) One() (Value, error) { 117 | for { 118 | form, err := rd.readOne() 119 | if err != nil { 120 | if err == ErrSkip { 121 | continue 122 | } 123 | 124 | return nil, rd.annotateErr(err) 125 | } 126 | 127 | return form, nil 128 | } 129 | } 130 | 131 | // IsTerminal returns true if the rune should terminate a form. ReaderMacro 132 | // trigger runes defined in the read table and all space characters including 133 | // "," are considered terminal. 134 | func (rd *Reader) IsTerminal(r rune) bool { 135 | if isSpace(r) { 136 | return true 137 | } 138 | 139 | if rd.dispatching { 140 | _, found := rd.dispatch[r] 141 | if found { 142 | return true 143 | } 144 | } 145 | 146 | _, found := rd.macros[r] 147 | return found 148 | } 149 | 150 | // SetMacro sets the given reader macro as the handler for init rune in the 151 | // read table. Overwrites if a macro is already present. If the macro value 152 | // given is nil, entry for the init rune will be removed from the read table. 153 | // isDispatch decides if the macro is a dispatch macro and takes effect only 154 | // after a '#' sign. 155 | func (rd *Reader) SetMacro(init rune, macro ReaderMacro, isDispatch bool) { 156 | if isDispatch { 157 | if macro == nil { 158 | delete(rd.dispatch, init) 159 | return 160 | } 161 | rd.dispatch[init] = macro 162 | } else { 163 | if macro == nil { 164 | delete(rd.macros, init) 165 | return 166 | } 167 | rd.macros[init] = macro 168 | } 169 | } 170 | 171 | // NextRune returns next rune from the stream and advances the stream. 172 | func (rd *Reader) NextRune() (rune, error) { 173 | var r rune 174 | if len(rd.buf) > 0 { 175 | r = rd.buf[0] 176 | rd.buf = rd.buf[1:] 177 | } else { 178 | temp, _, err := rd.rs.ReadRune() 179 | if err != nil { 180 | return -1, err 181 | } 182 | 183 | r = temp 184 | } 185 | 186 | if r == '\n' { 187 | rd.line++ 188 | rd.lastCol = rd.col 189 | rd.col = 0 190 | } else { 191 | rd.col++ 192 | } 193 | 194 | return r, nil 195 | } 196 | 197 | // Unread can be used to return runes consumed from the stream back to the 198 | // stream. Un-reading more runes than read is guaranteed to work but might 199 | // cause inconsistency in stream positional information. 200 | func (rd *Reader) Unread(runes ...rune) { 201 | newLine := false 202 | for _, r := range runes { 203 | if r == '\n' { 204 | newLine = true 205 | break 206 | } 207 | } 208 | 209 | if newLine { 210 | rd.line-- 211 | rd.col = rd.lastCol 212 | } else { 213 | rd.col-- 214 | } 215 | 216 | rd.buf = append(runes, rd.buf...) 217 | } 218 | 219 | // Position returns information about the stream including file name and 220 | // the position of the reader. 221 | func (rd Reader) Position() Position { 222 | file := strings.TrimSpace(rd.File) 223 | return Position{ 224 | File: file, 225 | Line: rd.line + 1, 226 | Column: rd.col, 227 | } 228 | } 229 | 230 | // SkipSpaces consumes and discards runes from stream repeatedly until a 231 | // character that is not a whitespace is identified. Along with standard 232 | // unicode white-space characters "," is also considered a white-space 233 | // and discarded. 234 | func (rd *Reader) SkipSpaces() error { 235 | for { 236 | r, err := rd.NextRune() 237 | if err != nil { 238 | return err 239 | } 240 | 241 | if !isSpace(r) { 242 | rd.Unread(r) 243 | break 244 | } 245 | } 246 | 247 | return nil 248 | } 249 | 250 | // readOne is same as One() but always returns un-annotated errors. 251 | func (rd *Reader) readOne() (Value, error) { 252 | if err := rd.SkipSpaces(); err != nil { 253 | return nil, err 254 | } 255 | 256 | r, err := rd.NextRune() 257 | if err != nil { 258 | return nil, err 259 | } 260 | 261 | if unicode.IsNumber(r) { 262 | return readNumber(rd, r) 263 | } else if r == '+' || r == '-' { 264 | r2, err := rd.NextRune() 265 | if err != nil && err != io.EOF { 266 | return nil, err 267 | } 268 | 269 | if err != io.EOF { 270 | rd.Unread(r2) 271 | if unicode.IsNumber(r2) { 272 | return readNumber(rd, r) 273 | } 274 | } 275 | } 276 | 277 | macro, found := rd.macros[r] 278 | if found { 279 | return macro(rd, r) 280 | } 281 | 282 | if r == dispatchTrigger { 283 | f, err := rd.execDispatch() 284 | if f != nil || err != nil { 285 | return f, err 286 | } 287 | } 288 | 289 | v, err := readSymbol(rd, r) 290 | if err != nil { 291 | return nil, err 292 | } 293 | 294 | if predefVal, found := predefSymbols[v.(Symbol).String()]; found { 295 | return predefVal, nil 296 | } 297 | 298 | return v, nil 299 | } 300 | 301 | func (rd *Reader) execDispatch() (Value, error) { 302 | pos := rd.Position() 303 | 304 | r2, err := rd.NextRune() 305 | if err != nil { 306 | // ignore the error and let readOne handle it. 307 | return nil, nil 308 | } 309 | 310 | dispatchMacro, found := rd.dispatch[r2] 311 | if !found { 312 | rd.Unread(r2) 313 | return nil, nil 314 | } 315 | 316 | rd.dispatching = true 317 | defer func() { 318 | rd.dispatching = false 319 | }() 320 | 321 | form, err := dispatchMacro(rd, r2) 322 | if err != nil { 323 | return nil, err 324 | } 325 | 326 | setPosition(form, pos) 327 | return form, nil 328 | } 329 | 330 | func (rd *Reader) annotateErr(e error) error { 331 | if e == io.EOF || e == ErrSkip { 332 | return e 333 | } 334 | 335 | return ReadError{ 336 | Cause: e, 337 | Position: rd.Position(), 338 | } 339 | } 340 | 341 | func readString(rd *Reader, _ rune) (Value, error) { 342 | var b strings.Builder 343 | 344 | for { 345 | r, err := rd.NextRune() 346 | if err != nil { 347 | if err == io.EOF { 348 | return nil, fmt.Errorf("%w: while reading string", ErrEOF) 349 | } 350 | 351 | return nil, err 352 | } 353 | 354 | if r == '\\' { 355 | r2, err := rd.NextRune() 356 | if err != nil { 357 | if err == io.EOF { 358 | return nil, fmt.Errorf("%w: while reading string", ErrEOF) 359 | } 360 | 361 | return nil, err 362 | } 363 | 364 | // TODO: Support for Unicode escape \uNN format. 365 | 366 | escaped, err := getEscape(r2) 367 | if err != nil { 368 | return nil, err 369 | } 370 | r = escaped 371 | } else if r == '"' { 372 | break 373 | } 374 | 375 | b.WriteRune(r) 376 | } 377 | 378 | return String(b.String()), nil 379 | } 380 | 381 | func readNumber(rd *Reader, init rune) (Value, error) { 382 | numStr, err := readToken(rd, init) 383 | if err != nil { 384 | return nil, err 385 | } 386 | 387 | decimalPoint := strings.ContainsRune(numStr, '.') 388 | isRadix := strings.ContainsRune(numStr, 'r') 389 | isScientific := strings.ContainsRune(numStr, 'e') 390 | 391 | switch { 392 | case isRadix && (decimalPoint || isScientific): 393 | return nil, fmt.Errorf("illegal number format: '%s'", numStr) 394 | 395 | case isScientific: 396 | return parseScientific(numStr) 397 | 398 | case decimalPoint: 399 | v, err := strconv.ParseFloat(numStr, 64) 400 | if err != nil { 401 | return nil, fmt.Errorf("illegal number format: '%s'", numStr) 402 | } 403 | return Float64(v), nil 404 | 405 | case isRadix: 406 | return parseRadix(numStr) 407 | 408 | default: 409 | v, err := strconv.ParseInt(numStr, 0, 64) 410 | if err != nil { 411 | return nil, fmt.Errorf("illegal number format '%s'", numStr) 412 | } 413 | 414 | return Int64(v), nil 415 | } 416 | } 417 | 418 | func readSymbol(rd *Reader, init rune) (Value, error) { 419 | pi := rd.Position() 420 | 421 | s, err := readToken(rd, init) 422 | if err != nil { 423 | return nil, err 424 | } 425 | 426 | return Symbol{ 427 | Value: s, 428 | Position: pi, 429 | }, nil 430 | } 431 | 432 | func readKeyword(rd *Reader, init rune) (Value, error) { 433 | token, err := readToken(rd, -1) 434 | if err != nil { 435 | return nil, err 436 | } 437 | 438 | return Keyword(token), nil 439 | } 440 | 441 | func readCharacter(rd *Reader, _ rune) (Value, error) { 442 | r, err := rd.NextRune() 443 | if err != nil { 444 | return nil, fmt.Errorf("%w: while reading character", ErrEOF) 445 | } 446 | 447 | token, err := readToken(rd, r) 448 | if err != nil { 449 | return nil, err 450 | } 451 | runes := []rune(token) 452 | 453 | if len(runes) == 1 { 454 | return Character(runes[0]), nil 455 | } 456 | 457 | v, found := charLiterals[token] 458 | if found { 459 | return Character(v), nil 460 | } 461 | 462 | if token[0] == 'u' { 463 | return readUnicodeChar(token[1:], 16) 464 | } 465 | 466 | return nil, fmt.Errorf("unsupported character: '\\%s'", token) 467 | } 468 | 469 | func readList(rd *Reader, _ rune) (Value, error) { 470 | pi := rd.Position() 471 | forms, err := readContainer(rd, '(', ')', "list") 472 | if err != nil { 473 | return nil, err 474 | } 475 | 476 | return &List{ 477 | Values: forms, 478 | Position: pi, 479 | }, nil 480 | } 481 | 482 | func readHashMap(rd *Reader, _ rune) (Value, error) { 483 | pi := rd.Position() 484 | forms, err := readContainer(rd, '{', '}', "hash-map") 485 | if err != nil { 486 | return nil, err 487 | } 488 | 489 | if len(forms)%2 != 0 { 490 | return nil, errors.New("expecting even number of forms within {}") 491 | } 492 | 493 | hm := &HashMap{ 494 | Position: pi, 495 | Data: map[Value]Value{}, 496 | } 497 | 498 | for i := 0; i < len(forms); i += 2 { 499 | if !isHashable(forms[i]) { 500 | return nil, fmt.Errorf("value of type '%s' is not hashable", 501 | reflect.TypeOf(forms[i])) 502 | } 503 | 504 | hm.Data[forms[i]] = forms[i+1] 505 | } 506 | 507 | return hm, nil 508 | } 509 | 510 | func readVector(rd *Reader, _ rune) (Value, error) { 511 | pi := rd.Position() 512 | 513 | forms, err := readContainer(rd, '[', ']', "vector") 514 | if err != nil { 515 | return nil, err 516 | } 517 | 518 | return Vector{ 519 | Values: forms, 520 | Position: pi, 521 | }, nil 522 | } 523 | 524 | func readSet(rd *Reader, _ rune) (Value, error) { 525 | pi := rd.Position() 526 | 527 | forms, err := readContainer(rd, '{', '}', "set") 528 | if err != nil { 529 | return nil, err 530 | } 531 | 532 | set := Set{ 533 | Values: forms, 534 | Position: pi, 535 | } 536 | if !set.valid() { 537 | return nil, errors.New("duplicate value in set") 538 | } 539 | 540 | return set, nil 541 | } 542 | 543 | func readUnicodeChar(token string, base int) (Character, error) { 544 | num, err := strconv.ParseInt(token, base, 64) 545 | if err != nil { 546 | return -1, fmt.Errorf("invalid unicode character: '\\%s'", token) 547 | } 548 | 549 | if num < 0 || num >= unicode.MaxRune { 550 | return -1, fmt.Errorf("invalid unicode character: '\\%s'", token) 551 | } 552 | 553 | return Character(num), nil 554 | } 555 | 556 | func readComment(rd *Reader, _ rune) (Value, error) { 557 | for { 558 | r, err := rd.NextRune() 559 | if err != nil { 560 | return nil, err 561 | } 562 | 563 | if r == '\n' { 564 | break 565 | } 566 | } 567 | 568 | return nil, ErrSkip 569 | } 570 | 571 | func quoteFormReader(expandFunc string) ReaderMacro { 572 | return func(rd *Reader, _ rune) (Value, error) { 573 | expr, err := rd.One() 574 | if err != nil { 575 | if err == io.EOF { 576 | return nil, fmt.Errorf("%w: while reading quote form", ErrEOF) 577 | } else if err == ErrSkip { 578 | return nil, errors.New("no-op form while reading quote form") 579 | } 580 | return nil, err 581 | } 582 | 583 | return &List{ 584 | Values: []Value{ 585 | Symbol{Value: expandFunc}, 586 | expr, 587 | }, 588 | }, nil 589 | } 590 | } 591 | 592 | func parseRadix(numStr string) (Int64, error) { 593 | parts := strings.Split(numStr, "r") 594 | if len(parts) != 2 { 595 | return 0, fmt.Errorf("illegal radix notation '%s'", numStr) 596 | } 597 | 598 | base, err := strconv.ParseInt(parts[0], 10, 64) 599 | if err != nil { 600 | return 0, fmt.Errorf("illegal radix notation '%s'", numStr) 601 | } 602 | 603 | repr := parts[1] 604 | if base < 0 { 605 | base = -1 * base 606 | repr = "-" + repr 607 | } 608 | 609 | v, err := strconv.ParseInt(repr, int(base), 64) 610 | if err != nil { 611 | return 0, fmt.Errorf("illegal radix notation '%s'", numStr) 612 | } 613 | 614 | return Int64(v), nil 615 | } 616 | 617 | func parseScientific(numStr string) (Float64, error) { 618 | parts := strings.Split(numStr, "e") 619 | if len(parts) != 2 { 620 | return 0, fmt.Errorf("illegal scientific notation '%s'", numStr) 621 | } 622 | 623 | base, err := strconv.ParseFloat(parts[0], 64) 624 | if err != nil { 625 | return 0, fmt.Errorf("illegal scientific notation '%s'", numStr) 626 | } 627 | 628 | pow, err := strconv.ParseInt(parts[1], 10, 64) 629 | if err != nil { 630 | return 0, fmt.Errorf("illegal scientific notation '%s'", numStr) 631 | } 632 | 633 | return Float64(base * math.Pow(10, float64(pow))), nil 634 | } 635 | 636 | func getEscape(r rune) (rune, error) { 637 | escaped, found := escapeMap[r] 638 | if !found { 639 | return -1, fmt.Errorf("illegal escape sequence '\\%c'", r) 640 | } 641 | 642 | return escaped, nil 643 | } 644 | 645 | func unmatchedDelimiter(_ *Reader, initRune rune) (Value, error) { 646 | return nil, fmt.Errorf("unmatched delimiter '%c'", initRune) 647 | } 648 | 649 | func readToken(rd *Reader, init rune) (string, error) { 650 | var b strings.Builder 651 | if init != -1 { 652 | b.WriteRune(init) 653 | } 654 | 655 | for { 656 | r, err := rd.NextRune() 657 | if err != nil { 658 | if err == io.EOF { 659 | break 660 | } 661 | return "", err 662 | } 663 | 664 | if rd.IsTerminal(r) { 665 | rd.Unread(r) 666 | break 667 | } 668 | 669 | b.WriteRune(r) 670 | } 671 | 672 | return b.String(), nil 673 | } 674 | 675 | func readContainer(rd *Reader, _ rune, end rune, formType string) ([]Value, error) { 676 | var forms []Value 677 | 678 | for { 679 | if err := rd.SkipSpaces(); err != nil { 680 | if err == io.EOF { 681 | return nil, fmt.Errorf("%w: while reading %s", ErrEOF, formType) 682 | } 683 | return nil, err 684 | } 685 | 686 | r, err := rd.NextRune() 687 | if err != nil { 688 | if err == io.EOF { 689 | return nil, fmt.Errorf("%w: while reading %s", ErrEOF, formType) 690 | } 691 | return nil, err 692 | } 693 | 694 | if r == end { 695 | break 696 | } 697 | rd.Unread(r) 698 | 699 | expr, err := rd.readOne() 700 | if err != nil { 701 | if err == ErrSkip { 702 | continue 703 | } 704 | return nil, err 705 | } 706 | forms = append(forms, expr) 707 | } 708 | 709 | return forms, nil 710 | } 711 | 712 | func defaultReadTable() map[rune]ReaderMacro { 713 | return map[rune]ReaderMacro{ 714 | '"': readString, 715 | ';': readComment, 716 | ':': readKeyword, 717 | '\\': readCharacter, 718 | '\'': quoteFormReader("quote"), 719 | '~': quoteFormReader("unquote"), 720 | '`': quoteFormReader("syntax-quote"), 721 | '(': readList, 722 | ')': unmatchedDelimiter, 723 | '[': readVector, 724 | ']': unmatchedDelimiter, 725 | '{': readHashMap, 726 | '}': unmatchedDelimiter, 727 | } 728 | } 729 | 730 | func defaultDispatchTable() map[rune]ReaderMacro { 731 | return map[rune]ReaderMacro{ 732 | '{': readSet, 733 | '}': unmatchedDelimiter, 734 | } 735 | } 736 | 737 | func isHashable(v Value) bool { 738 | switch v.(type) { 739 | case String, Int64, Float64, Nil, Character, Keyword: 740 | return true 741 | 742 | default: 743 | return false 744 | } 745 | } 746 | 747 | func isSpace(r rune) bool { 748 | return unicode.IsSpace(r) || r == ',' 749 | } 750 | 751 | func inferFileName(rs io.Reader) string { 752 | switch r := rs.(type) { 753 | case *os.File: 754 | return r.Name() 755 | 756 | case *strings.Reader: 757 | return "" 758 | 759 | case *bytes.Reader: 760 | return "" 761 | 762 | case net.Conn: 763 | return fmt.Sprintf("", r.LocalAddr()) 764 | 765 | default: 766 | return fmt.Sprintf("<%s>", reflect.TypeOf(rs)) 767 | } 768 | } 769 | 770 | // ReadError wraps the parsing/eval errors with relevant information. 771 | type ReadError struct { 772 | Position 773 | Cause error 774 | Messag string 775 | } 776 | 777 | // Unwrap returns underlying cause of the error. 778 | func (err ReadError) Unwrap() error { 779 | return err.Cause 780 | } 781 | 782 | func (err ReadError) Error() string { 783 | if e, ok := err.Cause.(ReadError); ok { 784 | return e.Error() 785 | } 786 | 787 | return fmt.Sprintf( 788 | "syntax error in '%s' (Line %d Col %d): %v", 789 | err.File, err.Line, err.Column, err.Cause, 790 | ) 791 | } 792 | 793 | // Position represents the positional information about a value read 794 | // by reader. 795 | type Position struct { 796 | File string 797 | Line int 798 | Column int 799 | } 800 | 801 | // GetPos returns the file, line and column values. 802 | func (pi Position) GetPos() (file string, line, col int) { 803 | return pi.File, pi.Line, pi.Column 804 | } 805 | 806 | // SetPos sets the position information. 807 | func (pi *Position) SetPos(file string, line, col int) { 808 | pi.File = file 809 | pi.Line = line 810 | pi.Column = col 811 | } 812 | 813 | func (pi Position) String() string { 814 | if pi.File == "" { 815 | pi.File = "" 816 | } 817 | 818 | return fmt.Sprintf("%s:%d:%d", pi.File, pi.Line, pi.Column) 819 | } 820 | 821 | func setPosition(form Value, pos Position) Value { 822 | p, canSet := form.(interface { 823 | SetPos(file string, line, col int) 824 | }) 825 | if !canSet { 826 | return form 827 | } 828 | 829 | p.SetPos(pos.File, pos.Line, pos.Column) 830 | return form 831 | } 832 | 833 | func getPosition(form Value) Position { 834 | p, hasPosition := form.(interface { 835 | GetPos() (file string, line, col int) 836 | }) 837 | if !hasPosition { 838 | return Position{} 839 | } 840 | 841 | file, line, col := p.GetPos() 842 | return Position{ 843 | File: file, 844 | Line: line, 845 | Column: col, 846 | } 847 | } 848 | -------------------------------------------------------------------------------- /reader_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/spy16/sabre" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | r io.Reader 18 | fileName string 19 | }{ 20 | { 21 | name: "WithStringReader", 22 | r: strings.NewReader(":test"), 23 | fileName: "", 24 | }, 25 | { 26 | name: "WithBytesReader", 27 | r: bytes.NewReader([]byte(":test")), 28 | fileName: "", 29 | }, 30 | { 31 | name: "WihFile", 32 | r: os.NewFile(0, "test.lisp"), 33 | fileName: "test.lisp", 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | rd := sabre.NewReader(tt.r) 40 | if rd == nil { 41 | t.Errorf("New() should return instance of Reader, got nil") 42 | } else if rd.File != tt.fileName { 43 | t.Errorf("sabre.File = \"%s\", want = \"%s\"", rd.File, tt.name) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestReader_SetMacro(t *testing.T) { 50 | t.Run("UnsetDefaultMacro", func(t *testing.T) { 51 | rd := sabre.NewReader(strings.NewReader("~hello")) 52 | rd.SetMacro('~', nil, false) // remove unquote operator 53 | 54 | var want sabre.Value 55 | want = sabre.Symbol{ 56 | Value: "~hello", 57 | Position: sabre.Position{ 58 | File: "", 59 | Line: 1, 60 | Column: 1, 61 | }, 62 | } 63 | 64 | got, err := rd.One() 65 | if err != nil { 66 | t.Errorf("unexpected error: %#v", err) 67 | } 68 | 69 | if !reflect.DeepEqual(got, want) { 70 | t.Errorf("got = %#v, want = %#v", got, want) 71 | } 72 | }) 73 | 74 | t.Run("CustomMacro", func(t *testing.T) { 75 | rd := sabre.NewReader(strings.NewReader("~hello")) 76 | rd.SetMacro('~', func(rd *sabre.Reader, _ rune) (sabre.Value, error) { 77 | var ru []rune 78 | for { 79 | r, err := rd.NextRune() 80 | if err != nil { 81 | if err == io.EOF { 82 | break 83 | } 84 | return nil, err 85 | } 86 | 87 | if rd.IsTerminal(r) { 88 | break 89 | } 90 | ru = append(ru, r) 91 | } 92 | 93 | return sabre.String(ru), nil 94 | }, false) // override unquote operator 95 | 96 | var want sabre.Value 97 | want = sabre.String("hello") 98 | 99 | got, err := rd.One() 100 | if err != nil { 101 | t.Errorf("unexpected error: %v", err) 102 | } 103 | 104 | if !reflect.DeepEqual(got, want) { 105 | t.Errorf("got = %v, want = %v", got, want) 106 | } 107 | }) 108 | } 109 | 110 | func TestReader_All(t *testing.T) { 111 | tests := []struct { 112 | name string 113 | src string 114 | want sabre.Value 115 | wantErr bool 116 | }{ 117 | { 118 | name: "ValidLiteralSample", 119 | src: `'hello #{} 123 "Hello\tWorld" 12.34 -0xF +010 true nil 0b1010 \a :hello`, 120 | want: sabre.Module{ 121 | &sabre.List{ 122 | Values: []sabre.Value{ 123 | sabre.Symbol{Value: "quote"}, 124 | sabre.Symbol{ 125 | Value: "hello", 126 | Position: sabre.Position{ 127 | File: "", 128 | Line: 1, 129 | Column: 2, 130 | }, 131 | }, 132 | }, 133 | }, 134 | sabre.Set{ 135 | Position: sabre.Position{ 136 | File: "", 137 | Line: 1, 138 | Column: 9, 139 | }, 140 | }, 141 | sabre.Int64(123), 142 | sabre.String("Hello\tWorld"), 143 | sabre.Float64(12.34), 144 | sabre.Int64(-15), 145 | sabre.Int64(8), 146 | sabre.Bool(true), 147 | sabre.Nil{}, 148 | sabre.Int64(10), 149 | sabre.Character('a'), 150 | sabre.Keyword("hello"), 151 | }, 152 | }, 153 | { 154 | name: "WithComment", 155 | src: `:valid-keyword ; comment should return errSkip`, 156 | want: sabre.Module{sabre.Keyword("valid-keyword")}, 157 | }, 158 | { 159 | name: "UnterminatedString", 160 | src: `:valid-keyword "unterminated string literal`, 161 | wantErr: true, 162 | }, 163 | { 164 | name: "CommentFollowedByForm", 165 | src: `; comment should return errSkip` + "\n" + `:valid-keyword`, 166 | want: sabre.Module{sabre.Keyword("valid-keyword")}, 167 | }, 168 | { 169 | name: "UnterminatedList", 170 | src: `:valid-keyword (add 1 2`, 171 | wantErr: true, 172 | }, 173 | { 174 | name: "UnterminatedVector", 175 | src: `:valid-keyword [1 2`, 176 | wantErr: true, 177 | }, 178 | { 179 | name: "EOFAfterQuote", 180 | src: `:valid-keyword '`, 181 | wantErr: true, 182 | }, 183 | { 184 | name: "CommentAfterQuote", 185 | src: `:valid-keyword ';hello world`, 186 | wantErr: true, 187 | }, 188 | { 189 | name: "UnbalancedParenthesis", 190 | src: `())`, 191 | wantErr: true, 192 | }, 193 | } 194 | for _, tt := range tests { 195 | t.Run(tt.name, func(t *testing.T) { 196 | got, err := sabre.NewReader(strings.NewReader(tt.src)).All() 197 | if (err != nil) != tt.wantErr { 198 | t.Errorf("All() error = %#v, wantErr %#v", err, tt.wantErr) 199 | return 200 | } 201 | if !reflect.DeepEqual(got, tt.want) { 202 | t.Errorf("All() got = %#v, want %#v", got, tt.want) 203 | } 204 | }) 205 | } 206 | } 207 | 208 | func TestReader_One(t *testing.T) { 209 | executeReaderTests(t, []readerTestCase{ 210 | { 211 | name: "Empty", 212 | src: "", 213 | want: nil, 214 | wantErr: true, 215 | }, 216 | { 217 | name: "QuotedEOF", 218 | src: "';comment is a no-op form\n", 219 | wantErr: true, 220 | }, 221 | { 222 | name: "ListEOF", 223 | src: "( 1", 224 | wantErr: true, 225 | }, 226 | { 227 | name: "UnQuote", 228 | src: "~(x 3)", 229 | want: &sabre.List{ 230 | Values: []sabre.Value{ 231 | sabre.Symbol{Value: "unquote"}, 232 | &sabre.List{ 233 | Values: []sabre.Value{ 234 | sabre.Symbol{ 235 | Value: "x", 236 | Position: sabre.Position{ 237 | File: "", 238 | Line: 1, 239 | Column: 3, 240 | }, 241 | }, 242 | sabre.Int64(3), 243 | }, 244 | Position: sabre.Position{ 245 | File: "", 246 | Line: 1, 247 | Column: 2, 248 | }, 249 | }, 250 | }, 251 | }, 252 | }, 253 | }) 254 | } 255 | 256 | func TestReader_One_Number(t *testing.T) { 257 | executeReaderTests(t, []readerTestCase{ 258 | { 259 | name: "NumberWithLeadingSpaces", 260 | src: " +1234", 261 | want: sabre.Int64(1234), 262 | }, 263 | { 264 | name: "PositiveInt", 265 | src: "+1245", 266 | want: sabre.Int64(1245), 267 | }, 268 | { 269 | name: "NegativeInt", 270 | src: "-234", 271 | want: sabre.Int64(-234), 272 | }, 273 | { 274 | name: "PositiveFloat", 275 | src: "+1.334", 276 | want: sabre.Float64(1.334), 277 | }, 278 | { 279 | name: "NegativeFloat", 280 | src: "-1.334", 281 | want: sabre.Float64(-1.334), 282 | }, 283 | { 284 | name: "PositiveHex", 285 | src: "0x124", 286 | want: sabre.Int64(0x124), 287 | }, 288 | { 289 | name: "NegativeHex", 290 | src: "-0x124", 291 | want: sabre.Int64(-0x124), 292 | }, 293 | { 294 | name: "PositiveOctal", 295 | src: "0123", 296 | want: sabre.Int64(0123), 297 | }, 298 | { 299 | name: "NegativeOctal", 300 | src: "-0123", 301 | want: sabre.Int64(-0123), 302 | }, 303 | { 304 | name: "PositiveBinary", 305 | src: "0b10", 306 | want: sabre.Int64(2), 307 | }, 308 | { 309 | name: "NegativeBinary", 310 | src: "-0b10", 311 | want: sabre.Int64(-2), 312 | }, 313 | { 314 | name: "PositiveBase2Radix", 315 | src: "2r10", 316 | want: sabre.Int64(2), 317 | }, 318 | { 319 | name: "NegativeBase2Radix", 320 | src: "-2r10", 321 | want: sabre.Int64(-2), 322 | }, 323 | { 324 | name: "PositiveBase4Radix", 325 | src: "4r123", 326 | want: sabre.Int64(27), 327 | }, 328 | { 329 | name: "NegativeBase4Radix", 330 | src: "-4r123", 331 | want: sabre.Int64(-27), 332 | }, 333 | { 334 | name: "ScientificSimple", 335 | src: "1e10", 336 | want: sabre.Float64(1e10), 337 | }, 338 | { 339 | name: "ScientificNegativeExponent", 340 | src: "1e-10", 341 | want: sabre.Float64(1e-10), 342 | }, 343 | { 344 | name: "ScientificWithDecimal", 345 | src: "1.5e10", 346 | want: sabre.Float64(1.5e+10), 347 | }, 348 | { 349 | name: "FloatStartingWith0", 350 | src: "012.3", 351 | want: sabre.Float64(012.3), 352 | wantErr: false, 353 | }, 354 | { 355 | name: "InvalidValue", 356 | src: "1ABe13", 357 | wantErr: true, 358 | }, 359 | { 360 | name: "InvalidScientificFormat", 361 | src: "1e13e10", 362 | wantErr: true, 363 | }, 364 | { 365 | name: "InvalidExponent", 366 | src: "1e1.3", 367 | wantErr: true, 368 | }, 369 | { 370 | name: "InvalidRadixFormat", 371 | src: "1r2r3", 372 | wantErr: true, 373 | }, 374 | { 375 | name: "RadixBase3WithDigit4", 376 | src: "-3r1234", 377 | wantErr: true, 378 | }, 379 | { 380 | name: "RadixMissingValue", 381 | src: "2r", 382 | wantErr: true, 383 | }, 384 | { 385 | name: "RadixInvalidBase", 386 | src: "2ar", 387 | wantErr: true, 388 | }, 389 | { 390 | name: "RadixWithFloat", 391 | src: "2.3r4", 392 | wantErr: true, 393 | }, 394 | { 395 | name: "DecimalPointInBinary", 396 | src: "0b1.0101", 397 | wantErr: true, 398 | }, 399 | { 400 | name: "InvalidDigitForOctal", 401 | src: "08", 402 | wantErr: true, 403 | }, 404 | { 405 | name: "IllegalNumberFormat", 406 | src: "9.3.2", 407 | wantErr: true, 408 | }, 409 | }) 410 | } 411 | 412 | func TestReader_One_String(t *testing.T) { 413 | executeReaderTests(t, []readerTestCase{ 414 | { 415 | name: "SimpleString", 416 | src: `"hello"`, 417 | want: sabre.String("hello"), 418 | }, 419 | { 420 | name: "EscapeQuote", 421 | src: `"double quote is \""`, 422 | want: sabre.String(`double quote is "`), 423 | }, 424 | { 425 | name: "EscapeSlash", 426 | src: `"hello\\world"`, 427 | want: sabre.String(`hello\world`), 428 | }, 429 | { 430 | name: "UnexpectedEOF", 431 | src: `"double quote is`, 432 | wantErr: true, 433 | }, 434 | { 435 | name: "InvalidEscape", 436 | src: `"hello \x world"`, 437 | wantErr: true, 438 | }, 439 | { 440 | name: "EscapeEOF", 441 | src: `"hello\`, 442 | wantErr: true, 443 | }, 444 | }) 445 | } 446 | 447 | func TestReader_One_Keyword(t *testing.T) { 448 | executeReaderTests(t, []readerTestCase{ 449 | { 450 | name: "SimpleASCII", 451 | src: `:test`, 452 | want: sabre.Keyword("test"), 453 | }, 454 | { 455 | name: "LeadingTrailingSpaces", 456 | src: " :test ", 457 | want: sabre.Keyword("test"), 458 | }, 459 | { 460 | name: "SimpleUnicode", 461 | src: `:∂`, 462 | want: sabre.Keyword("∂"), 463 | }, 464 | { 465 | name: "WithSpecialChars", 466 | src: `:this-is-valid?`, 467 | want: sabre.Keyword("this-is-valid?"), 468 | }, 469 | { 470 | name: "FollowedByMacroChar", 471 | src: `:this-is-valid'hello`, 472 | want: sabre.Keyword("this-is-valid"), 473 | }, 474 | }) 475 | } 476 | 477 | func TestReader_One_Character(t *testing.T) { 478 | executeReaderTests(t, []readerTestCase{ 479 | { 480 | name: "ASCIILetter", 481 | src: `\a`, 482 | want: sabre.Character('a'), 483 | }, 484 | { 485 | name: "ASCIIDigit", 486 | src: `\1`, 487 | want: sabre.Character('1'), 488 | }, 489 | { 490 | name: "Unicode", 491 | src: `\∂`, 492 | want: sabre.Character('∂'), 493 | }, 494 | { 495 | name: "Newline", 496 | src: `\newline`, 497 | want: sabre.Character('\n'), 498 | }, 499 | { 500 | name: "FormFeed", 501 | src: `\formfeed`, 502 | want: sabre.Character('\f'), 503 | }, 504 | { 505 | name: "Unicode", 506 | src: `\u00AE`, 507 | want: sabre.Character('®'), 508 | }, 509 | { 510 | name: "InvalidUnicode", 511 | src: `\uHELLO`, 512 | wantErr: true, 513 | }, 514 | { 515 | name: "OutOfRangeUnicode", 516 | src: `\u-100`, 517 | wantErr: true, 518 | }, 519 | { 520 | name: "UnknownSpecial", 521 | src: `\hello`, 522 | wantErr: true, 523 | }, 524 | { 525 | name: "EOF", 526 | src: `\`, 527 | wantErr: true, 528 | }, 529 | }) 530 | } 531 | 532 | func TestReader_One_Symbol(t *testing.T) { 533 | executeReaderTests(t, []readerTestCase{ 534 | { 535 | name: "SimpleASCII", 536 | src: `hello`, 537 | want: sabre.Symbol{ 538 | Value: "hello", 539 | Position: sabre.Position{ 540 | File: "", 541 | Line: 1, 542 | Column: 1, 543 | }, 544 | }, 545 | }, 546 | { 547 | name: "Unicode", 548 | src: `find-∂`, 549 | want: sabre.Symbol{ 550 | Value: "find-∂", 551 | Position: sabre.Position{ 552 | File: "", 553 | Line: 1, 554 | Column: 1, 555 | }, 556 | }, 557 | }, 558 | { 559 | name: "SingleChar", 560 | src: `+`, 561 | want: sabre.Symbol{ 562 | Value: "+", 563 | Position: sabre.Position{ 564 | File: "", 565 | Line: 1, 566 | Column: 1, 567 | }, 568 | }, 569 | }, 570 | }) 571 | } 572 | 573 | func TestReader_One_List(t *testing.T) { 574 | executeReaderTests(t, []readerTestCase{ 575 | { 576 | name: "EmptyList", 577 | src: `()`, 578 | want: &sabre.List{ 579 | Values: nil, 580 | Position: sabre.Position{ 581 | File: "", 582 | Line: 1, 583 | Column: 1, 584 | }, 585 | }, 586 | }, 587 | { 588 | name: "ListWithOneEntry", 589 | src: `(help)`, 590 | want: &sabre.List{ 591 | Values: []sabre.Value{ 592 | sabre.Symbol{ 593 | Value: "help", 594 | Position: sabre.Position{ 595 | File: "", 596 | Line: 1, 597 | Column: 2, 598 | }, 599 | }, 600 | }, 601 | Position: sabre.Position{ 602 | File: "", 603 | Line: 1, 604 | Column: 1, 605 | }, 606 | }, 607 | }, 608 | { 609 | name: "ListWithMultipleEntry", 610 | src: `(+ 0xF 3.1413)`, 611 | want: &sabre.List{ 612 | Values: []sabre.Value{ 613 | sabre.Symbol{ 614 | Value: "+", 615 | Position: sabre.Position{ 616 | File: "", 617 | Line: 1, 618 | Column: 2, 619 | }, 620 | }, 621 | sabre.Int64(15), 622 | sabre.Float64(3.1413), 623 | }, 624 | Position: sabre.Position{ 625 | File: "", 626 | Line: 1, 627 | Column: 1, 628 | }, 629 | }, 630 | }, 631 | { 632 | name: "ListWithCommaSeparator", 633 | src: `(+,0xF,3.1413)`, 634 | want: &sabre.List{ 635 | Values: []sabre.Value{ 636 | sabre.Symbol{ 637 | Value: "+", 638 | Position: sabre.Position{ 639 | File: "", 640 | Line: 1, 641 | Column: 2, 642 | }, 643 | }, 644 | sabre.Int64(15), 645 | sabre.Float64(3.1413), 646 | }, 647 | Position: sabre.Position{ 648 | File: "", 649 | Line: 1, 650 | Column: 1, 651 | }, 652 | }, 653 | }, 654 | { 655 | name: "MultiLine", 656 | src: `(+ 657 | 0xF 658 | 3.1413 659 | )`, 660 | want: &sabre.List{ 661 | Values: []sabre.Value{ 662 | sabre.Symbol{ 663 | Value: "+", 664 | Position: sabre.Position{ 665 | File: "", 666 | Line: 1, 667 | Column: 2, 668 | }, 669 | }, 670 | sabre.Int64(15), 671 | sabre.Float64(3.1413), 672 | }, 673 | Position: sabre.Position{ 674 | File: "", 675 | Line: 1, 676 | Column: 1, 677 | }, 678 | }, 679 | }, 680 | { 681 | name: "MultiLineWithComments", 682 | src: `(+ ; plus operator adds numerical values 683 | 0xF ; hex representation of 15 684 | 3.1413 ; value of math constant pi 685 | )`, 686 | want: &sabre.List{ 687 | Values: []sabre.Value{ 688 | sabre.Symbol{ 689 | Value: "+", 690 | Position: sabre.Position{ 691 | File: "", 692 | Line: 1, 693 | Column: 2, 694 | }, 695 | }, 696 | sabre.Int64(15), 697 | sabre.Float64(3.1413), 698 | }, 699 | Position: sabre.Position{ 700 | File: "", 701 | Line: 1, 702 | Column: 1, 703 | }, 704 | }, 705 | }, 706 | { 707 | name: "UnexpectedEOF", 708 | src: "(+ 1 2 ", 709 | wantErr: true, 710 | }, 711 | }) 712 | } 713 | 714 | func TestReader_One_Vector(t *testing.T) { 715 | executeReaderTests(t, []readerTestCase{ 716 | { 717 | name: "Empty", 718 | src: `[]`, 719 | want: sabre.Vector{ 720 | Values: nil, 721 | Position: sabre.Position{ 722 | File: "", 723 | Line: 1, 724 | Column: 1, 725 | }, 726 | }, 727 | }, 728 | { 729 | name: "WithOneEntry", 730 | src: `[help]`, 731 | want: sabre.Vector{ 732 | Values: []sabre.Value{ 733 | sabre.Symbol{ 734 | Value: "help", 735 | Position: sabre.Position{ 736 | File: "", 737 | Line: 1, 738 | Column: 2, 739 | }, 740 | }, 741 | }, 742 | Position: sabre.Position{ 743 | File: "", 744 | Line: 1, 745 | Column: 1, 746 | }, 747 | }, 748 | }, 749 | { 750 | name: "WithMultipleEntry", 751 | src: `[+ 0xF 3.1413]`, 752 | want: sabre.Vector{ 753 | Values: []sabre.Value{ 754 | sabre.Symbol{ 755 | Value: "+", 756 | Position: sabre.Position{ 757 | File: "", 758 | Line: 1, 759 | Column: 2, 760 | }, 761 | }, 762 | sabre.Int64(15), 763 | sabre.Float64(3.1413), 764 | }, 765 | Position: sabre.Position{ 766 | File: "", 767 | Line: 1, 768 | Column: 1, 769 | }, 770 | }, 771 | }, 772 | { 773 | name: "WithCommaSeparator", 774 | src: `[+,0xF,3.1413]`, 775 | want: sabre.Vector{ 776 | Values: []sabre.Value{ 777 | sabre.Symbol{ 778 | Value: "+", 779 | Position: sabre.Position{ 780 | File: "", 781 | Line: 1, 782 | Column: 2, 783 | }, 784 | }, 785 | sabre.Int64(15), 786 | sabre.Float64(3.1413), 787 | }, 788 | Position: sabre.Position{ 789 | File: "", 790 | Line: 1, 791 | Column: 1, 792 | }, 793 | }, 794 | }, 795 | { 796 | name: "MultiLine", 797 | src: `[+ 798 | 0xF 799 | 3.1413 800 | ]`, 801 | want: sabre.Vector{ 802 | Values: []sabre.Value{ 803 | sabre.Symbol{ 804 | Value: "+", 805 | Position: sabre.Position{ 806 | File: "", 807 | Line: 1, 808 | Column: 2, 809 | }, 810 | }, 811 | sabre.Int64(15), 812 | sabre.Float64(3.1413), 813 | }, 814 | Position: sabre.Position{ 815 | File: "", 816 | Line: 1, 817 | Column: 1, 818 | }, 819 | }, 820 | }, 821 | { 822 | name: "MultiLineWithComments", 823 | src: `[+ ; plus operator adds numerical values 824 | 0xF ; hex representation of 15 825 | 3.1413 ; value of math constant pi 826 | ]`, 827 | want: sabre.Vector{ 828 | Values: []sabre.Value{ 829 | sabre.Symbol{ 830 | Value: "+", 831 | Position: sabre.Position{ 832 | File: "", 833 | Line: 1, 834 | Column: 2, 835 | }, 836 | }, 837 | sabre.Int64(15), 838 | sabre.Float64(3.1413), 839 | }, 840 | Position: sabre.Position{ 841 | File: "", 842 | Line: 1, 843 | Column: 1, 844 | }, 845 | }, 846 | }, 847 | { 848 | name: "UnexpectedEOF", 849 | src: "[+ 1 2 ", 850 | wantErr: true, 851 | }, 852 | }) 853 | } 854 | 855 | func TestReader_One_Set(t *testing.T) { 856 | executeReaderTests(t, []readerTestCase{ 857 | { 858 | name: "Empty", 859 | src: "#{}", 860 | want: sabre.Set{ 861 | Values: nil, 862 | Position: sabre.Position{ 863 | File: "", 864 | Line: 1, 865 | Column: 2, 866 | }, 867 | }, 868 | }, 869 | { 870 | name: "Valid", 871 | src: "#{1 2 []}", 872 | want: sabre.Set{ 873 | Values: []sabre.Value{sabre.Int64(1), 874 | sabre.Int64(2), 875 | sabre.Vector{ 876 | Position: sabre.Position{ 877 | File: "", 878 | Column: 7, 879 | Line: 1, 880 | }, 881 | }, 882 | }, 883 | Position: sabre.Position{ 884 | File: "", 885 | Line: 1, 886 | Column: 2, 887 | }, 888 | }, 889 | }, 890 | { 891 | name: "HasDuplicate", 892 | src: "#{1 2 2}", 893 | wantErr: true, 894 | }, 895 | }) 896 | } 897 | 898 | func TestReader_One_HashMap(t *testing.T) { 899 | executeReaderTests(t, []readerTestCase{ 900 | { 901 | name: "SimpleKeywordMap", 902 | src: `{:age 10 903 | :name "Bob"}`, 904 | want: &sabre.HashMap{ 905 | Position: sabre.Position{File: "", Line: 1, Column: 1}, 906 | Data: map[sabre.Value]sabre.Value{ 907 | sabre.Keyword("age"): sabre.Int64(10), 908 | sabre.Keyword("name"): sabre.String("Bob"), 909 | }, 910 | }, 911 | }, 912 | { 913 | name: "NonHashableKey", 914 | src: `{[] 10}`, 915 | wantErr: true, 916 | }, 917 | { 918 | name: "OddNumberOfForms", 919 | src: "{:hello 10 :age}", 920 | wantErr: true, 921 | }, 922 | }) 923 | } 924 | 925 | type readerTestCase struct { 926 | name string 927 | src string 928 | want sabre.Value 929 | wantErr bool 930 | } 931 | 932 | func executeReaderTests(t *testing.T, tests []readerTestCase) { 933 | t.Parallel() 934 | 935 | for _, tt := range tests { 936 | t.Run(tt.name, func(t *testing.T) { 937 | got, err := sabre.NewReader(strings.NewReader(tt.src)).One() 938 | if (err != nil) != tt.wantErr { 939 | t.Errorf("One() error = %#v, wantErr %#v", err, tt.wantErr) 940 | return 941 | } 942 | if !reflect.DeepEqual(got, tt.want) { 943 | t.Errorf("One() got = %#v, want %#v", got, tt.want) 944 | } 945 | }) 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /reflect.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | scopeType = reflect.TypeOf((*Scope)(nil)).Elem() 11 | errorType = reflect.TypeOf((*error)(nil)).Elem() 12 | ) 13 | 14 | // ValueOf converts a Go value to sabre Value type. If 'v' is already a Value 15 | // type, it is returned as is. Primitive Go values like string, rune, int, float, 16 | // bool are converted to the right sabre Value types. Functions are converted to 17 | // the wrapper 'Fn' type. Value of type 'reflect.Type' will be wrapped as 'Type' 18 | // which enables initializing a value of that type when invoked. All other types 19 | // will be wrapped using 'Any' type. 20 | func ValueOf(v interface{}) Value { 21 | if v == nil { 22 | return Nil{} 23 | } 24 | 25 | if val, isValue := v.(Value); isValue { 26 | return val 27 | } 28 | 29 | if rt, ok := v.(reflect.Type); ok { 30 | return Type{T: rt} 31 | } 32 | 33 | rv := reflect.ValueOf(v) 34 | 35 | switch rv.Kind() { 36 | case reflect.Func: 37 | return reflectFn(rv) 38 | 39 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 40 | return Int64(rv.Int()) 41 | 42 | case reflect.Float32, reflect.Float64: 43 | return Float64(rv.Float()) 44 | 45 | case reflect.String: 46 | return String(rv.String()) 47 | 48 | case reflect.Uint8: 49 | return Character(rv.Uint()) 50 | 51 | case reflect.Bool: 52 | return Bool(rv.Bool()) 53 | 54 | default: 55 | // TODO: handle array & slice as list/vector. 56 | return Any{V: rv} 57 | } 58 | } 59 | 60 | // Any can be used to wrap arbitrary Go value into Sabre scope. 61 | type Any struct{ V reflect.Value } 62 | 63 | // Eval returns itself. 64 | func (any Any) Eval(_ Scope) (Value, error) { return any, nil } 65 | 66 | func (any Any) String() string { return fmt.Sprintf("Any{%v}", any.V) } 67 | 68 | // Type represents the type value of a given value. Type also implements 69 | // Value type. 70 | type Type struct{ T reflect.Type } 71 | 72 | // Eval returns the type value itself. 73 | func (t Type) Eval(_ Scope) (Value, error) { return t, nil } 74 | 75 | func (t Type) String() string { return fmt.Sprintf("%v", t.T) } 76 | 77 | // Invoke creates zero value of the given type. 78 | func (t Type) Invoke(scope Scope, args ...Value) (Value, error) { 79 | if isKind(t.T, reflect.Interface, reflect.Chan, reflect.Func) { 80 | return nil, fmt.Errorf("type '%s' cannot be initialized", t.T) 81 | } 82 | 83 | argVals, err := evalValueList(scope, args) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | switch t.T { 89 | case reflect.TypeOf((*List)(nil)): 90 | return &List{Values: argVals}, nil 91 | 92 | case reflect.TypeOf(Vector{}): 93 | return Vector{Values: argVals}, nil 94 | 95 | case reflect.TypeOf(Set{}): 96 | return Set{Values: Values(argVals).Uniq()}, nil 97 | } 98 | 99 | likeSeq := isKind(t.T, reflect.Slice, reflect.Array) 100 | if likeSeq { 101 | return Values(argVals), nil 102 | } 103 | 104 | return ValueOf(reflect.New(t.T).Elem().Interface()), nil 105 | } 106 | 107 | // reflectFn creates a wrapper Fn for the given Go function value using 108 | // reflection. 109 | func reflectFn(rv reflect.Value) *Fn { 110 | fw := wrapFunc(rv) 111 | return &Fn{ 112 | Args: fw.argNames(), 113 | Variadic: rv.Type().IsVariadic(), 114 | Func: func(scope Scope, args []Value) (_ Value, err error) { 115 | defer func() { 116 | if v := recover(); v != nil { 117 | err = fmt.Errorf("panic: %v", v) 118 | } 119 | }() 120 | 121 | args, err = evalValueList(scope, args) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | return fw.Call(scope, args...) 127 | }, 128 | } 129 | } 130 | 131 | func wrapFunc(rv reflect.Value) *funcWrapper { 132 | rt := rv.Type() 133 | 134 | minArgs := rt.NumIn() 135 | if rt.IsVariadic() { 136 | minArgs = minArgs - 1 137 | } 138 | 139 | passScope := (minArgs > 0) && (rt.In(0) == scopeType) 140 | lastOutIdx := rt.NumOut() - 1 141 | returnsErr := lastOutIdx >= 0 && rt.Out(lastOutIdx) == errorType 142 | if returnsErr { 143 | lastOutIdx-- // ignore error value from return values 144 | } 145 | 146 | return &funcWrapper{ 147 | rv: rv, 148 | rt: rt, 149 | minArgs: minArgs, 150 | passScope: passScope, 151 | returnsErr: returnsErr, 152 | lastOutIdx: lastOutIdx, 153 | } 154 | } 155 | 156 | type funcWrapper struct { 157 | rv reflect.Value 158 | rt reflect.Type 159 | passScope bool 160 | minArgs int 161 | returnsErr bool 162 | lastOutIdx int 163 | } 164 | 165 | func (fw *funcWrapper) Call(scope Scope, vals ...Value) (Value, error) { 166 | args := reflectValues(vals) 167 | if fw.passScope { 168 | args = append([]reflect.Value{reflect.ValueOf(scope)}, args...) 169 | } 170 | 171 | if err := fw.checkArgCount(len(args)); err != nil { 172 | return nil, err 173 | } 174 | 175 | args, err := fw.convertTypes(args...) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | return fw.wrapReturns(fw.rv.Call(args)...) 181 | } 182 | 183 | func (fw *funcWrapper) argNames() []string { 184 | cleanArgName := func(t reflect.Type) string { 185 | return strings.Replace(t.String(), "sabre.", "", -1) 186 | } 187 | 188 | var argNames []string 189 | 190 | i := 0 191 | for ; i < fw.minArgs; i++ { 192 | argNames = append(argNames, cleanArgName(fw.rt.In(i))) 193 | } 194 | 195 | if fw.rt.IsVariadic() { 196 | argNames = append(argNames, cleanArgName(fw.rt.In(i).Elem())) 197 | } 198 | 199 | return argNames 200 | } 201 | 202 | func (fw *funcWrapper) convertTypes(args ...reflect.Value) ([]reflect.Value, error) { 203 | var vals []reflect.Value 204 | 205 | for i := 0; i < fw.rt.NumIn(); i++ { 206 | if fw.rt.IsVariadic() && i == fw.rt.NumIn()-1 { 207 | c, err := convertArgsTo(fw.rt.In(i).Elem(), args[i:]...) 208 | if err != nil { 209 | return nil, err 210 | } 211 | vals = append(vals, c...) 212 | break 213 | } 214 | 215 | c, err := convertArgsTo(fw.rt.In(i), args[i]) 216 | if err != nil { 217 | return nil, err 218 | } 219 | vals = append(vals, c...) 220 | } 221 | 222 | return vals, nil 223 | } 224 | 225 | func (fw *funcWrapper) checkArgCount(count int) error { 226 | if count != fw.minArgs { 227 | if fw.rt.IsVariadic() && count < fw.minArgs { 228 | return fmt.Errorf( 229 | "call requires at-least %d argument(s), got %d", 230 | fw.minArgs, count, 231 | ) 232 | } else if !fw.rt.IsVariadic() && count > fw.minArgs { 233 | return fmt.Errorf( 234 | "call requires exactly %d argument(s), got %d", 235 | fw.minArgs, count, 236 | ) 237 | } 238 | } 239 | 240 | return nil 241 | } 242 | 243 | func (fw *funcWrapper) wrapReturns(vals ...reflect.Value) (Value, error) { 244 | if fw.rt.NumOut() == 0 { 245 | return Nil{}, nil 246 | } 247 | 248 | if fw.returnsErr { 249 | errIndex := fw.lastOutIdx + 1 250 | if !vals[errIndex].IsNil() { 251 | return nil, vals[errIndex].Interface().(error) 252 | } 253 | 254 | if fw.rt.NumOut() == 1 { 255 | return Nil{}, nil 256 | } 257 | } 258 | 259 | wrapped := sabreValues(vals[0 : fw.lastOutIdx+1]) 260 | if len(wrapped) == 1 { 261 | return wrapped[0], nil 262 | } 263 | 264 | return Values(wrapped), nil 265 | } 266 | 267 | func convertArgsTo(expected reflect.Type, args ...reflect.Value) ([]reflect.Value, error) { 268 | var converted []reflect.Value 269 | for _, arg := range args { 270 | actual := arg.Type() 271 | switch { 272 | case isAssignable(actual, expected): 273 | converted = append(converted, arg) 274 | 275 | case actual.ConvertibleTo(expected): 276 | converted = append(converted, arg.Convert(expected)) 277 | 278 | default: 279 | return args, fmt.Errorf( 280 | "value of type '%s' cannot be converted to '%s'", 281 | actual, expected, 282 | ) 283 | } 284 | } 285 | 286 | return converted, nil 287 | } 288 | 289 | func isAssignable(from, to reflect.Type) bool { 290 | return (from == to) || from.AssignableTo(to) || 291 | (to.Kind() == reflect.Interface && from.Implements(to)) 292 | } 293 | 294 | func reflectValues(args []Value) []reflect.Value { 295 | var rvs []reflect.Value 296 | for _, arg := range args { 297 | if any, ok := arg.(Any); ok { 298 | rvs = append(rvs, any.V) 299 | } else { 300 | rvs = append(rvs, reflect.ValueOf(arg)) 301 | } 302 | } 303 | return rvs 304 | } 305 | 306 | func sabreValues(rvs []reflect.Value) []Value { 307 | var vals []Value 308 | for _, arg := range rvs { 309 | vals = append(vals, ValueOf(arg.Interface())) 310 | } 311 | return vals 312 | } 313 | 314 | func isKind(rt reflect.Type, kinds ...reflect.Kind) bool { 315 | for _, k := range kinds { 316 | if k == rt.Kind() { 317 | return true 318 | } 319 | } 320 | 321 | return false 322 | } 323 | -------------------------------------------------------------------------------- /reflect_test.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | var simpleFn = func() {} 10 | var simpleFnRV = reflect.ValueOf(simpleFn) 11 | 12 | var anyVal = struct{ name string }{} 13 | var anyValRV = reflect.ValueOf(anyVal) 14 | 15 | func TestValueOf(t *testing.T) { 16 | t.Parallel() 17 | 18 | table := []struct { 19 | name string 20 | v interface{} 21 | want Value 22 | }{ 23 | { 24 | name: "int64", 25 | v: int64(10), 26 | want: Int64(10), 27 | }, 28 | { 29 | name: "float", 30 | v: float32(10.), 31 | want: Float64(10.), 32 | }, 33 | { 34 | name: "uint8", 35 | v: uint8('a'), 36 | want: Character('a'), 37 | }, 38 | { 39 | name: "bool", 40 | v: true, 41 | want: Bool(true), 42 | }, 43 | { 44 | name: "Value", 45 | v: Int64(10), 46 | want: Int64(10), 47 | }, 48 | { 49 | name: "Nil", 50 | v: nil, 51 | want: Nil{}, 52 | }, 53 | { 54 | name: "ReflectType", 55 | v: reflect.TypeOf(10), 56 | want: Type{T: reflect.TypeOf(10)}, 57 | }, 58 | { 59 | name: "Any", 60 | v: anyVal, 61 | want: Any{V: anyValRV}, 62 | }, 63 | } 64 | 65 | for _, tt := range table { 66 | t.Run(tt.name, func(t *testing.T) { 67 | got := ValueOf(tt.v) 68 | if !reflect.DeepEqual(got, tt.want) { 69 | t.Errorf("ValueOf() got = %v, want %v", got, tt.want) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func Test_strictFn_Invoke(t *testing.T) { 76 | t.Parallel() 77 | 78 | table := []struct { 79 | name string 80 | getScope func() Scope 81 | v interface{} 82 | args []Value 83 | want Value 84 | wantErr bool 85 | }{ 86 | { 87 | name: "WithScopeArgNoBinding", 88 | getScope: func() Scope { 89 | sc := NewScope(nil) 90 | sc.Bind("hello", Int64(10)) 91 | return sc 92 | }, 93 | v: func(sc Scope) (Value, error) { return sc.Resolve("hello") }, 94 | want: Int64(10), 95 | wantErr: false, 96 | }, 97 | { 98 | name: "SimpleNoArgNoReturn", 99 | v: func() {}, 100 | want: Nil{}, 101 | }, 102 | { 103 | name: "SimpleNoArg", 104 | v: func() int { return 10 }, 105 | want: Int64(10), 106 | }, 107 | { 108 | name: "NoArgSingleErrorReturn", 109 | v: func() error { return errors.New("failed") }, 110 | wantErr: true, 111 | }, 112 | { 113 | name: "NoArgSingleReturnNilError", 114 | v: func() error { return nil }, 115 | want: Nil{}, 116 | wantErr: false, 117 | }, 118 | { 119 | name: "SimpleNoReturn", 120 | v: func(arg Int64) {}, 121 | args: []Value{Int64(10)}, 122 | want: Nil{}, 123 | }, 124 | { 125 | name: "SimpleSingleReturn", 126 | v: func(arg Int64) int64 { return 10 }, 127 | args: []Value{Int64(10)}, 128 | want: Int64(10), 129 | }, 130 | { 131 | name: "MultiReturn", 132 | v: func(arg Int64) (int64, string) { return 10, "hello" }, 133 | args: []Value{Int64(10)}, 134 | want: Values([]Value{Int64(10), String("hello")}), 135 | }, 136 | { 137 | name: "NoArgMultiReturnWithError", 138 | v: func() (int, error) { return 0, errors.New("failed") }, 139 | wantErr: true, 140 | }, 141 | { 142 | name: "NoArgMultiReturnWithoutError", 143 | v: func() (int, error) { return 10, nil }, 144 | want: Int64(10), 145 | }, 146 | { 147 | name: "PureVariadicNoCallArgs", 148 | v: func(args ...Int64) int64 { 149 | sum := int64(0) 150 | for _, arg := range args { 151 | sum += int64(arg) 152 | } 153 | return sum 154 | }, 155 | want: Int64(0), 156 | }, 157 | { 158 | name: "PureVariadicWithCallArgs", 159 | v: func(args ...Int64) int64 { 160 | sum := int64(0) 161 | for _, arg := range args { 162 | sum += int64(arg) 163 | } 164 | return sum 165 | }, 166 | args: []Value{Int64(1), Int64(10)}, 167 | want: Int64(11), 168 | }, 169 | { 170 | name: "ArityErrorNonVariadic", 171 | v: func() {}, 172 | args: []Value{Int64(10)}, 173 | want: nil, 174 | wantErr: true, 175 | }, 176 | { 177 | name: "ArityErrorWithVariadic", 178 | v: func(first string, args ...int) {}, 179 | args: []Value{}, 180 | want: nil, 181 | wantErr: true, 182 | }, 183 | { 184 | name: "ArgTypeMismatchNonVariadic", 185 | v: func(a int) {}, 186 | args: []Value{String("hello")}, 187 | want: nil, 188 | wantErr: true, 189 | }, 190 | { 191 | name: "ArgTypeMismatchVariadic", 192 | v: func(args ...int) {}, 193 | args: []Value{String("hello")}, 194 | want: nil, 195 | wantErr: true, 196 | }, 197 | } 198 | 199 | for _, tt := range table { 200 | t.Run(tt.name, func(t *testing.T) { 201 | if tt.getScope == nil { 202 | tt.getScope = func() Scope { return NewScope(nil) } 203 | } 204 | 205 | fn := reflectFn(reflect.ValueOf(tt.v)) 206 | 207 | got, err := fn.Invoke(tt.getScope(), tt.args...) 208 | if (err != nil) != tt.wantErr { 209 | t.Errorf("Invoke() error = %v, wantErr %v", err, tt.wantErr) 210 | return 211 | } 212 | if !reflect.DeepEqual(got, tt.want) { 213 | t.Errorf("Invoke() got = %v, want %v", got, tt.want) 214 | } 215 | }) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /repl/option.go: -------------------------------------------------------------------------------- 1 | package repl 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/spy16/sabre" 11 | ) 12 | 13 | // Option implementations can be provided to New() to configure the REPL 14 | // during initialization. 15 | type Option func(repl *REPL) 16 | 17 | // ReaderFactory should return an instance of reader when called. This might 18 | // be called repeatedly. See WithReaderFactory() 19 | type ReaderFactory interface { 20 | NewReader(r io.Reader) *sabre.Reader 21 | } 22 | 23 | // ReaderFactoryFunc implements ReaderFactory using a function value. 24 | type ReaderFactoryFunc func(r io.Reader) *sabre.Reader 25 | 26 | // NewReader simply calls the wrapped function value and returns the result. 27 | func (factory ReaderFactoryFunc) NewReader(r io.Reader) *sabre.Reader { 28 | return factory(r) 29 | } 30 | 31 | // ErrMapper should map a custom Input error to nil to indicate error that 32 | // should be ignored by REPL, EOF to signal end of REPL session and any 33 | // other error to indicate a irrecoverable failure. 34 | type ErrMapper func(err error) error 35 | 36 | // WithInput sets the REPL's input stream. `nil` defaults to bufio.Scanner 37 | // backed by os.Stdin 38 | func WithInput(in Input, mapErr ErrMapper) Option { 39 | if in == nil { 40 | in = &lineReader{ 41 | scanner: bufio.NewScanner(os.Stdin), 42 | out: os.Stdout, 43 | } 44 | } 45 | 46 | if mapErr == nil { 47 | mapErr = func(e error) error { return e } 48 | } 49 | 50 | return func(repl *REPL) { 51 | repl.input = in 52 | repl.mapInputErr = mapErr 53 | } 54 | } 55 | 56 | // WithOutput sets the REPL's output stream.`nil` defaults to stdout. 57 | func WithOutput(w io.Writer) Option { 58 | if w == nil { 59 | w = os.Stdout 60 | } 61 | 62 | return func(repl *REPL) { 63 | repl.output = w 64 | } 65 | } 66 | 67 | // WithBanner sets the REPL's banner which is displayed once when the REPL 68 | // starts. 69 | func WithBanner(banner string) Option { 70 | return func(repl *REPL) { 71 | repl.banner = strings.TrimSpace(banner) 72 | } 73 | } 74 | 75 | // WithPrompts sets the prompt to be displayed when waiting for user input 76 | // in the REPL. 77 | func WithPrompts(oneLine, multiLine string) Option { 78 | return func(repl *REPL) { 79 | repl.prompt = oneLine 80 | repl.multiPrompt = multiLine 81 | } 82 | } 83 | 84 | // WithReaderFactory can be used set factory function for initializing sabre 85 | // Reader. This is useful when you want REPL to use custom reader instance. 86 | func WithReaderFactory(factory ReaderFactory) Option { 87 | if factory == nil { 88 | factory = ReaderFactoryFunc(sabre.NewReader) 89 | } 90 | 91 | return func(repl *REPL) { 92 | repl.factory = factory 93 | } 94 | } 95 | 96 | // WithPrinter sets the print function for the REPL. It is useful for customizing 97 | // how different types should be rendered into human-readable character streams. 98 | func WithPrinter(f func(io.Writer, interface{}) error) Option { 99 | if f == nil { 100 | f = func(w io.Writer, v interface{}) (err error) { 101 | switch v.(type) { 102 | case error: 103 | _, err = fmt.Fprintf(w, "%+v\n", v) 104 | 105 | default: 106 | _, err = fmt.Fprintf(w, "%s\n", v) 107 | } 108 | 109 | return 110 | } 111 | } 112 | 113 | return func(repl *REPL) { 114 | repl.printer = f 115 | } 116 | } 117 | 118 | func withDefaults(opts []Option) []Option { 119 | return append([]Option{ 120 | WithInput(nil, nil), 121 | WithOutput(nil), 122 | WithReaderFactory(nil), 123 | WithPrinter(nil), 124 | }, opts...) 125 | } 126 | 127 | type lineReader struct { 128 | scanner *bufio.Scanner 129 | out io.Writer 130 | prompt string 131 | } 132 | 133 | func (lr *lineReader) Readline() (string, error) { 134 | lr.out.Write([]byte(lr.prompt)) 135 | 136 | if !lr.scanner.Scan() { 137 | if lr.scanner.Err() == nil { // scanner swallows EOF 138 | return lr.scanner.Text(), io.EOF 139 | } 140 | 141 | return "", lr.scanner.Err() 142 | } 143 | 144 | return lr.scanner.Text(), nil 145 | } 146 | 147 | // no-op 148 | func (lr *lineReader) SetPrompt(p string) { 149 | lr.prompt = p 150 | } 151 | -------------------------------------------------------------------------------- /repl/repl.go: -------------------------------------------------------------------------------- 1 | // Package repl provides a REPL implementation and options to expose Sabre 2 | // features through a read-eval-print-loop. 3 | package repl 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "strings" 11 | 12 | "github.com/spy16/sabre" 13 | ) 14 | 15 | // New returns a new instance of REPL with given sabre Scope. Option values 16 | // can be used to configure REPL input, output etc. 17 | func New(scope sabre.Scope, opts ...Option) *REPL { 18 | repl := &REPL{ 19 | scope: scope, 20 | currentNamespace: func() string { return "" }, 21 | } 22 | 23 | if ns, ok := scope.(NamespacedScope); ok { 24 | repl.currentNamespace = ns.CurrentNS 25 | } 26 | 27 | for _, option := range withDefaults(opts) { 28 | option(repl) 29 | } 30 | 31 | return repl 32 | } 33 | 34 | // NamespacedScope can be implemented by sabre.Scope implementations to allow 35 | // namespace based isolation (similar to Clojure). REPL will call CurrentNS() 36 | // method to get the current Namespace and display it as part of input prompt. 37 | type NamespacedScope interface { 38 | CurrentNS() string 39 | } 40 | 41 | // REPL implements a read-eval-print loop for a generic Runtime. 42 | type REPL struct { 43 | scope sabre.Scope 44 | input Input 45 | output io.Writer 46 | mapInputErr ErrMapper 47 | currentNamespace func() string 48 | factory ReaderFactory 49 | 50 | banner string 51 | prompt string 52 | multiPrompt string 53 | 54 | printer func(io.Writer, interface{}) error 55 | } 56 | 57 | // Input implementation is used by REPL to read user-input. See WithInput() 58 | // REPL option to configure an Input. 59 | type Input interface { 60 | SetPrompt(string) 61 | Readline() (string, error) 62 | } 63 | 64 | // Loop starts the read-eval-print loop. Loop runs until context is cancelled 65 | // or input stream returns an irrecoverable error (See WithInput()). 66 | func (repl *REPL) Loop(ctx context.Context) error { 67 | repl.printBanner() 68 | repl.setPrompt(false) 69 | 70 | if repl.scope == nil { 71 | return errors.New("scope is not set") 72 | } 73 | 74 | for ctx.Err() == nil { 75 | err := repl.readEvalPrint() 76 | if err != nil { 77 | if err == io.EOF { 78 | return nil 79 | } 80 | 81 | return err 82 | } 83 | } 84 | 85 | return ctx.Err() 86 | } 87 | 88 | // readEval reads one form from the input, evaluates it and prints the result. 89 | func (repl *REPL) readEvalPrint() error { 90 | form, err := repl.read() 91 | if err != nil { 92 | switch err.(type) { 93 | case sabre.ReadError, sabre.EvalError: 94 | repl.print(err) 95 | default: 96 | return err 97 | } 98 | } 99 | 100 | if form == nil { 101 | return nil 102 | } 103 | 104 | v, err := sabre.Eval(repl.scope, form) 105 | if err != nil { 106 | return repl.print(err) 107 | } 108 | 109 | return repl.print(v) 110 | } 111 | 112 | func (repl *REPL) Write(b []byte) (int, error) { 113 | return repl.output.Write(b) 114 | } 115 | 116 | func (repl *REPL) print(v interface{}) error { 117 | return repl.printer(repl.output, v) 118 | } 119 | 120 | func (repl *REPL) read() (sabre.Value, error) { 121 | var src string 122 | lineNo := 1 123 | 124 | for { 125 | repl.setPrompt(lineNo > 1) 126 | 127 | line, err := repl.input.Readline() 128 | err = repl.mapInputErr(err) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | src += line + "\n" 134 | 135 | if strings.TrimSpace(src) == "" { 136 | return nil, nil 137 | } 138 | 139 | rd := repl.factory.NewReader(strings.NewReader(src)) 140 | rd.File = "REPL" 141 | 142 | form, err := rd.All() 143 | if err != nil { 144 | if errors.Is(err, sabre.ErrEOF) { 145 | lineNo++ 146 | continue 147 | } 148 | 149 | return nil, err 150 | } 151 | 152 | return form, nil 153 | } 154 | } 155 | 156 | func (repl *REPL) setPrompt(multiline bool) { 157 | if repl.prompt == "" { 158 | return 159 | } 160 | 161 | nsPrefix := repl.currentNamespace() 162 | prompt := repl.prompt 163 | 164 | if multiline { 165 | nsPrefix = strings.Repeat(" ", len(nsPrefix)+1) 166 | prompt = repl.multiPrompt 167 | } 168 | 169 | repl.input.SetPrompt(fmt.Sprintf("%s%s ", nsPrefix, prompt)) 170 | } 171 | 172 | func (repl *REPL) printBanner() { 173 | if repl.banner != "" { 174 | fmt.Println(repl.banner) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /sabre.go: -------------------------------------------------------------------------------- 1 | // Package sabre provides data structures, reader for reading LISP source 2 | // into data structures and functions for evluating forms against a context. 3 | package sabre 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | // Eval evaluates the given form against the scope and returns the result 12 | // of evaluation. 13 | func Eval(scope Scope, form Value) (Value, error) { 14 | if form == nil { 15 | return Nil{}, nil 16 | } 17 | 18 | v, err := form.Eval(scope) 19 | if err != nil { 20 | return v, newEvalErr(form, err) 21 | } 22 | 23 | return v, nil 24 | } 25 | 26 | // ReadEval consumes data from reader 'r' till EOF, parses into forms 27 | // and evaluates all the forms obtained and returns the result. 28 | func ReadEval(scope Scope, r io.Reader) (Value, error) { 29 | mod, err := NewReader(r).All() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return Eval(scope, mod) 35 | } 36 | 37 | // ReadEvalStr is a convenience wrapper for Eval that reads forms from 38 | // string and evaluates for result. 39 | func ReadEvalStr(scope Scope, src string) (Value, error) { 40 | return ReadEval(scope, strings.NewReader(src)) 41 | } 42 | 43 | // Scope implementation is responsible for managing value bindings. 44 | type Scope interface { 45 | Parent() Scope 46 | Bind(symbol string, v Value) error 47 | Resolve(symbol string) (Value, error) 48 | } 49 | 50 | func newEvalErr(v Value, err error) EvalError { 51 | if ee, ok := err.(EvalError); ok { 52 | return ee 53 | } else if ee, ok := err.(*EvalError); ok && ee != nil { 54 | return *ee 55 | } 56 | 57 | return EvalError{ 58 | Position: getPosition(v), 59 | Cause: err, 60 | Form: v, 61 | } 62 | } 63 | 64 | // EvalError represents error during evaluation. 65 | type EvalError struct { 66 | Position 67 | Cause error 68 | Form Value 69 | } 70 | 71 | // Unwrap returns the underlying cause of this error. 72 | func (ee EvalError) Unwrap() error { return ee.Cause } 73 | 74 | func (ee EvalError) Error() string { 75 | return fmt.Sprintf("eval-error in '%s' (at line %d:%d): %v", 76 | ee.File, ee.Line, ee.Column, ee.Cause, 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /sabre_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | const sampleProgram = ` 11 | (def v [1 2 3]) 12 | (def pi 3.1412) 13 | (def echo (fn* [arg] arg)) 14 | (echo pi) 15 | 16 | (def int-num 10) 17 | (def float-num 10.1234) 18 | (def list '(nil 1 [])) 19 | (def vector ["hello" nil]) 20 | (def set #{1 2 3}) 21 | (def empty-set #{}) 22 | 23 | (def complex-calc (let* [sample '(1 2 3 4 [])] 24 | (sample.First))) 25 | 26 | (assert (= int-num 10) 27 | (= float-num 10.1234) 28 | (= pi 3.1412) 29 | (= list '(nil 1 [])) 30 | (= vector ["hello" nil]) 31 | (= empty-set #{}) 32 | (= echo (fn* [arg] arg)) 33 | (= complex-calc 1)) 34 | 35 | (echo pi) 36 | ` 37 | 38 | func BenchmarkEval(b *testing.B) { 39 | scope := sabre.NewScope(nil) 40 | _ = scope.BindGo("inc", func(a int) int { 41 | return a + 1 42 | }) 43 | 44 | f := &sabre.List{ 45 | Values: sabre.Values{ 46 | sabre.Symbol{Value: "inc"}, 47 | sabre.Int64(10), 48 | }, 49 | } 50 | 51 | for i := 0; i < b.N; i++ { 52 | _, _ = sabre.Eval(scope, f) 53 | } 54 | } 55 | 56 | func BenchmarkGoCall(b *testing.B) { 57 | inc := func(a int) int { 58 | return a + 1 59 | } 60 | 61 | for i := 0; i < b.N; i++ { 62 | _ = inc(10) 63 | } 64 | } 65 | 66 | func TestEval(t *testing.T) { 67 | t.Parallel() 68 | 69 | table := []struct { 70 | name string 71 | src string 72 | getScope func() sabre.Scope 73 | want sabre.Value 74 | wantErr bool 75 | }{ 76 | { 77 | name: "Empty", 78 | src: "", 79 | want: sabre.Nil{}, 80 | }, 81 | { 82 | name: "SingleForm", 83 | src: "123", 84 | want: sabre.Int64(123), 85 | }, 86 | { 87 | name: "MultiForm", 88 | src: `123 [] ()`, 89 | want: &sabre.List{ 90 | Values: sabre.Values(nil), 91 | Position: sabre.Position{File: "", Line: 1, Column: 8}, 92 | }, 93 | }, 94 | { 95 | name: "WithFunctionCalls", 96 | getScope: func() sabre.Scope { 97 | scope := sabre.NewScope(nil) 98 | _ = scope.BindGo("ten?", func(i sabre.Int64) bool { 99 | return i == 10 100 | }) 101 | return scope 102 | }, 103 | src: `(ten? 10)`, 104 | want: sabre.Bool(true), 105 | }, 106 | { 107 | name: "ReadError", 108 | src: `123 [] (`, 109 | want: nil, 110 | wantErr: true, 111 | }, 112 | { 113 | name: "Program", 114 | src: sampleProgram, 115 | want: sabre.Float64(3.1412), 116 | }, 117 | } 118 | 119 | for _, tt := range table { 120 | t.Run(tt.name, func(t *testing.T) { 121 | scope := sabre.Scope(sabre.New()) 122 | if tt.getScope != nil { 123 | scope = tt.getScope() 124 | } 125 | 126 | scope.Bind("=", sabre.ValueOf(sabre.Compare)) 127 | scope.Bind("assert", &sabre.Fn{Func: asserter(t)}) 128 | 129 | got, err := sabre.ReadEvalStr(scope, tt.src) 130 | if (err != nil) != tt.wantErr { 131 | t.Errorf("Eval() error = %v, wantErr %v", err, tt.wantErr) 132 | return 133 | } 134 | if !reflect.DeepEqual(got, tt.want) { 135 | t.Errorf("Eval() got = %#v, want %#v", got, tt.want) 136 | } 137 | }) 138 | } 139 | } 140 | 141 | func asserter(t *testing.T) func(sabre.Scope, []sabre.Value) (sabre.Value, error) { 142 | return func(scope sabre.Scope, exprs []sabre.Value) (sabre.Value, error) { 143 | var res sabre.Value 144 | var err error 145 | 146 | for _, expr := range exprs { 147 | res, err = expr.Eval(scope) 148 | if err != nil { 149 | t.Errorf("%s: %s", expr, err) 150 | } 151 | 152 | if !isTruthy(res) { 153 | t.Errorf("assertion failed: %s (result=%v)", expr, res) 154 | } 155 | } 156 | 157 | return res, err 158 | } 159 | } 160 | 161 | func isTruthy(v sabre.Value) bool { 162 | if v == nil || v == (sabre.Nil{}) { 163 | return false 164 | } 165 | if b, ok := v.(sabre.Bool); ok { 166 | return bool(b) 167 | } 168 | return true 169 | } 170 | -------------------------------------------------------------------------------- /scope.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | // ErrResolving is returned when a scope implementation fails to resolve 10 | // a binding for given symbol. 11 | var ErrResolving = errors.New("unable to resolve symbol") 12 | 13 | // New initializes a new scope with all the core bindings. 14 | func New() *MapScope { 15 | scope := &MapScope{ 16 | parent: nil, 17 | mu: new(sync.RWMutex), 18 | bindings: map[string]Value{}, 19 | } 20 | 21 | scope.Bind("macroexpand", ValueOf(func(scope Scope, v Value) (Value, error) { 22 | f, _, err := MacroExpand(scope, v) 23 | return f, err 24 | })) 25 | 26 | scope.Bind("quote", SimpleQuote) 27 | scope.Bind("syntax-quote", SyntaxQuote) 28 | 29 | scope.Bind("fn*", Lambda) 30 | scope.Bind("macro*", Macro) 31 | scope.Bind("let*", Let) 32 | scope.Bind("if", If) 33 | scope.Bind("do", Do) 34 | scope.Bind("def", Def) 35 | scope.Bind("recur", Recur) 36 | 37 | return scope 38 | } 39 | 40 | // NewScope returns an instance of MapScope with no bindings. If you need 41 | // builtin special forms, pass result of New() as argument. 42 | func NewScope(parent Scope) *MapScope { 43 | return &MapScope{ 44 | parent: parent, 45 | mu: new(sync.RWMutex), 46 | bindings: map[string]Value{}, 47 | } 48 | } 49 | 50 | // MapScope implements Scope using a Go native hash-map. 51 | type MapScope struct { 52 | parent Scope 53 | mu *sync.RWMutex 54 | bindings map[string]Value 55 | } 56 | 57 | // Parent returns the parent scope of this scope. 58 | func (scope *MapScope) Parent() Scope { return scope.parent } 59 | 60 | // Bind adds the given value to the scope and binds the symbol to it. 61 | func (scope *MapScope) Bind(symbol string, v Value) error { 62 | scope.mu.Lock() 63 | defer scope.mu.Unlock() 64 | 65 | scope.bindings[symbol] = v 66 | return nil 67 | } 68 | 69 | // Resolve finds the value bound to the given symbol and returns it if 70 | // found in this scope or parent scope if any. Returns error otherwise. 71 | func (scope *MapScope) Resolve(symbol string) (Value, error) { 72 | scope.mu.RLock() 73 | defer scope.mu.RUnlock() 74 | 75 | v, found := scope.bindings[symbol] 76 | if !found { 77 | if scope.parent != nil { 78 | return scope.parent.Resolve(symbol) 79 | } 80 | 81 | return nil, fmt.Errorf("%w: %v", ErrResolving, symbol) 82 | } 83 | 84 | return v, nil 85 | } 86 | 87 | // BindGo is similar to Bind but handles conversion of Go value 'v' to 88 | // sabre Value type. See `ValueOf()` 89 | func (scope *MapScope) BindGo(symbol string, v interface{}) error { 90 | return scope.Bind(symbol, ValueOf(v)) 91 | } 92 | -------------------------------------------------------------------------------- /scope_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | var _ sabre.Scope = (*sabre.MapScope)(nil) 11 | 12 | func TestMapScope_Resolve(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | symbol string 16 | getScope func() *sabre.MapScope 17 | want sabre.Value 18 | wantErr bool 19 | }{ 20 | { 21 | name: "WithBinding", 22 | symbol: "hello", 23 | getScope: func() *sabre.MapScope { 24 | scope := sabre.NewScope(nil) 25 | _ = scope.Bind("hello", sabre.String("Hello World!")) 26 | return scope 27 | }, 28 | want: sabre.String("Hello World!"), 29 | }, 30 | { 31 | name: "WithBindingInParent", 32 | symbol: "pi", 33 | getScope: func() *sabre.MapScope { 34 | parent := sabre.NewScope(nil) 35 | _ = parent.Bind("pi", sabre.Float64(3.1412)) 36 | return sabre.NewScope(parent) 37 | }, 38 | want: sabre.Float64(3.1412), 39 | }, 40 | { 41 | name: "WithNoBinding", 42 | symbol: "hello", 43 | getScope: func() *sabre.MapScope { 44 | return sabre.NewScope(nil) 45 | }, 46 | want: nil, 47 | wantErr: true, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | scope := tt.getScope() 53 | 54 | got, err := scope.Resolve(tt.symbol) 55 | if (err != nil) != tt.wantErr { 56 | t.Errorf("Resolve() error = %v, wantErr %v", err, tt.wantErr) 57 | return 58 | } 59 | if !reflect.DeepEqual(got, tt.want) { 60 | t.Errorf("Resolve() got = %v, want %v", got, tt.want) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /specials.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | ) 9 | 10 | var ( 11 | // Def implements (def symbol value) form for defining bindings. 12 | Def = SpecialForm{ 13 | Name: "def", 14 | Parse: parseDef, 15 | } 16 | 17 | // Lambda defines an anonymous function and returns. Must have the form 18 | // (fn* name? [arg*] expr*) or (fn* name? ([arg]* expr*)+) 19 | Lambda = SpecialForm{ 20 | Name: "fn*", 21 | Parse: fnParser(false), 22 | } 23 | 24 | // Macro defines an anonymous function and returns. Must have the form 25 | // (macro* name? [arg*] expr*) or (fn* name? ([arg]* expr*)+) 26 | Macro = SpecialForm{ 27 | Name: "macro*", 28 | Parse: fnParser(true), 29 | } 30 | 31 | // Let implements the (let [binding*] expr*) form. expr are evaluated 32 | // with given local bindings. 33 | Let = SpecialForm{ 34 | Name: "let", 35 | Parse: parseLet, 36 | } 37 | 38 | // Do special form evaluates args one by one and returns the result of 39 | // the last expr. 40 | Do = SpecialForm{ 41 | Name: "do", 42 | Parse: parseDo, 43 | } 44 | 45 | // If implements if-conditional flow using (if test then else?) form. 46 | If = SpecialForm{ 47 | Name: "if", 48 | Parse: parseIf, 49 | } 50 | 51 | // SimpleQuote prevents a form from being evaluated. 52 | SimpleQuote = SpecialForm{ 53 | Name: "quote", 54 | Parse: parseSimpleQuote, 55 | } 56 | 57 | // SyntaxQuote recursively applies the quoting to the form. 58 | SyntaxQuote = SpecialForm{ 59 | Name: "syntax-quote", 60 | Parse: parseSyntaxQuote, 61 | } 62 | 63 | Recur = SpecialForm{ 64 | Name: "recur", 65 | Parse: parseRecur, 66 | } 67 | ) 68 | 69 | func fnParser(isMacro bool) func(scope Scope, forms []Value) (*Fn, error) { 70 | return func(scope Scope, forms []Value) (*Fn, error) { 71 | if len(forms) < 1 { 72 | return nil, fmt.Errorf("insufficient args (%d) for 'fn'", len(forms)) 73 | } 74 | 75 | nextIndex := 0 76 | def := MultiFn{ 77 | IsMacro: isMacro, 78 | } 79 | 80 | name, isName := forms[nextIndex].(Symbol) 81 | if isName { 82 | def.Name = name.String() 83 | nextIndex++ 84 | } 85 | 86 | return &Fn{ 87 | Func: func(_ Scope, args []Value) (Value, error) { 88 | _, isList := forms[nextIndex].(*List) 89 | if isList { 90 | for _, arg := range forms[nextIndex:] { 91 | spec, isList := arg.(*List) 92 | if !isList { 93 | return nil, fmt.Errorf("expected arg to be list, not %s", 94 | reflect.TypeOf(arg)) 95 | } 96 | 97 | fn, err := makeFn(scope, spec.Values) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | def.Methods = append(def.Methods, *fn) 103 | } 104 | } else { 105 | fn, err := makeFn(scope, forms[nextIndex:]) 106 | if err != nil { 107 | return nil, err 108 | } 109 | def.Methods = append(def.Methods, *fn) 110 | } 111 | return def, def.validate() 112 | }, 113 | }, nil 114 | } 115 | } 116 | 117 | func parseLet(scope Scope, args []Value) (*Fn, error) { 118 | if len(args) < 1 { 119 | return nil, fmt.Errorf("call requires at-least bindings argument") 120 | } 121 | 122 | vec, isVector := args[0].(Vector) 123 | if !isVector { 124 | return nil, fmt.Errorf( 125 | "first argument to let must be bindings vector, not %v", 126 | reflect.TypeOf(args[0]), 127 | ) 128 | } 129 | 130 | if len(vec.Values)%2 != 0 { 131 | return nil, fmt.Errorf("bindings must contain event forms") 132 | } 133 | 134 | var bindings []binding 135 | for i := 0; i < len(vec.Values); i += 2 { 136 | sym, isSymbol := vec.Values[i].(Symbol) 137 | if !isSymbol { 138 | return nil, fmt.Errorf( 139 | "item at %d must be symbol, not %s", 140 | i, vec.Values[i], 141 | ) 142 | } 143 | 144 | bindings = append(bindings, binding{ 145 | Name: sym.Value, 146 | Expr: vec.Values[i+1], 147 | }) 148 | } 149 | 150 | return &Fn{ 151 | Func: func(scope Scope, _ []Value) (Value, error) { 152 | letScope := NewScope(scope) 153 | for _, b := range bindings { 154 | v, err := b.Expr.Eval(letScope) 155 | if err != nil { 156 | return nil, err 157 | } 158 | _ = letScope.Bind(b.Name, v) 159 | } 160 | return Module(args[1:]).Eval(letScope) 161 | }, 162 | }, nil 163 | } 164 | 165 | func parseDo(scope Scope, args []Value) (*Fn, error) { 166 | return &Fn{ 167 | Func: func(scope Scope, args []Value) (Value, error) { 168 | if len(args) == 0 { 169 | return Nil{}, nil 170 | } 171 | 172 | results, err := evalValueList(scope, args) 173 | if err != nil { 174 | return nil, err 175 | } 176 | return results[len(results)-1], err 177 | }, 178 | }, nil 179 | } 180 | 181 | func parseDef(scope Scope, forms []Value) (*Fn, error) { 182 | if err := verifyArgCount([]int{2}, forms); err != nil { 183 | return nil, err 184 | } 185 | 186 | if err := analyze(scope, forms[1]); err != nil { 187 | return nil, err 188 | } 189 | 190 | return &Fn{ 191 | Func: func(scope Scope, args []Value) (Value, error) { 192 | sym, isSymbol := args[0].(Symbol) 193 | if !isSymbol { 194 | return nil, fmt.Errorf("first argument must be symbol, not '%v'", 195 | reflect.TypeOf(args[0])) 196 | } 197 | 198 | v, err := args[1].Eval(scope) 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | if err := rootScope(scope).Bind(sym.String(), v); err != nil { 204 | return nil, err 205 | } 206 | 207 | return sym, nil 208 | }, 209 | }, nil 210 | } 211 | 212 | func parseIf(scope Scope, args []Value) (*Fn, error) { 213 | if err := verifyArgCount([]int{2, 3}, args); err != nil { 214 | return nil, err 215 | } 216 | 217 | if err := analyzeSeq(scope, Values(args)); err != nil { 218 | return nil, err 219 | } 220 | 221 | return &Fn{ 222 | Func: func(scope Scope, args []Value) (Value, error) { 223 | test, err := args[0].Eval(scope) 224 | if err != nil { 225 | return nil, err 226 | } 227 | 228 | if !isTruthy(test) { 229 | // handle 'else' flow. 230 | if len(args) == 2 { 231 | return Nil{}, nil 232 | } 233 | 234 | return args[2].Eval(scope) 235 | } 236 | 237 | // handle 'if true' flow. 238 | return args[1].Eval(scope) 239 | }, 240 | }, nil 241 | } 242 | 243 | func parseSimpleQuote(scope Scope, forms []Value) (*Fn, error) { 244 | return &Fn{ 245 | Func: func(scope Scope, _ []Value) (Value, error) { 246 | return forms[0], verifyArgCount([]int{1}, forms) 247 | }, 248 | }, nil 249 | } 250 | 251 | func parseSyntaxQuote(scope Scope, forms []Value) (*Fn, error) { 252 | if err := verifyArgCount([]int{1}, forms); err != nil { 253 | return nil, err 254 | } 255 | 256 | if err := analyzeSeq(scope, Values(forms)); err != nil { 257 | return nil, err 258 | } 259 | 260 | return &Fn{ 261 | Func: func(scope Scope, _ []Value) (Value, error) { 262 | return recursiveQuote(scope, forms[0]) 263 | }, 264 | }, nil 265 | } 266 | 267 | func parseRecur(scope Scope, forms []Value) (*Fn, error) { 268 | 269 | return &Fn{ 270 | Func: func(scope Scope, args []Value) (Value, error) { 271 | symbol := Symbol{ 272 | Value: "recur", 273 | } 274 | 275 | results, err := evalValueList(scope, args) 276 | if err != nil { 277 | return nil, err 278 | } 279 | 280 | results = append([]Value{symbol}, results...) 281 | return &List{Values: results}, nil 282 | }, 283 | }, nil 284 | } 285 | 286 | // SpecialForm is a Value type for representing special forms that will be 287 | // subjected to an intermediate Parsing stage before evaluation. 288 | type SpecialForm struct { 289 | Name string 290 | Parse func(scope Scope, args []Value) (*Fn, error) 291 | } 292 | 293 | // Eval always returns error since it is not allowed to directly evaluate 294 | // a special form. 295 | func (sf SpecialForm) Eval(_ Scope) (Value, error) { 296 | return nil, errors.New("can't take value of special form") 297 | } 298 | 299 | func (sf SpecialForm) String() string { 300 | return fmt.Sprintf("SpecialForm{name=%s}", sf.Name) 301 | } 302 | 303 | func analyze(scope Scope, form Value) error { 304 | switch f := form.(type) { 305 | case Module: 306 | for _, expr := range f { 307 | if err := analyze(scope, expr); err != nil { 308 | return err 309 | } 310 | } 311 | 312 | case *List: 313 | return f.parse(scope) 314 | 315 | case String: 316 | return nil 317 | 318 | case Seq: 319 | return analyzeSeq(scope, f) 320 | } 321 | 322 | return nil 323 | } 324 | 325 | func analyzeSeq(scope Scope, seq Seq) error { 326 | for seq != nil { 327 | f := seq.First() 328 | if f == nil { 329 | break 330 | } 331 | 332 | if err := analyze(scope, f); err != nil { 333 | return err 334 | } 335 | seq = seq.Next() 336 | } 337 | 338 | return nil 339 | } 340 | 341 | func recursiveQuote(scope Scope, f Value) (Value, error) { 342 | switch v := f.(type) { 343 | case *List: 344 | if isUnquote(v.Values) { 345 | if err := verifyArgCount([]int{1}, v.Values[1:]); err != nil { 346 | return nil, err 347 | } 348 | 349 | return v.Values[1].Eval(scope) 350 | } 351 | 352 | quoted, err := quoteSeq(scope, v.Values) 353 | return &List{Values: quoted}, err 354 | 355 | case Set: 356 | quoted, err := quoteSeq(scope, v.Values) 357 | return Set{Values: quoted}, err 358 | 359 | case Vector: 360 | quoted, err := quoteSeq(scope, v.Values) 361 | return Vector{Values: quoted}, err 362 | 363 | case String: 364 | return f, nil 365 | 366 | case Seq: 367 | return quoteSeq(scope, v) 368 | 369 | default: 370 | return f, nil 371 | } 372 | } 373 | 374 | func isUnquote(list []Value) bool { 375 | if len(list) == 0 { 376 | return false 377 | } 378 | 379 | sym, isSymbol := list[0].(Symbol) 380 | if !isSymbol { 381 | return false 382 | } 383 | 384 | return sym.Value == "unquote" 385 | } 386 | 387 | func quoteSeq(scope Scope, seq Seq) (Values, error) { 388 | var quoted []Value 389 | for seq != nil { 390 | f := seq.First() 391 | if f == nil { 392 | break 393 | } 394 | 395 | q, err := recursiveQuote(scope, f) 396 | if err != nil { 397 | return nil, err 398 | } 399 | 400 | quoted = append(quoted, q) 401 | seq = seq.Next() 402 | } 403 | return quoted, nil 404 | } 405 | 406 | func verifyArgCount(arities []int, args []Value) error { 407 | actual := len(args) 408 | sort.Ints(arities) 409 | 410 | if len(arities) == 0 && actual != 0 { 411 | return fmt.Errorf("call requires no arguments, got %d", actual) 412 | } 413 | 414 | L := len(arities) 415 | switch { 416 | case L == 1 && actual != arities[0]: 417 | return fmt.Errorf("call requires exactly %d argument(s), got %d", arities[0], actual) 418 | 419 | case L == 2: 420 | c1, c2 := arities[0], arities[1] 421 | if actual != c1 && actual != c2 { 422 | return fmt.Errorf("call requires %d or %d argument(s), got %d", c1, c2, actual) 423 | } 424 | 425 | case L > 2: 426 | return fmt.Errorf("wrong number of arguments (%d) passed", actual) 427 | } 428 | 429 | return nil 430 | } 431 | 432 | func rootScope(scope Scope) Scope { 433 | if scope == nil { 434 | return nil 435 | } 436 | p := scope 437 | for temp := scope; temp != nil; temp = temp.Parent() { 438 | p = temp 439 | } 440 | return p 441 | } 442 | 443 | func isTruthy(v Value) bool { 444 | if v == (Nil{}) { 445 | return false 446 | } 447 | if b, ok := v.(Bool); ok { 448 | return bool(b) 449 | } 450 | return true 451 | } 452 | 453 | func makeFn(scope Scope, spec []Value) (*Fn, error) { 454 | if len(spec) < 1 { 455 | return nil, fmt.Errorf("insufficient args (%d) for 'fn'", len(spec)) 456 | } 457 | 458 | body := Module(spec[1:]) 459 | if err := analyze(scope, body); err != nil { 460 | return nil, err 461 | } 462 | 463 | fn := &Fn{Body: body} 464 | if err := fn.parseArgSpec(spec[0]); err != nil { 465 | return nil, err 466 | } 467 | 468 | return fn, nil 469 | } 470 | 471 | type binding struct { 472 | Name string 473 | Expr Value 474 | } 475 | 476 | func accessMember(target reflect.Value, member string) (reflect.Value, error) { 477 | if member[0] >= 'a' && member[0] <= 'z' { 478 | return reflect.Value{}, fmt.Errorf("cannot access private member") 479 | } 480 | 481 | if _, found := target.Type().MethodByName(member); found { 482 | return target.MethodByName(member), nil 483 | } 484 | 485 | if target.Kind() == reflect.Ptr { 486 | target = target.Elem() 487 | } 488 | 489 | if _, found := target.Type().FieldByName(member); found { 490 | return target.FieldByName(member), nil 491 | } 492 | 493 | return reflect.Value{}, fmt.Errorf("value of type '%s' has no member named '%s'", 494 | target.Type(), member) 495 | } 496 | -------------------------------------------------------------------------------- /specials_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/spy16/sabre" 10 | ) 11 | 12 | const src = ` 13 | (def temp (let* [pi 3.1412] 14 | pi)) 15 | 16 | (def hello (fn* hello 17 | ([arg] arg) 18 | ([arg & rest] rest))) 19 | ` 20 | 21 | func TestSpecials(t *testing.T) { 22 | scope := sabre.New() 23 | 24 | expected := sabre.MultiFn{ 25 | Name: "hello", 26 | IsMacro: false, 27 | Methods: []sabre.Fn{ 28 | { 29 | Args: []string{"arg", "rest"}, 30 | Variadic: true, 31 | Body: sabre.Module{ 32 | sabre.Symbol{Value: "rest"}, 33 | }, 34 | }, 35 | }, 36 | } 37 | 38 | res, err := sabre.ReadEvalStr(scope, src) 39 | if err != nil { 40 | t.Errorf("Eval() unexpected error: %v", err) 41 | } 42 | if reflect.DeepEqual(res, expected) { 43 | t.Errorf("Eval() expected=%v, got=%v", expected, res) 44 | } 45 | } 46 | 47 | func TestDot(t *testing.T) { 48 | t.Parallel() 49 | 50 | table := []struct { 51 | name string 52 | src string 53 | want sabre.Value 54 | wantErr bool 55 | }{ 56 | { 57 | name: "StringFieldAccess", 58 | src: "foo.Name", 59 | want: sabre.String("Bob"), 60 | }, 61 | { 62 | name: "BoolFieldAccess", 63 | src: "foo.Enabled", 64 | want: sabre.Bool(false), 65 | }, 66 | { 67 | name: "MethodAccess", 68 | src: `(foo.Bar "Baz")`, 69 | want: sabre.String("Bar(\"Baz\")"), 70 | }, 71 | { 72 | name: "MethodAccessPtr", 73 | src: `(foo.BarPtr "Bob")`, 74 | want: sabre.String("BarPtr(\"Bob\")"), 75 | }, 76 | { 77 | name: "EvalFailed", 78 | src: `blah.BarPtr`, 79 | want: nil, 80 | wantErr: true, 81 | }, 82 | { 83 | name: "NonExistentMember", 84 | src: `foo.Baz`, 85 | want: nil, 86 | wantErr: true, 87 | }, 88 | { 89 | name: "PrivateMember", 90 | src: `foo.privateMember`, 91 | want: nil, 92 | wantErr: true, 93 | }, 94 | } 95 | 96 | for _, tt := range table { 97 | t.Run(tt.name, func(t *testing.T) { 98 | scope := sabre.New() 99 | scope.BindGo("foo", &Foo{ 100 | Name: "Bob", 101 | }) 102 | 103 | form, err := sabre.NewReader(strings.NewReader(tt.src)).All() 104 | if err != nil { 105 | t.Fatalf("failed to read source='%s': %+v", tt.src, err) 106 | } 107 | 108 | got, err := sabre.Eval(scope, form) 109 | if (err != nil) != tt.wantErr { 110 | t.Errorf("Eval() unexpected error: %+v", err) 111 | } 112 | if !reflect.DeepEqual(tt.want, got) { 113 | t.Errorf("Eval() want=%#v, got=%#v", tt.want, got) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | // Foo is a dummy type for member access tests. 120 | type Foo struct { 121 | Name string 122 | Enabled bool 123 | privateMember bool 124 | } 125 | 126 | func (foo *Foo) BarPtr(arg string) string { 127 | return fmt.Sprintf("BarPtr(\"%s\")", arg) 128 | } 129 | 130 | func (foo Foo) Bar(arg string) string { 131 | return fmt.Sprintf("Bar(\"%s\")", arg) 132 | } 133 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package sabre 2 | 3 | import "reflect" 4 | 5 | // Value represents data/forms in sabre. This includes those emitted by 6 | // Reader, values obtained as result of an evaluation etc. 7 | type Value interface { 8 | // String should return the LISP representation of the value. 9 | String() string 10 | // Eval should evaluate this value against the scope and return 11 | // the resultant value or an evaluation error. 12 | Eval(scope Scope) (Value, error) 13 | } 14 | 15 | // Invokable represents any value that supports invocation. Vector, Fn 16 | // etc support invocation. 17 | type Invokable interface { 18 | Value 19 | Invoke(scope Scope, args ...Value) (Value, error) 20 | } 21 | 22 | // Seq implementations represent a sequence/list of values. 23 | type Seq interface { 24 | Value 25 | // First should return first value of the sequence or nil if the 26 | // sequence is empty. 27 | First() Value 28 | // Next should return the remaining sequence when the first value 29 | // is excluded. 30 | Next() Seq 31 | // Cons should add the value to the beginning of the sequence and 32 | // return the new sequence. 33 | Cons(v Value) Seq 34 | // Conj should join the given values to the sequence and return a 35 | // new sequence. 36 | Conj(vals ...Value) Seq 37 | } 38 | 39 | // Compare compares two values in an identity independent manner. If 40 | // v1 has `Compare(Value) bool` method, the comparison is delegated to 41 | // it as `v1.Compare(v2)`. 42 | func Compare(v1, v2 Value) bool { 43 | if (v1 == nil && v2 == nil) || 44 | (v1 == (Nil{}) && v2 == (Nil{})) { 45 | return true 46 | } 47 | 48 | if cmp, ok := v1.(comparable); ok { 49 | return cmp.Compare(v2) 50 | } 51 | 52 | return reflect.DeepEqual(v1, v2) 53 | } 54 | 55 | // comparable can be implemented by Value types to support comparison. 56 | // See Compare(). 57 | type comparable interface { 58 | Value 59 | Compare(other Value) bool 60 | } 61 | 62 | // Values represents a list of values and implements the Seq interface. 63 | type Values []Value 64 | 65 | // Eval returns itself. 66 | func (vals Values) Eval(_ Scope) (Value, error) { return vals, nil } 67 | 68 | // First returns the first value in the list if the list is not empty. 69 | // Returns Nil{} otherwise. 70 | func (vals Values) First() Value { 71 | if len(vals) == 0 { 72 | return nil 73 | } 74 | return vals[0] 75 | } 76 | 77 | // Next returns a new sequence containing values after the first one. If 78 | // there are no values to create a next sequence, returns nil. 79 | func (vals Values) Next() Seq { 80 | if len(vals) <= 1 { 81 | return nil 82 | } 83 | return &List{Values: Values(vals[1:])} 84 | } 85 | 86 | // Cons returns a new sequence where 'v' is prepended to the values. 87 | func (vals Values) Cons(v Value) Seq { 88 | return &List{Values: append(Values{v}, vals...)} 89 | } 90 | 91 | // Conj returns a new sequence where 'v' is appended to the values. 92 | func (vals Values) Conj(args ...Value) Seq { 93 | return &List{Values: append(vals, args...)} 94 | } 95 | 96 | // Size returns the number of items in the list. 97 | func (vals Values) Size() int { return len(vals) } 98 | 99 | // Compare compares the values in this sequence to the other sequence. 100 | // other sequence will be realized for comparison. 101 | func (vals Values) Compare(v Value) bool { 102 | other, ok := v.(Seq) 103 | if !ok { 104 | return false 105 | } 106 | 107 | if s, hasSize := other.(interface { 108 | Size() int 109 | }); hasSize { 110 | if vals.Size() != s.Size() { 111 | return false 112 | } 113 | } 114 | 115 | var this Seq = vals 116 | isEqual := true 117 | for this != nil && other != nil { 118 | v1, v2 := this.First(), other.First() 119 | isEqual = isEqual && Compare(v1, v2) 120 | if !isEqual { 121 | break 122 | } 123 | 124 | this = this.Next() 125 | other = other.Next() 126 | } 127 | 128 | return isEqual && (this == nil && other == nil) 129 | } 130 | 131 | // Uniq removes all the duplicates from the given value array. 132 | // TODO: remove this naive implementation 133 | func (vals Values) Uniq() []Value { 134 | var result []Value 135 | 136 | hashSet := map[string]struct{}{} 137 | for _, v := range vals { 138 | src := v.String() 139 | if _, found := hashSet[src]; !found { 140 | hashSet[src] = struct{}{} 141 | result = append(result, v) 142 | } 143 | } 144 | 145 | return result 146 | } 147 | 148 | func (vals Values) String() string { 149 | return containerString(vals, "(", ")", " ") 150 | } 151 | 152 | func evalValueList(scope Scope, vals []Value) ([]Value, error) { 153 | var result []Value 154 | 155 | for _, arg := range vals { 156 | v, err := arg.Eval(scope) 157 | if err != nil { 158 | return nil, newEvalErr(arg, err) 159 | } 160 | 161 | result = append(result, v) 162 | } 163 | 164 | return result, nil 165 | } 166 | -------------------------------------------------------------------------------- /value_test.go: -------------------------------------------------------------------------------- 1 | package sabre_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/sabre" 8 | ) 9 | 10 | var _ sabre.Seq = sabre.Values(nil) 11 | 12 | func TestValues_First(t *testing.T) { 13 | t.Run("Empty", func(t *testing.T) { 14 | vals := sabre.Values{} 15 | 16 | want := sabre.Value(nil) 17 | got := vals.First() 18 | 19 | if !reflect.DeepEqual(got, want) { 20 | t.Errorf("First() want=%#v, got=%#v", want, got) 21 | } 22 | }) 23 | 24 | t.Run("Nil", func(t *testing.T) { 25 | vals := sabre.Values(nil) 26 | want := sabre.Value(nil) 27 | got := vals.First() 28 | 29 | if !reflect.DeepEqual(got, want) { 30 | t.Errorf("First() want=%#v, got=%#v", want, got) 31 | } 32 | }) 33 | 34 | t.Run("NonEmpty", func(t *testing.T) { 35 | vals := sabre.Values{sabre.Int64(10)} 36 | 37 | want := sabre.Int64(10) 38 | got := vals.First() 39 | 40 | if !reflect.DeepEqual(got, want) { 41 | t.Errorf("First() want=%#v, got=%#v", want, got) 42 | } 43 | }) 44 | } 45 | 46 | func TestValues_Next(t *testing.T) { 47 | t.Parallel() 48 | 49 | table := []struct { 50 | name string 51 | vals []sabre.Value 52 | want sabre.Seq 53 | }{ 54 | { 55 | name: "Nil", 56 | vals: []sabre.Value(nil), 57 | want: nil, 58 | }, 59 | { 60 | name: "Empty", 61 | vals: []sabre.Value{}, 62 | want: nil, 63 | }, 64 | { 65 | name: "SingleItem", 66 | vals: []sabre.Value{sabre.Int64(10)}, 67 | want: nil, 68 | }, 69 | { 70 | name: "MultiItem", 71 | vals: []sabre.Value{sabre.Int64(10), sabre.String("hello"), sabre.Bool(true)}, 72 | want: &sabre.List{Values: sabre.Values{sabre.String("hello"), sabre.Bool(true)}}, 73 | }, 74 | } 75 | 76 | for _, tt := range table { 77 | t.Run(tt.name, func(t *testing.T) { 78 | got := sabre.Values(tt.vals).Next() 79 | 80 | if !reflect.DeepEqual(got, tt.want) { 81 | t.Errorf("Next() want=%#v, got=%#v", tt.want, got) 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func TestValues_Cons(t *testing.T) { 88 | t.Parallel() 89 | 90 | table := []struct { 91 | name string 92 | vals []sabre.Value 93 | item sabre.Value 94 | want sabre.Values 95 | }{ 96 | { 97 | name: "Nil", 98 | vals: []sabre.Value(nil), 99 | item: sabre.Int64(10), 100 | want: sabre.Values{sabre.Int64(10)}, 101 | }, 102 | { 103 | name: "Empty", 104 | vals: []sabre.Value{}, 105 | item: sabre.Int64(10), 106 | want: sabre.Values{sabre.Int64(10)}, 107 | }, 108 | { 109 | name: "SingleItem", 110 | vals: []sabre.Value{sabre.Int64(10)}, 111 | item: sabre.String("hello"), 112 | want: sabre.Values{sabre.String("hello"), sabre.Int64(10)}, 113 | }, 114 | { 115 | name: "MultiItem", 116 | vals: []sabre.Value{sabre.Int64(10), sabre.String("hello")}, 117 | item: sabre.Bool(true), 118 | want: sabre.Values{sabre.Bool(true), sabre.Int64(10), sabre.String("hello")}, 119 | }, 120 | } 121 | 122 | for _, tt := range table { 123 | t.Run(tt.name, func(t *testing.T) { 124 | got := sabre.Values(tt.vals).Cons(tt.item) 125 | 126 | if !reflect.DeepEqual(got, &sabre.List{Values: tt.want}) { 127 | t.Errorf("Next() want=%#v, got=%#v", tt.want, got) 128 | } 129 | }) 130 | } 131 | } 132 | --------------------------------------------------------------------------------