falafel 0.0.1.1 → 0.0.2.2
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 +4 -4
- data/lib/cfg.rb +11 -27
- data/lib/cfg_helper/chaining_free.rb +69 -0
- data/lib/cfg_helper/chomsky_nf.rb +155 -0
- data/lib/cfg_helper/chomsky_nf_simplifier.rb +117 -0
- data/lib/cfg_helper/cyk.rb +100 -0
- data/lib/cfg_helper/epsilon_free.rb +98 -0
- data/lib/falafel.rb +2 -3
- data/lib/pump.rb +52 -13
- metadata +10 -9
- data/lib/chaining_free.rb +0 -67
- data/lib/chomsky_nf.rb +0 -153
- data/lib/chomsky_nf_simplifier.rb +0 -110
- data/lib/cyk.rb +0 -74
- data/lib/epsilon_free.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 754e7aa62a91e0f16ed875b859d8e208379df55509569dbd5d5a38e93b874fbe
|
4
|
+
data.tar.gz: bcbb64b7d4627514c2e3560b80e15ccc3f0dbf721448b96247188f778d8f897c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fda2266343ea3c9c919898f8ebd0381329189cf6eb08f3e281ba20e30e950d6d1bac77356ff91263c7377e77c808c1b0cb224b705dda1f3970b67b294242d9f
|
7
|
+
data.tar.gz: b9e9c7b8552c38d9a3f09143e68ac4cb78b5527899052a79ec975e1b6a08b45cef97158c3bc785f9f05bb1267943c38b7fc37a8575ea86ed409bd72a4f5317d8
|
data/lib/cfg.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'cyk'
|
4
|
-
require_relative 'chomsky_nf'
|
5
|
-
require_relative 'epsilon_free'
|
6
|
-
require_relative 'chaining_free'
|
3
|
+
require_relative 'cfg_helper/cyk'
|
4
|
+
require_relative 'cfg_helper/chomsky_nf'
|
5
|
+
require_relative 'cfg_helper/epsilon_free'
|
6
|
+
require_relative 'cfg_helper/chaining_free'
|
7
7
|
|
8
8
|
# Samll CFG impletation
|
9
9
|
# Generate words and check if words are the @lang
|
10
10
|
# generates random words
|
11
11
|
class CFG
|
12
12
|
attr_accessor :start_var, :rules
|
13
|
-
attr_reader :rules_ef, :rules_cf, :rnd_words,
|
13
|
+
attr_reader :rules_ef, :rules_cf, :rnd_words,
|
14
14
|
:rules_ef_res, :rules_cf_res, :chomsky_nf_rules,
|
15
15
|
:cyk_matrix, :is_in_l
|
16
16
|
|
@@ -18,7 +18,6 @@ class CFG
|
|
18
18
|
@start_var = start_var
|
19
19
|
@rules = _rules rules
|
20
20
|
@rnd_words = []
|
21
|
-
@reachables = []
|
22
21
|
@vars = vars
|
23
22
|
@alphabet = alphabet
|
24
23
|
end
|
@@ -35,42 +34,27 @@ class CFG
|
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
@reachables.uniq!
|
43
|
-
end
|
44
|
-
|
45
|
-
def var_productive?
|
46
|
-
''
|
47
|
-
end
|
48
|
-
|
49
|
-
def var_reduced?
|
50
|
-
''
|
51
|
-
end
|
52
|
-
|
53
|
-
def epsilon_free
|
54
|
-
e_free = EpsilonFree.new
|
55
|
-
@rules_ef_res = e_free.run @rules
|
37
|
+
def epsilon_free(custom_rule)
|
38
|
+
e_free = CFGHelper::EpsilonFree.new
|
39
|
+
@rules_ef_res = e_free.run custom_rule || @rules
|
56
40
|
@rules_ef = e_free.rebuild_rules @rules, @rules_ef_res
|
57
41
|
end
|
58
42
|
|
59
43
|
def chaining_free
|
60
|
-
c_free = ChainingFree.new
|
44
|
+
c_free = CFGHelper::ChainingFree.new
|
61
45
|
@rules_cf_res = c_free.run @rules, @vars
|
62
46
|
@rules_cf = c_free.rebuild_rules @rules_cf_res
|
63
47
|
end
|
64
48
|
|
65
49
|
def chomsky_nf(custom_rule)
|
66
|
-
chomsky = ChomskyNF.new
|
50
|
+
chomsky = CFGHelper::ChomskyNF.new
|
67
51
|
rules = chomsky.run custom_rule || @rules, @alphabet
|
68
52
|
|
69
53
|
@chomsky_nf_rules = chomsky.simplify rules
|
70
54
|
end
|
71
55
|
|
72
56
|
def cyk_run(word)
|
73
|
-
cyk = CYK.new word, @chomsky_nf_rules
|
57
|
+
cyk = CFGHelper::CYK.new word, @chomsky_nf_rules
|
74
58
|
|
75
59
|
cyk.run
|
76
60
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFGHelper
|
4
|
+
# remove chaining rules from CFG
|
5
|
+
class ChainingFree
|
6
|
+
def run(rules, vars)
|
7
|
+
@rules = rules
|
8
|
+
@vars = vars
|
9
|
+
c_r = _chaining_relation
|
10
|
+
|
11
|
+
# build transivity relation from _chaining_relation(K) as A
|
12
|
+
# remove _chaining_relation from rules
|
13
|
+
_rebuild_rules + c_r.map { |r| _transitivity_relation r }.reduce(:concat) - c_r
|
14
|
+
end
|
15
|
+
|
16
|
+
def rebuild_rules(current_rules)
|
17
|
+
res = {}
|
18
|
+
|
19
|
+
@vars.each { |v| res[v] = [] }
|
20
|
+
|
21
|
+
current_rules.each do |l, r|
|
22
|
+
res[l] << r.chars unless res[l].include? r.chars
|
23
|
+
end
|
24
|
+
|
25
|
+
res
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def _chaining_relation
|
31
|
+
rules = _rebuild_rules
|
32
|
+
|
33
|
+
rules & @vars.product(@vars)
|
34
|
+
end
|
35
|
+
|
36
|
+
def _rebuild_rules
|
37
|
+
result = []
|
38
|
+
@rules.each do |var, rule|
|
39
|
+
rule.map do |r|
|
40
|
+
result << [var, r.join]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def _transitivity_relation(pair)
|
48
|
+
transitive_relation = [pair]
|
49
|
+
|
50
|
+
new_pairs = []
|
51
|
+
|
52
|
+
_rebuild_rules.each do |a, b|
|
53
|
+
next unless pair[1] == a && !transitive_relation.include?([pair[0], b])
|
54
|
+
|
55
|
+
new_pairs << [pair[0], b]
|
56
|
+
end
|
57
|
+
|
58
|
+
transitive_relation.concat new_pairs
|
59
|
+
end
|
60
|
+
|
61
|
+
def _new_pairs(rules)
|
62
|
+
rules.each do |a, b|
|
63
|
+
next unless pair[1] == a && !transitive_relation.include?([pair[0], b])
|
64
|
+
|
65
|
+
new_pairs << [pair[0], b]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'chomsky_nf_simplifier'
|
4
|
+
|
5
|
+
module CFGHelper
|
6
|
+
# convert to Chomsky Normal Form
|
7
|
+
class ChomskyNF
|
8
|
+
def run(rules, alphabet)
|
9
|
+
new_rules = {}
|
10
|
+
@rules = rules
|
11
|
+
@alphabet = alphabet
|
12
|
+
|
13
|
+
# add cases like (A, a), (B, b)
|
14
|
+
_add_single_vars new_rules
|
15
|
+
|
16
|
+
# change rules like (X, aX) to (X, AX)
|
17
|
+
_handle_simple_rule new_rules
|
18
|
+
|
19
|
+
_handle_all_rule new_rules
|
20
|
+
end
|
21
|
+
|
22
|
+
def simplify(rules)
|
23
|
+
simplifier = CFGHelper::ChomskyNFSimplifier.new
|
24
|
+
|
25
|
+
simplifier.run rules, @alphabet
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def _add_single_vars(new_rules)
|
31
|
+
@rules.values.reduce(:concat).each do |rule|
|
32
|
+
rule.select { |var| var == var.downcase }.each do |r|
|
33
|
+
new_rules[r.upcase] = r
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def _handle_simple_rule(new_rules)
|
39
|
+
@rules.each do |var, rule|
|
40
|
+
new_rules[var] = []
|
41
|
+
rule.each do |r|
|
42
|
+
next if r.empty?
|
43
|
+
|
44
|
+
new_rules[var] << r.map(&:upcase)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def _handle_all_rule(new_rules)
|
50
|
+
new_rules_buffer = []
|
51
|
+
|
52
|
+
new_rules.each do |var, rules|
|
53
|
+
next if _is_character? rules
|
54
|
+
|
55
|
+
new_rules_buffer << _new_rules_buffer(rules, var)
|
56
|
+
end
|
57
|
+
|
58
|
+
new_rules.merge! new_rules_buffer.reduce(&:merge)
|
59
|
+
end
|
60
|
+
|
61
|
+
def _is_character?(value)
|
62
|
+
value.is_a?(String) && value.length == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def _new_rules_buffer(rules, var)
|
66
|
+
helper_vars = _helper_vars rules
|
67
|
+
helper_vars.merge! @alphabet.each_with_object({}) { |ele, meme| meme[ele.capitalize] = ele.capitalize }
|
68
|
+
|
69
|
+
_new_rules rules, var, helper_vars
|
70
|
+
end
|
71
|
+
|
72
|
+
def _check_simplify(rule, holder, current_var)
|
73
|
+
return false unless rule.size == 1
|
74
|
+
|
75
|
+
holder[current_var] << rule.last unless holder[current_var].include? rule
|
76
|
+
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def _new_rules(rules, var, helper_vars)
|
81
|
+
holder = {}
|
82
|
+
|
83
|
+
rules.each do |rule|
|
84
|
+
rest = ''
|
85
|
+
rule.each_with_index do |head, index|
|
86
|
+
current_var = index.zero? ? var : rest
|
87
|
+
holder[current_var] ||= []
|
88
|
+
|
89
|
+
is_simple = _check_simplify rule, holder, current_var
|
90
|
+
|
91
|
+
break if is_simple
|
92
|
+
|
93
|
+
rest = _chomsky_nf_vars index, rule, helper_vars
|
94
|
+
|
95
|
+
nf_rest = "#{head}#{rest}"
|
96
|
+
|
97
|
+
if index + 2 == rule.size
|
98
|
+
if rest.nil?
|
99
|
+
rest = rule.last
|
100
|
+
nf_rest += rest
|
101
|
+
end
|
102
|
+
|
103
|
+
holder[current_var] << nf_rest unless holder[current_var].include? nf_rest
|
104
|
+
|
105
|
+
break
|
106
|
+
else
|
107
|
+
holder[current_var] << nf_rest unless holder[current_var].include? nf_rest
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
holder
|
113
|
+
end
|
114
|
+
|
115
|
+
def _chomsky_nf_vars(index, rule, helper_vars)
|
116
|
+
rule_size = rule.size
|
117
|
+
rest = rule[(index + 1)..rule_size].join
|
118
|
+
|
119
|
+
helper_vars.key(rest)
|
120
|
+
end
|
121
|
+
|
122
|
+
def _helper_vars(rules)
|
123
|
+
helper_vars = {}
|
124
|
+
|
125
|
+
rules.each do |rule|
|
126
|
+
rule.each_with_index do |_, index|
|
127
|
+
letter, rest = _chomsky_nf_helper_vars index, rule, helper_vars.keys
|
128
|
+
|
129
|
+
break if index + 2 == rule.size
|
130
|
+
|
131
|
+
next if helper_vars.values.include? rest
|
132
|
+
|
133
|
+
helper_vars[letter] = rest unless helper_vars.key?(letter) && rest.size > 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
helper_vars
|
138
|
+
end
|
139
|
+
|
140
|
+
def _chomsky_nf_helper_vars(index, rule, helper_vars)
|
141
|
+
rule_size = rule.size
|
142
|
+
letter = helper_vars.empty? ? _find_letter(index) : helper_vars.sort!.last.succ
|
143
|
+
rest = rule[(index + 1)..rule_size].join
|
144
|
+
|
145
|
+
[letter, rest]
|
146
|
+
end
|
147
|
+
|
148
|
+
def _find_letter(n)
|
149
|
+
result = 'H'
|
150
|
+
n.times { result = result.succ }
|
151
|
+
|
152
|
+
result
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFGHelper
|
4
|
+
# simplify chomsky normal form
|
5
|
+
class ChomskyNFSimplifier
|
6
|
+
def run(new_rules, alphabet)
|
7
|
+
vars_with_one_letter = _vars_with_one_letter new_rules, alphabet
|
8
|
+
|
9
|
+
return new_rules if vars_with_one_letter.empty?
|
10
|
+
|
11
|
+
extendible_rules = _find_extendible_rules new_rules, vars_with_one_letter.keys
|
12
|
+
|
13
|
+
return new_rules if extendible_rules.empty?
|
14
|
+
|
15
|
+
missing_rules = _missing_rules extendible_rules, vars_with_one_letter
|
16
|
+
|
17
|
+
_add_missing_rules missing_rules, new_rules
|
18
|
+
|
19
|
+
new_rules
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def _vars_with_one_letter(new_rules, alphabet)
|
25
|
+
vars = {}
|
26
|
+
|
27
|
+
new_rules.each do |key, values|
|
28
|
+
# skip simple rules like (A, a), (B, b),...
|
29
|
+
next if values.size == 1
|
30
|
+
|
31
|
+
letters = values.select { |val| _is_letter val, alphabet }.uniq
|
32
|
+
vars[key] = letters
|
33
|
+
end
|
34
|
+
|
35
|
+
vars
|
36
|
+
end
|
37
|
+
|
38
|
+
def _is_letter(val, alphabet)
|
39
|
+
val.size == 1 && alphabet.include?(val.downcase)
|
40
|
+
end
|
41
|
+
|
42
|
+
def _find_extendible_rules(new_rules, rules_keys)
|
43
|
+
extendible = {}
|
44
|
+
|
45
|
+
filterd_rules = new_rules.reject { |_, rules| rules.is_a?(String) }
|
46
|
+
|
47
|
+
filterd_rules.each do |letter, rules|
|
48
|
+
rules_keys.each do |rules_key|
|
49
|
+
rules.each do |rule|
|
50
|
+
next unless rule.include?(rules_key)
|
51
|
+
|
52
|
+
extendible[letter] ||= []
|
53
|
+
extendible[letter] << rule
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
extendible
|
59
|
+
end
|
60
|
+
|
61
|
+
def _missing_rules(extendible_rules, vars)
|
62
|
+
res = {}
|
63
|
+
|
64
|
+
extendible_rules.each do |var_rule, nfs|
|
65
|
+
nfs.each do |nf|
|
66
|
+
vars.each do |match_char, replacement_chars|
|
67
|
+
replacement_chars.each do |rc|
|
68
|
+
res[var_rule] ||= []
|
69
|
+
res[var_rule] << _combinations(nf, rc, match_char)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
res[var_rule] = res[var_rule]&.flatten&.uniq
|
74
|
+
|
75
|
+
_check_special_case nf, res, var_rule, vars
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
res
|
80
|
+
end
|
81
|
+
|
82
|
+
def _combinations(original_string, replacement_char, match_char)
|
83
|
+
combinations = []
|
84
|
+
|
85
|
+
0.upto(1) do |i|
|
86
|
+
0.upto(1) do |j|
|
87
|
+
new_rule = original_string.dup
|
88
|
+
new_rule[0] = replacement_char if i == 1 && original_string[0] == match_char
|
89
|
+
new_rule[1] = replacement_char if j == 1 && original_string[1] == match_char
|
90
|
+
combinations << new_rule
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
combinations.uniq
|
95
|
+
end
|
96
|
+
|
97
|
+
def _add_missing_rules(missing_rules, new_rules)
|
98
|
+
missing_rules.each do |key, val|
|
99
|
+
next if val.nil?
|
100
|
+
|
101
|
+
new_rules[key].concat(val).uniq!
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def _check_special_case(nf, res, var_rule, vars)
|
106
|
+
vars.each do |match_char, combi|
|
107
|
+
next unless nf == match_char * 2
|
108
|
+
|
109
|
+
missing_rules = combi.map { |lft| combi.map { |rgt| "#{lft}#{rgt}" } }.flatten
|
110
|
+
|
111
|
+
res[var_rule].concat(missing_rules)
|
112
|
+
end
|
113
|
+
|
114
|
+
res
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFGHelper
|
4
|
+
# Implemention of cyk algo.
|
5
|
+
class CYK
|
6
|
+
attr_reader :matrix, :is_in_l
|
7
|
+
|
8
|
+
def initialize(word, custom_rule)
|
9
|
+
@word = word
|
10
|
+
@rules = custom_rule.select { |_, val| val.is_a?(Array) }
|
11
|
+
@helper_vars = @rules.values.flatten
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
_cyk_fill_diagonal
|
16
|
+
_cyk_fill_matrix
|
17
|
+
@is_in_l = _is_in_l?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def _cyk_fill_diagonal
|
23
|
+
wrd_lng = @word.length
|
24
|
+
@matrix = wrd_lng.times.map { wrd_lng.times.map { [] } }
|
25
|
+
|
26
|
+
(0..wrd_lng - 1).each do |index|
|
27
|
+
@matrix[index][index] = @word[index].upcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def _cyk_fill_matrix
|
32
|
+
@matrix.size.times do |limiter|
|
33
|
+
(0...@matrix.length - limiter).each do |i|
|
34
|
+
j = i + limiter
|
35
|
+
|
36
|
+
next if i == j
|
37
|
+
|
38
|
+
helper_var = _helper_var i, j
|
39
|
+
matrix_ele_info = _matrix_ele_info i, j
|
40
|
+
@matrix[i][j] = _add_to_matrix helper_var, matrix_ele_info
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def _helper_var(row, col)
|
46
|
+
res = '0'
|
47
|
+
|
48
|
+
(row..col - 1).step(1) do |h|
|
49
|
+
p_ = @matrix[row][h]
|
50
|
+
q_ = @matrix[h + 1][col]
|
51
|
+
res = "#{p_}#{q_}"
|
52
|
+
|
53
|
+
break if @helper_vars.include? res
|
54
|
+
end
|
55
|
+
|
56
|
+
res
|
57
|
+
end
|
58
|
+
|
59
|
+
def _matrix_ele_info(i, j)
|
60
|
+
res = {}
|
61
|
+
|
62
|
+
res['i'] = i
|
63
|
+
res['j'] = j
|
64
|
+
res['row_c'] = @matrix.count
|
65
|
+
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def _add_to_matrix(helper_var, matrix_ele_info)
|
70
|
+
matrix_ele = '0'
|
71
|
+
return if helper_var == matrix_ele
|
72
|
+
|
73
|
+
keys = []
|
74
|
+
|
75
|
+
@rules.each do |key, vals|
|
76
|
+
vals.each do |val|
|
77
|
+
keys << key if val == helper_var
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
_matrix_ele keys, matrix_ele_info
|
82
|
+
end
|
83
|
+
|
84
|
+
def _matrix_ele(keys, matrix_ele_info)
|
85
|
+
return 'S' if keys.include?('S') && _is_last_element?(matrix_ele_info)
|
86
|
+
|
87
|
+
return keys.last if keys.size == 1
|
88
|
+
|
89
|
+
keys.reject { |key| key == 'S' }.last || '0'
|
90
|
+
end
|
91
|
+
|
92
|
+
def _is_last_element?(matrix_ele_info)
|
93
|
+
matrix_ele_info['i'].zero? && matrix_ele_info['j'] == matrix_ele_info['row_c'] - 1
|
94
|
+
end
|
95
|
+
|
96
|
+
def _is_in_l?
|
97
|
+
@matrix.first.last == 'S'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFGHelper
|
4
|
+
# used to clear a CFG from the epsilon rules
|
5
|
+
class EpsilonFree
|
6
|
+
def run(rules)
|
7
|
+
epsilon = _epsilon rules
|
8
|
+
rules_ef = {}
|
9
|
+
|
10
|
+
return if epsilon.empty?
|
11
|
+
|
12
|
+
epsilon.each do |e_class|
|
13
|
+
cleared_rules = _cleared_rules rules[e_class]
|
14
|
+
epsilon_free_word = cleared_rules.map(&:join)
|
15
|
+
grammer = _grammer_epsilon_free epsilon_free_word, e_class
|
16
|
+
rules_ef[e_class] = grammer
|
17
|
+
end
|
18
|
+
|
19
|
+
rules_ef
|
20
|
+
end
|
21
|
+
|
22
|
+
def rebuild_rules(current_rules, rules_epsilon_free)
|
23
|
+
missing_vars = current_rules.keys - rules_epsilon_free.keys
|
24
|
+
|
25
|
+
res = {}
|
26
|
+
rules_epsilon_free.each do |key, rules|
|
27
|
+
res[key] = []
|
28
|
+
rules.each do |rule|
|
29
|
+
res[key] << rule.chars
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
missing_vars.each { |v| res[v] = current_rules[v] }
|
34
|
+
|
35
|
+
res
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def _epsilon(rules)
|
41
|
+
res = rules.map do |var, rule|
|
42
|
+
next unless rule.include? []
|
43
|
+
|
44
|
+
var
|
45
|
+
end
|
46
|
+
res.reject(&:nil?)
|
47
|
+
end
|
48
|
+
|
49
|
+
def _cleared_rules(rules)
|
50
|
+
rules.reject(&:empty?)
|
51
|
+
end
|
52
|
+
|
53
|
+
def _grammer_epsilon_free(epsilon_free_word, e_class)
|
54
|
+
rules_epsilon_location = _new_rules_epsilon_location epsilon_free_word, e_class
|
55
|
+
|
56
|
+
res = []
|
57
|
+
rules_epsilon_location.map do |rule, possibilities|
|
58
|
+
possibilities.each do |possibility|
|
59
|
+
res << _remove_e_class(possibility, rule.split(''))
|
60
|
+
end
|
61
|
+
|
62
|
+
res << epsilon_free_word.delete(e_class) if possibilities.size == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
res.uniq.reject(&:nil?)
|
66
|
+
end
|
67
|
+
|
68
|
+
def _new_rules_epsilon_location(s_rule, e_class)
|
69
|
+
res = {}
|
70
|
+
s_rule.each do |rule|
|
71
|
+
s_index = []
|
72
|
+
rule.each_char.with_index { |char, index| s_index << index if char == e_class }
|
73
|
+
res[rule] = _power_set(s_index)
|
74
|
+
end
|
75
|
+
|
76
|
+
res
|
77
|
+
end
|
78
|
+
|
79
|
+
def _power_set(set)
|
80
|
+
if set.empty?
|
81
|
+
[[]]
|
82
|
+
else
|
83
|
+
element = set[0]
|
84
|
+
subsets = _power_set set[1..]
|
85
|
+
subsets + subsets.map { |subset| [element] + subset }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def _remove_e_class(possibility, cleared_rules)
|
90
|
+
return cleared_rules.join '' if possibility.empty?
|
91
|
+
|
92
|
+
new_rule = cleared_rules.join ''
|
93
|
+
possibility.reverse.each { |index| new_rule.slice! index }
|
94
|
+
|
95
|
+
new_rule
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/falafel.rb
CHANGED
@@ -104,10 +104,10 @@ class Falafel
|
|
104
104
|
dfa.to_min
|
105
105
|
end
|
106
106
|
|
107
|
-
def pump_lemma
|
107
|
+
def pump_lemma(lang, length)
|
108
108
|
require_relative 'pump'
|
109
109
|
|
110
|
-
Pump.new lang:
|
110
|
+
Pump.new lang: lang, length: length
|
111
111
|
end
|
112
112
|
|
113
113
|
def cfg(alphabet, vars_set, start_var, rules)
|
@@ -118,7 +118,6 @@ class Falafel
|
|
118
118
|
|
119
119
|
private
|
120
120
|
|
121
|
-
# alle bilder eine menge m unter r
|
122
121
|
def _image(m, r)
|
123
122
|
m.flat_map { |x| r.map { |y, z| z if x == y }.compact }.sort.uniq
|
124
123
|
end
|
data/lib/pump.rb
CHANGED
@@ -2,29 +2,68 @@
|
|
2
2
|
|
3
3
|
# Pump Lemma
|
4
4
|
# It takes language as lambda function and a word as a string
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# lang is a regular expression like a^*b^* given as lambda function and set by user
|
6
|
+
# word is the word to check pumping
|
7
|
+
# length is the length of the string you want to pump
|
8
|
+
# return is_regular to tell if lang is regular and decomposition to show why
|
7
9
|
class Pump
|
8
10
|
attr_accessor :lang, :word
|
11
|
+
attr_reader :is_regular, :decomposition
|
9
12
|
|
10
|
-
def initialize(lang:,
|
11
|
-
@lang
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@k = k
|
13
|
+
def initialize(lang:, length:)
|
14
|
+
@lang = lang
|
15
|
+
@length = length
|
16
|
+
@word = ''
|
15
17
|
end
|
16
18
|
|
17
|
-
def run
|
19
|
+
def run(show_pros:)
|
20
|
+
_clear_old_run
|
21
|
+
|
18
22
|
r, s, t = ''
|
19
23
|
|
20
|
-
|
21
|
-
r =
|
22
|
-
|
23
|
-
|
24
|
+
@word.length.times do |leng|
|
25
|
+
r, s, t = _sigma_chars leng
|
26
|
+
|
27
|
+
pump_up_down_res = _pump_up_down_res r, s, t, show_pros
|
28
|
+
not_in_language = pump_up_down_res.include? false
|
24
29
|
|
25
|
-
|
30
|
+
@is_regular = false if not_in_language
|
31
|
+
|
32
|
+
break if not_in_language
|
26
33
|
end
|
27
34
|
|
35
|
+
@decomposition = [r, s, t]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def _clear_old_run
|
41
|
+
@decomposition = []
|
42
|
+
@is_regular = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# ∃r, s, t ∈ Σ^∗
|
46
|
+
def _sigma_chars(leng)
|
47
|
+
r = @word[0...leng] || ''
|
48
|
+
s = @word[leng...leng + @length] || ''
|
49
|
+
t = @word[leng + @length..] || ''
|
50
|
+
|
28
51
|
[r, s, t]
|
29
52
|
end
|
53
|
+
|
54
|
+
# ∀k ≥ 0 : r·s^k·t ∈ L
|
55
|
+
def _pump_up_down_res(r, s, t, show_pros)
|
56
|
+
pump_up_down_res = []
|
57
|
+
|
58
|
+
20.times do |k|
|
59
|
+
puts "r: #{r}, s: #{s}, t: #{t}, k: #{k}, r + s * k + t: #{r + s * k + t}" if show_pros
|
60
|
+
pump_up_down_res << _pump_condtion_satisfied?(r, s, t, k)
|
61
|
+
end
|
62
|
+
|
63
|
+
pump_up_down_res
|
64
|
+
end
|
65
|
+
|
66
|
+
def _pump_condtion_satisfied?(r, s, t, k)
|
67
|
+
(s.length.positive? && @lang.call(r + s * k + t))
|
68
|
+
end
|
30
69
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: falafel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abed Alkedda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -17,18 +17,18 @@ extensions: []
|
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
19
|
- lib/cfg.rb
|
20
|
-
- lib/chaining_free.rb
|
21
|
-
- lib/chomsky_nf.rb
|
22
|
-
- lib/chomsky_nf_simplifier.rb
|
23
|
-
- lib/cyk.rb
|
20
|
+
- lib/cfg_helper/chaining_free.rb
|
21
|
+
- lib/cfg_helper/chomsky_nf.rb
|
22
|
+
- lib/cfg_helper/chomsky_nf_simplifier.rb
|
23
|
+
- lib/cfg_helper/cyk.rb
|
24
|
+
- lib/cfg_helper/epsilon_free.rb
|
24
25
|
- lib/dfa.rb
|
25
|
-
- lib/epsilon_free.rb
|
26
26
|
- lib/falafel.rb
|
27
27
|
- lib/pump.rb
|
28
28
|
- lib/rx.rb
|
29
29
|
homepage: https://github.com/AbedAlkedda/AFS
|
30
30
|
licenses:
|
31
|
-
-
|
31
|
+
- ''
|
32
32
|
metadata: {}
|
33
33
|
post_install_message:
|
34
34
|
rdoc_options: []
|
@@ -48,5 +48,6 @@ requirements: []
|
|
48
48
|
rubygems_version: 3.3.26
|
49
49
|
signing_key:
|
50
50
|
specification_version: 4
|
51
|
-
summary: Falafel is a gem
|
51
|
+
summary: Falafel is a gem that will hopefully help you better understand automata
|
52
|
+
and formal languages
|
52
53
|
test_files: []
|
data/lib/chaining_free.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# remove chaining rules from CFG
|
4
|
-
class ChainingFree
|
5
|
-
def run(rules, vars)
|
6
|
-
@rules = rules
|
7
|
-
@vars = vars
|
8
|
-
c_r = _chaining_relation
|
9
|
-
|
10
|
-
# build transivity relation from _chaining_relation(K) as A
|
11
|
-
# remove _chaining_relation from rules
|
12
|
-
_rebuild_rules + c_r.map { |r| _transitivity_relation r }.reduce(:concat) - c_r
|
13
|
-
end
|
14
|
-
|
15
|
-
def rebuild_rules(current_rules)
|
16
|
-
res = {}
|
17
|
-
|
18
|
-
@vars.each { |v| res[v] = [] }
|
19
|
-
|
20
|
-
current_rules.each do |l, r|
|
21
|
-
res[l] << r.chars unless res[l].include? r.chars
|
22
|
-
end
|
23
|
-
|
24
|
-
res
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def _chaining_relation
|
30
|
-
rules = _rebuild_rules
|
31
|
-
|
32
|
-
rules & @vars.product(@vars)
|
33
|
-
end
|
34
|
-
|
35
|
-
def _rebuild_rules
|
36
|
-
result = []
|
37
|
-
@rules.each do |var, rule|
|
38
|
-
rule.map do |r|
|
39
|
-
result << [var, r.join]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
result
|
44
|
-
end
|
45
|
-
|
46
|
-
def _transitivity_relation(pair)
|
47
|
-
transitive_relation = [pair]
|
48
|
-
|
49
|
-
new_pairs = []
|
50
|
-
|
51
|
-
_rebuild_rules.each do |a, b|
|
52
|
-
next unless pair[1] == a && !transitive_relation.include?([pair[0], b])
|
53
|
-
|
54
|
-
new_pairs << [pair[0], b]
|
55
|
-
end
|
56
|
-
|
57
|
-
transitive_relation.concat new_pairs
|
58
|
-
end
|
59
|
-
|
60
|
-
def _new_pairs(rules)
|
61
|
-
rules.each do |a, b|
|
62
|
-
next unless pair[1] == a && !transitive_relation.include?([pair[0], b])
|
63
|
-
|
64
|
-
new_pairs << [pair[0], b]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
data/lib/chomsky_nf.rb
DELETED
@@ -1,153 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'chomsky_nf_simplifier'
|
4
|
-
|
5
|
-
# convert to Chomsky Normal Form
|
6
|
-
class ChomskyNF
|
7
|
-
def run(rules, alphabet)
|
8
|
-
new_rules = {}
|
9
|
-
@rules = rules
|
10
|
-
@alphabet = alphabet
|
11
|
-
|
12
|
-
# add cases like (A, a), (B, b)
|
13
|
-
_add_single_vars new_rules
|
14
|
-
|
15
|
-
# change rules like (X, aX) to (X, AX)
|
16
|
-
_handle_simple_rule new_rules
|
17
|
-
|
18
|
-
_handle_all_rule new_rules
|
19
|
-
end
|
20
|
-
|
21
|
-
def simplify(rules)
|
22
|
-
simplifier = ChomskyNFSimplifier.new
|
23
|
-
|
24
|
-
simplifier.run rules, @alphabet
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def _add_single_vars(new_rules)
|
30
|
-
@rules.values.reduce(:concat).each do |rule|
|
31
|
-
rule.select { |var| var == var.downcase }.each do |r|
|
32
|
-
new_rules[r.upcase] = r
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def _handle_simple_rule(new_rules)
|
38
|
-
@rules.each do |var, rule|
|
39
|
-
new_rules[var] = []
|
40
|
-
rule.each do |r|
|
41
|
-
next if r.empty?
|
42
|
-
|
43
|
-
new_rules[var] << r.map(&:upcase)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def _handle_all_rule(new_rules)
|
49
|
-
new_rules_buffer = {}
|
50
|
-
|
51
|
-
new_rules.each do |var, rules|
|
52
|
-
next if _is_character? rules
|
53
|
-
|
54
|
-
new_rules_buffer = _new_rules_buffer rules, var
|
55
|
-
end
|
56
|
-
|
57
|
-
new_rules.merge! new_rules_buffer
|
58
|
-
end
|
59
|
-
|
60
|
-
def _is_character?(value)
|
61
|
-
value.is_a?(String) && value.length == 1
|
62
|
-
end
|
63
|
-
|
64
|
-
def _new_rules_buffer(rules, var)
|
65
|
-
helper_vars = _helper_vars rules
|
66
|
-
helper_vars.merge! @alphabet.each_with_object({}) { |ele, meme| meme[ele.capitalize] = ele.capitalize }
|
67
|
-
|
68
|
-
_new_rules rules, var, helper_vars
|
69
|
-
end
|
70
|
-
|
71
|
-
def _check_simplify(rule, holder, current_var)
|
72
|
-
return false unless rule.size == 1
|
73
|
-
|
74
|
-
holder[current_var] << rule.last unless holder[current_var].include? rule
|
75
|
-
|
76
|
-
true
|
77
|
-
end
|
78
|
-
|
79
|
-
def _new_rules(rules, var, helper_vars)
|
80
|
-
holder = {}
|
81
|
-
|
82
|
-
rules.each do |rule|
|
83
|
-
rest = ''
|
84
|
-
rule.each_with_index do |head, index|
|
85
|
-
current_var = index.zero? ? var : rest
|
86
|
-
holder[current_var] ||= []
|
87
|
-
|
88
|
-
is_simple = _check_simplify rule, holder, current_var
|
89
|
-
|
90
|
-
break if is_simple
|
91
|
-
|
92
|
-
rest = _chomsky_nf_vars index, rule, helper_vars
|
93
|
-
|
94
|
-
nf_rest = "#{head}#{rest}"
|
95
|
-
|
96
|
-
if index + 2 == rule.size
|
97
|
-
if rest.nil?
|
98
|
-
rest = rule.last
|
99
|
-
nf_rest += rest
|
100
|
-
end
|
101
|
-
|
102
|
-
holder[current_var] << nf_rest unless holder[current_var].include? nf_rest
|
103
|
-
|
104
|
-
break
|
105
|
-
else
|
106
|
-
holder[current_var] << nf_rest unless holder[current_var].include? nf_rest
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
holder
|
112
|
-
end
|
113
|
-
|
114
|
-
def _chomsky_nf_vars(index, rule, helper_vars)
|
115
|
-
rule_size = rule.size
|
116
|
-
rest = rule[(index + 1)..rule_size].join
|
117
|
-
|
118
|
-
helper_vars.key(rest)
|
119
|
-
end
|
120
|
-
|
121
|
-
def _helper_vars(rules)
|
122
|
-
helper_vars = {}
|
123
|
-
|
124
|
-
rules.each do |rule|
|
125
|
-
rule.each_with_index do |_, index|
|
126
|
-
letter, rest = _chomsky_nf_helper_vars index, rule, helper_vars.keys
|
127
|
-
|
128
|
-
break if index + 2 == rule.size
|
129
|
-
|
130
|
-
next if helper_vars.values.include? rest
|
131
|
-
|
132
|
-
helper_vars[letter] = rest unless helper_vars.key?(letter) && rest.size > 1
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
helper_vars
|
137
|
-
end
|
138
|
-
|
139
|
-
def _chomsky_nf_helper_vars(index, rule, helper_vars)
|
140
|
-
rule_size = rule.size
|
141
|
-
letter = helper_vars.empty? ? _find_letter(index) : helper_vars.sort!.last.succ
|
142
|
-
rest = rule[(index + 1)..rule_size].join
|
143
|
-
|
144
|
-
[letter, rest]
|
145
|
-
end
|
146
|
-
|
147
|
-
def _find_letter(n)
|
148
|
-
result = 'H'
|
149
|
-
n.times { result = result.succ }
|
150
|
-
|
151
|
-
result
|
152
|
-
end
|
153
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# simplify chomsky normal form
|
4
|
-
class ChomskyNFSimplifier
|
5
|
-
def run(new_rules, alphabet)
|
6
|
-
var_has_one_letter = _var_has_one_letter new_rules, alphabet
|
7
|
-
|
8
|
-
return new_rules if var_has_one_letter.empty?
|
9
|
-
|
10
|
-
rules = _find_modify_rules new_rules, var_has_one_letter
|
11
|
-
|
12
|
-
return new_rules if rules.empty?
|
13
|
-
|
14
|
-
missing_rules = _generate_missing_rules rules, var_has_one_letter
|
15
|
-
|
16
|
-
_add_missing_rules missing_rules, new_rules
|
17
|
-
|
18
|
-
new_rules
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def _var_has_one_letter(new_rules, alphabet)
|
24
|
-
vars = {}
|
25
|
-
|
26
|
-
new_rules.each do |key, values|
|
27
|
-
next if values.size == 1
|
28
|
-
|
29
|
-
values.each do |val|
|
30
|
-
next unless val.size == 1 && alphabet.include?(val.downcase)
|
31
|
-
|
32
|
-
vars[key] ||= []
|
33
|
-
vars[key] << val
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
vars
|
38
|
-
end
|
39
|
-
|
40
|
-
def _find_modify_rules(new_rules, vars)
|
41
|
-
modify = {}
|
42
|
-
|
43
|
-
new_rules.each do |key, val|
|
44
|
-
next if val.is_a?(String)
|
45
|
-
|
46
|
-
val.each do |v|
|
47
|
-
vars.each_key do |k|
|
48
|
-
next unless v.include?(k)
|
49
|
-
|
50
|
-
res = val.select { |x| x.include? k }
|
51
|
-
|
52
|
-
modify[key] ||= []
|
53
|
-
modify[key] << res
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
modify
|
59
|
-
end
|
60
|
-
|
61
|
-
def _generate_missing_rules(rules, vars)
|
62
|
-
res = {}
|
63
|
-
|
64
|
-
rules.each do |var_rule, nfs|
|
65
|
-
nfs.flatten.each do |nf|
|
66
|
-
vars.each do |match_char, replacement_chars|
|
67
|
-
replacement_chars.each do |rc|
|
68
|
-
new_rules = _combinations nf, rc, match_char
|
69
|
-
res[var_rule] ||= []
|
70
|
-
res[var_rule] << new_rules
|
71
|
-
end
|
72
|
-
end
|
73
|
-
_check_special_case nf, res, var_rule, vars
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
res
|
78
|
-
end
|
79
|
-
|
80
|
-
def _combinations(original_string, replacement_char, match_char)
|
81
|
-
combinations = []
|
82
|
-
|
83
|
-
0.upto(1) do |i|
|
84
|
-
0.upto(1) do |j|
|
85
|
-
new_rule = original_string.dup
|
86
|
-
new_rule[0] = replacement_char if i == 1 && original_string[0] == match_char
|
87
|
-
new_rule[1] = replacement_char if j == 1 && original_string[1] == match_char
|
88
|
-
combinations << new_rule
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
combinations.uniq
|
93
|
-
end
|
94
|
-
|
95
|
-
def _add_missing_rules(missing_rules, new_rules)
|
96
|
-
missing_rules.each { |key, val| new_rules[key] = val.flatten.uniq! }
|
97
|
-
end
|
98
|
-
|
99
|
-
def _check_special_case(nf, res, var_rule, vars)
|
100
|
-
vars.each do |match_char, combi|
|
101
|
-
next unless nf == match_char * 2
|
102
|
-
|
103
|
-
missing_rules = combi.map { |lft| combi.map { |rgt| "#{lft}#{rgt}" } }.flatten
|
104
|
-
|
105
|
-
res[var_rule] << missing_rules
|
106
|
-
end
|
107
|
-
|
108
|
-
res
|
109
|
-
end
|
110
|
-
end
|
data/lib/cyk.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Implemention of cyk algo.
|
4
|
-
class CYK
|
5
|
-
attr_reader :matrix, :is_in_l
|
6
|
-
|
7
|
-
def initialize(word, custom_rule)
|
8
|
-
@word = word
|
9
|
-
@rules = custom_rule.select { |_, val| val.is_a?(Array) }
|
10
|
-
@helper_vars = @rules.values.flatten
|
11
|
-
end
|
12
|
-
|
13
|
-
def run
|
14
|
-
_cyk_fill_diagonal
|
15
|
-
_cyk_fill_matrix
|
16
|
-
_is_in_l?
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def _cyk_fill_diagonal
|
22
|
-
wrd_lng = @word.length
|
23
|
-
@matrix = wrd_lng.times.map { wrd_lng.times.map { [] } }
|
24
|
-
|
25
|
-
(0..wrd_lng - 1).each do |index|
|
26
|
-
@matrix[index][index] = @word[index].upcase
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def _cyk_fill_matrix
|
31
|
-
@matrix.size.times do |limiter|
|
32
|
-
(0...@matrix.length - limiter).each do |i|
|
33
|
-
j = i + limiter
|
34
|
-
|
35
|
-
next if i == j
|
36
|
-
|
37
|
-
helper_var = _helper_var i, j
|
38
|
-
|
39
|
-
@matrix[i][j] = _add_to_matrix helper_var
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def _helper_var(row, col)
|
45
|
-
res = '0'
|
46
|
-
|
47
|
-
(row..col - 1).step(1) do |h|
|
48
|
-
p_ = @matrix[row][h]
|
49
|
-
q_ = @matrix[h + 1][col]
|
50
|
-
res = "#{p_}#{q_}"
|
51
|
-
|
52
|
-
break if @helper_vars.include? res
|
53
|
-
end
|
54
|
-
|
55
|
-
res
|
56
|
-
end
|
57
|
-
|
58
|
-
def _add_to_matrix(rule)
|
59
|
-
res = '0'
|
60
|
-
return if rule == res
|
61
|
-
|
62
|
-
@rules.each do |key, vals|
|
63
|
-
vals.each do |val|
|
64
|
-
res = key if val == rule
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
res
|
69
|
-
end
|
70
|
-
|
71
|
-
def _is_in_l?
|
72
|
-
@is_in_l = @matrix.first.last == 'S'
|
73
|
-
end
|
74
|
-
end
|
data/lib/epsilon_free.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# used to clear a CFG from the epsilon rules
|
4
|
-
class EpsilonFree
|
5
|
-
def run(rules)
|
6
|
-
epsilon = _epsilon rules
|
7
|
-
rules_ef = {}
|
8
|
-
|
9
|
-
return if epsilon.empty?
|
10
|
-
|
11
|
-
epsilon.each do |e_class|
|
12
|
-
cleared_rules = _cleared_rules rules[e_class]
|
13
|
-
epsilon_free_word = cleared_rules.map(&:join)
|
14
|
-
grammer = _grammer_epsilon_free epsilon_free_word, e_class
|
15
|
-
rules_ef[e_class] = grammer
|
16
|
-
end
|
17
|
-
|
18
|
-
rules_ef
|
19
|
-
end
|
20
|
-
|
21
|
-
def rebuild_rules(current_rules, rules_epsilon_free)
|
22
|
-
missing_vars = current_rules.keys - rules_epsilon_free.keys
|
23
|
-
|
24
|
-
res = {}
|
25
|
-
rules_epsilon_free.each do |key, rules|
|
26
|
-
res[key] = []
|
27
|
-
rules.each do |rule|
|
28
|
-
res[key] << rule.chars
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
missing_vars.each { |v| res[v] = current_rules[v] }
|
33
|
-
|
34
|
-
res
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def _epsilon(rules)
|
40
|
-
res = rules.map do |var, rule|
|
41
|
-
next unless rule.include? []
|
42
|
-
|
43
|
-
var
|
44
|
-
end
|
45
|
-
res.reject(&:nil?)
|
46
|
-
end
|
47
|
-
|
48
|
-
def _cleared_rules(rules)
|
49
|
-
rules.reject(&:empty?)
|
50
|
-
end
|
51
|
-
|
52
|
-
def _grammer_epsilon_free(epsilon_free_word, e_class)
|
53
|
-
rules_epsilon_location = _new_rules_epsilon_location epsilon_free_word, e_class
|
54
|
-
|
55
|
-
res = []
|
56
|
-
rules_epsilon_location.map do |rule, possibilities|
|
57
|
-
possibilities.each do |possibility|
|
58
|
-
res << _remove_e_class(possibility, rule.split(''))
|
59
|
-
end
|
60
|
-
|
61
|
-
res << epsilon_free_word.delete(e_class) if possibilities.size == 1
|
62
|
-
end
|
63
|
-
|
64
|
-
res
|
65
|
-
end
|
66
|
-
|
67
|
-
def _new_rules_epsilon_location(s_rule, e_class)
|
68
|
-
res = {}
|
69
|
-
s_rule.each do |rule|
|
70
|
-
s_index = []
|
71
|
-
rule.each_char.with_index { |char, index| s_index << index if char == e_class }
|
72
|
-
res[rule] = _power_set(s_index)
|
73
|
-
end
|
74
|
-
|
75
|
-
res
|
76
|
-
end
|
77
|
-
|
78
|
-
def _power_set(set)
|
79
|
-
if set.empty?
|
80
|
-
[[]]
|
81
|
-
else
|
82
|
-
element = set[0]
|
83
|
-
subsets = _power_set set[1..]
|
84
|
-
subsets + subsets.map { |subset| [element] + subset }
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def _remove_e_class(possibility, cleared_rules)
|
89
|
-
return cleared_rules.join '' if possibility.empty?
|
90
|
-
|
91
|
-
new_rule = cleared_rules.join ''
|
92
|
-
possibility.reverse.each { |index| new_rule.slice! index }
|
93
|
-
|
94
|
-
new_rule
|
95
|
-
end
|
96
|
-
end
|