falafel 0.0.0.2 → 0.0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|