resyma 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +31 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +69 -0
  7. data/LICENSE +674 -0
  8. data/README.md +167 -0
  9. data/Rakefile +8 -0
  10. data/lib/resyma/core/algorithm/engine.rb +189 -0
  11. data/lib/resyma/core/algorithm/matcher.rb +48 -0
  12. data/lib/resyma/core/algorithm/tuple.rb +25 -0
  13. data/lib/resyma/core/algorithm.rb +5 -0
  14. data/lib/resyma/core/automaton/builder.rb +78 -0
  15. data/lib/resyma/core/automaton/definition.rb +32 -0
  16. data/lib/resyma/core/automaton/epsilon_NFA.rb +115 -0
  17. data/lib/resyma/core/automaton/matchable.rb +16 -0
  18. data/lib/resyma/core/automaton/regexp.rb +175 -0
  19. data/lib/resyma/core/automaton/state.rb +22 -0
  20. data/lib/resyma/core/automaton/transition.rb +58 -0
  21. data/lib/resyma/core/automaton/visualize.rb +23 -0
  22. data/lib/resyma/core/automaton.rb +9 -0
  23. data/lib/resyma/core/parsetree/builder.rb +89 -0
  24. data/lib/resyma/core/parsetree/converter.rb +61 -0
  25. data/lib/resyma/core/parsetree/default_converter.rb +331 -0
  26. data/lib/resyma/core/parsetree/definition.rb +77 -0
  27. data/lib/resyma/core/parsetree/source.rb +73 -0
  28. data/lib/resyma/core/parsetree/traversal.rb +26 -0
  29. data/lib/resyma/core/parsetree.rb +8 -0
  30. data/lib/resyma/core/utilities.rb +30 -0
  31. data/lib/resyma/language.rb +290 -0
  32. data/lib/resyma/nise/date.rb +53 -0
  33. data/lib/resyma/nise/rubymoji.rb +13 -0
  34. data/lib/resyma/nise/toml.rb +63 -0
  35. data/lib/resyma/parsetree.rb +163 -0
  36. data/lib/resyma/program/automaton.rb +84 -0
  37. data/lib/resyma/program/parsetree.rb +79 -0
  38. data/lib/resyma/program/traverse.rb +77 -0
  39. data/lib/resyma/version.rb +5 -0
  40. data/lib/resyma.rb +12 -0
  41. data/resyma.gemspec +47 -0
  42. data/sig/resyma.rbs +4 -0
  43. metadata +184 -0
@@ -0,0 +1,331 @@
1
+ require "resyma/core/parsetree/definition"
2
+ require "resyma/core/parsetree/converter"
3
+
4
+ module Resyma
5
+ module Core
6
+ DEFAULT_CONVERTER = Converter.new
7
+
8
+ CONST_TOKEN_TABLE = {
9
+ "(" => :round_left,
10
+ ")" => :round_right,
11
+ "begin" => :kwd_begin,
12
+ "end" => :kwd_end,
13
+ "," => :comma,
14
+ "[" => :square_left,
15
+ "]" => :square_right,
16
+ "*" => :star,
17
+ "**" => :star2,
18
+ ":" => :colon,
19
+ "=>" => :arrow,
20
+ "{" => :curly_left,
21
+ "}" => :curly_right,
22
+ ".." => :dot2,
23
+ "..." => :dot3,
24
+ "defined?" => :kwd_defined?,
25
+ "." => :dot,
26
+ "&." => :and_dot,
27
+ "do" => :kwd_do,
28
+ "&" => :ampersand,
29
+ "|" => :tube,
30
+ "=" => :eq,
31
+ "true" => :the_true,
32
+ "false" => :the_false,
33
+ "nil" => :the_nil
34
+ }.freeze
35
+ end
36
+ end
37
+
38
+ Resyma::Core::DEFAULT_CONVERTER.instance_eval do
39
+
40
+ def make_token(type, value, parent, index, ast)
41
+ Resyma::Core::ParseTree.new(type, [value], parent, index, true, ast)
42
+ end
43
+
44
+ def_fallback do |ast, parent, index|
45
+ make_token(:any, ast.type.to_s, parent, index, ast)
46
+ end
47
+
48
+ simple_literal = {
49
+ true: :the_true,
50
+ false: :the_false,
51
+ nil: :the_nil,
52
+ complex: :complex,
53
+ rational: :rational,
54
+ str: :str,
55
+ regexp: :regexp,
56
+ sym: :sym,
57
+ self: :the_self,
58
+ lvar: :id,
59
+ ivar: :ivar,
60
+ cvar: :cvar,
61
+ gvar: :gvar,
62
+ nth_ref: :nth_ref,
63
+ back_ref: :back_ref
64
+ }
65
+
66
+ def_rule simple_literal.keys do |ast, parent, index|
67
+ make_token simple_literal[ast.type], ast.loc.expression.source,
68
+ parent, index, ast
69
+ end
70
+
71
+ number_regexp = /^\s*(\+|-)?\s*([0-9.]+)\s*$/
72
+ def_rule %i[int float] do |ast, parent, index|
73
+ m = number_regexp.match(ast.loc.expression.source)
74
+ if m.nil?
75
+ raise Resyma::Core::ConversionError,
76
+ "Internal error: Number pattern [#{ast.loc.expression}] is invalid"
77
+ end
78
+ Resyma::Core::ParseTreeBuilder.root(ast.type, nil, index, ast) do
79
+ leaf :numop, m[1] unless m[1].nil?
80
+ leaf :numbase, m[2]
81
+ end.build(parent)
82
+ end
83
+
84
+ def check_boundary(boundary, pt_builder)
85
+ return if boundary.nil?
86
+
87
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
88
+ value = boundary.source
89
+ type = ctt[value]
90
+ if type.nil?
91
+ raise Resyma::Core::ConversionError,
92
+ "Unknwon boundary-token of AST with type <begin>: #{value}"
93
+ end
94
+ pt_builder.add_child!(type, nil, true, [value])
95
+ end
96
+
97
+ def_rule %i[begin kwbegin] do |ast, parent, index|
98
+ ptb = Resyma::Core::ParseTreeBuilder.new(:begin, index, false, [], ast)
99
+ check_boundary ast.loc.begin, ptb
100
+ ast.children.each do |sub|
101
+ ptb.add_parsetree_child!(convert(sub), sub)
102
+ end
103
+ check_boundary ast.loc.end, ptb
104
+ ptb.build(parent)
105
+ end
106
+
107
+ def_rule %i[dstr dsym xstr] do |ast, parent, index|
108
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
109
+ ast.children.each do |sub|
110
+ ptb.add_parsetree_child!(convert(sub), sub)
111
+ end
112
+ ptb.build(parent)
113
+ end
114
+
115
+ def def_rule_for_seq(type, open, sep, close)
116
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
117
+ def_rule type do |ast, parent, index|
118
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
119
+ ptb.add_child!(ctt[open], nil, true, [open]) unless open.nil?
120
+ unless ast.children.empty?
121
+ first = ast.children.first
122
+ ptb.add_parsetree_child!(convert(first), first)
123
+ ast.children[1..].each do |sub|
124
+ ptb.add_child!(ctt[sep], nil, true, [sep]) unless sep.nil?
125
+ ptb.add_parsetree_child!(convert(sub), sub)
126
+ end
127
+ end
128
+ ptb.add_child!(ctt[close], nil, true, [close]) unless close.nil?
129
+ ptb.build(parent)
130
+ end
131
+ end
132
+
133
+ def_rule_for_seq(:array, "[", ",", "]")
134
+ def_rule_for_seq(:hash, "{", ",", "}")
135
+ def_rule_for_seq(:kwargs, nil, ",", nil)
136
+
137
+ def_rule %i[splat kwsplat] do |ast, parent, index|
138
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
139
+ star = ast.loc.operator.source
140
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
141
+ ptb.add_child!(ctt[star], nil, true, [star])
142
+ first = ast.children.first
143
+ ptb.add_parsetree_child!(convert(first), first)
144
+ ptb.build(parent)
145
+ end
146
+
147
+ def_rule :pair do |ast, parent, index|
148
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
149
+ left, right = ast.children
150
+ op_value = ast.loc.operator.source
151
+ op_type = ctt[op_value]
152
+ if op_type.nil?
153
+ raise Resyma::Core::ConversionError,
154
+ "Unknown operator for hash pair: #{op_value}"
155
+ end
156
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
157
+ ptb.add_parsetree_child!(convert(left), left)
158
+ ptb.add_child!(op_type, nil, true, [op_value])
159
+ ptb.add_parsetree_child!(convert(right), right)
160
+ ptb.build(parent)
161
+ end
162
+
163
+ def_rule %i[erange irange] do |ast, parent, index|
164
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
165
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
166
+ left, right = ast.children
167
+ op_value = ast.loc.operator.source
168
+ ptb.add_parsetree_child!(convert(left), left) unless left.nil?
169
+ ptb.add_child!(ctt[op_value], nil, true, [op_value])
170
+ ptb.add_parsetree_child!(convert(right), right) unless right.nil?
171
+ ptb.build(parent)
172
+ end
173
+
174
+ def_rule :const do |ast, parent, index|
175
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
176
+ scope, sym = ast.children
177
+ maybe_colon = ast.loc.double_colon
178
+ ptb = Resyma::Core::ParseTreeBuilder.new(:const, index, false, [], ast)
179
+ unless scope.nil? || scope.type == :cbase
180
+ ptb.add_parsetree_child!(convert(scope), scope)
181
+ end
182
+ unless maybe_colon.nil?
183
+ op_value = maybe_colon.source
184
+ ptb.add_child!(ctt[op_value], nil, true, [op_value])
185
+ end
186
+ ptb.add_child!(:id, nil, true, [sym.to_s])
187
+ ptb.build(parent)
188
+ end
189
+
190
+ def try_token!(ptb, optional_range)
191
+ ctt = Resyma::Core::CONST_TOKEN_TABLE
192
+ return if optional_range.nil?
193
+
194
+ token_value = if optional_range.is_a?(String)
195
+ optional_range
196
+ else
197
+ optional_range.source
198
+ end
199
+ ptb.add_child!(ctt[token_value], nil, true, [token_value])
200
+ end
201
+
202
+ def_rule :defined? do |ast, parent, index|
203
+ ptb = Resyma::Core::ParseTreeBuilder.new(:defined?, index, false, [], ast)
204
+ try_token! ptb, ast.loc.source.keyword
205
+ try_token! ptb, ast.loc.source.begin
206
+ expr, = ast.children
207
+ ptb.add_parsetree_child!(convert(expr), expr)
208
+ try_token! ptb, ast.loc.source.end
209
+ ptb.build(parent)
210
+ end
211
+
212
+ asgn_table = {
213
+ lvasgn: :id,
214
+ ivasgn: :ivar,
215
+ cvasgn: :cvar,
216
+ gvasgn: :gvar
217
+ }
218
+
219
+ def_rule %i[lvasgn ivasgn cvasgn gvasgn] do |ast, parent, index|
220
+ name = ast.loc.name.source
221
+ value = ast.children[1]
222
+ ptb = Resyma::Core::ParseTreeBuilder.new(ast.type, index, false, [], ast)
223
+ name_tkn = make_token(asgn_table[ast.type], name, nil, 0, nil)
224
+ ptb.add_parsetree_child!(name_tkn)
225
+ try_token!(ptb, ast.loc.operator)
226
+ ptb.add_parsetree_child!(convert(value), value)
227
+ ptb.build(parent)
228
+ end
229
+
230
+ #
231
+ # @yieldparam [Resyma::Core::ParseTreeBuilder]
232
+ #
233
+ def with_ptb(ast, parent, index, type = ast.type)
234
+ ptb = Resyma::Core::ParseTreeBuilder.new(type, index, false, [], ast)
235
+ yield ptb
236
+ ptb.build(parent)
237
+ end
238
+
239
+ def add_ast!(ptb, ast)
240
+ ptb.add_parsetree_child!(convert(ast), ast) unless ast.nil?
241
+ end
242
+
243
+ def_rule :casgn do |ast, parent, index|
244
+ with_ptb ast, parent, index do |ptb|
245
+ base, sym, value_ast = ast.children
246
+ add_ast! ptb, base unless base.nil? || base.type == :cbase
247
+ try_token! ptb, ast.loc.double_colon
248
+ name = ast.loc.name.source
249
+ name_tkn = make_token(:id, name, nil, 0, nil)
250
+ ptb.add_parsetree_child!(name_tkn)
251
+ try_token! ptb, ast.loc.operator
252
+ add_ast! ptb, value_ast
253
+ end
254
+ end
255
+
256
+ def add_id!(ptb, name)
257
+ tkn = make_token(:id, name.to_s, nil, 0, nil)
258
+ ptb.add_parsetree_child!(tkn)
259
+ end
260
+
261
+ def_rule %i[send csend] do |ast, parent, index|
262
+ with_ptb ast, parent, index do |ptb|
263
+ if ast.loc.respond_to? :operator
264
+ rec, _, rhs = ast.children
265
+ add_ast! ptb, rec
266
+ try_token! ptb, ast.loc.dot
267
+ selector = ast.loc.selector.source
268
+ add_id! ptb, selector
269
+ try_token! ptb, ast.loc.operator
270
+ add_ast! ptb, rhs
271
+ end
272
+ end
273
+ end
274
+
275
+ def_rule :block do |ast, parent, index|
276
+ with_ptb ast, parent, index do |ptb|
277
+ send, args, block = ast.children
278
+ add_ast! ptb, send
279
+ try_token! ptb, ast.loc.begin
280
+ add_ast! ptb, args
281
+ add_ast! ptb, block
282
+ try_token! ptb, ast.loc.end
283
+ end
284
+ end
285
+
286
+ def_rule :args do |ast, parent, index|
287
+ with_ptb ast, parent, index do |ptb|
288
+ try_token! ptb, ast.loc.begin
289
+ unless ast.children.empty?
290
+ add_ast! ptb, ast.children.first
291
+ ast.children[1..].each do |arg|
292
+ try_token! ptb, ","
293
+ add_ast! ptb, arg
294
+ end
295
+ end
296
+ try_token! ptb, ast.loc.end
297
+ end
298
+ end
299
+
300
+ def_rule :arg do |ast, parent, index|
301
+ with_ptb ast, parent, index do |ptb|
302
+ add_id! ptb, ast.loc.name.source
303
+ end
304
+ end
305
+
306
+ def_rule :optarg do |ast, parent, index|
307
+ with_ptb ast, parent, index do |ptb|
308
+ add_id! ptb, ast.loc.name.source
309
+ try_token! ptb, ast.loc.operator
310
+ add_ast! ptb, ast.children[1]
311
+ end
312
+ end
313
+
314
+ def def_rule_with_ptb(types)
315
+ def_rule types do |ast, parent, index|
316
+ with_ptb ast, parent, index do |ptb|
317
+ yield ptb, ast, parent, index
318
+ end
319
+ end
320
+ end
321
+
322
+ def_rule_with_ptb :restarg do |ptb, ast|
323
+ try_token! ptb, "*"
324
+ add_id! ptb, ast.loc.name.source unless ast.loc.name.nil?
325
+ end
326
+
327
+ def_rule_with_ptb :blockarg do |ptb, ast|
328
+ try_token! ptb, "&"
329
+ add_id! ptb, ast.loc.name.source unless ast.loc.name.nil?
330
+ end
331
+ end
@@ -0,0 +1,77 @@
1
+ require "set"
2
+
3
+ module Resyma
4
+ module Core
5
+ class Field
6
+ #
7
+ # Create an instance of Field, which is set and used by the matching
8
+ # algorithm
9
+ #
10
+ # @param [Integer] id ID of the node
11
+ # @param [Hash<Integer, Set<Resyma::Core::Tuple2>>] start Sets of 2
12
+ # tuples, corresponding to different automata
13
+ # @param [Hash<Integer, Set<Resyma::Core::Tuple4>>] trans Sets of 4
14
+ # tuples, corresponding to different automata
15
+ #
16
+ def initialize(id, start, trans)
17
+ @id = id
18
+ @start = start
19
+ @trans = trans
20
+ end
21
+
22
+ attr_accessor :id, :start, :trans
23
+
24
+ def self.clean_field
25
+ start = Hash.new { |hash, key| hash[key] = Set[] }
26
+ trans = Hash.new { |hash, key| hash[key] = Set[] }
27
+ new(-1, start, trans)
28
+ end
29
+ end
30
+
31
+ #
32
+ # Parse tree with fields used by the matching algorithm
33
+ #
34
+ class ParseTree
35
+ attr_accessor :symbol, :children, :parent, :index, :field, :ast, :cache
36
+
37
+ #
38
+ # Create an instance of parse tree
39
+ #
40
+ # @param [Symbol] symbol Symbol associating to the node
41
+ # @param [Array] children Subtrees of current node, or an array with a
42
+ # single element if it is a leaf
43
+ # @param [Resyma::Core::ParseTree, nil] parent Parent tree, or nil if the
44
+ # current node is the root
45
+ # @param [Integer] index There are `index` brother preceding to the
46
+ # current node
47
+ # @param [true,false] is_leaf Whether or not the current node is a leaf
48
+ # @param [Parser::AST::Node,nil] ast Its corresponding abstract syntax
49
+ # tree
50
+ #
51
+ def initialize(symbol, children, parent, index, is_leaf, ast = nil)
52
+ @symbol = symbol
53
+ @children = children
54
+ @parent = parent
55
+ @index = index
56
+ @field = Field.clean_field
57
+ @is_leaf = is_leaf
58
+ @ast = ast
59
+ @cache = {}
60
+ end
61
+
62
+ def clear!
63
+ @field = Field.clean_field
64
+ @cache = {}
65
+ @children.each(&:clear!) unless leaf?
66
+ end
67
+
68
+ def root?
69
+ @parent.nil?
70
+ end
71
+
72
+ def leaf?
73
+ @is_leaf
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,73 @@
1
+ require "set"
2
+ require "parser/current"
3
+
4
+ module Resyma
5
+ module Core
6
+ SourceLocator = Struct.new("SourceDetector", :matcher, :locate)
7
+
8
+ SOURCE_LOCATORS = []
9
+
10
+ def self.def_source_locator(regexp, &block)
11
+ SOURCE_LOCATORS.unshift SourceLocator.new(regexp, block)
12
+ end
13
+
14
+ #
15
+ # Locate the AST of a callable object
16
+ #
17
+ # @param [#source_location] procedure A callable object, particular a
18
+ # instance of Proc or Method
19
+ #
20
+ # @return [nil, Parser::AST::Node] AST of the procedure, or nil if cannot
21
+ # locate its source
22
+ #
23
+ def self.locate(procedure)
24
+ if procedure.respond_to? :source_location
25
+ filename, lino = procedure.source_location
26
+ SOURCE_LOCATORS.each do |locator|
27
+ if locator.matcher.match?(filename)
28
+ return locator.locate.call(procedure, filename, lino)
29
+ end
30
+ end
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ CALLABLE_TYPES = Set[:def, :defs, :block]
37
+
38
+ def self.line_number_of_callable(ast)
39
+ case ast.type
40
+ when Set[:def, :defs] then ast.loc.keyword.line
41
+ when :block then ast.loc.begin.line
42
+ end
43
+ end
44
+
45
+ def self.locate_possible_procedures(ast, lino)
46
+ return [] unless ast.is_a?(Parser::AST::Node)
47
+
48
+ procs = []
49
+ if CALLABLE_TYPES.include?(ast.type) &&
50
+ line_number_of_callable(ast) == lino
51
+ procs.push ast
52
+ end
53
+ ast.children.each do |sub|
54
+ procs += locate_possible_procedures(sub, lino)
55
+ end
56
+ procs
57
+ end
58
+
59
+ class MultipleProcedureError < Resyma::Error; end
60
+
61
+ def_source_locator(/^.*$/) do |_, filename, lino|
62
+ tree = Parser::CurrentRuby.parse_file(filename)
63
+ procs = locate_possible_procedures(tree, lino)
64
+ case procs.size
65
+ when 1 then procs[0]
66
+ when 0 then nil
67
+ else raise MultipleProcedureError,
68
+ "Detected multiple procedures in [#{filename}:#{lino}], " +
69
+ "which is unsupported currently"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ require "resyma/core/parsetree/definition"
2
+
3
+ module Resyma
4
+ module Core
5
+ class ParseTree
6
+ #
7
+ # Depth-firstly traverse the tree
8
+ #
9
+ # @yieldparam [Resyma::Core::ParseTree] A parse tree
10
+ #
11
+ # @return [nil] Nothing
12
+ #
13
+ def depth_first_each(&block)
14
+ yield self
15
+
16
+ return if leaf?
17
+
18
+ @children.each do |child|
19
+ child.depth_first_each(&block)
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ require "resyma/core/parsetree/source"
2
+ require "resyma/core/parsetree/definition"
3
+ require "resyma/core/parsetree/traversal"
4
+ require "resyma/core/parsetree/builder"
5
+ require "resyma/core/parsetree/converter"
6
+ require "resyma/core/parsetree/default_converter"
7
+
8
+ __END__
@@ -0,0 +1,30 @@
1
+ require "set"
2
+
3
+ module Resyma
4
+ module Core
5
+ module Utils
6
+ def self.big_union(sets)
7
+ union = Set[]
8
+ sets.each { |set| union.merge(set) }
9
+ union
10
+ end
11
+
12
+ #
13
+ # Whether an automaton accepts the input
14
+ #
15
+ # @param [Resyma::Core::Automaton] automaton An well-formed automaton
16
+ # @param [Array] input_array A list of input tokens
17
+ #
18
+ # @return [true,false] Result
19
+ #
20
+ def self.automaton_accept?(automaton, input_array)
21
+ current_state = automaton.start
22
+ input_array.each do |word|
23
+ current_state = automaton.destination(current_state, word)
24
+ return false if current_state.nil?
25
+ end
26
+ automaton.accept? current_state
27
+ end
28
+ end
29
+ end
30
+ end