simply_stored 0.1.4

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 (121) hide show
  1. data/lib/simply_stored/class_methods_base.rb +31 -0
  2. data/lib/simply_stored/couch/belongs_to.rb +117 -0
  3. data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
  4. data/lib/simply_stored/couch/has_many.rb +148 -0
  5. data/lib/simply_stored/couch/has_one.rb +93 -0
  6. data/lib/simply_stored/couch/validations.rb +74 -0
  7. data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
  8. data/lib/simply_stored/couch/views.rb +1 -0
  9. data/lib/simply_stored/couch.rb +278 -0
  10. data/lib/simply_stored/instance_methods.rb +143 -0
  11. data/lib/simply_stored/simpledb/associations.rb +196 -0
  12. data/lib/simply_stored/simpledb/attributes.rb +173 -0
  13. data/lib/simply_stored/simpledb/storag.rb +85 -0
  14. data/lib/simply_stored/simpledb/validations.rb +88 -0
  15. data/lib/simply_stored/simpledb.rb +212 -0
  16. data/lib/simply_stored/storage.rb +93 -0
  17. data/lib/simply_stored.rb +9 -0
  18. data/test/custom_views_test.rb +33 -0
  19. data/test/fixtures/couch.rb +182 -0
  20. data/test/fixtures/simpledb/item.rb +11 -0
  21. data/test/fixtures/simpledb/item_daddy.rb +8 -0
  22. data/test/fixtures/simpledb/log_item.rb +3 -0
  23. data/test/fixtures/simpledb/namespace_bar.rb +5 -0
  24. data/test/fixtures/simpledb/namespace_foo.rb +7 -0
  25. data/test/fixtures/simpledb/protected_item.rb +3 -0
  26. data/test/simply_stored_couch_test.rb +1684 -0
  27. data/test/simply_stored_simpledb_test.rb +1341 -0
  28. data/test/test_helper.rb +22 -0
  29. data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
  30. data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
  31. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
  32. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
  33. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
  34. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
  35. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
  36. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
  37. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
  38. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
  39. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
  40. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
  41. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
  42. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
  43. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
  44. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
  45. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
  46. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
  47. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
  48. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
  49. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
  50. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
  51. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
  52. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
  53. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
  54. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
  55. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
  56. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
  57. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
  58. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
  59. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
  60. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
  61. data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
  62. data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
  63. data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
  64. data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
  65. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
  66. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
  67. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
  68. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
  69. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
  70. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
  71. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
  72. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
  73. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
  74. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
  75. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
  76. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
  77. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
  78. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
  79. data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
  80. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
  81. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
  82. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
  83. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
  84. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
  85. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
  86. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
  87. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
  88. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
  89. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
  90. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
  91. data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
  92. data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
  93. data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
  94. data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
  95. data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
  96. data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
  97. data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
  98. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
  99. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
  100. data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
  101. data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
  102. data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
  103. data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
  104. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
  105. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
  106. data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
  107. data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
  108. data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
  109. data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
  110. data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
  111. data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
  112. data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
  113. data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
  114. data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
  115. data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
  116. data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
  117. data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
  118. data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
  119. data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
  120. data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
  121. metadata +182 -0
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'active_support'
6
+ require 'active_support/testing/assertions'
7
+
8
+ require 'simply_stored/couch'
9
+ require 'simply_stored/simpledb'
10
+
11
+ $:.<< File.expand_path(File.dirname(__FILE__) + "/vendor/simplerdb-0.2/lib")
12
+ $:.<< File.expand_path(File.dirname(__FILE__) + "/vendor/dhaka-2.2.1/lib")
13
+
14
+ class Test::Unit::TestCase
15
+ include ActiveSupport::Testing::Assertions
16
+
17
+ def recreate_db
18
+ CouchPotato.couchrest_database.delete! rescue nil
19
+ CouchPotato.couchrest_database.server.create_db CouchPotato::Config.database_name
20
+ end
21
+
22
+ end
@@ -0,0 +1,29 @@
1
+ module Dhaka
2
+ module Dot #:nodoc:
3
+ class Digraph #:nodoc:
4
+ def initialize(node_attributes = {})
5
+ @result = ["digraph x {"]
6
+ @result << %(node #{dotify_hash(node_attributes)})
7
+ yield(self)
8
+ @result << '}'
9
+ end
10
+
11
+ def node(obj, attributes = {})
12
+ @result << "#{obj.object_id} #{dotify_hash(attributes)}"
13
+ end
14
+
15
+ def edge(src, dest, attributes = {})
16
+ @result << "#{src.object_id} -> #{dest.object_id} #{dotify_hash(attributes)}"
17
+ end
18
+
19
+ def dotify_hash hash
20
+ sorted_key_value_pairs = hash.collect {|key, value| [key.to_s, value.to_s]}.sort
21
+ hash.empty? ? "" : '[' + sorted_key_value_pairs.collect {|key, value| "#{key}=#{value.to_s.inspect}"}.join(' ') + ']'
22
+ end
23
+
24
+ def to_dot
25
+ @result.join("\n")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,133 @@
1
+ module Dhaka
2
+ # Abstract base class for evaluators.
3
+ #
4
+ # Defining an evaluator is an easy way to perform syntax-directed evaluation without having to generate an abstract
5
+ # syntax tree representation of the input.
6
+ #
7
+ # An evaluation rule for a given production named +bar+ is defined by calling +for_bar+ with
8
+ # a block that performs the evaluation. For detailed examples, see the evaluators in the
9
+ # test suite.
10
+ #
11
+ # The following is an evaluator for arithmetic expressions. When a parse tree node is encountered that
12
+ # corresponds to the production named +addition+, the block passed to +for_addition+ is invoked. The +evaluate+
13
+ # method is then recursively called on the child nodes, in this case the operands to the addition operation. The
14
+ # result is obtained by adding the evaluation results of the child nodes.
15
+ #
16
+ # class ArithmeticPrecedenceEvaluator < Dhaka::Evaluator
17
+ #
18
+ # self.grammar = ArithmeticPrecedenceGrammar
19
+ #
20
+ # define_evaluation_rules do
21
+ #
22
+ # for_subtraction do
23
+ # evaluate(child_nodes[0]) - evaluate(child_nodes[2])
24
+ # end
25
+ #
26
+ # for_addition do
27
+ # evaluate(child_nodes[0]) + evaluate(child_nodes[2])
28
+ # end
29
+ #
30
+ # for_division do
31
+ # evaluate(child_nodes[0]).to_f/evaluate(child_nodes[2])
32
+ # end
33
+ #
34
+ # for_multiplication do
35
+ # evaluate(child_nodes[0]) * evaluate(child_nodes[2])
36
+ # end
37
+ #
38
+ # for_literal do
39
+ # child_nodes[0].token.value.to_i
40
+ # end
41
+ #
42
+ # for_parenthetized_expression do
43
+ # evaluate(child_nodes[1])
44
+ # end
45
+ #
46
+ # for_negated_expression do
47
+ # -evaluate(child_nodes[1])
48
+ # end
49
+ #
50
+ # for_power do
51
+ # evaluate(child_nodes[0])**evaluate(child_nodes[2])
52
+ # end
53
+ #
54
+ # end
55
+ #
56
+ # end
57
+
58
+ class Evaluator < SimpleDelegator
59
+ class << self
60
+ # Define evaluation rules within a block passed to this method. The evaluator will define
61
+ # default evaluation rules for pass-through productions (i.e. productions with expansions
62
+ # consisting of exactly one grammar symbol). The default evaluation rule for such productions
63
+ # is to simply return the result of calling +evaluate+ on the unique child node. Setting the
64
+ # <tt>:raise_error</tt> option to true tells the evaluator to throw an exception if you neglect
65
+ # to define a rule for a non-pass-through production (one where the expansion consists of
66
+ # multiple symbols), listing all the productions that absolutely need to be defined before you
67
+ # can continue.
68
+ def define_evaluation_rules(options = {})
69
+ yield
70
+ check_definitions(options)
71
+ end
72
+
73
+ private
74
+
75
+ def check_definitions(options)
76
+ filter = lambda {|productions| productions.map {|production| production.name} - actions}
77
+ pass_through_productions_without_rules = filter[grammar.productions.select {|production| production.expansion.size == 1}]
78
+ pass_through_productions_without_rules.each do |rule_name|
79
+ send(:define_method, rule_name) do
80
+ evaluate(child_nodes.first)
81
+ end
82
+ end
83
+ non_trivial_productions_with_rules_undefined = filter[grammar.productions.select {|production| production.expansion.size != 1}]
84
+ raise EvaluatorDefinitionError.new(non_trivial_productions_with_rules_undefined) unless non_trivial_productions_with_rules_undefined.empty? || !options[:raise_error]
85
+ end
86
+
87
+ def inherited(evaluator)
88
+ class << evaluator
89
+ attr_accessor :grammar, :actions
90
+ end
91
+ evaluator.actions = []
92
+ end
93
+
94
+ def method_missing(method_name, *args, &blk)
95
+ name = method_name.to_s
96
+ if name =~ /^for_(.+)$/
97
+ rule_name = $1
98
+ raise "Attempted to define evaluation rule for non-existent production '#{rule_name}'" unless grammar.production_named(rule_name)
99
+ actions << rule_name
100
+ send(:define_method, rule_name, &blk)
101
+ else
102
+ super
103
+ end
104
+ end
105
+ end
106
+
107
+ # Evaluate a parse tree node.
108
+ def evaluate node
109
+ @node_stack ||= []
110
+ @node_stack << node
111
+ __setobj__(@node_stack.last)
112
+ result = send(node.production.name)
113
+ @node_stack.pop
114
+ __setobj__(@node_stack.last)
115
+ result
116
+ end
117
+
118
+ def initialize
119
+
120
+ end
121
+ end
122
+
123
+ class EvaluatorDefinitionError < StandardError #:nodoc:
124
+ def initialize(non_trivial_productions_with_rules_undefined)
125
+ @non_trivial_productions_with_rules_undefined = non_trivial_productions_with_rules_undefined
126
+ end
127
+
128
+ def to_s
129
+ result = "The following non-trivial productions do not have any evaluation rules defined:\n"
130
+ result << @non_trivial_productions_with_rules_undefined.join("\n")
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,15 @@
1
+ module Dhaka
2
+ # A subclass of Hash with a dirty flag
3
+ class ClosureHash < Hash #:nodoc:
4
+ attr_accessor :dirty
5
+
6
+ def initialize
7
+ super
8
+ @dirty = false
9
+ end
10
+
11
+ def load_set(set)
12
+ set.each {|item| self[item] = item}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,240 @@
1
+ module Dhaka
2
+
3
+ # Reserved name for the start symbol for all grammars.
4
+ START_SYMBOL_NAME = "_Start_"
5
+ END_SYMBOL_NAME = "_End_" #:nodoc:
6
+
7
+ # Productions for specific grammar symbols are defined in the context of this class.
8
+ class ProductionBuilder
9
+ # +symbol+ is the grammar symbol that productions are being defined for.
10
+ def initialize(grammar, symbol)
11
+ @grammar = grammar
12
+ @symbol = symbol
13
+ end
14
+
15
+ def to_byte(char)
16
+ char.respond_to?(:bytes) ? char.bytes.first : char[0]
17
+ end
18
+
19
+ # Creates a new production for +symbol+ with an expansion of +expansion+. The options hash can include
20
+ # a directive <tt>:prec</tt>, the value of which is a grammar symbol name. The precedence of the production is then
21
+ # set to the precedence of the grammar symbol corresponding to that name.
22
+ #
23
+ # See the arithmetic precedence grammar in the test suites for an example.
24
+ def method_missing(production_name, expansion, options = {}, &blk)
25
+ expansion_symbols = Array(expansion).collect {|name| @grammar.symbols[name]}
26
+ production_args = [@symbol, expansion_symbols, production_name.to_s, blk, @grammar.production_index]
27
+ if precedence_symbol_name = options[:prec]
28
+ production_args << @grammar.symbol_for_name(precedence_symbol_name).precedence
29
+ end
30
+
31
+ production = Production.new(*production_args)
32
+ @grammar.production_index += 1
33
+
34
+ @symbol.nullable = true if expansion_symbols.empty?
35
+ @grammar.productions_by_symbol[production.symbol] << production
36
+ raise "Duplicate production named #{production.name}" if @grammar.productions_by_name[production.name]
37
+ @grammar.productions_by_name[production.name] = production
38
+ end
39
+ end
40
+
41
+ # The precedence builder defines three methods, +left+, +right+ and +nonassoc+. These accept arrays of grammar
42
+ # symbols all of which have the same precedence level and associativity.
43
+ #
44
+ # See the arithmetic precedence grammar in the test suites for an example of how this works.
45
+ class PrecedenceBuilder
46
+ def initialize(grammar) #:nodoc:
47
+ @grammar = grammar
48
+ @precedence_level = 0
49
+ end
50
+
51
+ [:left, :right, :nonassoc].each do |associativity|
52
+ define_method(associativity) do |symbols|
53
+ assign_precedences associativity, symbols
54
+ end
55
+ end
56
+
57
+ private
58
+ def assign_precedences(associativity, symbol_names)
59
+ symbol_names.each do |symbol_name|
60
+ symbol = @grammar.symbols[symbol_name]
61
+ symbol.precedence = Precedence.new(@precedence_level, associativity)
62
+ end
63
+ @precedence_level += 1
64
+ end
65
+ end
66
+
67
+ # Abstract base class for grammar specifications.
68
+ #
69
+ # The following is a grammar specification for simple arithmetic. Precedences are specified as in Yacc -
70
+ # in ascending order of binding strength, with equal-strength symbols on the same level.
71
+ # Production rules are specified for each symbol by specifying the name of the production (used when
72
+ # encoding the Evaluator) and the expansion for that particular production. For example, the production named
73
+ # +addition+ expands the symbol <tt>'E'</tt> to the list of symbols <tt>['E', '+', 'E']</tt>.
74
+ #
75
+ # class ArithmeticPrecedenceGrammar < Dhaka::Grammar
76
+ # precedences do
77
+ # left ['+', '-']
78
+ # left ['*', '/']
79
+ # nonassoc ['^']
80
+ # end
81
+ #
82
+ # for_symbol(Dhaka::START_SYMBOL_NAME) do
83
+ # expression ['E']
84
+ # end
85
+ #
86
+ # for_symbol('E') do
87
+ # addition ['E', '+', 'E']
88
+ # subtraction ['E', '-', 'E']
89
+ # multiplication ['E', '*', 'E']
90
+ # division ['E', '/', 'E']
91
+ # power ['E', '^', 'E']
92
+ # literal ['n']
93
+ # parenthetized_expression ['(', 'E', ')']
94
+ # negated_expression ['-', 'E'], :prec => '*'
95
+ # end
96
+ # end
97
+ #
98
+ # In the above grammar, the symbols <tt>+</tt> and <tt>-</tt> are declared as being +left+-associative, meaning that
99
+ # 1 + 2 + 3 is parsed as (1 + 2) + 3 as opposed to 1 + (2 + 3) (+right+-associativity). The symbol <tt>^</tt> is declared
100
+ # +nonassoc+ which means that expressions such as 2 ^ 3 ^ 4 are not allowed (non-associative). <tt>+</tt> and <tt>-</tt> are listed
101
+ # before <tt>^</tt> which means that they bind lower, and an expression such as 2 + 3 ^ 5 will be always be parsed as
102
+ # 2 + (3 ^ 5) and not (2 + 3) ^ 5.
103
+ class Grammar
104
+ class << self
105
+ # Used for defining the Production-s for the symbol with name +symbol+. The block +blk+ is
106
+ # evaluated in the context of a ProductionBuilder.
107
+ def for_symbol symbol, &blk
108
+ symbol = symbols[symbol]
109
+ symbol.non_terminal = true
110
+ ProductionBuilder.new(self, symbol).instance_eval(&blk)
111
+ end
112
+
113
+ # Used for defining the precedences and associativities of symbols. The block +blk+ is
114
+ # evaluated in the context of a PrecedenceBuilder.
115
+ def precedences &blk
116
+ PrecedenceBuilder.new(self).instance_eval(&blk)
117
+ end
118
+
119
+ # Returns the grammar symbol identified by +name+
120
+ def symbol_for_name(name)
121
+ if symbols.has_key? name
122
+ symbols[name]
123
+ else
124
+ raise "No symbol with name #{name} found"
125
+ end
126
+ end
127
+
128
+ # Returns a list of all the Production-s in this grammar.
129
+ def productions
130
+ productions_by_name.values
131
+ end
132
+
133
+ def productions_for_symbol(symbol) #:nodoc:
134
+ productions_by_symbol[symbol]
135
+ end
136
+
137
+ def closure(kernel) #:nodoc:
138
+ channels = Hash.new {|hash, start_item| hash[start_item] = Set.new}
139
+ result = compute_closure(kernel) do |hash, item|
140
+ if item.next_symbol and item.next_symbol.non_terminal
141
+ productions_by_symbol[item.next_symbol].each do |production|
142
+ new_channel = spontaneous_channel(item, hash[Item.new(production, 0)])
143
+ channels[item] << new_channel
144
+ end
145
+ end
146
+ end
147
+ [result, channels]
148
+ end
149
+
150
+ def passive_channel(start_item, end_item) #:nodoc:
151
+ PassiveChannel.new(self, start_item, end_item)
152
+ end
153
+
154
+ def first(given_symbol) #:nodoc:
155
+ cached_result = __first_cache[given_symbol]
156
+ return cached_result if cached_result
157
+ result = compute_closure([given_symbol]) do |hash, symbol|
158
+ productions_by_symbol[symbol].each do |production|
159
+ symbol_index = 0
160
+ while next_symbol = production.expansion[symbol_index]
161
+ hash[next_symbol]
162
+ break unless next_symbol.nullable
163
+ symbol_index += 1
164
+ end
165
+ end if symbol.non_terminal
166
+ end.values.select {|symbol| symbol.terminal}.to_set
167
+ __first_cache[given_symbol] = result
168
+ result
169
+ end
170
+
171
+ # Returns the Production identified by +name+.
172
+ def production_named(name)
173
+ productions_by_name[name]
174
+ end
175
+
176
+ # Returns the set of terminal symbols in the grammar.
177
+ def terminal_symbols
178
+ symbols.values.select {|symbol| symbol.terminal}
179
+ end
180
+
181
+ # Returns the set of non-terminal symbols in the grammar.
182
+ def non_terminal_symbols
183
+ symbols.values.select {|symbol| symbol.non_terminal}
184
+ end
185
+
186
+ # Export the grammar to a BNF-like format
187
+ def to_bnf
188
+ result = []
189
+ last_symbol = nil
190
+ productions.sort.each do |production|
191
+ if production.symbol != last_symbol
192
+ result << ""
193
+ result << "#{production.symbol.name.inspect} :"
194
+ last_symbol = production.symbol
195
+ end
196
+ result << " | #{production.expansion.collect{|symbol| symbol.name.inspect}.join(' ')}"
197
+ end
198
+ result.join("\n")
199
+ end
200
+
201
+ private
202
+
203
+ def inherited(grammar)
204
+ class << grammar
205
+ attr_accessor :symbols, :productions_by_symbol, :productions_by_name, :start_symbol, :end_symbol, :__first_cache, :production_index
206
+ end
207
+ grammar.symbols = Hash.new {|hash, name| hash[name] = GrammarSymbol.new(name)}
208
+ grammar.productions_by_symbol = Hash.new {|hash, name| hash[name] = Set.new([])}
209
+ grammar.productions_by_name = {}
210
+ grammar.end_symbol = grammar.symbols[END_SYMBOL_NAME]
211
+ grammar.start_symbol = grammar.symbols[START_SYMBOL_NAME]
212
+ grammar.__first_cache = {}
213
+ grammar.production_index = 0
214
+ end
215
+
216
+ def spontaneous_channel(start_item, end_item)
217
+ SpontaneousChannel.new(self, start_item, end_item)
218
+ end
219
+
220
+ def compute_closure(initial)
221
+ closure_hash = ClosureHash.new do |hash, item|
222
+ hash.dirty = true
223
+ hash[item] = item
224
+ end
225
+
226
+ closure_hash.load_set(initial)
227
+
228
+ loop do
229
+ closure_hash.keys.each do |element|
230
+ yield closure_hash, element
231
+ end
232
+ break unless closure_hash.dirty
233
+ closure_hash.dirty = false
234
+ end
235
+ closure_hash
236
+ end
237
+ end
238
+ end
239
+
240
+ end
@@ -0,0 +1,27 @@
1
+ module Dhaka
2
+ # Each grammar symbol is uniquely identified by a string name. The name of a symbol can
3
+ # be anything (except the two reserved names <tt>'\_Start_'</tt> and <tt>'\_End_'</tt>) and need not
4
+ # correspond to its character representation. For example, an ampersand in the input string could
5
+ # be tokenized as a symbol with a name 'AND_OP'. You never have to directly instantiate a
6
+ # GrammarSymbol. It is done implicitly for you when you define a Grammar.
7
+ class GrammarSymbol
8
+ attr_reader :name
9
+ attr_accessor :non_terminal, :nullable, :precedence, :associativity
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ def terminal
16
+ !non_terminal
17
+ end
18
+
19
+ def to_s #:nodoc:
20
+ name.dup
21
+ end
22
+
23
+ def <=> other
24
+ name <=> other.name
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module Dhaka
2
+ class Precedence #:nodoc:
3
+ include Comparable
4
+ attr_reader :precedence_level, :associativity
5
+
6
+ def initialize(precedence_level, associativity)
7
+ @precedence_level = precedence_level
8
+ @associativity = associativity
9
+ end
10
+
11
+ def <=> other
12
+ precedence_level <=> other.precedence_level
13
+ end
14
+
15
+ def to_s
16
+ "Precedence: #{precedence_level} Associativity: #{associativity}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module Dhaka
2
+ class Production
3
+ include Comparable
4
+
5
+ attr_reader :symbol, :expansion, :name, :action, :priority
6
+
7
+ def initialize(symbol, expansion, name, action, priority, precedence = nil)
8
+ @symbol = symbol
9
+ @expansion = expansion
10
+ @name = name
11
+ @precedence = precedence
12
+ @action = action || proc { self }
13
+ @priority = priority
14
+ end
15
+
16
+ def precedence
17
+ unless @precedence
18
+ expansion.reverse_each do |symbol|
19
+ if symbol.terminal
20
+ @precedence = symbol.precedence
21
+ break
22
+ end
23
+ end
24
+ end
25
+ @precedence
26
+ end
27
+
28
+ def to_s #:nodoc:
29
+ "#{name} #{symbol} ::= #{expansion.join(' ')}"
30
+ end
31
+
32
+ def <=> other
33
+ priority <=> other.priority
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Dhaka
2
+ module LexerSupport
3
+ class AcceptAction
4
+ attr_reader :pattern
5
+ def initialize(pattern)
6
+ @pattern = pattern
7
+ end
8
+
9
+ def call(lexer_run)
10
+ lexer_run.accept(pattern)
11
+ end
12
+
13
+ def compile_to_ruby_source
14
+ "accept(#{pattern.inspect})"
15
+ end
16
+
17
+ def to_dot
18
+ "Accept #{pattern.inspect}"
19
+ end
20
+ end
21
+
22
+ class LookaheadAcceptAction < AcceptAction
23
+ def call(lexer_run)
24
+ lexer_run.accept_last_saved_checkpoint(pattern)
25
+ end
26
+
27
+ def compile_to_ruby_source
28
+ "accept_with_lookahead(#{pattern.inspect})"
29
+ end
30
+
31
+ def to_dot
32
+ "Accept With Lookahead #{pattern.inspect}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ module Dhaka
2
+ module LexerSupport
3
+ DIGITS = ('0'..'9').to_a
4
+ LOWERCASE_LETTERS = ('a'..'z').to_a
5
+ UPPERCASE_LETTERS = ('A'..'Z').to_a
6
+ LETTERS = LOWERCASE_LETTERS + UPPERCASE_LETTERS
7
+ WHITESPACE = [" ", "\r", "\n", "\t"]
8
+ SYMBOLS = %w| ~ ` ! @ # % & _ = : ; " ' < , > - |
9
+ CLASSES = {'d' => DIGITS, 'w' => LETTERS, 's' => WHITESPACE}
10
+
11
+ OPERATOR_CHARACTERS = {'(' => 'open_parenth', ')' => 'close_parenth', '[' => 'open_square_bracket',
12
+ ']' => 'close_square_bracket', '+' => 'plus', '*' => 'asterisk',
13
+ '?' => 'question_mark', '.' => 'period', '\\' => 'back_slash',
14
+ '|' => 'pipe', '{' => 'left_curly_brace', '}' => 'right_curly_brace',
15
+ '/' => 'forward_slash', '^' => 'caret', '$' => 'dollar'}
16
+
17
+ SET_OPERATOR_CHARACTERS = %w| - ^ [ ] \\ |
18
+
19
+ ALL_CHARACTERS = DIGITS + LETTERS + SYMBOLS + WHITESPACE + OPERATOR_CHARACTERS.keys
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ module Dhaka
2
+ # Abstract base class of all compiled Lexers. It is only used by generated code.
3
+ class CompiledLexer
4
+
5
+ class << self
6
+ # Returns a LexerRun that tokenizes +input+.
7
+ def lex input
8
+ LexerRun.new(self, input)
9
+ end
10
+
11
+ def start_state #:nodoc:
12
+ states[start_state_id]
13
+ end
14
+
15
+ def action_for_pattern pattern #:nodoc:
16
+ specification.items[pattern].action
17
+ end
18
+
19
+ private
20
+ def inherited(lexer)
21
+ class << lexer
22
+ attr_accessor :states, :specification, :start_state_id
23
+ end
24
+ lexer.states = Hash.new do |hash, state_id|
25
+ hash[state_id] = LexerSupport::State.new(lexer, nil)
26
+ end
27
+ end
28
+
29
+ def at_state x, &blk
30
+ states[x].instance_eval(&blk)
31
+ end
32
+
33
+ def start_with start_state_id
34
+ self.start_state_id = start_state_id
35
+ end
36
+
37
+ def switch_to dest_state_id
38
+ states[dest_state_id]
39
+ end
40
+
41
+ def inspect
42
+ "<Dhaka::CompiledLexer specification : #{specification}>"
43
+ end
44
+ end
45
+ end
46
+ end