Skip to content

Commit

Permalink
Merge feadc16 into c690ace
Browse files Browse the repository at this point in the history
  • Loading branch information
dkubb committed Jan 12, 2014
2 parents c690ace + feadc16 commit 7dcce4f
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ measurements
Gemfile.lock

## PROJECT::SPECIFIC
lib/sql/scanner.rb
lib/sql/parser.rb
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ AllCops:
- 'Gemfile'
Excludes:
- 'Gemfile.devtools'
- 'lib/sql/parser.rb'
- 'lib/sql/scanner.rb'
- 'vendor/**'
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ end

group :development, :test do
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
gem 'racc', '~> 1.4'
end

eval_gemfile 'Gemfile.devtools'
2 changes: 1 addition & 1 deletion Gemfile.devtools
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ group :metrics do

platform :rbx do
gem 'json', '~> 1.8.1'
gem 'racc', '~> 1.4.10'
gem 'racc', '~> 1.4'
gem 'rubysl-logger', '~> 2.0.0'
gem 'rubysl-open-uri', '~> 2.0.0'
gem 'rubysl-prettyprint', '~> 2.0.2'
Expand Down
23 changes: 23 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,26 @@
require 'devtools'

Devtools.init_rake_tasks

# Compile the scanner using ragel
rule '.rb' => '.rb.rl' do |task|
sh "ragel -I. -R -o #{task.name} #{task.source}"
end

# Compile the parser using racc
rule '.rb' => '.rb.y' do |task|
sh "racc -l -o #{task.name} #{task.source}"
end

desc 'Compile the scanner and parser'
task :compile => %w[clean lib/sql/scanner.rb lib/sql/parser.rb]

desc 'Clean up compiled code'
task :clean do
sh 'git clean -dfx lib/sql/{scanner,parser}.rb'
end

# Add compile as a dependency to spec tasks
%w[spec spec:unit spec:integration].each do |task_name|
task(task_name => :compile)
end
4 changes: 3 additions & 1 deletion lib/sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ def initialize(type)
require 'sql/generator/emitter/set'
require 'sql/generator/emitter/join'

require 'sql/parser'
require 'sql/version'
require 'sql/node_helper'

require 'sql/scanner'
require 'sql/parser'

# Finalize the emitter dispatch table
SQL::Generator::Emitter.finalize
1 change: 1 addition & 0 deletions lib/sql/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module Constants
O_SQRT = 'SQRT'.freeze
O_ABS = 'ABS'.freeze
O_LENGTH = 'LENGTH'.freeze
O_DATE = 'DATE'.freeze

# Binary Infix Operators
O_AND = 'AND'.freeze
Expand Down
24 changes: 21 additions & 3 deletions lib/sql/generator/emitter/binary_infix_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Emitter

# Binary infix operation emitter base class
class BinaryInfixOperation < self
include ConditionalParenthesis

TYPES = IceNine.deep_freeze(
in: O_IN,
Expand Down Expand Up @@ -39,9 +40,26 @@ class BinaryInfixOperation < self
#
# @api private
def dispatch
visit(left)
write(WS, TYPES.fetch(node_type), WS)
visit(right)
parenthesis do
visit(left)
write(WS, TYPES.fetch(node_type), WS)
visit(right)
end
end

def parenthesize?
# TODO: when a node has lower precedence than it's parent it
# should be parenthesized.

# TODO: when a left node has the same precedence as it's parent,
# and is right associative, it should be parenthesized.
# eg: s(:pow, s(:pow, 2, 3), 4) -> (2 ^ 3) ^ 4

# TODO: when a right node has the same precedence as it's parent,
# and is left associative, it should be parenthesized.
# eg: s(:sub, 2, s(:sub, 3, 4)) -> 2 - (3 - 4)

true
end

end # BinaryInfixOperation
Expand Down
2 changes: 1 addition & 1 deletion lib/sql/generator/emitter/literal/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Date < self
#
# @api private
def dispatch
write(D_QUOTE, value.iso8601, D_QUOTE)
write(O_DATE + WS, D_QUOTE, value.iso8601, D_QUOTE)
end

end # Date
Expand Down
2 changes: 1 addition & 1 deletion lib/sql/generator/emitter/select.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Select < self
def dispatch
parenthesis do
write_command(fields, K_SELECT)
write_node(from, K_FROM)
write_node(from, K_FROM) if from
remaining_children.each(&method(:visit))
end
end
Expand Down
8 changes: 0 additions & 8 deletions lib/sql/parser.rb

This file was deleted.

217 changes: 217 additions & 0 deletions lib/sql/parser.rb.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Rule naming conventions follow the ANSI SQL-92 BNF grammar:
# http://savage.net.au/SQL/sql-92.bnf

# An SQL parser
class SQL::Parser

token select from as
true false null
left_paren right_paren
comma period
pow
asterisk solidus mod
plus_sign minus_sign
E
identifier
unsigned_integer
string

prechigh
left IDENTIFIER_SEPARATOR
right UNARY
left pow
left asterisk solidus mod
left plus_sign minus_sign
preclow

start
query_specification

rule
sign
: plus_sign { result = :uplus }
| minus_sign { result = :uminus }

truth_value
: true { result = s(:true) }
| false { result = s(:false) }

general_literal
: character_string_literal
| truth_value
| null { result = s(:null) }

character_string_literal
: character_string_literal string { result = s(:string, [*val[0], val[1]].join) }
| string { result = s(:string, val[0]) }

subquery
: left_paren query_expression right_paren { result = val[1] }

query_expression
: non_join_query_expression

non_join_query_expression
: non_join_query_term

non_join_query_term
: non_join_query_primary

non_join_query_primary
: simple_table
| left_paren non_join_query_term right_paren { result = val[1] }

simple_table
: query_specification

table_name
: qualified_name

qualified_name
: qualified_identifier

qualified_identifier
: identifier { result = s(:id, *val) }

column_name
: identifier { result = s(:id, *val) }

column_reference
: qualifier period =IDENTIFIER_SEPARATOR column_name { result = val[0].concat(val[2]) }
| column_name

qualifier
: table_name

correlation_name
: identifier { result = s(:id, *val) }

query_specification
: query_specification table_expression { result = val[0].append(val[1]) }
| select select_list { result = s(:select, val[1]) }

select_list
: asterisk { result = s(:fields, s(:asterisk)) }
| select_list comma select_sublist { result = val[0].append(val[2]) }
| select_sublist { result = s(:fields, *val) }

select_sublist
: derived_column
| qualifier period =IDENTIFIER_SEPARATOR asterisk { result = val[0].append(s(:asterisk)) }

derived_column
: derived_column as_clause { result = s(:as, *val) }
| value_expression

as_clause
: as column_name { result = val[1] }

table_expression
: from_clause

from_clause
: from table_reference { result = val[1] }
| from derived_table_reference { result = val[1] }

table_reference
: table_reference correlation_specification { result = s(:as, *val) }
| table_name

derived_table_reference
: derived_table correlation_specification { result = s(:as, *val) }

derived_table
: table_subquery

table_subquery
: subquery

correlation_specification
: as correlation_name { result = val[1] }

value_expression
: numeric_value_expression

numeric_value_expression
: term
| numeric_value_expression plus_sign term { result = s(:add, val[0], val[2]) }
| numeric_value_expression minus_sign term { result = s(:sub, val[0], val[2]) }

term
: factor
| term pow factor { result = s(:pow, val[0], val[2]) }
| term asterisk factor { result = s(:mul, val[0], val[2]) }
| term solidus factor { result = s(:div, val[0], val[2]) }
| term mod factor { result = s(:mod, val[0], val[2]) }

factor
: sign =UNARY numeric_primary { result = s(*val) }
| numeric_primary

numeric_primary
: value_expression_primary

value_expression_primary
: unsigned_value_specification
| column_reference
| left_paren value_expression right_paren { result = val[1] }

unsigned_value_specification
: unsigned_literal

unsigned_literal
: unsigned_numeric_literal
| general_literal

unsigned_numeric_literal
: exact_numeric_literal
| approximate_numeric_literal

exact_numeric_literal
: exact_numeric_literal period unsigned_integer { result = s(:float, sprintf('%d.%d', *val[0], val[2]).to_f) }
| unsigned_integer { result = s(:integer, val[0]) }

approximate_numeric_literal
: mantissa E exponent { result = s(:float, sprintf('%fE%d', *val[0], *val[2]).to_f) }

mantissa
: exact_numeric_literal

exponent
: signed_integer

signed_integer
: sign =UNARY unsigned_integer {
op = case val[0]
when :uplus then :+@
when :uminus then :-@
end
result = s(:integer, val[1].public_send(op))
}
| unsigned_integer { result = s(:integer, *val) }
end

---- inner
include NodeHelper

attr_reader :result

def self.parse(input, scanner_class = Scanner)
new(scanner_class.new(input)).parse
end

def initialize(scanner)
@tokens = scanner.each
super()
end

def parse
@result ||= do_parse
self
end

def next_token
@tokens.next
rescue StopIteration
# return nil
end

0 comments on commit 7dcce4f

Please sign in to comment.