Skip to content

Commit c46bfe5

Browse files
authored
Merge pull request #23 from aneshodza/feature/allow-reruns
Make run revert correctly to allows reruns
2 parents a33a414 + 0939451 commit c46bfe5

File tree

7 files changed

+172
-15
lines changed

7 files changed

+172
-15
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ It only checks for these two things on the node it's currently on (while being i
1313
---
1414

1515
## Usage
16+
**Important:** This just goes over creating an automaton using every configuration option. [Here](./tests/integration_tests/README.md) you can find real use-cases.
1617
Creating a pushdown automaton involves following steps:
1718
1. Creating the automaton instance
1819
2. Creating all the states
@@ -93,6 +94,7 @@ It holds a message, if it was sucessful and the return code. Following codes are
9394
|1 |The automaton didn't terminate in a valid end state |
9495
|2 |The automaton didn't find a valid transition, so went to a sink state |
9596
|Exception |The automaton isn't deterministic |
97+
|Exception |The input word is undefined |
9698

9799

98100
## Other links
@@ -102,4 +104,4 @@ It holds a message, if it was sucessful and the return code. Following codes are
102104
- [Feature requests](./docs/FEATURE_REQUESTS.md)
103105

104106
#### Credits
105-
This library was written and published by [Anes Hodza](aneshodza.ch)
107+
This library was written and published by [Anes Hodza](https://aneshodza.ch)

src/PushdownAutomaton.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,47 @@ import TerminationMessage from "./TerminationMessage";
77
*/
88
// eslint-disable-next-line @typescript-eslint/no-unused-vars
99
class PushdownAutomaton {
10-
stack: Stack = new Stack();
10+
stack: Stack;
1111
startState: State | null = null;
1212
endStates: Array<State> = [];
1313

1414
currentState: State | null = null;
15-
inputWord: string = "";
15+
inputWord: string | undefined;
16+
defaultStackTocken: string = "$";
1617
operation: ((automata: PushdownAutomaton) => void) | null = null;
1718

1819
/**
1920
* Creates an instance of `PushdownAutomaton`.
2021
* @param {string} inputWord - The input word to be processed by the automaton.
22+
* @param {string} defaultStackTocken - The default token to be used as the initial value of the stack.
2123
*/
22-
constructor(inputWord: string, defaultStackTocken: string = "$") {
24+
constructor(inputWord: string|undefined = undefined, defaultStackTocken: string = "$") {
2325
this.inputWord = inputWord;
24-
this.stack.pop();
25-
this.stack.push(defaultStackTocken);
26+
this.defaultStackTocken = defaultStackTocken;
27+
this.stack = new Stack(defaultStackTocken);
2628
}
2729

2830
/**
2931
* Runs the automaton until the input word is fully processed or a failure occurs.
3032
* @returns {TerminationMessage} An object describing the result of the execution.
3133
*/
32-
run(): TerminationMessage {
34+
run(inputWord: string|undefined = undefined): TerminationMessage {
35+
if (inputWord !== undefined) {
36+
this.inputWord = inputWord;
37+
} else if (this.inputWord === undefined) {
38+
throw new Error("No input word provided");
39+
}
40+
41+
this.currentState = this.startState;
42+
this.stack = new Stack(this.defaultStackTocken);
43+
3344
while (this.inputWord.length > 0) {
3445
const returnValue = this.step();
3546

47+
this.operation?.call(this, this);
3648
if (!returnValue.successful) {
3749
return returnValue;
3850
}
39-
40-
this.operation?.call(this, this);
4151
}
4252

4353
if (this.endStates.includes(this.currentState!)) {
@@ -59,8 +69,8 @@ class PushdownAutomaton {
5969
* @returns {TerminationMessage} An object detailing the outcome of the step.
6070
*/
6171
step(): TerminationMessage {
62-
const currentToken = this.inputWord.charAt(0);
63-
this.inputWord = this.inputWord.slice(1);
72+
const currentToken = this.inputWord!.charAt(0);
73+
this.inputWord = this.inputWord!.slice(1);
6474

6575
if (
6676
this.currentState!.allTransitionFunctions(currentToken, this.stack.last())
@@ -138,7 +148,7 @@ class PushdownAutomaton {
138148
`Current node: ${this.currentState!.name} \n\n` +
139149
`Stack: \n` +
140150
`${this.stack.stackClone().reverse().join("\n")}\n\n` +
141-
`Ugly stack [${this.stack.stackClone().reverse().join(", ")}]`,
151+
`Ugly stack [${this.stack.stackClone().join(", ")}]`,
142152
);
143153
}
144154

src/Stack.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ class Stack {
88
/**
99
* Constructs a stack instance with an initial symbol.
1010
*/
11-
constructor() {
12-
this.stackValues = ["$"];
11+
constructor(defaultValue: string = "$") {
12+
this.stackValues = [defaultValue];
1313
}
1414

1515
/**

tests/integration_tests/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Real use-cases
2+
In this folder you can find tests of real applications of pushdown automata.
3+
4+
## Balanced parantheses
5+
This pushdown automaton can check if the input word has balanced parantheses. This is how the language is defined:
6+
$$`
7+
L = \{ w \mid w \text{ is a string of balanced parentheses} \}
8+
`$$
9+
**Accepted words that are tested:**
10+
```
11+
-> (()())
12+
-> ()()()
13+
-> ((()))
14+
-> ()(()())
15+
```
16+
17+
**Not accepted words that are tested:**
18+
```
19+
-> (()()
20+
-> ())
21+
-> ((())
22+
-> )()()
23+
```
24+
25+
$$`
26+
\text{The automaton can be defined as such:} \newline
27+
M = (\{q_1, q_2\}, \{(, )\}, \{\$, 1, L\}, \delta, q_1, \$, F)
28+
`$$
29+
30+
$$`
31+
\text{Following transition functions are defined:} \newline
32+
\delta(q_1, "(", "\$") = \{(q_1, "\$L")\} \newline
33+
\delta(q_1, "(", "L") = \{(q_1, "L1")\} \newline
34+
\delta(q_1, "(", "1") = \{(q_1, "11")\} \newline
35+
\delta(q_1, ")", "1") = \{(q_1, \epsilon)\} \newline
36+
\delta(q_1, ")", "L") = \{(q_2, \epsilon)\} \newline
37+
\delta(q_2, "(", "\$") = \{(q_1, "\$L")\}
38+
`$$
39+
40+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import PushdownAutomaton from "../../src/PushdownAutomaton";
2+
import State from "../../src/State";
3+
import Transition from "../../src/TransitionFunction";
4+
5+
/*
6+
* In this test a pushdown automaton that checks following language is tested:
7+
* L = { w | w contains balanced parentheses }
8+
*/
9+
10+
let automaton: PushdownAutomaton;
11+
beforeEach(() => {
12+
// Initialize the automaton
13+
automaton = new PushdownAutomaton();
14+
15+
// Initialize the start and end states
16+
let startState = new State("q1");
17+
let endState = new State("q2");
18+
automaton.setStartSate(startState);
19+
automaton.addEndState(endState);
20+
21+
// Add transition functions to start state
22+
let transition1 = new Transition("(", startState, "$", ["$", "L"]);
23+
startState.addTransitionFunction(transition1);
24+
let transition2 = new Transition("(", startState, "L", ["L", "1"]);
25+
startState.addTransitionFunction(transition2);
26+
let transition3 = new Transition("(", startState, "1", ["1", "1"]);
27+
startState.addTransitionFunction(transition3);
28+
let transition4 = new Transition(")", startState, "1", []);
29+
startState.addTransitionFunction(transition4);
30+
let transition5 = new Transition(")", endState, "L", []);
31+
startState.addTransitionFunction(transition5);
32+
33+
// Add transition functions to end state
34+
let transition6 = new Transition("(", startState, "$", ["$", "L"]);
35+
endState.addTransitionFunction(transition6);
36+
});
37+
38+
test("Balanced parentheses", () => {
39+
let result = automaton.run("(()())");
40+
expect(result.successful).toBe(true);
41+
42+
let result2 = automaton.run("()()()");
43+
expect(result2.successful).toBe(true);
44+
45+
let result3 = automaton.run("((()))");
46+
expect(result3.successful).toBe(true);
47+
48+
let result4 = automaton.run("()(()())");
49+
expect(result4.successful).toBe(true);
50+
});
51+
52+
test("Unbalanced parentheses", () => {
53+
let result = automaton.run("(()()");
54+
expect(result.successful).toBe(false);
55+
56+
let result2 = automaton.run("())");
57+
expect(result2.successful).toBe(false);
58+
59+
let result3 = automaton.run("((())");
60+
expect(result3.successful).toBe(false);
61+
62+
let result4 = automaton.run(")()()");
63+
expect(result4.successful).toBe(false);
64+
});

tests/unit_tests/pushdown_automaton.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,41 @@ test("Throws an exception if it finds two matching transitions", () => {
162162
test("Allows the user to define a default stack token", () => {
163163
let automata = new PushdownAutomaton("test", "default");
164164
expect(automata.stack.stackValues).toStrictEqual(["default"]);
165-
})
165+
});
166+
167+
test("Allows creating an automaton without any input parameters", () => {
168+
automata = new PushdownAutomaton();
169+
expect(automata).toBeDefined();
170+
expect(automata.inputWord).toBeUndefined();
171+
});
172+
173+
test("run() throws if no input word is provided", () => {
174+
automata = new PushdownAutomaton();
175+
expect(() => automata.run()).toThrow("No input word provided");
176+
});
177+
178+
test("Allow run() to overwrite the input word", () => {
179+
automata.run("new word");
180+
expect(automata.inputWord).toBe("ew word");
181+
});
182+
183+
test("run() in sucession works", () => {
184+
let transition5 = new Transition("", otherState, "e", ["d"]);
185+
let transition2 = new Transition("e", oneState, "d", ["d"]);
186+
let transition3 = new Transition("s", otherState, "d", ["d"]);
187+
let transition4 = new Transition("t", oneState, "d", ["e"]);
188+
189+
otherState.addTransitionFunction(transition2);
190+
oneState.addTransitionFunction(transition3);
191+
otherState.addTransitionFunction(transition4);
192+
193+
oneState.addTransitionFunction(transition5);
194+
195+
automata.run();
196+
197+
expect(automata.run("test")).toStrictEqual({
198+
reason: "Word accepted",
199+
successful: true,
200+
code: 0,
201+
});
202+
});

tests/unit_tests/stack.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ test("Initializes the stack with a $", () => {
99
expect(stack.stackValues).toEqual(["$"]);
1010
});
1111

12+
test("Allows for a custom initial value", () => {
13+
expect(new Stack("a").stackValues).toEqual(["a"]);
14+
});
15+
1216
test("Pushes a value onto the stack", () => {
1317
stack.push("a");
1418
expect(stack.stackValues).toEqual(["$", "a"]);

0 commit comments

Comments
 (0)