source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/grammar_fa/fa.tcl

devel
Last change on this file was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 33.0 KB
Line 
1# -*- tcl -*-
2# (c) 2004-1007 Andreas Kupries
3# Grammar / Finite Automatons / Container
4
5# ### ### ### ######### ######### #########
6## Package description
7
8## A class whose instances hold all the information describing a
9## single finite automaton (states, symbols, start state, set of
10## accepting states, transition function), and operations to define,
11## manipulate, and query this information.
12
13# ### ### ### ######### ######### #########
14## Requisites
15
16package require grammar::fa::op ; # Heavy FA operations.
17package require snit            ; # OO system in use
18package require struct::list    ; # Extended list operations.
19package require struct::set     ; # Extended set operations.
20
21# ### ### ### ######### ######### #########
22## Implementation
23
24snit::type ::grammar::fa {
25    # ### ### ### ######### ######### #########
26    ## Type API. A number of operations on FAs
27
28    # ### ### ### ######### ######### #########
29    ## Instance API
30
31    #constructor {args} {}
32    #destructor  {}
33
34    method =   {b} {}
35    method --> {b} {}
36
37    method serialize {} {}
38    method deserialize {value} {}
39    method deserialize_merge {value} {}
40
41    method states {} {}
42    method state {cmd s args} {}
43
44    method startstates {} {}
45    method start?      {s} {}
46    method start?set   {states} {}
47    method start       {cmd args} {}
48
49    method finalstates {} {}
50    method final?      {s} {}
51    method final?set   {states} {}
52    method final       {cmd args} {}
53
54    method symbols     {} {}
55    method symbols@    {state} {}
56    method symbols@set {states} {}
57    method symbol      {cmd sym} {}
58
59    method next  {s sym args} {}
60    method !next {s sym args} {}
61    method nextset {states sym} {}
62
63    method is {cmd} {}
64
65    method reachable_states   {} {}
66    method unreachable_states {} {}
67    method reachable          {s} {}
68
69    method useful_states   {} {}
70    method unuseful_states {} {}
71    method useful          {s} {}
72
73    method epsilon_closure {s} {}
74
75    method clear {} {}
76
77    # ### ### ### ######### ######### #########
78    ## Instance API. Complex FA operations.
79    ## The heavy lifting is done by the operations package.
80
81    method reverse    {}                          {op::reverse    $self}
82    method complete   {{sink {}}}                 {op::complete   $self $sink}
83    method remove_eps {}                          {op::remove_eps $self}
84    method trim       {{what !reachable|!useful}} {op::trim       $self $what}
85    method complement {}                          {op::complement $self}
86    method kleene     {}                          {op::kleene     $self}
87    method optional   {}                          {op::optional   $self}
88    method fromRegex  {regex {over {}}}           {op::fromRegex  $self $regex $over}
89
90    method determinize {{mapvar {}}} {
91        if {$mapvar ne ""} {upvar 1 $mapvar map}
92        op::determinize $self map
93    }
94
95    method minimize {{mapvar {}}} {
96        if {$mapvar ne ""} {upvar 1 $mapvar map}
97        op::minimize $self map
98    }
99
100    method union {fa {mapvar {}}} {
101        if {$mapvar ne ""} {upvar 1 $mapvar map}
102        op::union $self $fa map
103    }
104
105    method intersect {fa {mapvar {}}} {
106        if {$mapvar ne ""} {upvar 1 $mapvar map}
107        op::intersect $self $fa map
108    }
109
110    method difference {fa {mapvar {}}} {
111        if {$mapvar ne ""} {upvar 1 $mapvar map}
112        op::difference $self $fa map
113    }
114
115    method concatenate {fa {mapvar {}}} {
116        if {$mapvar ne ""} {upvar 1 $mapvar map}
117        op::concatenate $self $fa map
118    }
119
120    # ### ### ### ######### ######### #########
121    ## Internal data structures.
122
123    ## State information:
124    ## - Order    : Defined for all states, values provide creation order.
125    ## - Start    : Defined for states which are "start" (Input processing begins in).
126    ## - Final    : Defined for states which are "final" ("accept" input).
127    ## - Transinv : Inverse transitions. Per state the set of (state,sym)'s
128    ##              which have transitions into the state. Defined only for
129    ##              states which have inbound transitions.
130    ##
131    ## Transinv is maintained to make state deletion easier: Direct
132    ## access to the states and transitions which are inbound, for
133    ## their deletion.
134
135    variable order        ; # Map : State -> Order of creation
136    variable final        ; # Map : State -> .   Exists <=> Is a final State
137    variable start        ; # Map : State -> .   Exists <=> Is a start State
138    variable transinv     ; # Map : State -> {(State, Sym)}
139
140    ## Global information:
141    ## - Scount     : Counter for creation order of states.
142
143    variable scount     0  ; # Counter for orderering states.
144
145    ## Symbol information:
146    ## - Symbol : Defined for all symbols, values irrelevant.
147
148    variable symbol       ; # Map : Symbol -> . Exists = Symbol declared.
149
150    ## Transition data:
151    ## - TransN  : Dynamically created instance variables. Transition tables
152    ##             for single states. Defined only for states which have
153    ##             transitions.
154    ## - Transym : List of states having transitions on that symbol.
155
156    ## Transym is maintained for symbol deletion. Direct access to the transitions
157    ## we have to delete as well.
158
159    ## selfns::trans_$order(state) : Per state map : symbol -> list of destinations.
160    variable transym      ; # Map : Sym -> {State}
161
162    ## Derived information:
163    ## - Reach       : Cache for set of states reachable from start.
164    ## - Reachvalid  : Boolean flag. True iff the reach cache contains valid data
165    ## - Useful      : Cache for set of states able to reach final.
166    ## - Usefulvalid : Boolean flag. True iff the useful cache contains valid data
167    ## - Nondete     : Set of states which are non-deterministic, because they have
168    #                  epsilon-transitions.
169    # -  EC          : Cache of epsilon-closures
170
171    variable reach      {} ; # Set of states reachable from 'start'.
172    variable reachvalid 0  ; # Boolean flag, if 'reach' is valid.
173
174    variable useful      {} ; # Set of states able to reach 'final'.
175    variable usefulvalid 0  ; # Boolean flag, if 'useful' is valid.
176
177    variable nondete    {} ; # Set of non-deterministic states, by epsilon/non-epsilon.
178    variable nondets       ; # Per non-det state the set of symbols it is non-det in.
179
180    variable ec            ; # Cache of epsilon-closures for states.
181
182
183    # ### ### ### ######### ######### #########
184    ## Instance API Implementation.
185
186    constructor {args} {
187        set alen [llength $args]
188        if {($alen != 2) && ($alen != 0) && ($alen != 3)} {
189            return -code error "wrong#args: $self ?=|:=|<--|as|deserialize a'|fromRegex re ?over??"
190        }
191
192        array set order    {} ; set nondete     {}
193        array set start    {} ; set scount      0
194        array set final    {} ; set reach       {}
195        array set symbol   {} ; set reachvalid  0
196        array set transym  {} ; set useful      {}
197        array set transinv {} ; set usefulvalid 0
198        array set nondets  {}
199        array set ec       {}
200
201        if {$alen == 0} return
202
203        foreach {cmd object} $args break
204        switch -exact -- $cmd {
205            = - := - <-- - as {
206                if {$alen != 2} {
207                    return -code error "wrong#args: $self ?=|:=|<--|as|deserialize a'|fromRegex re ?over??"
208                }
209                $self = $object
210            }
211            deserialize {
212                if {$alen != 2} {
213                    return -code error "wrong#args: $self ?=|:=|<--|as|deserialize a'|fromRegex re ?over??"
214                }
215                # Object is actually a value, the deserialization to use.
216                $self deserialize $object
217            }
218            fromRegex {
219                # Object is actually a value, the regular expression to use.
220                if {$alen == 2} {
221                    $self fromRegex $object
222                } else {
223                    $self fromRegex $object [lindex $args 2]
224                }
225            }
226            default {
227                return -code error "bad assignment: $self ?=|:=|<--|as|deserialize a'|fromRegex re ?over??"
228            }
229        }
230        return
231    }
232
233    # destructor {}
234
235    # --- --- --- --------- --------- ---------
236
237    method = {b} {
238        $self deserialize [$b serialize]
239    }
240
241    method --> {b} {
242        $b deserialize [$self serialize]
243    }
244
245    # --- --- --- --------- --------- ---------
246
247    method serialize {} {
248        set ord {}
249        foreach {s n} [array get order] {
250            lappend ord [list $s $n]
251        }
252        set states {} ; # Dictionary
253        foreach item [lsort -index 1 -integer -increasing $ord] {
254            set s [lindex $item 0]
255            set sdata {}
256
257            # Dict data per state :
258
259            lappend sdata [info exists start($s)]
260            lappend sdata [info exists final($s)]
261
262            # Transitions from the state.
263
264            upvar #0 ${selfns}::trans_$order($s) jump
265
266            if {![info exists jump]} {
267                lappend sdata {}
268            } else {
269                lappend sdata [array get jump]
270            }
271
272            # ----------------------
273            lappend states $s $sdata
274        }
275
276        return [::list \
277                grammar::fa \
278                [array names symbol] \
279                $states \
280                ]
281    }
282
283    method deserialize {value} {
284        $self CheckSerialization $value st states acc tr newsymbols
285        $self clear
286
287        foreach s   $states     {set order($s)    [incr scount]}
288        foreach sym $newsymbols {set symbol($sym) .}
289        foreach s   $acc        {set final($s)    .}
290        foreach s   $st         {set start($s)    .}
291
292        foreach {sa sym se} $tr {$self Next $sa $sym $se}
293        return
294    }
295
296    method deserialize_merge {value} {
297        $self CheckSerialization $value st states acc tr newsymbols
298
299        foreach s   $states     {set order($s)    [incr scount]}
300        foreach sym $newsymbols {set symbol($sym) .}
301        foreach s   $acc        {set final($s)    .}
302        foreach s   $st         {set start($s)    .}
303
304        foreach {sa sym se} $tr {$self Next $sa $sym $se}
305        return
306    }
307
308    # --- --- --- --------- --------- ---------
309
310    method states {} {
311        return [array names order]
312    }
313
314    method state {cmd s args} {
315        switch -exact -- $cmd {
316            add {
317                set args [linsert $args 0 $s]
318                foreach s $args {
319                    if {[info exists order($s)]} {
320                        return -code error "State \"$s\" is already known"
321                    }
322                }
323                foreach s $args {set order($s) [incr scount]}
324                return
325            }
326            delete {
327                set args [linsert $args 0 $s]
328                $self StateCheckSet $args
329
330                foreach s $args {
331                    unset -nocomplain start($s)                   ; # Start/Initial indicator
332                    unset -nocomplain final($s)                   ; # Final/Accept indicator
333
334                    # Remove all inbound transitions.
335                    if {[info exists transinv($s)]} {
336                        set src $transinv($s)
337                        unset    transinv($s)
338
339                        foreach srcitem $src {
340                            struct::list assign $srcitem sin sym
341                            $self !Next $sin $sym $s
342                        }
343                    }
344
345                    # We remove transition data only after the inbound
346                    # ones. Otherwise we screw up the removal of
347                    # looping transitions. We have to consider the
348                    # backpointers to us in transinv as well.
349
350                    upvar #0  ${selfns}::trans_$order($s) jump
351                    if {[info exists jump]} {
352                        foreach sym [array names jump] {
353                            $self !Transym $s $sym
354                            foreach nexts $jump($sym) {
355                                $self !Transinv $s $sym $nexts
356                            }
357                        }
358
359                        unset ${selfns}::trans_$order($s) ; # Transitions from s
360                    }
361                    unset order($s)                               ; # State ordering
362
363                    # Removal of a state may break the automaton into
364                    # disconnected pieces. This means that the set of
365                    # reachable and useful states may change, and the
366                    # cache cannot be used from now on.
367
368                    $self InvalidateReach
369                    $self InvalidateUseful
370                }
371                return
372            }
373            rename {
374                set alen [llength $args]
375                if {($alen != 1)} {
376                    return -code error "wrong#args: [list $self] state rename s s'"
377                }
378                $self StateCheck $s
379                set snew [lindex $args 0]
380                if {[info exists order($snew)]} {
381                    return -code error "State \"$snew\" is already known"
382                }
383
384                set o $order($s)
385                unset order($s)                               ; # State ordering
386                set   order($snew) $o
387
388                # Start/Initial indicator
389                if {[info exists start($s)]} {
390                    set   start($snew) $start($s)
391                    unset start($s)
392                }
393                # Final/Accept indicator
394                if {[info exists final($s)]} {
395                    set   final($snew) $final($s)
396                    unset final($s)
397                }
398                # Update all inbound transitions.
399                if {[info exists transinv($s)]} {
400                    set   transinv($snew) $transinv($s)
401                    unset transinv($s)
402
403                    # We have to perform a bit more here. We have to
404                    # go through the inbound transitions and chane the
405                    # listed destination state to the new name.
406
407                    foreach srcitem $transinv($snew) {
408                        struct::list assign $srcitem sin sym
409                        upvar #0 ${selfns}::trans_$order($sin) jump
410                        upvar 0 jump($sym) destinations
411                        set pos [lsearch -exact $destinations $s]
412                        set destinations [lreplace $destinations $pos $pos $snew]
413                    }
414                }
415
416                # Another place to change are the back pointers from
417                # all the states we have transitions to, i.e. transinv
418                # for all outbound states.
419
420                upvar #0 ${selfns}::trans_$o jump
421                if {[info exists jump]} {
422                    foreach sym [array names jump] {
423                        foreach sout $jump($sym) {
424                            upvar 0 transinv($sout) backpointer
425                            set pos [lsearch -exact $backpointer [list $s $sym]]
426                            set backpointer [lreplace $backpointer $pos $pos [list $snew $sym]]
427                        }
428
429                        # And also to update: Transym information for the symbol.
430                        upvar 0 transym($sym) users
431                        set pos [lsearch -exact $users $s]
432                        set users [lreplace $users $pos $pos $snew]
433                    }
434                }
435
436                # Changing the name of a state does not change the
437                # reachables / useful states per se. We just may have
438                # to replace the name in the caches as well.
439
440                # - Invalidation will do the same, at the expense of a
441                # - larger computation later.
442
443                $self InvalidateReach
444                $self InvalidateUseful
445            }
446            exists {
447                set alen [llength $args]
448                if {$alen != 0} {
449                    return -code error "wrong#args: [list $self] state exists s"
450                }
451                return [info exists order($s)]
452            }
453            default {
454                return -code error "Expected add, exists, delete, or rename, got \"$cmd\""
455            }
456        }
457        return
458    }
459
460    # --- --- --- --------- --------- ---------
461
462    method startstates {} {
463        return [array names start]
464    }
465
466    method start? {s} {
467        $self StateCheck $s
468        return [info exists start($s)]
469    }
470
471    method start?set {states} {
472        $self StateCheckSet $states
473        foreach s $states {
474            if {[info exists start($s)]} {return 1}
475        }
476        return 0
477    }
478
479    method start {cmd args} {
480        # Note: Adding or removing start states does not change
481        # usefulness, only reachability
482        switch -exact -- $cmd {
483            add {
484                if {[llength $args] < 1} {
485                    return -code error "wrong#args: start add state ..."
486                }
487                $self StateCheckSet $args
488                foreach s $args {set start($s) .}
489                $self InvalidateReach
490            }
491            set {
492                if {[llength $args] != 1} {
493                    return -code error "wrong#args: start set states"
494                }
495                set states [lindex $args 0]
496                $self StateCheckSet $states
497                array unset start
498                foreach s $states {set start($s) .}
499                $self InvalidateReach
500            }
501            remove {
502                if {[llength $args] < 1} {
503                    return -code error "wrong#args: start remove state ..."
504                }
505                $self StateCheckSet $args
506                foreach s $args {
507                    unset -nocomplain start($s)
508                }
509                $self InvalidateReach
510            }
511            clear {
512                if {[llength $args] != 0} {
513                    return -code error "wrong#args: start clear"
514                }
515                array unset start
516                $self InvalidateReach
517            }
518            default {
519                return -code error "Expected add, clear, remove, or set, got \"$cmd\""
520            }
521        }
522        return
523    }
524
525    # --- --- --- --------- --------- ---------
526
527    method finalstates {} {
528        return [array names final]
529    }
530
531    method final? {s} {
532        $self StateCheck $s
533        return [info exists final($s)]
534    }
535
536    method final?set {states} {
537        $self StateCheckSet $states
538        foreach s $states {
539            if {[info exists final($s)]} {return 1}
540        }
541        return 0
542    }
543
544    method final {cmd args} {
545        # Note: Adding or removing start states does not change
546        # reachability, only usefulness
547        switch -exact -- $cmd {
548            add {
549                if {[llength $args] < 1} {
550                    return -code error "wrong#args: final add state ..."
551                }
552                $self StateCheckSet $args
553                foreach s $args {set final($s) .}
554                $self InvalidateUseful
555            }
556            set {
557                if {[llength $args] != 1} {
558                    return -code error "wrong#args: final set states"
559                }
560                set states [lindex $args 0]
561                $self StateCheckSet $states
562                array unset final
563                foreach s $states {set final($s) .}
564                $self InvalidateReach
565            }
566            remove {
567                if {[llength $args] < 1} {
568                    return -code error "wrong#args: final remove state ..."
569                }
570                $self StateCheckSet $args
571                foreach s $args {
572                    unset -nocomplain final($s)
573                }
574                $self InvalidateUseful
575            }
576            clear {
577                if {[llength $args] != 0} {
578                    return -code error "wrong#args: final clear"
579                }
580                array unset final
581                $self InvalidateReach
582            }
583            default {
584                return -code error "Expected add, clear, remove, or set, got \"$cmd\""
585            }
586        }
587        return
588    }
589
590    # --- --- --- --------- --------- ---------
591
592    method symbols {} {
593        return [array names symbol]
594    }
595
596    method symbols@ {s {t {}}} {
597        $self StateCheck $s
598        if {$t ne ""} { $self StateCheck $t}
599        upvar #0 ${selfns}::trans_$order($s) jump
600        if {![info exists jump]} {return {}}
601        if {$t eq ""} {
602            # No destination, all symbols.
603            return [array names jump]
604        }
605        # Specific destination, locate the symbols going there.
606        set result {}
607        foreach sym [array names jump] {
608            if {[lsearch -exact $jump($sym) $t] < 0} continue
609            lappend result $sym
610        }
611        return [lsort -uniq $result]
612    }
613
614    method symbols@set {states} {
615        # Union (fa symbol@ s, f.a. s in states)
616
617        $self StateCheckSet $states
618        set result {}
619        foreach s $states {
620            upvar #0 ${selfns}::trans_$order($s) jump
621            if {![info exists jump]} continue
622            foreach sym [array names jump] {
623                lappend result $sym
624            }
625        }
626        return [lsort -uniq $result]
627    }
628
629    method symbol {cmd sym args} {
630        switch -exact -- $cmd {
631            add {
632                set args [linsert $args 0 $sym]
633                foreach sym $args {
634                    if {$sym eq ""} {
635                        return -code error "Cannot add illegal empty symbol \"\""
636                    }
637                    if {[info exists symbol($sym)]} {
638                        return -code error "Symbol \"$sym\" is already known"
639                    }
640                }
641                foreach sym $args {set symbol($sym) .}
642            }
643            delete {
644                set args [linsert $args 0 $sym]
645                $self SymbolCheckSetNE $args
646                foreach sym $args {
647                    unset symbol($sym)
648
649                    # Delete all transitions using the removed symbol.
650
651                    if {[info exists transym($sym)]} {
652                        foreach s $transym($sym) {
653                            $self !Next $s $sym
654                        }
655                    }
656                }
657            }
658            rename {
659                set alen [llength $args]
660                if {$alen != 1} {
661                    return -code error "wrong#args: [list $self] symbol rename sym newsym"
662                }
663                $self SymbolCheckNE $sym
664                set newsym [lindex $args 0]
665
666                if {$newsym eq ""} {
667                    return -code error "Cannot add illegal empty symbol \"\""
668                }
669                if {[info exists symbol($newsym)]} {
670                    return -code error "Symbol \"$newsym\" is already known"
671                }
672
673                unset symbol($sym)
674                set symbol($newsym) .
675
676                if {[info exists transym($sym)]} {
677                    set   transym($newsym) [set states $transym($sym)]
678                    unset transym($sym)
679
680                    foreach s $states {
681                        # Update the jump tables for each of the states
682                        # using this symbol, and the reverse tables as
683                        # well.
684
685                        upvar #0 ${selfns}::trans_$order($s) jump
686                        set   jump($newsym) [set destinations $jump($sym)]
687                        unset jump($sym)
688
689                        foreach sd $destinations {
690                            upvar 0 transinv($sd) backpointer
691                            set pos [lsearch -exact $backpointer [list $s $sym]]
692                            set backpointer [lreplace $backpointer $pos $pos [list $s $newsym]]
693                        }
694                    }
695                }
696            }
697            exists {
698                set alen [llength $args]
699                if {$alen != 0} {
700                    return -code error "wrong#args: [list $self] symbol exists sym"
701                }
702                return [info exists symbol($sym)]
703            }
704            default {
705                return -code error "Expected add, delete, exists, or rename, got \"$cmd\""
706            }
707        }
708        return
709    }
710
711    # --- --- --- --------- --------- ---------
712
713    method next {s sym args} {
714        ## Split into checking and functionality ...
715
716        set alen [llength $args]
717        if {($alen != 2) && ($alen != 0)} {
718            return -code error "wrong#args: [list $self] next s sym ?--> s'?"
719        }
720        $self StateCheck  $s
721        $self SymbolCheck $sym
722
723        if {($alen == 2) && [set cmd [lindex $args 0]] ne "-->"} {
724            return -code error "Expected -->, got \"$cmd\""
725        }
726
727        if {$alen == 0} {
728            # Query transition table.
729            upvar #0 ${selfns}::trans_$order($s) jump
730            if {![info exists jump($sym)]} {return {}}
731            return $jump($sym)
732        }
733
734        set nexts [lindex $args 1]
735        $self StateCheck $nexts
736
737        upvar #0 ${selfns}::trans_$order($s) jump
738        if {[info exists jump($sym)] && [struct::set contains $jump($sym) $nexts]} {
739            return -code error "Transition \"($s, ($sym)) --> $nexts\" is already known"
740        }
741
742        $self Next $s $sym $nexts
743        return
744    }
745
746    method !next {s sym args} {
747        set alen [llength $args]
748        if {($alen != 2) && ($alen != 0)} {
749            return -code error "wrong#args: [list $self] !next s sym ?--> s'?"
750        }
751        $self StateCheck  $s
752        $self SymbolCheck $sym
753
754        if {$alen == 2} {
755            if {[lindex $args 0] ne "-->"} {
756                return -code error "Expected -->, got \"[lindex $args 0]\""
757            }
758            set nexts [lindex $args 1]
759            $self StateCheck $nexts
760            $self !Next $s $sym $nexts
761        } else {
762            $self !Next $s $sym
763        }
764    }
765
766    method nextset {states sym} {
767        $self SymbolCheck   $sym
768        $self StateCheckSet $states
769
770        set result {}
771        foreach s $states {
772            upvar #0 ${selfns}::trans_$order($s) jump
773            if {![info exists jump($sym)]} continue
774            struct::set add result $jump($sym)
775        }
776        return $result
777    }
778
779    # --- --- --- --------- --------- ---------
780
781    method is {cmd} {
782        switch -exact -- $cmd {
783            complete {
784                # The FA is complete if Trans(State, Sym) != {} for all
785                # states and symbols (Not counting epsilon transitions).
786                # Without symbols the FA is deemed complete. Note:
787                # States with epsilon transitions can use symbols
788                # indirectly! Need their closures for exact
789                # computation.
790
791                set nsymbols [llength [array names symbol]]
792                if {$nsymbols == 0} {return 1}
793                foreach s [array names order] {
794                    upvar #0 ${selfns}::trans_$order($s) jump
795                    if {![info exists jump]} {return 0}
796                    set njsym [array size jump]
797                    if {[info exists jump()]} {
798                        set  njsym [llength [$self symbols@set [$self epsilon_closure $s]]]
799                        incr njsym -1
800                    }
801                    if {$njsym != $nsymbols}  {return 0}
802                }
803                return 1
804            }
805            deterministic {
806                # The FA is deterministic if it has on start state, no
807                # epsilon transitions, and the transition function is
808                # State x Symbol -> State, and not
809                # State x Symbol -> P(State).
810
811                return [expr {
812                    ([array size start] == 1) &&
813                    ![llength $nondete] &&
814                    ![array size nondets]
815                }] ;#{}
816            }
817            epsilon-free {
818                # FA is epsion-free if there are no states having epsilon transitions.
819                return [expr {![llength $nondete]}]
820            }
821            useful {
822                # The FA is useful if and only if we have states and
823                # all states are reachable and useful.
824
825                set states [$self states]
826                return [expr {
827                    [struct::set size $states] &&
828                    [struct::set equal $states [$self reachable_states]] &&
829                    [struct::set equal $states [$self useful_states]]
830                }] ;# {}
831            }
832        }
833        return -code error "Expected complete, deterministic, epsilon-free, or useful, got \"$cmd\""
834    }
835
836    # --- --- --- --------- --------- ---------
837
838    method reachable_states {} {
839        if {$reachvalid} {return $reach}
840        if {![array size start]} {
841            set reach {}
842        } else {
843            # Basic algorithm like for epsilon_closure, except that we
844            # process all transitions, not only epsilons, and that
845            # the initial state is fixed to start.
846
847            set reach   [array names start]
848            set pending $reach
849            array set visited {}
850            while {[llength $pending]} {
851                set s [struct::list shift pending]
852                if {[info exists visited($s)]} continue
853                set visited($s) .
854                upvar #0 ${selfns}::trans_$order($s) jump
855                if {![info exists jump]} continue
856                if {![array size  jump]} continue
857                foreach sym [array names jump] {
858                    struct::set add reach   $jump($sym)
859                    struct::set add pending $jump($sym)
860                }
861            }
862        }
863        set reachvalid 1
864        return $reach
865    }
866
867    method unreachable_states {} {
868        # unreachable = states - reachables
869        return [struct::set difference \
870                [$self states] [$self reachable_states]]
871    }
872
873    method reachable {s} {
874        $self StateCheck $s
875        return [struct::set contains [$self reachable_states] $s]
876    }
877
878    # --- --- --- --------- --------- ---------
879
880    method useful_states {} {
881        if {$usefulvalid} {return $useful}
882
883        # A state is useful if a final state
884        # can be reached from it.
885
886        if {![array size final]} {
887            set useful {}
888        } else {
889            # Basic algorithm like for epsilon_closure, except that we
890            # process all transitions, not only epsilons, and that
891            # the initial set of states is fixed to final.
892
893            set useful      [array names final]
894            array set known [array get final]
895            set pending $useful
896            array set visited {}
897            while {[llength $pending]} {
898                set s [struct::list shift pending]
899                if {[info exists visited($s)]} continue
900                set visited($s) .
901
902                # All predecessors are useful, and have to be visited as well.
903                # We get the predecessors from the transinv structure.
904
905                if {![info exists transinv($s)]} continue
906                foreach before $transinv($s) {
907                    set before [lindex $before 0]
908                    if {[info exists visited($before)]} continue
909                    lappend pending $before
910                    if {[info exists known($before)]} continue
911                    lappend useful $before
912                    set known($before) .
913                }
914            }
915        }
916        set usefulvalid 1
917        return $useful
918    }
919
920    method unuseful_states {} {
921        # unuseful = states - useful
922        return [struct::set difference \
923                [$self states] [$self useful_states]]
924    }
925
926    method useful {s} {
927        $self StateCheck $s
928        return [struct::set contains [$self useful_states] $s]
929    }
930
931    # --- --- --- --------- --------- ---------
932
933    method epsilon_closure {s} {
934        # Iterative graph traversal. Keeps a set of states to look at,
935        # and adds to them everything it can reach from the current
936        # state via epsilon-transitions. Loops are handled through the
937        # visited array to weed out all the states already processed.
938
939        $self StateCheck $s
940
941        # Prefer cached information
942        if {[info exists ec($s)]} {
943            return $ec($s)
944        }
945
946        set closure [list $s]
947        set pending [list $s]
948        array set visited {}
949        while {[llength $pending]} {
950            set t [struct::list shift pending]
951            if {[info exists visited($t)]} continue
952            set visited($t) .
953            upvar #0 ${selfns}::trans_$order($t) jump
954            if {![info exists jump()]} continue
955            struct::set add closure $jump()
956            struct::set add pending $jump()
957        }
958        set ec($s) $closure
959        return $closure
960    }
961
962    # --- --- --- --------- --------- ---------
963
964    method clear {} {
965        array unset order    ; set nondete     {}
966        array unset start    ; set scount      0
967        array unset final    ; set reach       {}
968        array unset symbol   ; set reachvalid  0
969        array unset transym  ; set useful      {}
970        array unset transinv ; set usefulvalid 0
971        array unset nondets
972        array unset ec
973
974        # Locate all 'trans_' arrays and remove them as well.
975
976        foreach v [info vars ${selfns}::trans_*] {
977            unset $v
978        }
979        return
980    }
981
982    # ### ### ### ######### ######### #########
983    ## Instance Internals.
984
985    method StateCheck {s} {
986        if {![info exists order($s)]} {
987            return -code error "Illegal state \"$s\""
988        }
989    }
990
991    method StateCheckSet {states} {
992        foreach s $states {
993            if {![info exists order($s)]} {
994                return -code error "Illegal state \"$s\""
995            }
996        }
997    }
998
999    method SymbolCheck {sym} {
1000        if {$sym eq ""} return
1001        if {![info exists symbol($sym)]} {
1002            return -code error "Illegal symbol \"$sym\""
1003        }
1004    }
1005
1006    method SymbolCheckNE {sym} {
1007        if {($sym eq "") || ![info exists symbol($sym)]} {
1008            return -code error "Illegal symbol \"$sym\""
1009        }
1010    }
1011
1012    if 0 {
1013        # Unused. Activate when needed.
1014        method SymbolCheckSet {symbols} {
1015            foreach sym $symbols {
1016                if {$sym eq ""} continue
1017                if {![info exists symbol($sym)]} {
1018                    return -code error "Illegal symbol \"$sym\""
1019                }
1020            }
1021        }
1022    }
1023
1024    method SymbolCheckSetNE {symbols} {
1025        foreach sym $symbols {
1026            if {($sym eq "") || ![info exists symbol($sym)]} {
1027                return -code error "Illegal symbol \"$sym\""
1028            }
1029        }
1030    }
1031
1032    method Next {s sym nexts} {
1033        # Modify transition table. May update the set of
1034        # non-deterministic states. Invalidates reachable
1035        # cache, as states may become reachable. Updates
1036        # the transym and transinv mappings.
1037
1038        upvar #0 ${selfns}::trans_$order($s) jump
1039
1040        $self InvalidateReach
1041        $self InvalidateUseful
1042        # Clear closure cache when epsilons change.
1043        if {$sym eq ""} {array unset ec}
1044
1045        if {[info exists transym($sym)]} {
1046            struct::set include transym($sym) $s
1047        } else {
1048            set transym($sym) [list $s]
1049        }
1050
1051        if {[info exists transinv($nexts)]} {
1052            struct::set include transinv($nexts) [list $s $sym]
1053        } else {
1054            set transinv($nexts) [list [list $s $sym]]
1055        }
1056
1057        if {![info exists jump($sym)]} {
1058            set jump($sym) [list $nexts]
1059        } else {
1060            struct::set include jump($sym) $nexts
1061        }
1062        $self NonDeterministic $s $sym
1063        return
1064    }
1065
1066    method !Next {s sym args} {
1067        upvar #0 ${selfns}::trans_$order($s) jump
1068        # Anything to do at all ?
1069        if {![info exists jump($sym)]} return
1070        $self InvalidateReach
1071        $self InvalidateUseful
1072        # Clear closure cache when epsilons change.
1073        if {$sym eq ""} {array unset ec}
1074
1075        if {![llength $args]} {
1076            # Unset all transitions for (s, sym)
1077            # Update transym and transinv mappings as well, if existing.
1078
1079            $self !Transym $s $sym
1080            foreach nexts $jump($sym) {
1081                $self !Transinv $s $sym $nexts
1082            }
1083
1084            unset jump($sym)
1085        } else {
1086            # Remove the single transition (s, sym) -> nexts
1087            set nexts [lindex $args 0]
1088
1089            struct::set exclude jump($sym) $nexts
1090            $self !Transinv $s $sym $nexts
1091
1092            if {![struct::set size $jump($sym)]} {
1093                $self !Transym $s $sym
1094                unset jump($sym)
1095                if {![array size jump]} {
1096                    unset jump
1097                }
1098            }
1099        }
1100
1101        $self NonDeterministic $s $sym
1102        return
1103    }
1104
1105    method !Transym {s sym} {
1106        struct::set exclude transym($sym) $s
1107        if {![struct::set size $transym($sym)]} {
1108            unset transym($sym)
1109        }
1110    }
1111
1112    method !Transinv {s sym nexts} {
1113        if {[info exists transinv($nexts)]} {
1114            struct::set exclude transinv($nexts) [list $s $sym]
1115            if {![struct::set size $transinv($nexts)]} {
1116                unset transinv($nexts)
1117            }
1118        }
1119    }
1120
1121    method InvalidateReach {} {
1122        set reachvalid 0
1123        set reach      {}
1124        return
1125    }
1126
1127    method InvalidateUseful {} {
1128        set usefulvalid 0
1129        set useful     {}
1130        return
1131    }
1132
1133    method NonDeterministic {s sym} {
1134        upvar #0 ${selfns}::trans_$order($s) jump
1135
1136        # Epsilon rule, whole state check. Epslion present <=> Not a DFA.
1137
1138        if {[info exists jump()]} {
1139            struct::set include nondete $s
1140        } else {
1141            struct::set exclude nondete $s
1142        }
1143
1144        # Non-determinism over a symbol.
1145
1146        upvar #0 ${selfns}::trans_$order($s) jump
1147
1148        if {[info exists jump($sym)] && [struct::set size $jump($sym)] > 1} {
1149            if {![info exists nondets($s)]} {
1150                set nondets($s) [list $sym]
1151            } else {
1152                struct::set include nondets($s) $sym
1153            }
1154            return
1155        } else {
1156            if {![info exists nondets($s)]} return
1157            struct::set exclude nondets($s) $sym
1158            if {![struct::set size $nondets($s)]} {
1159                unset nondets($s)
1160            }
1161        }
1162        return
1163    }
1164
1165    method CheckSerialization {value startst states acc trans syms} {
1166        # value is list/3 ('grammar::fa' symbols states)
1167        # !("" in symbols)
1168        # states is ordered dict (key is state, value is statedata)
1169        # statedata is list/3 (start final trans|"")
1170        # start is boolean
1171        # final is boolean
1172        # trans is dict (key in symbols, value is destinations)
1173        # destinations is set of states
1174
1175        upvar 1 $startst startstates \
1176                $states  sts \
1177                $acc     a \
1178                $trans   t \
1179                $syms    symbols
1180
1181        set prefix "error in serialization:"
1182        if {[llength $value] != 3} {
1183            return -code error "$prefix list length not 3"
1184        }
1185
1186        struct::list assign $value   stype symbols statedata
1187
1188        if {$stype ne "grammar::fa"} {
1189            return -code error "$prefix unknown type \"$stype\""
1190        }
1191        if {[struct::set contains $symbols ""]} {
1192            return -code error "$prefix empty symbol is not legal"
1193        }
1194
1195        if {[llength $statedata] % 2 == 1} {
1196            return -code error "$prefix state data is not a dictionary"
1197        }
1198        array set _states $statedata
1199        if {[llength $statedata] != (2*[array size _states])} {
1200            return -code error "$prefix state data contains duplicate states"
1201        }
1202        set startstates {}
1203        set sts {}
1204        set p   {}
1205        set a   {}
1206        set e   {}
1207        set l   {}
1208        set m   {}
1209        set t   {}
1210        foreach {k v} $statedata {
1211            lappend sts $k
1212            if {[llength $v] != 3} {
1213                return -code error "$prefix state list length not 3"
1214            }
1215
1216            struct::list assign $v begin accept trans
1217
1218            if {![string is boolean -strict $begin]} {
1219                return -code error "$prefix expected boolean for start, got \"$begin\""
1220            }
1221            if {$begin} {lappend startstates $k}
1222            if {![string is boolean -strict $accept]} {
1223                return -code error "$prefix expected boolean for final, got \"$accept\""
1224            }
1225            if {$accept} {lappend a $k}
1226
1227            if {[llength $trans] % 2 == 1} {
1228                return -code error "$prefix transition data is not a dictionary"
1229            }
1230            array set _trans $trans
1231            if {[llength $trans] != (2*[array size _trans])} {
1232                return -code error "$prefix transition data contains duplicate symbols"
1233            }
1234            unset _trans
1235
1236            foreach {sym destinations} $trans {
1237                # destinations = list of state
1238                if {($sym ne "") && ![struct::set contains $symbols $sym]} {
1239                    return -code error "$prefix illegal symbol \"$sym\" in transition"
1240                }
1241                foreach dest $destinations {
1242                    if {![info exists _states($dest)]} {
1243                        return -code error "$prefix illegal destination state \"$dest\""
1244                    }
1245                    lappend t $k $sym $dest
1246                }
1247            }
1248        }
1249        return
1250    }
1251
1252    # ### ### ### ######### ######### #########
1253    ## Type API implementation.
1254
1255    # ### ### ### ######### ######### #########
1256    ## Type Internals.
1257
1258    # ### ### ### ######### ######### #########
1259}
1260
1261# ### ### ### ######### ######### #########
1262## Initialization. Specify the container constructor command to use by
1263## the operations package.
1264
1265::grammar::fa::op::constructor ::grammar::fa
1266
1267# ### ### ### ######### ######### #########
1268## Package Management
1269
1270package provide grammar::fa 0.3
Note: See TracBrowser for help on using the repository browser.