├── LICENSE.md ├── README.md ├── build.xml └── src └── de └── dominicscheurer └── fsautils ├── Conversions.scala ├── DFA.scala ├── FSA_DSL.scala ├── FSM.scala ├── Helpers.scala ├── NFA.scala ├── RegularExpressions.scala ├── Relations.scala ├── Types.scala └── test └── Test.scala /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2019 Dominic Steinhöfel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FSAUtils 2 | ======== 3 | 4 | Models of finite automata (DFA, NFA) with support of common operations and easily readable creation of objects. 5 | 6 | The main goals of this project are: 7 | 8 | * Support of easily readable definitions of finite automata (FA) and regular expressions 9 | * Support of important basic operations on FA 10 | * Adherence to the following coding guidelines aiming to assure correctness: 11 | * Simple and easily understandable code 12 | * Mostly adherence to the functional programming paradigm 13 | * Functional parts of the code (the core) closely follows abstract mathematical definitions of the respective operations 14 | 15 | Features supported so far 16 | ------------------------- 17 | 18 | * Creation of Deterministic Finite Automata (DFA) 19 | * Creation of Nondeterministic Finite Automata (NFA) 20 | * Determinization of NFA 21 | * Creation of Regular Expressions (RE) 22 | * Checking for acceptance of a word by an automaton 23 | * Concatenation, Star, Union, Intersection, Complement for DFA/NFA 24 | * Checking DFA/NFA for equivalence 25 | * Implicit conversion of DFA to NFA 26 | * Pretty-printing toString methods for DFA/NFA 27 | * Determination of the language (RE) of a DFA/NFA 28 | * Conversion of RE to NFA (i.e. also checking of equivalence of DFA/NFA with RE) 29 | * Minimization of DFA 30 | * (De-)Serialization to and from XML 31 | 32 | Get Started (1.1 Beta Version) 33 | ------------------------------ 34 | 35 | **Prerequisites:** You need to have Scala and the JVM installed. FSAUtils 36 | has been tested with Scala 2.11.2 and Java 1.7. Furthermore, the environment 37 | variable `$SCALA_HOME` has to be correctly set to the path where Scala resides. 38 | 39 | The following steps should work for a Linux system. 40 | 41 | 1. Download the archive: 42 | 43 | ```bash 44 | wget https://github.com/rindPHI/FSAUtils/archive/v1.1-beta.tar.gz -O FSAUtils-1.1-beta.tar.gz 45 | ``` 46 | 47 | 2. Extract it: 48 | 49 | ```bash 50 | tar xzf FSAUtils-1.1-beta.tar.gz 51 | ``` 52 | 53 | 2. Build it: 54 | 55 | ```bash 56 | cd FSAUtils-1.1-beta/ 57 | ant 58 | ``` 59 | 60 | As the result, you find a file "FSAUtils.jar" in the directory `lib/` 61 | which you need to add to the classpath of scalac and scala in order 62 | to compile / run your objects that make use of FSAUtils. 63 | 64 | 3. In your Scala files, add the import 65 | 66 | ```scala 67 | import de.dominicscheurer.fsautils._ 68 | ``` 69 | 70 | and, if you want to use the FSA domain specific language 71 | for better readability, let your object extend `FSA_DSL`: 72 | 73 | ```scala 74 | object MyObject extends FSA_DSL { 75 | ``` 76 | 77 | 4. Compile your scala object: 78 | 79 | ```bash 80 | scalac -classpath "/path/to/FSAUtils.jar" YourObject.scala 81 | ``` 82 | 83 | 5. ...and run it: 84 | 85 | ```bash 86 | scala -classpath ".:/path/to/FSAUtils.jar" YourObject 87 | ``` 88 | 89 | An example file like mentioned in points 3. to 5. could have, for instance, 90 | the following content: 91 | 92 | ```scala 93 | import de.dominicscheurer.fsautils._ 94 | 95 | object FSAUtilsTest extends FSA_DSL { 96 | 97 | def main(args: Array[String]) { 98 | val myDFA = 99 | dfa ('Z, 'S, 'q0, 'd, 'A) where 100 | 'Z ==> Set('a, 'b) and 101 | 'S ==> Set(0, 1) and 102 | 'q0 ==> 0 and 103 | 'A ==> Set(0) and 104 | 'd ==> Delta( 105 | (0, 'a) -> 0, 106 | (0, 'b) -> 1, 107 | (1, 'a) -> 0, 108 | (1, 'b) -> 1 109 | )| 110 | 111 | print("DFA accepts aaab: ") 112 | println(myDFA accepts "aaab") 113 | print("DFA accepts aaaba: ") 114 | println(myDFA accepts "aaaba") 115 | } 116 | 117 | } 118 | ``` 119 | 120 | If you wish to run the included unit tests, execute 121 | 122 | ```bash 123 | ant runTests 124 | ``` 125 | 126 | in the `FSAUtils-1.1-beta` directory. 127 | 128 | Examples 129 | -------- 130 | 131 | Please consider the file Test.scala to see some working applied examples. 132 | 133 | ### Creation of a DFA 134 | 135 | ```scala 136 | val myDFA = 137 | dfa ('Z, 'S, 'q0, 'd, 'A) where 138 | 'Z ==> Set('a, 'b) and 139 | 'S ==> Set(0, 1) and 140 | 'q0 ==> 0 and 141 | 'A ==> Set(0) and 142 | 'd ==> Delta( 143 | (0, 'a) -> 0, 144 | (0, 'b) -> 1, 145 | (1, 'a) -> 0, 146 | (1, 'b) -> 1 147 | )| 148 | 149 | print("DFA accepts aaab: ") 150 | println(myDFA accepts "aaab") 151 | ``` 152 | 153 | ### Creation of an NFA 154 | 155 | ```scala 156 | val myNFA = 157 | nfa ('Z, 'S, 'q0, 'd, 'A) where 158 | 'Z ==> Set('a, 'b) and 159 | 'S ==> Set(0, 1) and 160 | 'q0 ==> 0 and 161 | 'A ==> Set(1) and 162 | 'd ==> Delta( 163 | (0, 'a) -> Set(0, 1), 164 | (0, 'b) -> Set(0) 165 | )|| 166 | 167 | print("NFA accepts aaab: ") 168 | println(myNFA accepts "aaab") 169 | ``` 170 | 171 | ### Star Operation for NFA 172 | 173 | ```scala 174 | println((myNFA*) accepts "aaabaaab") 175 | ``` 176 | 177 | ### Determinization for NFA 178 | 179 | ```scala 180 | println((myNFA toDFA) accepts "aaab") 181 | ``` 182 | 183 | ### Complement for DFA 184 | 185 | ```scala 186 | println((!myDFA) accepts "aaab") 187 | ``` 188 | 189 | ### Concatenation 190 | 191 | ```scala 192 | println(myNFA ++ myNFA2); 193 | ``` 194 | 195 | ### Pretty Printing 196 | 197 | `println(myNFA toDFA)` yields: 198 | 199 | ``` 200 | DFA (Z,S,q0,d,A) with 201 | | Z = {a,b} 202 | | S = {{},{0},{1},{0,1}} 203 | | q0 = {0} 204 | | A = {{1},{0,1}} 205 | | d = { 206 | | ({},a) => {}, 207 | | ({},b) => {}, 208 | | ({0},a) => {0,1}, 209 | | ({0},b) => {0}, 210 | | ({1},a) => {}, 211 | | ({1},b) => {}, 212 | | ({0,1},a) => {0,1}, 213 | | ({0,1},b) => {0} 214 | | } 215 | ``` 216 | 217 | ### Creation of RE 218 | 219 | ```scala 220 | def myRegExp = (('a*) + ('b & ('b*) & 'a))* : RE 221 | ``` 222 | 223 | License 224 | ------- 225 | 226 | ### MIT License 227 | 228 | Copyright (c) 2014-2019 Dominic Steinhöfel 229 | 230 | Permission is hereby granted, free of charge, to any person obtaining a copy 231 | of this software and associated documentation files (the "Software"), to deal 232 | in the Software without restriction, including without limitation the rights 233 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 234 | copies of the Software, and to permit persons to whom the Software is 235 | furnished to do so, subject to the following conditions: 236 | 237 | The above copyright notice and this permission notice shall be included in all 238 | copies or substantial portions of the Software. 239 | 240 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 241 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 242 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 243 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 244 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 245 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 246 | SOFTWARE. 247 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/Conversions.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Types._ 3 | import RegularExpressions._ 4 | 5 | object Conversions { 6 | implicit def bool2int(b: Boolean) = if (b) 1 else 0 7 | 8 | implicit def DFAFromTuple( 9 | t: (Set[Letter], Set[State], State, ((State, Letter) => State), Set[State])): DFA = { 10 | new DFA(t._1, t._2, t._3, t._4, t._5) 11 | } 12 | 13 | implicit def NFAFromTuple( 14 | t: (Set[Letter], Set[State], State, ((State, Letter) => Set[State]), Set[State])): NFA = { 15 | new NFA(t._1, t._2, t._3, t._4, t._5) 16 | } 17 | 18 | implicit def DFAtoNFA[T](dfa: DFA): NFA = 19 | (dfa.alphabet, dfa.states, dfa.initialState, 20 | (state: State, letter: Letter) => { 21 | try { 22 | Set(dfa.delta(state, letter)) 23 | } catch { 24 | case _: Throwable => Set(): Set[State] 25 | } 26 | }, 27 | dfa.accepting) 28 | 29 | implicit def REFromLetter( 30 | letter: Letter): RE = { 31 | L(letter) 32 | } 33 | 34 | // The following could be dangerous for short 35 | // regular expressions (conflicting implicits): 36 | implicit def SetFromStates( 37 | state: State): Set[State] = 38 | Set(state) 39 | 40 | implicit def SetFromStates( 41 | states: (State, State)) = 42 | Set(states._1, states._2) 43 | 44 | implicit def SetFromStates( 45 | states: (State, State, State)) = 46 | Set(states._1, states._2, states._3) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/DFA.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Types._ 3 | import Conversions._ 4 | import Helpers._ 5 | import RegularExpressions._ 6 | import Relations._ 7 | 8 | import scala.annotation.tailrec 9 | import scala.xml.Node 10 | 11 | import Predef.{ any2stringadd => _, _ } 12 | 13 | class DFA( 14 | var alphabet: Set[Letter], 15 | var states: Set[State], 16 | var initialState: State, 17 | var delta: ((State, Letter) => State), 18 | var accepting: Set[State]) extends FSM { 19 | 20 | require(states contains initialState) 21 | require(accepting subsetOf states) 22 | 23 | def accepts(word: String): Boolean = 24 | accepts(for (x <- word.toList) yield Symbol(x toString)) 25 | 26 | def accepts(word: Word): Boolean = accepts(word, initialState) 27 | 28 | def accepts(word: Word, fromState: State): Boolean = word match { 29 | case Nil => accepting contains fromState 30 | case letter :: rest => 31 | (alphabet contains letter) && 32 | accepts(rest, delta(fromState, letter)) 33 | } 34 | 35 | def extendAlphabet(newLetters: Set[Letter]): NFA = { 36 | (alphabet ++ newLetters, states, initialState, delta, accepting): DFA 37 | } 38 | 39 | def unary_! : DFA = new DFA(alphabet, states, initialState, delta, states -- accepting) 40 | 41 | def * : NFA = (this: NFA)* 42 | 43 | def ++(other: NFA): DFA = 44 | ((this: NFA) ++ other) toDFA 45 | 46 | def ++(other: DFA): DFA = 47 | this ++ (other: NFA) 48 | 49 | private def productAutomaton(other: DFA): DFA = { 50 | require(alphabet equals other.alphabet) 51 | 52 | val productStates = cartesianStateProduct(states, other.states) 53 | 54 | def productDelta(s: State, l: Letter): State = s match { 55 | case pair(s1, s2) => pair(delta(s1, l), other.delta(s2, l)) 56 | case _ => error("Impossible case") 57 | } 58 | 59 | (alphabet, productStates, pair(initialState, other.initialState), productDelta _, Set(): Set[State]) 60 | } 61 | 62 | def &(other: DFA): DFA = { 63 | require(alphabet equals other.alphabet) 64 | 65 | val intersAccepting = cartesianStateProduct(accepting, other.accepting) 66 | val product = productAutomaton(other) 67 | 68 | (alphabet, product.states, product.initialState, product.delta, intersAccepting) 69 | } 70 | 71 | def &(other: NFA): NFA = { 72 | require(alphabet equals other.alphabet) 73 | 74 | (this: NFA) & other 75 | } 76 | 77 | def |(other: DFA): DFA = { 78 | require(alphabet equals other.alphabet) 79 | 80 | val unionAccepting = cartesianStateProduct(accepting, other.states) ++ 81 | cartesianStateProduct(states, other.accepting) 82 | val product = productAutomaton(other) 83 | 84 | (alphabet, product.states, product.initialState, product.delta, unionAccepting) 85 | } 86 | 87 | def |(other: NFA): NFA = { 88 | require(alphabet equals other.alphabet) 89 | 90 | (this: NFA) | other 91 | } 92 | 93 | def \(other: DFA): DFA = { 94 | require(alphabet equals other.alphabet) 95 | 96 | this & (!other) 97 | } 98 | 99 | def \(other: NFA): DFA = { 100 | require(alphabet equals other.alphabet) 101 | 102 | this & (!(other toDFA)) 103 | } 104 | 105 | def ==(other: DFA): Boolean = 106 | ((this \ other) isEmpty) && ((other \ this) isEmpty) 107 | 108 | def ==(other: NFA): Boolean = 109 | this == other.toDFA 110 | 111 | def isEmpty: Boolean = accepting.foldLeft(true)( 112 | (acc, s) => acc && !traverseDFS(List(initialState), List()).contains(s)) 113 | 114 | def toRegExp: RE = { 115 | // Rename states: 1 .. n 116 | val renDFA = this getRenamedCopy 1 117 | renDFA.accepting.foldLeft(Empty(): RE)( 118 | (re, s) => re + renDFA.alpha(renDFA.states.size, renDFA initialState, s)) 119 | } 120 | 121 | def getRenamedCopy(startVal: Int): DFA = { 122 | val emptyMap: Map[State, State] = Map() 123 | val renameMap: Map[State, State] = 124 | states.foldLeft(emptyMap) { (z, s) => 125 | z + (s -> q(z.size + startVal)) 126 | } 127 | val reverseRenameMap = renameMap.map(_.swap) 128 | 129 | def deltaRen(state: State, letter: Letter): State = 130 | renameMap(delta(reverseRenameMap(state), letter)) 131 | 132 | (alphabet, 133 | states.map(s => renameMap(s)), 134 | renameMap(initialState), 135 | deltaRen _, 136 | accepting.map(s => renameMap(s))) 137 | } 138 | 139 | def traverseDFS(toVisit: List[State], visited: List[State]): List[State] = { 140 | if (toVisit isEmpty) { 141 | List() 142 | } else { 143 | val next = toVisit head 144 | val succ = alphabet.map(l => delta(next, l)).toList diff toVisit diff visited 145 | 146 | next :: traverseDFS(toVisit.tail ++ succ, next :: visited) 147 | } 148 | } 149 | 150 | def minimize: DFA = { 151 | // first, remove unreachable states 152 | val reachableStates = states intersect (Set() ++ traverseDFS(List(initialState), List())) 153 | val reachableAccepting = accepting intersect reachableStates 154 | 155 | val rel = (reachableStates -- reachableAccepting) 156 | .foldLeft(new AntiReflSymmRel(): AntiReflSymmRel[State])( 157 | (rel, s) => rel ++ reachableAccepting.foldLeft(Set(): Set[(State,State)])( 158 | (set, a) => set + (s -> a))) 159 | 160 | val reachDFA = (alphabet, reachableStates, initialState, delta, reachableAccepting): DFA 161 | 162 | reachDFA minimize rel 163 | } 164 | 165 | @tailrec 166 | private def minimize(rel: AntiReflSymmRel[State]): DFA = { 167 | val cartProd = cartesianProduct(states, states) 168 | val differentPairs = cartProd.filter(p => p match { 169 | case (k, l) => rel.inRel(k, l) || 170 | alphabet.foldLeft(false)( 171 | (acc, a) => acc || rel.inRel(delta(k, a), delta(l, a))) 172 | case _ => error("Should not happen") 173 | }) 174 | 175 | val newRel = rel ++ differentPairs 176 | 177 | if (rel == newRel) { 178 | 179 | // Recursion anchor: 180 | // The distinguishing relation does not change in the next 181 | // iteration, so we construct the resulting automaton now 182 | // by "contracting" the states that are not distinguished 183 | 184 | val eqRel: EquivRel[State] = 185 | new EquivRel() ++ cartProd.filter(p => !rel.inRel(p._1, p._2)) 186 | 187 | val newStates = eqRel.equivalenceClasses.map( 188 | setOfStates => set(setOfStates)): Set[State] 189 | 190 | val newInitialState = newStates.filter(state => state match { 191 | case set(setOfStates: Set[State]) => setOfStates contains initialState 192 | case _ => error("Impossible case.") 193 | }) head: State 194 | 195 | val newAccepting = newStates.filter(state => state match { 196 | case set(setOfStates: Set[State]) => (setOfStates intersect accepting).nonEmpty 197 | case _ => error("Impossible case.") 198 | }): Set[State] 199 | 200 | def newDelta(state: State, letter: Letter): State = 201 | (state) match { 202 | case set(setOfStates) => { 203 | val someState = setOfStates head 204 | val transResult = delta(someState, letter) 205 | 206 | newStates.filter(state => state match { 207 | case set(setOfStates: Set[State]) => setOfStates contains transResult 208 | case _ => error("Impossible case.") 209 | }) head 210 | } 211 | case _ => error("Impossible case.") 212 | } 213 | 214 | (alphabet, newStates, newInitialState, newDelta _, newAccepting): DFA 215 | 216 | } else { 217 | 218 | minimize(newRel) 219 | 220 | } 221 | } 222 | 223 | private def alpha(k: Int, from: State, to: State): RE = 224 | if (k == 0) { 225 | val oneStepTransitions = alphabet 226 | .filter(a => delta(from, a) == to) 227 | .foldLeft(Empty(): RE)((re, a) => re + a) 228 | 229 | if (from == to) { 230 | (Empty()*) + oneStepTransitions 231 | } else { 232 | oneStepTransitions 233 | } 234 | } else { 235 | (from, to) match { 236 | case (q(l), q(m)) => 237 | alpha(k - 1, q(l), q(m)) + 238 | (alpha(k - 1, q(l), q(k)) & 239 | (alpha(k - 1, q(k), q(k))*) & 240 | alpha(k - 1, q(k), q(m))) 241 | 242 | case _ => error("Should not happen: Call toRegExp() and not this method") 243 | } 244 | } 245 | 246 | override def toString = { 247 | val indentSpace = " " 248 | val indentBeginner = "|" 249 | val indent = "|" + indentSpace 250 | val dindent = indent + indentSpace 251 | var sb = new StringBuilder() 252 | 253 | sb ++= "DFA (Z,S,q0,d,A) with\n" 254 | 255 | sb ++= toStringUpToDelta( 256 | indentBeginner, 257 | indentSpace, 258 | "Z", alphabet, 259 | "S", states, 260 | "q0", initialState, 261 | "A", accepting); 262 | 263 | sb ++= indent ++= "d = {" 264 | states.foreach(s => 265 | alphabet.foreach(l => 266 | sb ++= "\n" 267 | ++= dindent 268 | ++= "(" 269 | ++= s.toString 270 | ++= "," 271 | ++= l.name 272 | ++= ") => " 273 | ++= delta(s, l).toString 274 | ++= ",")) 275 | sb = sb.dropRight(1 - alphabet.isEmpty) 276 | sb ++= "\n" ++= indent ++= "}\n" 277 | 278 | sb toString 279 | } 280 | 281 | override def toXml: scala.xml.Elem = { 282 | val renamed = getRenamedCopy(0) 283 | val alphabet = renamed.alphabet 284 | val states = renamed.states 285 | val initialState = renamed.initialState 286 | val delta = renamed.delta 287 | val accepting = renamed.accepting 288 | 289 | 290 | {alphabet.map { letter => {letter.toString.replaceFirst("'", "")} }} 291 | 292 | 293 | {states.map { state => {state.toString} }} 294 | 295 | {initialState} 296 | 297 | {cartesianProduct(states,alphabet).map({ 298 | case (s,l) => 299 | 300 | })} 301 | 302 | 303 | {accepting.map { state => {state.toString} }} 304 | 305 | 306 | } 307 | } 308 | 309 | object DFA { 310 | def Empty = { 311 | val alphabet = Set('a) : Set[Letter] 312 | val states = Set(q(0)) : Set[State] 313 | def initial = q(0) : State 314 | val accepting = Set() : Set[State] 315 | def delta(s: State, l: Letter): State = q(0) 316 | 317 | (alphabet, states, initial, delta _, accepting): DFA 318 | } 319 | 320 | def fromXml(node: Node): DFA = { 321 | /* 322 | 323 | 324 | a 325 | b 326 | 327 | 328 | 1 329 | 3 330 | 5 331 | 6 332 | 4 333 | 2 334 | 335 | 1 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 3 352 | 4 353 | 5 354 | 355 | 356 | */ 357 | val alphabet = (node \ "alphabet" \ "letter") map { 358 | lNode => Symbol(lNode.text) 359 | }: Seq[Letter] 360 | 361 | val states = (node \ "states" \ "state") map { 362 | sNode => q(sNode.text.toInt) 363 | }: Seq[State] 364 | 365 | val initialState = q((node \ "initialState").text.toInt): State 366 | 367 | def delta(state: State, letter: Letter): State = { 368 | val transitions = (node \ "delta" \ "transition").foldLeft(Map[(State,Letter), State]())( 369 | (map: Map[(State,Letter), State], elem: scala.xml.Node) => 370 | map + ( 371 | (q((elem \ "@from").text.toInt), Symbol((elem \ "@trigger").text)) -> 372 | q((elem \ "@to").text.toInt))): Map[(State,Letter), State] 373 | 374 | transitions(state, letter) 375 | } 376 | 377 | val accepting = (node \ "accepting" \ "state") map { 378 | sNode => q(sNode.text.toInt) 379 | }: Seq[State] 380 | 381 | new DFA(alphabet.toSet, states.toSet, initialState, delta _, accepting.toSet) 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/FSA_DSL.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Types._ 3 | 4 | trait FSA_DSL { 5 | case class FSABuilder( 6 | t: (Symbol, Symbol, Symbol, Symbol, Symbol), isDFA: Boolean) { 7 | // The elements that need to be filled 8 | var alphabet : Option[Set[Letter]] = None 9 | var states : Option[States] = None 10 | var q0 : Option[State] = None 11 | var deltaF : Option[(State, Letter) => State] = None 12 | var deltaR : Option[(State, Letter) => Set[State]] = None 13 | var A : Option[States] = None 14 | 15 | // Connectors for definition: "where" and "and" 16 | def where = (input: (Symbol, Any)) => (input._2 match { 17 | case symbols: SymbolSet => input._1 match { 18 | case t._1 => { alphabet = Some(symbols.set); this } 19 | } 20 | case stateSet: IntSet => input._1 match { 21 | case t._2 => { states = Some(stateSet.set.map(s => q(s))); this } 22 | case t._5 => { A = Some(stateSet.set.map(s => q(s))); this } 23 | } 24 | case state: Int => input._1 match { 25 | case t._3 => { q0 = Some(q(state)); this } 26 | } 27 | case func: DeltaFun => input._1 match { 28 | case t._4 => { 29 | def myDelta (s: State, l: Letter): State = s match { 30 | case q(i) => 31 | if (func.fun contains(i, l)) 32 | q(func.fun(i, l)) 33 | else 34 | error("DFA transition function must be total, " 35 | + "but is not defined for (" + i.toString + "," + l.toString + ")") 36 | case _ => error("Should not occur") 37 | } 38 | deltaF = Some(myDelta) 39 | this 40 | } 41 | } 42 | case func: DeltaRel => input._1 match { 43 | case t._4 => { 44 | def myDelta (s: State, l: Letter): Set[State] = s match { 45 | case q(i) => 46 | if (func.fun contains(i, l)) 47 | func.fun(i, l).map(s => q(s)) 48 | else 49 | Set(): Set[State] 50 | case _ => error("Should not occur") 51 | } 52 | deltaR = Some(myDelta) 53 | this 54 | } 55 | } 56 | }) 57 | 58 | def and = where 59 | 60 | def testDFADone : Boolean = 61 | alphabet.isDefined && 62 | states.isDefined && 63 | q0.isDefined && 64 | deltaF.isDefined && 65 | A.isDefined 66 | 67 | def testNFADone : Boolean = 68 | alphabet.isDefined && 69 | states.isDefined && 70 | q0.isDefined && 71 | deltaR.isDefined && 72 | A.isDefined 73 | 74 | def | : DFA = 75 | if (testDFADone) 76 | new DFA(alphabet.get, states.get, q0.get, deltaF.get, A.get) 77 | else 78 | error("Some values of the DFA are still undefined") 79 | 80 | def || : NFA = 81 | if (testNFADone) 82 | new NFA(alphabet.get, states.get, q0.get, deltaR.get, A.get) 83 | else 84 | error("Some values of the NFA are still undefined") 85 | } 86 | 87 | // Starting point: dfa/nfa function 88 | def dfa(t: (Symbol, Symbol, Symbol, Symbol, Symbol)) = 89 | FSABuilder(t, true) 90 | def nfa(t: (Symbol, Symbol, Symbol, Symbol, Symbol)) = 91 | FSABuilder(t, false) 92 | 93 | // Syntactic Sugar 94 | object Delta { 95 | def apply (t: ((Int, Symbol), Int)*) = DeltaFun(Map() ++ t) 96 | def apply (t: ((Int, Symbol), Set[Int])*) = DeltaRel(Map() ++ t) 97 | } 98 | 99 | case class SymbolWrapper(s: Symbol) { 100 | def ==>(vals: SymbolSet) = (s, vals) 101 | def ==>(vals: IntSet) = (s, vals) 102 | def ==>(aval: Int) = (s, aval) 103 | def ==>(afun: DeltaFun) = (s, afun) 104 | def ==>(afun: DeltaRel) = (s, afun) 105 | } 106 | 107 | implicit def SymbToSymbWrapper(s: Symbol) = SymbolWrapper(s) 108 | 109 | case class IntSet(set: Set[Int]) 110 | case class StringSet(set: Set[String]) 111 | case class SymbolSet(set: Set[Symbol]) 112 | case class DeltaFun(fun: Map[(Int, Letter), Int]) 113 | case class DeltaRel(fun: Map[(Int, Letter), Set[Int]]) 114 | implicit def is(set: Set[Int]) = IntSet(set) 115 | implicit def sts(set: Set[String]) = StringSet(set) 116 | implicit def sys(set: Set[Symbol]) = SymbolSet(set) 117 | // implicit def dfun(fun: Map[(Int, Letter), Int]) = DeltaFun(fun) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/FSM.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | 3 | import Types._ 4 | import Conversions._ 5 | import RegularExpressions._ 6 | 7 | abstract class FSM { 8 | def isDFA = this.isInstanceOf[DFA] 9 | def isNFA = this.isInstanceOf[NFA] 10 | def asDFA: Option[DFA] = 11 | if (isDFA) 12 | Some(this.asInstanceOf[DFA]) 13 | else 14 | None 15 | def asNFA: Option[NFA] = 16 | if (isNFA) 17 | Some(this.asInstanceOf[NFA]) 18 | else 19 | None 20 | 21 | def toXml: scala.xml.Elem 22 | 23 | def toPrettyXml: String = { 24 | val printer = new scala.xml.PrettyPrinter(80, 2) 25 | printer.format(toXml) 26 | } 27 | 28 | def toStringUpToDelta( 29 | indentBeginner: String, 30 | indentSpace: String, 31 | alphabetDesignator: String, 32 | alphabet: Set[Letter], 33 | statesDesignator: String, 34 | states: Set[State], 35 | initialStateDesignator: String, 36 | initialState: State, 37 | acceptingDesignator: String, 38 | accepting: Set[State]): String = { 39 | 40 | val indent = indentBeginner + indentSpace 41 | val dindent = indent + indentSpace 42 | var sb = new StringBuilder() 43 | 44 | sb ++= indent ++= alphabetDesignator ++= " = {" 45 | alphabet.foreach(s => sb ++= s.name ++= ",") 46 | sb = sb.dropRight(1 - alphabet.isEmpty) 47 | sb ++= "}\n" 48 | 49 | sb ++= indent ++= statesDesignator ++= " = {" 50 | states.foreach(s => sb ++= s.toString() ++= ",") 51 | sb = sb.dropRight(1 - states.isEmpty) 52 | sb ++= "}\n" 53 | 54 | sb ++= indent ++= initialStateDesignator ++= " = " ++= initialState.toString ++= "\n" 55 | 56 | sb ++= indent ++= acceptingDesignator ++= " = {" 57 | accepting.foreach(s => sb ++= s.toString() ++= ",") 58 | sb = sb.dropRight(1 - accepting.isEmpty) 59 | sb ++= "}\n" 60 | 61 | sb.toString 62 | } 63 | 64 | def accepts(word: String): Boolean 65 | 66 | def accepts(word: Word): Boolean 67 | 68 | def accepts(word: Word, fromState: State): Boolean 69 | 70 | def extendAlphabet(newLetters: Set[Letter]): NFA 71 | 72 | def adjustAlphabet(other: FSM): NFA = 73 | if (other.isDFA) 74 | extendAlphabet(other.asDFA.get.alphabet) 75 | else 76 | extendAlphabet(other.asNFA.get.alphabet) 77 | 78 | def unary_! : FSM 79 | 80 | def * : FSM 81 | 82 | def ++(other: DFA): FSM 83 | def ++(other: NFA): FSM 84 | def ++(other: FSM): FSM = 85 | if (other isDFA) 86 | this ++ other.asDFA.get 87 | else 88 | this ++ other.asNFA.get 89 | 90 | def &(other: DFA): FSM 91 | def &(other: NFA): FSM 92 | def &(other: FSM): FSM = 93 | if (other isDFA) 94 | this & other.asDFA.get 95 | else 96 | this & other.asNFA.get 97 | 98 | def |(other: DFA): FSM 99 | def |(other: NFA): FSM 100 | def |(other: FSM): FSM = 101 | if (other isDFA) 102 | this | other.asDFA.get 103 | else 104 | this | other.asNFA.get 105 | 106 | def \(other: DFA): FSM 107 | def \(other: NFA): FSM 108 | def \(other: FSM): FSM = 109 | if (other isDFA) 110 | this \ other.asDFA.get 111 | else 112 | this \ other.asNFA.get 113 | 114 | def ==(other: DFA): Boolean 115 | def ==(other: NFA): Boolean 116 | def ==(other: FSM): Boolean = 117 | if (other isDFA) 118 | this == other.asDFA.get 119 | else 120 | this == other.asNFA.get 121 | 122 | def isEmpty: Boolean 123 | 124 | def toRegExp: RE 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/Helpers.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Types._ 3 | 4 | object Helpers { 5 | 6 | def powerSet[A](s: Set[A]) = s.subsets.toSet 7 | 8 | def optJoin[A](a: Option[Set[A]], b: Option[Set[A]]): Option[Set[A]] = 9 | a match { 10 | case Some(s1) => b match { 11 | case Some(s2) => Some(s1 ++ s2) 12 | case None => Some(s1) 13 | } 14 | case None => b match { 15 | case Some(s) => Some(s) 16 | case None => None 17 | } 18 | } 19 | 20 | def cartesianStateProduct(a: Set[State], b: Set[State]) : Set[State] = 21 | cartesianProduct(a,b).map(p => pair(p._1, p._2)) 22 | 23 | def cartesianProduct[A,B](a: Set[A], b: Set[B]) : Set[(A,B)] = 24 | a.foldLeft(Set(): Set[(A,B)])( 25 | (acc,elem) => acc ++ b.foldLeft(Set(): Set[(A,B)])( 26 | (acc1,elem1) => acc1 + (elem -> elem1) 27 | ) 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/NFA.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Types._ 3 | import Conversions._ 4 | import Helpers._ 5 | import RegularExpressions._ 6 | 7 | class NFA( 8 | var alphabet: Set[Letter], 9 | var states: Set[State], 10 | var initialState: State, 11 | var delta: ((State, Letter) => Set[State]), //Power Set instead of Relation 12 | var accepting: Set[State]) extends FSM { 13 | 14 | require(states contains initialState) 15 | require(accepting subsetOf states) 16 | 17 | def acceptsEmptyWord: Boolean = 18 | accepting contains initialState // There are no epsilon-translations 19 | 20 | def accepts(word: String): Boolean = 21 | accepts(for (x <- word.toList) yield Symbol(x toString)) 22 | 23 | def accepts(word: Word): Boolean = accepts(word, initialState) 24 | 25 | def accepts(word: Word, fromState: State): Boolean = word match { 26 | case Nil => accepting contains fromState 27 | case letter :: rest => 28 | (alphabet contains letter) && 29 | delta(fromState, letter).foldLeft(false)( 30 | (result: Boolean, possibleSuccessor: State) => 31 | result || accepts(rest, possibleSuccessor)) 32 | } 33 | 34 | def extendAlphabet(newLetters: Set[Letter]): NFA = { 35 | (alphabet ++ newLetters, states, initialState, delta, accepting) 36 | } 37 | 38 | def unary_! : DFA = !(this toDFA) 39 | 40 | def * : NFA = { 41 | // Avoid name clash for case where new start state has to be added 42 | if (!(this acceptsEmptyWord) && states.contains(q(0))) { 43 | (getRenamedCopy(1)*): NFA 44 | } 45 | 46 | def deltaStar(state: State, letter: Letter): Set[State] = { 47 | val deltaRes = delta(state, letter): Set[State] 48 | if (deltaRes exists ( accepting contains _ )) 49 | deltaRes + initialState 50 | else 51 | deltaRes 52 | } 53 | 54 | if (!(this acceptsEmptyWord)) { 55 | val newInitialState = q(0) 56 | 57 | def deltaStar2(state: State, letter: Letter): Set[State] = 58 | if (state == newInitialState) { 59 | deltaStar(initialState, letter) 60 | } else { 61 | deltaStar(state, letter) 62 | } 63 | 64 | (alphabet, states + newInitialState, newInitialState, deltaStar2 _, accepting + newInitialState) 65 | 66 | } else { 67 | 68 | (alphabet, states, initialState, deltaStar _, accepting) 69 | 70 | } 71 | } 72 | 73 | def ++(otherOrig: NFA): NFA = { 74 | // Rename before concatenation to avoid state name clash 75 | val thisR = this getRenamedCopy 0 76 | val other = otherOrig getRenamedCopy states.size 77 | 78 | thisR concat other 79 | } 80 | 81 | def ++(otherOrig: DFA): NFA = { 82 | this ++ (otherOrig: NFA) 83 | } 84 | 85 | private def productAutomaton(other: NFA): NFA = { 86 | require(alphabet equals other.alphabet) 87 | 88 | val productStates = cartesianStateProduct(states, other.states) 89 | 90 | def productDelta(s: State, l: Letter): Set[State] = s match { 91 | case pair(s1, s2) => { 92 | cartesianStateProduct(delta(s1, l), other.delta(s2, l)) 93 | } 94 | case _ => error("Impossible case") 95 | } 96 | 97 | (alphabet, productStates, pair(initialState, other.initialState), productDelta _, Set(): Set[State]) 98 | } 99 | 100 | def &(other: NFA): NFA = { 101 | require(alphabet equals other.alphabet) 102 | 103 | val intersAccepting = cartesianStateProduct(accepting, other.accepting) 104 | val product = productAutomaton(other) 105 | 106 | (alphabet, product.states, product.initialState, product.delta, intersAccepting) 107 | } 108 | 109 | def &(other: DFA): NFA = { 110 | require(alphabet equals other.alphabet) 111 | 112 | this & (other: NFA) 113 | } 114 | 115 | def |(other: NFA): NFA = { 116 | require(alphabet equals other.alphabet) 117 | 118 | if ( !(states intersect other.states).isEmpty 119 | || (states union other.states).contains(q(0))) 120 | getRenamedCopy(1) | other.getRenamedCopy(states.size + 1) 121 | else { 122 | def newDelta(s: State, l: Letter): Set[State] = 123 | if (s == q(0)) 124 | delta(initialState, l) ++ other.delta(other.initialState, l) 125 | else 126 | if (states.contains(s)) 127 | delta(s, l) 128 | else 129 | other.delta(s,l) 130 | 131 | if ((this acceptsEmptyWord) || (other acceptsEmptyWord)) 132 | (alphabet, states ++ other.states + q(0), q(0), newDelta _, accepting ++ other.accepting + q(0)) 133 | else 134 | (alphabet, states ++ other.states + q(0), q(0), newDelta _, accepting ++ other.accepting) 135 | } 136 | } 137 | 138 | def |(other: DFA): NFA = { 139 | require(alphabet equals other.alphabet) 140 | 141 | this & (other: NFA) 142 | } 143 | 144 | def \(other: NFA): DFA = 145 | (this toDFA) \ (other toDFA) 146 | 147 | def \(other: DFA): DFA = 148 | (this toDFA) \ other 149 | 150 | def ==(other: NFA): Boolean = 151 | (this toDFA) == (other toDFA) 152 | 153 | def ==(other: DFA): Boolean = 154 | (this toDFA) == other 155 | 156 | def isEmpty: Boolean = (this toDFA) isEmpty 157 | 158 | def toRegExp: RE = (this toDFA) toRegExp 159 | 160 | def toDFA: DFA = { 161 | val pInitialState = set(Set(initialState)): State 162 | 163 | def pDelta(state: State, letter: Letter) = 164 | (state, letter) match { 165 | case (set(setOfStates), letter) => 166 | set(setOfStates.foldLeft(Set(): States)((result, q) => 167 | result union delta(q, letter) 168 | )) 169 | case _ => error("Impossible case") 170 | } 171 | 172 | 173 | val interm = (alphabet, Set(pInitialState), pInitialState, pDelta _, Set(pInitialState)): DFA 174 | val pStates = interm.traverseDFS(List(pInitialState), List()).toSet 175 | val pAccepting = pStates.filter { 176 | case (set(setOfStates)) => (setOfStates intersect accepting) nonEmpty 177 | case _ => error("Impossible case") 178 | } 179 | 180 | (alphabet, pStates, pInitialState, pDelta _, pAccepting) 181 | } 182 | 183 | def getRenamedCopy(startVal: Int): NFA = { 184 | val emptyMap: Map[State, State] = Map() 185 | val renameMap: Map[State, State] = 186 | states.foldLeft(emptyMap) { (z, s) => 187 | z + (s -> q(z.size + startVal)) 188 | } 189 | val reverseRenameMap = renameMap.map(_.swap) 190 | 191 | def deltaRen(state: State, letter: Letter): Set[State] = 192 | delta(reverseRenameMap(state), letter).map(s => renameMap(s)) 193 | 194 | (alphabet, 195 | states.map(s => renameMap(s)), 196 | renameMap(initialState), 197 | deltaRen _, 198 | accepting.map(s => renameMap(s))) 199 | } 200 | 201 | def removeUnreachable: NFA = { 202 | val reachableStates = states intersect (traverseDFS(List(initialState), List()).toSet) 203 | val reachableAccepting = accepting intersect reachableStates 204 | 205 | (alphabet, reachableStates, initialState, delta, reachableAccepting): NFA 206 | } 207 | 208 | private def concat(other: NFA): NFA = { 209 | if (this acceptsEmptyWord) { 210 | 211 | val noEpsAccepting = accepting - initialState 212 | val concatNoEps = ((alphabet, states, initialState, delta, noEpsAccepting): NFA) concat other 213 | 214 | val statesCup = concatNoEps.states + q(-1) 215 | 216 | def deltaCup(state: State, letter: Letter): Set[State] = 217 | if (state == q(-1)) 218 | concatNoEps.delta(initialState, letter) ++ other.delta(other.initialState, letter) 219 | else 220 | concatNoEps.delta(state, letter) 221 | 222 | ((alphabet ++ other.alphabet, statesCup, q(-1), deltaCup _, concatNoEps.accepting): NFA) getRenamedCopy 0 223 | 224 | } else { 225 | 226 | val statesCup = states ++ other.states 227 | 228 | def deltaCup(state: State, letter: Letter): Set[State] = 229 | if (other.states contains state) 230 | other.delta(state, letter) // Delta_2 231 | else { // Delta_1 \cup Delta_{1->2} 232 | val deltaRes = delta(state, letter) 233 | if (deltaRes exists ( accepting contains _ )) 234 | deltaRes + other.initialState 235 | else 236 | deltaRes 237 | } 238 | 239 | (alphabet ++ other.alphabet, statesCup, initialState, deltaCup _, other.accepting) 240 | 241 | } 242 | } 243 | 244 | private def traverseDFS(toVisit: List[State], visited: List[State]): List[State] = { 245 | if (toVisit isEmpty) { 246 | List() 247 | } else { 248 | val next = toVisit head 249 | val succ = alphabet.foldLeft(Set(): Set[State])( 250 | (acc, l) => acc ++ delta(next, l) 251 | ).toList diff toVisit diff visited 252 | 253 | next :: traverseDFS(toVisit.tail ++ succ, next :: visited) 254 | } 255 | } 256 | 257 | override def toXml: scala.xml.Elem = { 258 | val renamed = getRenamedCopy(0) 259 | val alphabet = renamed.alphabet 260 | val states = renamed.states 261 | val initialState = renamed.initialState 262 | val delta = renamed.delta 263 | val accepting = renamed.accepting 264 | 265 | 266 | {alphabet.map { letter => {letter.toString.replaceFirst("'", "")} }} 267 | 268 | 269 | {states.map { state => {state.toString} }} 270 | 271 | {initialState} 272 | 273 | {cartesianProduct(states,alphabet).map({ 274 | case (s,l) => 275 | delta(s,l).map { 276 | to => 277 | } 278 | })} 279 | 280 | 281 | {accepting.map { state => {state.toString} }} 282 | 283 | 284 | } 285 | 286 | override def toString = { 287 | val indentSpace = " " 288 | val indentBeginner = "|" 289 | val indent = "|" + indentSpace 290 | val dindent = indent + indentSpace 291 | var sb = new StringBuilder() 292 | 293 | sb ++= "NFA (Z,S,q0,D,A) with\n" 294 | 295 | sb ++= toStringUpToDelta( 296 | indentBeginner, 297 | indentSpace, 298 | "Z", alphabet, 299 | "S", states, 300 | "q0", initialState, 301 | "A", accepting); 302 | 303 | sb ++= indent ++= "D = {" 304 | states.foreach(s => 305 | alphabet.filter(l => !delta(s, l).isEmpty).foreach(l => 306 | sb ++= "\n" 307 | ++= dindent 308 | ++= "(" 309 | ++= s.toString 310 | ++= "," 311 | ++= l.name 312 | ++= ") => " 313 | ++= (if (delta(s, l).isEmpty) "{}" 314 | else 315 | delta(s, l).foldLeft("")( 316 | (result, aState) => result + aState.toString + " | ").stripSuffix(" | ")) 317 | ++= ",")) 318 | sb = sb.dropRight(1 - alphabet.isEmpty) 319 | sb ++= "\n" ++= indent ++= "}\n" 320 | 321 | sb toString 322 | } 323 | } 324 | 325 | object NFA { 326 | def fromXml(node: scala.xml.Node): NFA = { 327 | /* 328 | 329 | 330 | a 331 | b 332 | 333 | 334 | 0 335 | 1 336 | 337 | 0 338 | 339 | 340 | 341 | 342 | 343 | 344 | 1 345 | 346 | 347 | */ 348 | val alphabet = (node \ "alphabet" \ "letter") map { 349 | lNode => Symbol(lNode.text) 350 | }: Seq[Letter] 351 | 352 | val states = (node \ "states" \ "state") map { 353 | sNode => q(sNode.text.toInt) 354 | }: Seq[State] 355 | 356 | val initialState = q((node \ "initialState").text.toInt): State 357 | 358 | def delta(state: State, letter: Letter): Set[State] = { 359 | val transitions = (node \ "delta" \ "transition").foldLeft(Map[(State,Letter), Set[State]]())( 360 | (map: Map[(State,Letter), Set[State]], elem: scala.xml.Node) => { 361 | val from = q((elem \ "@from").text.toInt): State 362 | val trigger = Symbol((elem \ "@trigger").text): Letter 363 | val to = q((elem \ "@to").text.toInt): State 364 | 365 | if (map contains (from,trigger)) { 366 | val oldRes = map(from, trigger) 367 | (map - ((from,trigger))) + ((from, trigger) -> (oldRes + to)) 368 | } else { 369 | map + ((from, trigger) -> Set(to)): Map[(State,Letter), Set[State]] 370 | } 371 | }) 372 | 373 | if (transitions contains(state, letter)) 374 | transitions(state, letter) 375 | else 376 | Set() 377 | } 378 | 379 | val accepting = (node \ "accepting" \ "state") map { 380 | sNode => q(sNode.text.toInt) 381 | }: Seq[State] 382 | 383 | new NFA(alphabet.toSet, states.toSet, initialState, delta _, accepting.toSet) 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/RegularExpressions.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils 2 | 3 | import Types._ 4 | import Conversions._ 5 | 6 | object RegularExpressions { 7 | 8 | sealed abstract class RE extends FSA_DSL { 9 | type MutableMap[A,B] = scala.collection.mutable.Map[A,B] 10 | type Map[A,B] = scala.collection.immutable.Map[A,B] 11 | def MutableMap[A,B]() : MutableMap[A,B] = collection.mutable.Map[A,B]() 12 | 13 | def *(): RE = Star(this) 14 | def +(rhs: RE): RE = Or(this, rhs) 15 | def &(rhs: RE): RE = Concat(this, rhs) 16 | 17 | def alphabet: Set[Letter] 18 | 19 | def toNFA: NFA = toNFAInt(alphabet, MutableMap()) 20 | def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]): NFA 21 | 22 | override def hashCode: Int = toString hashCode 23 | override def equals(other: Any): Boolean = 24 | other.isInstanceOf[RE] && (this.hashCode == other.hashCode) 25 | 26 | /** 27 | * cleanString does some post processing on the 28 | * toString method in order to make the output better 29 | * readable. However, you naturally achieve better 30 | * correctness guarantees without this method (since 31 | * this is just string manipulation with regular expressions). 32 | */ 33 | def cleanString = recClean(toString) 34 | 35 | private def recClean(s: String): String = { 36 | val cleanRes = clean(s) 37 | if (s equals cleanRes) { 38 | s 39 | } else { 40 | recClean(cleanRes) 41 | } 42 | } 43 | 44 | private def clean(s: String): String = s 45 | .replace("{} + ", "") 46 | .replace("({})*", "\u025B") // epsilon 47 | .replace("{}", "\u00D8") // emptyset 48 | .replace("**", "*") 49 | .replaceAll("""'([a-z])""", "$1") 50 | .replaceAll("""\(([a-z])\)""", "$1") 51 | .replaceAll("""\(\(([^\(\)]+)\)\)\*""", "($1)*") 52 | .replaceAll("""\(\u025B \+ ([^\(\)]+)\)\*""", "($1)*") 53 | .replaceAll(""" [&\+] \u00D8""", "") 54 | .replaceAll("""\u00D8 [&\+] """, "") 55 | .replaceAll("""\(([a-z\u025B])\)([\*]?)""", "$1$2") 56 | .replaceAll("""\(\(([^\(\)]+)\)\)""", "($1)") 57 | .replaceAll("""\(([a-z])\*\)""", "$1*") 58 | 59 | def clean: RE = this match { 60 | case Star(inner) => inner match { 61 | // ((...)*)* => (...)* 62 | case Star(inner2) => Star(inner2 clean) 63 | // ({}* + XXX)* => (XXX)* 64 | case Or(Star(Empty()), rhs) => rhs clean 65 | // (XXX + {}*)* => (XXX)* 66 | case Or(lhs, Star(Empty())) => lhs clean 67 | case _ => Star(inner clean) 68 | } 69 | case Or(lhs, rhs) => lhs match { 70 | // {} + (...) => (...) 71 | case Empty() => rhs clean 72 | case Star(Empty()) => rhs match { 73 | // {}* + (...)* => (...)* 74 | case Star(rhsInner) => Star(rhsInner clean) 75 | case _ => Or(lhs clean, rhs clean) 76 | } 77 | case _ => rhs match { 78 | // (...) + {} => (...) 79 | case Empty() => lhs clean 80 | case Star(Empty()) => lhs match { 81 | // (...)* + {}* => (...)* 82 | case Star(lhsInner) => Star(lhsInner clean) 83 | case _ => Or(lhs clean, rhs clean) 84 | } 85 | case _ => if (lhs equals rhs) 86 | // XXX + XXX => XXX 87 | lhs 88 | else 89 | Or(lhs clean, rhs clean) 90 | } 91 | } 92 | case Concat(lhs, rhs) => lhs match { 93 | // {} & (...) => (...) 94 | case Empty() => rhs clean 95 | case Or(Star(Empty()), lhsInner) => rhs match { 96 | case Star(rhsInner) => { 97 | val lhsInnerClean = lhsInner clean 98 | val rhsInnerClean = rhsInner clean 99 | 100 | if (lhsInnerClean equals rhsInnerClean) 101 | // (eps + XXX) & (XXX)* => (XXX)* 102 | Star(rhsInnerClean) 103 | else 104 | Concat(lhs clean, rhs clean) 105 | } 106 | case _ => Concat(lhs clean, rhs clean) 107 | } 108 | case Or(lhsInner, Star(Empty())) => rhs match { 109 | case Star(rhsInner) => { 110 | val lhsInnerClean = lhsInner clean 111 | val rhsInnerClean = rhsInner clean 112 | 113 | if (lhsInnerClean equals rhsInnerClean) 114 | // (XXX + eps) & (XXX)* => (XXX)* 115 | Star(rhsInnerClean) 116 | else 117 | Concat(lhs clean, rhs clean) 118 | } 119 | case _ => Concat(lhs clean, rhs clean) 120 | } 121 | case _ => rhs match { 122 | // (...) + {} => (...) 123 | case Empty() => lhs clean 124 | case Or(rhsInner, Star(Empty())) => lhs match { 125 | case Star(lhsInner) => { 126 | val lhsInnerClean = lhsInner clean 127 | val rhsInnerClean = rhsInner clean 128 | 129 | if (lhsInnerClean equals rhsInnerClean) 130 | // (XXX)* & (XXX + eps) => (XXX)* 131 | Star(lhsInnerClean) 132 | else 133 | Concat(lhs clean, rhs clean) 134 | } 135 | case _ => Concat(lhs clean, rhs clean) 136 | } 137 | case Or(Star(Empty()), rhsInner) => lhs match { 138 | case Star(lhsInner) => { 139 | val lhsInnerClean = lhsInner clean 140 | val rhsInnerClean = rhsInner clean 141 | 142 | if (lhsInnerClean equals rhsInnerClean) 143 | // (XXX)* & (eps + XXX) => (XXX)* 144 | Star(lhsInnerClean) 145 | else 146 | Concat(lhs clean, rhs clean) 147 | } 148 | case _ => Concat(lhs clean, rhs clean) 149 | } 150 | case _ => Concat(lhs clean, rhs clean) 151 | } 152 | } 153 | case _ => this 154 | } 155 | } 156 | 157 | case class L(l: Letter) extends RE { 158 | override def toString = l toString 159 | override def alphabet = Set(l) 160 | override def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]) = { 161 | val genNFA = nfa('Z, 'S, 'q0, 'd, 'A) where 162 | 'Z ==> alph and 163 | 'S ==> Set(0, 1) and 164 | 'q0 ==> 0 and 165 | 'A ==> Set(1) and 166 | 'd ==> Delta( 167 | (0, l) -> Set(1)) || 168 | 169 | cache += (this -> genNFA) 170 | genNFA 171 | } 172 | } 173 | 174 | case class Empty() extends RE { 175 | override def toString = "{}" 176 | override def alphabet = Set() 177 | override def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]) = { 178 | val emptyAcc: Set[Int] = Set() 179 | val genNFA = nfa('Z, 'S, 'q0, 'd, 'A) where 180 | 'Z ==> alph and 181 | 'S ==> Set(0) and 182 | 'q0 ==> 0 and 183 | 'A ==> emptyAcc and 184 | 'd ==> DeltaRel(Map()) || 185 | 186 | cache += (this -> genNFA) 187 | genNFA 188 | } 189 | } 190 | 191 | case class Star(re: RE) extends RE { 192 | override def toString = "(" + re.toString + ")*" 193 | override def alphabet = re.alphabet 194 | override def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]) = 195 | if (re equals Empty()) 196 | nfa('Z, 'S, 'q0, 'd, 'A) where 197 | 'Z ==> alph and 198 | 'S ==> Set(0) and 199 | 'q0 ==> 0 and 200 | 'A ==> Set(0) and 201 | 'd ==> DeltaRel(Map()) || 202 | else { 203 | cache get this match { 204 | case None => { 205 | val genNFA = (re toNFAInt (alph, cache))* 206 | 207 | cache += (this -> genNFA) 208 | genNFA 209 | } 210 | case Some(nfa) => nfa 211 | } 212 | } 213 | } 214 | 215 | case class Or(lhs: RE, rhs: RE) extends RE { 216 | override def toString = "(" + lhs.toString + " + " + rhs.toString + ")" 217 | override def alphabet = lhs.alphabet ++ rhs.alphabet 218 | override def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]) = 219 | cache get this match { 220 | case None => { 221 | val genNFA = ((lhs toNFAInt (alph, cache)) | (rhs toNFAInt (alph, cache))): NFA 222 | 223 | cache += (this -> genNFA) 224 | genNFA 225 | } 226 | case Some(nfa) => nfa 227 | } 228 | } 229 | 230 | case class Concat(lhs: RE, rhs: RE) extends RE { 231 | override def toString = "(" + lhs.toString + " & " + rhs.toString + ")" 232 | override def alphabet = lhs.alphabet ++ rhs.alphabet 233 | override def toNFAInt(alph: Set[Letter], cache: MutableMap[RE, NFA]) = 234 | cache get this match { 235 | case None => { 236 | val genNFA = ((lhs toNFAInt (alph, cache)) ++ (rhs toNFAInt (alph, cache))): NFA 237 | 238 | cache += (this -> genNFA) 239 | genNFA 240 | } 241 | case Some(nfa) => nfa 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/Relations.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils 2 | 3 | import Types._ 4 | 5 | object Relations { 6 | class AntiReflSymmRel[A] private (val values: Set[(A,A)]) { 7 | 8 | def this() = this(Set(): Set[(A,A)]) 9 | 10 | def +(a: A, b: A): AntiReflSymmRel[A] = { 11 | require(a != b) 12 | 13 | new AntiReflSymmRel[A] (values + (a -> b) + (b -> a)) 14 | } 15 | 16 | def ++(set: Set[(A,A)]): AntiReflSymmRel[A] = 17 | set.foldLeft(this)((rel,p) => rel + (p._1,p._2)) 18 | 19 | def inRel(a: A, b: A): Boolean = 20 | values.contains(a -> b) 21 | 22 | def ==(other: AntiReflSymmRel[A]) = 23 | values.equals(other.values) 24 | 25 | def !=(other: AntiReflSymmRel[A]) = 26 | !values.equals(other.values) 27 | 28 | } 29 | 30 | class EquivRel[A] private (val map: Set[(A,A)], val values: Set[A]) { 31 | 32 | def this() = this(Set(), Set()) 33 | 34 | def +(a: A, b: A): EquivRel[A] = { 35 | val newVals = map + (a -> b) + (b -> a) + (a -> a) + (b -> b) 36 | val transClosure = transitiveClosure(newVals) 37 | new EquivRel[A] (transClosure, getValues(transClosure)) 38 | } 39 | 40 | def ++(set: Set[(A,A)]): EquivRel[A] = 41 | set.foldLeft(this)((rel,p) => rel + (p._1,p._2)) 42 | 43 | def inRel(a: A, b: A): Boolean = 44 | map.contains(a -> b) 45 | 46 | def ==(other: EquivRel[A]) = 47 | map.equals(other.map) 48 | 49 | def !=(other: EquivRel[A]) = 50 | !map.equals(other.map) 51 | 52 | def equivalenceClasses: Set[Set[A]] = { 53 | val valList = values toList : List[A] 54 | valList match { 55 | case h :: t => 56 | equivalenceClasses(h, List(h), t) 57 | case Nil => 58 | Set() 59 | } 60 | } 61 | 62 | private def equivalenceClasses(forElem: A, seen: List[A], rest: List[A]): Set[Set[A]] = { 63 | val classForElem = (rest ++ seen) 64 | .filter(p => inRel(forElem, p)) 65 | .foldLeft(Set(): Set[A])((set,p) => set + p) + forElem : Set[A] 66 | 67 | rest match { 68 | case h :: t => 69 | equivalenceClasses(h, h :: seen, t) + classForElem 70 | case Nil => 71 | Set(classForElem) 72 | } 73 | 74 | } 75 | 76 | private def getValues(map: Set[(A,A)]): Set[A] = 77 | map.foldLeft(Set(): Set[A])((acc,p) => 78 | acc + p._1 + p._2 79 | ) 80 | 81 | private def addTransitive[A, B](s: Set[(A, B)]) = { 82 | s ++ (for ((x1, y1) <- s; (x2, y2) <- s if y1 == x2) yield (x1, y2)) 83 | } 84 | 85 | private def transitiveClosure[A, B](s: Set[(A, B)]): Set[(A, B)] = { 86 | val t = addTransitive(s) 87 | if (t.size == s.size) s else transitiveClosure(t) 88 | } 89 | 90 | override def toString(): String = 91 | map toString 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/Types.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils { 2 | import Conversions._ 3 | 4 | object Types { 5 | type Letter = Symbol 6 | type Word = List[Letter] 7 | type NFADeltaResult = Set[State] 8 | type States = Set[State] 9 | 10 | abstract sealed class State 11 | 12 | case class q(i: Int) extends State { 13 | override def toString = i toString 14 | } 15 | 16 | case class set(s: Set[State]) extends State { 17 | override def toString = { 18 | var sb = new StringBuilder 19 | 20 | sb ++= s.foldLeft("{")((result, state) => result + state.toString + ",") 21 | sb = sb.dropRight(1 - s.isEmpty) 22 | sb ++= "}" 23 | 24 | sb toString 25 | } 26 | } 27 | 28 | case class pair(s1: State, s2: State) extends State 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/de/dominicscheurer/fsautils/test/Test.scala: -------------------------------------------------------------------------------- 1 | package de.dominicscheurer.fsautils.test 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.Matchers 5 | import de.dominicscheurer.fsautils.FSA_DSL 6 | import de.dominicscheurer.fsautils.Types._ 7 | import de.dominicscheurer.fsautils.DFA 8 | import de.dominicscheurer.fsautils.NFA 9 | 10 | class Test extends FlatSpec with Matchers with FSA_DSL { 11 | 12 | // ,..._ 13 | // ,-'''-. ,-----. ,-'''-. ,' `. 14 | // / `. b / / \ `. b / `. | a,b 15 | // | 2 |-------->|| 4 | |--------->| 6 | | 16 | // `. / `.`._,' / `. /.<-:_,_/ 17 | // `...-' `...-'`. a /\ `...-' 18 | // /|\ `._ ,' | 19 | // |a `.,' | b 20 | // | ,'`. | 21 | // \|/ ,'b `. | 22 | // ,-'''-. ,-----. - `v,-;--.. 23 | // / `. b /,' `.`. a / / `:. 24 | //--->| 1 |--------->|| 3 | |-------->|| 5 || 25 | // `. / `\_ ,'/ `.\ //_, 26 | // `...-' `----' `:,-' '|. 27 | // | ` \ 28 | // \ | a 29 | // `" ../ 30 | 31 | val dfa1 = 32 | dfa ('Z, 'S, 'q0, 'd, 'A) where 33 | 'Z ==> Set('a, 'b) and 34 | 'S ==> Set(1, 2, 3, 4, 5, 6) and 35 | 'q0 ==> 1 and 36 | 'A ==> Set(3, 4, 5) and 37 | 'd ==> Delta( 38 | (1, 'a) -> 2, 39 | (1, 'b) -> 3, 40 | (2, 'a) -> 1, 41 | (2, 'b) -> 4, 42 | (3, 'a) -> 5, 43 | (3, 'b) -> 6, 44 | (4, 'a) -> 5, 45 | (4, 'b) -> 6, 46 | (5, 'a) -> 5, 47 | (5, 'b) -> 6, 48 | (6, 'a) -> 6, 49 | (6, 'b) -> 6 50 | )| 51 | 52 | 53 | // 54 | // ,---. ,-;===:-. 55 | // ,' `. /,' `.\ 56 | // .' '. b .:' ':. 57 | //---->| 1 |----------->|| 2 || 58 | // \ / \\ // 59 | // `.._,,' `:.._,,;' 60 | // ,' /'`. _/---'''/\ 61 | // | `. .' | 62 | // ` | |. | 63 | // `-...,' ` -__ ,' 64 | // a '`'' 65 | // a 66 | 67 | val dfa1eqNFA = 68 | nfa ('Z, 'S, 'q0, 'd, 'A) where 69 | 'Z ==> Set('a, 'b) and 70 | 'S ==> Set(1, 2) and 71 | 'q0 ==> 1 and 72 | 'A ==> Set(2) and 73 | 'd ==> Delta( 74 | (1, 'a) -> Set(1), 75 | (1, 'b) -> Set(2), 76 | (2, 'a) -> Set(2) 77 | )|| 78 | 79 | // a a 80 | // __..._ _,..... ____ 81 | // /' \ /' `| a,b ,' '`. 82 | // /' / | ,| / | 83 | // .. `,/,. ' . `.i_ L_ |__ 84 | // ,-''-:_ ,-ii-._ ,-''-._ 85 | // .' \ b .',' `-.\ b .' \ 86 | // ----->| 1 |------->|/ 2 '.------->| 3 | 87 | // \ / \\ // \ / 88 | // `._ _,' `:._,,;' `._ _,' 89 | // `' `' `' 90 | 91 | val dfa1eqMinDFA = 92 | dfa ('Z, 'S, 'q0, 'd, 'A) where 93 | 'Z ==> Set('a, 'b) and 94 | 'S ==> Set(1, 2, 3) and 95 | 'q0 ==> 1 and 96 | 'A ==> Set(2) and 97 | 'd ==> Delta( 98 | (1, 'a) -> 1, 99 | (1, 'b) -> 2, 100 | (2, 'a) -> 2, 101 | (2, 'b) -> 3, 102 | (3, 'a) -> 3, 103 | (3, 'b) -> 3 104 | )| 105 | 106 | /////// DFA /////// 107 | 108 | "A DFA" should "equal its expected minimization" in { 109 | assert(dfa1.minimize == dfa1) 110 | assert(dfa1.minimize == dfa1eqMinDFA) 111 | } 112 | 113 | it should "equal the equivalent NFA" in 114 | assert(dfa1 == dfa1eqNFA) 115 | 116 | it should "not be changed by and/or operations with itself" in { 117 | assert((dfa1 & dfa1) == dfa1) 118 | assert((dfa1 | dfa1) == dfa1) 119 | } 120 | 121 | it should "be stable under double negation" in 122 | assert(!(!dfa1) == dfa1) 123 | 124 | it should "be stable under serialization" in { 125 | assert(DFA.fromXml(dfa1.toXml) == dfa1) 126 | assert(DFA.fromXml(dfa1eqNFA.toDFA.toXml) == dfa1eqNFA) 127 | } 128 | 129 | // This may take a while: 130 | it should "be stable under Regular Expression building" in 131 | assert(dfa1.toRegExp.toNFA == dfa1) 132 | 133 | "The result of a DFA minus itself" should "be empty" in 134 | assert((dfa1 \ dfa1).isEmpty) 135 | 136 | "The cut of a DFA with its star" should "be the DFA again" in 137 | assert((dfa1 & (dfa1*).toDFA) == dfa1) 138 | 139 | /////// NFA /////// 140 | 141 | "An NFA" should "equal the equivalent DFA" in 142 | assert(dfa1eqNFA == dfa1) 143 | 144 | it should "not be changed by and/or operations with itself" in { 145 | assert((dfa1eqNFA & dfa1eqNFA) == dfa1eqNFA) 146 | assert((dfa1eqNFA | dfa1eqNFA) == dfa1eqNFA) 147 | } 148 | 149 | it should "be stable under double negation" in 150 | assert(!(!dfa1eqNFA) == dfa1eqNFA) 151 | 152 | it should "be stable under serialization" in { 153 | assert(NFA.fromXml(dfa1eqNFA.toXml) == dfa1eqNFA) 154 | } 155 | 156 | it should "be stable under Regular Expression building" in 157 | assert(dfa1eqNFA.toRegExp.toNFA == dfa1eqNFA) 158 | 159 | "The result of an NFA minus itself" should "be empty" in 160 | assert((dfa1eqNFA \ dfa1eqNFA).isEmpty) 161 | 162 | "The cut of an NFA with its star" should "be the NFA again" in 163 | assert((dfa1eqNFA & (dfa1eqNFA*)) == dfa1eqNFA) 164 | 165 | "RegExp operations" should "behave according to the corresponding automaton operations" in { 166 | val re = dfa1eqNFA.toRegExp 167 | assert((re & re).toNFA == (dfa1eqNFA ++ dfa1eqNFA)) 168 | assert((re + re).toNFA == (dfa1eqNFA | dfa1eqNFA)) 169 | assert((re*).toNFA == (dfa1eqNFA*)) 170 | } 171 | 172 | } 173 | --------------------------------------------------------------------------------