-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscanner.ts
165 lines (158 loc) · 6.6 KB
/
scanner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/**
* @typedef {object} Scanner
* @property {function} next [See here]{@link next}
* @property {function} hasNext [See here]{@link hasNext}
* @property {function} nextLine [See here]{@link nextLine}
* @property {function} hasNextLine [See here]{@link hasNextLine}
* @property {function} nextNumber [See here]{@link nextNumber}
* @property {function} hasNextNumber [See here]{@link hasNextNumber}
*/
/**
* Returns a Java-like [Scanner](https://docs.oracle.com/javase/10/docs/api/java/util/Scanner.html) that uses a source to parse
* @param {string[]|string} source the source to parse
* @returns {Scanner} the Scanner object
* @public
* @throws {TypeError} Will throw an error if the given source is not of type Array.<string> or string
*/
const scanner = (source: string | string[]) => {
const INTEGER_REGEX = /^(\+|-)?\d+((\.\d+)?(e(\+|-)\d+)?)?$/i;
const NO_INTEGER_REGEX = /^(\+|-)?\.\d+(e(\+|-)\d+)?$/i;
let buffer: string;
if (typeof source === 'string') {
buffer = source;
} else if (Array.isArray(source)) {
buffer = source.slice().join('');
} else {
throw new TypeError('Input must be a string or an Array.<string>');
}
/**
* Returns a string that represents the next element in the buffer separated by whitespace or null if the buffer is empty
* @param {boolean} retainElement determines whether the value retrieved is to remain in the buffer
* @returns {?string} a string that represents the next element in the buffer separated by whitespace or null if the buffer is empty
* @private
*/
const next = (retainElement: boolean): string | null => {
if (!buffer)
return null;
const regex = /^\s*([^\s]+)/;
const ret = regex.exec(buffer);
if (ret) {
if (!retainElement)
buffer = buffer.replace(regex, '');
return ret[1];
}
if (!retainElement)
buffer = '';
return ret;
};
/**
* Returns a string that represents the next line in the buffer separated by whitespace or null if the buffer is empty
* @param {boolean} retainElement determines whether the value retrieved is to remain in the buffer
* @returns {?string} a string that represents the next line in the buffer separated by whitespace or null if the buffer is empty
* @private
*/
const nextLine = (retainElement: boolean): string | null => {
if (!buffer)
return null;
const regex = /^([^\r\n]*)(?:\r\n|\n|$)/;
const ret = regex.exec(buffer);
if (ret) {
if (!retainElement)
buffer = buffer.replace(regex, '');
return ret[1];
}
if (!retainElement)
buffer = '';
return null;
};
/**
* Obtains the next non-whitespace element separated by whitespace in the buffer as a number
* @param {boolean} retainElement determines whether the value retrieved is to remain in the buffer
* @returns {number} the next element in the buffer as a number
* @private
* @throws {TypeError} Will throw an error if the next element is not a number
*/
const nextNumber = (retainElement: boolean): number => {
const res = next(retainElement);
if (typeof res === 'string' && (INTEGER_REGEX.test(res) || NO_INTEGER_REGEX.test(res))) // integer/double regex
return parseFloat(res);
else
throw new TypeError('Not a number');
};
return {
/**
* Obtains the next non-whitespace element in the buffer separated by whitespace
* @returns {?string} the next element in the buffer separated by whitespace or null if the buffer is empty
* @public
*/
next(): string | null {
return next(false);
},
/**
* Determines whether a non-whitespace element separated by whitespace exists in the buffer
* @returns {boolean} true if an element exists that is not whitespace in the buffer and is not EOF (End-of-File), false otherwise
* @public
*/
hasNext(): boolean {
const obj = next(true);
return typeof obj === 'string';
},
/**
* Obtains the remaining parts of a line. If {@link next} or {@link nextNumber} is used to capture the last element of a line,
* this will return an empty string. To prevent this, the buffer must be flushed (see example).
* @returns {?string} the remaining parts of a line or null if the buffer is empty
* @public
* @example
* // input simulation where `System.in` contains the following:
* // hello 1
* // happy birthday
* // incorrect way using the same input as above
* var input = scanner(System.in);
* var word = input.next(); // has the value "hello"
* var value = input.nextNumber(); // will have the value 1
* var line = input.nextLine(); // has the value of an empty string ("")
*
* // correct way
* var input = scanner(System.in);
* var word = input.next(); // has the value "hello"
* var value = input.nextNumber(); // has the the value 1
* input.nextLine() // flushes the buffer (removes the newline character "\n")
* var line = input.nextLine(); // correctly gets the next line (has the value "happy birthday")
*/
nextLine(): string | null {
return nextLine(false);
},
/**
* Determines whether there are lines available in the buffer
* @returns {boolean} true if there are lines available in the buffer, false otherwise
* @public
*/
hasNextLine(): boolean {
const obj = nextLine(true);
return typeof obj === 'string';
},
/**
* Obtains the next non-whitespace element separated by whitespace in the buffer as a number
* @returns {number} the next element in the buffer as a number
* @public
* @throws {TypeError} Will throw an error if the next element is not a number
*/
nextNumber(): number {
return nextNumber(false);
},
/**
* Determines whether the next non-whitespace element separated by whitespace is a number
* @returns {boolean} true if the next element is a number, false otherwise
* @public
*/
hasNextNumber(): boolean {
try {
nextNumber(true);
return true;
} catch (err) {
return false;
}
}
};
};
export = scanner;