├── 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 |
--------------------------------------------------------------------------------