rlsm 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +12 -1
- data/lib/rlsm.rb +1 -1
- data/lib/rlsm/dfa.rb +13 -4
- data/lib/rlsm/monoid.rb +7 -6
- data/lib/rlsm/monoid_db.rb +1 -1
- data/lib/rlsm/regexp.rb +11 -375
- data/lib/rlsm/regexp_nodes/concat.rb +112 -0
- data/lib/rlsm/regexp_nodes/primexp.rb +49 -0
- data/lib/rlsm/regexp_nodes/renodes.rb +95 -0
- data/lib/rlsm/regexp_nodes/star.rb +50 -0
- data/lib/rlsm/regexp_nodes/union.rb +85 -0
- data/lib/smon/commands/db_find.rb +37 -0
- data/lib/smon/commands/db_stat.rb +20 -0
- data/lib/smon/commands/show.rb +4 -17
- data/lib/smon/presenter.rb +18 -0
- data/lib/smon/presenter/txt_presenter.rb +157 -0
- data/lib/smon/smon.rb +3 -4
- data/test/dfa_spec.rb +99 -0
- data/test/monoid_spec.rb +270 -0
- data/test/regexp_spec.rb +25 -0
- metadata +16 -5
- data/test/test_rlsm.rb +0 -0
data/Manifest.txt
CHANGED
@@ -12,6 +12,13 @@ lib/rlsm/monkey_patching.rb
|
|
12
12
|
lib/rlsm/monoid.rb
|
13
13
|
lib/rlsm/monoid_db.rb
|
14
14
|
lib/rlsm/regexp.rb
|
15
|
+
lib/rlsm/regexp_nodes/concat.rb
|
16
|
+
lib/rlsm/regexp_nodes/primexp.rb
|
17
|
+
lib/rlsm/regexp_nodes/renodes.rb
|
18
|
+
lib/rlsm/regexp_nodes/star.rb
|
19
|
+
lib/rlsm/regexp_nodes/union.rb
|
20
|
+
lib/smon/commands/db_find.rb
|
21
|
+
lib/smon/commands/db_stat.rb
|
15
22
|
lib/smon/commands/exit.rb
|
16
23
|
lib/smon/commands/help.rb
|
17
24
|
lib/smon/commands/intro.rb
|
@@ -20,5 +27,9 @@ lib/smon/commands/quit.rb
|
|
20
27
|
lib/smon/commands/regexp.rb
|
21
28
|
lib/smon/commands/reload.rb
|
22
29
|
lib/smon/commands/show.rb
|
30
|
+
lib/smon/presenter.rb
|
31
|
+
lib/smon/presenter/txt_presenter.rb
|
23
32
|
lib/smon/smon.rb
|
24
|
-
test/
|
33
|
+
test/dfa_spec.rb
|
34
|
+
test/monoid_spec.rb
|
35
|
+
test/regexp_spec.rb
|
data/lib/rlsm.rb
CHANGED
data/lib/rlsm/dfa.rb
CHANGED
@@ -29,6 +29,7 @@
|
|
29
29
|
require File.join(File.dirname(__FILE__), 'monkey_patching')
|
30
30
|
require File.join(File.dirname(__FILE__), 'monoid')
|
31
31
|
require File.join(File.dirname(__FILE__), 'regexp')
|
32
|
+
require File.join(File.dirname(__FILE__), 'exceptions.rb')
|
32
33
|
|
33
34
|
module RLSM
|
34
35
|
class DFA
|
@@ -453,13 +454,14 @@ module RLSM
|
|
453
454
|
|
454
455
|
matr.map! do |row|
|
455
456
|
row.each_with_index do |re,j|
|
456
|
-
row[j] = re + ri[j]
|
457
|
+
row[j] = re + row[i]*ri[j]
|
457
458
|
end
|
458
459
|
|
459
460
|
row[i] = RLSM::RegExp.new
|
460
461
|
|
461
462
|
row
|
462
463
|
end
|
464
|
+
|
463
465
|
end
|
464
466
|
|
465
467
|
#Examine now the last remaining first row (irritating...)
|
@@ -514,8 +516,15 @@ module RLSM
|
|
514
516
|
|
515
517
|
#Returns a string represantation
|
516
518
|
def to_s
|
517
|
-
|
519
|
+
str = "\t| #{@alphabet.join(' | ')} \n"
|
520
|
+
str += '-'*(str.length+7) + "\n"
|
521
|
+
str + @states.map { |state| state.to_s }.join("\n")
|
522
|
+
end
|
523
|
+
|
524
|
+
def inspect
|
525
|
+
"<#{self.class}: #{@states.map {|c| c.label }.join(', ') }"
|
518
526
|
end
|
527
|
+
|
519
528
|
|
520
529
|
|
521
530
|
protected
|
@@ -678,8 +687,8 @@ module RLSM
|
|
678
687
|
str = ''
|
679
688
|
str += '-> ' if initial?
|
680
689
|
str += '* ' if final?
|
681
|
-
str += @label.to_s +
|
682
|
-
str += @
|
690
|
+
str += @label.to_s + "\t| "
|
691
|
+
str += @dfa.alphabet.map { |c| process(c) ? process(c).label : ' ' }.join(' | ')
|
683
692
|
|
684
693
|
str
|
685
694
|
end
|
data/lib/rlsm/monoid.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
|
29
29
|
require File.join(File.dirname(__FILE__), 'monkey_patching')
|
30
30
|
require File.join(File.dirname(__FILE__), 'dfa')
|
31
|
+
require File.join(File.dirname(__FILE__), 'exceptions.rb')
|
31
32
|
|
32
33
|
# A Monoid is a set of elements with an associative binary operation and a neutral element.
|
33
34
|
module RLSM
|
@@ -371,9 +372,9 @@ class Monoid
|
|
371
372
|
|
372
373
|
#Returns a DFA with the monoid elements as states, the neutral element as initial state and transitions given by the binary operation. The argument gives the final states. If the monoid is syntactic, the finals must be a disjunctive subset. If no argument is given in this case, the smallest disjunctive subset is used.
|
373
374
|
def to_dfa(finals = [])
|
374
|
-
alph = @names.values_at(generating_subset)
|
375
|
+
alph = @names.values_at(*generating_subset)
|
375
376
|
states = @names.clone
|
376
|
-
|
377
|
+
initial = @names.first
|
377
378
|
|
378
379
|
if syntactic?
|
379
380
|
if finals.empty?
|
@@ -387,10 +388,10 @@ class Monoid
|
|
387
388
|
|
388
389
|
trans = []
|
389
390
|
alph.each do |char|
|
390
|
-
@names.each { |s1| trans << [char, s1, self[s1,char]] }
|
391
|
+
@names.each { |s1| trans << [char, s1, @names[self[s1,char]]] }
|
391
392
|
end
|
392
393
|
|
393
|
-
RLSM::DFA.new alph, states, initial, finals, trans
|
394
|
+
RLSM::DFA.new alph, states, initial, finals.map {|c| @names[c] }, trans
|
394
395
|
end
|
395
396
|
|
396
397
|
private
|
@@ -489,11 +490,11 @@ class Monoid
|
|
489
490
|
if names and names.class == Array and names.size == @order
|
490
491
|
@names = names
|
491
492
|
else
|
492
|
-
#Make a guess, works if convention is followed.
|
493
|
+
#Make a guess, works if the convention that the first row belongs to the neutral element is followed.
|
493
494
|
@names = @bo.flatten.uniq.clone
|
494
495
|
end
|
495
496
|
|
496
|
-
@bo = (@bo.flatten.map { |e| @names.index(e) })/@order
|
497
|
+
@bo = (@bo.flatten.map { |e| @names.include?(e) ? @names.index(e) : e })/@order
|
497
498
|
end
|
498
499
|
|
499
500
|
def _check_form_of_binary_operation
|
data/lib/rlsm/monoid_db.rb
CHANGED
data/lib/rlsm/regexp.rb
CHANGED
@@ -26,23 +26,22 @@
|
|
26
26
|
#
|
27
27
|
|
28
28
|
require File.join(File.dirname(__FILE__), 'monkey_patching')
|
29
|
+
require File.join(File.dirname(__FILE__), 'exceptions.rb')
|
30
|
+
require File.join(File.dirname(__FILE__), 'regexp_nodes', 'renodes')
|
31
|
+
require File.join(File.dirname(__FILE__), 'dfa')
|
29
32
|
|
30
33
|
module RLSM
|
31
34
|
class RegExp
|
35
|
+
include RLSM::RENodes
|
36
|
+
|
32
37
|
#Creates a new RegExp from a string description. Metacharacters are
|
33
38
|
# & * | ( )
|
34
39
|
#Here & is the empty word and an empty string represents the empty set.
|
35
|
-
def initialize(
|
40
|
+
def initialize(desc = "")
|
36
41
|
#Is the argument a well formed RegExp?
|
37
|
-
_well_formed?(
|
42
|
+
_well_formed?(desc)
|
38
43
|
|
39
|
-
|
40
|
-
re = str.squeeze('&*')
|
41
|
-
|
42
|
-
#* on a & is &
|
43
|
-
re = re.gsub('&*', '&')
|
44
|
-
|
45
|
-
@re = NodeFactory.new_node(nil, re)
|
44
|
+
@re = RLSM::RENodes.new(desc)
|
46
45
|
end
|
47
46
|
|
48
47
|
#--
|
@@ -55,7 +54,7 @@ module RLSM
|
|
55
54
|
#A double star is also useless
|
56
55
|
return if empty? or lambda? or (@re.class == Star)
|
57
56
|
str = '(' + to_s + ')*'
|
58
|
-
@re =
|
57
|
+
@re = RLSM::RENodes.new(str)
|
59
58
|
|
60
59
|
#Unset the str rep
|
61
60
|
@re_str = nil
|
@@ -142,7 +141,7 @@ module RLSM
|
|
142
141
|
end
|
143
142
|
end
|
144
143
|
|
145
|
-
tmp_re =
|
144
|
+
tmp_re = RLSM::RENodes.new(rre)
|
146
145
|
|
147
146
|
#Step 2a: Construct a DFA representation of this new regexp
|
148
147
|
#Step 2b: reverse the substitution (yields (maybe) a NFA)
|
@@ -213,11 +212,8 @@ module RLSM
|
|
213
212
|
#parantheses must be balanced, somthing like |) or *a or (| isn't allowed
|
214
213
|
#1 balanced parenthesis
|
215
214
|
state = 0
|
216
|
-
count = Hash.new(0)
|
217
|
-
count['('] = 1
|
218
|
-
count[')'] = -1
|
219
215
|
str.each_char do |c|
|
220
|
-
state +=
|
216
|
+
state += RLSM::RENodes::PCount[c]
|
221
217
|
end
|
222
218
|
|
223
219
|
if state != 0
|
@@ -229,365 +225,5 @@ module RLSM
|
|
229
225
|
raise RegExpException, "Bad character sequence #{$&} found in #{str}"
|
230
226
|
end
|
231
227
|
end
|
232
|
-
|
233
|
-
class PrimExp
|
234
|
-
def initialize(parent, str)
|
235
|
-
@parent = parent
|
236
|
-
if str == '&' or str == ['&']
|
237
|
-
@content = '&'
|
238
|
-
@null = true
|
239
|
-
else
|
240
|
-
@content = str.reject { |c| c == '&' }
|
241
|
-
@null = false
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
def null?
|
246
|
-
@null
|
247
|
-
end
|
248
|
-
|
249
|
-
def first
|
250
|
-
@null ? [] : @content[0,1]
|
251
|
-
end
|
252
|
-
|
253
|
-
def last
|
254
|
-
@null ? [] : @content[-1,1]
|
255
|
-
end
|
256
|
-
|
257
|
-
def follow
|
258
|
-
res = []
|
259
|
-
|
260
|
-
(1...@content.length).each do |i|
|
261
|
-
res << [@content[i-1,1], @content[i,1]]
|
262
|
-
end
|
263
|
-
|
264
|
-
res
|
265
|
-
end
|
266
|
-
|
267
|
-
def to_s
|
268
|
-
@content.to_s
|
269
|
-
end
|
270
|
-
|
271
|
-
def lambda?
|
272
|
-
@null
|
273
|
-
end
|
274
|
-
|
275
|
-
def empty?
|
276
|
-
@content == '' or @content == []
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
class Star
|
281
|
-
def initialize(parent, str)
|
282
|
-
@parent = parent
|
283
|
-
@child = NodeFactory.new_node(self, str[(0..-2)])
|
284
|
-
end
|
285
|
-
|
286
|
-
def null?
|
287
|
-
true
|
288
|
-
end
|
289
|
-
|
290
|
-
def first
|
291
|
-
@child.first
|
292
|
-
end
|
293
|
-
|
294
|
-
def last
|
295
|
-
@child.last
|
296
|
-
end
|
297
|
-
|
298
|
-
def follow
|
299
|
-
res = @child.follow
|
300
|
-
|
301
|
-
#Cross of last and first
|
302
|
-
first.each do |f|
|
303
|
-
last.each do |l|
|
304
|
-
res << [l,f]
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
res
|
309
|
-
end
|
310
|
-
|
311
|
-
def to_s
|
312
|
-
if @child.class == PrimExp and @child.to_s.length == 1
|
313
|
-
return "#{@child.to_s}*"
|
314
|
-
else
|
315
|
-
return "(#{@child.to_s})*"
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
def lambda?
|
320
|
-
false
|
321
|
-
end
|
322
|
-
|
323
|
-
def empty?
|
324
|
-
false
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
class Union
|
329
|
-
def initialize(parent, str)
|
330
|
-
@parent = parent
|
331
|
-
@childs = _split(str).map do |substr|
|
332
|
-
NodeFactory.new_node(self, substr)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
def null?
|
337
|
-
@childs.any? { |child| child.null? }
|
338
|
-
end
|
339
|
-
|
340
|
-
def first
|
341
|
-
res = []
|
342
|
-
@childs.each do |child|
|
343
|
-
child.first.each do |f|
|
344
|
-
res << f
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
res
|
349
|
-
end
|
350
|
-
|
351
|
-
def last
|
352
|
-
res = []
|
353
|
-
@childs.each do |child|
|
354
|
-
child.last.each do |l|
|
355
|
-
res << l
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
res
|
360
|
-
end
|
361
|
-
|
362
|
-
def follow
|
363
|
-
res = []
|
364
|
-
@childs.each do |child|
|
365
|
-
child.follow.each do |f|
|
366
|
-
res << f
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
res
|
371
|
-
end
|
372
|
-
|
373
|
-
def to_s
|
374
|
-
if @parent.nil? or @parent.class == Union or @paarent.class == Star
|
375
|
-
return @childs.map { |child| child.to_s }.join('|')
|
376
|
-
else
|
377
|
-
return '(' + @childs.map { |child| child.to_s }.join('|') + ')'
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
def lambda?
|
382
|
-
false
|
383
|
-
end
|
384
|
-
|
385
|
-
def empty?
|
386
|
-
false
|
387
|
-
end
|
388
|
-
|
389
|
-
private
|
390
|
-
def _split(str)
|
391
|
-
state = 0
|
392
|
-
count = Hash.new(0)
|
393
|
-
count['('] = 1
|
394
|
-
count[')'] = -1
|
395
|
-
|
396
|
-
res = [[]]
|
397
|
-
|
398
|
-
str.each_char do |c|
|
399
|
-
state += count[c]
|
400
|
-
if c == '|' and state == 0
|
401
|
-
res << []
|
402
|
-
else
|
403
|
-
res.last << c
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
res#.map { |substr| substr.join }
|
408
|
-
end
|
409
|
-
end
|
410
|
-
|
411
|
-
class Concat
|
412
|
-
def initialize(parent, str)
|
413
|
-
@parent = parent
|
414
|
-
@childs = _split(str).map do |substr|
|
415
|
-
NodeFactory.new_node(self, substr)
|
416
|
-
end.reject { |child| child.lambda? }
|
417
|
-
end
|
418
|
-
|
419
|
-
def null?
|
420
|
-
@childs.all? { |child| child.null? }
|
421
|
-
end
|
422
|
-
|
423
|
-
def first
|
424
|
-
res = []
|
425
|
-
@childs.each do |child|
|
426
|
-
child.first.each do |f|
|
427
|
-
res << f
|
428
|
-
end
|
429
|
-
|
430
|
-
break unless child.null?
|
431
|
-
end
|
432
|
-
|
433
|
-
res
|
434
|
-
end
|
435
|
-
|
436
|
-
def last
|
437
|
-
res = []
|
438
|
-
@childs.reverse.each do |child|
|
439
|
-
child.last.each do |f|
|
440
|
-
res << f
|
441
|
-
end
|
442
|
-
|
443
|
-
break unless child.null?
|
444
|
-
end
|
445
|
-
|
446
|
-
res
|
447
|
-
end
|
448
|
-
|
449
|
-
def follow
|
450
|
-
res = []
|
451
|
-
|
452
|
-
@childs.each do |child|
|
453
|
-
child.follow.each do |f|
|
454
|
-
res << f
|
455
|
-
end
|
456
|
-
end
|
457
|
-
|
458
|
-
(1...@childs.size).each do |i|
|
459
|
-
@childs[i-1].last.each do |l|
|
460
|
-
@childs[(i..-1)].each do |ch|
|
461
|
-
ch.first.each do |f|
|
462
|
-
res << [l,f]
|
463
|
-
end
|
464
|
-
|
465
|
-
break unless ch.null?
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
res
|
471
|
-
end
|
472
|
-
|
473
|
-
def to_s
|
474
|
-
@childs.map { |child| child.to_s }.join
|
475
|
-
end
|
476
|
-
|
477
|
-
def lambda?
|
478
|
-
false
|
479
|
-
end
|
480
|
-
|
481
|
-
def empty?
|
482
|
-
false
|
483
|
-
end
|
484
|
-
|
485
|
-
private
|
486
|
-
def _split(str)
|
487
|
-
state = 0
|
488
|
-
count = Hash.new(0)
|
489
|
-
count['('] = 1
|
490
|
-
count[')'] = -1
|
491
|
-
|
492
|
-
res = [[]]
|
493
|
-
previous = nil
|
494
|
-
str.each_char do |c|
|
495
|
-
state += count[c]
|
496
|
-
|
497
|
-
if state == 1 and c == '('
|
498
|
-
res << []
|
499
|
-
res.last << c
|
500
|
-
elsif state == 0 and c == '*'
|
501
|
-
if previous == ')'
|
502
|
-
res[-2] << c
|
503
|
-
else
|
504
|
-
res << [res.last.pop, c]
|
505
|
-
res << []
|
506
|
-
end
|
507
|
-
elsif state == 0 and c == ')'
|
508
|
-
res.last << c
|
509
|
-
res << []
|
510
|
-
else
|
511
|
-
res.last << c
|
512
|
-
end
|
513
|
-
|
514
|
-
previous = c
|
515
|
-
end
|
516
|
-
|
517
|
-
res.select { |subarr| subarr.size > 0 }#.map { |substr| substr.join }
|
518
|
-
end
|
519
|
-
end
|
520
|
-
|
521
|
-
class NodeFactory
|
522
|
-
def self.new_node(parent, arg)
|
523
|
-
|
524
|
-
#Remove parentheses
|
525
|
-
str = arg.dup
|
526
|
-
while sp(str)
|
527
|
-
str = str[(1..-2)]
|
528
|
-
end
|
529
|
-
#puts "Processing: #{arg} from #{parent.class}"
|
530
|
-
#Choose the right node type
|
531
|
-
if prim?(str)
|
532
|
-
return PrimExp.new(parent, str)
|
533
|
-
elsif star?(str)
|
534
|
-
return Star.new(parent, str)
|
535
|
-
elsif union?(str)
|
536
|
-
return Union.new(parent, str)
|
537
|
-
else
|
538
|
-
return Concat.new(parent, str)
|
539
|
-
end
|
540
|
-
|
541
|
-
end
|
542
|
-
|
543
|
-
private
|
544
|
-
def self.sp(str)
|
545
|
-
if str[0,1].include? '(' and str[-1,1].include? ')'
|
546
|
-
state = 0
|
547
|
-
l = 0
|
548
|
-
count = Hash.new(0)
|
549
|
-
count['('] = 1
|
550
|
-
count[')'] = -1
|
551
|
-
|
552
|
-
str.each_char do |c|
|
553
|
-
state += count[c]
|
554
|
-
l += 1
|
555
|
-
break if state == 0
|
556
|
-
end
|
557
|
-
|
558
|
-
return true if str.length == l
|
559
|
-
end
|
560
|
-
|
561
|
-
false
|
562
|
-
end
|
563
|
-
|
564
|
-
def self.prim?(str)
|
565
|
-
not ['(', ')', '|', '*'].any? { |c| str.include? c }
|
566
|
-
end
|
567
|
-
|
568
|
-
def self.star?(str)
|
569
|
-
if str[-1,1].include? '*'
|
570
|
-
return true if sp(str[(0..-2)]) #something like (....)*
|
571
|
-
return true if str.length == 2 #something like a*
|
572
|
-
end
|
573
|
-
|
574
|
-
false
|
575
|
-
end
|
576
|
-
|
577
|
-
def self.union?(str)
|
578
|
-
state = 0
|
579
|
-
count = Hash.new(0)
|
580
|
-
count['('] = 1
|
581
|
-
count[')'] = -1
|
582
|
-
|
583
|
-
str.each_char do |c|
|
584
|
-
state += count[c]
|
585
|
-
|
586
|
-
return true if c == '|' and state == 0
|
587
|
-
end
|
588
|
-
|
589
|
-
false
|
590
|
-
end
|
591
|
-
end
|
592
228
|
end
|
593
229
|
end
|