Skip to content

Instantly share code, notes, and snippets.

@raganwald
Created September 21, 2019 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raganwald/41ae26b93243405136b786298bafe8e9 to your computer and use it in GitHub Desktop.
Save raganwald/41ae26b93243405136b786298bafe8e9 to your computer and use it in GitHub Desktop.
Pushdown Automata
const END = Symbol('end');
class PushdownAutomaton {
constructor(internal = 'start', external = []) {
this.internal = internal;
this.external = external;
this.halted = false;
this.recognized = false;
}
isDeterministic () {
return false;
}
push(token) {
this.external.push(token);
return this;
}
pop() {
this.external.pop();
return this;
}
replace(token) {
this.external[this.external.length - 1] = token;
return this;
}
top() {
return this.external[this.external.length - 1];
}
hasEmptyStack() {
return this.external.length === 0;
}
transitionTo(internal) {
this.internal = internal;
return this;
}
recognize() {
this.recognized = true;
return this;
}
halt() {
this.halted = true;
return this;
}
consume(token) {
const states = [...this[this.internal](token)];
if (this.isDeterministic()) {
return states[0] || [];
} else {
return states;
}
}
fork() {
return new this.constructor(this.internal, this.external.slice(0));
}
static evaluate (string) {
let states = [new this()];
for (const token of string) {
const newStates = states
.flatMap(state => state.consume(token))
.filter(state => state && !state.halted);
if (newStates.length === 0) {
return false;
} else if (newStates.some(state => state.recognized)) {
return true;
} else {
states = newStates;
}
}
return states
.flatMap(state => state.consume(END))
.some(state => state && state.recognized);
}
}
class BinaryPalindrome extends PushdownAutomaton {
* start (token) {
if (token === '0') {
yield this
.fork()
.push(token)
.transitionTo('opening');
}
if (token === '1') {
yield this
.fork()
.push(token)
.transitionTo('opening');
}
if (token === END) {
yield this
.fork()
.recognize();
}
}
* opening (token) {
if (token === '0') {
yield this
.fork()
.push(token);
}
if (token === '1') {
yield this
.fork()
.push(token);
}
if (token === '0' && this.top() === '0') {
yield this
.fork()
.pop()
.transitionTo('closing');
}
if (token === '1' && this.top() === '1') {
yield this
.fork()
.pop()
.transitionTo('closing');
}
}
* closing (token) {
if (token === '0' && this.top() === '0') {
yield this
.fork()
.pop();
}
if (token === '1' && this.top() === '1') {
yield this
.fork()
.pop();
}
if (token === END && this.hasEmptyStack()) {
yield this
.fork()
.recognize();
}
}
}
function test (recognizer, examples) {
for (const example of examples) {
console.log(`'${example}' => ${recognizer.evaluate(example)}`);
}
}
test(BinaryPalindrome, [
'', '0', '00', '11', '0110',
'1001', '0101', '100111',
'01000000000000000010'
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment