├── .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 | Creative Commons License
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 | --------------------------------------------------------------------------------