ffast 0.1.9 → 0.2.2
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 +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
|