resyma 0.1.1

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 (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,175 @@
1
+ require "resyma/core/automaton/builder"
2
+
3
+ module Resyma
4
+ module Core
5
+ class Regexp
6
+ #
7
+ # Converts self to automaton, which is implemented by subclasses. Note
8
+ # that only add states and transitions by the automaton builder, do not
9
+ # modify starting state and accept set
10
+ #
11
+ # @param [Resyma::Core::AutomatonBuilder] ab Output automaton
12
+ # @param [Resyma::Core::State] start_state Starting state, every
13
+ # implements should work starting from this state
14
+ #
15
+ # @return [Resyma::Core::State] Ending state, every implements should end
16
+ # their automaton with a single acceptable state
17
+ #
18
+ def inject(ab, start_state)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ #
23
+ # Convert the regexp to a DFA
24
+ #
25
+ # @return [Resyma::Core::Automaton] A automaton without `Epsilon`
26
+ #
27
+ def to_automaton(eliminate_epsilon = true)
28
+ ab = AutomatonBuilder.new
29
+ start = ab.new_state!
30
+ ab.start! start
31
+ accept = inject ab, start
32
+ ab.accept! accept
33
+ result = ab.build
34
+ if eliminate_epsilon
35
+ result.to_DFA
36
+ else
37
+ result
38
+ end
39
+ end
40
+ end
41
+
42
+ class RegexpConcat < Regexp
43
+ #
44
+ # Concatentate a list of regexps
45
+ #
46
+ # @param [Array<Regexp>] regexp_list A list of instances of Regexp
47
+ #
48
+ def initialize(regexp_list)
49
+ @regexp_list = regexp_list
50
+ end
51
+
52
+ attr_reader :regexp_list
53
+
54
+ def ==(other)
55
+ other.is_a?(self.class) && other.regexp_list == @regexp_list
56
+ end
57
+
58
+ def inject(ab, start_state)
59
+ current_start = start_state
60
+ @regexp_list.each do |regexp|
61
+ current_start = regexp.inject(ab, current_start)
62
+ end
63
+ current_start
64
+ end
65
+ end
66
+
67
+ class RegexpSelect < Regexp
68
+ #
69
+ # Select one regexp from a list of regexps
70
+ #
71
+ # @param [Array<Regexp>] regexp_list A list of instances of Regexp
72
+ #
73
+ def initialize(regexp_list)
74
+ @regexp_list = regexp_list
75
+ end
76
+
77
+ attr_reader :regexp_list
78
+
79
+ def ==(other)
80
+ other.is_a?(self.class) && other.regexp_list == @regexp_list
81
+ end
82
+
83
+ def inject(ab, start_state)
84
+ accept = ab.new_state!
85
+ @regexp_list.each do |regexp|
86
+ option_start = ab.new_state!
87
+ ab.add_transition!(start_state, Epsilon, option_start)
88
+ option_end = regexp.inject(ab, option_start)
89
+ ab.add_transition!(option_end, Epsilon, accept)
90
+ end
91
+ accept
92
+ end
93
+ end
94
+
95
+ class RegexpRepeat < Regexp
96
+ #
97
+ # Repeat the regexp zero, one, or more times
98
+ #
99
+ # @param [Regexp] regexp A instance of Regexp
100
+ #
101
+ def initialize(regexp)
102
+ @regexp = regexp
103
+ end
104
+
105
+ attr_reader :regexp
106
+
107
+ def ==(other)
108
+ other.is_a?(self.class) && other.regexp == @regexp
109
+ end
110
+
111
+ def inject(ab, start_state)
112
+ accept = @regexp.inject(ab, start_state)
113
+ ab.add_transition!(start_state, Epsilon, accept)
114
+ ab.add_transition!(accept, Epsilon, start_state)
115
+ accept
116
+ end
117
+ end
118
+
119
+ class RegexpSomething < Regexp
120
+ #
121
+ # Matches what the matchable matches
122
+ #
123
+ # @param [Resyma::Core::Matchable] matchable A matchable object
124
+ #
125
+ def initialize(matchable)
126
+ @condition = matchable
127
+ end
128
+
129
+ attr_reader :condition
130
+
131
+ def ==(other)
132
+ other.is_a?(self.class) && other.condition == @condition
133
+ end
134
+
135
+ def inject(ab, start_state)
136
+ accept = ab.new_state!
137
+ ab.add_transition!(start_state, @condition, accept)
138
+ accept
139
+ end
140
+ end
141
+
142
+ class RegexpNothing < Regexp
143
+
144
+ def ==(other)
145
+ other.is_a?(RegexpNothing)
146
+ end
147
+
148
+ def inject(_ab, start_state)
149
+ start_state
150
+ end
151
+ end
152
+
153
+ module RegexpOp
154
+ def rcat(*regexps)
155
+ RegexpConcat.new(regexps)
156
+ end
157
+
158
+ def ror(*regexps)
159
+ RegexpSelect.new(regexps)
160
+ end
161
+
162
+ def rrep(regexp)
163
+ RegexpRepeat.new(regexp)
164
+ end
165
+
166
+ def rchr(matchable)
167
+ RegexpSomething.new(matchable)
168
+ end
169
+
170
+ def reps
171
+ RegexpNothing.new
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,22 @@
1
+ module Resyma
2
+ module Core
3
+ class State
4
+ def initialize(id)
5
+ @id = id
6
+ end
7
+
8
+ attr_reader :id
9
+
10
+ #
11
+ # Returns a new State whose ID is `id`
12
+ #
13
+ # @param [Integer] id ID of the state
14
+ #
15
+ # @return [Resyma::Core::State] A new instance of State
16
+ #
17
+ def self.with_id(id)
18
+ new(id)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,58 @@
1
+ module Resyma
2
+ module Core
3
+ class TransitionTable
4
+
5
+ attr_reader :table
6
+
7
+ Candidate = Struct.new("Candidate", :condition, :destination)
8
+
9
+ def initialize
10
+ @table = Hash.new { |hash, key| hash[key] = [] }
11
+ end
12
+
13
+ #
14
+ # Add a transition from `from_state` to `to_state` through `matchable`
15
+ #
16
+ # @param [Resyma::Core::State] from_state Starting state
17
+ # @param [Resyma::Core::Matchable] matchable Condition of transition
18
+ # @param [Resyma::Core::State] to_state Destination state
19
+ #
20
+ # @return [nil] Undefined
21
+ #
22
+ def add_transition!(from_state, matchable, to_state)
23
+ @table[from_state].push Candidate.new(matchable, to_state)
24
+ nil
25
+ end
26
+
27
+ #
28
+ # Query the destination state in the table. `nil` will be returned if the
29
+ # destination is not defined
30
+ #
31
+ # @param [Resyma::Core::State] from_state Starting state
32
+ # @param [Object] value Value to be matched, see `Resyme::Core::Matchable`
33
+ #
34
+ # @return [nil, Resyma::Core::State] The destination if exists
35
+ #
36
+ def destination(from_state, value)
37
+ @table[from_state].each do |candidate|
38
+ if candidate.condition.match_with_value? value
39
+ return candidate.destination
40
+ end
41
+ end
42
+ nil
43
+ end
44
+
45
+ #
46
+ # Candidate states that has a transition starting from `from_state`
47
+ #
48
+ # @param [Resyma::Core::State] from_state Starting state
49
+ #
50
+ # @return [Array<Resyma::Core::TransitionTable::Candidate>] A list of
51
+ # candidates
52
+ #
53
+ def candidates(from_state)
54
+ @table[from_state]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ module Resyma
2
+ module Core
3
+ def Epsilon.to_s
4
+ "ε"
5
+ end
6
+
7
+ class Automaton
8
+ def to_lwg(port = $>)
9
+ transition_table.table.each do |state, candidates|
10
+ candidates.each do |can|
11
+ port << "#{state.id} - #{can.destination.id}\n"
12
+ port <<
13
+ %(move.#{state.id}.#{can.destination.id} = "#{can.condition}"\n)
14
+ end
15
+ end
16
+ accept_set.each do |state|
17
+ port << "accept.#{state.id}\n"
18
+ end
19
+ port << "start.from.#{start.id}\n"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require "resyma/core/automaton/matchable"
2
+ require "resyma/core/automaton/state"
3
+ require "resyma/core/automaton/transition"
4
+ require "resyma/core/automaton/definition"
5
+ require "resyma/core/automaton/builder"
6
+ require "resyma/core/automaton/epsilon_NFA"
7
+ require "resyma/core/automaton/regexp"
8
+
9
+ __END__
@@ -0,0 +1,89 @@
1
+ require "resyma/core/parsetree/definition"
2
+
3
+ module Resyma
4
+ module Core
5
+ class ParseTree
6
+ def build(parent = nil)
7
+ @parent = parent
8
+ self
9
+ end
10
+ end
11
+
12
+ #
13
+ # Builder of Resyma::Core::ParseTree
14
+ #
15
+ class ParseTreeBuilder
16
+ def initialize(symbol, index = 0, is_leaf = false,
17
+ children = [], ast = nil)
18
+ @symbol = symbol
19
+ @children = children
20
+ @index = index
21
+ @is_leaf = is_leaf
22
+ @ast = ast
23
+ end
24
+
25
+ #
26
+ # Define and add a node to current tree as a child
27
+ #
28
+ # @param [Symbol] symbol Type of the node
29
+ # @param [Parser::AST::Node] ast Abstract syntax tree of the new node
30
+ # @param [true, false] is_leaf Is a leaf node?
31
+ # @param [Array] value Should only be used when `is_leaf`, meaning that
32
+ # this node is a token node. Pass an array with a single value as the
33
+ # value of the token
34
+ #
35
+ # @return [Resyma::Core::ParseTreeBuilder] The builder of the new node
36
+ #
37
+ def add_child!(symbol, ast = nil, is_leaf = false, value = [])
38
+ ptb = ParseTreeBuilder.new(symbol, @children.length, is_leaf, value,
39
+ ast)
40
+ @children.push ptb
41
+ ptb
42
+ end
43
+
44
+ #
45
+ # Add a node to current tree as a child
46
+ #
47
+ # @param [Resyma::Core::ParseTree] tree The new child
48
+ #
49
+ # @return [nil] Nothing
50
+ #
51
+ def add_parsetree_child!(tree, ast = nil)
52
+ tree.index = @children.length
53
+ tree.ast = ast
54
+ @children.push tree
55
+ nil
56
+ end
57
+
58
+ def build(parent = nil)
59
+ pt = ParseTree.new(@symbol, nil, parent, @index, @is_leaf, @ast)
60
+ pt.children = if @is_leaf
61
+ @children
62
+ else
63
+ @children.map { |c| c.build(pt) }
64
+ end
65
+ pt
66
+ end
67
+
68
+ def node(symbol, ast = nil, &block)
69
+ ptb = add_child! symbol, ast
70
+ ptb.instance_eval(&block) unless block.nil?
71
+ ptb
72
+ end
73
+
74
+ def leaf(symbol, value, ast = nil)
75
+ add_child! symbol, ast, true, [value]
76
+ end
77
+
78
+ def self.root(symbol, value = nil, index = 0, ast = nil, &block)
79
+ if block.nil?
80
+ ParseTreeBuilder.new(symbol, index, true, [value], ast)
81
+ else
82
+ ptb = ParseTreeBuilder.new(symbol, index, false, [], ast)
83
+ ptb.instance_eval(&block) unless block.nil?
84
+ ptb
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,61 @@
1
+ require "parser"
2
+
3
+ module Resyma
4
+ module Core
5
+ class ConversionError < Resyma::Error; end
6
+
7
+ #
8
+ # Converter for Parser::AST::Node
9
+ #
10
+ class Converter
11
+ def initialize
12
+ @rules = {}
13
+ @fallback = nil
14
+ end
15
+
16
+ #
17
+ # Define the conversion rule for AST with particular type(s)
18
+ #
19
+ # @param [Symbol, Array<Symbol>] type_or_types Types
20
+ # @param [Proc] &cvt Procedure taking a AST and returning a parse tree,
21
+ # i.e. Parser::AST::Node -> Resyma::Core::ParseTree
22
+ # @yieldparam [Parser::AST::Node]
23
+ # @yieldparam [Resyma::Core::ParseTree]
24
+ # @yieldparam [Integer]
25
+ #
26
+ # @return [nil] Nothing
27
+ #
28
+ def def_rule(type_or_types, &cvt)
29
+ types = if type_or_types.is_a?(Symbol)
30
+ [type_or_types]
31
+ else
32
+ type_or_types
33
+ end
34
+ types.each { |type| @rules[type] = cvt }
35
+ end
36
+
37
+ def def_fallback(&cvt)
38
+ @fallback = cvt
39
+ end
40
+
41
+ #
42
+ # Convert a Parser::AST::Node to Resyma::Core::ParseTree
43
+ #
44
+ # @param [Parser::AST::Node] ast An abstract syntax tree
45
+ #
46
+ # @return [Resyma::Core::ParseTree] A concrete syntax tree
47
+ #
48
+ def convert(ast, parent = nil, index = 0)
49
+ converter = @rules[ast.type]
50
+ if !converter.nil?
51
+ converter.call(ast, parent, index)
52
+ elsif !@fallback.nil?
53
+ @fallback.call(ast, parent, index)
54
+ else
55
+ raise Resyma::Core::ConversionError,
56
+ "Unable to convert AST whose type is #{ast.type}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end