├── .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 | [](https://godoc.org/github.com/spy16/sabre) [](https://goreportcard.com/report/github.com/spy16/sabre) [](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 |
--------------------------------------------------------------------------------