jaina 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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