ffast 0.1.9 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +104 -1
- data/.sourcelevel.yml +2 -0
- data/.travis.yml +1 -1
- data/Fastfile +59 -3
- data/README.md +228 -130
- data/bin/console +5 -0
- data/docs/experiments.md +2 -0
- data/docs/git.md +115 -0
- data/docs/ideas.md +0 -10
- data/docs/index.md +2 -0
- data/docs/sql-support.md +253 -0
- data/docs/videos.md +5 -1
- data/docs/walkthrough.md +135 -0
- data/fast.gemspec +24 -4
- data/lib/fast/cli.rb +102 -24
- data/lib/fast/experiment.rb +1 -2
- data/lib/fast/git.rb +101 -0
- data/lib/fast/shortcut.rb +13 -11
- data/lib/fast/sql/rewriter.rb +69 -0
- data/lib/fast/sql.rb +167 -0
- data/lib/fast/version.rb +1 -1
- data/lib/fast.rb +114 -17
- data/mkdocs.yml +10 -1
- metadata +66 -15
data/README.md
CHANGED
@@ -32,7 +32,7 @@ The current version of Fast covers the following token elements:
|
|
32
32
|
to build custom rules.
|
33
33
|
- `.<method-name>` - will call `<method-name>` from the `node`
|
34
34
|
|
35
|
-
The syntax is inspired by the [RuboCop Node Pattern](https://github.com/
|
35
|
+
The syntax is inspired by the [RuboCop Node Pattern](https://github.com/rubocop-hq/rubocop-ast/blob/master/lib/rubocop/ast/node_pattern.rb).
|
36
36
|
|
37
37
|
## Installation
|
38
38
|
|
@@ -53,48 +53,64 @@ to represent code called `s-expressions`.
|
|
53
53
|
|
54
54
|
For example, let's take an `Integer` in Ruby:
|
55
55
|
|
56
|
-
|
56
|
+
```ruby
|
57
|
+
1
|
58
|
+
```
|
57
59
|
|
58
60
|
It's corresponding s-expression would be:
|
59
61
|
|
60
|
-
|
62
|
+
```ruby
|
63
|
+
s(:int, 1)
|
64
|
+
```
|
61
65
|
|
62
66
|
`s` in `Fast` and `Parser` are a shorthand for creating an `Parser::AST::Node`.
|
63
67
|
Each of these nodes has a `#type` and `#children` contained in it:
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
```ruby
|
70
|
+
def s(type, *children)
|
71
|
+
Parser::AST::Node.new(type, children)
|
72
|
+
end
|
73
|
+
```
|
68
74
|
|
69
75
|
### Variable Assignments
|
70
76
|
|
71
77
|
Now let's take a look at a local variable assignment:
|
72
78
|
|
73
|
-
|
79
|
+
```ruby
|
80
|
+
value = 42
|
81
|
+
```
|
74
82
|
|
75
83
|
It's corresponding s-expression would be:
|
76
84
|
|
77
|
-
|
85
|
+
```ruby
|
86
|
+
ast = s(:lvasgn, :value, s(:int, 42))
|
87
|
+
```
|
78
88
|
|
79
89
|
If we wanted to find this particular assignment somewhere in our AST, we can use
|
80
90
|
Fast to look for a local variable named `value` with a value `42`:
|
81
91
|
|
82
|
-
|
92
|
+
```ruby
|
93
|
+
Fast.match?('(lvasgn value (int 42))', ast) # => true
|
94
|
+
```
|
83
95
|
|
84
96
|
### Wildcard Token
|
85
97
|
|
86
98
|
If we wanted to find a variable named `value` that was assigned any integer value
|
87
99
|
we could replace `42` in our query with an underscore ( `_` ) as a shortcut:
|
88
100
|
|
89
|
-
|
101
|
+
```ruby
|
102
|
+
Fast.match?('(lvasgn value (int _))', ast) # => true
|
103
|
+
```
|
90
104
|
|
91
105
|
### Set Inclusion Token
|
92
106
|
|
93
107
|
If we weren't sure the type of the value we're assigning, we can use our set
|
94
108
|
inclusion token (`{}`) from earlier to tell Fast that we expect either a `Float`
|
95
109
|
or an `Integer`:
|
96
|
-
|
97
|
-
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
Fast.match?('(lvasgn value ({float int} _))', ast) # => true
|
113
|
+
```
|
98
114
|
|
99
115
|
### All Matching Token
|
100
116
|
|
@@ -103,28 +119,38 @@ all matching token (`[]`) to express multiple conditions that need to be true.
|
|
103
119
|
In this case we don't want the value to be a `String`, `Hash`, or an `Array` by
|
104
120
|
prefixing all of the types with `!`:
|
105
121
|
|
106
|
-
|
122
|
+
```ruby
|
123
|
+
Fast.match?('(lvasgn value ([!str !hash !array] _))', ast) # => true
|
124
|
+
```
|
107
125
|
|
108
126
|
### Node Child Token
|
109
127
|
|
110
128
|
We can match any node with children by using the child token ( `...` ):
|
111
129
|
|
112
|
-
|
130
|
+
```ruby
|
131
|
+
Fast.match?('(lvasgn value ...)', ast) # => true
|
132
|
+
```
|
113
133
|
|
114
134
|
We could even match any local variable assignment combining both `_` and `...`:
|
115
135
|
|
116
|
-
|
136
|
+
```ruby
|
137
|
+
Fast.match?('(lvasgn _ ...)', ast) # => true
|
138
|
+
```
|
117
139
|
|
118
140
|
### Capturing the Value of an Expression
|
119
141
|
|
120
142
|
You can use `$` to capture the contents of an expression for later use:
|
121
143
|
|
122
|
-
|
144
|
+
```ruby
|
145
|
+
Fast.match?('(lvasgn value $...)', ast) # => [s(:int, 42)]
|
146
|
+
```
|
123
147
|
|
124
148
|
Captures can be used in any position as many times as you want to capture whatever
|
125
149
|
information you might need:
|
126
150
|
|
127
|
-
|
151
|
+
```ruby
|
152
|
+
Fast.match?('(lvasgn $_ $...)', ast) # => [:value, s(:int, 42)]
|
153
|
+
```
|
128
154
|
|
129
155
|
> Keep in mind that `_` means something not nil and `...` means a node with
|
130
156
|
> children.
|
@@ -135,44 +161,53 @@ You can also define custom methods to set more complicated rules. Let's say
|
|
135
161
|
we're looking for duplicated methods in the same class. We need to collect
|
136
162
|
method names and guarantee they are unique.
|
137
163
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
164
|
+
```ruby
|
165
|
+
def duplicated(method_name)
|
166
|
+
@methods ||= []
|
167
|
+
already_exists = @methods.include?(method_name)
|
168
|
+
@methods << method_name
|
169
|
+
already_exists
|
170
|
+
end
|
144
171
|
|
145
|
-
|
172
|
+
puts Fast.search_file('(def #duplicated)', 'example.rb')
|
173
|
+
```
|
146
174
|
|
147
175
|
The same principle can be used in the node level or for debugging purposes.
|
148
176
|
|
177
|
+
```ruby
|
149
178
|
require 'pry'
|
150
179
|
def debug(node)
|
151
180
|
binding.pry
|
152
181
|
end
|
153
182
|
|
154
183
|
puts Fast.search_file('#debug', 'example.rb')
|
155
|
-
|
184
|
+
```
|
156
185
|
If you want to get only `def` nodes you can also intersect expressions with `[]`:
|
157
186
|
|
158
|
-
|
187
|
+
```ruby
|
188
|
+
puts Fast.search_file('[ def #debug ]', 'example.rb')
|
189
|
+
```
|
159
190
|
|
160
191
|
### Methods
|
161
192
|
|
162
193
|
Let's take a look at a method declaration:
|
163
194
|
|
164
|
-
|
165
|
-
|
166
|
-
|
195
|
+
```ruby
|
196
|
+
def my_method
|
197
|
+
call_other_method
|
198
|
+
end
|
199
|
+
```
|
167
200
|
|
168
201
|
It's corresponding s-expression would be:
|
169
202
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
203
|
+
```ruby
|
204
|
+
ast =
|
205
|
+
s(:def, :my_method,
|
206
|
+
s(:args),
|
207
|
+
s(:send, nil, :call_other_method))
|
208
|
+
```
|
174
209
|
|
175
|
-
|
210
|
+
Note the node `(args)`. We can't use `...` to match it, as it
|
176
211
|
has no children (or arguments in this case), but we _can_ match it with a wildcard
|
177
212
|
`_` as it's not `nil`.
|
178
213
|
|
@@ -181,45 +216,55 @@ has no children (or arguments in this case), but we _can_ match it with a wildca
|
|
181
216
|
Let's take a look at a few other examples. Sometimes you have a chain of calls on
|
182
217
|
a single `Object`, like `a.b.c.d`. Its corresponding s-expression would be:
|
183
218
|
|
184
|
-
|
219
|
+
```ruby
|
220
|
+
ast =
|
221
|
+
s(:send,
|
222
|
+
s(:send,
|
185
223
|
s(:send,
|
186
|
-
s(:send,
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
:d)
|
224
|
+
s(:send, nil, :a),
|
225
|
+
:b),
|
226
|
+
:c),
|
227
|
+
:d)
|
228
|
+
```
|
192
229
|
|
193
230
|
### Alternate Syntax
|
194
231
|
|
195
232
|
You can also search using nested arrays with **pure values**, or **shortcuts** or
|
196
233
|
**procs**:
|
197
234
|
|
198
|
-
|
199
|
-
|
235
|
+
```ruby
|
236
|
+
Fast.match? [:send, [:send, '...'], :d], ast # => true
|
237
|
+
Fast.match? [:send, [:send, '...'], :c], ast # => false
|
238
|
+
```
|
200
239
|
|
201
240
|
Shortcut tokens like child nodes `...` and wildcards `_` are just placeholders
|
202
241
|
for procs. If you want, you can even use procs directly like so:
|
203
242
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
243
|
+
```ruby
|
244
|
+
Fast.match?([
|
245
|
+
:send, [
|
246
|
+
-> (node) { node.type == :send },
|
247
|
+
[:send, '...'],
|
248
|
+
:c
|
249
|
+
],
|
250
|
+
:d
|
251
|
+
], ast) # => true
|
252
|
+
```
|
212
253
|
|
213
254
|
This also works with expressions:
|
214
255
|
|
215
|
-
|
256
|
+
```ruby
|
257
|
+
Fast.match?('(send (send (send (send nil $_) $_) $_) $_)', ast) # => [:a, :b, :c, :d]
|
258
|
+
```
|
216
259
|
|
217
260
|
### Debugging
|
218
261
|
|
219
262
|
If you find that a particular expression isn't working, you can use `debug` to
|
220
263
|
take a look at what Fast is doing:
|
221
264
|
|
222
|
-
|
265
|
+
```ruby
|
266
|
+
Fast.debug { Fast.match?([:int, 1], s(:int, 1)) }
|
267
|
+
```
|
223
268
|
|
224
269
|
Each comparison made while searching will be logged to your console (STDOUT) as
|
225
270
|
Fast goes through the AST:
|
@@ -233,61 +278,83 @@ We can also dynamically interpolate arguments into our queries using the
|
|
233
278
|
interpolation token `%`. This works much like `sprintf` using indexes starting
|
234
279
|
from `1`:
|
235
280
|
|
236
|
-
|
281
|
+
```ruby
|
282
|
+
Fast.match? '(lvasgn %1 (int _))', ('a = 1'), :a # => true
|
283
|
+
```
|
237
284
|
|
238
285
|
## Using previous captures in search
|
239
286
|
|
240
287
|
Imagine you're looking for a method that is just delegating something to
|
241
288
|
another method, like this `name` method:
|
242
289
|
|
243
|
-
|
244
|
-
|
245
|
-
|
290
|
+
```ruby
|
291
|
+
def name
|
292
|
+
person.name
|
293
|
+
end
|
294
|
+
```
|
246
295
|
|
247
296
|
This can be represented as the following AST:
|
248
297
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
298
|
+
```
|
299
|
+
(def :name
|
300
|
+
(args)
|
301
|
+
(send
|
302
|
+
(send nil :person) :name))
|
303
|
+
```
|
253
304
|
|
254
305
|
We can create a query that searches for such a method:
|
255
306
|
|
256
|
-
|
307
|
+
```ruby
|
308
|
+
Fast.match?('(def $_ ... (send (send nil _) \1))', ast) # => [:name]
|
309
|
+
```
|
310
|
+
|
257
311
|
|
258
312
|
## Fast.search
|
259
313
|
|
260
314
|
Search allows you to go search the entire AST, collecting nodes that matches given
|
261
315
|
expression. Any matching node is then returned:
|
262
316
|
|
263
|
-
|
317
|
+
```ruby
|
318
|
+
Fast.search('(int _)', Fast.ast('a = 1')) # => s(:int, 1)
|
319
|
+
```
|
264
320
|
|
265
321
|
If you use captures along with a search, both the matching nodes and the
|
266
322
|
captures will be returned:
|
267
323
|
|
268
|
-
|
324
|
+
```ruby
|
325
|
+
Fast.search('(int $_)', Fast.ast('a = 1')) # => [s(:int, 1), 1]
|
326
|
+
```
|
327
|
+
|
269
328
|
|
270
329
|
You can also bind external parameters from the search:
|
271
330
|
|
272
|
-
|
331
|
+
```ruby
|
332
|
+
Fast.search('(int %1)', Fast.ast('a = 1'), 1) # => [s(:int, 1)]
|
333
|
+
```
|
273
334
|
|
274
335
|
## Fast.capture
|
275
336
|
|
276
337
|
To only pick captures and ignore the nodes, use `Fast.capture`:
|
277
338
|
|
278
|
-
|
339
|
+
```ruby
|
340
|
+
Fast.capture('(int $_)', Fast.ast('a = 1')) # => 1
|
341
|
+
```
|
279
342
|
|
280
343
|
## Fast.replace
|
281
344
|
|
282
345
|
Let's consider the following example:
|
283
346
|
|
284
|
-
|
285
|
-
|
286
|
-
|
347
|
+
```ruby
|
348
|
+
def name
|
349
|
+
person.name
|
350
|
+
end
|
351
|
+
```
|
287
352
|
|
288
353
|
And, we want to replace code to use `delegate` in the expression:
|
289
354
|
|
290
|
-
|
355
|
+
```ruby
|
356
|
+
delegate :name, to: :person
|
357
|
+
```
|
291
358
|
|
292
359
|
We already target this example using `\1` on
|
293
360
|
[Search and refer to previous capture](#using-previous-captures-in-search) and
|
@@ -297,59 +364,73 @@ The [Fast.replace](Fast#replace-class_method) yields a #{Fast::Rewriter} context
|
|
297
364
|
The internal replace method accepts a range and every `node` have
|
298
365
|
a `location` with metadata about ranges of the node expression.
|
299
366
|
|
300
|
-
|
301
|
-
|
367
|
+
```ruby
|
368
|
+
ast = Fast.ast("def name; person.name end")
|
369
|
+
# => s(:def, :name, s(:args), s(:send, s(:send, nil, :person), :name))
|
370
|
+
```
|
302
371
|
|
303
372
|
Generally, we use the `location.expression`:
|
304
373
|
|
305
|
-
|
374
|
+
```ruby
|
375
|
+
ast.location.expression # => #<Parser::Source::Range (string) 0...25>
|
376
|
+
```
|
306
377
|
|
307
378
|
But location also brings some metadata about specific fragments:
|
308
379
|
|
309
|
-
|
310
|
-
|
380
|
+
```ruby
|
381
|
+
ast.location.instance_variables # => [:@keyword, :@operator, :@name, :@end, :@expression, :@node]
|
382
|
+
```
|
311
383
|
|
312
384
|
Range for the keyword that identifies the method definition:
|
313
|
-
|
314
|
-
|
385
|
+
```ruby
|
386
|
+
ast.location.keyword # => #<Parser::Source::Range (string) 0...3>
|
387
|
+
```
|
315
388
|
|
316
389
|
You can always pick the source of a source range:
|
317
390
|
|
318
|
-
|
391
|
+
```ruby
|
392
|
+
ast.location.keyword.source # => "def"
|
393
|
+
```
|
319
394
|
|
320
395
|
Or only the method name:
|
321
396
|
|
322
|
-
|
323
|
-
|
397
|
+
```ruby
|
398
|
+
ast.location.name # => #<Parser::Source::Range (string) 4...8>
|
399
|
+
ast.location.name.source # => "name"
|
400
|
+
```
|
324
401
|
|
325
402
|
In the context of the rewriter, the objective is removing the method and inserting the new
|
326
403
|
delegate content. Then, the scope is `node.location.expression`:
|
327
404
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
replace(
|
332
|
-
node.location.expression,
|
333
|
-
"delegate :#{attribute}, to: :#{object}"
|
334
|
-
)
|
335
|
-
end
|
405
|
+
```ruby
|
406
|
+
Fast.replace '(def $_ ... (send (send nil $_) \1))', ast do |node, captures|
|
407
|
+
attribute, object = captures
|
336
408
|
|
409
|
+
replace(
|
410
|
+
node.location.expression,
|
411
|
+
"delegate :#{attribute}, to: :#{object}"
|
412
|
+
)
|
413
|
+
end
|
414
|
+
```
|
337
415
|
|
338
416
|
### Replacing file
|
339
417
|
|
340
418
|
Now let's imagine we have a file like `sample.rb` with the following code:
|
341
419
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
420
|
+
```ruby
|
421
|
+
def good_bye
|
422
|
+
message = ["good", "bye"]
|
423
|
+
puts message.join(' ')
|
424
|
+
end
|
425
|
+
```
|
346
426
|
|
347
427
|
and we decide to inline the contents of the `message` variable right after
|
348
428
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
429
|
+
```ruby
|
430
|
+
def good_bye
|
431
|
+
puts ["good", "bye"].join(' ')
|
432
|
+
end
|
433
|
+
```
|
353
434
|
|
354
435
|
To refactor and reach the proposed example, follow a few steps:
|
355
436
|
|
@@ -359,22 +440,24 @@ To refactor and reach the proposed example, follow a few steps:
|
|
359
440
|
|
360
441
|
#### Entire example
|
361
442
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
443
|
+
```ruby
|
444
|
+
assignment = nil
|
445
|
+
Fast.replace_file '({ lvasgn lvar } message )', 'sample.rb' do |node, _|
|
446
|
+
# Find a variable assignment
|
447
|
+
if node.type == :lvasgn
|
448
|
+
assignment = node.children.last
|
449
|
+
# Remove the node responsible for the assignment
|
450
|
+
remove(node.location.expression)
|
451
|
+
# Look for the variable being used
|
452
|
+
elsif node.type == :lvar
|
453
|
+
# Replace the variable with the contents of the variable
|
454
|
+
replace(
|
455
|
+
node.location.expression,
|
456
|
+
assignment.location.expression.source
|
457
|
+
)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
```
|
378
461
|
|
379
462
|
Keep in mind the current example returns a content output but do not rewrite the
|
380
463
|
file.
|
@@ -386,14 +469,17 @@ To manipulate ruby files, sometimes you'll need some extra tasks.
|
|
386
469
|
## Fast.ast_from_file(file)
|
387
470
|
|
388
471
|
This method parses code from a file and loads it into an AST representation.
|
389
|
-
|
390
|
-
|
472
|
+
```ruby
|
473
|
+
Fast.ast_from_file('sample.rb')
|
474
|
+
```
|
391
475
|
|
392
476
|
## Fast.search_file
|
393
477
|
|
394
478
|
You can use `search_file` to for search for expressions inside files.
|
395
479
|
|
396
|
-
|
480
|
+
```ruby
|
481
|
+
Fast.search_file(expression, 'file.rb')
|
482
|
+
```
|
397
483
|
|
398
484
|
It's a combination of `Fast.ast_from_file` with `Fast.search`.
|
399
485
|
|
@@ -401,15 +487,22 @@ It's a combination of `Fast.ast_from_file` with `Fast.search`.
|
|
401
487
|
|
402
488
|
You can use `Fast.capture_file` to only return captures:
|
403
489
|
|
404
|
-
|
405
|
-
|
490
|
+
```ruby
|
491
|
+
Fast.capture_file('(class (const nil $_))', 'lib/fast.rb')
|
492
|
+
# => [:Rewriter, :ExpressionParser, :Find, :FindString, ...]
|
493
|
+
```
|
406
494
|
|
407
495
|
## Fast.ruby_files_from(arguments)
|
408
496
|
|
409
497
|
The `Fast.ruby_files_from(arguments)` can get all ruby files from file list or folders:
|
410
498
|
|
411
|
-
|
412
|
-
|
499
|
+
```ruby
|
500
|
+
Fast.ruby_files_from('lib')
|
501
|
+
# => ["lib/fast/experiment.rb", "lib/fast/cli.rb", "lib/fast/version.rb", "lib/fast.rb"]
|
502
|
+
```
|
503
|
+
|
504
|
+
> Note: it doesn't support glob special selectors like `*.rb` or `**/*` as it
|
505
|
+
> recursively looks for ruby files in the givem params.
|
413
506
|
|
414
507
|
## `fast` in the command line
|
415
508
|
|
@@ -532,19 +625,21 @@ from our specs.
|
|
532
625
|
|
533
626
|
If the spec still pass we can confidently say that the hook is useless.
|
534
627
|
|
535
|
-
|
536
|
-
|
537
|
-
|
628
|
+
```ruby
|
629
|
+
Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
|
630
|
+
# Lookup our spec files
|
631
|
+
lookup 'spec'
|
538
632
|
|
539
|
-
|
540
|
-
|
633
|
+
# Look for every block starting with before or after
|
634
|
+
search "(block (send nil {before after}))"
|
541
635
|
|
542
|
-
|
543
|
-
|
636
|
+
# Remove those blocks
|
637
|
+
edit { |node| remove(node.loc.expression) }
|
544
638
|
|
545
|
-
|
546
|
-
|
547
|
-
|
639
|
+
# Create a new file, and run RSpec against that new file
|
640
|
+
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
641
|
+
end
|
642
|
+
```
|
548
643
|
|
549
644
|
- `lookup` can be used to pass in files or folders.
|
550
645
|
- `search` contains the expression you want to match
|
@@ -578,7 +673,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
578
673
|
On the console we have a few functions like `s` and `code` to make it easy ;)
|
579
674
|
|
580
675
|
bin/console
|
581
|
-
|
676
|
+
|
677
|
+
```ruby
|
678
|
+
code("a = 1") # => s(:lvasgn, s(:int, 1))
|
679
|
+
```
|
582
680
|
|
583
681
|
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).
|
584
682
|
|
data/bin/console
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'fast'
|
6
|
+
require 'fast/sql'
|
6
7
|
|
7
8
|
def s(type, *children)
|
8
9
|
Parser::AST::Node.new(type, children)
|
@@ -12,6 +13,10 @@ def code(string)
|
|
12
13
|
Fast.ast(string)
|
13
14
|
end
|
14
15
|
|
16
|
+
def sql(string)
|
17
|
+
Fast.parse_sql(string)
|
18
|
+
end
|
19
|
+
|
15
20
|
def reload!
|
16
21
|
load 'lib/fast.rb'
|
17
22
|
end
|