carbonate 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +669 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/carbonate.gemspec +34 -0
- data/examples/user.crb +10 -0
- data/exe/crb2rb +62 -0
- data/lib/carbonate.rb +57 -0
- data/lib/carbonate/parser.rb +749 -0
- data/lib/carbonate/version.rb +3 -0
- metadata +203 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: c37e5aef9fbc0edef926681f59511b35b7946ea6
|
|
4
|
+
data.tar.gz: 7d624dc7a2fd2ce5f04d07bc691f0f57aafdd297
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1fed03d1b276ec929440206087b8f377dadbdc1e0fc58bf882a4d5bfcd550dd40584208200515c7022f7891be2889bb969dd9a8f84b9aaa55440dd97845ced59
|
|
7
|
+
data.tar.gz: 444f03109191cc664b619c16d947222dd1e5011ec9905c41eabeb5f5a6a750dbbac33e160a139fecdbc97dfcb44e2aed509bcfb9e82bee6d744bfc425bef7614
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec', all_on_start: true do
|
|
2
|
+
require 'guard/rspec/dsl'
|
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
|
4
|
+
|
|
5
|
+
rspec = dsl.rspec
|
|
6
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
|
7
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
|
8
|
+
watch(rspec.spec_files)
|
|
9
|
+
|
|
10
|
+
ruby = dsl.ruby
|
|
11
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
|
12
|
+
end
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Vsevolod Romashov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
# Carbonate
|
|
2
|
+
|
|
3
|
+
Carbonate is a Lisp dialect heavily influenced by Clojure. It is transpiled into Ruby code.
|
|
4
|
+
|
|
5
|
+
Carbonate tries to cover all of Ruby's functionality while giving a more concise form to the code.
|
|
6
|
+
|
|
7
|
+
Here's what it looks like:
|
|
8
|
+
|
|
9
|
+
``` clojure
|
|
10
|
+
(defclass User
|
|
11
|
+
(defmethod initialize [first-name last-name email]
|
|
12
|
+
(def @first-name first-name)
|
|
13
|
+
(def @last-name last-name)
|
|
14
|
+
(def @email email))
|
|
15
|
+
(defmethod full-name []
|
|
16
|
+
(join [@first_name @last_name]))
|
|
17
|
+
(defmethod each-name []
|
|
18
|
+
(each [@first-name @last-name] #([name] (@yield name)))))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The code above is equvalent to the following ruby:
|
|
22
|
+
|
|
23
|
+
``` ruby
|
|
24
|
+
class User
|
|
25
|
+
def initialize(first_name, last_name, email)
|
|
26
|
+
@first_name = first_name
|
|
27
|
+
@last_name = last_name
|
|
28
|
+
@email = email
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def full_name
|
|
32
|
+
[@first_name, @last_name].join
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def each_name
|
|
36
|
+
[@first_name, @last_name].each do |name|
|
|
37
|
+
yield name
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
``` ruby
|
|
46
|
+
# Gemfile
|
|
47
|
+
gem 'carbonate'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
``` sh
|
|
51
|
+
$ bundle
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
Currently there are 2 ways to run Carbonate code: convert it to Ruby statically and use the resulting `.rb` files as you normally would or evaluate Carbonate source files dynamically.
|
|
57
|
+
|
|
58
|
+
### Converting from Carbonate to Ruby
|
|
59
|
+
|
|
60
|
+
The gem ships with a `crb2rb` utility that converts Carbonate source code into Ruby source code. You can use it to convert a Carbonate file to a Ruby file:
|
|
61
|
+
|
|
62
|
+
``` sh
|
|
63
|
+
$ crb2rb < source.crb > target.rb
|
|
64
|
+
# or
|
|
65
|
+
$ crb2rb -i source.crb -o target.rb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Using Carbonate sources directly
|
|
69
|
+
|
|
70
|
+
Carbonate source code can be transpiled and instantly evaluated by Ruby code. This allows you to plug it in a Ruby application and use it right away.
|
|
71
|
+
|
|
72
|
+
Carbonate gives 2 functions to transpile and evaluate Carbonate sources: `Carbonate.require` and `Carbonate.require_relative` - they work exactly like their counterparts from Ruby's `Kernel` but they are searching for a `.crb` file instead of a `.rb` one, and they transpile it to Ruby before evaluating.
|
|
73
|
+
|
|
74
|
+
## Syntax
|
|
75
|
+
|
|
76
|
+
### Literal values
|
|
77
|
+
|
|
78
|
+
Numbers in Carbonate look exactly like they do in Ruby:
|
|
79
|
+
|
|
80
|
+
``` clojure
|
|
81
|
+
127
|
|
82
|
+
-32
|
|
83
|
+
3.14
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Strings are always double-quoted and support all the usual control characters like `\n` and `\t`. Double quotes have to be escaped with a backslash. String interpolation is not supported.
|
|
87
|
+
|
|
88
|
+
``` clojure
|
|
89
|
+
"Hello world!"
|
|
90
|
+
"Line 1\nLine 2\n\tIndented line"
|
|
91
|
+
"Yukihiro \"Matz\" Matsumoto"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Symbols look similarly to Ruby symbols but use dashes (`-`) instead of underscores (`_`):
|
|
95
|
+
|
|
96
|
+
``` clojure
|
|
97
|
+
:north
|
|
98
|
+
:user-name
|
|
99
|
+
:exists?
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Regular expressions are written as double-quoted strings prefixed with a pound sign (`#`). Like strings, they support control characters and require you to escape double quotes.
|
|
103
|
+
|
|
104
|
+
``` clojure
|
|
105
|
+
#"[A-Za-z]+"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`true`, `false`, and `nil` are the same as in Ruby.
|
|
109
|
+
|
|
110
|
+
Arrays are enclosed within brackets (`[]`) but do not require commas between elements. In fact, comma is treated as a whitespace character in Carbonate - you can use it but you don't have to.
|
|
111
|
+
|
|
112
|
+
``` clojure
|
|
113
|
+
[1 2 3]
|
|
114
|
+
["Yukihiro Matsumoto" "Rich Hickey"]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Hashes are represented by key-value pairs inside curly brackets (`{}`). In contrast to Ruby, there are no delimiters between a key and a value. Separating pairs with commas can sometimes be useful to keep readability.
|
|
118
|
+
|
|
119
|
+
``` clojure
|
|
120
|
+
{:type :book
|
|
121
|
+
:title "SICP"
|
|
122
|
+
:authors ["Harold Abelson" "Gerald Jay Sussman" "Julie Sussman"]}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Sets are also enclosed within curly brackets but prefixed with a pound sign.
|
|
126
|
+
|
|
127
|
+
``` clojure
|
|
128
|
+
#{"one" "two" "three"}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Be sure to `require 'set'` to use them - sets live in a standard library package in Ruby (read on to learn how to call methods like `require` in Carbonate).
|
|
132
|
+
|
|
133
|
+
Ranges look exactly like in Ruby - values separated with two dots for inclusive ranges and values separated with three dots for exclusive ones:
|
|
134
|
+
|
|
135
|
+
``` clojure
|
|
136
|
+
"a".."z"
|
|
137
|
+
0...10
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Constants are written down using the same CamelCase'd words as in Ruby but `.` is used as a delimiter:
|
|
141
|
+
|
|
142
|
+
``` clojure
|
|
143
|
+
Carbonate.Parser
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Explicit top-level constants are prefixed with `.` (exactly like they are with `::` in Ruby):
|
|
147
|
+
|
|
148
|
+
``` clojure
|
|
149
|
+
.Hash
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The current object known as `self` in Ruby is written down as `@` in Carbonate.
|
|
153
|
+
|
|
154
|
+
### Calling functions/methods
|
|
155
|
+
|
|
156
|
+
In Lisp function calls are written down using prefix notation in S-expressions; basically this means that every operation is a list of elements enclosed within parentheses where the first element represents the function and all the other elements are it's arguments.
|
|
157
|
+
|
|
158
|
+
Here's some basic arithmetic:
|
|
159
|
+
|
|
160
|
+
``` clojure
|
|
161
|
+
(+ 2 2)
|
|
162
|
+
(- 2 1)
|
|
163
|
+
(* 2 3)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Of course S-expressions can be nested:
|
|
167
|
+
|
|
168
|
+
``` clojure
|
|
169
|
+
(/ (* 3 4) 2)
|
|
170
|
+
(** (- 4 2) 3)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Comparison operators also mirror the Ruby ones with an exception of equality - it is represented with a single `=`:
|
|
174
|
+
|
|
175
|
+
``` clojure
|
|
176
|
+
(= x y)
|
|
177
|
+
(!= x y)
|
|
178
|
+
(< 1 2)
|
|
179
|
+
(> 2 1)
|
|
180
|
+
(>= 2 2)
|
|
181
|
+
(<= 2 2)
|
|
182
|
+
(<=> a b)
|
|
183
|
+
(=== a b)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Binary operators `&`, `|`, `^`, `~`, `<<` and `>>` follow their Ruby counterparts. Logic operators, on the other hand, are slightly changed:
|
|
187
|
+
|
|
188
|
+
``` clojure
|
|
189
|
+
(and (= x y) (!= x z))
|
|
190
|
+
(or (> x z) (> y z))
|
|
191
|
+
(! false)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Variables and assignment
|
|
195
|
+
|
|
196
|
+
Carbonate supports local and instance variables. They look like in Ruby but use `-` separator instead of `_`:
|
|
197
|
+
|
|
198
|
+
``` clojure
|
|
199
|
+
local-variable
|
|
200
|
+
@instance-variable
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
You can assign a value to a variable using `def` keyword:
|
|
204
|
+
|
|
205
|
+
``` clojure
|
|
206
|
+
(def a 1)
|
|
207
|
+
(def @age 35)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This also works for constants:
|
|
211
|
+
|
|
212
|
+
``` clojure
|
|
213
|
+
(def DAYS-IN-WEEK 7)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Carbonate also supports the so-called conditional assignment (`||=` in Ruby) using `def-or` keyword:
|
|
217
|
+
|
|
218
|
+
``` clojure
|
|
219
|
+
(def-or name "Steve")
|
|
220
|
+
(def-or @city "NYC")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
(This is not supported with constants for obvious reasons)
|
|
224
|
+
|
|
225
|
+
There are a few more special cases. First you can assign a value to an object's attribute (like you would do with `user.name = 'John'` in Ruby):
|
|
226
|
+
|
|
227
|
+
``` clojure
|
|
228
|
+
(def user.name "John")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Second you can both read from and write to an array or a hash member using essentially the same syntax as in Ruby:
|
|
232
|
+
|
|
233
|
+
``` clojure
|
|
234
|
+
user[:name]
|
|
235
|
+
(def user[:name] "John")
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Both attribute and collection member writers support conditional assignment with the aforementioned `def-or`.
|
|
239
|
+
|
|
240
|
+
### Conditional statements
|
|
241
|
+
|
|
242
|
+
Almost any code contains conditional execution - you won't go far without `if` & `unless` statements so here they are:
|
|
243
|
+
|
|
244
|
+
``` clojure
|
|
245
|
+
(if (> 2 1) "2 is greater" "1 is greater")
|
|
246
|
+
(if (= x 5) "x is 5")
|
|
247
|
+
(unless (>= age 18) "too young")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
As you can see from the snippet above, `if` can be used in 2 variations: if you pass it a condition and 2 more forms (S-expressions, literal values, variables or anything that returns a value) the first form will be used for "truthy" condition value and the second for "falsy". If you just pass one form it will be used for the "truthy" case.
|
|
251
|
+
|
|
252
|
+
`unless` doesn't have a 2-form mode - it's a bad practice anyway - so the only form after the condition will be used for the falsy condition.
|
|
253
|
+
|
|
254
|
+
If you need to group several statements inside one `if` or `unless` statement you can use a `do` statement - it allows you to join several S-expressions into one:
|
|
255
|
+
|
|
256
|
+
``` clojure
|
|
257
|
+
(if (valid? user)
|
|
258
|
+
(do
|
|
259
|
+
(save user)
|
|
260
|
+
(@puts (name user))))
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Carbonate also has a `case` statement:
|
|
264
|
+
|
|
265
|
+
``` clojure
|
|
266
|
+
(case x
|
|
267
|
+
1 "one"
|
|
268
|
+
2 "two")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Pretty self-explanatory. It also supports an else clause as the last form of the statement:
|
|
272
|
+
|
|
273
|
+
``` clojure
|
|
274
|
+
(case lang
|
|
275
|
+
"clojure" "great!"
|
|
276
|
+
"ruby" "cool"
|
|
277
|
+
"crap")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Loop statements
|
|
281
|
+
|
|
282
|
+
There are 2 main loop statements in Carbonate: `while` and `until`. Both of them take a condition as the first argument and the loop body as the second:
|
|
283
|
+
|
|
284
|
+
``` clojure
|
|
285
|
+
(while (< x 5)
|
|
286
|
+
(def x (+ x 1)))
|
|
287
|
+
(until (>= x 5)
|
|
288
|
+
(def x (+ x 1)))
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Calling methods
|
|
292
|
+
|
|
293
|
+
The most common construct in Ruby code is a method call. Carbonate allows you to call a method within an S-expression consisting of the method name and the receiver object:
|
|
294
|
+
|
|
295
|
+
``` clojure
|
|
296
|
+
(name user)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
This is equivalent to the following Ruby:
|
|
300
|
+
|
|
301
|
+
``` ruby
|
|
302
|
+
user.name
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
*(all Carbonate snippets are followed by equivalent Ruby snippets later on)*
|
|
306
|
+
|
|
307
|
+
If you need to pass some arguments to a method you do so after the receiver:
|
|
308
|
+
|
|
309
|
+
``` clojure
|
|
310
|
+
(include? [1 2 3] 4)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
``` ruby
|
|
314
|
+
[1, 2, 3].include?(4)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Carbonate supports splat arguments - if you have some `Enumerable` collection you can pass it to the method as several separate arguments. Ruby uses `*` for that goal, Carbonate uses `&` (note that `&` and the argument are separated by a space):
|
|
318
|
+
|
|
319
|
+
``` clojure
|
|
320
|
+
(add-tags article & tags)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
``` ruby
|
|
324
|
+
article.add_tags(*tags)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Class methods are invoked a little differently - the method name is prefixed with the class name separated with `/`, and all following elements are method's arguments:
|
|
328
|
+
|
|
329
|
+
``` clojure
|
|
330
|
+
(User/count)
|
|
331
|
+
(User/find-by {:first-name "John"})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
``` ruby
|
|
335
|
+
User.count
|
|
336
|
+
User.find_by(first_name: 'John')
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Method calls without an explicit receiver (which are implicitly called on `self`) are written with a method name prefixed by `@`:
|
|
340
|
+
|
|
341
|
+
``` clojure
|
|
342
|
+
(@attr-reader :first-name)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
``` ruby
|
|
346
|
+
attr_reader :first_name
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Carbonate offers a special syntax for class constructor calls - it looks like a class name followed by a dot:
|
|
350
|
+
|
|
351
|
+
``` clojure
|
|
352
|
+
(User. {:name "John"})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
``` ruby
|
|
356
|
+
User.new(name: 'John')
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Another special case is `super` - a call to parent class' respective method:
|
|
360
|
+
|
|
361
|
+
``` clojure
|
|
362
|
+
(super)
|
|
363
|
+
(super "parameter")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
``` ruby
|
|
367
|
+
super()
|
|
368
|
+
super('parameter')
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
The tricky part here is a call to `super` with implicit parameters - as you may know, calling `super` without parameters and without parentheses in Ruby actually passes it all the parameters passed to the enclosing method, and if you need to force `super` call without parameters you have to write `super()`. The latter is written as just `(super)` in Carbonate and the former is `(zsuper)` (Zero-arity super).
|
|
372
|
+
|
|
373
|
+
Like in Ruby, you can pass a block to a method - it is enclosed within parentheses prefixed with `#`, and the first element inside the parentheses is the block parameters list:
|
|
374
|
+
|
|
375
|
+
``` clojure
|
|
376
|
+
(map users #([user] (upcase (name user))))
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
``` ruby
|
|
380
|
+
users.map do |user|
|
|
381
|
+
user.name.upcase
|
|
382
|
+
end
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
You don't have to specify an empty parameters list if your block has no parameters - just type your block body inside `#(...)`:
|
|
386
|
+
|
|
387
|
+
``` clojure
|
|
388
|
+
(times 5 #(@puts "Hello"))
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
``` ruby
|
|
392
|
+
5.times { puts 'Hello' }
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
If you want to use the `Symbol#to_proc` trick you can just pass the symbol after `#` (like `&`, it needs to be separated from the following element with a space):
|
|
396
|
+
|
|
397
|
+
``` clojure
|
|
398
|
+
(map users # :name)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
``` ruby
|
|
402
|
+
users.map(&:name)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
This works with plain procs too:
|
|
406
|
+
|
|
407
|
+
``` clojure
|
|
408
|
+
(each users # some-proc)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
``` ruby
|
|
412
|
+
users.each(&some_proc)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Defining methods
|
|
416
|
+
|
|
417
|
+
A method definition consists of the `defmethod` keyword, then the name of the method, then the parameters list and the method body. As usual, all the identifiers (method name, parameters and variables) use `-` instead of `_`. It looks like this:
|
|
418
|
+
|
|
419
|
+
``` clojure
|
|
420
|
+
(defmethod full-name []
|
|
421
|
+
(join [@first-name @last-name]))
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
``` ruby
|
|
425
|
+
def full_name
|
|
426
|
+
[@first_name, @last_name].join
|
|
427
|
+
end
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The parameters list supports splat arguments and blocks with basically the same syntax as in method invocation:
|
|
431
|
+
|
|
432
|
+
``` clojure
|
|
433
|
+
(defmethod iterate [a b c & d # block]
|
|
434
|
+
(@puts a b c d)
|
|
435
|
+
(@yield a)
|
|
436
|
+
(@yield b)
|
|
437
|
+
(@yield c)
|
|
438
|
+
(each d # block))
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
``` ruby
|
|
442
|
+
def iterate(a, b, c, *d, &block)
|
|
443
|
+
puts a, b, c, d
|
|
444
|
+
yield a
|
|
445
|
+
yield b
|
|
446
|
+
yield c
|
|
447
|
+
d.each(&block)
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Carbonate also supports default parameter values - just put the parameter name with a default value inside the brackets:
|
|
452
|
+
|
|
453
|
+
``` clojure
|
|
454
|
+
(defmethod int-to-string [int [base 10]]
|
|
455
|
+
(to-s int base))
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
``` ruby
|
|
459
|
+
def int_to_string(int, base = 10)
|
|
460
|
+
int.to_s(base)
|
|
461
|
+
end
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
A `return` clause is used like this:
|
|
465
|
+
|
|
466
|
+
``` clojure
|
|
467
|
+
(defmethod nothing [] (return))
|
|
468
|
+
(defmethod name [] (return "John"))
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
``` ruby
|
|
472
|
+
def nothing
|
|
473
|
+
return
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def name
|
|
477
|
+
return 'John'
|
|
478
|
+
end
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Exception handling
|
|
482
|
+
|
|
483
|
+
If you need to use a `rescue` clause inside your method you can just put it at the end of the method body:
|
|
484
|
+
|
|
485
|
+
``` clojure
|
|
486
|
+
(defmethod read-file [path]
|
|
487
|
+
(File/read path)
|
|
488
|
+
(rescue Errno.ENOENT e
|
|
489
|
+
(@puts "No file found.")))
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
``` ruby
|
|
493
|
+
def read_file(path)
|
|
494
|
+
File.read(path)
|
|
495
|
+
rescue Errno::ENOENT => e
|
|
496
|
+
puts 'No file found.'
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
*(you can have as many `rescue` clauses as you want - just be sure to keep them at the end of method body)*
|
|
501
|
+
|
|
502
|
+
The `ensure` clause is available as well, you can put it after all `rescue` clauses (or at the end of the method body if you don't need to rescue anything):
|
|
503
|
+
|
|
504
|
+
``` clojure
|
|
505
|
+
(defmethod read-file [path]
|
|
506
|
+
(File/read path)
|
|
507
|
+
(rescue Errno.ENOENT e (@puts "No file found."))
|
|
508
|
+
(ensure (@puts "Tried to read a file.")))
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
``` ruby
|
|
512
|
+
def read_file(path)
|
|
513
|
+
File.read(path)
|
|
514
|
+
rescue Errno::ENOENT => e
|
|
515
|
+
puts 'No file found.'
|
|
516
|
+
ensure
|
|
517
|
+
puts 'Tried to read a file.'
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
There are cases when you need to intercept an exception from an arbitrary piece of code, not just method; a `try` statement can help you with that:
|
|
522
|
+
|
|
523
|
+
``` clojure
|
|
524
|
+
(try (File/read path)
|
|
525
|
+
(rescue Errno.ENOENT e
|
|
526
|
+
(@puts (message e))
|
|
527
|
+
(@raise)))
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
``` ruby
|
|
531
|
+
begin
|
|
532
|
+
File.read(path)
|
|
533
|
+
rescue Errno::ENOENT => e
|
|
534
|
+
puts e.message
|
|
535
|
+
raise
|
|
536
|
+
end
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
`try` allows you to put any number of `rescue` clauses and/or an `ensure` clause at the end, just like method bodies.
|
|
540
|
+
|
|
541
|
+
### Lambdas
|
|
542
|
+
|
|
543
|
+
Lambda definitions look a lot like method definitions - they have a parameters list and a body:
|
|
544
|
+
|
|
545
|
+
``` clojure
|
|
546
|
+
(-> [user] (email user))
|
|
547
|
+
(-> [user] (@p user) (save user))
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
``` ruby
|
|
551
|
+
-> (user) { user.email }
|
|
552
|
+
|
|
553
|
+
-> (user) do
|
|
554
|
+
p user
|
|
555
|
+
user.save
|
|
556
|
+
end
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
If the lambda doesn't have parameters you can omit them altogether:
|
|
560
|
+
|
|
561
|
+
``` clojure
|
|
562
|
+
(-> (@puts "Hello world!"))
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
``` ruby
|
|
566
|
+
-> { puts 'Hello world!' }
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Classes and modules
|
|
570
|
+
|
|
571
|
+
No serious Ruby application can exist without classes and modules. Carbonate allows defining classes with `defclass` keyword:
|
|
572
|
+
|
|
573
|
+
``` clojure
|
|
574
|
+
(defclass User
|
|
575
|
+
(defmethod initialize [first-name last-name]
|
|
576
|
+
(def @first-name first-name)
|
|
577
|
+
(def @last-name last-name))
|
|
578
|
+
(defmethod full-name []
|
|
579
|
+
(join [@first-name @last-name])))
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
``` ruby
|
|
583
|
+
class User
|
|
584
|
+
def initialize(first_name, last_name)
|
|
585
|
+
@first_name = first_name
|
|
586
|
+
@last_name = last_name
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def full_name
|
|
590
|
+
[@first_name, @last_name].join
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
If you need to specify the parent class you can use an already familiar syntax:
|
|
596
|
+
|
|
597
|
+
``` clojure
|
|
598
|
+
(defclass User < Base
|
|
599
|
+
(@include Naming))
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
``` ruby
|
|
603
|
+
class User < Base
|
|
604
|
+
include Naming
|
|
605
|
+
end
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
Similarly a module can be defined with `defmodule`:
|
|
609
|
+
|
|
610
|
+
``` clojure
|
|
611
|
+
(defmodule Naming
|
|
612
|
+
(@attr-reader :first-name :last-name)
|
|
613
|
+
(defmethod full-name []
|
|
614
|
+
(join [first-name last-name])))
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
``` ruby
|
|
618
|
+
module Naming
|
|
619
|
+
attr_reader :first_name, :last_name
|
|
620
|
+
|
|
621
|
+
def full_name
|
|
622
|
+
[first_name, last_name].join
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
When you want to open up an object's singleton class you need `<<-`:
|
|
628
|
+
|
|
629
|
+
``` clojure
|
|
630
|
+
(<<- user
|
|
631
|
+
(defmethod name []
|
|
632
|
+
@name))
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
``` ruby
|
|
636
|
+
class << user
|
|
637
|
+
def name
|
|
638
|
+
@name
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Acknowledgements
|
|
644
|
+
|
|
645
|
+
This project would not be possible without these wonderful libraries:
|
|
646
|
+
|
|
647
|
+
* [farcaller/rly](https://github.com/farcaller/rly)
|
|
648
|
+
* [whitequark/parser](https://github.com/whitequark/parser)
|
|
649
|
+
* [mbj/unparser](https://github.com/mbj/unparser)
|
|
650
|
+
|
|
651
|
+
## Roadmap
|
|
652
|
+
|
|
653
|
+
* improve parser performance
|
|
654
|
+
* add meaningful stack traces
|
|
655
|
+
* add macros support
|
|
656
|
+
|
|
657
|
+
## Development
|
|
658
|
+
|
|
659
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake[ spec]` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
660
|
+
|
|
661
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
662
|
+
|
|
663
|
+
## Contributing
|
|
664
|
+
|
|
665
|
+
Bug reports and pull requests are welcome on GitHub at [7even/carbonate](https://github.com/7even/carbonate).
|
|
666
|
+
|
|
667
|
+
## License
|
|
668
|
+
|
|
669
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|