falafel 0.0.0.2 → 0.0.2.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 +4 -4
- data/lib/cfg.rb +54 -18
- data/lib/chaining_free.rb +67 -0
- data/lib/chomsky_nf.rb +153 -0
- data/lib/chomsky_nf_simplifier.rb +115 -0
- data/lib/cyk.rb +98 -0
- data/lib/dfa.rb +16 -13
- data/lib/epsilon_free.rb +96 -0
- data/lib/falafel.rb +8 -5
- data/lib/pump.rb +52 -13
- data/lib/rx.rb +29 -19
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5180f16b8868a35cf65faa396c7b341166604e2aaf7db8d9f7099c819630497f
|
4
|
+
data.tar.gz: 7c773f6a53d8895333d37115cba598dcf60128bff2eaf4dc47e73f7e1ca21298
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b7470e125d1f803eacc1d207704b90782e439fef8c7fe00dcf0acdb6c27324d24ed2da0666b3c456fe7cc7b255902002f20870e8f93d589482d4d1df3370aca
|
7
|
+
data.tar.gz: c8534f9da1cfec2623caa5cf0422f3d03009579374d2183a10341398a93aad87fbf2a0e85aaba0a0a766ee1d8ae62baa02e39bd2a76c7857fa6613629ae64f5a
|
data/lib/cfg.rb
CHANGED
@@ -1,44 +1,80 @@
|
|
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'
|
7
|
+
|
3
8
|
# Samll CFG impletation
|
4
9
|
# Generate words and check if words are the @lang
|
10
|
+
# generates random words
|
5
11
|
class CFG
|
6
|
-
attr_accessor :
|
7
|
-
attr_reader :rnd_words
|
8
|
-
|
12
|
+
attr_accessor :start_var, :rules
|
13
|
+
attr_reader :rules_ef, :rules_cf, :rnd_words,
|
14
|
+
:rules_ef_res, :rules_cf_res, :chomsky_nf_rules,
|
15
|
+
:cyk_matrix, :is_in_l
|
9
16
|
|
10
17
|
def initialize(alphabet, vars, start_var, rules)
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
18
|
+
@start_var = start_var
|
19
|
+
@rules = _rules rules
|
20
|
+
@rnd_words = []
|
21
|
+
@vars = vars
|
22
|
+
@alphabet = alphabet
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_words(count)
|
15
26
|
@rnd_words = []
|
27
|
+
|
28
|
+
loop do
|
29
|
+
word = _expand @start_var
|
30
|
+
|
31
|
+
@rnd_words << word unless @rnd_words.include? word
|
32
|
+
|
33
|
+
break if @rnd_words.size == count
|
34
|
+
end
|
16
35
|
end
|
17
36
|
|
18
|
-
def
|
19
|
-
|
37
|
+
def epsilon_free(custom_rule)
|
38
|
+
e_free = EpsilonFree.new
|
39
|
+
@rules_ef_res = e_free.run custom_rule || @rules
|
40
|
+
@rules_ef = e_free.rebuild_rules @rules, @rules_ef_res
|
41
|
+
end
|
20
42
|
|
21
|
-
|
43
|
+
def chaining_free
|
44
|
+
c_free = ChainingFree.new
|
45
|
+
@rules_cf_res = c_free.run @rules, @vars
|
46
|
+
@rules_cf = c_free.rebuild_rules @rules_cf_res
|
47
|
+
end
|
22
48
|
|
23
|
-
|
49
|
+
def chomsky_nf(custom_rule)
|
50
|
+
chomsky = ChomskyNF.new
|
51
|
+
rules = chomsky.run custom_rule || @rules, @alphabet
|
52
|
+
|
53
|
+
@chomsky_nf_rules = chomsky.simplify rules
|
24
54
|
end
|
25
55
|
|
26
|
-
def
|
27
|
-
|
56
|
+
def cyk_run(word)
|
57
|
+
cyk = CYK.new word, @chomsky_nf_rules
|
58
|
+
|
59
|
+
cyk.run
|
60
|
+
|
61
|
+
@cyk_matrix = cyk.matrix
|
62
|
+
@is_in_l = cyk.is_in_l
|
28
63
|
end
|
29
64
|
|
30
65
|
private
|
31
66
|
|
67
|
+
def _rules(rules)
|
68
|
+
rules.each_value { |k| k.each_with_index { |item, index| k[index] = item[0].is_a?(String) ? item[0].split('') : item } }
|
69
|
+
end
|
70
|
+
|
32
71
|
def _expand(symbol)
|
33
72
|
production = @rules[symbol]
|
34
73
|
|
35
74
|
return symbol if production.nil?
|
36
75
|
|
37
|
-
rhs = production.sample
|
38
|
-
rhs.map { |s| _expand(s) }.join
|
39
|
-
end
|
76
|
+
rhs = production.sample # pick up a random element from array
|
40
77
|
|
41
|
-
|
42
|
-
rules.each_value { |k| k.each_with_index { |item, index| k[index] = item[0].is_a?(String) ? item[0].split('') : item } }
|
78
|
+
rhs.map { |s| _expand(s) }.join
|
43
79
|
end
|
44
80
|
end
|
@@ -0,0 +1,67 @@
|
|
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
ADDED
@@ -0,0 +1,153 @@
|
|
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.reduce(&:merge)
|
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
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# simplify chomsky normal form
|
4
|
+
class ChomskyNFSimplifier
|
5
|
+
def run(new_rules, alphabet)
|
6
|
+
vars_with_one_letter = _vars_with_one_letter new_rules, alphabet
|
7
|
+
|
8
|
+
return new_rules if vars_with_one_letter.empty?
|
9
|
+
|
10
|
+
extendible_rules = _find_extendible_rules new_rules, vars_with_one_letter.keys
|
11
|
+
|
12
|
+
return new_rules if extendible_rules.empty?
|
13
|
+
|
14
|
+
missing_rules = _missing_rules extendible_rules, vars_with_one_letter
|
15
|
+
|
16
|
+
_add_missing_rules missing_rules, new_rules
|
17
|
+
|
18
|
+
new_rules
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def _vars_with_one_letter(new_rules, alphabet)
|
24
|
+
vars = {}
|
25
|
+
|
26
|
+
new_rules.each do |key, values|
|
27
|
+
# skip simple rules like (A, a), (B, b),...
|
28
|
+
next if values.size == 1
|
29
|
+
|
30
|
+
letters = values.select { |val| _is_letter val, alphabet }.uniq
|
31
|
+
vars[key] = letters
|
32
|
+
end
|
33
|
+
|
34
|
+
vars
|
35
|
+
end
|
36
|
+
|
37
|
+
def _is_letter(val, alphabet)
|
38
|
+
val.size == 1 && alphabet.include?(val.downcase)
|
39
|
+
end
|
40
|
+
|
41
|
+
def _find_extendible_rules(new_rules, rules_keys)
|
42
|
+
extendible = {}
|
43
|
+
|
44
|
+
filterd_rules = new_rules.reject { |_, rules| rules.is_a?(String) }
|
45
|
+
|
46
|
+
filterd_rules.each do |letter, rules|
|
47
|
+
rules_keys.each do |rules_key|
|
48
|
+
rules.each do |rule|
|
49
|
+
next unless rule.include?(rules_key)
|
50
|
+
|
51
|
+
extendible[letter] ||= []
|
52
|
+
extendible[letter] << rule
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
extendible
|
58
|
+
end
|
59
|
+
|
60
|
+
def _missing_rules(extendible_rules, vars)
|
61
|
+
res = {}
|
62
|
+
|
63
|
+
extendible_rules.each do |var_rule, nfs|
|
64
|
+
nfs.each do |nf|
|
65
|
+
vars.each do |match_char, replacement_chars|
|
66
|
+
replacement_chars.each do |rc|
|
67
|
+
res[var_rule] ||= []
|
68
|
+
res[var_rule] << _combinations(nf, rc, match_char)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
res[var_rule] = res[var_rule]&.flatten&.uniq
|
73
|
+
|
74
|
+
_check_special_case nf, res, var_rule, vars
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
res
|
79
|
+
end
|
80
|
+
|
81
|
+
def _combinations(original_string, replacement_char, match_char)
|
82
|
+
combinations = []
|
83
|
+
|
84
|
+
0.upto(1) do |i|
|
85
|
+
0.upto(1) do |j|
|
86
|
+
new_rule = original_string.dup
|
87
|
+
new_rule[0] = replacement_char if i == 1 && original_string[0] == match_char
|
88
|
+
new_rule[1] = replacement_char if j == 1 && original_string[1] == match_char
|
89
|
+
combinations << new_rule
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
combinations.uniq
|
94
|
+
end
|
95
|
+
|
96
|
+
def _add_missing_rules(missing_rules, new_rules)
|
97
|
+
missing_rules.each do |key, val|
|
98
|
+
next if val.nil?
|
99
|
+
|
100
|
+
new_rules[key].concat(val).uniq!
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def _check_special_case(nf, res, var_rule, vars)
|
105
|
+
vars.each do |match_char, combi|
|
106
|
+
next unless nf == match_char * 2
|
107
|
+
|
108
|
+
missing_rules = combi.map { |lft| combi.map { |rgt| "#{lft}#{rgt}" } }.flatten
|
109
|
+
|
110
|
+
res[var_rule].concat(missing_rules)
|
111
|
+
end
|
112
|
+
|
113
|
+
res
|
114
|
+
end
|
115
|
+
end
|
data/lib/cyk.rb
ADDED
@@ -0,0 +1,98 @@
|
|
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 = _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
|
+
matrix_ele_info = _matrix_ele_info i, j
|
39
|
+
@matrix[i][j] = _add_to_matrix helper_var, matrix_ele_info
|
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 _matrix_ele_info(i, j)
|
59
|
+
res = {}
|
60
|
+
|
61
|
+
res['i'] = i
|
62
|
+
res['j'] = j
|
63
|
+
res['row_c'] = @matrix.count
|
64
|
+
|
65
|
+
res
|
66
|
+
end
|
67
|
+
|
68
|
+
def _add_to_matrix(helper_var, matrix_ele_info)
|
69
|
+
matrix_ele = '0'
|
70
|
+
return if helper_var == matrix_ele
|
71
|
+
|
72
|
+
keys = []
|
73
|
+
|
74
|
+
@rules.each do |key, vals|
|
75
|
+
vals.each do |val|
|
76
|
+
keys << key if val == helper_var
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
_matrix_ele keys, matrix_ele_info
|
81
|
+
end
|
82
|
+
|
83
|
+
def _matrix_ele(keys, matrix_ele_info)
|
84
|
+
return 'S' if keys.include?('S') && _is_last_element?(matrix_ele_info)
|
85
|
+
|
86
|
+
return keys.last if keys.size == 1
|
87
|
+
|
88
|
+
keys.reject { |key| key == 'S' }.last || '0'
|
89
|
+
end
|
90
|
+
|
91
|
+
def _is_last_element?(matrix_ele_info)
|
92
|
+
matrix_ele_info['i'].zero? && matrix_ele_info['j'] == matrix_ele_info['row_c'] - 1
|
93
|
+
end
|
94
|
+
|
95
|
+
def _is_in_l?
|
96
|
+
@matrix.first.last == 'S'
|
97
|
+
end
|
98
|
+
end
|
data/lib/dfa.rb
CHANGED
@@ -53,26 +53,29 @@ class DFA < Falafel
|
|
53
53
|
(finals & states.keys).each_with_object({}) { |elt, memo| memo[elt] = states[elt] }
|
54
54
|
end
|
55
55
|
|
56
|
-
def _r(finals, states,
|
57
|
-
r
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
56
|
+
def _r(finals, states, steps)
|
57
|
+
r = []
|
58
|
+
last_r = steps["R#{steps.size - 1}"]
|
59
|
+
|
60
|
+
last_r.each do |pair|
|
61
|
+
@delta_star.each_key do |c|
|
62
|
+
pair_frst = pair[0]
|
63
|
+
pair_lst = pair[1]
|
64
|
+
|
65
|
+
ri_set = _ri_set states, c, states.key(pair_frst), states.key(pair_lst)
|
66
|
+
|
67
|
+
break unless last_r.include? ri_set
|
68
|
+
|
69
|
+
r << [pair_frst, pair_lst] unless r.include? [pair_frst, pair_lst]
|
66
70
|
end
|
67
71
|
end
|
68
|
-
r.uniq!
|
69
72
|
|
70
73
|
r
|
71
74
|
end
|
72
75
|
|
73
76
|
def _steps_builder(finals, states, steps)
|
74
77
|
0.upto(1_000) do |l|
|
75
|
-
r = _r finals, states, steps
|
78
|
+
r = _r finals, states, steps
|
76
79
|
steps["R#{l + 1}"] = r
|
77
80
|
|
78
81
|
return steps if steps["R#{l}"] == r
|
@@ -95,6 +98,6 @@ class DFA < Falafel
|
|
95
98
|
lft = states[(_image [p], @delta_star[c])[0]]
|
96
99
|
rgt = states[(_image [q], @delta_star[c])[0]]
|
97
100
|
|
98
|
-
[
|
101
|
+
[lft, rgt]
|
99
102
|
end
|
100
103
|
end
|
data/lib/epsilon_free.rb
ADDED
@@ -0,0 +1,96 @@
|
|
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.uniq.reject(&:nil?)
|
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
|
data/lib/falafel.rb
CHANGED
@@ -2,9 +2,13 @@
|
|
2
2
|
|
3
3
|
require_relative 'rx'
|
4
4
|
|
5
|
-
#
|
5
|
+
# NFA to DFA
|
6
6
|
# DFA to REG
|
7
7
|
# Minimize DFA
|
8
|
+
# Remove epsilons from CFG
|
9
|
+
# Remove chaining from CFG
|
10
|
+
# Find a chomsky normal form fro CFG
|
11
|
+
# Apply CYK alogrithm on CFG and solve word problem
|
8
12
|
class Falafel
|
9
13
|
def self.new
|
10
14
|
instance = allocate
|
@@ -86,7 +90,7 @@ class Falafel
|
|
86
90
|
end
|
87
91
|
|
88
92
|
def nfa_to_reg
|
89
|
-
RX.new { |rx| rx.build @states, @delta_star }
|
93
|
+
RX.new { |rx| rx.build @states, @delta_star, @start, @finals }
|
90
94
|
end
|
91
95
|
|
92
96
|
def dfa_to_min(automat)
|
@@ -100,10 +104,10 @@ class Falafel
|
|
100
104
|
dfa.to_min
|
101
105
|
end
|
102
106
|
|
103
|
-
def pump_lemma
|
107
|
+
def pump_lemma(lang, length)
|
104
108
|
require_relative 'pump'
|
105
109
|
|
106
|
-
Pump.new lang:
|
110
|
+
Pump.new lang: lang, length: length
|
107
111
|
end
|
108
112
|
|
109
113
|
def cfg(alphabet, vars_set, start_var, rules)
|
@@ -114,7 +118,6 @@ class Falafel
|
|
114
118
|
|
115
119
|
private
|
116
120
|
|
117
|
-
# alle bilder eine menge m unter r
|
118
121
|
def _image(m, r)
|
119
122
|
m.flat_map { |x| r.map { |y, z| z if x == y }.compact }.sort.uniq
|
120
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
|
data/lib/rx.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
# Regex string builder
|
4
4
|
class RX
|
5
|
+
attr_reader :final_reg
|
6
|
+
|
5
7
|
def self.new
|
6
8
|
instance = allocate
|
7
9
|
yield instance
|
@@ -9,31 +11,52 @@ class RX
|
|
9
11
|
instance
|
10
12
|
end
|
11
13
|
|
12
|
-
def build(states, delta_star)
|
14
|
+
def build(states, delta_star, start, finals)
|
13
15
|
@states = states
|
14
16
|
@delta_star = delta_star
|
17
|
+
@start = start
|
18
|
+
@finals = finals
|
15
19
|
@empty = "\u2205"
|
16
20
|
_to_reg
|
17
21
|
end
|
18
22
|
|
23
|
+
def print_matrix
|
24
|
+
@steps.each do |key, value|
|
25
|
+
puts key
|
26
|
+
value.transpose.each do |column|
|
27
|
+
column.each do |element|
|
28
|
+
printf("\t|%-#{value.map { |c| c.max_by(&:length).length }.max}s", element)
|
29
|
+
end
|
30
|
+
puts '|'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
_final_reg
|
34
|
+
end
|
35
|
+
|
36
|
+
def _final_reg
|
37
|
+
start = @start[0] - 1
|
38
|
+
final = @finals[0] - 1
|
39
|
+
lang = @steps["l#{@steps.size - 1}"]
|
40
|
+
@final_reg = lang[start][final]
|
41
|
+
end
|
42
|
+
|
19
43
|
private
|
20
44
|
|
21
45
|
def _to_reg
|
22
46
|
l0 = _l0
|
23
|
-
steps = {}
|
24
|
-
steps['l0'] = l0
|
47
|
+
@steps = {}
|
48
|
+
@steps['l0'] = l0
|
25
49
|
|
26
50
|
@states.size.times do |h|
|
27
51
|
l = []
|
28
52
|
@states.each_with_index do |_, p|
|
29
53
|
l[p] = []
|
30
54
|
@states.each_with_index do |_, q|
|
31
|
-
l[p][q] = _l steps["l#{h}"], p, q, steps.size - 1
|
55
|
+
l[p][q] = _l @steps["l#{h}"], p, q, @steps.size - 1
|
32
56
|
end
|
33
57
|
end
|
34
|
-
steps["l#{steps.size}"] = l
|
58
|
+
@steps["l#{@steps.size}"] = l
|
35
59
|
end
|
36
|
-
_print_matrix steps
|
37
60
|
end
|
38
61
|
|
39
62
|
def _l0
|
@@ -138,17 +161,4 @@ class RX
|
|
138
161
|
|
139
162
|
rgt
|
140
163
|
end
|
141
|
-
|
142
|
-
def _print_matrix(steps)
|
143
|
-
steps.each do |key, value|
|
144
|
-
puts key
|
145
|
-
max_width = value.map { |column| column.max_by(&:length).length }.max
|
146
|
-
value.transpose.each do |column|
|
147
|
-
column.each do |element|
|
148
|
-
printf("\t|%-#{max_width}s", element)
|
149
|
-
end
|
150
|
-
puts '|'
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
164
|
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Abed
|
7
|
+
- Abed Alkedda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -17,12 +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
24
|
- lib/dfa.rb
|
25
|
+
- lib/epsilon_free.rb
|
21
26
|
- lib/falafel.rb
|
22
27
|
- lib/pump.rb
|
23
28
|
- lib/rx.rb
|
24
|
-
homepage:
|
25
|
-
licenses:
|
29
|
+
homepage: https://github.com/AbedAlkedda/AFS
|
30
|
+
licenses:
|
31
|
+
- ''
|
26
32
|
metadata: {}
|
27
33
|
post_install_message:
|
28
34
|
rdoc_options: []
|
@@ -42,5 +48,6 @@ requirements: []
|
|
42
48
|
rubygems_version: 3.3.26
|
43
49
|
signing_key:
|
44
50
|
specification_version: 4
|
45
|
-
summary: Falafel is a gem
|
51
|
+
summary: Falafel is a gem that will hopefully help you better understand automata
|
52
|
+
and formal languages
|
46
53
|
test_files: []
|