├── .gitignore
├── .midje.clj
├── LICENSE
├── README.md
├── bin
└── funcgo-compiler-0.5.1-standalone.jar
├── doc
├── FAQ.md
└── reference.md
├── project.clj
├── publish
├── src
└── funcgo
│ ├── codegen.go
│ ├── core.go
│ ├── main.go
│ ├── parser.go
│ └── symboltable.go
├── tasks
└── leiningen
│ ├── fgoc.clj
│ └── fgoc.go
├── test-cljs
├── index.hl.gos
└── mainupload.gos
└── test
└── funcgo
├── async.go
├── clojure_cookbook_test.go
├── compiler_test.go
├── fundamental_test.go
├── goscript_test.go
├── joy.go
├── misc.go
├── reference
├── contract.go
├── hello.go
├── larger.go
├── matrix.go
├── operator.go
├── reference.go
├── row.go
└── row_test.go
├── webmunged.go
└── wiki.go
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.class
7 | /.lein-*
8 | /.nrepl-port
9 | *~
10 | *.cljs
11 | *.cljs.hl
12 | /src/*.clj
13 | /src/*/*.clj
14 | /test/*.clj
15 | /test/**/*.clj
16 |
--------------------------------------------------------------------------------
/.midje.clj:
--------------------------------------------------------------------------------
1 | ;; (change-defaults :print-level :print-facts)
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of Washington and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [Tour][fgotour] - [FAQ][faq] - [Reference][ref]
2 |
3 | # funcgo
4 |
5 | Funcgo is a compiler that converts Functional Go into Clojure, to run
6 | on the JVM or as JavaScript.
7 |
8 | ## Try It Out
9 |
10 | Without installing anything you can try the [online tour][fgotour]
11 | where you can type Funcgo and see how it converts to Clojure
12 | and evaluates.
13 |
14 | (By the way the online tour is itself an example web application that
15 | [uses Funcgo][fgosite] for both it server side (JVM) and its
16 | client side (JS).
17 |
18 | ## Quick Start
19 |
20 | #### 1. Set up Clojure development environment.
21 |
22 | Follow the [install instructions for Leiningen][lein]
23 |
24 | On the command line, type ...
25 | ```sh
26 | lein new appfgo hellofuncgo
27 | cd hellofuncgo
28 | lein do fgoc, run
29 | ```
30 | This should print out `Hello, World from Funcgo`.
31 |
32 | You can also execute the tests ...
33 |
34 | ```sh
35 | lein do fgoc, test
36 | ```
37 |
38 | #### 2. Write Funcgo
39 |
40 | Edit `src/hellofuncgo/core.go` and modify it.
41 |
42 | Then again do ...
43 | ```sh
44 | lein do fgoc, run
45 | ```
46 |
47 | Congratulations, you have just written and executed your first
48 | Funcgo program!
49 |
50 | ## Next Steps
51 |
52 | You can get a better feel for the language by reading the Introduction
53 | to the Funcgo Language section below.
54 |
55 | To dive deeper, see [Funcgo Reference][ref] doc.
56 |
57 | To browse some actual working code, the biggest and most complex
58 | program so far written in Funcgo is its own compiler. (Turtles
59 | all the way down!) You might start at the `main.go` file in
60 | [the source directory][src].
61 |
62 | A smaller set of working code is [fgolib][fgolib]. In addition to
63 | looking at the Funcgo code there, you can also examine the
64 | `project.clj` file which is a working example of using the Leiningen
65 | plugin.
66 |
67 | If you want to see a complete web app, that generates both
68 | Clojurescript and Clojure, see
69 | [the source for www.funcgo.org][fgosite].
70 |
71 | There is also do `lein fgoc --repl` to bring up the beginnings of a
72 | REPL that you can use to explore...
73 | ```
74 | $ lein fgoc --repl
75 | test
76 | src
77 |
78 | fgo=> 2+3
79 | Clojure: (+ 2 3)
80 | Result: 5
81 |
82 | fgo=> func{10 * $1} map [1,2,3,4,5,6]
83 | Clojure: (map #(* 10 %) [1 2 3 4 5 6])
84 | Result: (10 20 30 40 50 60)
85 |
86 | fgo=>
87 | ```
88 | (In the above example, note you *must* have double-spaces around the
89 | `map` in the infix expression. This expression is equivalent to `map(func{10 * $1}, [1,2,3,4,5,6])`)
90 |
91 | ### Not Using Leiningen?
92 |
93 | The preferred way to use this compiler is via the
94 | [Leiningen Plugin][plugin] as described in the Quick Start section.
95 |
96 | If you are not using Leiningen you can use `java -jar
97 | bin/funcgo-compiler-*-standalone.jar directory ...` to compile.
98 |
99 | ## Introduction to the Funcgo Language
100 |
101 | ### Why a new language?
102 |
103 | The goal of Funcgo is to combine the readability of the Go language
104 | with the semantics of Clojure.
105 |
106 | 1. Go is a language that has been well designed to be very
107 | readable. However it is best as a low-level system programming
108 | language (replacing C) and it is missing many of the higher-level
109 | features that programmers expect for working further up the stack, in
110 | for example in web applications.
111 |
112 | 2. Clojure is a variety of Lisp that inter-operates with Java or
113 | JavaScript. It encourages a functional programming style with
114 | efficient immutable containers, combined with a thread-safe model for
115 | mutable state called software transactional memory. However, Clojure
116 | is difficult to read for programmers unfamiliar with Lisp syntax.
117 |
118 | ### Examples for Clojure Programmers
119 |
120 | In this section are Funcgo versions of some of the Clojure examples
121 | from the [Clojure Cookbook][cookbook].
122 |
123 | #### Defining and using a function
124 | ```go
125 | func add(x, y) {
126 | x + y
127 | }
128 | add(1, 2)
129 |
130 | => 3
131 | ```
132 | Here we define a function `add` and then call it. If you are a Go
133 | programmer this should look familiar. However you might notice that
134 | the types are missing and that there is no `return` statement.
135 |
136 | Funcgo does not require types, though as we will see later, in certain
137 | cases when performance is important you can specify types at a few
138 | strategic locations.
139 |
140 | Funcgo does not need a `return` statement, rather a function simply
141 | returns the value of its last expression (often its only expression).
142 |
143 | #### Adding a file header
144 | ```go
145 | package example
146 | import(
147 | "clojure/string"
148 | )
149 | ```
150 |
151 | Here we see what the top of a Funcgo source file called `example.go`
152 | might look like. Here we import in a Clojure
153 | [string utility package][string] to be used in this file.
154 |
155 | #### Using symbols from other packages
156 | ```go
157 | string.isBlank("")
158 |
159 | => true
160 | ```
161 |
162 | Because of the `import` statement at the top we can now access
163 | functions in the `string` package provide by Clojure. One little
164 | wrinkle is that the Clojure function is actually [`blank?`][isblank],
165 | with a `?` character that is illegal in Funcgo. Similarly many Clojure
166 | functions have `-` characters in their name that Funcgo does not
167 | allow. So we automatically _mangle_ identifiers so that `isSomething`
168 | becomes `something?` and `thisIsAnIdentifier` becomes
169 | `this-is-an-identifier`. This is important, because you will often
170 | have to refer to the [Clojure documentation of its library][apidoc].
171 |
172 | ```go
173 | string.capitalize("this is a proper sentence.")
174 |
175 | => "This is a proper sentence."
176 | ```
177 |
178 | ```go
179 | string.upperCase("Dépêchez-vous, l'ordinateur!")
180 |
181 | => "DÉPÊCHEZ-VOUS, L'ORDINATEUR!"
182 | ```
183 |
184 | #### Specifying string escapes and regular expressions
185 | ```go
186 | string.replace("Who\t\nput all this\fwhitespace here?", /\s+/, " ")
187 |
188 | => "Who put all this whitespace here?"
189 | ```
190 |
191 | The example above shows that string escapes are familiar-looking to
192 | most programmers. It also introduces the syntax for _regular
193 | expression literals_, which are written between a pair of `/`
194 | characters.
195 |
196 | #### Concatenating strings
197 | ```go
198 | str("John", " ", "Doe")
199 |
200 | => "John Doe"
201 | ```
202 |
203 | Funcgo does *not* concatenate strings using a `+` operator like other
204 | languages you may be familiar with. Instead you use the [`str`][str]
205 | function. This is one of the many functions defined in [`clojure.core`][ccore]
206 | that can be used without needing an `import` statement.
207 |
208 | #### Specifying local (immutable) variables
209 | ```go
210 | firstName, lastName, age := "John", "Doe", 42
211 | str(lastName, ", ", firstName, " - age: ", age)
212 | => "Doe, John - age: 42"
213 | ```
214 |
215 | In keeping with its orientation as a functional programming language,
216 | Funcgo does *not* have mutable local variables. Instead, inside
217 | functions and other scopes you should create constants (whose values
218 | can not be changed.
219 |
220 | #### Specifying global (mutable) variables
221 | ```
222 | var firstName = "John"
223 | var lastName = "Doe"
224 | var age = 42
225 | str(lastName, ", ", firstName, " - age: ", age)
226 |
227 | => "Doe, John - age: 42"
228 | ```
229 |
230 | You can create mutable variables using `var`, but these are global and
231 | changes are not propagated between threads, so you should avoid using
232 | them if possible.
233 |
234 | #### Using vectors
235 | ```go
236 | into([], range(1, 20))
237 |
238 | => [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
239 | ```
240 |
241 | Here we see an example of using the [`range`][range] function to create
242 | a lazy sequence of integers and then using the [`into`][into] function
243 | to create a vector with the same values.
244 |
245 | This example also introduces vector literals, with the empty vector
246 | being passed as the first parameter of `into`.
247 |
248 | #### Getting cleaner syntax using infix notation
249 | ```go
250 | [] into range(1, 20)
251 |
252 | =>, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
253 | ```
254 |
255 | This example has the exact same effect as the previous example, but we
256 | are taking advantage of another feature of Funcgo any function that
257 | takes two parameters `foo(param1, param2)` can alternatively be
258 | written in _infix_ notation as `param1 foo param2` (with double spaces
259 | around the `foo`). This can sometimes lead to cleaner and more
260 | readable code.
261 |
262 | #### Specifying keyword and dictionary literals
263 | ```go
264 | me := {FIRST_NAME: "Eamonn", FAVORITE_LANGUAGE: "Funcgo"}
265 | str("My name is ", me(FIRST_NAME),
266 | ", and I really like to program in ", me(FAVORITE_LANGUAGE))
267 | => "My name is Eamonn, and I really like to program in Funcgo"
268 | ```
269 |
270 | The above example introduces a number of new language features.
271 |
272 | First note the _dictionary literal_ which creates a dictionary with
273 | two entries.
274 |
275 | Here the keys are _keywords_ which in Funcgo are distinguished by
276 | being all-uppercase. Unlike symbols that evaluate to something else,
277 | keywords just evaluate to themselves and are most commonly used like
278 | this as dictionary keys.
279 |
280 | Note that to extract values from the dictionary you treat it as if it
281 | were a function, using the key as the parameter to the function.
282 |
283 | #### Combining infix and functional programming
284 | ```go
285 | str apply (" " interpose [1, 2.000, 3/1, 4/9])
286 |
287 | => "1 2.0 3 4/9"
288 | ```
289 |
290 | This example shows two nested infix expressions.
291 |
292 | The inner ones uses the [`interpose`][interpose] function to take the
293 | vector `[1, 2.000, 3/1, 4/9]` and create a new vector with blanks
294 | inserted between `[1, " ", 2.000, " ", 3/1, " ", 4/9]`.
295 |
296 | The outer infix expression shows an example of Funcgo being used as a
297 | functional programming language. The [`apply`][apply] function is an
298 | example of a function that takes a function as a parameter. Here
299 | [`str`][str] is passed as the first argument.
300 |
301 | #### Calling function variadically
302 | ```go
303 | str(...(" " interpose [1, 2.000, 3/1, 4/9]))
304 |
305 | => "1 2.0 3 4/9"
306 | ```
307 |
308 | This example is equivalent to the previous one, but it shows some
309 | syntactic sugar for the `apply` function in a way that echoes how
310 | variadic functions are declared. Essentially if you have `const args
311 | = [a, b, c]` then calling `foo(...args)` is the same as calling
312 | `foo(a, b, c)`.
313 |
314 |
315 | #### Inter-operating with Java or JavaScript
316 | ```go
317 | func isYelling(utterance String) {
318 | isEvery(
319 | func(ch Character) { !Character::isLetter(ch) || Character::isUpperCase(ch) },
320 | utterance
321 | )
322 | }
323 | ```
324 |
325 | This example shows an example of Java interoperability. The `::`
326 | specifies access to a static function (with symbol names not being
327 | mangled, but passed to Java as-is).
328 |
329 | This is also the first time we have specified a type for a value,
330 | specifying the `String` type on the outer function's parameter. This
331 | is optional, but doing so in this case avoids Java reflection, making
332 | for a more efficient implementation.
333 |
334 | We also see here an example of an anonymous function, here a predicate
335 | (function returning Boolean) that tests if a character is a non-letter
336 | or an upper-case letter.
337 |
338 | The [`isEvery`][isevery] function tests whether this predicate is true
339 | for every character in the string.
340 |
341 | ### Examples for Go Programmers
342 |
343 | In this section are Funcgo versions of some of the Go examples
344 | from the [A Tour of Go][tour].
345 |
346 | #### Placement of constant definitions
347 | ```go
348 | package main
349 |
350 | import "fmt"
351 |
352 | Pi := 3.14
353 |
354 | func main() {
355 | World := "世界"
356 | fmt.Println("Hello", World)
357 | fmt.Println("Happy", Pi, "Day")
358 | {
359 | Truth := true
360 | fmt.Println("Go rules?", Truth)
361 | }
362 | }
363 |
364 |
365 | => Hello 世界
366 | Happy 3.14 Day
367 | Go rules? true
368 | ```
369 |
370 | One constraint on `:=` definitions is that, except for at the top
371 | level, they have to be at the beginning of a curly-brace block. So
372 | above we had to add an extra level of curlies to allow `Truth` to be
373 | defined at the bottom of the function.
374 |
375 |
376 | #### Go primitive types
377 | ```go
378 | package main
379 |
380 | import (
381 | "fmt"
382 | "math"
383 | )
384 |
385 | func pow(x, n, lim float64) float64 {
386 | if v := math.Pow(x, n); v < lim {
387 | v
388 | } else {
389 | lim
390 | }
391 | }
392 |
393 | func main() {
394 | fmt.Println(
395 | pow(3, 2, 10),
396 | pow(3, 3, 20),
397 | )
398 | }
399 |
400 |
401 | => 9 20
402 | ```
403 |
404 | For compatibility with Go, you can use Go-style primitive types, but they are mapped to JVM
405 | primitive types that may have different bit sizes.
406 |
407 | #### Optional `return`
408 |
409 | ```go
410 | package main
411 |
412 | import (
413 | "fmt"
414 | )
415 |
416 | func newton(n int, x, z float64) float64 {
417 | if n == 0 {
418 | z
419 | } else {
420 | newton(n-1, x, z-(z*z-x)/(2*x))
421 | }
422 | }
423 |
424 | func Sqrt(x float64) float64 {
425 | return newton(500, x, x/2)
426 | }
427 |
428 | func main() {
429 | fmt.Println(Sqrt(100))
430 | }
431 |
432 |
433 | => 10.000000000000007
434 | ```
435 |
436 | For compatibility with Go, you can add a cosmetic `return` to a
437 | function, but only in the special case of returning the top level
438 | expression of a function.
439 |
440 | #### Data structures
441 |
442 | ```go
443 | package main
444 |
445 | import "fmt"
446 |
447 | type Vertex struct {
448 | X int
449 | Y int
450 | }
451 |
452 | func main() {
453 | fmt.Println(Vertex{1, 2})
454 | }
455 |
456 | => {1 2}
457 | ```
458 |
459 | You can go a long way in Funcgo just using the built in
460 | dictionary and vector types, but you can also create data structures
461 | that are implemented as Java classes.
462 |
463 |
464 | ## Building and Development
465 |
466 | You need Leiningen (the Clojure build tool) to build the compiler.
467 | (Note that if you are on Ubuntu, as of March 2014 the version in the
468 | standard Ubuntu package manager is too old to work with this project.
469 | Instead download the `lein` script from the
470 | [Leiningen web site](http://leiningen.org/#install) and put in your
471 | PATH.
472 |
473 | First clone this repo, and cd into the `funcgo` directory.
474 |
475 | To create a new compiler JAR execute ...
476 |
477 | ```sh
478 | lein with-profile bootstrap fgoc
479 | lein uberjar
480 | ```
481 |
482 | ... which will compile the compiler and generate a JAR file
483 | target/funcgo-x.y.z-standalone.jar
484 |
485 | You can run the unit tests by doing
486 |
487 | ```sh
488 | lein do run test, midje
489 | ```
490 |
491 | ## Thanks
492 |
493 | Funcgo is built on the folder of giants.
494 |
495 | Thanks to Rich Hickey and the Clojure contributors, to Thompson, Pike,
496 | and Griesemer and the Go contributors, and to Mark Engelberg for the
497 | instaparse parsing library.
498 |
499 | ## License
500 |
501 | The Funcgo code is distributed under the Eclipse Public License either
502 | version 1.0 or (at your option) any later version.
503 |
504 | 
Funcgo Documentation by Eamonn O'Brien-Strain is licensed under a Creative Commons Attribution 4.0 International License.
505 |
506 | [lein]: http://leiningen.org/
507 | [cookbook]: http://clojure-cookbook.com/
508 | [tour]: http://tour.golang.org
509 | [string]: http://clojure.github.io/clojure/clojure.string-api.html
510 | [isblank]: http://clojure.github.io/clojure/clojure.string-api.html#clojure.string/blank?
511 | [apidoc]: http://clojure.github.io/clojure/index.html
512 | [ccore]: http://clojure.github.io/clojure/clojure.core-api.html
513 | [str]: http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/str
514 | [into]: http://clojuredocs.org/clojure_core/clojure.core/into
515 | [range]: http://clojuredocs.org/clojure_core/clojure.core/range
516 | [apply]: http://clojuredocs.org/clojure_core/clojure.core/apply
517 | [interpose]: http://clojuredocs.org/clojure_core/clojure.core/interpose
518 | [isevery]: http://clojuredocs.org/clojure_core/clojure.core/every_q
519 | [plugin]: https://github.com/eobrain/funcgo-lein
520 | [src]: https://github.com/eobrain/funcgo/tree/master/src/funcgo
521 | [fgolib]: https://github.com/eobrain/fgolib
522 | [fgosite]: https://github.com/eobrain/fgosite
523 | [fgotour]: http://tour.funcgo.org
524 | [ref]: doc/reference.md
525 | [faq]: doc/FAQ.md
526 |
--------------------------------------------------------------------------------
/bin/funcgo-compiler-0.5.1-standalone.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eobrain/funcgo/a5808afffaeb42c18b71499b831748ece901d8a4/bin/funcgo-compiler-0.5.1-standalone.jar
--------------------------------------------------------------------------------
/doc/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | ## Are other targets being considered?
4 |
5 | The Funcgo compiler is likely to only ever emit Clojure. However,
6 | that means it already has good support for the JVM and JavaScript
7 | targets, and in addition there are projects to have Clojure target
8 | Android and the CLR, and perhaps there will be more targets in future.
9 | If there is enough interest, Funcgo can tweak its Clojure output
10 | appropriately for new targets, as it already does for Clojure versus
11 | ClojureScript.
12 |
13 | ## Does it support numerical programming?
14 |
15 | Funcgo can readily call Java code, so it supports numerical
16 | programming or linking in C++ code to the same extent that Java does.
17 | It can also of course directly use any Clojure numerical programming
18 | library such as [core.matrix][1].
19 |
20 | ## Does it support operator overoading?
21 |
22 | Yes, it supports [limited operator overloading][4].
23 |
24 | ## Is this a full implementation of Go as the "Functional Go" name implies?
25 |
26 | No. Perhaps it would be better named "Glojure". The language is
27 | fundamentally just syntactical sugar on top of Clojure, though I did
28 | try to keep the sugar as close to Go as I could.
29 |
30 | ## Can a programmer use macros (as in Clojure) to modify the language itself.
31 |
32 | I do not plan to make it easy to _define_ macros in Funcgo. Inspired
33 | by Go's philosophy, I want to limit the scope of the language to keep
34 | it somewhat simple. Funcgo however can _use_ macros, so a sufficiently
35 | motivated programmer can write macros in Clojure and use them in
36 | Funcgo.
37 |
38 | ## So Funcgo is really just Lisp without parentheses... again.
39 |
40 | Basically yes, or put it another way, it is Lisp with more syntax. The
41 | fact that Lisp has so little syntax is beautifully elegant from a
42 | mathematical point of view, but that is I believe at the expense of
43 | readability, at least for programmers coming from other programming
44 | traditions. Of course readability is somewhat subjective and I
45 | understand that for long-time Lisp programmers, Lisp is perfectly
46 | readable.
47 |
48 | ## How does the user keeps track of the source location (source map)?
49 |
50 | I have [plans to implement source maps][3] when targeting JavaScript.
51 | I have not yet figured out the best way to do something equivalent
52 | when targeting the JVM. This is one of several tool-chain issues. In
53 | addition to stabilizing the language itself, getting a smooth
54 | tool-chain is the biggest thing left to do before a 1.0 release of
55 | Funcgo. (Though it is worth noting that Coffeescript, an analogous
56 | language, got widespread adoption before tackling the source location
57 | issue.)
58 |
59 |
60 | ## Credit
61 |
62 | Some of these questions are paraphrased from some threads on
63 | [Hacker News][2]. Thanks to the posters there.
64 |
65 |
66 | [1]: https://github.com/mikera/core.matrix
67 | [2]: https://news.ycombinator.com/item?id=8017588
68 | [3]: https://github.com/eobrain/funcgo/issues/19
69 | [4]: reference.md#operator-overloading
70 |
--------------------------------------------------------------------------------
/doc/reference.md:
--------------------------------------------------------------------------------
1 | # Funcgo Reference
2 |
3 | _[incomplete]_
4 |
5 | ## Source File Structure
6 |
7 | Source files names must end with either `.go` (if to be run on the
8 | JVM) or `.gos` (if to be run in JavaScript).
9 |
10 | A file called `name.go` must start with `package name` followed by
11 | optional `import` statements, an optional `const` declaration, and a
12 | list of expressions.
13 |
14 | ```go
15 | package hello
16 | println("Hello World")
17 | ```
18 | As a minimal example, the code above is in a file `hello.go`.
19 |
20 | ```go
21 | package larger
22 |
23 | import (
24 | test "midje/sweet"
25 | )
26 |
27 | test.fact("can concatenate strings",
28 | {
29 | greeting := "Hello "
30 | name := "Eamonn"
31 | str(greeting, name)
32 | }, =>, "Hello Eamonn"
33 | )
34 |
35 | test.fact("can use infix when calling two-parameter-function",
36 | {
37 | greeting := "Hello "
38 | name := "Eamonn"
39 | greeting str name
40 | }, =>, "Hello Eamonn"
41 | )
42 | ```
43 | The above slightly longer example is in a file called `larger.go`.
44 |
45 | ## Most things are Expression
46 |
47 | In Funcgo most things are expression, including constructs like `if`
48 | statements that are statements in Go.
49 |
50 | ```go
51 | smaller := if a < b {
52 | a
53 | } else {
54 | b
55 | }
56 | smaller
57 | => 55
58 | ```
59 | The above treats an `if`-`else` as an expression, setting `smaller`
60 | to either `a` or `b`.
61 |
62 | ```go
63 | digits := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
64 | squares := for d := lazy digits {
65 | d * d
66 | }
67 | squares
68 | => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
69 | ```
70 | And here the value returned from a `for` loop is actually the vector
71 | of the values generated on each iteration. (Called a _list
72 | comprehension_ in some language.)
73 |
74 | ## Syntax
75 |
76 | Unlike some languages, newlines can be significant in Funcgo. This
77 | happens when you have multiple expressions inside curly braces or at
78 | the top level of the source file.
79 |
80 | ```go
81 | if a < b {
82 | println("Conclusion:")
83 | println(a, "is smaller than", b)
84 | }
85 | =>
86 | Conclusion:
87 | 55 is smaller than 66
88 | ```
89 |
90 | For example in the `if` statement above the two `println` expressions
91 | must be separated by a newline. (In this case we are ignoring the
92 | values returned by the two expressions, the latter of which is
93 | returned by the `if`. Instead we are using these expressions for
94 | their side-effects:
95 |
96 |
97 | ```go
98 | if a < b { println("Conclusion:"); println(a, "is smaller than", b) }
99 | =>
100 | Conclusion:
101 | 55 is smaller than 66
102 | ```
103 |
104 | If you really want to, you can use semicolons instead of newlines as
105 | shown above, but for readability I recommend you avoid semicolons.
106 |
107 | ## Imports
108 |
109 | You can directly use anything provided by the [clojure.core][1] API
110 | without further specification. However if you want to use anything
111 | from any other library you have to explicitly import it at the top of
112 | your file. Depending on what you are you importing you use one of
113 | these forms.
114 |
115 | 1. `import` (the most common case) for Clojure or Funcgo libraries
116 |
117 | ```go
118 | import (
119 | test "midje/sweet"
120 | fgo "funcgo/core"
121 | fgoc "funcgo/main"
122 | "clojure/string"
123 | )
124 | ...
125 | test.fact(...
126 | ...
127 | fgo.Parse(...
128 | ...
129 | string.trim(...
130 |
131 | ```
132 |
133 | As shown above an `import` statement can import multiple Clojure
134 | or Funcgo libraries. It specifies the library as a string of
135 | slash-separated identifiers. Each library can be preceded by a
136 | short name by which the library is referred to in the body of the
137 | code. If no short name is specified, then the last identifier in
138 | the library name is used (for example `string` in the last example
139 | above).
140 |
141 | In the body of the code any function or variable referenced from
142 | an imported library must be qualified by short name.
143 |
144 | ```go
145 | import(
146 | _ "hiccups/runtime"
147 | "fgosite/code"
148 | )
149 | ```
150 |
151 | Sometimes you want to import a library only for the side-effect of
152 | importing it. To avoid getting a compile error complaining about
153 | an unused import, you can use `_` as shown above.
154 |
155 | ```go
156 | import "clojure/string"
157 | ```
158 |
159 | If you are only importing a single library you can use a short
160 | form without parentheses as shown above.
161 |
162 | 1. `import type` for JVM classes and interfaces
163 |
164 | ```go
165 | import type (
166 | java.io.{BufferedWriter, File, StringWriter}
167 | jline.console.ConsoleReader
168 | )
169 | ...
170 | ... = new StringWriter()
171 | ...
172 | func compileTree(root File, opts) {
173 | ...
174 | ```
175 |
176 | JVM types, such as defined in Java (and sometimes in Clojure),
177 | have a different syntax for importing as show above. Each type
178 | must be explicitly listed, though types in the same package can
179 | expressed using the compressed syntax shown above for `java.io`.
180 |
181 | Once imported, such types can be simply referenced by name,
182 | without qualification.
183 |
184 | Types from the base `java.lang` API do not need to be imported.
185 |
186 | 1. `import macros` (when targeting JavaScript only) for importing
187 | ClojureScript macros
188 |
189 | ```go
190 | import macros (
191 | hiccups "hiccups/core"
192 | )
193 | ...
194 | func pageTemplate(index) {
195 | ...
196 | ```
197 |
198 | When targeting the JavaScript runtime you sometimes need to import
199 | macro definitions in a special way as shown above.
200 |
201 | 1. `import extern` (advanced use only) needed when creating macros
202 |
203 | ```go
204 | import extern(
205 | produce
206 | bakery
207 | )
208 | ...
209 | ... quote(produce.onions) ...
210 | ...
211 | ```
212 |
213 | Occasionally you will need to refer to symbols in libraries that
214 | you cannot import. As shown above you can declare them as
215 | `extern` libraries.
216 |
217 | ## Const
218 |
219 | In Funcgo you should use constants for any value that is
220 | set once and never changed.
221 |
222 | ```go
223 | ...
224 | {
225 | cljText := core.Parse(inPath, fgoText)
226 | strWriter := new StringWriter()
227 | writer := new BufferedWriter(strWriter)
228 | cljText writePrettyTo writer
229 | strWriter->toString()
230 | }
231 | ```
232 | As shown above, constants are defined using the `:=` operator.
233 |
234 | There can only be a single contiguous group of constant declarations in
235 | each _block_ of expressions, and they must appear at the top of the
236 | block. A block is either to top-level code if a file after the
237 | `import` statements, or some newline-separated expressions surrounded
238 | in curly braces. The constants you define in a block can only be used
239 | inside that block.
240 |
241 | ```go
242 | ...
243 | {
244 | const (
245 | cljText = core.Parse(inPath, fgoText, EXPR)
246 | strWriter = new StringWriter()
247 | writer = new BufferedWriter(strWriter)
248 | )
249 | cljText writePrettyTo writer
250 | strWriter->toString()
251 | }
252 | ```
253 |
254 | As shown above, there is also an alternative syntax using the `const`
255 | keyword. It too must be at the beginning of a block.
256 |
257 | ```go
258 | ...
259 | {
260 | const consoleReader = new ConsoleReader()
261 | consoleReader->setPrompt("fgo=> ")
262 | consoleReader
263 | }
264 | ```
265 |
266 | If there is a just a single constant, you can drop the parentheses.
267 |
268 | ## Looping with tail recursion
269 |
270 | First, lets look at an ordinary (non-tail) recursion
271 |
272 | ```go
273 | func sumSquares(vec) {
274 | if isEmpty(vec) {
275 | 0
276 | } else {
277 | x := first(vec)
278 | x * x + sumSquares(rest(vec))
279 | }
280 | }
281 | sumSquares([3, 4, 5, 10])
282 | => 150
283 | ```
284 |
285 | The above example shows the `sumSquares` function that returns the sum
286 | of squares of a vector of numbers. It is implemented as the square of
287 | the first element plus the recursive sum of squares of the rest of the
288 | vector. This works fine for small vectors but for large vectors it
289 | could cause one of the infamous _stack overflow_ exception.
290 |
291 | ```go
292 | func sumSquares(vec) {
293 | func sumSq(accum, v) {
294 | if isEmpty(v) {
295 | accum
296 | } else {
297 | x := first(v)
298 | recur(accum + x * x, rest(v))
299 | }
300 | }
301 | sumSq(0, vec)
302 | }
303 | sumSquares([3, 4, 5, 10])
304 | => 150
305 | ```
306 |
307 | The above example avoids this stack overflow by using the special
308 | `recur` syntax to recursively call the containing function. However
309 | `recur` must be in _tail position_, which means that the function
310 | needs to be re-arranged to add an inner recursive function that passes
311 | down an accumulator variable. This version can be called on
312 | arbitrarily long vectors without blowing your stack.
313 |
314 | There is also an equivalent way of getting the same result without
315 | using an inner function by using instead the `loop` construct.
316 |
317 | ```go
318 | func sumSquares(vec) {
319 | loop(accum=0, v=vec) {
320 | if isEmpty(v) {
321 | accum
322 | } else {
323 | x int := first(v)
324 | recur(accum + x * x, rest(v))
325 | }
326 | }
327 | }
328 | sumSquares([3, 4, 5, 10])
329 | => 150
330 | ```
331 |
332 | The `loop` construct declares a set of iteration variables and sets
333 | their initial values. The `recur` calls the nearest enclosing `loop`
334 | passing in updated iteration variables (which are actually constants
335 | in each iteration). The number of parameters in the `recur` must match the
336 | number of parameters in the `loop`.
337 |
338 | ```go
339 | loop(vec=[], count = 0) {
340 | if count < 10 {
341 | v := vec conj count
342 | recur(v, count + 1)
343 | } else {
344 | vec
345 | }
346 | => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
347 | ```
348 |
349 | And above is another simpler example of using `loop`, starting with an
350 | empty vector and using the `conj` operator to add numbers to it.
351 |
352 | ## Curly Brace Block
353 |
354 | Everywhere you can put an expression you can put a newline-separated
355 | sequence of expressions in a curly braces block. The result of the
356 | last expression is returned as the result of the block.
357 |
358 | ```go
359 | product := {
360 | logging.info("doing the multiplication")
361 | 100 * 100
362 | }
363 | product
364 | => 10000
365 | ```
366 |
367 | Above is an example of the `product` constant being assigned the value
368 | of the block, with the multiplication expression being preceded by a
369 | logging statement that is executed only for its side-effects.
370 |
371 | # Switch
372 |
373 | There are three forms of switch statement.
374 |
375 | ```go
376 | switch count(remaining) {
377 | case 1: {
378 | [expr] := remaining
379 | str(acc, " :else ", expr, ")")
380 | }
381 | case 2:
382 | typeCase() str ")"
383 | default:
384 | recur(typeCase(), 2 drop remaining)
385 | }
386 | ```
387 |
388 | In the first form, shown above, the switch takes an expression and
389 | matches execute whichever of its `case` sections match the result of
390 | the expression. This is the more efficient form of switch because the
391 | dispatch to a case happens in constant time, but it has the restriction
392 | that the `case` sections must have compile-time constants values.
393 |
394 | ```go
395 | switch {
396 | case isNil(t):
397 | new TreeNode(v, nil, nil)
398 | case v < VAL(t):
399 | new TreeNode(VAL(t), L(t) xconj v, R(t))
400 | default:
401 | new TreeNode(VAL(t), L(t), R(t) xconj v)
402 | }
403 | ```
404 |
405 | The second form, shown above, is more general. There is no expression
406 | beside the `switch` but instead each `case` has an arbitrary Boolean
407 | expression. In general this form is slower because the dispatch
408 | happens in linear time, each case expression being evaluated in turn
409 | until one returns true.
410 |
411 | The third form is the _type switch_ using the `.(type)`
412 | suffix to indicate that we are switching on the type, and using
413 | type names in the case statements.
414 |
415 | ```go
416 | func plus(a, b) {
417 | switch a.(type) {
418 | case Number: a + b
419 | case String: a str b
420 | case Iterable: vec(a concat b)
421 | default: str("Unknown types for ", a, " and ", b)
422 | }
423 | }
424 |
425 | [
426 | 2 plus 3,
427 | 0.5 plus 0.75,
428 | [P, Q] plus [R, S, T],
429 | "foo" plus "bar",
430 | FOO plus BAR
431 | ]
432 |
433 | => [
434 | 5,
435 | 1.25,
436 | [P, Q, R, S, T],
437 | "foobar",
438 | "Unknown types for :foo and :bar"
439 | ]
440 | ```
441 |
442 | In the above example we define a _plus_ function that does different
443 | operations depending on the types of the first argument. (A more
444 | robust version would check both arguments.)
445 |
446 | ## Java Statics
447 |
448 | To use static methods or fields from a Java class you use the `::`
449 | operator after the Java class name (which you should import using the
450 | `import type` syntax unless it is in the java.lang package).
451 |
452 | ```go
453 | 2 * Double::MAX_VALUE // => Double::POSITIVE_INFINITY
454 | Integer::parseInt("-42") // => -42
455 | Math::round(2.999) // => 3
456 | 13 Integer::toString 2 // => "1101"
457 | ```
458 |
459 | The first example shows how a static field is uses. The remaining
460 | three are static method invocations, with the last one showing how you
461 | can use infix notation to invoke a static method that takes two
462 | parameters.
463 |
464 | ## Identifiers
465 |
466 | Clojure allows characters in identifiers that are not allowed in
467 | Funcgo identifiers, therefore to allow inter-operation Funcgo
468 | identifiers are mangled like so:
469 |
470 | * camel-case is converted to dash-separated:
471 | * `fooBarBaz` → `foo-bar-baz`
472 | * `FooBarBaz` → `Foo-bar-baz`
473 | * `is` prefix is converted to `?` suffix
474 | * `isEqual` → `equal?`
475 | * `mutate` prefix is converted to `!` suffix
476 | * `mutateSort` → `sort!`
477 | * underscore prefix is converted to dash prefix
478 | * `_main` → `-main`
479 |
480 | However, identifiers referring to Java entities are *not* mangled.
481 | These are any identifiers in `import type` statements, anything before
482 | or after a `::` and anything after a `->`.
483 |
484 | You can avoid mangling by surrounding arbitrary closure code in
485 | backslashes:
486 |
487 | ```go
488 | origDispatch := \pprint/*print-pprint-dispatch*\
489 | ```
490 |
491 | The above example uses this escaped identifier syntax to refer to the
492 | `pprint/*print-pprint-dispatch*` Clojure identifier which has the
493 | "earmuff" characters not allowed by Funcgo.
494 |
495 | ### Operator Overloading
496 |
497 | You can redefine any of the built-in operators.
498 |
499 | ```go
500 | package operator
501 | import test "midje/sweet"
502 | exclude ( ^, + )
503 |
504 | func ^(x, y) {
505 | Math::pow(x, y)
506 | }
507 |
508 | func +(x, y) {
509 | x str y
510 | }
511 |
512 | test.fact("Can redefine existing operators",
513 | 2 ^ 3, =>, 8.0,
514 | 10 ^ 2, =>, 100.0,
515 | "foo" + "bar", =>, "foobar"
516 | )
517 | ```
518 |
519 | The above example shows how you can simply use an operator as the name
520 | of a two-parameter function. The precedence of the built-in operator
521 | is preserved when you use it in the normal infix way.
522 |
523 | Note above the use of the `exclude` directive, which prevents
524 | importing the given operators from the built-in package. Without the
525 | exclude you will get warnings about functions being redefined.
526 |
527 | ```go
528 | func \**\(x, y) {
529 | Math::pow(x, y)
530 | }
531 |
532 | test.fact("Can use new operators",
533 | 2 \**\ 3, =>, 8.0,
534 | 10 \**\ 2, =>, 100.0
535 | )
536 | ```
537 |
538 | If you want to define your own operators that are not existing
539 | built-in operators then you will have to use the Clojure escape syntax
540 | to define and use them, as shown above.
541 |
542 | ```go
543 | // Operations on matrices, stored as sequences of row vectors
544 | package matrix
545 | import "clojure/core"
546 | exclude ( +, * )
547 |
548 | // Begin private functions
549 |
550 | func colCount(m) { count(first(m)) }
551 | func dotProduct(v1, v2) {
552 | core.+ reduce map(core.*, v1, v2)
553 | }
554 | func vecSum(a, b) { map(core.+, a, b) }
555 |
556 | // Begin exported functions
557 |
558 | func +(m1, m2) { map(vecSum, m1, m2) }
559 |
560 | func Transpose(m) {
561 | firstColumnT := first map m
562 | if colCount(m) == 1 {
563 | [firstColumnT]
564 | } else {
565 | firstColumnT cons Transpose(rest map m)
566 | }
567 | }
568 |
569 | func *(m1, m2) {
570 | for m1row := lazy m1 {
571 | for m2col := lazy Transpose(m2) {
572 | m1row dotProduct m2col
573 | }
574 | }
575 | }
576 | ```
577 |
578 | Above is an example of a simple matrix package supporting matrix
579 | addition, transpose, and multiplication. The operators are exported
580 | in the same way as capitalized functions for use in other packages.
581 | However, note that in other packages they must be qualified by the
582 | package as shown below.
583 |
584 | ```go
585 | import (
586 | ...
587 | "funcgo/reference/matrix"
588 | )
589 | ...
590 | a := [[3, 4]]
591 | b := [
592 | [5],
593 | [6]
594 | ]
595 |
596 | a matrix.* b
597 |
598 | => [[39]],
599 |
600 | {
601 | m := [
602 | [1, 2, 3],
603 | [4, 5, 6]
604 | ]
605 | mT := [
606 | [1, 4],
607 | [2, 5],
608 | [3, 6]
609 | ]
610 |
611 | m matrix.* mT
612 |
613 | => [
614 | [14, 32],
615 | [32, 77]
616 | ]
617 | ```
618 |
619 | ## Vars
620 |
621 | If possible you should use consts because they are immutable, but
622 | sometimes you need vars. These are thread-local mutable storage.
623 |
624 | The case of the name is significant. If it begins with an upper-case
625 | letter then it is exported and visible globally, otherwise it is
626 | private to the file it is declared in.
627 |
628 |
629 | ```go
630 | var initialBoard = [
631 | [EE, KW, EE],
632 | [EE, EE, EE],
633 | [EE, KB, EE]
634 | ]
635 | ```
636 |
637 | They use a syntax similar to the `const` construct. They can be
638 | without parentheses as shown above.
639 |
640 | ```go
641 | var (
642 | pp = 111
643 | qq = 222
644 | )
645 | pp + qq
646 | => 333
647 | ```
648 |
649 | Or it can use the grouped version of the syntax as shown
650 | above.
651 |
652 | ```go
653 | var rr = 111
654 | var ss = 222
655 | rr + ss
656 | => 333
657 | ```
658 |
659 | Alternatively each var can be separately declared as shown above.
660 |
661 | Unlike consts, var declarations do not have to be at the beginning of
662 | a curly-bracket block.
663 |
664 | ```go
665 | var tt int = 111
666 | var uu string = "foo"
667 | uu str tt
668 | => "foo111"
669 | ```
670 |
671 | If you want you can add type hints as shown above.
672 |
673 | ## If-Else
674 |
675 | ```go
676 | start := if suffixExtra == "" { SOURCEFILE } else { NONPKGFILE }
677 | ```
678 | The above example shows the if-else expression.
679 |
680 | ```go
681 | if cmdLine(ERRORS) {
682 | println(cmdLine(ERRORS))
683 | }
684 | ```
685 |
686 | If the `else` part is omitted the `if` expression returns nil when
687 | the condition is false, though in the example above this is ignored.
688 |
689 | ```go
690 | if met := meta(o); met {
691 | print("^")
692 | if count(met) == 1 {
693 | if met(TAG) {
694 | origDispatch(met(TAG))
695 | } else {
696 | if met(PRIVATE) == true {
697 | origDispatch(PRIVATE)
698 | } else {
699 | origDispatch(met)
700 | }
701 | }
702 | } else {
703 | origDispatch(met)
704 | }
705 | print(" ")
706 | pprint.pprintNewline(FILL)
707 | }
708 | ```
709 |
710 | Finally, the first line of the example above shows another format,
711 | where a constant `met` is set and tested as part of the `if` line.
712 | This constant can be used in the body of the if expression. The above
713 | example also emphasizes the fact that there is no "else-if" construct
714 | -- you must use nested if-else expressions (or alternatively use the
715 | switch expression).
716 |
717 | ## For loops
718 |
719 | There are three types of `for` expression.
720 |
721 | ```go
722 | fib := [1, 1, 2, 3, 5, 8]
723 | fibSquared := for x := lazy fib {
724 | x * x
725 | }
726 |
727 | fibSquared
728 | => [1, 1, 4, 9, 25, 64]
729 | ```
730 |
731 | The "lazy" version returns a sequence that is the same length as the
732 | input sequence (given after `lazy`), with the body of the loop being
733 | executed for each member of the input sequence.
734 |
735 | ```go
736 | fib := [1, 1, 2, 3, 5, 8]
737 | fibSquared := func(x){ x * x } map fib
738 |
739 | fibSquared
740 | => [1, 1, 4, 9, 25, 64]
741 | ```
742 |
743 | As an aside, you can get the same result as the lazy `for` using the
744 | `map` function as shown above.
745 |
746 | ```go
747 | fib := [1, 1, 2, 3, 5, 8]
748 | for x := lazy fib {
749 | print(" ", x)
750 | }
751 | => ""
752 | ```
753 |
754 | The reason that this construct is called "lazy", is shown in the example
755 | above where the body of the `for` does not return a value, but instead
756 | has a side-effect (writing on the console). In this case the `print`
757 | is *not* executed.
758 |
759 | ```go
760 | fib := [1, 1, 2, 3, 5, 8]
761 | for x := range fib {
762 | print(" ", x)
763 | }
764 | => " 1 1 2 3 5 8"
765 | ```
766 |
767 | To cause such a side-effect body to be executed you can use the
768 | "range" form of the for loop as shown above. It is not lazy, but will
769 | execute the body of the loop for each member of the input sequence.
770 |
771 | ```go
772 | for x := times 10 {
773 | print(" ", x)
774 | }
775 | => " 0 1 2 3 4 5 6 7 8 9"
776 | ```
777 |
778 | The final form of the for loop is the "times" version, which executes
779 | its body the number of times specified after `times` as shown above.
780 |
781 | ## Exceptions
782 |
783 | Funcgo supports exceptions in a way similar to Java.
784 |
785 | ```go
786 | eval := try {
787 | main := loadString(clj(id))
788 | withOutStr(main())
789 | } catch Throwable e {
790 | str(e)
791 | }
792 | ```
793 |
794 | The above example shows an example of catching an exception. A
795 | difference from Java is that try-catch is an expression, thus in the
796 | above case if the exception is caught the `eval` constant will be set
797 | to the value of `str(e)`.
798 |
799 | ```go
800 | try {
801 |
802 | throw(new AssertionError("foo"))
803 |
804 | } catch OutOfMemoryError e {
805 | "out of memory"
806 | } catch AssertionError e {
807 | "assertion failed: " str e->getMessage()
808 | } finally {
809 | "useless"
810 | }
811 | => "assertion failed: foo"
812 | ```
813 |
814 | The above example shows a more complete example, including how you can
815 | throw your own exceptions. Note, that although the `finally`
816 | expression is evaluated, its result is ignored so it is not useful in
817 | this case.
818 |
819 | ```go
820 | <-mutex // grab mutex
821 | try {
822 | i := dangerous->get(0)
823 | dangerous->set(0, i + 1)
824 | } finally {
825 | mutex <- true // release mutex
826 | }
827 | ```
828 |
829 | Above is an example of a more useful application of `finally` where we
830 | are depending on the side-effect of evaluating its expression.
831 |
832 | ## Asynchronous Channels
833 |
834 | ```go
835 | c1 := make(chan, 1)
836 | c2 := make(chan, 1)
837 | thread {
838 | Thread::sleep(10)
839 | c1 <- 111
840 | }
841 | c2 <- 222
842 | select {
843 | case x = <-c1:
844 | x * 100
845 | case x = <-c2:
846 | x * 100
847 | }
848 | => 22200
849 | ```
850 |
851 | The example above uses the same syntax as Go, where for a channel `c`
852 | the operation `<-c` is taking from a channel (blocking if necessary
853 | until input arrives) and `c <- x` is sending the value `x` to the
854 | channel.
855 |
856 | The `thread` construct executes its contents in a separate thread,
857 | running in parallel with the code following the thread construct.
858 |
859 | The `select` construct allows you to block on multiple asynchronous
860 | channel operations, such that the first one that unblocks will
861 | activate.
862 |
863 | When targeting JavaScript however we are restricted because
864 | JavaScript is single-threaded, instead you have to use a different
865 | syntax (using `<:` instead of `<-') for channel operations, and `go`
866 | instead of `thread`:
867 |
868 | ```go
869 | c1 := make(chan, 1)
870 | c2 := make(chan, 1)
871 | go {
872 | for i := times(10000) { var x = i }
873 | c1 <: 111
874 | }
875 | go {
876 | c2 <: 222
877 | }
878 | <-go {
879 | select {
880 | case x = <:c1:
881 | x * 100
882 | case x = <:c2:
883 | x * 100
884 | }
885 | }
886 | => 22200
887 | ```
888 |
889 | All the operations that use `<:` must be lexicallyinside a `go {
890 | ... }` block as shown above.
891 |
892 | The `go` block has an effect similar to `thread` except that on the
893 | JVM it shares a pool of threads, and in JavaScript it is implemented
894 | with some clever code reorganization rather than with threads. Even
895 | on JVM where `thread` is supported, `go` is often a better choice
896 | because it is more lightweigth and you can feel free to invoke a large
897 | number of `go` blocks.
898 |
899 | ```go
900 | c1 := make(chan, 1)
901 | c2 := make(chan)
902 | thread {
903 | Thread::sleep(20)
904 | c1 <- 111
905 | }
906 | thread {
907 | Thread::sleep(10)
908 | <-c2
909 | }
910 | select {
911 | case x = <-c1:
912 | x * 100
913 | case c2 <- 222:
914 | "wrote to c2"
915 | default:
916 | "nothing ready"
917 | }
918 | => "nothing ready"
919 | ```
920 |
921 | Finally the example above shows some more features.
922 |
923 | If there is a `default` clause in the `select` and all the `case`
924 | clauses are blocked, then it will execute instead.
925 |
926 | Finally note that this example has both types of `case` clauses, those
927 | writing to channels and those reading from channels, both of which can
928 | block.
929 |
930 | ## Infix functions
931 |
932 | ```go
933 | str("foo", "bar")
934 | => "foobar"
935 | ```
936 |
937 | You can call a function of two arguments in the normal prefix format
938 | of `f(a,b)`.
939 |
940 | ```go
941 | "foo" str "bar"
942 | => "foobar"
943 | ```
944 |
945 | Alternatively you can call such a function in an infix format
946 | of `a f b`. The infix function name must be separated by double-spaces.
947 |
948 | ## Binary Operators and Precedence
949 |
950 | This table shows all the built-in operators and how they group. The
951 | ones at the top bind most tightly.
952 |
953 | 6. unary expression
954 | 5. `*` `/` `%` `<<` `>>` `&` `&^`
955 | 4. `+` `-` `|` `^`
956 | 3. `==` `!=` `<` `>` `<=` `>=`
957 | 2. `&&`
958 | 1. `||`
959 | 0. infix function call
960 |
961 | ```go
962 | ^a * b // => (^a) * b,
963 | a * b - c // => (a * b) - c
964 | a + b < c // => (a + b) < c
965 | a < b && b < c // => (a < b) && (b < c)
966 | p && q || r // => (p && q) || r
967 | p || q str r // => (p || q) str r
968 | ```
969 |
970 | ## Destructuring
971 |
972 | You can declare multiple constants on the left-hand-side of the `=`
973 | and put a vector on the right-hand-side. Thus "unpacks" the vector
974 | assigning each element to the corresponding constant.
975 |
976 | ```go
977 | vec := [111, 222, 333, 444]
978 | [a, b, c, d] := vec
979 |
980 | b
981 | => 222
982 | ```
983 |
984 | For example, above we unpack the vector `vec`, so that constant `b`
985 | ends up with the value `222`.
986 |
987 | ```go
988 | func theSecond([a, b, c, d]) {
989 | b
990 | }
991 |
992 | theSecond(vec)
993 | => 222
994 | ```
995 |
996 | This also works for function arguments, where above we have used a
997 | function to extract the second element from the vector.
998 |
999 | ```go
1000 | vec := [111, 222, 333, 444]
1001 | [first, rest...] := vec
1002 |
1003 | rest
1004 | => [222, 333, 444]
1005 | ```
1006 |
1007 | For variable-length vectors you can use ellipses `...` after the
1008 | constant to match it to the remaining part of the vector. So for
1009 | example, above `first` gets the the first element in the vector and
1010 | `rest` gets the remaining elements.
1011 |
1012 | ```go
1013 | dict := {AAA: 11, BBB: 22, CCC: 33, DDD: 44}
1014 | {c: CCC, a: AAA} := dict
1015 |
1016 | c
1017 | => 33
1018 | ```
1019 |
1020 | You can also destructure dicts using the syntax shown above, where on
1021 | the left-hand-side each match is specified as _constant_`:` _key_.
1022 |
1023 | ```go
1024 | func extractCCC({c: CCC}) {
1025 | c
1026 | }
1027 |
1028 | extractCCC(dict)
1029 | => 33
1030 | ```
1031 |
1032 | Dict destructuring also works in function parameters as shown above.
1033 |
1034 | ```go
1035 | planets := [
1036 | {NAME: "Mercury", RADIUS_KM: 2440},
1037 | {NAME: "Venus", RADIUS_KM: 6052},
1038 | {NAME: "Earth", RADIUS_KM: 6371},
1039 | {NAME: "Mars", RADIUS_KM: 3390}
1040 | ]
1041 | [_, _, {earthRadiusKm: RADIUS_KM}, _] := planets
1042 |
1043 | earthRadiusKm
1044 | => 6371
1045 | ```
1046 |
1047 | You can nest these destructurings to any depth. For example the above
1048 | example plucks the `earthRadiusKm` constant from two-levels down inside
1049 | a vector of dicts. We are using the convention of using the `_`
1050 | identifier for unused values.
1051 |
1052 | ## Mutable State
1053 |
1054 | ## Quoting and Unquoting
1055 |
1056 | ## Invoking Functions
1057 |
1058 | ## Java Object Fields and Methods
1059 |
1060 | ## Defining Functions
1061 |
1062 | ## Closures
1063 |
1064 | ## Function-Like Macros
1065 |
1066 | ## Interfaces
1067 |
1068 | ## Structs
1069 |
1070 | ## new
1071 |
1072 | ## Labels
1073 |
1074 | ## Literals
1075 |
1076 | ## Regular Expressions
1077 |
1078 | ## Vectors
1079 |
1080 | ## Dictionaries
1081 |
1082 | ## Type Hints
1083 |
1084 | Funcgo is a _gradually typed_ language. Unlike Go, you do not need to
1085 | specify any types, and in most cases the Clojure runtime can figure
1086 | them out. However sometimes you may get a runtime warning from the
1087 | Clojure runtime that it is using _reflection_ because it cannot figure
1088 | out your types. To allow your code to run more efficiently you can
1089 | add types using the same syntax as the Go language.
1090 |
1091 | In practice, you usually only need to add types in a very few places
1092 | in your code.
1093 |
1094 | ```go
1095 | consoleReader ConsoleReader := newConsoleReader()
1096 | ```
1097 |
1098 | Above is an example of a constant being declared of `ConsoleReader`
1099 | type so future uses of the constant are more efficient.
1100 |
1101 | ```go
1102 | func compileFile(inFile File, root File, opts) {
1103 | ```
1104 |
1105 | And above is an example of the first two of the three function
1106 | parameters being declared to be of type `File`.
1107 |
1108 | [1]: http://clojure.github.io/clojure/
1109 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject org.eamonn.funcgo/funcgo-compiler "0.5.1"
2 | :description "Compile Functional Go into Clojure"
3 | :url "http://funcgo.org"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.6.0"]
7 | [org.clojure/core.async "0.1.303.0-886421-alpha"]
8 | [instaparse "1.3.3"]
9 | [jline "2.11"]
10 | [org.clojure/tools.cli "0.3.1"]
11 | [commons-lang/commons-lang "2.6"]
12 | [inflections "0.9.5" :scope "test" :exclusions [org.clojure/clojure]]
13 | [org.clojure/tools.logging "0.3.0" :scope "test"]
14 | [clj-logging-config "1.9.12" :scope "test"]
15 | [midje "1.6.3" :scope "test"]]
16 | :profiles {
17 | :dev {:plugins [[lein-midje "3.1.1"]]}
18 | :bootstrap {
19 | :dependencies [[org.eamonn.funcgo/funcgo-lein-plugin "0.5.1"]]
20 | :plugins [[org.eamonn.funcgo/funcgo-lein-plugin "0.5.1"]]
21 | :main ^:skip-aot funcgo.main
22 | :target-path "target/%s"
23 | }
24 | :uberjar {:aot :all}}
25 | :main funcgo.main)
26 |
--------------------------------------------------------------------------------
/publish:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | PROJECT=funcgo-compiler
4 |
5 | if [ $# != 1 ]; then
6 | echo "USAGE: $0 tag"
7 | exit 1
8 | fi
9 |
10 | dir=/tmp/deploy-$PROJECT-$$
11 | mkdir -p $dir
12 |
13 | set -x
14 | cd $dir
15 | git clone git@github.com:eobrain/funcgo.git
16 | cd funcgo
17 | git pull --tags
18 | git checkout $1
19 | git status
20 | java -jar bin/$PROJECT-$1-standalone.jar -f src/ test test-cljs/ tasks/
21 |
22 | read -p " to deploy, or -C to abort" dummy
23 | lein deploy clojars
24 |
--------------------------------------------------------------------------------
/src/funcgo/codegen.go:
--------------------------------------------------------------------------------
1 | //////
2 | // This file is part of the Funcgo compiler.
3 | //
4 | // Copyright (c) 2014 Eamonn O'Brien-Strain All rights
5 | // reserved. This program and the accompanying materials are made
6 | // available under the terms of the Eclipse Public License v1.0 which
7 | // accompanies this distribution, and is available at
8 | // http://www.eclipse.org/legal/epl-v10.html
9 | //
10 | // Contributors:
11 | // Eamonn O'Brien-Strain e@obrain.com - initial author
12 | //////
13 |
14 | package codegen
15 | import (
16 | s "clojure/string"
17 | insta "instaparse/core"
18 | symbols "funcgo/symboltable"
19 | )
20 | import type (
21 | java.util.List
22 | java.io.IOException
23 | )
24 |
25 | kAsyncRules := set{
26 | ASYNCPREFIX,
27 | CHAN,
28 | TAKE,
29 | TAKEINGO,
30 | SENDSTMT,
31 | SENDSTMTINGO,
32 | SENDOP,
33 | SENDOPINGO,
34 | SELECTSTMT,
35 | SELECTSTMTINGO
36 | }
37 |
38 | // Returns a map of parser targets to functions that generate the
39 | // corresponding Clojure code.
40 | func codeGenerator(symbolTable, isGoscript) {
41 |
42 | // Convert camelcase to clojure-dasj-seprateted, e.g. fooBar to foo-bar
43 | func camelcaseToDashed(idf string) {
44 | idfTweaked := if idf->length() > 1 {
45 | s.replace(idf, /^_/, "-")
46 | } else {
47 | idf
48 | }
49 | s.replace(idfTweaked, /\p{Ll}\p{Lu}/,
50 | func{str(first($1), "-", s.lowerCase(last($1)))}
51 | )
52 | }
53 |
54 | func noDot(s String) {
55 | !(/\./ reFind s)
56 | }
57 |
58 | func isJavaClass(clazz) {
59 | try{
60 | Class::forName(clazz)
61 | true
62 | }catch Exception e { //ClassNotFoundException e {
63 | false
64 | }
65 | }
66 |
67 | func hasType(typ String) {
68 | (symbolTable symbols.HasType typ)
69 | || isGoscript && typ->startsWith("js.")
70 | || !isGoscript && noDot(typ) && isJavaClass("java.lang." str typ)
71 | }
72 |
73 | func listStr(item...) {
74 | str("(", s.join(" ", item), ")")
75 | }
76 |
77 | func blankJoin (xs...){
78 | " " s.join xs
79 | }
80 |
81 | func vecStr(item...) {
82 | str("[", s.join(" ", item), "]")
83 | }
84 |
85 | func infix(expression) {
86 | expression
87 | } (left, operator, right) {
88 | listStr(operator, left, right)
89 | }
90 |
91 | // Capitalized
92 | func isPublic(identifier) {
93 | // not lowercode
94 | !(/^\p{Ll}/ reFind identifier) || identifier == "main" ||(/^bit-/ reFind identifier)
95 | }
96 |
97 | // Return a function that always returns the given constant string.
98 | func constantFunc(s) {
99 | func{s}
100 | }
101 |
102 | func splitPath(path String) {
103 | slash := path->lastIndexOf(int('/'))
104 | beforeSlash := subs(path, 0, slash + 1)
105 | afterSlash := subs(path, slash + 1)
106 | [
107 | s.replace(beforeSlash, '/', '.'),
108 | s.replace(afterSlash, /\.gos?$/, "")
109 | ]
110 | }
111 |
112 | func declBlockFunc(typ) {
113 | func(xs...){
114 | consts := butlast(xs)
115 | expressions := last(xs)
116 | str("(", typ, " [",
117 | " " s.join consts,
118 | "] ",
119 | expressions,
120 | ")")
121 | }
122 | }
123 |
124 | func stripQuotes(literal string) string{
125 | literal->substring(1, literal->length() - 1)
126 | }
127 |
128 | func _importSpec(identifier, dotted) {
129 | // As side effect, add to symbol table for future error checking
130 | symbolTable symbols.PackageImported identifier
131 | vecStr(dotted, ":as", identifier)
132 | }
133 |
134 | func importSpec(imported) {
135 | dotted := camelcaseToDashed(s.replace(stripQuotes(imported), '/', '.'))
136 | _importSpec(last(dotted s.split /\./), dotted)
137 | } (identifier, imported) {
138 | dotted := camelcaseToDashed(s.replace(stripQuotes(imported), '/', '.'))
139 | if identifier == "_" {
140 | // package imported for sideeffect only
141 | vecStr(dotted)
142 | } else {
143 | // normal import
144 | _importSpec(identifier, dotted)
145 | }
146 | }
147 |
148 | func externImportSpec(identifier) {
149 | symbolTable symbols.PackageImported identifier
150 | ""
151 | }
152 |
153 | func vardecl(identifier, expression) {
154 | if isPublic(identifier) {
155 | listStr("def", identifier, expression)
156 | } else {
157 | listStr("def", "^:private", identifier, expression)
158 | }
159 | } (identifier, typ, expression) {
160 | if isPublic(identifier) {
161 | listStr("def", "^" str typ, identifier, expression)
162 | } else {
163 | listStr("def",
164 | "^:private",
165 | "^" str typ,
166 | identifier,
167 | expression
168 | )
169 | }
170 | }
171 |
172 | func def_(identifier, expression) {
173 | if isPublic(identifier) {
174 | listStr("def", identifier, expression)
175 | } else {
176 | listStr("def", "^:private", identifier, expression)
177 | }
178 | }
179 |
180 | func sendClause(channel, val, expr) {
181 | vecStr(channel vecStr val) blankJoin expr
182 | }
183 |
184 | func doStr(expressions) {
185 | "do" listStr expressions
186 | }
187 |
188 | // Mapping from parse tree to generators of CLJ code.
189 | {
190 | SOURCEFILE: blankJoin,
191 | NONPKGFILE: identity,
192 | IMPORTDECLS: blankJoin,
193 | IMPORTSPEC: importSpec,
194 | EXTERNIMPORTSPEC: externImportSpec,
195 | EXCLUDE: func(symbols...) {
196 | listStr(":refer-clojure", ":exclude", vecStr(...symbols))
197 | },
198 | TYPEIMPORTDECL: func() {
199 | ""
200 | } (importSpecs...) {
201 | listStr(":import", ...importSpecs)
202 | },
203 | TYPEIMPORTSPEC: func(typepackage, typeclasses...) {
204 | for typeclass := range typeclasses {
205 | symbolTable symbols.TypeImported typeclass
206 | }
207 | listStr(typepackage, ...typeclasses)
208 | },
209 | TYPEPACKAGEIMPORTSPEC: func{
210 | "." s.join $*
211 | },
212 | TYPECLASSESIMPORTSPEC: blankJoin,
213 | PRECEDENCE00: infix,
214 | PRECEDENCE0: infix,
215 | PRECEDENCE1: infix,
216 | PRECEDENCE2: infix,
217 | PRECEDENCE3: infix,
218 | PRECEDENCE4: infix,
219 | PRECEDENCE5: infix,
220 | IFELSEEXPR: func(condition, exprs) {
221 | listStr("when", condition, exprs)
222 | } (condition, block1, block2) {
223 | listStr("if", condition, block1, block2)
224 | },
225 | LETIFELSEEXPR: func(lhs, rhs, condition, exprs) {
226 | str("(let [", lhs, " ", rhs, "] ",
227 | listStr("when", condition, exprs),
228 | ")"
229 | )
230 | } (lhs, rhs, condition, block1, block2) {
231 | str("(let [", lhs, " ", rhs, "] ",
232 | listStr("if", condition, block1, block2),
233 | ")"
234 | )
235 | },
236 | ASSOC: func(symbol, items...) {
237 | listStr("assoc", symbol, ...items)
238 | },
239 | DISSOC: func(symbol, items...) {
240 | listStr("dissoc", symbol, ...items)
241 | },
242 | ASSOCIN: func(symbol, path, value) {
243 | listStr("assoc-in", symbol, path, value)
244 | },
245 | ASSOCITEM: blankJoin,
246 | ASSOCINPATH: vecStr,
247 | BOOLSWITCH: func(clauses...) {
248 | listStr("cond", ...clauses)
249 | },
250 | BOOLCASECLAUSE: blankJoin,
251 | BOOLSWITCHCASE: func(){
252 | ":else"
253 | } (cond) {
254 | cond
255 | },
256 | SELECTSTMT: func(clauses...){
257 | listStr("alt!!", ...clauses)
258 | },
259 | SENDCLAUSE: func(channel, value) {
260 | sendClause(channel, value, "nil")
261 | } (channel, value, expressions) {
262 | sendClause(channel, value, doStr(expressions))
263 | },
264 | RECVCLAUSE: func(channel) {
265 | channel blankJoin "nil"
266 | } (channel, expressions) {
267 | channel blankJoin doStr(expressions)
268 | },
269 | RECVVALCLAUSE: func(identifier, channel, expressions) {
270 | channel blankJoin (vecStr(identifier) listStr expressions)
271 | },
272 | DEFAULTCLAUSE: func() {
273 | ":default"
274 | } (expessions) {
275 | ":default" blankJoin doStr(expessions)
276 | },
277 | SELECTSTMTINGO: func(clauses...){
278 | listStr("alt!", ...clauses)
279 | },
280 | SENDCLAUSEINGO: func(channel, value) {
281 | sendClause(channel, value, "nil")
282 | } (channel, value, expressions) {
283 | sendClause(channel, value, doStr(expressions))
284 | },
285 | RECVCLAUSEINGO: func(channel) {
286 | channel blankJoin "nil"
287 | } (channel, expressions) {
288 | channel blankJoin doStr(expressions)
289 | },
290 | RECVVALCLAUSEINGO: func(identifier, channel, expressions) {
291 | channel blankJoin (vecStr(identifier) listStr expressions)
292 | },
293 | TYPESWITCH: func(x, args...) {
294 | loop(acc="(cond", remaining=args) {
295 | func typeCase() {
296 | typ := first(remaining)
297 | expr := second(remaining)
298 | str(acc, " ", listStr("instance?", typ, x), " ", expr)
299 | }
300 | switch count(remaining) {
301 | case 1: {
302 | [expr] := remaining
303 | str(acc, " :else ", expr, ")")
304 | }
305 | case 2:
306 | typeCase() str ")"
307 | default:
308 | recur(typeCase(), 2 drop remaining)
309 | }
310 | }
311 | },
312 | CONSTSWITCH: func(expr, clauses...) {
313 | listStr("case", expr, ...clauses)
314 | },
315 | LETCONSTSWITCH: func(lhs, rhs, expr, clauses...) {
316 | str("(let [", lhs, " ", rhs, "] ",
317 | listStr("case", expr, ...clauses),
318 | ")"
319 | )
320 | },
321 | CONSTCASECLAUSE: blankJoin,
322 | CONSTANTLIST: func(c) {
323 | c
324 | }(c0, c...){
325 | listStr(c0, ...c)
326 | },
327 | CONSTSWITCHCASE: func(){
328 | ""
329 | } (cond) {
330 | cond
331 | },
332 | FORRANGE: func(identifier, seq, expressions) {
333 | str("(doseq [", identifier, " ", seq, "] ", expressions, ")")
334 | },
335 | FORLAZY: func(identifier, seq, expressions) {
336 | str("(for [", identifier, " ", seq, "] ", expressions, ")")
337 | } (identifier, seq, condition, expressions) {
338 | str("(for [", identifier, " ", seq, " :when ", condition, "] ", expressions, ")")
339 | },
340 | FORTIMES: func(identifier, count, expressions) {
341 | str("(dotimes [", identifier, " ", count, "] ", expressions, ")")
342 | },
343 | FORCSTYLE: func(ident, identAgain, count, identYetAgain, expressions) {
344 | if ident != identAgain || ident != identYetAgain {
345 | throw(new IOException(
346 | `cannot mix different identifiers in c-style for loop`
347 | ))
348 | }
349 | str("(dotimes [", ident, " ", count, "] ", expressions, ")")
350 | },
351 | TRYEXPR: func(expressions, catches) {
352 | listStr("try", expressions, catches)
353 | } (expressions, catches, finally) {
354 | listStr("try", expressions, catches, finally)
355 | },
356 | CATCHES: blankJoin,
357 | CATCH: func(typ, exception, expressions) {
358 | listStr("catch", typ, exception, expressions)
359 | },
360 | FINALLY: func{listStr("finally", $1)},
361 | NEW: func{str($1, ".")},
362 | SHORTVARDECL: func(identifier, expression) {
363 | def_(identifier, expression)
364 | } (ident1, ident2, expr1, expr2) {
365 | def_(ident1, expr1) blankJoin def_(ident2, expr2)
366 | } (ident1, ident2, ident3, expr1, expr2, expr3) {
367 | blankJoin(
368 | def_(ident1, expr1),
369 | def_(ident2, expr2),
370 | def_(ident3, expr3)
371 | )
372 | },
373 | PRIMARRAYVARDECL: func(identifier, number, primtype) {
374 | elements := blankJoin(...(for _ := times readString(number) {"0"}))
375 | listStr("def", identifier, listStr("vector-of", ":" str primtype, elements))
376 | },
377 | ARRAYVARDECL: func(identifier, number, typ) {
378 | elements := blankJoin(...(for _ := times readString(number) {"nil"}))
379 | listStr("def", identifier, listStr("vector", elements))
380 | },
381 | VARDECL1: vardecl,
382 | VARDECL2: func(identifier1, identifier2, expression1, expression2) {
383 | blankJoin(
384 | vardecl(identifier1, expression1),
385 | vardecl(identifier2, expression2)
386 | )
387 | } (identifier1, identifier2, typ, expression1, expression2) {
388 | blankJoin(
389 | vardecl(identifier1, typ, expression1),
390 | vardecl(identifier2, typ, expression2)
391 | )
392 | },
393 | PREFIXEDROUTINE: listStr,
394 | PREFIXEDBLOCK: listStr,
395 | PREFIX: identity,
396 | ASYNCPREFIX: identity,
397 | VARIADICCALL: func(function, params...) {
398 | listStr("apply", function, ...params)
399 | },
400 | FUNCTIONCALL: listStr,
401 | LEN: func(call) {
402 | listStr("count", call)
403 | },
404 | CHAN: func() {
405 | "(chan)"
406 | } (n) {
407 | listStr("chan", n)
408 | },
409 | EXPRESSIONLIST: blankJoin,
410 | EXPRESSIONS: blankJoin,
411 | CONSTS: blankJoin,
412 | ASSIGNS: blankJoin,
413 | COMMACONSTS: blankJoin,
414 | BLOCK: func (expr){
415 | expr
416 | } (expr0, exprRest...) {
417 | str("(do ", " " s.join (expr0 cons exprRest), ")")
418 | },
419 | TYPECONVERSION: listStr,
420 | INDEXED: func(xs, i){ listStr("nth", xs, i) },
421 | TAKESLICE: func(xs, i){ listStr("take", i, xs) },
422 | DROPSLICE: func(xs, i){ listStr("drop", i, xs) },
423 | TOPWITHCONST: declBlockFunc("let"),
424 | TOPWITHASSIGN: declBlockFunc("let"),
425 | WITHCONST: declBlockFunc("let"),
426 | WITHASSIGN: declBlockFunc("let"),
427 | LOOP: declBlockFunc("loop"),
428 | CONST: func(identifier, expression) {
429 | str(identifier, " ", expression)
430 | },
431 | ASSIGN: func(args...) {
432 | vArgs List := vec(args)
433 | opPos := vArgs->indexOf(":=")
434 | n := vArgs->size()
435 | if n % 2 != 1 || (n - 1) / 2 != opPos {
436 | throw(new IOException(
437 | "LHS and RHS of := do not match" str blankJoin(vArgs)
438 | ))
439 | } else {
440 | " " s.join (for i := lazy \`range`(opPos) {
441 | str(vArgs[i], " ", vArgs[opPos + 1 + i])
442 | })
443 | }
444 | },
445 | VECDESTRUCT: vecStr,
446 | DICTDESTRUCT: func{str('{', (" " s.join $*), "}")},
447 | DICTDESTRUCTELEM: func(destruct, label) {
448 | str(destruct, " ", label)
449 | },
450 | VARIADICDESTRUCT: func{str("& ", $1)},
451 | SYMBOL: func(identifier){
452 | identifier
453 | } (pkg, identifier) {
454 | if !(symbolTable symbols.HasPackage pkg) {
455 | throw(new IOException(format(
456 | `package "%s" in %s.%s does not appear in imports %s`,
457 | pkg, pkg, identifier, symbols.Packages(symbolTable))))
458 | }
459 | str(pkg, "/", identifier)
460 | },
461 | BINARYOP: identity,
462 | MULOP: identity,
463 | ADDOP: identity,
464 | RELOP: identity,
465 | OPERATOR: identity,
466 | FUNCTIONDECL: func(identifier, function) {
467 | defn := if isPublic(identifier) { "defn" } else { "defn-" }
468 | listStr(defn, identifier, function)
469 | },
470 | FUNCLIKEDECL: func(funclike, identifier, function) {
471 | listStr(funclike, identifier, function)
472 | },
473 | FUNCTIONLIT: func{listStr("fn", $1)},
474 | SHORTFUNCTIONLIT: func(expr) {
475 | if first(expr) == '(' && last(expr) == ')' {
476 | "#" str expr
477 | }else{
478 | listStr("fn", "[]", expr)
479 | }
480 | },
481 | STRUCTSPEC: func(javaIdentifier, fields...) {
482 | symbolTable symbols.TypeCreated javaIdentifier
483 | listStr("defrecord",
484 | javaIdentifier,
485 | vecStr(...fields),
486 | if isEmpty(fields) {
487 | ""
488 | } else {
489 | fs := fields[0] s.split / +/
490 | fsOnly := func(s String){!s->startsWith("^")} filter fs
491 | str(
492 | "Object (toString [this] ",
493 | listStr("str", `"{"`, ` " " ` s.join fsOnly, `"}"`),
494 | ")"
495 | )
496 | }
497 | )
498 | },
499 | FIELDS: blankJoin,
500 | INTERFACESPEC: func(args...){
501 | symbolTable symbols.TypeCreated first(args)
502 | listStr("defprotocol", ...args)
503 | },
504 | VOIDMETHODSPEC: func(javaIdentifier) {
505 | listStr(javaIdentifier, "[this]")
506 | }(javaIdentifier, methodparams) {
507 | listStr(javaIdentifier, str("[this ", methodparams, "]"))
508 | },
509 | TYPEDMETHODSPEC: func(javaIdentifier, typ) {
510 | listStr("^" str typ, javaIdentifier, "[this]")
511 | } (javaIdentifier, methodparams, typ) {
512 | listStr("^" str typ, javaIdentifier, str("[this ", methodparams, "]"))
513 | },
514 | IMPLEMENTS: func(protocol, concrete, methodimpls...) {
515 | symbolTable symbols.TypeCreated concrete
516 | listStr(...concat(list("extend-type", concrete, protocol), methodimpls))
517 | },
518 | METHODIMPL: func(javaIdentifier, function) {
519 | listStr(javaIdentifier, function)
520 | },
521 | METHODPARAMETERS: blankJoin,
522 | METHODPARAM: func(symbol) {
523 | symbol
524 | } (symbol, typ) {
525 | str("^", typ, " ", symbol)
526 | },
527 | PERCENT: constantFunc("%"),
528 | PERCENTNUM: func{"%" str $1},
529 | PERCENTVARADIC: constantFunc("%&"),
530 | FUNCTIONPARTS: func{str("(", ") (" s.join $*, ")")},
531 | FUNCTIONPART0: func(expression) {
532 | "[] " str expression
533 | } (typ, expression) {
534 | str("^", typ, " [] ", expression)
535 | },
536 | VFUNCTIONPART0: func(variadic, expression) {
537 | str("[", variadic, "] ", expression)
538 | } (variadic, typ, expression) {
539 | str("^", typ, " [", variadic, "] ", expression)
540 | },
541 | FUNCTIONPARTN: func(parameters, expression) {
542 | str("[", parameters, "] ", expression)
543 | } (parameters, typ, expression) {
544 | str("^", typ, " [", parameters, "] ", expression)
545 | },
546 | VFUNCTIONPARTN: func(parameters, variadic, expression) {
547 | str("[", parameters, " ", variadic, "] ", expression)
548 | } (parameters, variadic, typ, expression) {
549 | str("^", typ, " [", parameters, " ", variadic, "] ", expression)
550 | },
551 | UNTYPEDMETHODIMPL: func(name, block) {
552 | listStr(name, str("[this]"), block)
553 | } (name, params, block) {
554 | listStr(name, str("[this ", params, "]"), block)
555 | },
556 | TYPEDMETHODIMPL: func(name, typ, block) {
557 | listStr("^" str typ, name, str("[this]"), block)
558 | } (name, params, typ, block) {
559 | listStr("^" str typ, name, str("[this ", params, "]"), block)
560 | },
561 | PARAMETERS: blankJoin,
562 | VARIADIC: func{"& " str $1},
563 | VECLIT: vecStr,
564 | DICTLIT: func{str(...$*)},
565 | DICTELEMENT: func(key, value) {str(key, " ", value, " ")},
566 | SETLIT: func{str("#{", " " s.join $*, "}")},
567 | STRUCTLIT: func(typ, exprs...) {
568 | listStr(typ str ".", ...exprs)
569 | },
570 | LABEL: func{":" str s.replace(s.lowerCase($1), /_/, "-")},
571 | ISLABEL: func{str(":", s.replace(s.lowerCase($1), /_/, "-"), "?")},
572 | IDENTIFIER: camelcaseToDashed,
573 | TYPEDIDENTIFIER: func(identifier, typ) {
574 | str(`^`, typ, " ", identifier)
575 | },
576 | TYPEDIDENTIFIERS: func(args...) {
577 | typ := last(args)
578 | identifiers := butlast(args)
579 | decls := for identifier := lazy identifiers {
580 | str(`^`, typ, " ", identifier)
581 | }
582 | blankJoin(...decls)
583 | },
584 | PKG: func{"." s.join $*},
585 | DECIMALLIT: identity,
586 | BIGINTLIT: str,
587 | BIGFLOATLIT: str,
588 | FLOATLIT: identity, //str,
589 | HEXLIT: func(s string){
590 | Integer::parseInt(s, 16)
591 | },
592 | REGEX: func(regex string){
593 | str(
594 | `#"`,
595 | stripQuotes(regex)->replace(`\/`, `/`)->replace(`"`, `\"`),
596 | `"`
597 | )
598 | },
599 | INTERPRETEDSTRINGLIT: func(literal) {
600 | str(`"`, stripQuotes(literal), `"`)
601 | },
602 | RAWSTRINGLIT: func{str(`"`, s.escape($1, charEscapeString), `"`)},
603 | CLOJUREESCAPE: identity,
604 | LITTLEUVALUE: func(d1,d2,d3,d4){str(`\u`,d1,d2,d3,d4)},
605 | OCTALBYTEVALUE: func(d1,d2,d3){str(`\o`,d1,d2,d3)},
606 | UNICODECHAR: func{`\` str $1},
607 | NEWLINECHAR: constantFunc(`\newline`),
608 | SPACECHAR: constantFunc(`\space`),
609 | BACKSPACECHAR: constantFunc(`\backspace`),
610 | RETURNCHAR: constantFunc(`\return`),
611 | TABCHAR: constantFunc(`\tab`),
612 | BACKSLASHCHAR: constantFunc(`\\`),
613 | SQUOTECHAR: constantFunc(`\'`),
614 | DQUOTECHAR: constantFunc(`\"`),
615 | HEXDIGIT: identity,
616 | OCTALDIGIT: identity,
617 | ISIDENTIFIER: func(initial, identifier) {
618 | str( s.lowerCase(initial), identifier, "?")
619 | },
620 | EQUALS: constantFunc("="),
621 | AND: constantFunc("and"),
622 | OR: constantFunc("or"),
623 | MUTIDENTIFIER: func(initial, identifier) {
624 | str( s.lowerCase(initial), identifier, "!")
625 | },
626 | ESCAPEDIDENTIFIER: func{ stripQuotes($1) },
627 | UNARYEXPR: func(e) {
628 | e
629 | } (operator, expression){
630 | listStr(operator, expression)
631 | },
632 | NOTEQ: constantFunc("not="),
633 | BITAND: constantFunc("bit-and"),
634 | BITANDNOT: constantFunc("bit-and-not"),
635 | BITOR: constantFunc("bit-or"),
636 | BITXOR: constantFunc("bit-xor"),
637 | BITNOT: constantFunc("bit-not"),
638 | TAKE: constantFunc("!!"),
641 | SENDOPINGO: constantFunc(">!"),
642 | SHIFTLEFT: constantFunc("bit-shift-left"),
643 | SHIFTRIGHT: constantFunc("bit-shift-right"),
644 | NOT: constantFunc("not"),
645 | MOD: constantFunc("mod"),
646 | DEREF: func{"@" str $1},
647 | SYNTAXQUOTE: func{"`" str $1},
648 | UNQUOTE: func{"~" str $1},
649 | UNQUOTESPLICING: func{ "~@" str $1},
650 | JAVAFIELD: func(expression, identifier) {
651 | listStr(".", expression, identifier)
652 | },
653 | JAVASTATIC: func{"/" s.join $*},
654 | TYPENAME: func(segments...){
655 | typ := "." s.join segments
656 | if !hasType(typ) {
657 | throw(new IOException(format(
658 | `type "%s" does not appear in type imports %s`,
659 | typ, symbols.Types(symbolTable))))
660 | }
661 | typ
662 | },
663 | UNDERSCOREJAVAIDENTIFIER: func(s string){ "-" str s->substring(1)},
664 | JAVAMETHODCALL: func(expression, identifier) {
665 | str("(. ", expression, " (", identifier, "))")
666 | } (expression, identifier, call) {
667 | str("(. ", expression, " (", identifier, " ", call, "))")
668 | },
669 | LONG: constantFunc("long"),
670 | DOUBLE: constantFunc("double"),
671 | STRING: constantFunc("String")
672 | }
673 | }
674 |
675 | func syncImports(isGoscript, isSync) {
676 | if isSync {
677 | []
678 | } else {
679 | if isGoscript {
680 | [vecStr(
681 | "cljs.core.async", ":as", "async", ":refer",
682 | "[chan ! alt!]"
683 | )]
684 | } else {
685 | [vecStr(
686 | "clojure.core.async", ":as", "async", ":refer",
687 | "[chan go thread ! alt! !! alt!!]"
688 | )]
689 | }
690 | }
691 | }
692 |
693 | func macroSyncImports(isGoscript, isSync) {
694 | if isSync || !isGoscript {
695 | []
696 | } else {
697 | [vecStr("cljs.core.async.macros", ":as", "async", ":refer", "[go]")]
698 | }
699 | }
700 |
701 | // Return an empty list if tail is empty, otherwise return listStr(head, ...tail)
702 | func req(head, tail) {
703 | if isEmpty(tail) {
704 | []
705 | } else {
706 | [listStr(head, ...tail)]
707 | }
708 | }
709 |
710 | func packageclauseFunc(symbolTable, path String, isGoscript, isSync) {
711 | [parent, name] := splitPath(path)
712 | if isGoscript {
713 | symbolTable symbols.PackageCreated "js"
714 | }
715 | func(imported, importDecls String) {
716 | fullImported := parent str imported
717 | hasImports := importDecls->contains(":require ")
718 | hasMacroImports := importDecls->contains(":require-macros ")
719 | xtraImports := if hasImports {
720 | []
721 | } else {
722 | req(":require", syncImports(isGoscript, isSync))
723 | }
724 | xtraMacroImports := if hasMacroImports {
725 | []
726 | } else {
727 | req(":require-macros", macroSyncImports(isGoscript, isSync))
728 | }
729 | imports := concat([importDecls], xtraMacroImports, xtraImports)
730 | if imported != name {
731 | throw(new IOException(str(
732 | `Got package "`, imported, `" instead of expected "`,
733 | name, `" in "`, path, `"`
734 | )))
735 | }
736 | if isGoscript {
737 | listStr("ns", fullImported, ...imports)
738 | } else {
739 | str(
740 | listStr("ns", fullImported, "(:gen-class)", ...imports),
741 | " (set! *warn-on-reflection* true)"
742 | )
743 | }
744 | }
745 | }
746 |
747 | func importDeclFunc(isGoscript, isSync) {
748 | func() {
749 | ""
750 | } (importSpecs...) {
751 | imports := importSpecs concat syncImports(isGoscript, isSync)
752 | listStr(":require", ...imports)
753 | }
754 | }
755 |
756 | func macroImportDeclFunc(isGoscript, isSync) {
757 | func() {
758 | ""
759 | } (importSpecs...) {
760 | imports := importSpecs concat macroSyncImports(isGoscript, isSync)
761 | listStr(":require-macros", ...imports)
762 | }
763 | }
764 |
765 | func usesAsync(parsed) {
766 | func walk(vector) {
767 | if isEmpty(vector) {
768 | false
769 | } else {
770 | f := first(vector)
771 | if isVector(f) && usesAsync(f) {
772 | true
773 | } else {
774 | recur(rest(vector))
775 | }
776 | }
777 | }
778 | if kAsyncRules isContains first(parsed) {
779 | true
780 | } else {
781 | walk(rest(parsed))
782 | }
783 | }
784 |
785 | // Return the Clojure code generated from the given parse tree.
786 | func Generate(path String, parsed, isSync) {
787 | symbolTable := symbols.New()
788 | isGoscript := path->endsWith(".gos")
789 | isSync := !usesAsync(parsed)
790 | codeGen := codeGenerator(symbolTable, isGoscript) += {
791 | PACKAGECLAUSE: packageclauseFunc(symbolTable, path, isGoscript, isSync),
792 | IMPORTDECL: importDeclFunc(isGoscript, isSync) ,
793 | MACROIMPORTDECL: macroImportDeclFunc(isGoscript, isSync)
794 | }
795 | clj := insta.transform(codeGen, parsed)
796 | symbols.CheckAllUsed(symbolTable)
797 | clj
798 | }
799 |
--------------------------------------------------------------------------------
/src/funcgo/core.go:
--------------------------------------------------------------------------------
1 | //////
2 | // This file is part of the Funcgo compiler.
3 | //
4 | // Copyright (c) 2014 Eamonn O'Brien-Strain All rights
5 | // reserved. This program and the accompanying materials are made
6 | // available under the terms of the Eclipse Public License v1.0 which
7 | // accompanies this distribution, and is available at
8 | // http://www.eclipse.org/legal/epl-v10.html
9 | //
10 | // Contributors:
11 | // Eamonn O'Brien-Strain e@obrain.com - initial author
12 | //////
13 |
14 | package core
15 | import (
16 | insta "instaparse/core"
17 | "clojure/pprint"
18 | "instaparse/failure"
19 | "clojure/string"
20 | "funcgo/parser"
21 | "funcgo/codegen"
22 | )
23 | import type java.io.IOException
24 |
25 |
26 | func untabify(s){ string.replace(s, /\t/, " ") }
27 |
28 | func Ambiguity(fgo) {
29 | preprocessed := untabify(fgo)
30 | insta.parses(parser.Parse, preprocessed)
31 | }
32 |
33 | func parse(preprocessed, startRule, isAmbiguity) {
34 | if isAmbiguity {
35 |
36 | parsedList := insta.parses(parser.Parse, preprocessed, START, startRule)
37 | ambiguity := count(parsedList)
38 | switch ambiguity {
39 | case 0: {
40 | "__preprocessed.go" spit preprocessed
41 | throw(new IOException(
42 | "Parsing failure. Turn off ambiguity flag to see details."))
43 | }
44 | case 1:
45 | parsedList[0]
46 | default: {
47 | print(" WARNING, ambiguity=", ambiguity)
48 | parsedList[0]
49 | }
50 | }
51 |
52 | } else {
53 |
54 | parsed := parser.Parse(preprocessed, START, startRule)
55 | if insta.isFailure(parsed) {
56 | "__preprocessed.go" spit preprocessed
57 | throw(new IOException(str(withOutStr(failure.pprintFailure(parsed)))))
58 | } else {
59 | parsed
60 | }
61 |
62 | }
63 | }
64 |
65 | func Parse(path, fgo) {
66 | Parse(path, fgo, SOURCEFILE)
67 | } (path, fgo, startRule) {
68 | Parse(path, fgo, startRule, false, false, false)
69 | } (path, fgo, startRule, isNodes, isSync, isAmbiguity) {
70 | preprocessed := untabify(fgo)
71 | parsed := parse(preprocessed, startRule, isAmbiguity)
72 | if isNodes {
73 | pprint.pprint(parsed)
74 | }
75 | codegen.Generate(path, parsed, isSync)
76 | }
77 |
--------------------------------------------------------------------------------
/src/funcgo/main.go:
--------------------------------------------------------------------------------
1 | //////
2 | // This file is part of the Funcgo compiler.
3 | //
4 | // Copyright (c) 2014 Eamonn O'Brien-Strain All rights
5 | // reserved. This program and the accompanying materials are made
6 | // available under the terms of the Eclipse Public License v1.0 which
7 | // accompanies this distribution, and is available at
8 | // http://www.eclipse.org/legal/epl-v10.html
9 | //
10 | // Contributors:
11 | // Eamonn O'Brien-Strain e@obrain.com - initial author
12 | //////
13 |
14 | // This file contains the entry point for the standalone version of
15 | // the Funcgo compile and is called by the Leiningen plugin.
16 |
17 | package main
18 | import (
19 | "clojure/java/io"
20 | "clojure/pprint"
21 | "clojure/string"
22 | "clojure/tools/cli"
23 | "funcgo/core"
24 | )
25 | import type (
26 | java.io.{BufferedWriter, File, StringWriter, IOException}
27 | jline.console.ConsoleReader
28 | )
29 |
30 | commandLineOptions := [
31 | ["-r", "--repl", "start a Funcgo interactive console"],
32 | ["-s", "--sync", "No asynchronous channel constructs"],
33 | ["-n", "--nodes", "print out the parse tree that the parser produces"],
34 | ["-u", "--ugly", "do not pretty-print the Clojure"],
35 | ["-f", "--force", "Force compiling even if not out-of-date"],
36 | ["-a", "--ambiguity", "print out all matched parse trees to diagnose ambiguity"],
37 | ["-h", "--help", "print help"]
38 | ]
39 |
40 | // A version of pprint that preserves type hints.
41 | // See https://groups.google.com/forum/#!topic/clojure/5LRmPXutah8
42 | func prettyPrint(obj, writer) {
43 | origDispatch := \pprint/*print-pprint-dispatch*\ // */ for emacs
44 | pprint.withPprintDispatch(
45 | func(o) {
46 | if met := meta(o); met {
47 | print("^")
48 | if count(met) == 1 {
49 | if met(TAG) {
50 | origDispatch(met(TAG))
51 | } else {
52 | if met(PRIVATE) == true {
53 | origDispatch(PRIVATE)
54 | } else {
55 | origDispatch(met)
56 | }
57 | }
58 | } else {
59 | origDispatch(met)
60 | }
61 | print(" ")
62 | pprint.pprintNewline(FILL)
63 | }
64 | origDispatch(o)
65 | },
66 | pprint.pprint(obj, writer)
67 | )
68 | }
69 |
70 | func writePrettyTo(cljText, writer BufferedWriter) {
71 | for expr := range readString( str("[", cljText, "]")) {
72 | prettyPrint(expr, writer)
73 | writer->newLine()
74 | }
75 | writer->close()
76 | }
77 |
78 |
79 | func compileExpression(inPath, fgoText) {
80 | cljText := core.Parse(inPath, fgoText, EXPR)
81 | strWriter := new StringWriter()
82 | writer := new BufferedWriter(strWriter)
83 | cljText writePrettyTo writer
84 | strWriter->toString()
85 | }
86 |
87 | func newConsoleReader() {
88 | consoleReader := new ConsoleReader()
89 | consoleReader->setPrompt("fgo=> ")
90 | consoleReader
91 | }
92 |
93 | func repl(){
94 | consoleReader ConsoleReader := newConsoleReader()
95 | loop(){
96 | fgoText := consoleReader->readLine()
97 | if !string.isBlank(fgoText) {
98 | try{
99 | cljText := first(core.Parse("repl.go", fgoText, EXPR))
100 | println("Clojure: ", cljText)
101 | println("Result: ", eval(readString(cljText)))
102 | } catch Exception e {
103 | println(e)
104 | }
105 | println()
106 | }
107 | if fgoText != nil {
108 | recur()
109 | }
110 | }
111 | }
112 |
113 | func CompileString(inPath, fgoText) {
114 | cljText := core.Parse(inPath, fgoText)
115 | strWriter := new StringWriter()
116 | writer := new BufferedWriter(strWriter)
117 | cljText writePrettyTo writer
118 | strWriter->toString()
119 | }
120 |
121 | func compileFile(inFile File, root File, opts) {
122 | splitRoot := reMatches(/([^\.]+)(\.[a-z]+)?(\.gos?)/, inFile->getPath)
123 | if !isNil(splitRoot) {
124 | [_, inPath, suffixExtra, suffix] := splitRoot
125 | compileFile(
126 | inFile,
127 | root,
128 | inPath str suffix,
129 | opts,
130 | if isNil(suffixExtra) {""} else {suffixExtra}
131 | )
132 | }
133 | } (inFile File, root File, inPath, opts, suffixExtra) {
134 | outFile := io.file(string.replace(inPath, /\.go(s?)$/, ".clj$1" str suffixExtra))
135 | if opts(FORCE) || outFile->lastModified() < inFile->lastModified() {
136 | prefixLen := root->getAbsolutePath()->length()
137 | relative := subs(inFile->getAbsolutePath(), prefixLen + 1)
138 | println(" ", relative, "...")
139 | {
140 | fgoText := slurp(inFile)
141 | lines := count(func{ $1 == '\n' } filter fgoText)
142 | start := if suffixExtra == "" { SOURCEFILE } else { NONPKGFILE }
143 | try {
144 | beginTime := System::currentTimeMillis()
145 | cljText String := core.Parse(
146 | relative,
147 | fgoText,
148 | start,
149 | opts(NODES), opts(SYNC), opts(AMBIGUITY)
150 | )
151 | duration := System::currentTimeMillis() - beginTime
152 | // TODO(eob) open using with-open
153 | writer := io.writer(outFile)
154 |
155 | writer->write(str(";; Compiled from ", inFile, "\n"))
156 | if opts(UGLY) {
157 | writer->write(cljText)
158 | writer->close()
159 | } else {
160 | cljText writePrettyTo writer
161 | }
162 | if outFile->length() == 0 {
163 | outFile->delete()
164 | println("\t\tERROR: No output created.")
165 | } else {
166 | println("\t\t-->",
167 | outFile->getPath(),
168 | int(1000.0*lines/duration),
169 | "lines/s")
170 | if (outFile->length) / (inFile->length) < 0.4 {
171 | println("WARNING: Output file is only",
172 | int(100 * (outFile->length)
173 | / (inFile->length)),
174 | "% the size of the input file")
175 | }
176 | }
177 | } catch IOException e {
178 | println("Parsing ", relative, " failed:\n", e->getMessage())
179 | }
180 | }
181 | }
182 | }
183 |
184 | func compileTree(root File, opts) {
185 | println(root->getName())
186 | for f := range fileSeq(root) {
187 | inFile File := f
188 | try {
189 | compileFile(inFile, root, opts)
190 | } catch IOException e {
191 | println(e->getMessage())
192 | } catch Exception e {
193 | e->printStackTrace()
194 | }
195 | }
196 | }
197 |
198 | func printError(cmdLine) {
199 | println()
200 | if cmdLine(ERRORS) {
201 | println(cmdLine(ERRORS))
202 | }
203 | println("USAGE: fgoc [options] path ...")
204 | println("options:")
205 | println(cmdLine(SUMMARY))
206 | }
207 |
208 | // Convert Funcgo files to clojure files, using the commandLineOptions
209 | // to parse the arguments.
210 | func Compile(args...) {
211 | cmdLine := args cli.parseOpts commandLineOptions
212 | otherArgs := cmdLine(ARGUMENTS)
213 | opts := cmdLine(OPTIONS)
214 | here := io.file(".")
215 |
216 | if cmdLine(ERRORS) || opts(HELP){
217 | println(cmdLine(SUMMARY))
218 | }else{
219 | if not(seq(otherArgs)) {
220 | println("Missing directory or file argument.")
221 | printError(cmdLine)
222 | } else {
223 | // file arguments
224 | for arg := range otherArgs {
225 | if file := io.file(arg); file->isDirectory {
226 | compileTree(file, opts)
227 | } else {
228 | try {
229 | compileFile(file, here, opts)
230 | } catch Exception e {
231 | println("\n", e->getMessage())
232 | }
233 | }
234 | }
235 | }
236 | if opts(REPL) {
237 | repl()
238 | }
239 | }
240 | }
241 |
242 | // Entry point for stand-alone compiler. Usage is the same as for the
243 | // Compile function.
244 | func _main(args...) {
245 | Compile(...args)
246 | }
247 |
--------------------------------------------------------------------------------
/src/funcgo/parser.go:
--------------------------------------------------------------------------------
1 | //////
2 | // This file is part of the Funcgo compiler.
3 | //
4 | // Copyright (c) 2014 Eamonn O'Brien-Strain All rights
5 | // reserved. This program and the accompanying materials are made
6 | // available under the terms of the Eclipse Public License v1.0 which
7 | // accompanies this distribution, and is available at
8 | // http://www.eclipse.org/legal/epl-v10.html
9 | //
10 | // Contributors:
11 | // Eamonn O'Brien-Strain e@obrain.com - initial author
12 | //////
13 |
14 | package parser
15 |
16 | import insta "instaparse/core"
17 |
18 | whitespaceOrComments := insta.parser(`
19 | ws-or-comments = #'(\s|(//[^\n]*\n))+'
20 | `, NO_SLURP, true, // for App Engine compatibility
21 | );
22 |
23 | var Parse = insta.parser(`
24 | sourcefile = packageclause (expressions|topwithconst|topwithassign)
25 | nonpkgfile = (expressions|topwithconst|topwithassign) ?
26 | packageclause = <#'\bpackage\b'> pkg importdecls
27 | pkg = Identifier {<'/'> Identifier}
28 | = #'\s*[;\n]\s*' | #'\s*//[^\n]*\n\s*'
29 | importdecls = {AnyImportDecl}
30 | = importdecl | macroimportdecl | externimportdecl | typeimportdecl | exclude
31 | exclude = <#'\bexclude\b' '('>
32 | (Identifier|operator) { <','> (Identifier|operator) } <')'>
33 | importdecl = <#'\bimport\b' '('>
34 | ImportSpec {ImportSpec}
35 | <')'>
36 | | <#'\bimport\b'> ImportSpec
37 | macroimportdecl = <#'\bimport\b' #'\bmacros\b' '('>
38 | ImportSpec {ImportSpec} <')'>
39 | | <#'\bimport\b' #'\bmacros\b'> ImportSpec
40 | = <#'\bimport\b' #'\bextern\b' '('>
41 | externimportspec {externimportspec} <')'>
42 | | <#'\bimport\b' #'\bextern\b'> externimportspec
43 | externimportspec = identifier
44 | typeimportdecl = <#'\bimport\b' #'\btype\b' '('>
45 | typeimportspec {typeimportspec} <')'>
46 | | <#'\bimport\b' #'\btype\b'> typeimportspec
47 | typeimportspec = typepackageimportspec <'.'> (
48 | JavaIdentifier
49 | | <'{'> JavaIdentifier {<','> JavaIdentifier} <'}'>
50 | )
51 | typepackageimportspec = JavaIdentifier {<'.'> JavaIdentifier}
52 | = importspec
53 | importspec = ( Identifier )? string_lit
54 | expressions = expr
55 | | expressions expr
56 | = precedence00 | Vars | (*shortvardecl |*) ifelseexpr | letifelseexpr | tryexpr | forrange |
57 | forlazy | fortimes | forcstyle | Blocky | ExprSwitchStmt
58 | | functiondecl
59 |
60 |
61 | = block | withconst | withassign | loop
62 | withconst = <'{' #'\bconst\b'> ( const | <'('> consts <')'> ) expressions <'}'>
63 | withassign = <'{'> assigns expressions <'}'>
64 | consts = ( const { const} )?
65 | assigns = assign { assign}
66 | const = Destruct <'='> expr
67 | assign = Destruct {<','> Destruct} ':=' expr {<','> expr}
68 | = Identifier | typedidentifier | vecdestruct | dictdestruct
69 | typedidentifiers = Identifier ({ <','> Identifier })? typename
70 | typedidentifier = Identifier typename
71 | typename = JavaIdentifier {<'.'> JavaIdentifier} | primitivetype | string
72 | = long | double | #'\bbyte\b' | #'\bshort\b' | #'\bchar\b' | #'\bboolean\b'
73 | long = <#'\bint\b'> | <#'\blong\b'>
74 | double = <#'\bfloat\b'> | <#'\bfloat64\b'>
75 | string = <#'\bstring\b'>
76 | vecdestruct = <'['> VecDestructElem {<','> VecDestructElem } <']'>
77 | = Destruct | variadicdestruct | label
78 | variadicdestruct = Destruct Ellipsis
79 | dictdestruct = <'{'> dictdestructelem { <','> dictdestructelem} <'}'>
80 | dictdestructelem = Destruct <':'> expr
81 | loop = <#'\bloop\b' '('> commaconsts <')'> ImpliedDo
82 | commaconsts = ( const { <','> const} )?
83 | = <'{'> expressions <'}'> | withconst | withassign
84 | block = <'{'> expr { expr} <'}'>
85 | topwithconst = <#'\bconst\b'> ( const | <'('> consts <')'> ) expressions
86 | topwithassign = assigns expressions
87 | = boolswitch | constswitch | letconstswitch | typeswitch
88 | | selectstmtingo | selectstmt
89 | selectstmt = <#'\bselect\b' '{'> (CommClause { CommClause})? <'}'>
90 | = sendclause | recvclause | recvvalclause | defaultclause
91 | sendclause = <#'\bcase\b'> UnaryExpr < '<-'> UnaryExpr <':'> expressions?
92 | recvclause = <#'\bcase\b' '<-'> expr <':'> expressions?
93 | recvvalclause = <#'\bcase\b'> identifier <'=' '<-'> expr <':'> expressions
94 | defaultclause = <#'\bdefault\b' ':'> expressions?
95 | selectstmtingo = <#'\bselect\b' '{'> CommClauseInGo { CommClauseInGo} <'}'>
96 | = sendclauseingo | recvclauseingo | recvvalclauseingo | defaultclause
97 | sendclauseingo = <#'\bcase\b'> UnaryExpr < '<:'> UnaryExpr <':'> expressions?
98 | recvclauseingo = <#'\bcase\b' '<:'> expr <':'> expressions?
99 | recvvalclauseingo = <#'\bcase\b'> identifier <'=' '<:'> expr <':'> expressions
100 | typeswitch = <#'\bswitch\b'> PrimaryExpr <'.' '(' #'\btype\b' ')' '{'
101 | #'\bcase\b'> typename <':'> expressions
102 | { typename <':'> expressions}
103 | ( expressions )? <'}'>
104 | boolswitch = <#'\bswitch\b' '{'> boolcaseclause { boolcaseclause} <'}'>
105 | constswitch = <#'\bswitch\b'> expr <'{'> constcaseclause { constcaseclause} <'}'>
106 | letconstswitch = <#'\bswitch\b'> Destruct <':='> expr
107 | expr <'{'> constcaseclause { constcaseclause} <'}'>
108 | boolcaseclause = boolswitchcase <':'> expressions
109 | constcaseclause = constswitchcase <':'> expressions
110 | boolswitchcase = <#'\bcase\b'> expressionlist | <#'\bdefault\b'>
111 | constswitchcase = <#'\bcase\b'> constantlist | <#'\bdefault\b'>
112 | constantlist = expr { <','> expr}
113 | = label | BasicLit | veclit | dictlit | setlit | structlit
114 | operator =
115 | or
116 | |and
117 | |'<-'|'<:'|equals|noteq|'<'|'<='|'>='|'>'
118 | |'+'|!'->' '-'|bitor|bitxor
119 | |'*'|'/'|mod|shiftleft|shiftright|bitand|bitandnot
120 | |'+='|'-='
121 | precedence00 = precedence0
122 | | precedence00 SendOp precedence0
123 | | assoc | dissoc | associn
124 | assoc = precedence0 <'+=' '{'> associtem { <','> associtem } <'}'>
125 | dissoc = precedence0 <'-=' '{'> associtem { <','> associtem } <'}'>
126 | associtem = precedence0 <':'> precedence0
127 | associn = precedence0 <'+=' '{'> associnpath <':'> precedence0 <'}'>
128 | associnpath = precedence0 precedence0 {precedence0}
129 | = sendop | sendopingo
130 | sendop = <'<-'>
131 | sendopingo = <'<:'>
132 | precedence0 = precedence1
133 | | precedence0 symbol precedence1
134 | DoubleSpace = <#'[ \t][ \t]'>
135 | symbol = Identifier
136 | | Identifier <'.'> Identifier
137 | | Identifier <'.'> operator
138 | | javastatic
139 | precedence1 = precedence2
140 | | precedence1 or precedence2
141 | or = <'||'>
142 | precedence2 = precedence3
143 | | precedence2 and precedence3
144 | and = <'&&'>
145 | precedence3 = precedence4
146 | | precedence3 relop precedence4
147 | relop = equals | noteq | (!SendOp '<') | '<=' | '>=' | '>'
148 | equals = <'=='>
149 | noteq = <'!='>
150 | precedence4 = precedence5
151 | | precedence4 addop precedence5
152 | addop = '+' | !'->' '-' | ( !or bitor ) | bitxor
153 | bitor = <'|'>
154 | bitxor = <'^'>
155 | precedence5 = UnaryExpr
156 | | precedence5 mulop UnaryExpr
157 | mulop = '*' | (!'//' '/') | mod | shiftleft | shiftright | bitand | bitandnot
158 | shiftleft = <'<<'>
159 | shiftright = <'>>'>
160 | mod = <'%'>
161 | bitand = !and !bitandnot <'&'>
162 | bitandnot = !and <'&^'>
163 | javastatic = typename <'::'> JavaIdentifier
164 | = #'\b[\p{L}_][\p{L}_\p{Nd}]*\b'
165 | | underscorejavaidentifier
166 | underscorejavaidentifier = #'\b_[\p{L}_][\p{L}_\p{Nd}]*\b'
167 | = !(Keyword | hexlit) (identifier | isidentifier | mutidentifier |
168 | escapedidentifier)
169 | Keyword = #'\bcase\b'
170 | | #'\bconst\b'
171 | | #'\bfor\b'
172 | | #'\bif\b'
173 | | #'\bnew\b'
174 | | #'\bpackage\b'
175 | (*| #'\brange\b'*)
176 | | #'\bselect\b'
177 | identifier = #'[\p{L}_[\p{S}&&[^\p{Punct}]]][\p{L}_[\p{S}&&[^\p{Punct}]]\p{Nd}]*'
178 | isidentifier = <#'\bis'> #'\p{L}' identifier (* TODO(eob) make a regex *)
179 | mutidentifier = <#'\bmutate'> #'\p{L}' identifier (* TODO(eob) make a regex *)
180 | escapedidentifier = #'\\[^\n\\]+\\'
181 | = <#'\bvar\b'> ( <'('> VarDecl+ <')'> | VarDecl )
182 | = primarrayvardecl | arrayvardecl | vardecl1 | vardecl2
183 | primarrayvardecl = Identifier <'['> int_lit <']'> primitivetype
184 | arrayvardecl = Identifier <'['> int_lit <']'> typename
185 | vardecl1 = Identifier ( typename )? <'='> expr
186 | vardecl2 = Identifier <','> Identifier ( typename )? <'='> precedence00 <','> precedence00
187 | ifelseexpr = <#'\bif\b'> expr Blocky ( <#'\belse\b'> Blocky )?
188 | letifelseexpr = <#'\bif\b'> Destruct <':='> expr
189 | expr Blocky ( <#'\belse\b'> Blocky )?
190 | forrange = <#'\bfor\b'> Destruct <':=' #'\brange\b'> expr Blocky
191 | forlazy = <#'\bfor\b'> Destruct <':=' #'\blazy\b'> expr
192 | (<#'\bif\b'> expr )? Blocky
193 | fortimes = <#'\bfor\b'> Identifier <':=' #'\btimes\b'> expr Blocky
194 | forcstyle = <#'\bfor\b'> Identifier <':=' '0' ';'>
195 | Identifier <'<'> expr <';'>
196 | Identifier <'++'>
197 | Blocky
198 | tryexpr = <#'\btry\b'> ImpliedDo catches finally?
199 | catches = {catch}
200 | catch = <#'\bcatch\b'> typename Identifier ImpliedDo
201 | finally = <#'\bfinally\b'> ImpliedDo
202 | = unaryexpr (* TODO(eob) remove this indirection *)
203 | unaryexpr = unary_op unaryexpr
204 | | PrimaryExpr | javafield | ReaderMacro | prefixedblock
205 | = '+' | !'->' '-' | '!' | not | bitnot | take | takeingo
206 | bitnot = <'^'>
207 | not = <'!'>
208 | takeingo = <'<:'>
209 | take = <'<-'>
210 | = deref | syntaxquote | unquote | unquotesplicing
211 | deref = <'*'> UnaryExpr
212 | syntaxquote = <#'\bsyntax\b'> UnaryExpr
213 | unquote = <#'\bunquote\b'> UnaryExpr
214 | unquotesplicing = <#'\bunquotes\b'> UnaryExpr
215 | javafield = UnaryExpr <'->'> JavaIdentifier
216 | = Routine
217 | | prefixedroutine
218 | | chan
219 | | Operand
220 | | TypeDecl
221 | | implements
222 | | funclikedecl
223 | | indexed
224 | | dropslice
225 | | takeslice
226 | (* Conversion |
227 | BuiltinCall |
228 | PrimaryExpr Selector |
229 | PrimaryExpr Slice |
230 | PrimaryExpr TypeAssertion | *)
231 | prefixedroutine = prefix Routine
232 | prefixedblock = prefix ImpliedDo
233 | prefix = asyncprefix | #'\bdosync\b'
234 | asyncprefix = #'\bgo\b' | #'\bthread\b'
235 | threadblock = <#'\bthread\b'> ImpliedDo
236 | chan = <#'\bmake\b' '(' #'\bchan\b'> ( )? ( <','> expr)? <')'>
237 | = functioncall
238 | | MappedFunctionCall
239 | | variadiccall
240 | | typeconversion
241 | | javamethodcall
242 | typeconversion = primitivetype <'('> expr <')'>
243 | indexed = PrimaryExpr <'['> expr <']'>
244 | takeslice = PrimaryExpr <'[' ':'> expr <']'>
245 | dropslice = PrimaryExpr <'['> expr <':' ']'>
246 | variadiccall = PrimaryExpr
247 | <'('> ( ArgumentList <','> )? Ellipsis PrimaryExpr <')'>
248 | functioncall = PrimaryExpr Call
249 | = len
250 | len = <#'\blen\b'> Call
251 | javamethodcall = UnaryExpr <'->'> JavaIdentifier Call
252 | = <'('> ArgumentList? <')'>
253 | = expressionlist (* [ Ellipsis ] *)
254 | expressionlist = expr { <','> expr} ( <','>)?
255 | = <#'\btype\b'> ( TypeSpec | <'('> {TypeSpec} <')'> )
256 | = interfacespec | structspec
257 | structspec = JavaIdentifier <#'\bstruct\b' '{'> (fields )? <'}'>
258 | fields = Field
259 | | fields Field
260 | = Identifier | typedidentifiers
261 | interfacespec = JavaIdentifier <#'\binterface\b' '{'> {MethodSpec} <'}'>
262 | = voidmethodspec | typedmethodspec
263 | voidmethodspec = Identifier <'('> methodparameters? <')'>
264 | typedmethodspec = Identifier <'('> methodparameters? <')'> typename
265 | methodparameters = methodparam
266 | | methodparameters <','> methodparam
267 | methodparam = symbol ( Identifier)?
268 | implements = <#'\bimplements\b'> typename <#'\bfunc\b' '('> JavaIdentifier <')'> (
269 | MethodImpl | <'('> MethodImpl { MethodImpl} <')'>
270 | )
271 | = typedmethodimpl | untypedmethodimpl
272 | untypedmethodimpl = Identifier <'('> parameters? <')'>
273 | (ReturnBlock|Blocky)
274 | typedmethodimpl = Identifier <'('> parameters? <')'> typename
275 | (ReturnBlock|Blocky)
276 | functiondecl = <#'\bfunc\b'> (Identifier|operator) Function
277 | funclikedecl = <#'\bfunc\b' '<'> symbol <'>'> Identifier Function
278 | = FunctionPart | functionparts
279 | functionparts = FunctionPart FunctionPart { FunctionPart}
280 | = functionpart0 | functionpartn | vfunctionpart0 | vfunctionpartn
281 | functionpart0 = <'(' ')'> ( typename )? (ReturnBlock|Blocky)
282 | vfunctionpart0 = <'('> variadic <')'> ( typename )? (ReturnBlock|Blocky)
283 | functionpartn = <'('> parameters <')'> ( typename )? (ReturnBlock|Blocky)
284 | vfunctionpartn = <'('> parameters <','> variadic <')'> ( typename )?
285 | (ReturnBlock|Blocky)
286 | parameters = Destruct {<','> Destruct}
287 | variadic = Identifier Ellipsis
288 | = <'{' #'\breturn\b'> expr <'}'>
289 | = Literal | OperandName | label | islabel | new | <'('> expr <')'> (*|MethodExpr*)
290 | label = #'\b\p{Lu}[\p{Lu}_\p{Nd}#\.]*\b'
291 | islabel = <#'\bIS_'> #'\p{Lu}[\p{Lu}_\p{Nd}#\.]*\b'
292 | = BasicLit | veclit | dictlit | setlit | structlit | functionlit | shortfunctionlit
293 | functionlit = <#'\bfunc\b'> Function
294 | shortfunctionlit = <#'\bfunc\b' '{'> expr <'}'>
295 | = int_lit | bigintlit | regex | string_lit | rune_lit | floatlit | bigfloatlit (*| imaginary_lit *)
296 |
297 | (* http://stackoverflow.com/a/2509752/978525 *)
298 | floatlit = FloatLitA | FloatLitB
299 | = #'([0-9]+\.[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?'
300 | = #'[0-9]+[eE][+-]?[0-9]+'
301 |
302 | bigfloatlit = (floatlit | int_lit) #'M\b'
303 | = decimallit | octal_lit | hexlit
304 | decimallit = #'[1-9][0-9]*' | #'[0-9]'
305 | = #'0[0-7]+'
306 | hexlit = <'0x'> #'[0-9a-fA-F]+'
307 | bigintlit = int_lit #'N\b'
308 | regex = #'/([^\/\n\\]|\\.)+/'
309 | = interpretedstringlit | rawstringlit | clojureescape
310 | interpretedstringlit = #'["“”](?:[^"\\]|\\.)*["“”]'
311 | rawstringlit = <#'\x60'> #'[^\x60]*' <#'\x60'> (* \x60 is back quote character *)
312 | clojureescape = <'\\' #'\x60'> #'[^\x60]*' <#'\x60'> (* \x60 is back quote *)
313 | = <'\''> ( unicode_value | byte_value ) <'\''>
314 | = unicodechar | littleuvalue | escaped_char
315 | unicodechar = #'[^\n ]'
316 | = newlinechar | spacechar | backspacechar | returnchar | tabchar |
317 | backslashchar | squotechar| dquotechar
318 | newlinechar = <'\\n'>
319 | spacechar = <' '>
320 | backspacechar = <'\\b'>
321 | returnchar = <'\\r'>
322 | tabchar = <'\\t'>
323 | backslashchar = <'\\\\'>
324 | squotechar = <'\\\''>
325 | dquotechar = <'\\"'>
326 | = octalbytevalue (* | hex_byte_value *)
327 | octalbytevalue = <'\\'> octaldigit octaldigit octaldigit
328 | octaldigit = #'[0-7]'
329 | littleuvalue = <'\\u'> hexdigit hexdigit hexdigit hexdigit
330 | hexdigit = #'[0-9a-fA-F]'
331 | veclit = <'['> ( expr {<','> expr} )? <']'>
332 | | <'[' ']' typename '{'> ( expr { <','> expr } )? <'}'>
333 | dictlit = '{' ( dictelement {<','> dictelement} )? ( <','>)? '}'
334 | dictelement = expr <':'> expr
335 | NotType = #'\bfunc\b' | #'\bset\b' | prefix
336 | structlit = !NotType typename <'{'> ( expr {<','> expr} )? (<','> )? <'}'>
337 | setlit = <#'\bset\b' '{'> ( expr {<','> expr} )? <'}'>
338 | new = <#'\bnew\b'> typename
339 | = symbol | NonAlphaSymbol (*| QualifiedIdent*)
340 | = '=>' | '->>' | relop | addop | mulop | unary_op
341 | | percentnum | percentvaradic
342 | percentnum = <'$'> #'[1-9]'
343 | percentvaradic = <'$*'>
344 | = <'...'> | <'…'>
345 | `,
346 | AUTO_WHITESPACE, whitespaceOrComments, // STANDARD, //whitespace,
347 | NO_SLURP, true, // for App Engine compatibility
348 | )
349 |
--------------------------------------------------------------------------------
/src/funcgo/symboltable.go:
--------------------------------------------------------------------------------
1 | //////
2 | // This file is part of the Funcgo compiler.
3 | //
4 | // Copyright (c) 2014 Eamonn O'Brien-Strain All rights
5 | // reserved. This program and the accompanying materials are made
6 | // available under the terms of the Eclipse Public License v1.0 which
7 | // accompanies this distribution, and is available at
8 | // http://www.eclipse.org/legal/epl-v10.html
9 | //
10 | // Contributors:
11 | // Eamonn O'Brien-Strain e@obrain.com - initial author
12 | //////
13 |
14 | // A symbol table is a mutable state that keeps track of the symbols
15 | // declared, so that the codegenerator can throw exceptions when it
16 | // encounters an undefined symbol.
17 |
18 | package symboltable
19 | import "clojure/string"
20 | import type java.io.IOException
21 |
22 | // Return a new symbol table.
23 | func New() {
24 | ref({
25 | "long": TYPE,
26 | "double": TYPE,
27 | "boolean": TYPE,
28 | UNUSED_PACKAGES: set{},
29 | UNUSED_TYPES: set{}
30 | })
31 | }
32 |
33 | // Add a package symbol to the table.
34 | func PackageImported(st, pkg) {
35 | dosync(st alter func{
36 | $1 += {
37 | pkg: PACKAGE,
38 | UNUSED_PACKAGES: (*st)(UNUSED_PACKAGES) conj pkg
39 | }
40 | })
41 | }
42 |
43 | // Add a package symbol to the table, but don't require it to be used.
44 | func PackageCreated(st, pkg) {
45 | dosync(st alter func{$1 += {
46 | pkg: PACKAGE
47 | }})
48 | }
49 |
50 | // Add a package symbol to the table.
51 | func TypeImported(st, typ) {
52 | dosync(st alter func{$1 += {
53 | typ: TYPE,
54 | UNUSED_TYPES: (*st)(UNUSED_TYPES) conj typ
55 | }})
56 | //dosync{
57 | // st := $1 += {
58 | // typ: TYPE,
59 | // UNUSED_TYPES: (*st)(UNUSED_TYPES) conj typ
60 | // }
61 | //}
62 | }
63 |
64 | // Add a package symbol to the table.
65 | func TypeCreated(st, typ) {
66 | dosync(st alter func{$1 += {
67 | typ: TYPE
68 | }})
69 | //dosync{
70 | // st := $1 += {typ: TYPE}
71 | //}
72 | }
73 |
74 | // Has this package been previously been added to the table?
75 | func HasPackage(st, pkg) {
76 | dosync(st alter func{$1 += {
77 | UNUSED_PACKAGES: (*st)(UNUSED_PACKAGES) disj pkg
78 | }});
79 | (*st)(pkg) == PACKAGE
80 | }
81 |
82 | // Has this type been previously been added to the table?
83 | func HasType(st, typ) {
84 | dosync(st alter func{$1 += {
85 | UNUSED_TYPES: (*st)(UNUSED_TYPES) disj typ
86 | }});
87 | (*st)(typ) == TYPE
88 | }
89 |
90 | // Return a string representation of packages in the table.
91 | func Packages(st) {
92 | const packages = for [symbol, key] := lazy *st if key == PACKAGE { symbol }
93 | str("[", ", " string.join packages, "]")
94 | }
95 |
96 | // Return a string representation of types in the table.
97 | func Types(st) {
98 | const packages = for [symbol, key] := lazy *st if key == TYPE { symbol }
99 | str("[", ", " string.join packages, "]")
100 | }
101 |
102 | func CheckAllUsed(st) {
103 | const (
104 | pkgs = (*st)(UNUSED_PACKAGES)
105 | typs = (*st)(UNUSED_TYPES)
106 | )
107 | if notEmpty(pkgs) {
108 | const pkgsS = ", " string.join pkgs
109 | throw(new IOException(str("Packages imported but never used: [", pkgsS, "]")))
110 | }
111 | if notEmpty(typs) {
112 | const typsS = ", " string.join typs
113 | throw(new IOException(str("Types imported but never used: [", typsS, "]")))
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/tasks/leiningen/fgoc.clj:
--------------------------------------------------------------------------------
1 | ;; Compiled from tasks/leiningen/fgoc.go
2 | (ns leiningen.fgoc (:gen-class) (:require [clojure.java.shell :as sh]))
3 |
4 | (set! *warn-on-reflection* true)
5 |
6 | (defn-
7 | fgoc
8 | [project & args]
9 | (let
10 | [cmd-line
11 | (concat
12 | ["java"
13 | "-jar"
14 | "bin/funcgo-compiler-0.5.1-standalone.jar"
15 | "src"
16 | "test"
17 | "tasks"]
18 | args)
19 | result
20 | (apply sh/sh cmd-line)]
21 | (println (result :err))
22 | (println (result :out))
23 | (if
24 | (= (result :exit) 0)
25 | (println "Compile finished")
26 | (println "ERROR"))))
27 |
28 |
--------------------------------------------------------------------------------
/tasks/leiningen/fgoc.go:
--------------------------------------------------------------------------------
1 | package fgoc
2 | import sh "clojure/java/shell"
3 |
4 | func fgoc(project, args...) {
5 | cmdLine := [
6 | "java", "-jar", "bin/funcgo-compiler-0.5.1-standalone.jar",
7 | "src", "test", "tasks"
8 | ] concat args
9 | result := sh.sh apply cmdLine
10 |
11 | println(result(ERR))
12 | println(result(OUT))
13 | if result(EXIT) == 0 {
14 | println("Compile finished")
15 | } else {
16 | println("ERROR")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test-cljs/index.hl.gos:
--------------------------------------------------------------------------------
1 | // -*- mode: go -*-
2 |
3 | page("index.html")
4 |
5 | html(
6 | head(
7 | link(REL, "stylesheet", TYPE, "text/css", HREF, "css/main.css")
8 | ),
9 | body(
10 | h1("Hello, World!")
11 | )
12 | )
13 |
--------------------------------------------------------------------------------
/test-cljs/mainupload.gos:
--------------------------------------------------------------------------------
1 | // -*- mode: Go -*-
2 |
3 | package mainupload
4 |
5 | import macros (
6 | em "enfocus/macros"
7 | )
8 |
9 | em.defaction(
10 | updateSadText,
11 | [],
12 | ["#inputtext"],
13 | em.content("Yay! I'm happy!")
14 | )
15 |
16 | em.defaction(
17 | makeHappyFace,
18 | [],
19 | ["#inputrow"],
20 | em.htmlContent("
")
21 | )
22 |
--------------------------------------------------------------------------------
/test/funcgo/async.go:
--------------------------------------------------------------------------------
1 | package async
2 | import (
3 | test "midje/sweet"
4 | "clojure/tools/logging"
5 | "cljLoggingConfig/log4j"
6 | )
7 | log4j.mutateSetLogger(LEVEL, WARN)
8 |
9 |
10 | func printAfterDelay(s) {
11 | Thread::sleep(100)
12 | print(s)
13 | }
14 |
15 | test.fact("can use goroutines to execute code in parallel",
16 |
17 | // no parallelism
18 | withOutStr(printAfterDelay("foo")), =>, "foo",
19 |
20 | // don't wait for results
21 | withOutStr({
22 | go printAfterDelay("bar")
23 | }), =>, "",
24 |
25 | // wait for result
26 | withOutStr({
27 | go printAfterDelay("baz")
28 | Thread::sleep(200)
29 | }), =>, "baz"
30 | )
31 |
32 | func sum(x1, x2, c) {
33 | c <- x1 + x2
34 | }
35 |
36 | func primes(c) {
37 | c <- 2
38 | c <- 3
39 | c <- 5
40 | c <- 7
41 | c <- 11
42 | }
43 |
44 | test.fact("can read and write channels in parallel",
45 |
46 | {
47 | c := make(chan)
48 | go sum(3, 4, c)
49 | <-c
50 | }, =>, 7,
51 |
52 | {
53 | c := make(chan)
54 | go primes(c)
55 | [<-c, <-c, <-c, <-c]
56 | }, =>, [2, 3, 5, 7],
57 |
58 | {
59 | c := make(chan)
60 | go primes(c)
61 | {
62 | c2 := make(chan)
63 | go func{
64 | c2 <- [<-c, <-c, <-c, <-c]
65 | }()
66 | <-c2
67 | }
68 | }, =>, [2, 3, 5, 7]
69 |
70 | )
71 |
72 | test.fact("can read and write channels in parallel using buffered channels",
73 |
74 | {
75 | c := make(chan, 10)
76 | go sum(3, 4, c)
77 | <-c
78 | }, =>, 7,
79 |
80 | {
81 | c := make(chan, 10)
82 | go primes(c)
83 | [<-c, <-c, <-c, <-c]
84 | }, =>, [2, 3, 5, 7],
85 |
86 | {
87 | c := make(chan, 10)
88 | go primes(c)
89 | {
90 | c2 := make(chan, 10)
91 | go func{
92 | c2 <- [<-c, <-c, <-c, <-c]
93 | }()
94 | <-c2
95 | }
96 | }, =>, [2, 3, 5, 7]
97 |
98 | )
99 |
100 | test.fact("can read and write channels in parallel using lightweight processes",
101 |
102 | {
103 | c := make(chan, 10)
104 | go { c <: 3 + 4 }
105 | <-c
106 | }, =>, 7,
107 |
108 | {
109 | c := make(chan, 10)
110 | go {
111 | c <: 2
112 | c <: 3
113 | c <: 5
114 | c <: 7
115 | c <: 11
116 | }
117 | [<-c, <-c, <-c, <-c]
118 | }, =>, [2, 3, 5, 7],
119 |
120 | {
121 | c := make(chan, 10)
122 | go primes(c)
123 | {
124 | c2 := make(chan, 10)
125 | go {
126 | c2 <: [<:c, <:c, <:c, <:c]
127 | }
128 | <-c2
129 | }
130 | }, =>, [2, 3, 5, 7]
131 |
132 | )
133 |
134 | func fibonacci(c, quit) {
135 | loop(x=0, y=1){
136 | select {
137 | case c <- x:
138 | recur(y, x + y)
139 | case <-quit:
140 | println("quit")
141 | }
142 | }
143 | }
144 |
145 |
146 |
147 | test.fact("can use select to block on multiple things",
148 |
149 | withOutStr({
150 | c := make(chan int)
151 | quit := make(chan int)
152 | go func() {
153 | for i := 0; i < 10; i++ {
154 | println(<-c)
155 | }
156 | quit <- 0
157 | }()
158 | fibonacci(c, quit)
159 | }), =>, `0
160 | 1
161 | 1
162 | 2
163 | 3
164 | 5
165 | 8
166 | 13
167 | 21
168 | 34
169 | quit
170 | `,
171 |
172 | withOutStr({
173 | c := make(chan int)
174 | quit := make(chan int)
175 | go {
176 | for i := 0; i < 10; i++ {
177 | println(<:c)
178 | }
179 | quit <: 0
180 | }
181 | fibonacci(c, quit)
182 | }), =>, `0
183 | 1
184 | 1
185 | 2
186 | 3
187 | 5
188 | 8
189 | 13
190 | 21
191 | 34
192 | quit
193 | `
194 | )
195 |
196 | func goFibonacci(c, quit) {
197 | loop(x=0, y=1){
198 | select {
199 | case c <: x:
200 | recur(y, x + y)
201 | case <:quit:
202 | println("quit")
203 | }
204 | }
205 | }
206 |
207 | func elapsedTimeMs(f) {
208 | start := System::currentTimeMillis()
209 | f()
210 | System::currentTimeMillis() - start
211 | }
212 |
213 | func doSomeWork() {
214 | var r = 10
215 | }
216 |
217 | test.fact("go blocks are more lightweight than thread blocks",
218 |
219 | {
220 | n := 10000
221 | threadMs := elapsedTimeMs(func(){
222 | c := make(chan, n)
223 | for _ := times n {
224 | thread {
225 | doSomeWork()
226 | c <- true
227 | }
228 | }
229 | for _ := times n { <-c }
230 | })
231 | goMs := elapsedTimeMs(func(){
232 | c := make(chan, n)
233 | for _ := times n {
234 | go {
235 | doSomeWork()
236 | c <- true
237 | }
238 | }
239 | for _ := times n { <-c }
240 | })
241 | logging.infof("thread: %dms -- go: %dms", threadMs, goMs)
242 | goMs < threadMs
243 | }, =>, true
244 |
245 | )
246 |
--------------------------------------------------------------------------------
/test/funcgo/clojure_cookbook_test.go:
--------------------------------------------------------------------------------
1 | package clojure_cookbook_test
2 | import(
3 | test "midje/sweet"
4 | "clojure/string"
5 | inf "inflections/core"
6 | )
7 | import extern(
8 | produce
9 | bakery
10 | )
11 |
12 | test.fact("Simple example",
13 | {
14 | func add(x, y) {
15 | x + y
16 | }
17 | add(1, 2)
18 | },
19 | =>, 3
20 | )
21 |
22 | test.fact("More complex example",
23 | into([], range(1, 20)),
24 | =>, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
25 | )
26 |
27 | test.fact("Any function of two arguments can be written infix",
28 | [] into range(1, 20),
29 | =>, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
30 | )
31 |
32 | test.fact("Infix is most convenient for math operators.",
33 | 1 + 2,
34 | =>, 3
35 | )
36 |
37 | test.fact("Dotted identifers are from other packages.",
38 | // import section includes
39 | // "clojure/string"
40 | string.isBlank(""),
41 | =>, true
42 | )
43 |
44 | test.fact("Capitalize first character in a string.",
45 | string.capitalize("this is a proper sentence."),
46 | =>, "This is a proper sentence."
47 | )
48 |
49 | test.fact("Capitalize or lower-case all characters.",
50 |
51 | string.upperCase("loud noises!"),
52 | =>, "LOUD NOISES!",
53 |
54 | string.lowerCase("COLUMN_HEADER_ONE"),
55 | =>, "column_header_one",
56 |
57 | string.lowerCase("!&$#@#%^[]"),
58 | =>, "!&$#@#%^[]",
59 |
60 | string.upperCase("Dépêchez-vous, l'ordinateur!"),
61 | =>, "DÉPÊCHEZ-VOUS, L'ORDINATEUR!"
62 | )
63 |
64 | test.fact("Remove whitespace at beginning and end.",
65 | string.trim(" \tBacon ipsum dolor sit.\n"),
66 | =>, "Bacon ipsum dolor sit."
67 | )
68 |
69 | test.fact("Collapse whitespace into single whitespace",
70 | string.replace("Who\t\nput all this\fwhitespace here?", /\s+/, " "),
71 | =>, "Who put all this whitespace here?"
72 | )
73 |
74 | test.fact("Windows to Unix line-endings",
75 | string.replace("Line 1\r\nLine 2", "\r\n", "\n"),
76 | =>, "Line 1\nLine 2"
77 | )
78 |
79 | test.fact("Trim only from one end",
80 | string.triml(" Column Header\t"),
81 | =>, "Column Header\t",
82 |
83 | string.trimr("\t\t* Second-level bullet.\n"),
84 | =>, "\t\t* Second-level bullet."
85 | )
86 |
87 | test.fact("Concatenate strings",
88 | str("John", " ", "Doe"),
89 | =>, "John Doe"
90 | )
91 |
92 | test.fact("Can concatenate consts.",
93 | {
94 | firstName, lastName, age := "John", "Doe", 42
95 | str(lastName, ", ", firstName, " - age: ", age)
96 | }, =>, "Doe, John - age: 42"
97 | )
98 |
99 | test.fact("Can concatenate vars.",
100 | {
101 | var firstName = "John"
102 | var lastName = "Doe"
103 | var age = 42
104 | str(lastName, ", ", firstName, " - age: ", age)
105 | },
106 | =>, "Doe, John - age: 42"
107 | )
108 |
109 | test.fact("turn characters into a string",
110 | apply(str, "ROT13: ", ['W', 'h', 'y', 'v', 'h', 'f', ' ', 'P', 'n', 'r', 'f', 'n', 'e']),
111 | =>, "ROT13: Whyvhf Pnrfne"
112 | )
113 |
114 | test.fact("make file from lines (with newlines)",
115 | {
116 | lines := [
117 | "#! /bin/bash\n",
118 | "du -a ./ | sort -n -r\n"
119 | ]
120 | str(...lines)
121 | },
122 | =>, "#! /bin/bash\ndu -a ./ | sort -n -r\n"
123 | )
124 |
125 | test.fact("Making CSV from header vector of rows",
126 | {
127 | header := "first_name,last_name,employee_number\n"
128 | rows := [
129 | "luke,vanderhart,1",
130 | "ryan,neufeld,2"
131 | ]
132 | str(header, ...("\n" interpose rows))
133 | },
134 | =>, `first_name,last_name,employee_number
135 | luke,vanderhart,1
136 | ryan,neufeld,2`
137 | )
138 |
139 |
140 | test.fact("Join can be easier",
141 | {
142 | var foodItems = ["milk", "butter", "flour", "eggs"]
143 | string.join(", ", foodItems)
144 | },
145 | =>, "milk, butter, flour, eggs",
146 |
147 | ", " string.join foodItems,
148 | =>, "milk, butter, flour, eggs",
149 |
150 | string.join( [1, 2, 3, 4] ),
151 | =>, "1234"
152 | )
153 |
154 | test.fact("seq() exposes the characters in a string",
155 | seq("Hello, world!"),
156 | =>, ['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!']
157 | )
158 |
159 | func isYelling(s String) {
160 | isEvery(
161 | func(ch Character) { !Character::isLetter(ch) || Character::isUpperCase(ch) },
162 | s
163 | )
164 | }
165 |
166 | test.fact("Function taking a sequence will cooerce a string into a set of chars",
167 |
168 | // count of chars in string
169 | frequencies(string.lowerCase("An adult all about A's")),
170 | =>, {' ':4, 'a':5, 'b':1, 'd':1, '\'':1, 'l':3, 'n':1, 'o':1, 's':1, 't':2, 'u':2},
171 |
172 | // every letter capitalized?
173 | isYelling("LOUD NOISES!"),
174 | =>, true,
175 |
176 | isYelling("Take a DEEP breath."),
177 | =>, false
178 | )
179 |
180 |
181 | test.fact("Can transform characters back into a string",
182 | str(...['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!']),
183 | =>, "Hello, world!"
184 | )
185 |
186 | test.fact("int function converts characters to integers",
187 |
188 | int('a'),
189 | =>, 97,
190 |
191 | int('ø'),
192 | =>, 248,
193 |
194 | int('α'), // Greek letter alpha
195 | =>, 945,
196 |
197 | int('\u03B1'), // Greek letter alpha (by code point)
198 | =>, 945,
199 |
200 | int map "Hello, world!",
201 | =>, [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
202 | )
203 |
204 | test.fact("char function does the opposite",
205 | char(97),
206 | =>, 'a',
207 |
208 | char(125),
209 | =>, '}',
210 |
211 | char(945),
212 | =>, 'α',
213 |
214 | reduce(
215 | func(acc, i){str(acc, char(i))},
216 | "",
217 | [115, 101, 99, 114, 101, 116, 32, 109, 101, 115, 115, 97, 103, 101, 115]
218 | ),
219 | =>, "secret messages"
220 | )
221 |
222 |
223 | test.fact("str is the easiest way of formatting values into a string",
224 |
225 | {
226 | me := {FIRST_NAME: "Eamonn", FAVORITE_LANGUAGE: "Funcgo"}
227 | str("My name is ", me(FIRST_NAME),
228 | ", and I really like to program in ", me(FAVORITE_LANGUAGE))
229 | }, =>, "My name is Eamonn, and I really like to program in Funcgo",
230 |
231 | str(...(" " interpose [1, 2.000, 3/1, 4/9])),
232 | =>, "1 2.0 3 4/9"
233 | )
234 |
235 |
236 |
237 | test.fact("format is another way of constructing strings",
238 | {
239 | // Produce a filename with a zero-padded sortable index
240 | func filename(name, i) {
241 | format("%03d-%s", i, name)
242 | }
243 | "my-awesome-file.txt" filename 42
244 | },
245 | =>, "042-my-awesome-file.txt",
246 |
247 | "%07.3f" format 0.005,
248 | =>, "000.005"
249 | )
250 |
251 | // Create a table using justification
252 | func tableify(row) {
253 | format("%-20s | %-20s | %-20s", ...row)
254 | }
255 |
256 | var header = ["First Name", "Last Name", "Employee ID"]
257 | var employees = [
258 | ["Ryan", "Neufeld", 2],
259 | ["Luke", "Vanderhart", 1]
260 | ]
261 |
262 | test.fact("formatting",
263 | withOutStr(
264 | println mapv (tableify map ([header] concat employees))
265 | ),
266 | =>, `First Name | Last Name | Employee ID
267 | Ryan | Neufeld | 2
268 | Luke | Vanderhart | 1
269 | `,
270 |
271 | withOutStr(
272 | ->>(
273 | [header] concat employees,
274 | map(tableify),
275 | mapv(println)
276 | )
277 | ),
278 | =>, `First Name | Last Name | Employee ID
279 | Ryan | Neufeld | 2
280 | Luke | Vanderhart | 1
281 | `
282 | )
283 |
284 | test.fact("Regular expressions, using reFind",
285 |
286 | /\d+/ reFind "I've just finished reading Fahrenheit 451",
287 | =>, "451",
288 |
289 | /Bees/ reFind "Beads aren't cheap.",
290 | =>, nil
291 | )
292 |
293 | test.fact("To match only the whole string use reMatches",
294 |
295 | /\w+/ reFind "my-param",
296 | =>, "my",
297 |
298 | /\w+/ reMatches "my-param",
299 | =>, nil,
300 |
301 | /\w+/ reMatches "justLetters",
302 | =>, "justLetters"
303 | )
304 |
305 | test.fact("Extract strings from a larger string using reSeq",
306 |
307 | /\w+/ reSeq "My Favorite Things",
308 | =>, ["My", "Favorite", "Things"],
309 |
310 | /\d{3}-\d{4}/ reSeq "My phone number is 555-1234.",
311 | =>, ["555-1234"],
312 |
313 | {
314 | // Extract Twitter identifiers in a tweet
315 | func mentions(tweet) {
316 | /(@|#)(\w+)/ reSeq tweet
317 | }
318 |
319 | mentions("So long, @earth, and thanks for all the #fish. #goodbyes")
320 | },
321 | =>, [["@earth", "@", "earth"], ["#fish", "#", "fish"], ["#goodbyes", "#", "goodbyes"]],
322 |
323 | {
324 | // Capture and decompose a phone number and its title
325 | rePhoneNumber := /(\w+): \((\d{3})\) (\d{3}-\d{4})/
326 | rePhoneNumber reSeq "Home: (919) 555-1234, Work: (191) 555-1234"
327 | },
328 | =>, [["Home: (919) 555-1234", "Home", "919", "555-1234"],
329 | ["Work: (191) 555-1234", "Work", "191", "555-1234"]]
330 |
331 | )
332 |
333 |
334 | test.fact("simple string replacement via string.replace",
335 | {
336 | aboutMe := "My favorite color is green!"
337 | string.replace(aboutMe, "green", "red")
338 | },
339 | =>, "My favorite color is red!",
340 | {
341 | func deCanadianize(s) {
342 | string.replace(s, "ou", "o")
343 | }
344 | deCanadianize(str(
345 | "Those Canadian neighbours have coloured behaviour",
346 | " when it comes to word endings"))
347 | },
348 | =>, "Those Canadian neighbors have colored behavior when it comes to word endings"
349 | )
350 |
351 | test.fact("More complex string replacement requires regular expressions",
352 | {
353 | // Add Markdown-style links for any GitHub issue numbers present in comment
354 | func linkifyComment(repo, comment) {
355 | string.replace(
356 | comment,
357 | /#(\d+)/,
358 | str("[#$1](https://github.com/", repo, "/issues/$1)")
359 | )
360 | }
361 | linkifyComment(
362 | "next/big-thing",
363 | "As soon as we fix #42 and #1337 we should be set to release!"
364 | )
365 | },
366 | =>, "As soon as we fix [#42](https://github.com/next/big-thing/issues/42) and [#1337](https://github.com/next/big-thing/issues/1337) we should be set to release!"
367 | )
368 |
369 |
370 | test.fact("Use string.split to split strings",
371 |
372 | "HEADER1,HEADER2,HEADER3" string.split /,/,
373 | =>, ["HEADER1", "HEADER2", "HEADER3"],
374 |
375 | "Spaces Newlines\n\n" string.split /\s+/,
376 | =>, ["Spaces", "Newlines"],
377 |
378 | // whitespace splitting with implicit trim
379 | "field1 field2 field3 " string.split /\s+/,
380 | =>, ["field1", "field2", "field3"],
381 |
382 | // avoid implicit trimming by adding limit of -1
383 |
384 | string.split("ryan,neufeld,", /,/, -1),
385 | =>, ["ryan", "neufeld", ""],
386 |
387 | {
388 | var dataDelimiters = /[ :-]/
389 |
390 | //No-limit split on any delimiter
391 | "2013-04-05 14:39" string.split dataDelimiters
392 | },
393 | =>, ["2013", "04", "05", "14", "39"],
394 |
395 | // Limit of 1 - functionally: return this string in a collection
396 | string.split("2013-04-05 14:39", dataDelimiters, 1),
397 | =>, ["2013-04-05 14:39"],
398 |
399 | // Limit of 2
400 | string.split("2013-04-05 14:39", dataDelimiters, 2),
401 | =>, ["2013", "04-05 14:39"],
402 |
403 | // Limit of 100
404 | string.split("2013-04-05 14:39", dataDelimiters, 100),
405 | =>, ["2013", "04", "05", "14", "39"]
406 | )
407 |
408 |
409 | // The following requires the following in leinigen dependencies
410 | // [inflections "0.9.5"]
411 | test.fact("can use inf.pluralize to with word labelling counts",
412 | // In import have
413 | // inf "inflections/core"
414 |
415 | 1 inf.pluralize "monkey",
416 | =>, "1 monkey",
417 |
418 | 12 inf.pluralize "monkey",
419 | =>, "12 monkeys",
420 |
421 | // Can provide non-standard pluralization as an arg
422 |
423 | inf.pluralize(1, "box", "boxen"),
424 | =>, "1 box",
425 |
426 | inf.pluralize(3, "box", "boxen"),
427 | =>, "3 boxen",
428 |
429 | // Or you can add your own rules
430 | inf.plural("box"),
431 | =>, "boxes",
432 |
433 | {
434 | // Words ending in 'ox' pluralize with 'en' (and not 'es')
435 | /(ox)(?i)$/ inf.mutatePlural "$1en"
436 |
437 | inf.plural("box")
438 | },
439 | =>, "boxen",
440 |
441 | // plural is also the basis for pluralize...
442 | 2 inf.pluralize "box",
443 | =>, "2 boxen",
444 |
445 | // Convert "snake_case" to "CamelCase"
446 | inf.camelize("my_object"),
447 | =>, "MyObject",
448 |
449 | // Clean strings for usage as URL parameters
450 | inf.parameterize("My most favorite URL!"),
451 | =>, "my-most-favorite-url",
452 |
453 | // Turn numbers into ordinal numbers
454 | inf.ordinalize(42),
455 | =>, "42nd"
456 | )
457 |
458 | test.fact("Can convert between different types of language things (note Funcgo mangling).",
459 |
460 | symbol("valid?"),
461 | =>, quote(isValid),
462 |
463 | str(quote(isValid)),
464 | =>, "valid?",
465 |
466 | name(TRIUMPH),
467 | =>, "triumph",
468 |
469 | str(TRIUMPH),
470 | =>, ":triumph",
471 |
472 | keyword("fantastic"),
473 | =>, FANTASTIC,
474 |
475 | keyword(quote(fantastic)),
476 | =>, FANTASTIC,
477 |
478 | symbol(name(WONDERFUL)),
479 | =>, quote(wonderful),
480 |
481 | // If you only want the name part of a keyword.
482 | // (We have to escape into Clojure for this.)
483 | name( \:user/valid?\ ),
484 | =>, "valid?",
485 |
486 | // If you only want the namespace
487 | namespace( \:user/valid?\ ),
488 | =>, "user",
489 |
490 | str( \:user/valid?\ ),
491 | =>, ":user/valid?",
492 |
493 | str( \:user/valid?\ )->substring(1),
494 | =>, "user/valid?",
495 |
496 | keyword(quote(produce.onions)),
497 | =>, \:produce/onions\,
498 |
499 | symbol(str( \:produce/onions\ )->substring(1)),
500 | =>, quote(produce.onions),
501 |
502 | // keyword and symbol also have 2-argument (infix) versions
503 | {
504 | var shoppingArea = "bakery"
505 | shoppingArea keyword "bagels"
506 | },
507 | =>, \:bakery/bagels\,
508 |
509 | shoppingArea symbol "cakes",
510 | =>, quote(bakery.cakes)
511 | )
512 |
513 | test.fact("Funcgo has numbers",
514 |
515 | // Avogadro's number
516 | 6.0221413e23,
517 | =>, 6.0221413E23,
518 |
519 | // 1 Angstrom in meters
520 | 1e-10,
521 | =>, 1.0E-10,
522 |
523 | // Size-bounded integers can overflow
524 | try {
525 | 9999 * 9999 * 9999 * 9999 * 9999
526 | } catch ArithmeticException e {
527 | e->getMessage
528 | },
529 | =>, "integer overflow",
530 |
531 | // which you can avoid using Big integers
532 | 9999N * 9999 * 9999 * 9999 * 9999,
533 | =>, 99950009999000049999N,
534 |
535 | 2 * Double::MAX_VALUE,
536 | =>, Double::POSITIVE_INFINITY,
537 |
538 | 2 * bigdec(Double::MAX_VALUE),
539 | =>, 3.5953862697246314E+308M,
540 |
541 | // Result of integer division is a ratio type
542 | type(1 / 3),
543 | =>, \clojure.lang.Ratio\,
544 |
545 | 3 * (1 / 3),
546 | =>, 1N,
547 |
548 | (1 / 3) + 0.3,
549 | =>, 0.6333333333333333,
550 | // Avoid losing precision
551 | rationalize(0.3),
552 | =>, 3/10,
553 |
554 | (1 / 3) + rationalize(0.3),
555 | =>, 19/30
556 | )
557 |
558 | test.fact("Can parse numbers from strings.",
559 |
560 | Integer::parseInt("-42"),
561 | =>, -42,
562 |
563 | Double::parseDouble("3.14"),
564 | =>, 3.14,
565 |
566 | bigdec("3.141592653589793238462643383279502884197"),
567 | =>, 3.141592653589793238462643383279502884197M,
568 |
569 | bigint("122333444455555666666777777788888888999999999"),
570 | =>, 122333444455555666666777777788888888999999999N
571 |
572 | )
573 |
574 | test.fact("Can coerce numbers.",
575 |
576 | int(2.0001),
577 | =>, 2,
578 |
579 | int(2.999999999),
580 | =>, 2,
581 |
582 | Math::round(2.0001),
583 | =>, 2,
584 |
585 | Math::round(2.999),
586 | =>, 3,
587 |
588 | int(2.99 + 0.5),
589 | =>, 3,
590 |
591 | Math::ceil(2.0001),
592 | =>, 3.0,
593 |
594 | Math::floor(2.999),
595 | =>, 2.0,
596 |
597 | 3 withPrecision (7M / 9),
598 | =>, 0.778M,
599 |
600 | 1 withPrecision (7M / 9),
601 | =>, 0.8M,
602 |
603 | withPrecision(1, ROUNDING, \FLOOR\, (7M / 9)),
604 | =>, 0.7M,
605 |
606 | // note non-big arithmetic not effected by withPrecision
607 | 3 withPrecision (1 / 3),
608 | =>, 1/3,
609 |
610 | 3 withPrecision (bigdec(1) / 3),
611 | =>, 0.333M
612 | )
613 |
614 | test.fact("Easy to implement fuzzy equality",
615 |
616 | {
617 | func fuzzyEq(tolerance, x double, y double) {
618 | diff := Math::abs(x - y)
619 | diff < tolerance
620 | }
621 | fuzzyEq(0.01, 10, 10.000000000001)
622 | },
623 | =>, true,
624 |
625 | fuzzyEq(0.01, 10, 10.1),
626 | =>, false,
627 |
628 | 0.22 - 0.23,
629 | =>, -0.010000000000000009,
630 |
631 | 0.23 - 0.24,
632 | =>, -0.009999999999999981,
633 |
634 | {
635 | var isEqualWithinTen = partial(fuzzyEq, 10)
636 | 100 isEqualWithinTen 109
637 | },
638 | =>, true,
639 |
640 | 100 isEqualWithinTen 110,
641 | =>, false
642 | )
643 |
644 | test.fact("Can sort with fuzzy equality",
645 | {
646 | func fuzzyComparator(tolerance) {
647 | func(x, y) {
648 | if fuzzyEq(tolerance, x, y) {
649 | 0
650 | } else {
651 | x compare y
652 | }
653 | }
654 | }
655 | fuzzyComparator(10) sort [100, 11, 150, 10, 9]
656 | },
657 | =>, [11, 10, 9, 100, 150] // 100 and 150 have moved, but not 11, 10, and 9
658 | )
659 |
660 |
661 | test.fact("Can do trig",
662 |
663 | {
664 | // Calculating sin(a + b). The formula for this is
665 | // sin(a + b) = sin a * cos b + sin b cos a
666 | func sinPlus(a, b) {
667 | Math::sin(a) * Math::cos(b) + Math::sin(b) * Math::cos(a)
668 | }
669 | sinPlus(0.1, 0.3)
670 | },
671 | =>, 0.38941834230865047,
672 |
673 | {
674 | // Calculating the distance in kilometers between two points on Earth
675 | earthRadius := 6371.009
676 |
677 | func degreesToRadians(point) {
678 | func(x){Math::toRadians(x)} mapv point
679 | }
680 |
681 | // Calculate the distance in km between two points on Earth. Each
682 | // point is a pair of degrees latitude and longitude, in that order.
683 | func distanceBetween(p1, p2) {
684 | distanceBetween(p1, p2, earthRadius)
685 | } (p1, p2, radius) {
686 | [lat1, long1] := degreesToRadians(p1)
687 | [lat2, long2] := degreesToRadians(p2)
688 | radius * Math::acos(
689 | Math::sin(lat1) * Math::sin(lat2) +
690 | Math::cos(lat1) * Math::cos(lat2) * Math::cos(long1 - long2)
691 | )
692 | }
693 |
694 | [49.2000, -98.1000] distanceBetween [35.9939, -78.8989]
695 | },
696 | =>, 2139.42827188432
697 | )
698 |
699 |
700 | test.fact("Can convert numbers to various bases",
701 |
702 | 13 Integer::toString 2,
703 | =>, "1101",
704 |
705 | 42 Integer::toString 16,
706 | =>, "2a",
707 |
708 | 35 Integer::toString 36,
709 | =>, "z",
710 |
711 | {
712 | func toBase(radix, n) { n Integer::toString radix }
713 | {
714 | baseTwo := toBase partial 2
715 | baseTwo(9001)
716 | }
717 | },
718 | =>, "10001100101001"
719 | )
720 |
721 | test.fact("Doing statistics",
722 |
723 | {
724 | func mean(coll) {
725 | sum := +(...coll)
726 | count := count(coll)
727 | if isPos(count) {
728 | sum / count
729 | } else {
730 | 0
731 | }
732 | }
733 | mean([1, 2, 3, 4])
734 | },
735 | =>, 5/2,
736 |
737 | mean([1, 1.6, 7.4, 10]),
738 | =>, 5.0,
739 |
740 | mean([]),
741 | =>, 0,
742 |
743 | {
744 | func median(coll) {
745 | sorted := sort(coll)
746 | cnt := count(sorted)
747 | halfway := int(cnt / 2)
748 | if isOdd(cnt) {
749 | sorted[halfway]
750 | } else {
751 | bottom := halfway - 1
752 | bottomVal := sorted[bottom]
753 | topVal := sorted[halfway]
754 | mean([bottomVal, topVal])
755 | }
756 | }
757 | median([5, 2, 4, 1, 3])
758 | },
759 | =>, 3,
760 |
761 | median([7, 0, 2, 3]),
762 | =>, 5/2 // The average of 2 and 3.
763 | )
764 |
--------------------------------------------------------------------------------
/test/funcgo/compiler_test.go:
--------------------------------------------------------------------------------
1 | package compiler_test
2 | import (
3 | test "midje/sweet"
4 | fgo "funcgo/core"
5 | fgoc "funcgo/main"
6 | "clojure/string"
7 | )
8 | //import type (
9 | // java.math.BigInteger
10 | //)
11 |
12 | var requireAsync = `[clojure.core.async :as async :refer [chan go thread ! alt! !! alt!!]]`
13 | var requireJsAsync = `(:require-macros [cljs.core.async.macros :as async :refer [go]]) (:require [cljs.core.async :as async :refer [chan ! alt!]])`
14 |
15 | func compileString(path, fgoText) {
16 | string.trim(
17 | string.replace(
18 | fgoc.CompileString(path, fgoText),
19 | /\s+/,
20 | " "
21 | )
22 | )
23 | }
24 |
25 |
26 | test.fact("smallest complete program has no import and a single expression",
27 | compileString("foo.go", "package foo;12345"),
28 | =>,
29 | `(ns foo (:gen-class)) (set! *warn-on-reflection* true) 12345`)
30 |
31 | test.fact("Can use newlines instead of semicolons",
32 | compileString("foo.go", `
33 | package foo
34 | 12345
35 | `),
36 | =>,
37 | `(ns foo (:gen-class)) (set! *warn-on-reflection* true) 12345`)
38 |
39 | test.fact("package can be dotted",
40 | compileString("foo/bar.go", "package bar;12345"),
41 | => ,
42 | `(ns foo.bar (:gen-class)) (set! *warn-on-reflection* true) 12345`,
43 |
44 | compileString("yippee/yaday/yahoo/boys.go", "package boys;12345"),
45 | => ,
46 | `(ns yippee.yaday.yahoo.boys (:gen-class)) (set! *warn-on-reflection* true) 12345`
47 | )
48 |
49 | test.fact("can import other packages",
50 | compileString("foo.go", `
51 | package foo
52 | import(
53 | b "bar"
54 | )
55 | b.xxx
56 | `),
57 | =>,
58 | str(
59 | `(ns foo (:gen-class) (:require [bar :as b])) (set! *warn-on-reflection* true) b/xxx`
60 | )
61 | )
62 |
63 |
64 | test.fact("can import packages for side effect",
65 | compileString("foo.go", `
66 | package foo
67 | import(
68 | b "bar"
69 | _ "foo"
70 | )
71 | b.xxx
72 | `),
73 | =>, str(
74 | `(ns foo (:gen-class) (:require [bar :as b] [foo])) (set! *warn-on-reflection* true) b/xxx`
75 | )
76 | )
77 |
78 |
79 | test.fact("can exclude built-ins",
80 | compileString("foo.go", `
81 | package foo
82 | exclude ( +, * )
83 | a
84 | `),
85 | =>, str(
86 | `(ns foo (:gen-class) (:refer-clojure :exclude [+ *])) (set! *warn-on-reflection* true) a`
87 | )
88 | )
89 |
90 |
91 | func parse(expr) {
92 | parse(expr, [], [])
93 | } (expr, pkgs) {
94 | parse(expr, list(pkgs), [])
95 | } (expr, pkgs, types) {
96 | imports := if count(pkgs) == 0 {
97 | ""
98 | } else {
99 | lines := for p := lazy pkgs {str(`"`, p, `"`)}
100 | str("import(\n", "\n" string.join lines, "\n)\n")
101 | }
102 | importtypes := if count(types) == 0 {
103 | ""
104 | } else {
105 | str("import type(\n", "\n" string.join types, "\n)\n")
106 | }
107 | compileString("foo.go",
108 | str("package foo\n", imports, importtypes, expr)
109 | )
110 | }
111 |
112 | func parseJs(expr) {
113 | parseJs(expr, [], [])
114 | } (expr, pkgs) {
115 | parseJs(expr, list(pkgs), [])
116 | } (expr, pkgs, types) {
117 | imports := if count(pkgs) == 0 {
118 | ""
119 | } else {
120 | lines := for p := lazy pkgs {str(`"`, p, `"`)}
121 | str("import(\n", "\n" string.join lines, "\n)\n")
122 | }
123 | importtypes := if count(types) == 0 {
124 | ""
125 | } else {
126 | str("import type(\n", "\n" string.join types, "\n)\n")
127 | }
128 | compileString("foo.gos",
129 | str("package foo\n", imports, importtypes, expr)
130 | )
131 | }
132 | func parsed(expr) {
133 | parsed(expr, [], [])
134 | } (expr, pkgs) {
135 | parsed(expr, list(pkgs), [])
136 | } (expr, pkgs, types) {
137 | imports := if count(pkgs) == 0 {
138 | ""
139 | } else {
140 | lines := for p := lazy pkgs {str("[", p, " :as ", p, "]")}
141 | str(" (:require ", " " string.join lines, ")")
142 | }
143 | importtypes := if count(types) == 0 {
144 | ""
145 | } else {
146 | lines := for t := lazy types {str("(", t, ")")}
147 | str(" (:import ", " " string.join lines, ")")
148 | }
149 | str("(ns foo (:gen-class)",
150 | imports,
151 | importtypes,
152 | ") (set! *warn-on-reflection* true) ",
153 | expr
154 | )
155 | }
156 |
157 | func parsedAsync(expr) {
158 | str("(ns foo (:gen-class) ",
159 | "(:require ", requireAsync,
160 | ")) (set! *warn-on-reflection* true) ",
161 | expr
162 | )
163 | }
164 |
165 | func parsedJsAsync(expr) {
166 | str("(ns foo ",
167 | requireJsAsync,
168 | ") ",
169 | expr
170 | )
171 | }
172 |
173 | func parsedJs(expr) {
174 | parsedJs(expr, [], [])
175 | } (expr, pkgs) {
176 | parsedJs(expr, list(pkgs), [])
177 | } (expr, pkgs, types) {
178 | imports := if count(pkgs) == 0 {
179 | ""
180 | } else {
181 | lines := for p := lazy pkgs {str("[", p, " :as ", p, "]")}
182 | str(" (:require ", " " string.join lines, ")")
183 | }
184 | importtypes := if count(types) == 0 {
185 | ""
186 | } else {
187 | lines := for t := lazy types {str("(", t, ")")}
188 | str(" (:import ", " " string.join lines, ")")
189 | }
190 | str("(ns foo",
191 | imports,
192 | importtypes,
193 | ") ",
194 | expr
195 | )
196 | }
197 |
198 | func parseNoPretty(expr) {
199 | fgo.Parse("foo.go", "package foo;" str expr)
200 | }
201 |
202 | func parsedNoPretty(expr) {
203 | str("(ns foo (:gen-class) ) (set! *warn-on-reflection* true) ", expr)
204 | }
205 |
206 | test.fact("can refer to symbols",
207 | parse("a"), =>, parsed("a"),
208 | parse("foo"), =>, parsed("foo")
209 | )
210 |
211 | test.fact("can refer to numbers",
212 | parse("99"), =>, parsed("99"),
213 | parse("9"), =>, parsed("9"),
214 | parse("0"), =>, parsed("0")
215 | )
216 |
217 | test.fact("can refer to symbols in other packages",
218 | parse("other.foo", "other"), =>, parsed("other/foo", "other")
219 | )
220 |
221 | test.fact("can define things",
222 | //parse("a := 12345"), =>, parsed("(def ^:private a 12345)"),
223 | parse("var a = 12345"), =>, parsed("(def ^:private a 12345)"),
224 | parse("var a FooType = 12345", [], ["foo.FooType"]),
225 | =>, parsed("(def ^{:private true, :tag FooType} a 12345)", [], ["foo FooType"]),
226 | //parse("Foo := 12345"), =>, parsed("(def Foo 12345)"),
227 | parse("var Foo = 12345"), =>, parsed("(def Foo 12345)"),
228 | parse("var Foo FooType = 12345", [], ["foo.FooType"]),
229 | =>, parsed("(def ^FooType Foo 12345)", [], ["foo FooType"])
230 | )
231 |
232 |
233 | test.fact("can create vectors",
234 | parse("[]"), =>, parsed("[]"),
235 | parse("[a]"), =>, parsed("[a]"),
236 | parse("[a,b]"), =>, parsed("[a b]"),
237 | parse("[a,b,c]"), =>, parsed("[a b c]"),
238 | parse("[foo,bar]"), =>, parsed("[foo bar]"),
239 | parse(" [ d, e, f ]"), =>, parsed("[d e f]"),
240 | parse(" [ g , h , i ]"), =>, parsed("[g h i]"),
241 | parse(" [ j , k, l ] "), =>, parsed("[j k l]")
242 | )
243 |
244 | test.fact("can escape identifier that are not legal Funcgo identifiers",
245 | parse(`\range\`), =>, parsed("range"),
246 | parse(`\for\`), =>, parsed("for"),
247 | parse(`\+>*&%%*&$\`), =>, parsed(`+>*&%%*&$`),
248 | parse(`5 + \+>*&%%*&$\ * 3`), =>, parsed(`(+ 5 (* +>*&%%*&$ 3))`),
249 | )
250 |
251 | test.fact("one plus one",
252 | parse("1+1"), =>, parsed("(+ 1 1)")
253 | )
254 |
255 | test.fact("can have multiple expressions inside func",
256 | parse(`func(){if c {d}}`), =>, parsed(`(fn [] (when c d))`),
257 | parse(`func(){b;c}`), =>, parsed(`(fn [] (do b c))`),
258 | parse(`func(){b;if c {d}}`), =>, parsed(`(fn [] (do b (when c d)))`)
259 | )
260 |
261 | test.fact("operator functions",
262 | parse(`func ^(x, y) { Math::pow(x, y)}`), =>, parsed(`(defn bit-xor [x y] (Math/pow x y))`),
263 | parse(`func +(x, y) {x str y}`), =>, parsed(`(defn + [x y] (str x y))`),
264 | parse(`func ∈(elem, coll) { coll isContains elem}`),
265 | =>, parsed(`(defn ∈ [elem coll] (contains? coll elem))`),
266 | parse(`a ∈ b`), =>, parsed(`(∈ a b)`),
267 | parse(`func \**\(x, y) { Math::pow(x, y)}`), =>, parsed(`(defn ** [x y] (Math/pow x y))`),
268 | )
269 |
270 |
271 | test.fact("can have nested consts",
272 | parse(`{const(a=1)x;{const(b=2)y}}`), =>, parsed(`(let [a 1] x (let [b 2] y))`),
273 | parse(`{const a=1; {const b=2; y}}`), =>, parsed(`(let [a 1] (let [b 2] y))`)
274 | )
275 |
276 | test.fact("can have nested :=",
277 | parse(`{a:=1;x;{b:=2;y}}`), =>, parsed(`(let [a 1] x (let [b 2] y))`),
278 | parse(`{a:=1; {b:=2; y}}`), =>, parsed(`(let [a 1] (let [b 2] y))`)
279 | )
280 |
281 | // See http://blog.jayfields.com/2010/07/clojure-destructuring.html
282 | test.fact("Can destructure vectors using const",
283 | parse(`{const([a,b]=ab) f(a,b)}`),
284 | =>,
285 | parsed(`(let [[a b] ab] (f a b))`),
286 |
287 | parse(`{const [a,b]=ab; f(a,b)}`),
288 | =>,
289 | parsed(`(let [[a b] ab] (f a b))`),
290 |
291 | parse(`{const([x, more...] = indexes) f(x, more)}`),
292 | =>,
293 | parsed(`(let [[x & more] indexes] (f x more))`),
294 |
295 | parse(`{const [x, more...] = indexes; f(x, more)}`),
296 | =>,
297 | parsed(`(let [[x & more] indexes] (f x more))`),
298 |
299 | parse(`{const([x, more..., AS, full] = indexes) f(x, more, full)}`),
300 | =>,
301 | parsed(`(let [[x & more :as full] indexes] (f x more full))`),
302 |
303 | // TODO(eob) implement KEYS:
304 | //parse(`const({KEYS: [x, y]} = point) f(x, y)`),
305 | //=>,
306 | //parsed(`(let [{:keys [x y]} point] (f x y))`),
307 |
308 | parse(`{const([[a,b],[c,d]] = numbers) f(a, b, c, d)}`),
309 | =>,
310 | parsed(`(let [[[a b] [c d]] numbers] (f a b c d))`)
311 | )
312 |
313 | // See http://blog.jayfields.com/2010/07/clojure-destructuring.html
314 | test.fact("Can destructure vectors using :=",
315 | parse(`{[a,b]:=ab; f(a,b)}`),
316 | =>,
317 | parsed(`(let [[a b] ab] (f a b))`),
318 |
319 | parse(`{[x, more...] := indexes; f(x, more)}`),
320 | =>,
321 | parsed(`(let [[x & more] indexes] (f x more))`),
322 |
323 | parse(`{[x, more..., AS, full] := indexes; f(x, more, full)}`),
324 | =>,
325 | parsed(`(let [[x & more :as full] indexes] (f x more full))`),
326 |
327 | // TODO(eob) implement KEYS:
328 | //parse(`const({KEYS: [x, y]} = point) f(x, y)`),
329 | //=>,
330 | //parsed(`(let [{:keys [x y]} point] (f x y))`),
331 |
332 | parse(`{[[a,b],[c,d]] := numbers; f(a, b, c, d)}`),
333 | =>,
334 | parsed(`(let [[[a b] [c d]] numbers] (f a b c d))`)
335 | )
336 |
337 | test.fact("can destructure dicts using const and func",
338 | parse(`{const({theX: X, theY: Y} = point) f(theX, theY)}`),
339 | =>,
340 | parsed(`(let [{the-x :x, the-y :y} point] (f the-x the-y))`),
341 |
342 | parse(`{const {theX: X, theY: Y} = point; f(theX, theY)}`),
343 | =>,
344 | parsed(`(let [{the-x :x, the-y :y} point] (f the-x the-y))`),
345 |
346 | parse(`{const({name: NAME, {[pages, \isbn10\]: KEYS}: DETAILS} = book) f(name,pages,\isbn10\)}`),
347 | =>,
348 | parsed(`(let [{name :name, {[pages isbn10] :keys} :details} book] (f name pages isbn10))`),
349 |
350 | parse(`{const({name: NAME, [hole1, hole2]: SCORES} = golfer) f(name, hole1, hole2)}`),
351 | =>,
352 | parsed(`(let [{name :name, [hole1 hole2] :scores} golfer] (f name hole1 hole2))`),
353 |
354 | parse(`func printStatus({name: NAME, [hole1, hole2]: SCORES}) { f(name, hole1, hole2) }`),
355 | =>,
356 | parsed(`(defn- print-status [{name :name, [hole1 hole2] :scores}] (f name hole1 hole2))`),
357 |
358 | parse(`func PrintStatus({name: NAME, [hole1, hole2]: SCORES}) { f(name, hole1, hole2) }`),
359 | =>,
360 | parsed(`(defn Print-status [{name :name, [hole1 hole2] :scores}] (f name hole1 hole2))`),
361 |
362 | parse(`printStatus( {NAME: "Jim", SCORES: [3, 5, 4, 5]} )`),
363 | =>,
364 | parsed(`(print-status {:name "Jim", :scores [3 5 4 5]})`)
365 | )
366 |
367 | test.fact("can destructure dicts using :=",
368 | parse(`{{theX: X, theY: Y} := point; f(theX, theY)}`),
369 | =>,
370 | parsed(`(let [{the-x :x, the-y :y} point] (f the-x the-y))`),
371 |
372 | parse(`{{name: NAME, {[pages, \isbn10\]: KEYS}: DETAILS} := book; f(name,pages,\isbn10\)}`),
373 | =>,
374 | parsed(`(let [{name :name, {[pages isbn10] :keys} :details} book] (f name pages isbn10))`),
375 |
376 | parse(`{{name: NAME, [hole1, hole2]: SCORES} := golfer; f(name, hole1, hole2)}`),
377 | =>,
378 | parsed(`(let [{name :name, [hole1 hole2] :scores} golfer] (f name hole1 hole2))`)
379 | )
380 |
381 | test.fact("can specity types to avoid reflection",
382 | parse(`{const(a FooType = 3) f(a)}`, [], ["foo.FooType"]),
383 | =>, parsed(`(let [^FooType a 3] (f a))`, [], ["foo FooType"]),
384 |
385 | parse(`{const a FooType = 3; f(a)}`, [], ["foo.FooType"]),
386 | =>, parsed(`(let [^FooType a 3] (f a))`, [], ["foo FooType"]),
387 |
388 | parse(`func g(a FooType) { f(a) }`, [], ["foo.FooType"]),
389 | =>, parsed(`(defn- g [^FooType a] (f a))`, [], ["foo FooType"]),
390 |
391 | parse(`func(a FooType) { f(a) }`, [], ["foo.FooType"]),
392 | =>, parsed(`(fn [^FooType a] (f a))`, [], ["foo FooType"]),
393 |
394 | parse(`func g(a) FooType { f(a) }`, [], ["foo.FooType"]),
395 | =>, parsed(`(defn- g ^FooType [a] (f a))`, [], ["foo FooType"]),
396 |
397 | parse(`func(a) FooType { f(a) }`, [], ["foo.FooType"]),
398 | =>, parsed(`(fn ^FooType [a] (f a))`, [], ["foo FooType"]),
399 |
400 | parse(`func f(a) long {a/3} (a, b) double {a+b}`),
401 | =>,
402 | parsed(`(defn- f (^long [a] (/ a 3)) (^double [a b] (+ a b)))`),
403 |
404 | parse(`func(a) long {a/3} (a, b) double {a+b}`),
405 | =>,
406 | parsed(`(fn (^long [a] (/ a 3)) (^double [a b] (+ a b)))`)
407 | )
408 |
409 | test.fact("bit expressions are supported",
410 | parse(`1<<64 - 1`), =>, parsed(`(- (bit-shift-left 1 64) 1)`),
411 | parse(`var a = 1<<64 - 1`), =>, parsed(`(def ^:private a (- (bit-shift-left 1 64) 1))`)
412 | )
413 |
414 | test.fact("quoting",
415 | // \\u0060 is backtick
416 | parse("quote(foo(a))"), =>, parsed("'(foo a)"),
417 | parseNoPretty("syntax foo(a)"), =>, parsedNoPretty("\u0060(foo a)"),
418 | parseNoPretty("syntax \\(foo a)\\"), =>, parsedNoPretty("\u0060(foo a)"),
419 |
420 | parseNoPretty(`syntax fred(x, unquote x, lst, unquotes lst, 7, 8, NINE)`),
421 | =>,
422 | parsedNoPretty("\u0060(fred x ~x lst ~@lst 7 8 :nine)")
423 | )
424 |
425 | test.fact("symbol beginning with underscore",
426 | parse(`_main`), =>, parsed(`-main`),
427 | parse(`_foo`), =>, parsed(`-foo`),
428 | parseJs(`mutateSet( js.window->_onload, start)`),
429 | =>, parsedJs(`(set! (. js/window -onload) start)`)
430 | )
431 |
432 | test.fact("Javascript",
433 | parseJs(`new js.Date()->toISOString`),
434 | =>, parsedJs(`(. (js.Date.) toISOString)`)
435 | )
436 |
437 |
438 | test.fact("can call function",
439 | parse("f()") ,=>, parsed("(f)"),
440 | parse("f(x)") ,=>, parsed("(f x)"),
441 | //parse("x->f()") ,=>, parsed("(f x)"),
442 | //parse("x->f(y,z)") ,=>, parsed("(f x y z)"),
443 | parse("f(x,y,z)") ,=>, parsed("(f x y z)"))
444 |
445 | test.fact("can call outside functions",
446 | parse("o.f(x)", "o") ,=>, parsed("(o/f x)", "o")
447 | )
448 | test.fact("labels have no lower case",
449 | parse("FOO") ,=>, parsed(":foo"),
450 | parse("FOO_BAR") ,=>, parsed(":foo-bar"),
451 | parse("OVER18") ,=>, parsed(":over18")
452 | )
453 | test.fact("labels with other characters",
454 | parse("DIV#ID.CLASS1") ,=>, parsed(":div#id.class1"),
455 | parse("SPAN.TEXT") ,=>, parsed(":span.text"),
456 | parse("DIV#EMAIL.SELECTED.STARRED"),=>, parsed(":div#email.selected.starred"),
457 | parse("IS_EXISTS"),=>, parsed(":exists?")
458 | )
459 | test.fact("hex literal",
460 | parse("0xff"), =>, parsed(str(Integer::parseInt("ff",16))),
461 | parse("0xcafe"), =>, parsed(str(Integer::parseInt("cafe",16))),
462 | parse("0xbabe"), =>, parsed(str(Integer::parseInt("babe",16))),
463 | //parse("0xcafebabe"), =>, parsed(str(new BigInteger("cafebabe",16))),
464 | parse("0xFF"), =>, parsed(str(Integer::parseInt("FF",16))),
465 | parse("0xCAFE"), =>, parsed(str(Integer::parseInt("CAFE",16))),
466 | parse("0xBABE"), =>, parsed(str(Integer::parseInt("BABE",16))) //,
467 | //parse("0xCAFEBABE"), =>, parsed(str(new BigInteger("CAFEBABE",16)))
468 | )
469 | test.fact("dictionary literals",
470 | parse("{}") ,=>, parsed("{}"),
471 | parse("{A:1}") ,=>, parsed("{:a 1}"),
472 | parse("{A:1, B:2}") ,=>, parsed("{:a 1, :b 2}"),
473 | parse("{A:1, B:2, C:3}"),=>, parsed("{:a 1, :b 2, :c 3}")
474 | )
475 | test.fact("dictionary literals with trailing comma",
476 | parse("{}") ,=>, parsed("{}"),
477 | parse("{A:1,}") ,=>, parsed("{:a 1}"),
478 | parse("{A:1, B:2,}") ,=>, parsed("{:a 1, :b 2}"),
479 | parse("{A:1, B:2, C:3,}"),=>, parsed("{:a 1, :b 2, :c 3}")
480 | )
481 | test.fact("set literals",
482 | parse("set{}") ,=>, parsed("#{}"),
483 | parse("set{A}") ,=>, parsed("#{:a}"),
484 | parse("set{A, B}") ,=>, parsed("#{:b :a}"),
485 | parse("set{A, B, C}") ,=>, parsed("#{:c :b :a}"),
486 | parse(`set{"A", "B", "C"}`) ,=>, parsed(`#{"C" "B" "A"}`),
487 | parse(`set{'A', 'B', 'C'}`) ,=>, parsed(`#{\A \B \C}`),
488 | parse(`set{A, "B", 'C', 999}`) ,=>, parsed(`#{\C 999 "B" :a}`)
489 | )
490 | test.fact("private named functions",
491 | parse("func foo(){d}") ,=>, parsed("(defn- foo [] d)"),
492 | parse("func foo(a){d}") ,=>, parsed("(defn- foo [a] d)"),
493 | parse("func foo(a,b){d}") ,=>, parsed("(defn- foo [a b] d)"),
494 | parse("func foo(a,b,c){d}"),=>, parsed("(defn- foo [a b c] d)")
495 | )
496 | test.fact("named functions",
497 | parse("func Foo(){d}") ,=>, parsed("(defn Foo [] d)"),
498 | parse("func Foo(a){d}") ,=>, parsed("(defn Foo [a] d)"),
499 | parse("func Foo(a,b){d}") ,=>, parsed("(defn Foo [a b] d)"),
500 | parse("func Foo(a,b,c){d}"),=>, parsed("(defn Foo [a b c] d)"),
501 | parse("func n(a,b) {c}") ,=>, parsed("(defn- n [a b] c)")
502 | )
503 | test.fact("named multifunctions",
504 | parse("func n(a){b}(c){d}"),=>,parsed("(defn- n ([a] b) ([c] d))")
505 | )
506 | test.fact("named varadic",
507 | parse("func n(a...){d}") ,=>,parsed("(defn- n [& a] d)"),
508 | parse("func n(a,b...){d}"),=>,parsed("(defn- n [a & b] d)"),
509 | parse("func n(a,b,c...){d}"),=>,parsed("(defn- n [a b & c] d)")
510 | )
511 | test.fact("anonymous functions",
512 | parse("func(){c}") ,=>, parsed("(fn [] c)"),
513 | parse("func(a,b){c}") ,=>, parsed("(fn [a b] c)")
514 | )
515 | test.fact("anon multifunctions",
516 | parse("func(a){b}(c){d}"),=>, parsed("(fn ([a] b) ([c] d))")
517 | )
518 | test.fact("anon varadic",
519 | parse("func(a...){d}") ,=>, parsed("(fn [& a] d)"),
520 | parse("func(a,b...){d}") ,=>, parsed("(fn [a & b] d)"),
521 | parse("func(a,b,c...){d}"),=>,parsed("(fn [a b & c] d)")
522 | )
523 | test.fact("can have raw strings",
524 | parse("\u0060one two\u0060") ,=>, parsed(`"one two"`)
525 | )
526 | test.fact("can have strings",
527 | parse(`"one two"`) ,=>, parsed(`"one two"`)
528 | )
529 | test.fact("can have double-quotes in strings",
530 | parse(`"one \"two\" three"`), =>, parsed(`"one \"two\" three"`)
531 | )
532 | test.fact("characters in raw",
533 | parse("`\n'\b`") ,=>, parsed(`"\n'\b"`),
534 | parse(str("`", `"`, "`")) ,=>, parsed(`"\""`)
535 | )
536 | test.fact("backslash in raw",
537 | parse("`foo\\bar`") ,=>, parsed(`"foo\\bar"`)
538 | )
539 | test.fact("characters in strings",
540 | parse( `"\n"`) ,=>, parsed(`"\n"`)
541 | )
542 | test.fact("quotes in strings",
543 | parse(`"foo\"bar"`) ,=>, parsed(`"foo\"bar"`),
544 | //parse("\"foo\"bar\"") ,=>, parsed("\"foo\"bar\"")
545 | )
546 | test.fact("multiple expr ",
547 | parse("1;2;3") ,=>, parsed("1 2 3"),
548 | parse("1\n2\n3") ,=>, parsed("1 2 3")
549 | )
550 | test.fact("const",
551 | parse("{const(a = 2)a}"),=>, parsed("(let [a 2] a)"),
552 | parse("{ const( a = 2 ) a}"),=>, parsed("(let [a 2] a)"),
553 | parse(`{const(
554 | b = 2
555 | )
556 | a}`),=>, parsed("(let [b 2] a)"),
557 | parse("{ const(\n c = 2\n )\n a}"),=>, parsed("(let [c 2] a)"),
558 | parse("{const(a = 2)f(a,b)}"),=>, parsed("(let [a 2] (f a b))")
559 | )
560 | test.fact(":=",
561 | parse("{a := 2;a}"),=>, parsed("(let [a 2] a)"),
562 | parse("{ a := 2 ; a}"),=>, parsed("(let [a 2] a)"),
563 | parse("{\nb := 2\n\na}"),=>, parsed("(let [b 2] a)"),
564 | parse("{ \n c := 2\n \n a}"),=>, parsed("(let [c 2] a)"),
565 | parse("{a := 2;f(a,b)}"),=>, parsed("(let [a 2] (f a b))")
566 | )
567 | test.fact("comment",
568 | parse("//0 blah blah\naaa0") ,=>, parsed("aaa0"),
569 | parse(" //1 blah blah \naaa1") ,=>, parsed("aaa1"),
570 | parse(" //2 blah blah \naaa2") ,=>, parsed("aaa2"),
571 | parse(" //3 blah blah\naaa3") ,=>, parsed("aaa3"),
572 | parse("\n //4 blah blah\n \naaa4") ,=>, parsed("aaa4"),
573 | parse("// comment\n aaa5") ,=>, parsed("aaa5"),
574 | parse("// comment\n// another\naaa6") ,=>, parsed("aaa6"),
575 | parse("// comment\n// another\naaa7") ,=>, parsed("aaa7"),
576 | parse("\n\n//////\n// This file is part of the Funcgo compiler.\naaa8") ,=>, parsed("aaa8"),
577 | parse(`
578 |
579 | //////
580 | // This file is part of the Funcgo compiler.
581 | aaa9`) ,=>, parsed("aaa9"),
582 | parse("///////\naaa10") ,=>, parsed("aaa10"),
583 | parse(`///////
584 | aaa11`) ,=>, parsed("aaa11")
585 | )
586 | test.fact("bug1",
587 | parse(`{
588 | // Words ending in 'ox' pluralize with 'en' (and not 'es')
589 | /(ox)(?i)$/ mutatePlural "$1en"
590 |
591 | plural("box")
592 | }`), =>, parsed(`(do (plural! #"(ox)(?i)$" "$1en") (plural "box"))`)
593 | )
594 | test.fact("bug2",
595 | parseNoPretty(`func HasPackage(st, pkg) {
596 | dosync{
597 | st alter func{$1 += {
598 | UNUSED_PACKAGES: (*st)(UNUSED_PACKAGES) disj pkg
599 | }}
600 | }
601 |
602 | (*st)(pkg) == PACKAGE
603 | }`), =>, parsedNoPretty(`(defn Has-package [st pkg] (do (dosync (alter st #(assoc %1 :unused-packages (disj (@st :unused-packages) pkg)))) (= (@st pkg) :package)))`)
604 | )
605 |
606 | test.fact("bug3",
607 | parse(`try {
608 | i := dangerous->get(0)
609 | dangerous->set(0, i + 1)
610 | } finally {
611 | mutex <- true // release mutex
612 | }`),
613 | =>, parsedAsync(`(try (let [i (. dangerous (get 0))] (. dangerous (set 0 (+ i 1)))) (finally (>!! mutex true)))`)
614 | )
615 | test.fact("regex",
616 | parse("/aaa/") ,=>, parsed(`#"aaa"`),
617 | parse("/[0-9]+/") ,=>, parsed(`#"[0-9]+"`),
618 | parse(`/aaa\nbbb/`) ,=>, parsed(`#"aaa\nbbb"`),
619 | parse(`/(ox)(?i)$/`) ,=>, parsed(`#"(ox)(?i)$"`),
620 | parse("/a/") ,=>, parsed(`#"a"`),
621 | )
622 | test.fact("Regex excaping",
623 | parse(`/aaa\/bbb/`) ,=>, parsed(`#"aaa/bbb"`),
624 | parse(`/aaa'bbb/`) ,=>, parsed(`#"aaa'bbb"`),
625 | parse(`/aaa"bbb/`) ,=>, parsed(`#"aaa\"bbb"`),
626 | parse(`/aaa\p{Ll}bbb/`) ,=>, parsed(`#"aaa\p{Ll}bbb"`),
627 | parse(`/aaa\s+bbb/`) ,=>, parsed(`#"aaa\s+bbb"`),
628 |
629 | )
630 |
631 | // parse("/aaa\/bbb/" ,=>, parsed("#\"aaa/bbb"")) TODO implement
632 | test.fact("if",
633 | parse("if a {b}") ,=>, parsed("(when a b)"),
634 | parse("if a {b;c}") ,=>, parsed("(when a (do b c))"),
635 | parse("if a {b\nc}") ,=>, parsed("(when a (do b c))"),
636 | parse("if a {b}else{c}") ,=>, parsed("(if a b c)"),
637 | parse("if a { b }else{ c }") ,=>, parsed("(if a b c)"),
638 | parse("if a {b;c} else {d;e}") ,=>, parsed("(if a (do b c) (do d e))")
639 | )
640 | test.fact("new",
641 | parse("new Foo()", [], ["foo.Foo"]) ,=>, parsed("(Foo.)", [], ["foo Foo"]),
642 | parse("new Foo(a)", [], ["foo.Foo"]) ,=>, parsed("(Foo. a)", [], ["foo Foo"]),
643 | parse("new Foo(a,b,c)", [], ["foo.Foo"]) ,=>, parsed("(Foo. a b c)", [], ["foo Foo"])
644 | )
645 | test.fact("try catch",
646 | parse("try{a}catch T e{b}", [], ["a.T"]),
647 | =>, parsed("(try a (catch T e b))", [], ["a T"]),
648 |
649 | parse("try{a}catch T1 e1{b} catch T2 e2{c}", [], ["a.{T1,T2}"]),
650 | =>, parsed("(try a (catch T1 e1 b) (catch T2 e2 c))", [], ["a T1 T2"]),
651 |
652 | parse("try{a;b}catch T e{c;d}", [], ["a.T"]),
653 | =>, parsed("(try a b (catch T e c d))", [], ["a T"]),
654 |
655 | parse("try{a}catch T e{b}finally{c}", [], ["a.T"]),
656 | =>, parsed("(try a (catch T e b) (finally c))", [], ["a T"]),
657 |
658 | parse("try { a } catch T e{ b }", [], ["a.T"]),
659 | =>, parsed("(try a (catch T e b))", [], ["a T"])
660 | )
661 | test.fact("for",
662 | parse("for x:=range xs{f(x)}") ,=>, parsed("(doseq [x xs] (f x))"),
663 | parse("for x := range xs {f(x)}") ,=>, parsed("(doseq [x xs] (f x))"),
664 | parse("for x:= lazy xs{f(x)}") ,=>, parsed("(for [x xs] (f x))"),
665 | parse("for x:= lazy xs if a{f(x)}") ,=>, parsed("(for [x xs :when a] (f x))"),
666 | parse("for i:= times n {f(i)}") ,=>, parsed("(dotimes [i n] (f i))"),
667 | parse("for [a,b]:= lazy xs{f(a,b)}") ,=>, parsed("(for [[a b] xs] (f a b))"),
668 | parse("for x:=lazy xs if x<0 {f(x)}") ,=>, parsed("(for [x xs :when (< x 0)] (f x))")
669 | )
670 | test.fact("c-style for",
671 | parse("for i := 0; i, parsed("(dotimes [i n] (f i))")
672 | )
673 | test.fact("Camelcase is converted to dash-separated",
674 | parse("foo") ,=>, parsed("foo"),
675 | parse("fooBar") ,=>, parsed("foo-bar"),
676 | parse("fooBarBaz") ,=>, parsed("foo-bar-baz"),
677 | parse("foo_bar") ,=>, parsed("foo_bar"),
678 | parse("Foo") ,=>, parsed("Foo"),
679 | parse("FooBar") ,=>, parsed("Foo-bar"),
680 | parse("FOO") ,=>, parsed(":foo"),
681 | parse("FOO_BAR") ,=>, parsed(":foo-bar"),
682 | parse("A") ,=>, parsed(":a")
683 | )
684 | test.fact("leading underscore to dash",
685 | parse("_main") ,=>, parsed("-main")
686 | )
687 | test.fact("is to questionmark",
688 | parse("isFoo") ,=>, parsed("foo?")
689 | )
690 | test.fact("mutate to exclamation mark",
691 | parse("mutateFoo") ,=>, parsed("foo!")
692 | )
693 | test.fact("java method calls",
694 | parse("foo->bar") ,=>, parsed("(. foo bar)"),
695 | parse("foo->bar(a,b)") ,=>, parsed( "(. foo (bar a b))"),
696 | parse("foo->bar()") ,=>, parsed( "(. foo (bar))"),
697 | parse(`"fred"->toUpperCase()`) ,=>, parsed(`(. "fred" (toUpperCase))`),
698 | parse("println(a, e->getMessage())") ,=>, parsed("(println a (. e (getMessage)))"),
699 | parse(`System::getProperty("foo")`) ,=>, parsed(`(System/getProperty "foo")`),
700 | parse("Math::PI") ,=>, parsed("Math/PI"),
701 | parse("999 * f->foo()") ,=>, parsed( "(* 999 (. f (foo)))"),
702 | parse("f->foo() / b->bar()") ,=>, parsed( "(/ (. f (foo)) (. b (bar)))"),
703 | parse("999 * f->foo() / b->bar()") ,=>, parsed( "(/ (* 999 (. f (foo))) (. b (bar)))"),
704 | parse("999 * f->foo") ,=>, parsed( "(* 999 (. f foo))"),
705 | parse("f->foo / b->bar") ,=>, parsed( "(/ (. f foo) (. b bar))"),
706 | parse("999 * f->foo / b->bar") ,=>, parsed( "(/ (* 999 (. f foo)) (. b bar))")
707 | )
708 | test.fact("goroutine",
709 | parse(`func Main() {
710 | go say("world")
711 | say("hello")
712 | }`), =>, parsedAsync(`(defn Main [] (do (go (say "world")) (say "hello")))` )
713 | )
714 |
715 | test.fact("goroutine js",
716 | parseJs(`func Main() {
717 | go say("world")
718 | say("hello")
719 | }`), =>, parsedJsAsync(`(defn Main [] (do (go (say "world")) (say "hello")))` )
720 | )
721 |
722 | test.fact("channel",
723 | parse("make(chan)"), =>, parsedAsync("(chan)"),
724 | parse("make(chan int)"), =>, parsedAsync("(chan)"),
725 | parse("make(chan int)"), =>, parsedAsync("(chan)"),
726 | parse("make(chan, 10)"), =>, parsedAsync("(chan 10)"),
727 | parse("make(chan int, 10)"), =>, parsedAsync("(chan 10)")
728 | )
729 |
730 | test.fact("bug4",
731 | parse(`foo(<-go { b })`), =>,
732 | parsedAsync("(foo (,
738 | parsed("(do a (foo b))")
739 | )
740 |
741 | test.fact("bug6a",
742 | parseNoPretty(`
743 | func HasPackage(st, pkg) {
744 | dosync(st alter func{$1 += {
745 | UNUSED_PACKAGES: (*st)(UNUSED_PACKAGES) disj pkg
746 | }});
747 | (*st)(pkg) == PACKAGE
748 | }
749 | `), =>, parsedNoPretty(`(defn Has-package [st pkg] (do (dosync (alter st #(assoc %1 :unused-packages (disj (@st :unused-packages) pkg)))) (= (@st pkg) :package)))`)
750 | )
751 |
752 | test.fact("bug6b",
753 | parseNoPretty(`
754 | func HasPackage(st, pkg) {
755 | dosync{
756 | st alter func{$1 += {
757 | UNUSED_PACKAGES: (*st)(UNUSED_PACKAGES) disj pkg
758 | }}
759 | }
760 | (*st)(pkg) == PACKAGE
761 | }
762 | `), =>, parsedNoPretty(`(defn Has-package [st pkg] (do (dosync (alter st #(assoc %1 :unused-packages (disj (@st :unused-packages) pkg)))) (= (@st pkg) :package)))`)
763 | )
764 |
765 | test.fact("bug7",
766 | parse(`
767 | whitespaceOrComments := parser("xxx")
768 |
769 | yyy
770 | `), =>, parsed(`(let [whitespace-or-comments (parser "xxx")] yyy)`)
771 | )
772 |
773 | test.fact("select",
774 | parse(`
775 | select {
776 | case c <- x:
777 | foo
778 | case <-quit:
779 | bar
780 | }
781 | `), =>, parsedAsync("(alt!! [[c x]] (do foo) quit (do bar))"),
782 |
783 | parse(`
784 | select {
785 | case i1 = <-c1:
786 | print("received ", i1, " from c1\n")
787 | case c2 <- i2:
788 | print("sent ", i2, " to c2\n")
789 | default:
790 | print("no communication\n")
791 | }
792 | `), =>, parsedAsync(str(`(alt!!`,
793 | ` c1 ([i1] (print "received " i1 " from c1\n"))`,
794 | ` [[c2 i2]] (do (print "sent " i2 " to c2\n"))`,
795 | ` :default (do (print "no communication\n"))`,
796 | `)`)),
797 |
798 | parse(`
799 | select {
800 | case c <- 0: // note: no statement, no fallthrough, no folding of cases
801 | case c <- 1:
802 | }
803 | `), =>, parsedAsync("(alt!! [[c 0]] nil [[c 1]] nil)"),
804 |
805 | // parse(`
806 | // for { // send random sequence of bits to c
807 | // select {
808 | // case c <- 0: // note: no statement, no fallthrough, no folding of cases
809 | // case c <- 1:
810 | // }
811 | // }
812 | // `), =>, parsedAsync("(loop [] (alt!! [c 0] nil [c 1] nil) (recur))"),
813 |
814 | parse(`
815 | select {} // block forever
816 | `), =>, parsedAsync("(alt!!)")
817 | )
818 |
819 | test.fact("select in lightweight process",
820 | parse(`
821 | select {
822 | case c <: x:
823 | foo
824 | case <:quit:
825 | bar
826 | }
827 | `), =>, parsedAsync("(alt! [[c x]] (do foo) quit (do bar))"),
828 |
829 | parse(`
830 | select {
831 | case i1 = <:c1:
832 | print("received ", i1, " from c1\n")
833 | case c2 <: i2:
834 | print("sent ", i2, " to c2\n")
835 | default:
836 | print("no communication\n")
837 | }
838 | `), =>, parsedAsync(str(`(alt!`,
839 | ` c1 ([i1] (print "received " i1 " from c1\n"))`,
840 | ` [[c2 i2]] (do (print "sent " i2 " to c2\n"))`,
841 | ` :default (do (print "no communication\n"))`,
842 | `)`)),
843 |
844 | parse(`
845 | select {
846 | case c <- 0:
847 | case c <- 2:
848 | }
849 | `), =>, parsedAsync("(alt!! [[c 0]] nil [[c 2]] nil)"),
850 |
851 | parse(`
852 | select {
853 | case c <- 0: ;
854 | case c <- 3:
855 | }
856 | `), =>, parsedAsync("(alt!! [[c 0]] nil [[c 3]] nil)"),
857 |
858 | parse(`
859 | select {
860 | case c <- 0: // note: no statement, no fallthrough, no folding of cases
861 | case c <- 4:
862 | }
863 | `), =>, parsedAsync("(alt!! [[c 0]] nil [[c 4]] nil)"),
864 |
865 | parse(`
866 | select {
867 | case c <: 0: // note: no statement, no fallthrough, no folding of cases
868 | case c <: 5:
869 | }
870 | `), =>, parsedAsync("(alt! [[c 0]] nil [[c 5]] nil)")
871 |
872 | // parse(`
873 | // for { // send random sequence of bits to c
874 | // select {
875 | // case c <: 0: // note: no statement, no fallthrough, no folding of cases
876 | // case c <: 1:
877 | // }
878 | // }
879 | // `), =>, parsedAsync("(loop [] (alt! [c 0] nil [c 1] nil) (recur))")
880 |
881 | )
882 |
883 | test.fact("go syntax",
884 | parse(`
885 | func main() {
886 | a := []int{7, 2, 8, -9, 4, 0}
887 |
888 | c := make(chan int)
889 | go sum(a[:len(a)/2], c)
890 | go sum(a[len(a)/2:], c)
891 | var x, y = <-c, <-c // receive from c
892 |
893 | Println(x, y, x+y)
894 | }`), =>, parsedAsync(str(
895 | "(defn main [] (let",
896 | " [a [7 2 8 (- 9) 4 0]",
897 | " c (chan)]",
898 | " (go (sum (take (/ (count a) 2) a) c))",
899 | " (go (sum (drop (/ (count a) 2) a) c))",
900 | " (def ^:private x (, parsedAsync("(>!! c x)"),
908 | parse("c <: x"), =>, parsedAsync("(>! c x)"),
909 | parse("<-c"), =>, parsedAsync("(, parsedAsync("(, parsedAsync("(def Foo (, parsedAsync("(def Foo (, parsed(`(defn Main [] (do (say "world") (say "hello")))`)
920 | )
921 | test.fact("there are some non-alphanumeric symbols",
922 | parse("foo(a,=>,b)") ,=>, parsed("(foo a => b)"),
923 | parse(`test.fact("interesting", parse("a"), =>, parsed("a"))`, "test"),
924 | =>,
925 | parsed(`(test/fact "interesting" (parse "a") => (parsed "a"))`, "test")
926 | )
927 | test.fact("infix",
928 | parse("a b c") ,=>, parsed("(b a c)"),
929 | parse("22 / 7") ,=>, parsed("(/ 22 7)"),
930 | parse("22 / (7 + 4)") ,=>, parsed("(/ 22 (+ 7 4))")
931 | )
932 |
933 | test.fact("equality",
934 | parse("a == b") ,=>, parsed("(= a b)"),
935 | parse("a isIdentical b") ,=>, parsed("(identical? a b)")
936 | )
937 |
938 | test.fact("character literals",
939 | parse("'a'") ,=>, parsed("\\a"),
940 | parse("['a', 'b', 'c']") ,=>, parsed("[\\a \\b \\c]"),
941 | parse("'\\n'") ,=>, parsed("\\newline"),
942 | parse("' '") ,=>, parsed("\\space"),
943 | parse("'\\t'") ,=>, parsed("\\tab"),
944 | parse("'\\b'") ,=>, parsed("\\backspace"),
945 | parse("'\\r'") ,=>, parsed("\\return"),
946 | parseNoPretty("'\\uDEAD'") ,=>, parsedNoPretty("\\uDEAD"),
947 | parseNoPretty("'\\ubeef'") ,=>, parsedNoPretty("\\ubeef"),
948 | parseNoPretty("'\\u1234'") ,=>, parsedNoPretty("\\u1234"),
949 | parseNoPretty("'\\234'") ,=>, parsedNoPretty("\\o234")
950 | )
951 |
952 | test.fact("indexing",
953 | parse("aaa(BBB)") ,=>, parsed("(aaa :bbb)"),
954 | parse("aaa[bbb]") ,=>, parsed("(nth aaa bbb)"),
955 | parse("v(6)") ,=>, parsed("(v 6)"),
956 | parse("v[6]") ,=>, parsed("(nth v 6)"),
957 | parse("aaa[6]") ,=>, parsed("(nth aaa 6)")
958 | )
959 |
960 | test.fact("precedent",
961 | parse("a || b < c") ,=>, parsed("(or a (< b c))"),
962 | parse("a || b && c") ,=>, parsed("(or a (and b c))"),
963 | parse("a && b || c") ,=>, parsed("(or (and a b) c)"),
964 | parse("a * b - c") ,=>, parsed("(- (* a b) c)"),
965 | parse("c + a * b") ,=>, parsed("(+ c (* a b))"),
966 | parse("a / b + c") ,=>, parsed("(+ (/ a b) c)")
967 | )
968 |
969 | test.fact("associativity",
970 | parse("x / y * z") ,=>, parsed("(* (/ x y) z)"),
971 | parse("x * y / z") ,=>, parsed("(/ (* x y) z)"),
972 | parse("x + y - z") ,=>, parsed("(- (+ x y) z)"),
973 | parse("x - y + z") ,=>, parsed("(+ (- x y) z)")
974 | )
975 |
976 | test.fact("parentheses",
977 | parse("(a or b) and c") ,=>, parsed("(and (or a b) c)"),
978 | parse("a * b - c") ,=>, parsed("(- (* a b) c)")
979 | )
980 |
981 | test.fact("unary",
982 | parse("+a") ,=>, parsed("(+ a)"),
983 | parse("-a") ,=>, parsed("(- a)"),
984 | parse("!a") ,=>, parsed("(not a)"),
985 | parse("^a") ,=>, parsed("(bit-not a)"),
986 | parse("*a") ,=>, parsed("@a")
987 | )
988 |
989 | test.fact("float literals",
990 | parse("2.0") ,=>, parsed("2.0"),
991 | parse("2.000") ,=>, parsed("2.0"),
992 | parse("0.0") ,=>, parsed("0.0"),
993 | parse("0.") ,=>, parsed("0.0"),
994 | parse("72.40") ,=>, parsed("72.4"),
995 | parse("072.40") ,=>, parsed("72.4"),
996 | parse("2.71828") ,=>, parsed("2.71828"),
997 | parse("1.0") ,=>, parsed("1.0"),
998 | parse("1.e+0") ,=>, parsed("1.0"),
999 | parse("6.67428E-11") ,=>, parsed("6.67428E-11"),
1000 | parse("6.67428e-11") ,=>, parsed("6.67428E-11"),
1001 | parse("1000000.0") ,=>, parsed("1000000.0"),
1002 | parse("1E6") ,=>, parsed("1000000.0"),
1003 | parse(".25") ,=>, parsed(".25"),
1004 | parse(".12345E+5") ,=>, parsed(".12345E+5")
1005 | )
1006 |
1007 | test.fact("symbols can start with keywords",
1008 | parse("format") ,=>, parsed("format"),
1009 | parse("ranged") ,=>, parsed("ranged")
1010 | )
1011 |
1012 | test.fact("full source file", fgo.Parse("foo.go", `
1013 | package foo
1014 | import(
1015 | b "bar/baz"
1016 | ff "foo/faz/fedudle"
1017 | )
1018 |
1019 | var x = b.bbb("blah blah")
1020 | func FooBar(iii, jjj) {
1021 | ff.fumanchu(
1022 | {
1023 | OOO: func(m,n) {str(m,n)},
1024 | PPP: func(m,n) {
1025 | str(m,n)
1026 | },
1027 | QQQ: qq
1028 | }
1029 | )
1030 | }
1031 | `) ,=>, str(
1032 | `(ns foo (:gen-class) (:require [bar.baz :as b] [foo.faz.fedudle :as ff])) (set! *warn-on-reflection* true) (def ^:private x (b/bbb "blah blah")) (defn Foo-bar [iii jjj] (ff/fumanchu {:ooo (fn [m n] (str m n)) :ppp (fn [m n] (str m n)) :qqq qq }))`
1033 | ))
1034 |
1035 | test.fact("full source file with async", fgo.Parse("foo.go", `
1036 | package foo
1037 | import(
1038 | b "bar/baz"
1039 | ff "foo/faz/fedudle"
1040 | )
1041 |
1042 | var x = b.bbb("blah blah")
1043 | func FooBar(iii, jjj) {
1044 | ff.fumanchu(
1045 | {
1046 | OOO: func(m,n) {str(m,n)},
1047 | PPP: func(m,n) {
1048 | go str(m,n)
1049 | },
1050 | QQQ: qq
1051 | }
1052 | )
1053 | }
1054 | `) ,=>, str(
1055 | `(ns foo (:gen-class) (:require [bar.baz :as b] [foo.faz.fedudle :as ff] `,
1056 | requireAsync,
1057 | `)) (set! *warn-on-reflection* true) (def ^:private x (b/bbb "blah blah")) (defn Foo-bar [iii jjj] (ff/fumanchu {:ooo (fn [m n] (str m n)) :ppp (fn [m n] (go (str m n))) :qqq qq }))`
1058 | ))
1059 |
1060 |
1061 | test.fact("Escaped string terminator",
1062 | parse(`"aaa\"aaa"`), =>, parsed(`"aaa\"aaa"`)
1063 | )
1064 |
1065 | test.fact("tail recursion",
1066 | parse(`loop(){a;recur()}`), =>, parsed(`(loop [] a (recur))`),
1067 | parse(`loop(a=b){c;recur(d)}`), =>, parsed(`(loop [a b] c (recur d))`),
1068 | parse(`loop(a=b,c=d){e;recur(f,g)}`), =>, parsed(`(loop [a b c d] e (recur f g))`)
1069 | )
1070 |
1071 | test.fact("short anonymous functions",
1072 | parseNoPretty(`func{s}`), =>, parsedNoPretty(`(fn [] s)`),
1073 | parseNoPretty(`func{a+1}`), =>, parsedNoPretty(`#(+ a 1)`),
1074 | parseNoPretty(`func{$1+$1}`), =>, parsedNoPretty(`#(+ %1 %1)`),
1075 | parseNoPretty(`func{$1+$2}`), =>, parsedNoPretty(`#(+ %1 %2)`),
1076 | parseNoPretty(`func{$1 + $1}`), =>, parsedNoPretty(`#(+ %1 %1)`),
1077 | parseNoPretty(`func{$1 + $2}`), =>, parsedNoPretty(`#(+ %1 %2)`),
1078 | parseNoPretty(`func{str(...$*)}`), =>, parsedNoPretty(`#(apply str %&)`)
1079 |
1080 | )
1081 |
1082 | test.fact("Effective Go",
1083 | parse(`if a := b; f(a) {c}`),
1084 | =>,
1085 | parsed("(let [a b] (when (f a) c))"),
1086 |
1087 | parse(`if err := file.Chmod(0664); err != nil {
1088 | log.Print(err)
1089 | err
1090 | }`, ["file", "log"], []),
1091 | =>,
1092 | parsed("(let [err (file/Chmod 436)] (when (not= err nil) (do (log/Print err) err)))",
1093 | ["file", "log"],[])
1094 | )
1095 |
1096 | test.fact("interface",
1097 | parse(`type Ia interface{
1098 | f(a, b)
1099 | g()
1100 | }`),
1101 | =>, parsed(`(defprotocol Ia (f [this a b]) (g [this]))`)
1102 | )
1103 |
1104 | test.fact("An interface defining a sliceable object",
1105 | parse(`type ISliceable interface{
1106 | slice(s int, e int)
1107 | sliceCount() int
1108 | }`),
1109 | =>, parsed(`(defprotocol ISliceable (slice [this ^int s ^int e]) (^long slice-count [this]))`)
1110 | )
1111 |
1112 | test.fact("interface with three methods",
1113 | parse(`
1114 |
1115 | type Interface interface {
1116 | // Len is the number of elements in the collection.
1117 | Len() int
1118 | // Less reports whether the element with
1119 | // index i should sort before the element with index j.
1120 | Less(i, j int) boolean
1121 | // Swap swaps the elements with indexes i and j.
1122 | Swap(i, j int)
1123 | }
1124 | `), =>, parsed(str(
1125 | `(defprotocol Interface`,
1126 | ` (^long Len [this])`,
1127 | ` (^boolean Less [this i ^int j])`,
1128 | ` (Swap [this i ^int j]))`))
1129 | )
1130 |
1131 | //test.fact("",
1132 | // parse(`
1133 | //type Sequence []int
1134 | //`), =>, parsed(`(defrecord Sequence [??]`)
1135 | //)
1136 |
1137 | // test.fact("dispatch",
1138 | // parse(`func (v Vertex) Manh() float64 { return v->X + v->X }`),
1139 | // =>, parsed(str(
1140 | // `(defprotocol __ManhProtocol (Manh [v]))`,
1141 | // ` (extend-type Vertex __ManhProtocol (Manh [v] (+ (. v X) (. v Y))))`
1142 | // )),
1143 |
1144 | // parse(`func (v Vertex) plusX(x) float64 { return x + v->X}`),
1145 | // =>, parsed(str(
1146 | // `(defprotocol __plusXProtocol (PlusX [v x]))`,
1147 | // ` (extend-type Vertex __plusXProtocol (PlusX [v x] (+ (. v X) x)))`
1148 | // ))
1149 | // )
1150 |
1151 | test.fact("implements",
1152 |
1153 | parse(`implements Ia func (Ty) f(a) {b}`, [], ["a.Ia"]),
1154 | =>, parsed(`(extend-type Ty Ia (f [this a] b))`, [], ["a Ia"]),
1155 |
1156 | parse(`implements Ia func(Ty)(f(a) {b}; g() {c})`, [], ["a.Ia"]),
1157 | =>, parsed(`(extend-type Ty Ia (f [this a] b) (g [this] c))`, [], ["a Ia"])
1158 | )
1159 |
1160 | test.fact("Methods required by sort.Interface",
1161 | parse(`
1162 | implements Interface
1163 | func (Sequence) (
1164 | Len() int {
1165 | len(this)
1166 | }
1167 | Less(i, j int) boolean {
1168 | this[i] < this[j]
1169 | }
1170 | Swap(i, j int) {
1171 | this += {i: this[j], j: this[i]}
1172 | }
1173 | )
1174 | `, [], ["sort.Interface"]),
1175 | =>, parsed(str(
1176 | `(extend-type Sequence Interface`,
1177 | ` (^long Len [this] (count this))`,
1178 | ` (^boolean Less [this i ^long j] (< (nth this i) (nth this j)))`,
1179 | ` (Swap [this i ^long j] (assoc this i (nth this j) j (nth this i))))`),
1180 | [], ["sort Interface"])
1181 | )
1182 |
1183 | test.fact("Method for printing - sorts the elements before printing.",
1184 | parse(`
1185 | implements Stringer
1186 | func (Sequence) String() String {
1187 | str("[", " " join sort.Sort(this), "]")
1188 | }
1189 |
1190 | `, ["sort"], ["fmt.Stringer"]), =>, parsed(str(
1191 | `(extend-type Sequence Stringer`,
1192 | ` (^String String [this]`,
1193 | ` (str "[" (join " " (sort/Sort this)) "]")))`
1194 | ), ["sort"], ["fmt Stringer"]))
1195 |
1196 | test.fact("struct",
1197 | parse(`type TreeNode struct{val; l; r}`),
1198 | =>,
1199 | parsed(str(`(defrecord TreeNode [val l r]`,
1200 | ` Object (toString [this] (str "{" val " " l " " r "}")))`)),
1201 |
1202 | parse(`type TreeNode struct{}; type TreeNode struct{val; l TreeNode; r TreeNode}`),
1203 | =>,
1204 | parsed(str(`(defrecord TreeNode []) (defrecord TreeNode [val ^TreeNode l ^TreeNode r]`,
1205 | ` Object (toString [this] (str "{" val " " l " " r "}")))`))
1206 | )
1207 |
1208 | test.fact("switch",
1209 | parse(`switch {case a: b; case c: d; default: e}`),
1210 | =>, parsed(`(cond a b c d :else e)`),
1211 |
1212 | parse(`switch x.(type) {case String: x; case Integer: str(x*x); default: str(x)}`),
1213 | =>, parsed(`(cond (instance? String x) x (instance? Integer x) (str (* x x)) :else (str x))`),
1214 |
1215 | parse(`switch x {case A: b; case C: d; default: e}`),
1216 | =>, parsed(`(case x :a b :c d e)`),
1217 |
1218 | parse(`switch x {case P, Q, R: b; case S, T, U: d; default: e}`),
1219 | =>, parsed(`(case x (:p :q :r) b (:s :t :u) d e)`)
1220 | )
1221 | test.fact("switch with pre-expression",
1222 | parse(`switch {case a: b; case c: d; default: e}`),
1223 | =>, parsed(`(cond a b c d :else e)`),
1224 |
1225 | parse(`switch x.(type) {case String: x; case Integer: str(x*x); default: str(x)}`),
1226 | =>, parsed(`(cond (instance? String x) x (instance? Integer x) (str (* x x)) :else (str x))`),
1227 |
1228 | parse(`switch x := bar; x {case A: b; case C: d; default: e}`),
1229 | =>, parsed(`(let [x bar] (case x :a b :c d e))`),
1230 |
1231 | parse(`switch x := bar(); x {case P, Q, R: b; case S, T, U: d; default: e}`),
1232 | =>, parsed(`(let [x (bar)] (case x (:p :q :r) b (:s :t :u) d e))`)
1233 | )
1234 |
1235 | test.fact("Error if external package not imported",
1236 | parse("huh.bar"),
1237 | =>, test.throws(Exception, `package "huh" in huh.bar does not appear in imports []`),
1238 |
1239 | parse("huh.bar", ["aaa", "bbb"], []),
1240 | =>, test.throws(Exception, `package "huh" in huh.bar does not appear in imports [bbb, aaa]`)
1241 | )
1242 |
1243 | test.fact("Error if import not used",
1244 | parse("1234", "aaa"),
1245 | =>, test.throws(Exception, `Packages imported but never used: [aaa]`),
1246 |
1247 | parse("1234", ["aaa", "bbb"], []),
1248 | =>, test.throws(Exception, /Packages imported but never used/),
1249 |
1250 | parse("aaa.xxx", ["aaa", "bbb"], []),
1251 | =>, test.throws(Exception, `Packages imported but never used: [bbb]`),
1252 |
1253 | parse("1234", [], ["a.Aaa", "b.Bbb"]),
1254 | =>, test.throws(Exception, /Types imported but never used/),
1255 |
1256 | parse("Aaa::xxx", [], ["a.Aaa", "b.Bbb"]),
1257 | =>, test.throws(Exception, `Types imported but never used: [Bbb]`)
1258 | )
1259 |
1260 | test.fact("import type",
1261 | compileString("joy/java.go", `
1262 | package java
1263 | import type (
1264 | java.util.{HashMap, List}
1265 | java.util.concurrent.atomic.AtomicLong
1266 | )
1267 |
1268 | new HashMap({"happy?": true})
1269 | new AtomicLong(42)
1270 | new List()
1271 | `),
1272 | =>,
1273 | `(ns joy.java (:gen-class) (:import (java.util HashMap List) (java.util.concurrent.atomic AtomicLong))) (set! *warn-on-reflection* true) (HashMap. {"happy?" true}) (AtomicLong. 42) (List.)`
1274 | )
1275 |
1276 | test.fact("assoc",
1277 | parse(`x += {AA: aaa, BB: bbb}`), =>, parsed(`(assoc x :aa aaa :bb bbb)`)
1278 | )
1279 |
1280 | test.fact("dissoc",
1281 | parse(`x -= {AA: aaa, BB: bbb}`), =>, parsed(`(dissoc x :aa aaa :bb bbb)`)
1282 | )
1283 |
1284 | test.fact("assoc-in",
1285 | parse(`x += {4 AAA 6 8: aaa }`), =>, parsed(`(assoc-in x [4 :aaa 6 8] aaa)`)
1286 | )
1287 |
1288 | test.fact("Vertex struct",
1289 | parse(`type Vertex struct {
1290 | Lat, Long float64
1291 | }
1292 | `), =>, parsed(str(`(defrecord Vertex [^double Lat ^double Long]`,
1293 | ` Object (toString [this] (str "{" Lat " " Long "}")))`))
1294 | )
1295 |
1296 | test.fact("struct literal",
1297 | parse(`Vertex{
1298 | 40.68433, -74.39967
1299 | }`,[],["a.Vertex"]), =>, parsed(`(Vertex. 40.68433 (- 74.39967))`,[],["a Vertex"])
1300 | )
1301 |
1302 | test.fact("struct literal with trailing comma",
1303 | parse(`Vertex{
1304 | 40.68433, -74.39967,
1305 | }`,[],["a.Vertex"]), =>, parsed(`(Vertex. 40.68433 (- 74.39967))`,[],["a Vertex"])
1306 | )
1307 |
1308 | test.fact("Calling function variadically",
1309 | parse(`foo(...args)`), =>, parsed(`(apply foo args)`),
1310 | parse(`foo(a, b, ...args)`), =>, parsed(`(apply foo a b args)`)
1311 | )
1312 |
1313 | test.fact("Can have comment delimiter in string",
1314 | parse(`"aaa//bbb"`), =>, parsed(`"aaa//bbb"`)
1315 | )
1316 |
1317 | test.fact("Can have double-quotes in backtick string",
1318 | parse(`"aaa//bbb"`), =>, parsed(`"aaa//bbb"`)
1319 | )
1320 |
1321 |
1322 | //test.fact("type assertion",
1323 | // parse(`a.(string)`), =>, parsed(`[x (instance? String x)]`)
1324 | //)
1325 |
1326 | //test.fact("",
1327 | // parse(``), =>, parsed(``),
1328 | //)
1329 |
--------------------------------------------------------------------------------
/test/funcgo/fundamental_test.go:
--------------------------------------------------------------------------------
1 | package fundamental_test
2 | import (
3 | test "midje/sweet"
4 | fgoc "funcgo/main"
5 | )
6 |
7 | func parse(expr) {
8 | fgoc.CompileString("foo.go", "package foo;" str expr)
9 | }
10 |
11 | test.fact("func",
12 | parse("func foo(b,c){d;e}"), =>, parse("\\defn-\\(foo,[b,c],do(d,e))"),
13 | parse("func Foo(b,c){d;e}"), =>, parse("defn(Foo,[b,c],do(d,e))"),
14 | parse("func Foo(b,c){d;e}"), =>, parse("func Foo(b,c){d;e}")
15 | )
16 |
17 | test.fact("funcform",
18 | parse("func a(b,c){d;e}"), =>, parse("something(a,[b,c],do(d,e))")
19 | )
20 |
21 | test.fact("if",
22 | parse("if a{b;c}"), =>, parse(`\when\(a,do(b,c))`),
23 | parse("if a{b;c}else{d;e}"), =>, parse(`\if\(a,do(b,c),do(d,e))`),
24 | parse("if a{b}else{d;e}"), =>, parse(`\if\(a,b,do(d,e))`),
25 | parse("if a(b,c){d;e}"), =>, parse(`\when\(a(b,c),do(d,e))`),
26 | parse("if a(b){d;e}"), =>, parse(`\when\(a(b),do(d,e))`)
27 | )
28 |
29 | test.fact("const",
30 | parse("{const(a=b;c=d)e;f}"), =>, parse("let([a,b,c,d],e,f)")
31 | )
32 |
33 | test.fact(":=",
34 | parse("{a:=b;c:=d;e;f}"), =>, parse("let([a,b, c,d], e,f)"),
35 | parse("{x,y:=a,b;e}"), =>, parse("let([x,a, y,b], e)"),
36 | parse("{x,y,z:=a,b,c;e}"), =>, parse("let([x,a, y,b, z,c], e)"),
37 | parse("{x,y,z,w:=a,b,c,d;e}"), =>, parse("let([x,a, y,b, z,c, w,d], e)"),
38 | parse("{a,b,c,d,e,f,g,h,i,j:=1,2,3,4,5,6,7,8,9,10;v}"),
39 | =>, parse("let([a,1, b,2, c,3, d,4, e,5, f,6, g,7, h,8, i,9, j,10], v)"),
40 | )
41 |
--------------------------------------------------------------------------------
/test/funcgo/goscript_test.go:
--------------------------------------------------------------------------------
1 | package goscript_test
2 | import (
3 | test "midje/sweet"
4 | fgo "funcgo/core"
5 | )
6 |
7 |
8 | test.fact("Clojure script import",
9 | fgo.Parse("p.gos", `
10 | package p
11 | import(
12 | ef "enfocus/core"
13 | )
14 | import macros(
15 | em "enfocus/macros"
16 | )
17 |
18 | ef.someFunction
19 | em.someMacro
20 | `),
21 | =>,
22 | str(
23 | `(ns p (:require [enfocus.core :as ef]) (:require-macros [enfocus.macros :as em]))`,
24 | ` ef/some-function em/some-macro`
25 | )
26 | )
27 |
28 | func parse(expr) {
29 | fgo.Parse("foo.gos", "package foo;" str expr)
30 | }
31 |
32 | func parsed(expr) {
33 | str("(ns foo ) ", expr)
34 | }
35 |
36 |
37 | test.fact("symbol",
38 | parse(`apple`), =>, parsed(`apple`)
39 | )
40 |
41 | test.fact("enfocus",
42 | fgo.Parse("fgosite/client.gos", `
43 | package client
44 | import (
45 | ef "enfocus/core"
46 | "enfocus/effects"
47 | "enfocus/events"
48 | "clojure/browser/repl"
49 | )
50 | import macros(
51 | "enfocus/macros"
52 | )
53 | ef.a
54 | effects.b
55 | events.c
56 | repl.d
57 | macros.e
58 | `
59 | ), =>, str(
60 | `(ns fgosite.client`,
61 | ` (:require [enfocus.core :as ef]`,
62 | ` [enfocus.effects :as effects]`,
63 | ` [enfocus.events :as events]`,
64 | ` [clojure.browser.repl :as repl])`,
65 | ` (:require-macros [enfocus.macros :as macros]))`,
66 | ` ef/a effects/b events/c repl/d macros/e`
67 | )
68 | )
69 |
--------------------------------------------------------------------------------
/test/funcgo/joy.go:
--------------------------------------------------------------------------------
1 | package joy
2 | import(
3 | test "midje/sweet"
4 | )
5 | import type (
6 | java.util.concurrent.{ExecutorService, Executors}
7 | )
8 |
9 |
10 | var matrix = [
11 | [1,2,3],
12 | [4,5,6],
13 | [7,8,9]
14 | ]
15 |
16 | test.fact("getIn",
17 | matrix getIn [1,2],
18 | =>, 6
19 | )
20 |
21 | test.fact("assoc assoc",
22 | {
23 | row := matrix[1] += {2: X}
24 | matrix += {1: row}
25 | },
26 | =>, [
27 | [1,2,3],
28 | [4,5,X],
29 | [7,8,9]
30 | ]
31 | )
32 |
33 | test.fact("assoc-in",
34 | matrix += {1 2: X},
35 | =>, [
36 | [1,2,3],
37 | [4,5,X],
38 | [7,8,9]
39 | ]
40 | )
41 |
42 | test.fact("updateIn",
43 | updateIn(matrix, [1,2], *, 100),
44 | =>, [
45 | [1,2,3],
46 | [4,5,600],
47 | [7,8,9]
48 | ]
49 | )
50 |
51 | func onBoard(size) {
52 | func(yx) {
53 | func{-1 < $1 && $1 < size} isEvery yx
54 | }
55 | }
56 |
57 | func neighbors(size, yx) {
58 | neighbors([[-1,0], [1,0], [0,-1], [0,1]], size, yx)
59 | } (deltas, size, yx) {
60 | addYx := func{map(+, yx, $1)}
61 | unfiltered := addYx map deltas
62 | onBoard(size) filter unfiltered
63 | }
64 |
65 | test.fact("neighbors works",
66 | func{matrix getIn $1} map neighbors(3, [0,0]),
67 | =>, [4,2]
68 | )
69 |
70 |
71 | var pool ExecutorService = Executors::newFixedThreadPool(
72 | 2 + Runtime::getRuntime()->availableProcessors()
73 | )
74 |
75 | func mutateDothreads(f, {threadCount:THREADS, execCount:TIMES} ) {
76 | for _ := times threadCount {
77 | multipleCalls Runnable := func{ for _ := times execCount { f() } }
78 | pool->submit(multipleCalls)
79 | }
80 | }
81 |
82 | var initialBoard = [
83 | [EE, KW, EE],
84 | [EE, EE, EE],
85 | [EE, KB, EE]
86 | ]
87 |
88 | func boardMap(f, bd) {
89 | vec(
90 | func{vec(for s := lazy $1 { f(s) })} map bd
91 | )
92 | }
93 |
94 | func doReset() {
95 | var board = boardMap(ref, initialBoard)
96 | var toMove = ref([[KB, [2, 1]], [KW, [0,1]]])
97 | //toMove := &[[KB, [2, 2]], [KW, [0,1]]]
98 | var numMoves = ref(0)
99 | //numMoves := &0
100 | }
101 |
102 | func kingMoves(yx){
103 | neighbors(
104 | [[-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1]],
105 | 3,
106 | yx
107 | )
108 | }
109 |
110 | func isGoodMove(to, enemySq){
111 | if (to != enemySq) {
112 | to
113 | }
114 | }
115 |
116 | var rotateCount = ref(0)
117 | func fakeShuffle(xs) {
118 | dosync(rotateCount alter inc)
119 | {
120 | shift := (*rotateCount) % count(xs)
121 | (shift drop xs) concat (shift take xs)
122 | }
123 | }
124 |
125 | {
126 | // Fake shuffle to make test deterministic
127 | shuffle := fakeShuffle
128 |
129 | test.fact("fake shuffle is actually rotate",
130 | shuffle([111,222,333,444]), =>, [222,333,444,111],
131 | shuffle([111,222,333,444]), =>, [333,444,111,222],
132 | shuffle([111,222,333,444]), =>, [444,111,222,333],
133 | shuffle([111,222,333,444]), =>, [111,222,333,444],
134 | shuffle([111,222,333,444]), =>, [222,333,444,111],
135 | shuffle([111,222,333,444]), =>, [333,444,111,222],
136 | shuffle([111,222,333,444]), =>, [444,111,222,333],
137 | shuffle([111,222,333,444]), =>, [111,222,333,444]
138 | )
139 |
140 |
141 | func chooseMove([[mover, mpos], [_, enemyPos]]) {
142 | [
143 | mover,
144 | func{$1 isGoodMove enemyPos} some shuffle(kingMoves(mpos))
145 | ]
146 | }
147 |
148 | }
149 |
150 | doReset()
151 | test.fact("initial state",
152 | boardMap(deref, board),
153 | =>, [
154 | [EE, KW, EE],
155 | [EE, EE, EE],
156 | [EE, KB, EE]
157 | ],
158 | *toMove,
159 | =>, [[KB, [2, 1]], [KW, [0,1]]],
160 |
161 | *numMoves,
162 | =>, 0
163 | )
164 |
165 |
166 | test.fact("Coordinated, synchronous change using alter",
167 | 5 take repeatedly(func{chooseMove(*toMove)}),
168 | =>, [ // starting at [KB, [2,1]]
169 | [KB, [2,2]],
170 | [KB, [1,0]],
171 | [KB, [1,1]],
172 | [KB, [1,2]],
173 | [KB, [2,0]]
174 | ]
175 | )
176 |
177 |
178 |
179 | func place(from, to){to}
180 |
181 | func movePiece([piece, dest], [[_, src], _]) {
182 | getIn(board, dest) alter func{place($1, piece)}
183 | getIn(board, src) alter func{place($1, EE)}
184 | numMoves alter inc
185 | }
186 |
187 | func updateToMove(move) {
188 | toMove alter func{vector(second($1), move)}
189 | }
190 |
191 | func makeMove() {
192 | dosync(
193 | {
194 | move := chooseMove(*toMove)
195 | movePiece(move, *toMove)
196 | updateToMove(move)
197 | }
198 | )
199 | }
200 |
201 | doReset()
202 |
203 | test.fact("using alter to update a Ref",
204 | makeMove(),
205 | =>, [[KW, [0,1]], [KB, [2,2]]],
206 |
207 | boardMap(deref, board),
208 | =>, [
209 | [EE, KW, EE],
210 | [EE, EE, EE],
211 | [EE, EE, KB]
212 | ],
213 |
214 | *numMoves,
215 | =>, 1
216 | )
217 |
218 | // test.fact("An interface defining a sliceable object",
219 | // // {
220 | // // type ISliceable interface{
221 | // // slice(s int, e int)
222 | // // sliceCount() int
223 | // // }
224 |
225 | // // dumb := reify(
226 | // // \`ISliceable`,
227 | // // slice([_, s, e], [EMPTY]),
228 | // // sliceCount([_], 42)
229 | // // )
230 |
231 | // // dumb->slice(1, 2)
232 | // // },
233 | // // =>, [EMPTY],
234 |
235 | // {
236 | // type ISliceable interface{
237 | // slice(s int, e int)
238 | // sliceCount() int
239 | // }
240 | // dumb := new implements ISliceable func (
241 | // slice(s, e) { [EMPTY] },
242 | // sliceCount() {42}
243 | // )
244 |
245 | // dumb->slice(1, 2)
246 | // },
247 | // =>, [EMPTY],
248 |
249 | // dumb->sliceCount(),
250 | // =>, 42
251 | // )
252 |
253 | //Define recored type
254 | type TreeNode struct{val; l; r}
255 |
256 | test.fact("Persistent binary tree built of records",
257 | {
258 | // Add to tree
259 | func xconj(t, v) {
260 | //println("xconj(", t, ",", v, ")")
261 | switch {
262 | case isNil(t):
263 | new TreeNode(v, nil, nil)
264 | case v < VAL(t):
265 | new TreeNode(VAL(t), L(t) xconj v, R(t))
266 | default:
267 | new TreeNode(VAL(t), L(t), R(t) xconj v)
268 | }
269 | }
270 |
271 | // conver trees to seq
272 | func xseq(t) {
273 | //println("xseq(", t, ")")
274 | if t {
275 | concat(xseq(L(t)), [VAL(t)], xseq(R(t)))
276 | }
277 | }
278 |
279 | var sampleTree = reduce(xconj, nil, [3, 5, 2, 4, 6])
280 |
281 | xseq(sampleTree)
282 | },
283 |
284 | =>, [2, 3, 4, 5, 6]
285 | )
286 |
287 |
288 | type FIXO interface{
289 | fixoPush(value)
290 | fixoPop()
291 | fixoPeek()
292 | }
293 |
294 | implements FIXO
295 | func (TreeNode) fixoPush(value) { xconj(this, value) }
296 |
297 | test.fact("Protocols",
298 | xseq(fixoPush(sampleTree, 5/2)),
299 | =>, [2, 5/2, 3, 4, 5, 6]
300 | )
301 |
302 | // test.fact("Method implementations in defrecord",
303 | // {
304 | // type NodeDArbre struct {val; l; r}
305 |
306 | // implements FIXO
307 | // func (funcgo.joy.NodeDArbre) (
308 | // fixoPush(v) {
309 | // if v < this->val {
310 | // new funcgo.joy.NodeDArbre(
311 | // this->val,
312 | // this->l->fixoPush(v),
313 | // this->r)
314 | // }else{
315 | // new funcgo.joy.NodeDArbre(
316 | // this->val,
317 | // this->l,
318 | // this->r->fixoPush(v))
319 | // }
320 | // }
321 | // fixoPeek(){
322 | // if this->l {
323 | // this->l->fixoPeek()
324 | // } else {
325 | // this->val
326 | // }
327 | // }
328 | // fixoPop(){
329 | // if this->l {
330 | // new funcgo.joy.NodeDArbre(
331 | // this->val,
332 | // this->l->fixoPop(this->l),
333 | // this->r)
334 | // } else {
335 | // this->r
336 | // }
337 | // }
338 | // )
339 |
340 | // var sampleTree2 = reduce(
341 | // \`fixoPush`,
342 | // new funcgo.joy.NodeDArbre(3, nil, nil),
343 | // [5, 2, 4, 6])
344 | // xseq(sampleTree2)
345 | // },
346 | // =>, [2, 3, 4, 5, 6]
347 | // )
348 |
349 |
--------------------------------------------------------------------------------
/test/funcgo/misc.go:
--------------------------------------------------------------------------------
1 | package misc
2 |
3 | import test "midje/sweet"
4 |
5 | // func mostFrequentN(n, items) {
6 | // ->>(
7 | // items,
8 | // frequencies,
9 | // sortBy(val),
10 | // reverse,
11 | // take(n),
12 | // map(first)
13 | // )
14 | // }
15 |
16 | func mostFrequentN(n, items) {
17 | first map (n take reverse(val sortBy frequencies(items)))
18 | }
19 |
20 | test.fact("can find 2 most common items in a sequence",
21 | mostFrequentN(2, ["a", "bb", "a", "x", "bb", "ccc", "dddd", "dddd", "bb", "dddd", "bb"]),
22 | =>, ["bb", "dddd"]
23 | )
24 |
--------------------------------------------------------------------------------
/test/funcgo/reference/contract.go:
--------------------------------------------------------------------------------
1 | package contract
2 |
3 | // Set these booleans to control whether checking happens
4 | var (
5 | CheckPreconditions = true
6 | CheckPostconditions = false
7 | )
8 |
9 | // precondition is a function returning bool
10 | func Require(precondition) {
11 | if CheckPreconditions {
12 | if !precondition() {
13 | throw(new AssertionError("Precondition failed for " str precondition))
14 | }
15 | }
16 | }
17 |
18 | // postcondition is function of single value, returning boolean
19 | func Ensure(postcondition, result) {
20 | if CheckPostconditions {
21 | if !postcondition(result) {
22 | throw(new AssertionError("Postcondition failed for " str result))
23 | }
24 | }
25 | result
26 | }
27 |
--------------------------------------------------------------------------------
/test/funcgo/reference/hello.go:
--------------------------------------------------------------------------------
1 | package hello
2 | println("Hello World")
3 |
--------------------------------------------------------------------------------
/test/funcgo/reference/larger.go:
--------------------------------------------------------------------------------
1 | package larger
2 |
3 | import (
4 | test "midje/sweet"
5 | )
6 |
7 | test.fact("can concatenate strings",
8 | {
9 | greeting := "Hello "
10 | name := "Eamonn"
11 | str(greeting, name)
12 | }, =>, "Hello Eamonn"
13 | )
14 |
15 | test.fact("can use infix when calling two-parameter-function",
16 | {
17 | greeting := "Hello "
18 | name := "Eamonn"
19 | greeting str name
20 | }, =>, "Hello Eamonn"
21 | )
22 |
--------------------------------------------------------------------------------
/test/funcgo/reference/matrix.go:
--------------------------------------------------------------------------------
1 | // Operations on matrices, stored as sequences of row vectors
2 | package matrix
3 | import "clojure/core"
4 | exclude ( +, * )
5 |
6 | // Begin private functions
7 |
8 | func colCount(m) { count(first(m)) }
9 | func dotProduct(v1, v2) {
10 | core.+ reduce map(core.*, v1, v2)
11 | }
12 | func vecSum(a, b) { map(core.+, a, b) }
13 |
14 | // Begin exported functions
15 |
16 | func +(m1, m2) { map(vecSum, m1, m2) }
17 |
18 | func Transpose(m) {
19 | firstColumnT := first map m
20 | if colCount(m) == 1 {
21 | [firstColumnT]
22 | } else {
23 | firstColumnT cons Transpose(rest map m)
24 | }
25 | }
26 |
27 | func *(m1, m2) {
28 | for m1row := lazy m1 {
29 | for m2col := lazy Transpose(m2) {
30 | m1row dotProduct m2col
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/funcgo/reference/operator.go:
--------------------------------------------------------------------------------
1 | package operator
2 | import test "midje/sweet"
3 | exclude ( ^, + )
4 |
5 | func ^(x, y) {
6 | Math::pow(x, y)
7 | }
8 |
9 | func +(x, y) {
10 | x str y
11 | }
12 |
13 | test.fact("Can redefine existing operators",
14 | 2 ^ 3, =>, 8.0,
15 | 10 ^ 2, =>, 100.0,
16 | "foo" + "bar", =>, "foobar"
17 | )
18 |
19 | func \**\(x, y) {
20 | Math::pow(x, y)
21 | }
22 |
23 | test.fact("Can use new operators",
24 | 2 \**\ 3, =>, 8.0,
25 | 10 \**\ 2, =>, 100.0
26 | )
27 |
--------------------------------------------------------------------------------
/test/funcgo/reference/reference.go:
--------------------------------------------------------------------------------
1 | package reference
2 |
3 | import (
4 | test "midje/sweet"
5 | "funcgo/reference/matrix"
6 | "clojure/tools/logging"
7 | "cljLoggingConfig/log4j"
8 | )
9 | import type (
10 | java.util.ArrayList
11 | java.lang.Iterable
12 | )
13 | log4j.mutateSetLogger(LEVEL, WARN)
14 |
15 | var a = 55
16 | var b = 66
17 |
18 | test.fact("Most things are Expression",
19 |
20 | {
21 | smaller := if a < b {
22 | a
23 | } else {
24 | b
25 | }
26 | smaller
27 | }, =>, 55,
28 |
29 | {
30 | digits := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
31 | squares := for d := lazy digits {
32 | d * d
33 | }
34 | squares
35 | }, =>, [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
36 | )
37 |
38 | test.fact("syntax",
39 |
40 | withOutStr(
41 | if a < b {
42 | println("Conclusion:")
43 | println(a, "is smaller than", b)
44 | }
45 | ), =>, `Conclusion:
46 | 55 is smaller than 66
47 | `,
48 | withOutStr(
49 | if a < b { println("Conclusion:"); println(a, "is smaller than", b) }
50 | ), =>, `Conclusion:
51 | 55 is smaller than 66
52 | `
53 | )
54 |
55 | test.fact("Looping with tail recursion",
56 |
57 | {
58 | func sumSquares(vec) {
59 | if isEmpty(vec) {
60 | 0
61 | } else {
62 | x := first(vec)
63 | x * x + sumSquares(rest(vec))
64 | }
65 | }
66 | sumSquares([3, 4, 5, 10])
67 | }, =>, 150,
68 |
69 |
70 | {
71 | func sumSquares(vec) {
72 | func sumSq(accum, v) {
73 | if isEmpty(v) {
74 | accum
75 | } else {
76 | x := first(v)
77 | recur(accum + x * x, rest(v))
78 | }
79 | }
80 | sumSq(0, vec)
81 | }
82 | sumSquares([3, 4, 5, 10])
83 | }, =>, 150,
84 |
85 | {
86 | func sumSquares(vec) {
87 | loop(accum=0, v=vec) {
88 | if isEmpty(v) {
89 | accum
90 | } else {
91 | x int := first(v)
92 | recur(accum + x * x, rest(v))
93 | }
94 | }
95 | }
96 | sumSquares([3, 4, 5, 10])
97 | }, =>, 150,
98 |
99 | loop(vec=[], count = 0) {
100 | if count < 10 {
101 | v := vec conj count
102 | recur(v, count + 1)
103 | } else {
104 | vec
105 | }
106 | }, =>, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
107 |
108 | )
109 |
110 | test.fact("Curly Brace Blocks",
111 | {
112 | product := {
113 | logging.info("doing the multiplication")
114 | 100 * 100
115 | }
116 | product
117 | }, =>, 10000
118 | )
119 |
120 | test.fact("Type switch",
121 |
122 | {
123 | func plus(a, b) {
124 | switch a.(type) {
125 | case Number: a + b
126 | case String: a str b
127 | case Iterable: vec(a concat b)
128 | default: str("Unknown types for ", a, " and ", b)
129 | }
130 | }
131 |
132 | [
133 | 2 plus 3,
134 | 0.5 plus 0.75,
135 | [P, Q] plus [R, S, T],
136 | "foo" plus "bar",
137 | FOO plus BAR
138 | ]
139 | }, =>, [
140 | 5,
141 | 1.25,
142 | [P, Q, R, S, T],
143 | "foobar",
144 | "Unknown types for :foo and :bar"
145 | ]
146 | )
147 |
148 | func truthTable(op) {
149 | [
150 | false op false,
151 | false op true,
152 | true op false,
153 | true op true
154 | ]
155 | }
156 |
157 | test.fact("operators",
158 |
159 | 3 * 4 , =>, 12,
160 | 16.0 / 2.0 , =>, 8.0,
161 | 12 % 5 , =>, 2,
162 | 0xCAFE << 4 , =>, 0xCAFE0,
163 | 0xCAFE >> 4 , =>, 0xCAF,
164 | 0xFACADE & 0xFFF000 , =>, 0xFAC000,
165 | 0xFACADE &^ 0x000FFF , =>, 0xFAC000,
166 | 3 + 4 , =>, 7,
167 | 3 - 4 , =>, -1,
168 | 0xFACADE | 0xFFF000 , =>, 0xFFFADE,
169 | 0xFACADE ^ 0x000FFF , =>, 0xFAC521,
170 | 5 == 5 , =>, true,
171 | 5 == 4 , =>, false,
172 | 5 == "5" , =>, false,
173 | "5" == "5" , =>, true,
174 | [A, B, C] == [A, B, C] , =>, true,
175 | [A, B, C] == [A, B, DD] , =>, false,
176 | {A:1,B:2} == {A:1,B:2} , =>, true,
177 | {A:1,B:2} == {A:1,B:9} , =>, false,
178 | 5 > 5 , =>, false,
179 | 5 > 4 , =>, true,
180 | 5 < 5 , =>, false,
181 | 5 < 4 , =>, false,
182 | 5 >= 5 , =>, true,
183 | 5 >= 4 , =>, true,
184 | 5 <= 5 , =>, true,
185 | 5 <= 4 , =>, false,
186 | truthTable(func{$1 && $2}), =>, [false, false, false, true],
187 | truthTable(func{$1 || $2}), =>, [false, true, true, true]
188 | )
189 |
190 | var (
191 | a = randInt(100)
192 | b = randInt(100)
193 | c = randInt(100)
194 | p = randInt(2) == 0
195 | q = randInt(2) == 0
196 | r = randInt(2) == 0
197 | )
198 |
199 | test.fact("you can transpose matrices",
200 | {
201 | m := [
202 | [1, 2, 3],
203 | [4, 5, 6]
204 | ]
205 |
206 | matrix.Transpose(m)
207 |
208 | }, =>, [
209 | [1, 4],
210 | [2, 5],
211 | [3, 6]
212 | ]
213 | )
214 |
215 |
216 | test.fact("you can multiply matrices together",
217 | {
218 | a := [[3, 4]]
219 | b := [
220 | [5],
221 | [6]
222 | ]
223 |
224 | a matrix.* b
225 |
226 | }, =>, [[39]],
227 |
228 | {
229 | m := [
230 | [1, 2, 3],
231 | [4, 5, 6]
232 | ]
233 | mT := [
234 | [1, 4],
235 | [2, 5],
236 | [3, 6]
237 | ]
238 |
239 | m matrix.* mT
240 |
241 | }, =>, [
242 | [14, 32],
243 | [32, 77]
244 | ]
245 | )
246 |
247 | test.fact("vars",
248 | {
249 | var (
250 | pp = 111
251 | qq = 222
252 | )
253 | pp + qq
254 | }, =>, 333,
255 |
256 | {
257 | var rr = 111
258 | var ss = 222
259 | rr + ss
260 | }, =>, 333,
261 |
262 | {
263 | var tt int = 111
264 | var uu string = "foo"
265 | uu str tt
266 | }, =>, "foo111"
267 | )
268 |
269 | test.fact("for",
270 |
271 | {
272 | fib := [1, 1, 2, 3, 5, 8]
273 | fibSquared := for x := lazy fib {
274 | x * x
275 | }
276 |
277 | fibSquared
278 | }, =>, [1, 1, 4, 9, 25, 64],
279 |
280 | {
281 | fib := [1, 1, 2, 3, 5, 8]
282 | fibSquared := func(x){ x * x } map fib
283 |
284 | fibSquared
285 | }, =>, [1, 1, 4, 9, 25, 64],
286 |
287 | withOutStr({
288 | fib := [1, 1, 2, 3, 5, 8]
289 | for x := lazy fib {
290 | print(" ", x)
291 | }
292 | }), =>, "",
293 |
294 | withOutStr({
295 | fib := [1, 1, 2, 3, 5, 8]
296 | func(x){ print(" ", x) } map fib
297 | }), =>, "",
298 |
299 | withOutStr({
300 | fib := [1, 1, 2, 3, 5, 8]
301 | for x := range fib {
302 | print(" ", x)
303 | }
304 | }), =>, " 1 1 2 3 5 8",
305 |
306 | withOutStr({
307 | for x := times 10 {
308 | print(" ", x)
309 | }
310 | }), =>, " 0 1 2 3 4 5 6 7 8 9"
311 |
312 | )
313 |
314 | test.fact("exceptions",
315 | {
316 | try {
317 |
318 | throw(new AssertionError("foo"))
319 |
320 | } catch OutOfMemoryError e {
321 | "out of memory"
322 | } catch AssertionError e {
323 | "assertion failed: " str e->getMessage()
324 | } finally {
325 | "useless"
326 | }
327 | }, =>, "assertion failed: foo",
328 |
329 | {
330 | mutex := make(chan, 1)
331 | dangerous := new ArrayList()
332 | dangerous->add(0)
333 | mutex <- true // initialize mutex
334 | // for _ := times 1000 {
335 | for count := times 1000 {
336 | thread {
337 | <-mutex // grab mutex
338 | try {
339 | i := dangerous->get(0)
340 | dangerous->set(0, i + 1)
341 | } finally {
342 | mutex <- true // release mutex
343 | }
344 | }
345 | }
346 | Thread::sleep(100)
347 | dangerous->get(0)
348 | }, =>, 1000
349 | )
350 |
351 | test.fact("select (1)",
352 |
353 | {
354 | c1 := make(chan, 1)
355 | c2 := make(chan, 1)
356 | thread {
357 | Thread::sleep(10)
358 | c1 <- 111
359 | }
360 | c2 <- 222
361 | select {
362 | case x = <-c1:
363 | x * 100
364 | case x = <-c2:
365 | x * 100
366 | }
367 | }, =>, 22200,
368 |
369 | {
370 | c1 := make(chan, 1)
371 | c2 := make(chan, 1)
372 | go func(){
373 | Thread::sleep(10)
374 | c1 <- 111
375 | }()
376 | c2 <- 222
377 | select {
378 | case x = <-c1:
379 | x * 100
380 | case x = <-c2:
381 | x * 100
382 | }
383 | }, =>, 22200
384 | )
385 |
386 | test.fact("select (2)",
387 | {
388 | c1 := make(chan, 1)
389 | c2 := make(chan, 1)
390 | go {
391 | for i := times(10000) { var x = i }
392 | c1 <: 111
393 | }
394 | go {
395 | c2 <: 222
396 | }
397 | <-go {
398 | select {
399 | case x = <:c1:
400 | x * 100
401 | case x = <:c2:
402 | x * 100
403 | }
404 | }
405 | }, =>, 22200,
406 |
407 | {
408 | c1 := make(chan)
409 | c2 := make(chan)
410 | go func(){
411 | Thread::sleep(10)
412 | <-c1
413 | }()
414 | go func(){
415 | <-c2
416 | }()
417 | select {
418 | case c1 <- 111:
419 | "wrote to c1"
420 | case c2 <- 222:
421 | "wrote to c2"
422 | }
423 | }, =>, "wrote to c2",
424 |
425 | )
426 |
427 | test.fact("select (3)",
428 | {
429 | c1 := make(chan)
430 | c2 := make(chan)
431 | go {
432 | <:c2
433 | }
434 | go {
435 | for i := times(10000) { var x = i }
436 | <:c1
437 | }
438 | <-go {
439 | select {
440 | case c1 <: 111:
441 | "wrote to c1"
442 | case c2 <: 222:
443 | "wrote to c2"
444 | }
445 | }
446 | }, =>, "wrote to c2",
447 |
448 | {
449 | c1 := make(chan, 1)
450 | c2 := make(chan)
451 | thread {
452 | Thread::sleep(20)
453 | c1 <- 111
454 | }
455 | thread {
456 | Thread::sleep(10)
457 | <-c2
458 | }
459 | select {
460 | case x = <-c1:
461 | x * 100
462 | case c2 <- 222:
463 | "wrote to c2"
464 | default:
465 | "nothing ready"
466 | }
467 | }, =>, "nothing ready",
468 |
469 | )
470 |
471 | test.fact("infix",
472 |
473 | str("foo", "bar"),
474 | =>, "foobar",
475 |
476 | "foo" str "bar",
477 | =>, "foobar"
478 |
479 | )
480 |
481 | test.fact("precedence",
482 | ^a * b , =>, (^a) * b,
483 | a * b - c , =>, (a * b) - c,
484 | a + b < c , =>, (a + b) < c,
485 | a < b && b < c , =>, (a < b) && (b < c),
486 | p && q || r , =>, (p && q) || r,
487 | p || q str r , =>, (p || q) str r
488 | )
489 |
490 | test.fact("can destructure",
491 |
492 | {
493 | vec := [111, 222, 333, 444]
494 | [a, b, c, d] := vec
495 |
496 | b
497 | }, =>, 222,
498 |
499 | {
500 | vec := [111, 222, 333, 444]
501 |
502 | func theSecond([a, b, c, d]) {
503 | b
504 | }
505 |
506 | theSecond(vec)
507 | }, =>, 222,
508 |
509 | {
510 | vec := [111, 222, 333, 444]
511 | [first, rest...] := vec
512 |
513 | rest
514 | }, =>, [222, 333, 444],
515 |
516 | {
517 | dict := {AAA: 11, BBB: 22, CCC: 33, DDD: 44}
518 | {c: CCC, a: AAA} := dict
519 |
520 | c
521 | }, =>, 33,
522 |
523 | {
524 | dict := {AAA: 11, BBB: 22, CCC: 33, DDD: 44}
525 |
526 | func extractCCC({c: CCC}) {
527 | c
528 | }
529 |
530 | extractCCC(dict)
531 | }, =>, 33,
532 |
533 | {
534 | planets := [
535 | {NAME: "Mercury", RADIUS_KM: 2440},
536 | {NAME: "Venus", RADIUS_KM: 6052},
537 | {NAME: "Earth", RADIUS_KM: 6371},
538 | {NAME: "Mars", RADIUS_KM: 3390}
539 | ]
540 | [_, _, {earthRadiusKm: RADIUS_KM}, _] := planets
541 |
542 | earthRadiusKm
543 | }, =>, 6371
544 | )
545 |
546 |
--------------------------------------------------------------------------------
/test/funcgo/reference/row.go:
--------------------------------------------------------------------------------
1 | // Mathematical row vector
2 |
3 | package row
4 |
5 | exclude (+, *)
6 | import (
7 | "clojure/core"
8 | "funcgo/reference/contract"
9 | )
10 |
11 |
12 | func +(a, b) {
13 | map(core.+, a, b)
14 | }
15 |
16 | // dot product
17 | func *(v1, v2) {
18 | contract.Require(func{ count(v1) == count(v2) })
19 | core.+ reduce map(core.*, v1, v2)
20 | }
21 |
--------------------------------------------------------------------------------
/test/funcgo/reference/row_test.go:
--------------------------------------------------------------------------------
1 | package row_test
2 |
3 | import (
4 | test "midje/sweet"
5 | // "funcgo/reference/contract"
6 | r "funcgo/reference/row"
7 | )
8 | ϵ := 1e-10
9 | a := [2.0, 3.0, 4.0]
10 | b := [3.0, 4.0, 5.0]
11 |
12 | // contract.CheckPreconditions = true
13 |
14 |
15 | test.fact("a vector supports addition",
16 | a r.+ b, =>, [5.0, 7.0, 9.0]
17 | )
18 |
19 | test.fact("a vector supports dot product",
20 | a r.* b, =>, test.roughly(6.0 + 12.0 + 20.0, ϵ)
21 | )
22 |
--------------------------------------------------------------------------------
/test/funcgo/webmunged.go:
--------------------------------------------------------------------------------
1 | package webmunged
2 |
3 | func _main(args…) {
4 | println(“Hello, World from Functional Go”)
5 | }
6 |
--------------------------------------------------------------------------------
/test/funcgo/wiki.go:
--------------------------------------------------------------------------------
1 | package wiki
2 | import(
3 | test "midje/sweet"
4 | )
5 |
6 | test.fact("Anonymous Functions",
7 | func(x){x * x}(3),
8 | =>, 9,
9 |
10 | map(func{list($1, inc($2))}, [1, 2, 3], [1, 2, 3]),
11 | =>, [[1, 2], [2, 3], [3, 4]],
12 |
13 | map(func(x, y){list(x, inc(y))}, [1, 2, 3], [1, 2, 3]),
14 | [[1, 2], [2, 3], [3, 4]],
15 |
16 | func{list($1, inc($1))} map [1, 2, 3],
17 | =>, [[1, 2], [2, 3], [3, 4]],
18 |
19 | func(x){list(x, inc(x))} map [1, 2, 3],
20 | =>, [[1, 2], [2, 3], [3, 4]],
21 |
22 | func{str(...$*)}("Hello"),
23 | =>, "Hello",
24 |
25 | func{str(...$*)}("Hello", ", ", "World!"),
26 | =>, "Hello, World!"
27 | )
28 |
--------------------------------------------------------------------------------