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,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