user_query 0.1.0

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.
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
+