| 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 | |
|---|
| 16 | package require grammar::fa::op ; # Heavy FA operations. |
|---|
| 17 | package require snit ; # OO system in use |
|---|
| 18 | package require struct::list ; # Extended list operations. |
|---|
| 19 | package require struct::set ; # Extended set operations. |
|---|
| 20 | |
|---|
| 21 | # ### ### ### ######### ######### ######### |
|---|
| 22 | ## Implementation |
|---|
| 23 | |
|---|
| 24 | snit::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 | |
|---|
| 1270 | package provide grammar::fa 0.3 |
|---|