Skip to content

Commit 2bdbde5

Browse files
committed
feat: add combiniton functions
1 parent 7bb9cf2 commit 2bdbde5

10 files changed

Lines changed: 262 additions & 135 deletions

File tree

README.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,53 @@
1-
# lxa (WIP)
1+
# lxa
22

33
A lexical analysis / regular expression engine written in TypeScript
44

55
## Get started
66

7-
Please refer to the `/test` directory for details of usage.
7+
### Install with NPM or Yarn
8+
9+
- With NPM
10+
```
11+
$ npm install lxa --save
12+
```
13+
14+
- With Yarn
15+
16+
```
17+
$ yarn add lxa
18+
```
19+
20+
### Quick starting example
21+
22+
```ts
23+
import { stateOps, epsilon, DFA, NFA, concatMultipleStates, unionMultipleStates } from 'lxa';
24+
const { SingleInputState } = stateOps;
25+
26+
// .jpe?g
27+
test('.jpe?g work', () => {
28+
const final = concatMultipleStates(
29+
new SingleInputState('.'),
30+
new SingleInputState('j'),
31+
new SingleInputState('p'),
32+
unionMultipleStates({states: [
33+
new SingleInputState('e'),
34+
new SingleInputState(epsilon),
35+
]}),
36+
new SingleInputState('g', true)
37+
);
38+
const dfa: DFA = new NFA(final).toDFA();
39+
40+
expect(dfa.test('.jpg')).toBe(true);
41+
expect(dfa.test('.jpeg')).toBe(true);
42+
expect(dfa.test('')).toBe(false);
43+
expect(dfa.test('jpg')).toBe(false);
44+
expect(dfa.test('jpeg')).toBe(false);
45+
expect(dfa.test('jp')).toBe(false);
46+
expect(dfa.test('jpgg')).toBe(false);
47+
expect(dfa.test('png')).toBe(false);
48+
})
49+
```
50+
51+
### APIs
52+
53+
API documentation is under working. Feel free to check out the source code.

src/fas/combineStateOps.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { concatMultipleStates, unionMultipleStates } from './combineStateOps';
2+
import * as states from'./stateOps';
3+
const { SingleInputState, ConcatState, UnionState } = states;
4+
5+
6+
test('Should concat multiple state operations', () => {
7+
const one = new SingleInputState('1');
8+
const zero = new SingleInputState('0');
9+
10+
// 10
11+
const oneZero = new ConcatState(one, zero);
12+
// a
13+
const charA = new SingleInputState('a');
14+
// 10a
15+
const oneZeroA = new ConcatState(oneZero, charA);
16+
// 1|0
17+
const oneOrZero = new UnionState(one, zero);
18+
//10a(1|0)
19+
const oneZeroAOneOrZero = new ConcatState(oneZeroA, oneOrZero);
20+
expect(() => concatMultipleStates()).toThrow();
21+
expect(concatMultipleStates(one)).toBe(one);
22+
expect(concatMultipleStates(one, zero)).toEqual(oneZero);
23+
expect(concatMultipleStates(one, zero, charA)).toEqual(oneZeroA);
24+
expect(concatMultipleStates(one, zero, charA, oneOrZero)).toEqual(oneZeroAOneOrZero);
25+
})
26+
27+
test('Should union multiple state operations', () => {
28+
const one = new SingleInputState('1');
29+
const zero = new SingleInputState('0');
30+
31+
// 10
32+
const oneZero = new ConcatState(one, zero);
33+
// a
34+
const charA = new SingleInputState('a');
35+
// 1|0
36+
const oneOrZero = new UnionState(one, zero);
37+
expect(() => unionMultipleStates({states: []})).toThrow();
38+
expect(unionMultipleStates({states: [one]})).toEqual(one);
39+
expect(unionMultipleStates({states: [one, zero]})).toEqual(oneOrZero);
40+
expect(unionMultipleStates({states: [one, zero, charA]})).toEqual(new UnionState(new UnionState(one, zero), charA));
41+
})

src/fas/combineStateOps.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { StateOp, ConcatState, UnionState } from './stateOps';
2+
3+
export function concatMultipleStates(...states: StateOp[]): StateOp {
4+
if (states.length === 0) {
5+
throw new Error('Argument must be at least one state');
6+
}
7+
if (states.length === 1) {
8+
return states[0];
9+
}
10+
return states.reduce((stateA, stateB) => {
11+
return new ConcatState(stateA, stateB);
12+
})
13+
}
14+
15+
export function unionMultipleStates(
16+
{states, accepted = false}: {states: StateOp[], accepted?: boolean}
17+
): StateOp {
18+
if (states.length === 0) {
19+
throw new Error('Argument must be at least one state');
20+
}
21+
if (states.length === 1) {
22+
return states[0];
23+
}
24+
return states.reduce((stateA, stateB, currentIndex) => {
25+
return new UnionState(stateA, stateB, (currentIndex === states.length - 1) && accepted);
26+
})
27+
}

src/fas/dfa.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { State, Epsilon } from './state';
1+
import { State } from './state';
22
import { eqSet, mergeSetInto } from '../utils';
3+
import { Epsilon } from './epsilon';
34

45

56
export class DFAStatesSet {

src/fas/epsilon.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export class Epsilon {}
2+
const epsilon = new Epsilon();
3+
4+
export default epsilon;

src/fas/nfa.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StateOp } from './state';
1+
import { StateOp } from './stateOps';
22
import { DFAStatesSet, DFA } from './dfa';
33

44
export class NFA {

src/fas/state.ts

Lines changed: 3 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { mergeSetInto, eqSet } from '../utils';
1+
import { mergeSetInto } from '../utils';
2+
import epsilon, { Epsilon } from './epsilon';
23

3-
export class Epsilon {}
4-
export const epsilon = new Epsilon();
5-
6-
type InputType = string | Epsilon;
4+
export type InputType = string | Epsilon;
75

86
export class State {
97
public nextStatesMap: Map<InputType, Set<State>>;
@@ -44,104 +42,3 @@ export class State {
4442
return epsilonSet;
4543
}
4644
}
47-
48-
49-
export class StateOp {
50-
protected start: State | null;
51-
protected end: State | null;
52-
constructor() {
53-
this.start = null;
54-
this.end = null;
55-
}
56-
57-
public getStartState() {
58-
return this.start;
59-
}
60-
61-
public getEndState() {
62-
return this.end;
63-
}
64-
65-
public setNext(input: InputType, state: StateOp) {
66-
const start = state.getStartState();
67-
if (start && this.end) {
68-
this.end.setNext(input, start);
69-
} else if (!start) {
70-
throw new Error('Start state of param state is null');
71-
} else if (!this.end) {
72-
throw new Error('this.end is null')
73-
}
74-
}
75-
76-
}
77-
78-
export class SingleInputState extends StateOp {
79-
constructor(input: InputType, accepted: boolean = false) {
80-
super();
81-
this.start = new State();
82-
this.end = new State(accepted);
83-
this.start.setNext(input, this.end);
84-
}
85-
}
86-
87-
88-
export class ConcatState extends StateOp {
89-
private a: StateOp;
90-
private b: StateOp;
91-
constructor(a: StateOp, b: StateOp) {
92-
super();
93-
this.a = a;
94-
this.b = b;
95-
this.start = this.a.getStartState();
96-
this.a.setNext(epsilon, this.b);
97-
this.end = this.b.getEndState();
98-
}
99-
}
100-
101-
export class UnionState extends StateOp {
102-
private a: StateOp;
103-
private b: StateOp;
104-
constructor(a: StateOp, b: StateOp, accepted: boolean = false) {
105-
super();
106-
this.a = a;
107-
this.b = b;
108-
this.start = new State();
109-
this.end = new State(accepted);
110-
for (const arg of [this.a, this.b]) {
111-
const argOpStart = arg.getStartState();
112-
const argOpEnd = arg.getEndState();
113-
if (argOpStart) {
114-
this.start.setNext(epsilon, argOpStart)
115-
} else {
116-
throw new Error('start of argOp is null');
117-
}
118-
if (argOpEnd) {
119-
argOpEnd.setNext(epsilon, this.end);
120-
} else {
121-
throw new Error('end of argOp is null')
122-
}
123-
}
124-
}
125-
}
126-
127-
export class ClosureState extends StateOp {
128-
private a: StateOp;
129-
constructor(a: StateOp, accepted: boolean = false) {
130-
super();
131-
this.a = a;
132-
this.start = new State();
133-
this.end = new State(accepted);
134-
const aStart = this.a.getStartState();
135-
const aEnd = this.a.getEndState();
136-
if (aStart) {
137-
this.start.setNext(epsilon, aStart);
138-
} else {
139-
throw new Error('start of a is null');
140-
}
141-
this.start.setNext(epsilon, this.end);
142-
if (aEnd) {
143-
aEnd.setNext(epsilon, this.end);
144-
aEnd.setNext(epsilon, this.start);
145-
}
146-
}
147-
}

src/fas/stateOps.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { State, InputType} from './state';
2+
import epsilon from './epsilon';
3+
4+
export class StateOp {
5+
protected start: State | null;
6+
protected end: State | null;
7+
constructor() {
8+
this.start = null;
9+
this.end = null;
10+
}
11+
12+
public getStartState() {
13+
return this.start;
14+
}
15+
16+
public getEndState() {
17+
return this.end;
18+
}
19+
20+
public setNext(input: InputType, state: StateOp) {
21+
const start = state.getStartState();
22+
if (start && this.end) {
23+
this.end.setNext(input, start);
24+
} else if (!start) {
25+
throw new Error('Start state of param state is null');
26+
} else if (!this.end) {
27+
throw new Error('this.end is null')
28+
}
29+
}
30+
31+
}
32+
33+
export class SingleInputState extends StateOp {
34+
constructor(input: InputType, accepted: boolean = false) {
35+
super();
36+
this.start = new State();
37+
this.end = new State(accepted);
38+
this.start.setNext(input, this.end);
39+
}
40+
}
41+
42+
43+
export class ConcatState extends StateOp {
44+
private a: StateOp;
45+
private b: StateOp;
46+
constructor(a: StateOp, b: StateOp) {
47+
super();
48+
this.a = a;
49+
this.b = b;
50+
this.start = this.a.getStartState();
51+
this.a.setNext(epsilon, this.b);
52+
this.end = this.b.getEndState();
53+
}
54+
}
55+
56+
export class UnionState extends StateOp {
57+
private a: StateOp;
58+
private b: StateOp;
59+
constructor(a: StateOp, b: StateOp, accepted: boolean = false) {
60+
super();
61+
this.a = a;
62+
this.b = b;
63+
this.start = new State();
64+
this.end = new State(accepted);
65+
for (const arg of [this.a, this.b]) {
66+
const argOpStart = arg.getStartState();
67+
const argOpEnd = arg.getEndState();
68+
if (argOpStart) {
69+
this.start.setNext(epsilon, argOpStart)
70+
} else {
71+
throw new Error('start of argOp is null');
72+
}
73+
if (argOpEnd) {
74+
argOpEnd.setNext(epsilon, this.end);
75+
} else {
76+
throw new Error('end of argOp is null')
77+
}
78+
}
79+
}
80+
}
81+
82+
export class ClosureState extends StateOp {
83+
private a: StateOp;
84+
constructor(a: StateOp, accepted: boolean = false) {
85+
super();
86+
this.a = a;
87+
this.start = new State();
88+
this.end = new State(accepted);
89+
const aStart = this.a.getStartState();
90+
const aEnd = this.a.getEndState();
91+
if (aStart) {
92+
this.start.setNext(epsilon, aStart);
93+
} else {
94+
throw new Error('start of a is null');
95+
}
96+
this.start.setNext(epsilon, this.end);
97+
if (aEnd) {
98+
aEnd.setNext(epsilon, this.end);
99+
aEnd.setNext(epsilon, this.start);
100+
}
101+
}
102+
}

src/index.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
import { SingleInputState, UnionState, ClosureState, ConcatState } from './fas/state';
2-
import { DFA } from './fas/dfa';
3-
import { NFA } from './fas/nfa';
4-
5-
// (1|0)*1
6-
7-
const one = new SingleInputState('1');
8-
const zero = new SingleInputState('0');
9-
const oneOrZero = new UnionState(one, zero);
10-
const oneOrZeroStar = new ClosureState(oneOrZero);
11-
const final = new ConcatState(oneOrZeroStar, new SingleInputState('1', true))
12-
13-
const dfa: DFA = new NFA(final).toDFA();
14-
15-
console.log(dfa.test('1'));
16-
console.log('-------------', dfa.test('00000000001'));
17-
console.log('-------------', dfa.test('10101010000101'));
18-
console.log('-------------', dfa.test('01010011000101011'));
19-
console.log('-------------', dfa.test('010100110001010110'));
20-
console.log('-------------', dfa.test(''));
21-
console.log('-------------', dfa.test('0000000000000000'));
22-
console.log('-------------', dfa.test('11111111111'));
1+
import * as stateOps from './fas/stateOps';
2+
3+
export { stateOps };
4+
export * from './fas/combineStateOps';
5+
export { State } from './fas/state';
6+
export { default as epsilon } from './fas/epsilon';
7+
export { NFA } from './fas/nfa';
8+
export { DFA, DFAStatesSet } from './fas/dfa';

0 commit comments

Comments
 (0)