]> git.kianting.info Git - clo/blob - src/index.js
efa8b6854087eff3ca3b68153f1a575b614d0dba
[clo] / src / index.js
1 "use strict";
2 Object.defineProperty(exports, "__esModule", { value: true });
3 exports.tokenize = exports.zeroOrOnceDo = exports.notDo = exports.zeroOrMoreDo = exports.orDo = exports.thenDo = exports.charToCodepoint = exports.matchRange = exports.matchAny = exports.match1Char = exports.TokenType = void 0;
4 var fs = require('fs');
5 /**
6 * wrap a x in a `Some(T)`
7 * @param x : variable to be wrapped.
8 * @returns wrapped `x`.
9 */
10 function toSome(x) {
11 return { _tag: "Some", value: x };
12 }
13 /**
14 * The types of Token
15 * NL, // newline
16 *
17 * SP, // half-width space and tab
18 *
19 * ID, // identifier
20 *
21 * STR, // string
22 *
23 * OP, // operator or something like it
24 *
25 * FLO, // float num
26 *
27 * INT, // Integer
28 */
29 var TokenType;
30 (function (TokenType) {
31 TokenType[TokenType["NL"] = 0] = "NL";
32 TokenType[TokenType["SP"] = 1] = "SP";
33 TokenType[TokenType["ID"] = 2] = "ID";
34 TokenType[TokenType["STR"] = 3] = "STR";
35 TokenType[TokenType["OP"] = 4] = "OP";
36 TokenType[TokenType["FLO"] = 5] = "FLO";
37 TokenType[TokenType["INT"] = 6] = "INT";
38 })(TokenType || (exports.TokenType = TokenType = {}));
39 /**
40 * @description
41 * it returns a function which test if the first char of the `remained` part of
42 * the argument of the function is `c`, if it's true, update the `MatchedPair` wrapped
43 * in `Some`. Otherwise, it returns `None`.
44 * * @param c : the char to be test.
45 * @returns the updated `MatchedPair` wrapped in `Some(x)` or `None`.
46 */
47 function match1Char(c) {
48 return (m) => {
49 if (m.remained.length == 0) {
50 return { _tag: "None" };
51 }
52 const charToBeMatched = m.remained[0];
53 if (charToBeMatched === c) {
54 return { _tag: "Some", value: {
55 matched: m.matched + charToBeMatched,
56 remained: m.remained.substring(1)
57 } };
58 }
59 else {
60 return { _tag: "None" };
61 }
62 };
63 }
64 exports.match1Char = match1Char;
65 ;
66 /**
67 *
68 * @param m : the `MatcheePair` to be consumed.
69 * @returns if the length of `m.remained` >= 1; consumes the matchee by 1 char and wraps it in `Some`,
70 * otherwise, returns `None`.
71 */
72 function matchAny(m) {
73 if (m.remained.length >= 1) {
74 return { _tag: "Some", value: {
75 matched: m.matched + m.remained[0],
76 remained: m.remained.substring(1)
77 } };
78 }
79 else {
80 return { _tag: "None" };
81 }
82 }
83 exports.matchAny = matchAny;
84 /**
85 * @description
86 * it returns a function which test if the first char of the `remained` part of
87 * the argument of the function is between `l` and `u`, if it's true, update the `MatchedPair` wrapped
88 * in `Some`. Otherwise, it returns `None`.
89 * * @param l : lower bound char, 1-char string
90 * * @param u : upper bound char, 1-char string
91 * @returns the updated `MatchedPair` wrapped in `Some(x)` or `None`.
92 */
93 function matchRange(l, u) {
94 let lCodepoint = charToCodepoint(l);
95 let uCodepoint = charToCodepoint(u);
96 if (l > u) {
97 throw new Error("Error: the codepoint of `" + l + "` is not smaller than `" + u + "`)");
98 }
99 return (m) => {
100 if (m.remained.length < 1) {
101 return { _tag: "None" };
102 }
103 const charToBeMatched = m.remained[0];
104 const codePointToBeMatched = charToCodepoint(charToBeMatched);
105 if (codePointToBeMatched >= lCodepoint && codePointToBeMatched <= uCodepoint) {
106 return { _tag: "Some", value: {
107 matched: m.matched + charToBeMatched,
108 remained: m.remained.substring(1)
109 } };
110 }
111 else {
112 return { _tag: "None" };
113 }
114 };
115 }
116 exports.matchRange = matchRange;
117 ;
118 /**
119 * convert the one-char string to codepoint.
120 * @param s : the string to code point.
121 * @returns if `s.length > 1` return error; otherwise, return the codepoint of `s`.
122 */
123 function charToCodepoint(s) {
124 if (s.length > 1) {
125 throw new Error("Error: the length of input string for " + s + "is " + s.length + `,
126 however, it should be 1.`);
127 }
128 else {
129 return s.charCodeAt(0);
130 }
131 }
132 exports.charToCodepoint = charToCodepoint;
133 /**
134 * @description thendo(input, f, ...) like
135 * a ==> f
136 * @param input: the wrapped input.
137 * @param f: the function to be applied.
138 *
139 * @returns:the applied wrapped result `MatcheePair`.
140 */
141 function thenDo(input, f) {
142 if (input._tag == "None") {
143 return input;
144 }
145 else {
146 let inner = input.value;
147 return f(inner);
148 }
149 }
150 exports.thenDo = thenDo;
151 /**
152 * @description "or", like the regex `( f1 | f2 )` .
153 * It returns a function `f` of which the argument is`x`.
154 * if `f1(x)` is None, then `f` returns `f2(x)`. Otherwise,
155 * `F` returns `f1(x)`.
156 * @param f1 : 1st function to be compared
157 * @param f2 : 2nd function to be compared
158 * @returns:the combined function
159 */
160 function orDo(f1, f2) {
161 return (x) => {
162 let f1x = (f1(x));
163 {
164 if (f1x._tag == "None") {
165 return f2(x);
166 }
167 else {
168 return f1x;
169 }
170 }
171 };
172 }
173 exports.orDo = orDo;
174 /**
175 * @description repeating matching function `f`
176 * zero or more times, like the asterisk `*` in regex `f*` .
177 * @param f : the function to be repeated 0+ times.
178 * @returns:the combined function
179 */
180 function zeroOrMoreDo(f) {
181 return (x) => {
182 var wrapped_old_x = { _tag: "Some", value: x };
183 var wrapped_new_x = wrapped_old_x;
184 while (wrapped_new_x._tag != "None") {
185 wrapped_old_x = wrapped_new_x;
186 wrapped_new_x = thenDo(wrapped_old_x, f);
187 }
188 ;
189 return wrapped_old_x;
190 };
191 }
192 exports.zeroOrMoreDo = zeroOrMoreDo;
193 /**
194 * @description Not. like the `^` inside regex of [^f].
195 * returns a function `F(x)` such that if `f(x)` is `None`,
196 * returns the x consuming a char; if `f(x)` is not None, F(x)
197 * returns `None`.
198 * @param f: the function forbidden to be matched.
199 * @returns: combined function `F`.
200 */
201 function notDo(f) {
202 return (x) => {
203 let wrapped_x = {
204 _tag: "Some",
205 value: x
206 };
207 let f_x = thenDo(wrapped_x, f);
208 if (f_x._tag != "None") {
209 return { _tag: "None" };
210 }
211 else {
212 return thenDo(wrapped_x, matchAny);
213 }
214 };
215 }
216 exports.notDo = notDo;
217 /**
218 * if `x` is matched by `f` once, returns `f(x)`. Otherwise,
219 * returns x
220 * similar to `?` in regex `f?`.
221 * @param f : the function to be matched
222 * @returns return wrapped f(x)
223 */
224 function zeroOrOnceDo(f) {
225 return (x) => {
226 var wrapped_old_x = { _tag: "Some", value: x };
227 var wrapped_new_x = thenDo(wrapped_old_x, f);
228 if (wrapped_new_x._tag != "None") {
229 return wrapped_new_x;
230 }
231 else {
232 return wrapped_old_x;
233 }
234 };
235 }
236 exports.zeroOrOnceDo = zeroOrOnceDo;
237 function tokenize(input) {
238 var input_matchee_pair = toSome({ matched: "",
239 remained: input });
240 // integer = ([+]|[-])?\d\d*
241 let integer = (x) => {
242 let wrapped_x = toSome(x);
243 let plusMinus = orDo(match1Char('+'), match1Char('-')); // ([+]|[-])
244 let d = matchRange('0', '9'); // \d
245 var result = thenDo(thenDo(thenDo(wrapped_x, zeroOrOnceDo(plusMinus)), d), zeroOrMoreDo(d));
246 if (result._tag == "Some") {
247 result.value.matched_type = TokenType.INT;
248 }
249 return result;
250 };
251 let space = (x) => {
252 let wrapped_x = toSome(x);
253 let s_aux = orDo(match1Char(' '), match1Char('\t')); // (" " | "\t")
254 var result = thenDo(thenDo(wrapped_x, s_aux), zeroOrMoreDo(s_aux));
255 if (result._tag == "Some") {
256 result.value.matched_type = TokenType.SP;
257 }
258 return result;
259 };
260 let newline = (x) => {
261 let wrapped_x = toSome(x);
262 // nl = \r?\n
263 let result = thenDo(thenDo(wrapped_x, zeroOrOnceDo(match1Char('\r'))), match1Char('\n'));
264 if (result._tag == "Some") {
265 result.value.matched_type = TokenType.NL;
266 }
267 return result;
268 };
269 let term = (token_list, x) => {
270 var ln = 1;
271 var col = 0;
272 var old_x = x;
273 let term_list = [newline, space, integer];
274 let term_aux = term_list.reduce((x, y) => orDo(x, y));
275 var new_x = thenDo(old_x, term_aux);
276 while (new_x._tag != "None") {
277 if (new_x.value.matched_type != TokenType.NL) {
278 col += new_x.value.matched.length;
279 token_list.push({ text: new_x.value.matched,
280 type: new_x.value.matched_type,
281 ln: ln,
282 col: col });
283 }
284 else {
285 col = 0;
286 ln += 1;
287 token_list.push({ text: new_x.value.matched,
288 type: new_x.value.matched_type,
289 ln: ln,
290 col: col });
291 }
292 old_x = toSome({ matched: "",
293 remained: new_x.value.remained });
294 new_x = thenDo(old_x, term_aux);
295 }
296 if (old_x.value.remained.length) {
297 console.log(token_list);
298 throw new Error("the code can't be tokenized is near Ln. " + ln + ", Col." + col
299 + ", starting with " + old_x.value.remained.substring(0, 10));
300 }
301 return token_list;
302 };
303 console.log(term([], input_matchee_pair));
304 // TODO: id, string, space, basic operator, 3 marks: @, {, }.
305 }
306 exports.tokenize = tokenize;