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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +69 -0
- data/LICENSE +674 -0
- data/README.md +167 -0
- data/Rakefile +8 -0
- data/lib/resyma/core/algorithm/engine.rb +189 -0
- data/lib/resyma/core/algorithm/matcher.rb +48 -0
- data/lib/resyma/core/algorithm/tuple.rb +25 -0
- data/lib/resyma/core/algorithm.rb +5 -0
- data/lib/resyma/core/automaton/builder.rb +78 -0
- data/lib/resyma/core/automaton/definition.rb +32 -0
- data/lib/resyma/core/automaton/epsilon_NFA.rb +115 -0
- data/lib/resyma/core/automaton/matchable.rb +16 -0
- data/lib/resyma/core/automaton/regexp.rb +175 -0
- data/lib/resyma/core/automaton/state.rb +22 -0
- data/lib/resyma/core/automaton/transition.rb +58 -0
- data/lib/resyma/core/automaton/visualize.rb +23 -0
- data/lib/resyma/core/automaton.rb +9 -0
- data/lib/resyma/core/parsetree/builder.rb +89 -0
- data/lib/resyma/core/parsetree/converter.rb +61 -0
- data/lib/resyma/core/parsetree/default_converter.rb +331 -0
- data/lib/resyma/core/parsetree/definition.rb +77 -0
- data/lib/resyma/core/parsetree/source.rb +73 -0
- data/lib/resyma/core/parsetree/traversal.rb +26 -0
- data/lib/resyma/core/parsetree.rb +8 -0
- data/lib/resyma/core/utilities.rb +30 -0
- data/lib/resyma/language.rb +290 -0
- data/lib/resyma/nise/date.rb +53 -0
- data/lib/resyma/nise/rubymoji.rb +13 -0
- data/lib/resyma/nise/toml.rb +63 -0
- data/lib/resyma/parsetree.rb +163 -0
- data/lib/resyma/program/automaton.rb +84 -0
- data/lib/resyma/program/parsetree.rb +79 -0
- data/lib/resyma/program/traverse.rb +77 -0
- data/lib/resyma/version.rb +5 -0
- data/lib/resyma.rb +12 -0
- data/resyma.gemspec +47 -0
- data/sig/resyma.rbs +4 -0
- 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
|