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