user_query 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/ChangeLog +4 -0
  2. data/README +45 -0
  3. data/Rakefile +359 -0
  4. data/Releases +6 -0
  5. data/TODO +0 -0
  6. data/examples/userqueryex/HOWTO.txt +5 -0
  7. data/examples/userqueryex/README +183 -0
  8. data/examples/userqueryex/Rakefile +10 -0
  9. data/examples/userqueryex/WHAT.txt +16 -0
  10. data/examples/userqueryex/app/controllers/application.rb +4 -0
  11. data/examples/userqueryex/app/controllers/entries_controller.rb +68 -0
  12. data/examples/userqueryex/app/helpers/application_helper.rb +3 -0
  13. data/examples/userqueryex/app/helpers/entries_helper.rb +2 -0
  14. data/examples/userqueryex/app/models/entry.rb +8 -0
  15. data/examples/userqueryex/app/views/entries/_form.rhtml +20 -0
  16. data/examples/userqueryex/app/views/entries/edit.rhtml +9 -0
  17. data/examples/userqueryex/app/views/entries/list.rhtml +75 -0
  18. data/examples/userqueryex/app/views/entries/new.rhtml +8 -0
  19. data/examples/userqueryex/app/views/entries/show.rhtml +8 -0
  20. data/examples/userqueryex/app/views/layouts/entries.rhtml +13 -0
  21. data/examples/userqueryex/config/boot.rb +44 -0
  22. data/examples/userqueryex/config/database.yml +36 -0
  23. data/examples/userqueryex/config/environment.rb +54 -0
  24. data/examples/userqueryex/config/environments/development.rb +21 -0
  25. data/examples/userqueryex/config/environments/production.rb +18 -0
  26. data/examples/userqueryex/config/environments/test.rb +19 -0
  27. data/examples/userqueryex/config/routes.rb +22 -0
  28. data/examples/userqueryex/db/migrate/001_entry_migration.rb +16 -0
  29. data/examples/userqueryex/db/schema.rb +15 -0
  30. data/examples/userqueryex/doc/README_FOR_APP +2 -0
  31. data/examples/userqueryex/public/404.html +8 -0
  32. data/examples/userqueryex/public/500.html +8 -0
  33. data/examples/userqueryex/public/dispatch.cgi +10 -0
  34. data/examples/userqueryex/public/dispatch.fcgi +24 -0
  35. data/examples/userqueryex/public/dispatch.rb +10 -0
  36. data/examples/userqueryex/public/favicon.ico +0 -0
  37. data/examples/userqueryex/public/images/rails.png +0 -0
  38. data/examples/userqueryex/public/javascripts/application.js +2 -0
  39. data/examples/userqueryex/public/javascripts/controls.js +815 -0
  40. data/examples/userqueryex/public/javascripts/dragdrop.js +913 -0
  41. data/examples/userqueryex/public/javascripts/effects.js +958 -0
  42. data/examples/userqueryex/public/javascripts/prototype.js +2006 -0
  43. data/examples/userqueryex/public/robots.txt +1 -0
  44. data/examples/userqueryex/public/stylesheets/scaffold.css +74 -0
  45. data/examples/userqueryex/script/about +3 -0
  46. data/examples/userqueryex/script/breakpointer +3 -0
  47. data/examples/userqueryex/script/console +3 -0
  48. data/examples/userqueryex/script/destroy +3 -0
  49. data/examples/userqueryex/script/generate +3 -0
  50. data/examples/userqueryex/script/performance/benchmarker +3 -0
  51. data/examples/userqueryex/script/performance/profiler +3 -0
  52. data/examples/userqueryex/script/plugin +3 -0
  53. data/examples/userqueryex/script/process/reaper +3 -0
  54. data/examples/userqueryex/script/process/spawner +3 -0
  55. data/examples/userqueryex/script/runner +3 -0
  56. data/examples/userqueryex/script/server +3 -0
  57. data/examples/userqueryex/test/fixtures/entries.yml +5 -0
  58. data/examples/userqueryex/test/functional/entries_controller_test.rb +88 -0
  59. data/examples/userqueryex/test/test_helper.rb +28 -0
  60. data/examples/userqueryex/test/unit/entry_test.rb +10 -0
  61. data/lib/user_query.rb +10 -0
  62. data/lib/user_query/generator.rb +219 -0
  63. data/lib/user_query/parameters.rb +93 -0
  64. data/lib/user_query/parser.rb +762 -0
  65. data/lib/user_query/schema.rb +159 -0
  66. data/lib/user_query/user_query_version.rb +6 -0
  67. data/test/parser_test.rb +539 -0
  68. data/test/schema_test.rb +142 -0
  69. metadata +148 -0
@@ -0,0 +1,93 @@
1
+ # This class can be used as a stand-in for an ActiveRecord class, while
2
+ # using the ActionPack input tag helpers and standard ActionPack::Controller
3
+ # idiom.
4
+ #
5
+ module UserQuery
6
+
7
+ class Parameters
8
+ @active_record_errors = false
9
+ begin
10
+ # Kernel.require 'active_record'
11
+ @@active_record_errors = ActiveRecord::Errors
12
+ rescue Object => err
13
+ @@active_record_errors = false
14
+ end
15
+
16
+ unless @@active_record_errors
17
+ # Emulate ActiverRecord::Base errors support
18
+ class Errors
19
+ def initialize(obj)
20
+ @obj = obj
21
+ @name_errors = { }
22
+ @errors = nil
23
+ end
24
+ def add(name, err)
25
+ (@name_errors[name] ||= []).push(err)
26
+ (@errors ||= []).push([name, err])
27
+ end
28
+ def [](name)
29
+ (@name_errors[name] || []).join('\n')
30
+ end
31
+ def empty?
32
+ (! @errors) || @errors.empty?
33
+ end
34
+ end
35
+ @@active_record_errors = Errors
36
+ end
37
+
38
+
39
+ def initialize(*opts)
40
+ @hash = opts.empty? ? {} : opts[0]
41
+ @hash ||= { }
42
+ end
43
+
44
+ def _value_hash
45
+ @hash
46
+ end
47
+
48
+ # Emulate ActiverRecord::Base errors support
49
+ def errors
50
+ # See ActiveRecord::Validations
51
+ @errors ||= @@active_record_errors.new(self)
52
+ end
53
+ def self.human_attribute_name(x)
54
+ x # PUNT
55
+ end
56
+
57
+
58
+ def [](key)
59
+ @hash[key]
60
+ end
61
+
62
+ # OVERIDE Object
63
+ def id
64
+ @hash[:id]
65
+ end
66
+ def id=(x)
67
+ @hash[:id] = x
68
+ end
69
+
70
+ def respond_to?(meth)
71
+ true
72
+ end
73
+
74
+ def method_missing(method, *args)
75
+ m = method.to_s.clone
76
+ if m.sub!(/=$/, '') && args.size == 1
77
+ $stderr.puts "set #{method.inspect} #{args[0].inspect}" if @verbose
78
+ result = @hash[m.intern] = args[0]
79
+ elsif m.sub!(/_before_type_cast$/, '') && args.size == 0
80
+ $stderr.puts "get #{method.inspect}" if @verbose
81
+ result = @hash[m.intern]
82
+ elsif args.size == 0
83
+ $stderr.puts "get #{method.inspect}" if @verbose
84
+ result = @hash[method]
85
+ else
86
+ raise NotImplementedError, method
87
+ end
88
+
89
+ result
90
+ end
91
+ end
92
+
93
+ end # module
@@ -0,0 +1,762 @@
1
+ require 'time'
2
+
3
+ module UserQuery
4
+
5
+ # Do:
6
+ # require 'currency'
7
+ # for :type => :money
8
+ #
9
+
10
+ class Parser
11
+ @now = nil
12
+ @@now = nil
13
+ @today = nil
14
+ @@today= nil
15
+ @this_year = nil
16
+ @@this_year = nil
17
+
18
+ class Error < Exception; end
19
+ class SyntaxError < Error; end
20
+
21
+ attr_accessor :default_join_op
22
+ attr_accessor :default_literal_op
23
+ attr_accessor :keywords
24
+ attr_accessor :type
25
+ attr_accessor :expr
26
+ attr_accessor :verbose
27
+
28
+ def initialize(*opts)
29
+ self.verbose = false
30
+ self.default_join_op = :and
31
+ self.default_literal_op = :like
32
+ self.keywords = [ ]
33
+ self.type = :string
34
+ opts = Hash[*opts]
35
+ opts.each{|k, v| self.send("#{k}=", v)}
36
+ end
37
+
38
+ def parse(str)
39
+ return nil if str.nil?
40
+
41
+ str = str.to_s
42
+
43
+ @level = 0
44
+ @input_original = str.clone
45
+ @input = str.clone
46
+ @lex_next = nil
47
+ @expr = lex_peek && parse_top_level
48
+
49
+ eat_whitespace
50
+
51
+ @lex_extra = lex_peek
52
+ if @lex_extra
53
+ raise SyntaxError, "extra characters for #{type.to_s} field at #{@lex_extra[1].inspect}"
54
+ end
55
+
56
+ if @verbose
57
+ q_sql = UserQuery::Generator.new.sql(@expr)
58
+ $stderr.puts "#{str.inspect} =>\n " + @expr.inspect + " =>\n " + q_sql.inspect
59
+ end
60
+
61
+ @expr
62
+ end
63
+
64
+ def <<(str)
65
+ parse(str)
66
+ end
67
+
68
+ # Configuration
69
+
70
+ def now=(t)
71
+ @now = t
72
+ end
73
+ def now
74
+ @now || @@now || Time.new
75
+ end
76
+
77
+ def this_year=(y)
78
+ @this_year = y.respond_to?(:year) ? y.year : y
79
+ y
80
+ end
81
+ def this_year
82
+ @this_year || @@this_year || now.year
83
+ end
84
+
85
+ def today=(t)
86
+ t = t && Time.utc(t.year, t.month, t.day, 0, 0, 0, 0)
87
+ @today = t
88
+ end
89
+ def today
90
+ t = @today || @@today
91
+ unless t
92
+ t = now
93
+ t = Time.utc(t.year, t.month, t.day, 0, 0, 0, 0)
94
+ end
95
+ t
96
+ end
97
+
98
+ private
99
+ # Recursive-descendent parser
100
+ def parse_top_level
101
+ show_where("parse_top_level")
102
+
103
+ expr = parse_expression
104
+
105
+ show_where("parse_top_level", expr)
106
+
107
+ expr
108
+ end
109
+
110
+ def parse_expression
111
+ show_where("parse_expression")
112
+
113
+ expr = parse_conditional
114
+
115
+ show_where("parse_expression", expr)
116
+
117
+ expr
118
+ end
119
+
120
+ def parse_conditional
121
+ show_where("parse_conditional")
122
+
123
+ expr = parse_logical_or
124
+
125
+ show_where("parse_conditional", expr)
126
+
127
+ expr
128
+ end
129
+
130
+ def parse_logical_or
131
+ show_where("parse_logical_or")
132
+
133
+ expr = parse_logical_and
134
+
135
+ l = lex_peek
136
+ case type = l && l[0]
137
+ when :or
138
+ lex_read
139
+ expr = [ :or, expr, parse_logical_or ]
140
+ end
141
+
142
+ show_where("parse_logical_or", expr)
143
+
144
+ expr
145
+ end
146
+
147
+ def parse_logical_and
148
+ show_where("parse_logical_and")
149
+
150
+ expr = parse_relational
151
+
152
+ l = lex_peek
153
+ case type = l && l[0]
154
+ when :and
155
+ lex_read
156
+ show_where("parse_logical_and: expr2")
157
+ expr_2 = parse_logical_and
158
+ show_where("parse_logical_and: expr2: ", expr_2)
159
+ expr = [ :and, expr, expr_2 ]
160
+ end
161
+
162
+ show_where("parse_logical_and", expr)
163
+
164
+ expr
165
+ end
166
+
167
+
168
+ def parse_relational
169
+ show_where("parse_relational")
170
+
171
+ expr = lex_peek
172
+ case op = expr && expr[0]
173
+ when :eq
174
+ lex_read
175
+ expr = ranged_value_eq(parse_literal)
176
+
177
+ when :ne
178
+ lex_read
179
+ expr = ranged_value_ne(parse_literal)
180
+
181
+ when :gt
182
+ lex_read
183
+ expr = ranged_value_gt(parse_literal)
184
+
185
+ when :lt, :ge
186
+ lex_read
187
+ expr = [ op, parse_literal ]
188
+
189
+ when :le
190
+ lex_read
191
+ expr = ranged_value_le(parse_literal)
192
+
193
+ when :between
194
+ lex_read
195
+ expr_1 = parse_literal
196
+ lex_eat(:and, :elipsis)
197
+ expr_2 = parse_literal(*range_types(expr_1[0]))
198
+ expr = [ op, expr_1, expr_2 ]
199
+
200
+ else
201
+ expr = parse_unary
202
+ end
203
+
204
+ show_where("parse_relational", expr)
205
+
206
+ expr
207
+ end
208
+
209
+ def parse_unary
210
+ show_where("parse_unary")
211
+
212
+ expr = lex_peek
213
+ case op = expr && expr[0]
214
+ when :not
215
+ lex_read
216
+ expr = [ op, parse_sequence ]
217
+ when :like
218
+ lex_read
219
+ expr = [ op, to_string(parse_literal) ]
220
+ else
221
+ expr = parse_sequence
222
+ end
223
+
224
+ show_where("parse_unary", expr)
225
+
226
+ expr
227
+ end
228
+
229
+ def parse_sequence
230
+ show_where("parse_sequence")
231
+
232
+ expr = parse_primary
233
+
234
+ while l = lex_match(:word, :string, :number, :money, :year, :month, :date, :hour, :minute)
235
+ expr = [ default_join_op, expr, parse_primary ]
236
+ end
237
+
238
+ show_where("parse_sequence", expr)
239
+
240
+ expr
241
+ end
242
+
243
+ def parse_primary
244
+ show_where("parse_primary")
245
+
246
+ expr = lex_peek
247
+ case type = expr && expr[0]
248
+ when '('
249
+ lex_read
250
+ expr = parse_expression
251
+ lex_eat(')')
252
+
253
+ else
254
+ expr = parse_singular
255
+ end
256
+
257
+ show_where("parse_primary", expr)
258
+
259
+ expr
260
+ end
261
+
262
+
263
+ def parse_singular
264
+ show_where("parse_singular")
265
+
266
+ expr = parse_literal
267
+
268
+ l = lex_peek
269
+ case type = l && l[0]
270
+ when :elipsis
271
+ lex_read
272
+ expr = [ :between, expr, parse_literal ]
273
+ else
274
+ # Literals are enconsed with the default literal op
275
+ #
276
+ case type = expr && expr[0]
277
+ when :number, :boolean, :money
278
+ # Numericals should match exact.
279
+ expr = [ :eq, expr ]
280
+
281
+ when :string, :word
282
+ # Strings are usually inexact so we use the default literal op.
283
+ expr = [ default_literal_op, expr ]
284
+
285
+ when :year, :month, :day, :hour, :minute, :second
286
+ # datetime is inexact because intutively
287
+ # a date should match all values with that date,
288
+ # but datetime values have greater precision than just date
289
+ # So we translate year, month, day to intuitive ranges.
290
+ expr = [ :range, expr, ranged_value_plus_1(expr) ]
291
+ end
292
+ end
293
+
294
+ show_where("parse_singular", expr)
295
+
296
+ expr
297
+ end
298
+
299
+ def parse_literal(*types)
300
+ types = types.flatten
301
+ show_where("parse_literal")
302
+
303
+ expr = lex_peek(*types)
304
+
305
+ case type = expr && expr[0]
306
+ when :null, :string, :word, :number, :boolean, :money
307
+ expr = lex_read(types)
308
+
309
+ when :year, :month, :day, :hour, :minute, :second
310
+ expr = lex_read(types)
311
+
312
+ else
313
+ raise SyntaxError, "Unexpected #{expr.inspect}"
314
+
315
+ end
316
+
317
+ show_where("parse_literal", expr)
318
+
319
+ expr
320
+ end
321
+
322
+ ############################################################
323
+ # Ranged values
324
+ #
325
+
326
+ # :datetime is a ranged value type.
327
+ # because intutively a date should match all DATETIME values within
328
+ # that date, regardless of how much precision
329
+ # DATETIME has, i.e. down to the second.
330
+ #
331
+ # For example:
332
+ #
333
+ # The query:
334
+ #
335
+ # "12/31/2005"
336
+ #
337
+ # should generate SQL:
338
+ #
339
+ # column >= '2005-12-31 00:00:00' AND column < '2006-01-01 00:00:00'
340
+ #
341
+ # The query:
342
+ #
343
+ # "!= 12/31/2005"
344
+ #
345
+ # should generate SQL:
346
+ #
347
+ # column < '2005-12-31 00:00:00' AND column >= '2006-01-01 00:00:00'
348
+ #
349
+ # The query:
350
+ #
351
+ # "> 12/31/2005"
352
+ #
353
+ # should generate SQL:
354
+ #
355
+ # column >= '2006-01-01 00:00:00'
356
+ #
357
+
358
+ def range_types(type)
359
+ case type
360
+ when :word, :string
361
+ type = [ :word, :string ]
362
+ when :year, :month, :day, :hour, :minute, :second
363
+ type = [ :year, :month, :day, :hour, :minute, :second ]
364
+ else
365
+ type = [ type ]
366
+ end
367
+
368
+ type
369
+ end
370
+
371
+
372
+ def ranged_value_eq(expr)
373
+ case type = expr && expr[0]
374
+ when :year, :month, :day, :hour, :minute, :second
375
+ expr_0 = expr
376
+ expr_1 = ranged_value_plus_1(expr)
377
+ expr = [ :range, expr_0, expr_1 ]
378
+
379
+ else
380
+ expr = [ :eq, expr ]
381
+ end
382
+
383
+ expr
384
+ end
385
+
386
+ def ranged_value_ne(expr)
387
+ case type = expr && expr[0]
388
+ when :year, :month, :day, :hour, :minute, :second
389
+ expr_0 = expr
390
+ expr_1 = ranged_value_plus_1(expr)
391
+ expr = [ :or,
392
+ [ :lt, expr_0 ],
393
+ [ :ge, expr_1 ],
394
+ ]
395
+
396
+ else
397
+ expr = [ :ne, expr ]
398
+ end
399
+
400
+ expr
401
+ end
402
+
403
+ def ranged_value_gt(expr)
404
+ case type = expr && expr[0]
405
+ when :year, :month, :day, :hour, :minute, :second
406
+ expr = ranged_value_plus_1(expr)
407
+ expr = [ :ge, expr ]
408
+
409
+ else
410
+ expr = [ :gt, expr ]
411
+
412
+ end
413
+
414
+ expr
415
+ end
416
+
417
+ def ranged_value_le(expr)
418
+ case type = expr && expr[0]
419
+ when :year, :month, :day, :hour, :minute
420
+ expr = ranged_value_plus_1(expr)
421
+ expr = [ :lt, expr ]
422
+
423
+ else
424
+ expr = [ :le, expr ]
425
+
426
+ end
427
+
428
+ expr
429
+ end
430
+
431
+
432
+ def ranged_value_plus_1(expr)
433
+ case type = expr && expr[0]
434
+ when :year
435
+ expr = year_plus_1(expr)
436
+
437
+ when :month
438
+ expr = month_plus_1(expr)
439
+
440
+ when :day
441
+ expr = day_plus_1(expr)
442
+
443
+ when :hour
444
+ expr = hour_plus_1(expr)
445
+
446
+ when :minute
447
+ expr = minute_plus_1(expr)
448
+
449
+ when :second
450
+ expr = second_plus_1(expr)
451
+
452
+ end
453
+
454
+ expr
455
+ end
456
+
457
+
458
+ ############################################################
459
+ # datetime helper
460
+ #
461
+
462
+ def year_plus_1(x)
463
+ x = x.clone
464
+ x[2] = x[2] + 1
465
+ x
466
+ end
467
+
468
+ def month_plus_1(x)
469
+ x = x.clone
470
+ x[3] = x[3] + 1
471
+ if x[3] > 12
472
+ x[3] = 1
473
+ x[2] = x[2] + 1
474
+ end
475
+ x
476
+ end
477
+
478
+ def day_plus_1(x)
479
+ # $stderr.puts "day_plus_1(#{x.inspect})"
480
+ t = Time.utc(x[2], x[3] || 1, x[4] || 1, 0, 0, 0, 0)
481
+ t = t + (60 * 60 * 25) # Add one day, plus fudge for leap seconds
482
+ x = [ x[0],
483
+ x[1], # str
484
+ t.year,
485
+ t.month,
486
+ t.day
487
+ ]
488
+ x
489
+ end
490
+
491
+ def hour_plus_1(x)
492
+ t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, 0, 0, 0)
493
+ t = t + (65 * 60) # Add one hour, plus fudge for leap seconds
494
+ x = [ x[0],
495
+ x[1], # str
496
+ t.year,
497
+ t.month,
498
+ t.day,
499
+ t.hour
500
+ ]
501
+ x
502
+ end
503
+
504
+ def minute_plus_1(x)
505
+ t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, x[6] || 0, 0, 0)
506
+ t = t + 65 # Add one minunte, plus fudge for leap seconds
507
+ x = [ x[0],
508
+ x[1], # str
509
+ t.year,
510
+ t.month,
511
+ t.day,
512
+ t.hour,
513
+ t.min
514
+ ]
515
+ x
516
+ end
517
+
518
+ def second_plus_1(x)
519
+ t = Time.utc(x[2], x[3] || 1, x[4] || 1, x[5] || 0, x[6] || 0, x[7] || 0, 0)
520
+ t = t + 1 # Add one second
521
+ x = [ x[0],
522
+ x[1], # str
523
+ t.year,
524
+ t.month,
525
+ t.day,
526
+ t.hour,
527
+ t.min,
528
+ t.sec
529
+ ]
530
+ x
531
+ end
532
+
533
+ ############################################################
534
+ # Push back interface w/ lexeme type checks
535
+ #
536
+
537
+ def show_where(str = "", expr = nil)
538
+ @level = @level - 1 if expr != nil
539
+ $stderr.puts "#{" " * @level} #{str}: #{lex_peek.inspect} #{@input.inspect} #{expr && ' => '} #{expr && expr.inspect}" if @verbose
540
+ @level = @level + 1 if expr == nil
541
+ end
542
+
543
+ def lex_match(*types)
544
+ l = lex_peek
545
+ types = types.flatten
546
+ types.include?(l && l[0])
547
+ end
548
+
549
+ def lex_peek(*types)
550
+ check_types(@lex_next ||= lex, types)
551
+ end
552
+
553
+ def lex_read(*types)
554
+ l = @lex_next || lex
555
+ @lex_next = nil
556
+ check_types(l, types)
557
+ end
558
+
559
+ def lex_eat(*types)
560
+ l = lex_read
561
+ check_types(l, types)
562
+ end
563
+
564
+ def check_types(l, types)
565
+ types = types.flatten
566
+ unless types.empty?
567
+ raise SyntaxError, "expected #{types.inspect}, found #{l.inspect}" unless types.include?(l && l[0])
568
+ end
569
+ l
570
+ end
571
+
572
+ def to_string(l)
573
+ l = l.clone;
574
+ l[0] = :string
575
+ l[1] = l[1].to_s
576
+ l[2] = l[2].to_s
577
+ l
578
+ end
579
+
580
+
581
+ ############################################################
582
+ # Low-level lexer
583
+ #
584
+
585
+ def lex
586
+ # @verbose = true
587
+ x = @input
588
+
589
+ l = nil
590
+ if eat_whitespace
591
+ if false
592
+ l = nil
593
+
594
+ elsif md = match(/^NULL\b/)
595
+ l = [ :null, md[0] ]
596
+
597
+ elsif md = match(/^\.\.\./)
598
+ l = [ :elipsis ]
599
+
600
+ elsif md = match(/^(AND\b|[&])/)
601
+ l = [ :and ]
602
+
603
+ elsif md = match(/^(OR\b|[|])/)
604
+ l = [ :or ]
605
+
606
+ elsif md = match(/^BETWEEN\b/)
607
+ l = [ :between ]
608
+
609
+ elsif md = match(/^(NOT\s+EQUAL(\s+TO)?\b|!=|<>)/)
610
+ l = [ :ne ]
611
+
612
+ elsif md = match(/^(LESS\s+THAN\s+ORs\+EQUAL\s+TO\b|<=)/)
613
+ l = [ :le ]
614
+
615
+ elsif md = match(/^(GREATER\s+THAN\s+ORs\+EQUAL\s+TO\b|>=)/)
616
+ l = [ :ge ]
617
+
618
+ elsif md = match(/^(BEFORE\b|LESS(\s+THAN)?\b|<)/)
619
+ l = [ :lt ]
620
+
621
+ elsif md = match(/^(AFTER\b|GREATER(\s+THAN)?\b|>)/)
622
+ l = [ :gt ]
623
+
624
+ elsif md = match(/^(EQUAL(\s+TO)?\b|==?)/)
625
+ l = [ :eq ]
626
+
627
+ elsif md = match(/^(NOT\b|!)/)
628
+ l = [ :not ]
629
+
630
+ elsif md = match(/^(LIKE\b|~)/)
631
+ l = [ :like, ]
632
+
633
+ elsif md = match(/^([\(\)])/)
634
+ l = [ md[1], md[0] ]
635
+
636
+ elsif md = match(/^"((\\.|[^\\"]+)*)"/)
637
+ l = md[1]
638
+ l.gsub!(/\\(.)/){|x| $1}
639
+ @keywords << l
640
+ l = [ :string, l, l ]
641
+
642
+ elsif type == :boolean && (md = match(/^(true)/i))
643
+ l = [ :boolean, md[0], true ]
644
+
645
+ elsif type == :boolean && (md = match(/^(false)/i))
646
+ l = [ :boolean, md[0], false ]
647
+
648
+ elsif type == :money && (md = match(/^(\$?[-+]?([\d,]+(\.\d+)?|\.\d+))/))
649
+ l = [ :money, md[0], Currency::Money.new(md[1]) ]
650
+
651
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?):?(\d\d):?(\d\d)\s*([ap]?)m?/i))
652
+ # mm/dd/yyyy hh:mm:ss(am|pm)
653
+ hh = fix_hh_am_pm(md[5], md[8])
654
+ mm = md[6].to_i
655
+ ss = md[7].to_i
656
+ l = [ :second, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh, mm, ss ]
657
+
658
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?):?(\d\d)\s*([ap]?)m?/i))
659
+ # mm/dd/yyyy hh:mm(am|pm)
660
+ hh = fix_hh_am_pm(md[5], md[7])
661
+ mm = md[6].to_i
662
+ l = [ :minute, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh, mm ]
663
+
664
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)(-|\s+)(\d\d?)\s*([ap]?)m?/i))
665
+ # mm/dd/yyyy hh
666
+ hh = fix_hh_am_pm(md[5], md[6])
667
+ l = [ :hour, md[0], md[3].to_i, md[1].to_i, md[2].to_i, hh ]
668
+
669
+ elsif type == :datetime && (md = match(/^(\d\d\d\d)\/(\d\d?)\/(\d\d?)/))
670
+ # yyyy/mm/dd
671
+ l = [ :day, md[0], md[1].to_i, md[2].to_i, md[3].to_i ]
672
+
673
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)/))
674
+ # mm/dd/yyyy
675
+ l = [ :day, md[0], md[3].to_i, md[1].to_i, md[2].to_i ]
676
+
677
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d\d\d)/))
678
+ # mm/yyyy
679
+ l = [ :month, md[0], md[2].to_i, md[1].to_i ]
680
+
681
+ elsif type == :datetime && (md = match(/^(\d\d?)\/(\d\d?)/))
682
+ # mm/dd => THIS-YEAR/mm/dd
683
+ l = [ :day, md[0], this_year, md[1].to_i, md[2].to_i ]
684
+
685
+ elsif type == :datetime && (md = match(/^(\d\d\d\d)\/(\d\d?)/))
686
+ # yyyy/mm
687
+ l = [ :month, md[0], md[1].to_i, md[2].to_i ]
688
+
689
+ elsif type == :datetime && (md = match(/^(\d\d\d\d)/))
690
+ # yyyy
691
+ l = [ :year, md[0], md[1].to_i ]
692
+
693
+ elsif type == :datetime && (md = match(/^now\b/i))
694
+ # now
695
+ l = now
696
+ l = [ :second, md[0], l.year, l.month, l.day, l.hour, l.min, l.sec ]
697
+
698
+ elsif type == :datetime && (md = match(/^this\s*year\b/i))
699
+ # this year => THIS-YEAR
700
+ l = [ :year, md[0], this_year ]
701
+
702
+ elsif type == :datetime && (md = match(/^yesterday\b|today\s*\-\s*(\d+)/i))
703
+ # yesterday
704
+ l = today - (24 * 60 * 60) * (md[1] ? md[1].to_i : 1)
705
+ l = [ :day, md[0], l.year, l.month, l.day ]
706
+
707
+ elsif type == :datetime && (md = match(/^tomorrow\b|today\s*\+\s*(\d+)/i))
708
+ # tomorrow
709
+ l = today + (24 * 60 * 60) * (md[1] ? md[1].to_i : 1)
710
+ l = [ :day, md[0], l.year, l.month, l.day ]
711
+
712
+ elsif type == :datetime && (md = match(/^today\b/i))
713
+ # today
714
+ l = today
715
+ l = [ :day, md[0], l.year, l.month, l.day ]
716
+
717
+ elsif md = match(/^([-+]?((\d+|\.\d+|\d+\.\d*)[eE][-+]?\d+|\.\d+|\d+\.\d*))/)
718
+ l = [ :number, md[0], md[1].to_f ]
719
+
720
+ elsif md = match(/^([-+]?\d+)/)
721
+ l = [ :number, md[0], md[1].to_i ]
722
+
723
+ elsif type == :string && md = match(/^(\w+)/)
724
+ @keywords << md[1]
725
+ l = [ :word, md[0], md[1] ]
726
+ else
727
+ raise SyntaxError, "invalid character for #{type.to_s} field at #{@input.inspect}"
728
+ end
729
+ end
730
+
731
+ $stderr.puts "lex #{x.inspect} => #{l.inspect}" if @verbose
732
+
733
+ l
734
+ end
735
+
736
+ def fix_hh_am_pm(hh, am_pm)
737
+ hh = hh.to_i
738
+ if am_pm
739
+ am_pm = am_pm.downcase
740
+ hh = 0 if am_pm == 'a' && hh == 12
741
+ hh = hh + 12 if am_pm == 'p' && hh != 12
742
+ end
743
+ hh
744
+ end
745
+
746
+ def eat_whitespace
747
+ @input.sub!(/^\s+/, '')
748
+ ! @input.empty?
749
+ end
750
+
751
+ def match(rx)
752
+ # $stderr.puts "m_a_a @ #{@input.inspect}"
753
+ if md = rx.match(@input)
754
+ @input = md.post_match
755
+ end
756
+ md
757
+ end
758
+
759
+ end
760
+
761
+ end # module
762
+