jaina 0.0.0 → 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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +19 -0
  4. data/README.md +160 -1
  5. data/jaina.gemspec +2 -1
  6. data/lib/jaina/parser/ast/context.rb +98 -0
  7. data/lib/jaina/parser/ast/evaluator.rb +17 -0
  8. data/lib/jaina/parser/ast/tree.rb +35 -0
  9. data/lib/jaina/parser/ast/tree_builder.rb +129 -0
  10. data/lib/jaina/parser/ast.rb +62 -0
  11. data/lib/jaina/parser/code_converter/to_postfix_form.rb +167 -0
  12. data/lib/jaina/parser/code_converter/to_prefix_form.rb +122 -0
  13. data/lib/jaina/parser/code_converter.rb +28 -0
  14. data/lib/jaina/parser/expression/operator/abstract/dsl.rb +256 -0
  15. data/lib/jaina/parser/expression/operator/abstract.rb +67 -0
  16. data/lib/jaina/parser/expression/operator/and.rb +12 -0
  17. data/lib/jaina/parser/expression/operator/grouping/dsl.rb +100 -0
  18. data/lib/jaina/parser/expression/operator/grouping.rb +12 -0
  19. data/lib/jaina/parser/expression/operator/left_corner.rb +10 -0
  20. data/lib/jaina/parser/expression/operator/non_terminal.rb +25 -0
  21. data/lib/jaina/parser/expression/operator/not.rb +12 -0
  22. data/lib/jaina/parser/expression/operator/or.rb +12 -0
  23. data/lib/jaina/parser/expression/operator/right_corner.rb +10 -0
  24. data/lib/jaina/parser/expression/operator/terminal.rb +25 -0
  25. data/lib/jaina/parser/expression/operator.rb +15 -0
  26. data/lib/jaina/parser/expression/registry/access_interface_mixin.rb +49 -0
  27. data/lib/jaina/parser/expression/registry.rb +127 -0
  28. data/lib/jaina/parser/expression.rb +87 -0
  29. data/lib/jaina/parser/tokenizer.rb +37 -0
  30. data/lib/jaina/parser.rb +44 -0
  31. data/lib/jaina/version.rb +5 -1
  32. data/lib/jaina.rb +58 -4
  33. metadata +29 -2
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::CodeConverter::ToPostfixForm
6
+ # @api private
7
+ # @since 0.1.0
8
+ extend Forwardable
9
+
10
+ class << self
11
+ # @param program [String]
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ def call(program)
17
+ new(program).call
18
+ end
19
+ end
20
+
21
+ # @since 0.1.0
22
+ def_delegators Jaina::Parser::Expression,
23
+ :non_terminal?,
24
+ :terminal?,
25
+ :group_opener?,
26
+ :group_closener?
27
+
28
+ # @param program [String]
29
+ # @return [void]
30
+ #
31
+ # @api private
32
+ # @since 0.1.0
33
+ def initialize(program)
34
+ @program = program.dup.tap(&:freeze)
35
+ @tokens = Jaina::Parser::Tokenizer.tokenize(program)
36
+ end
37
+
38
+ # @return [String]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def call
43
+ to_postfix_form
44
+ end
45
+
46
+ private
47
+
48
+ # @return [String]
49
+ #
50
+ # @api private
51
+ # @since 0.1.0
52
+ attr_reader :program
53
+
54
+ # @return [Array<String>]
55
+ #
56
+ # @api private
57
+ # @since 0.1.0
58
+ attr_reader :tokens
59
+
60
+ # @return [String]
61
+ #
62
+ # @api private
63
+ # @since 0.1.0
64
+ def to_postfix_form
65
+ final_expression = []
66
+ structure_operators = []
67
+ token_series = tokens.map(&:dup)
68
+
69
+ until token_series.empty?
70
+ current_token = token_series.shift
71
+
72
+ case
73
+ when non_terminal?(current_token)
74
+ process_non_terminal_token(final_expression, structure_operators, current_token)
75
+ when group_opener?(current_token)
76
+ process_group_opening_token(final_expression, structure_operators, current_token)
77
+ when group_closener?(current_token)
78
+ process_group_closing_token(final_expression, structure_operators, current_token)
79
+ when terminal?(current_token)
80
+ process_terminal_token(final_expression, structure_operators, current_token)
81
+ end
82
+ end
83
+
84
+ finish_final_expression(final_expression, structure_operators, current_token)
85
+
86
+ # NOTE: build postfixed program string
87
+ Jaina::Parser::Tokenizer.join(final_expression)
88
+ end
89
+
90
+ # @param final_expression [Array<String>]
91
+ # @param structure_operators [Array<String>]
92
+ # @param current_token [String]
93
+ # @return [void]
94
+ #
95
+ # @api private
96
+ # @since 0.1.0
97
+ def process_non_terminal_token(final_expression, structure_operators, current_token)
98
+ if structure_operators.any? # NOTE: check assocaitivity with potential next token
99
+ potential_second_token = structure_operators.last
100
+
101
+ if non_terminal?(potential_second_token)
102
+ current_expression = Jaina::Parser::Expression.fetch(current_token)
103
+ next_expression = Jaina::Parser::Expression.fetch(potential_second_token)
104
+
105
+ # NOTE: form infix to postfix form switching
106
+ final_expression.push(structure_operators.pop) if current_expression.lower?(next_expression)
107
+ end
108
+ end
109
+
110
+ structure_operators.push(current_token)
111
+ end
112
+
113
+ # @param final_expression [Array<String>]
114
+ # @param structure_operators [Array<String>]
115
+ # @param current_token [String]
116
+ # @return [void]
117
+ #
118
+ # @api private
119
+ # @since 0.1.0
120
+ def process_group_opening_token(final_expression, structure_operators, current_token)
121
+ # NOTE: remember group opening operator
122
+ structure_operators.push(current_token)
123
+ end
124
+
125
+ # @param final_expression [Array<String>]
126
+ # @param structure_operators [Array<String>]
127
+ # @param current_token [String]
128
+ # @return [void]
129
+ #
130
+ # @api private
131
+ # @since 0.1.0
132
+ def process_group_closing_token(final_expression, structure_operators, current_token)
133
+ until group_opener?(structure_operators.last)
134
+ # NOTE: push all tokens to the final expression
135
+ final_expression.push(structure_operators.pop)
136
+ end
137
+
138
+ # NOTE: drop closing operator
139
+ structure_operators.pop
140
+ end
141
+
142
+ # @param final_expression [Array<String>]
143
+ # @param structure_operators [Array<String>]
144
+ # @param current_token [String]
145
+ # @return [void]
146
+ #
147
+ # @api private
148
+ # @since 0.1.0
149
+ def process_terminal_token(final_expression, structure_operators, current_token)
150
+ # NOTE: push terminal expression to the final expression
151
+ final_expression.push(current_token)
152
+ end
153
+
154
+ # @param final_expression [Array<String>]
155
+ # @param structure_operators [Array<String>]
156
+ # @param current_token [String]
157
+ # @return [void]
158
+ #
159
+ # @api private
160
+ # @since 0.1.0
161
+ def finish_final_expression(final_expression, structure_operators, current_token)
162
+ # NOTE: fill the rest tokens to the final expression
163
+ until structure_operators.empty?
164
+ final_expression.push(structure_operators.pop)
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::CodeConverter::ToPrefixForm
6
+ # @since 0.1.0
7
+ extend Forwardable
8
+
9
+ class << self
10
+ # @param program [String]
11
+ # @return [String]
12
+ #
13
+ # @api private
14
+ # @since 0.1.0
15
+ def call(program)
16
+ new(program).call
17
+ end
18
+ end
19
+
20
+ # @since 0.1.0
21
+ def_delegators Jaina::Parser::Expression, :terminal?, :non_terminal?
22
+
23
+ # @param program [String]
24
+ # @return [void]
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ def initialize(program)
29
+ @program = program.dup.tap(&:freeze)
30
+ @postfix_form = Jaina::Parser::CodeConverter::ToPostfixForm.call(@program)
31
+ @tokens = Jaina::Parser::Tokenizer.tokenize(@postfix_form)
32
+ end
33
+
34
+ # @return [String]
35
+ #
36
+ # @api private
37
+ # @since 0.1.0
38
+ def call
39
+ to_prefix_form
40
+ end
41
+
42
+ private
43
+
44
+ # @return [String]
45
+ #
46
+ # @api private
47
+ # @since 0.1.0
48
+ attr_reader :program
49
+
50
+ # @return [String]
51
+ #
52
+ # @api private
53
+ # @since 0.1.0
54
+ attr_reader :postfix_form
55
+
56
+ # @return [Array<String>]
57
+ #
58
+ # @api private
59
+ # @since 0.1.0
60
+ attr_reader :tokens
61
+
62
+ # @return [String]
63
+ #
64
+ # @api private
65
+ # @since 0.1.0
66
+ def to_prefix_form
67
+ token_series = tokens.map(&:dup)
68
+ expression_stack = []
69
+
70
+ until token_series.empty?
71
+ current_token = token_series.shift
72
+
73
+ case
74
+ when terminal?(current_token)
75
+ process_terminal_token(current_token, expression_stack)
76
+ when non_terminal?(current_token)
77
+ process_non_terminal_token(current_token, expression_stack)
78
+ end
79
+ end
80
+
81
+ # NOTE: build prefixed program string
82
+ Jaina::Parser::Tokenizer.join(expression_stack)
83
+ end
84
+
85
+ # @param current_token [String]
86
+ # @param expression_stack [Array<String>]
87
+ # @return [void]
88
+ #
89
+ # @api private
90
+ # @since 0.1.0
91
+ def process_terminal_token(current_token, expression_stack)
92
+ expression_stack.push(current_token)
93
+ end
94
+
95
+ # @param current_token [String]
96
+ # @param expression_stack [Array<String>]
97
+ # @return [void]
98
+ #
99
+ # @api private
100
+ # @since 0.1.0
101
+ def process_non_terminal_token(current_token, expression_stack)
102
+ expression = Jaina::Parser::Expression.fetch(current_token)
103
+
104
+ case # TODO: dry
105
+ when expression.acts_as_binary_term?
106
+ first_operand = expression_stack.pop
107
+ second_operand = expression_stack.pop
108
+
109
+ prefixed_expression_parts = [expression.token, second_operand, first_operand]
110
+ prefixed_expression = Jaina::Parser::Tokenizer.join(prefixed_expression_parts)
111
+
112
+ expression_stack.push(prefixed_expression)
113
+ when expression.acts_as_unary_term?
114
+ operand = expression_stack.pop
115
+
116
+ prefixed_expression_parts = [expression.token, operand]
117
+ prefixed_expression = Jaina::Parser::Tokenizer.join(prefixed_expression_parts)
118
+
119
+ expression_stack.push(prefixed_expression)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::CodeConverter
6
+ require_relative './code_converter/to_postfix_form'
7
+ require_relative './code_converter/to_prefix_form'
8
+
9
+ class << self
10
+ # @param program [String]
11
+ # @return [String]
12
+ #
13
+ # @api private
14
+ # @since 0.1.0
15
+ def to_postfix_form(program)
16
+ ToPrefixForm.call(program)
17
+ end
18
+
19
+ # @param program [String]
20
+ # @return [String]
21
+ #
22
+ # @api private
23
+ # @since 0.1.0
24
+ def to_prefix_form(program)
25
+ ToPrefixForm.call(program)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module Jaina::Parser::Expression::Operator::Abstract::DSL
6
+ # @since 0.1.0
7
+ Error = Class.new(StandardError)
8
+ # @since 0.1.0
9
+ IncorrectTokenError = Class.new(Error)
10
+ # @since 0.1.0
11
+ IncorrectPrecedenceLevelError = Class.new(Error)
12
+ # @since 0.1.0
13
+ IncorrectAssociativityDirectionError = Class.new(Error)
14
+
15
+ # @return [Symbol]
16
+ #
17
+ # @api private
18
+ # @since 0.1.0
19
+ LEFT_ASSOC = :left
20
+
21
+ # @return [Symbol]
22
+ #
23
+ # @api private
24
+ # @since 0.1.0
25
+ RIGHT_ASSOC = :right
26
+
27
+ class << self
28
+ # @param base_klass [Class]
29
+ # @return [void]
30
+ #
31
+ # @api private
32
+ # @since 0.1.0
33
+ def included(base_klass)
34
+ base_klass.instance_variable_set(:@token, nil)
35
+ base_klass.instance_variable_set(:@precedence_level, nil)
36
+ base_klass.instance_variable_set(:@associativity_direction, nil)
37
+ base_klass.instance_variable_set(:@acts_as_binary_term, false)
38
+ base_klass.instance_variable_set(:@acts_as_unary_term, false)
39
+
40
+ base_klass.extend ClassMethods
41
+ base_klass.include InstanceMethods
42
+ base_klass.singleton_class.prepend(ClassInheritance)
43
+ end
44
+ end
45
+
46
+ # @api private
47
+ # @since 0.1.0
48
+ module ClassInheritance
49
+ # @param child_klass [Class]
50
+ # @return [void]
51
+ #
52
+ # @api private
53
+ # @since 0.1.0
54
+ def inherited(child_klass)
55
+ child_klass.instance_variable_set(
56
+ :@token,
57
+ instance_variable_get(:@token)
58
+ )
59
+
60
+ child_klass.instance_variable_set(
61
+ :@precedence_level,
62
+ instance_variable_get(:@precedence_level)
63
+ )
64
+
65
+ child_klass.instance_variable_set(
66
+ :@associativity_direction,
67
+ instance_variable_get(:@associativity_direction)
68
+ )
69
+
70
+ child_klass.instance_variable_set(
71
+ :@acts_as_binary_term,
72
+ instance_variable_get(:@acts_as_binary_term)
73
+ )
74
+
75
+ child_klass.instance_variable_set(
76
+ :@acts_as_unary_term,
77
+ instance_variable_get(:@acts_as_unary_term)
78
+ )
79
+
80
+ super
81
+ end
82
+ end
83
+
84
+ # @api private
85
+ # @since 0.1.0
86
+ module ClassMethods
87
+ # @param term [String, NilClass]
88
+ # @return [String, NilClass]
89
+ #
90
+ # @api private
91
+ # @since 0.1.0
92
+ def token(term = nil)
93
+ unless term.is_a?(String) || term.is_a?(NilClass)
94
+ raise IncorrectTokenError, 'Token should be a type of string'
95
+ end
96
+
97
+ @token = term unless term.nil?
98
+ @token
99
+ end
100
+
101
+ # @param level [Integer, NilClass]
102
+ # @return [Integer, NilClass]
103
+ #
104
+ # @api private
105
+ # @since 0.1.0
106
+ def precedence_level(level = nil)
107
+ unless level.is_a?(Integer) || level.is_a?(NilClass)
108
+ raise IncorrectPrecedenceLevelError, 'Precendence level should be a type of integer'
109
+ end
110
+
111
+ @precedence_level = level unless level.nil?
112
+ @precedence_level
113
+ end
114
+
115
+ # @param assoc [String, NilClass]
116
+ # @return [String, NilClass]
117
+ #
118
+ # @api private
119
+ # @since 0.1.0
120
+ def associativity_direction(assoc = nil)
121
+ unless assoc.is_a?(NilClass) || assoc == LEFT_ASSOC || assoc == RIGHT_ASSOC
122
+ raise(
123
+ IncorrectAssociativityDirectionError,
124
+ "Associativity direction can be :#{LEFT_ASSOC} or :#{RIGHT_ASSOC}"
125
+ )
126
+ end
127
+
128
+ @associativity_direction = assoc unless assoc.nil?
129
+ @associativity_direction
130
+ end
131
+
132
+ # @return [void]
133
+ #
134
+ # @api private
135
+ # @since 0.1.0
136
+ def acts_as_binary_term
137
+ @acts_as_binary_term = true
138
+ end
139
+
140
+ # @return [void]
141
+ #
142
+ # @api private
143
+ # @since 0.1.0
144
+ def acts_as_unary_term
145
+ @acts_as_unary_term = true
146
+ end
147
+
148
+ # @return [void]
149
+ #
150
+ # @api private
151
+ # @since 0.1.0
152
+ def acts_as_binary_term?
153
+ @acts_as_binary_term
154
+ end
155
+
156
+ # @return [void]
157
+ #
158
+ # @api private
159
+ # @since 0.1.0
160
+ def acts_as_unary_term?
161
+ @acts_as_unary_term
162
+ end
163
+
164
+ # @return [Boolean]
165
+ #
166
+ # @api private
167
+ # @since 0.1.0
168
+ def terminal?
169
+ false
170
+ end
171
+
172
+ # @return [Boolean]
173
+ #
174
+ # @api private
175
+ # @since 0.1.0
176
+ def non_terminal?
177
+ false
178
+ end
179
+ end
180
+
181
+ # @api private
182
+ # @since 0.1.0
183
+ module InstanceMethods
184
+ # @return [String, NilClass]
185
+ #
186
+ # @api private
187
+ # @since 0.1.0
188
+ def token
189
+ self.class.token
190
+ end
191
+
192
+ # @return [Integer, NilClass]
193
+ #
194
+ # @api private
195
+ # @since 0.1.0
196
+ def precedence_level
197
+ self.class.precedence_level
198
+ end
199
+
200
+ # @return [Symbol, NilClass]
201
+ #
202
+ # @api private
203
+ # @since 0.1.0
204
+ def associativity_direction
205
+ self.class.associativity_direction
206
+ end
207
+
208
+ # @return [Boolean]
209
+ #
210
+ # @api private
211
+ # @since 0.1.0
212
+ def acts_as_group_closener?
213
+ self.class.acts_as_group_closener?
214
+ end
215
+
216
+ # @return [Boolean]
217
+ #
218
+ # @api private
219
+ # @since 0.1.0
220
+ def acts_as_group_opener?
221
+ self.class.acts_as_group_opener?
222
+ end
223
+
224
+ # @return [Boolean]
225
+ #
226
+ # @api private
227
+ # @since 0.1.0
228
+ def acts_as_binary_term?
229
+ self.class.acts_as_binary_term?
230
+ end
231
+
232
+ # @return [Boolean]
233
+ #
234
+ # @api private
235
+ # @since 0.1.0
236
+ def acts_as_unary_term?
237
+ self.class.acts_as_unary_term?
238
+ end
239
+
240
+ # @return [Boolean]
241
+ #
242
+ # @api private
243
+ # @since 0.1.0
244
+ def terminal?
245
+ self.class.terminal?
246
+ end
247
+
248
+ # @return [Boolean]
249
+ #
250
+ # @api private
251
+ # @since 0.1.0
252
+ def non_terminal?
253
+ self.class.non_terminal?
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::Expression::Operator::Abstract
6
+ require_relative './abstract/dsl'
7
+
8
+ # @since 0.1.0
9
+ include DSL
10
+ # @since 0.1.0
11
+ Error = Class.new(StandardError)
12
+ # @since 0.1.0
13
+ InompatibleDirectionComparisonError = Class.new(Error)
14
+
15
+ class << self
16
+ # @param another_operator [Class<Jaina::Parser::Expression::Operator::Abstract>]
17
+ # @return [Boolean]
18
+ #
19
+ # @raise [InompatibleDirectionComparisonError]
20
+ #
21
+ # @api private
22
+ # @since 0.1.0
23
+ def lower?(another_operator)
24
+ raise(
25
+ InompatibleDirectionComparisonError,
26
+ "Trying to compare non-comparable terms `#{token}` and `#{another_operator.token}`"
27
+ ) if associativity_direction.nil? || another_operator.associativity_direction.nil?
28
+
29
+ if associativity_direction == Jaina::Parser::Expression::Operator::Abstract::DSL::LEFT_ASSOC
30
+ associativity_direction <= another_operator.associativity_direction
31
+ else
32
+ associativity_direction < another_operator.associativity_direction
33
+ end
34
+ end
35
+ end
36
+
37
+ # @return [Array<Jaina::Parser::Expressions::Operator::Abstract>]
38
+ #
39
+ # @api private
40
+ # @since 0.1.0
41
+ attr_reader :expressions
42
+
43
+ # @param expressions [Array<Jaina::Parser::Expression::Operator::Abstract>]
44
+ # @return [void]
45
+ #
46
+ # @api private
47
+ # @since 0.1.0
48
+ def initialize(*expressions)
49
+ @expressions = expressions
50
+ end
51
+
52
+ # @param context [Any]
53
+ # @return [Any]
54
+ #
55
+ # @api private
56
+ # @since 0.1.0
57
+ def evaluate(context); end
58
+
59
+ # @param another_operator [Class<Jaina::Parser::Expression::Operator::Abstract>]
60
+ # @return [Boolean]
61
+ #
62
+ # @api private
63
+ # @since 0.1.0
64
+ def lower(another_operator)
65
+ self.class.lower(another_operator)
66
+ end
67
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaina::Parser::Expression::Operator
4
+ # @api private
5
+ # @since 0.1.0
6
+ class And < NonTerminal
7
+ precedence_level 3
8
+ associativity_direction :left
9
+ token 'AND'
10
+ acts_as_binary_term
11
+ end
12
+ end