]> git.kianting.info Git - clo/blob - src/index.ts
add a basic tokenzier function & an int tokenizer
[clo] / src / index.ts
1 var fs = require('fs');
2
3 export type Some<T> = { _tag: "Some"; value: T };
4 export type None = {_tag: "None"};
5
6
7 /**
8 * wrap a x in a `Some(T)`
9 * @param x : variable to be wrapped.
10 * @returns wrapped `x`.
11 */
12 function toSome<T>(x: T): Some<T>{
13 return { _tag: "Some", value: x};
14 }
15 /**
16 * @description Like the `Some(a)` and `None` in Rust.
17 *
18 * @example
19 * ```ts
20 * let exam1 : Maybe<Number> = { _tag: "Some", value: 12 };
21 * let exam2 : Maybe<Number> = None;
22 * ```
23 */
24 export type Maybe<T> = Some<T> | None;
25
26
27 /**
28 * @description
29 * the pair of the string to be matched later and the string that have been matched
30 * @param matched : string have been matched
31 * @param remained : string will be tested whether it'll be matched.
32 */
33 export type MatcheePair = {matched : string; remained : string};
34
35 /**
36 * @description
37 * it returns a function which test if the first char of the `remained` part of
38 * the argument of the function is `c`, if it's true, update the `MatchedPair` wrapped
39 * in `Some`. Otherwise, it returns `None`.
40 * * @param c : the char to be test.
41 * @returns the updated `MatchedPair` wrapped in `Some(x)` or `None`.
42 */
43 export function match1Char(c : string) : (m: MatcheePair) => Maybe<MatcheePair> {
44 return (m : MatcheePair)=>{
45 if (m.remained.length == 0){
46 return { _tag: "None" };
47 }
48 const charToBeMatched = m.remained[0];
49 if (charToBeMatched === c){
50 return {_tag: "Some", value :{
51 matched : m.matched + charToBeMatched,
52 remained : m.remained.substring(1)}};
53 }
54 else{
55 return {_tag: "None"};
56 }
57 }
58 };
59
60 /**
61 *
62 * @param m : the `MatcheePair` to be consumed.
63 * @returns if the length of `m.remained` >= 1; consumes the matchee by 1 char and wraps it in `Some`,
64 * otherwise, returns `None`.
65 */
66 export function matchAny(m : MatcheePair) : Maybe<MatcheePair>{
67 if (m.remained.length >= 1){
68 return {_tag: "Some", value :{
69 matched : m.matched + m.remained[0],
70 remained : m.remained.substring(1)}};
71 }else{
72 return {_tag: "None"};
73 }
74 }
75
76 /**
77 * @description
78 * it returns a function which test if the first char of the `remained` part of
79 * the argument of the function is between `l` and `u`, if it's true, update the `MatchedPair` wrapped
80 * in `Some`. Otherwise, it returns `None`.
81 * * @param l : lower bound char, 1-char string
82 * * @param u : upper bound char, 1-char string
83 * @returns the updated `MatchedPair` wrapped in `Some(x)` or `None`.
84 */
85 export function matchRange(l : string, u : string) : (m: MatcheePair) => Maybe<MatcheePair> {
86 let lCodepoint = charToCodepoint(l);
87 let uCodepoint = charToCodepoint(u);
88 if (l > u){
89 throw new Error("Error: the codepoint of `"+l+"` is not smaller than `"+u+"`)");
90 }
91 return (m : MatcheePair)=>{
92 if (m.remained.length < 1){
93 return {_tag : "None"};
94 }
95 const charToBeMatched = m.remained[0];
96 const codePointToBeMatched = charToCodepoint(charToBeMatched);
97 if (codePointToBeMatched >= lCodepoint && codePointToBeMatched <= uCodepoint){
98 return {_tag: "Some", value :{
99 matched : m.matched + charToBeMatched,
100 remained : m.remained.substring(1)}};
101 }
102 else{
103 return {_tag: "None"};
104 }
105 }
106 };
107
108 /**
109 * convert the one-char string to codepoint.
110 * @param s : the string to code point.
111 * @returns if `s.length > 1` return error; otherwise, return the codepoint of `s`.
112 */
113 export function charToCodepoint(s : string): number{
114 if (s.length > 1){
115 throw new Error("Error: the length of input string for "+s+ "is "+s.length+`,
116 however, it should be 1.`);
117 }else{
118 return s.charCodeAt(0);
119 }
120 }
121
122 /**
123 * @description thendo(input, f, ...) like
124 * a ==> f
125 * @param input: the wrapped input.
126 * @param f: the function to be applied.
127 *
128 * @returns:the applied wrapped result `MatcheePair`.
129 */
130 export function thenDo<T>(input : Maybe<T>, f : Function) : Maybe<T>{
131 if (input._tag == "None"){
132 return input;
133 }
134 else{
135 let inner = input.value;
136 return f(inner);
137 }
138 }
139
140 /**
141 * @description "or", like the regex `( f1 | f2 )` .
142 * It returns a function `f` of which the argument is`x`.
143 * if `f1(x)` is None, then `f` returns `f2(x)`. Otherwise,
144 * `F` returns `f1(x)`.
145 * @param f1 : 1st function to be compared
146 * @param f2 : 2nd function to be compared
147 * @returns:the combined function
148 */
149 export function orDo<T>(f1 : Function, f2: Function) : (x : T ) => Maybe<T>{
150 return (x) => {
151 let f1x : Maybe<T> = (f1(x));
152 {
153 if (f1x._tag == "None"){
154 return f2(x);
155 }
156 else{
157 return f1x;
158 }
159 }
160 };
161 }
162
163
164 /**
165 * @description repeating matching function `f`
166 * zero or more times, like the asterisk `*` in regex `f*` .
167 * @param f : the function to be repeated 0+ times.
168 * @returns:the combined function
169 */
170 export function zeroOrMoreDo<T>(f : Function): (x : T) => Maybe<T>{
171 return (x)=>{
172 var wrapped_old_x : Maybe<T> = {_tag: "Some", value : x};
173 var wrapped_new_x : Maybe<T> = wrapped_old_x;
174
175 while (wrapped_new_x._tag != "None"){
176 wrapped_old_x = wrapped_new_x;
177 wrapped_new_x = thenDo(wrapped_old_x, f);
178 };
179
180 return wrapped_old_x;
181 };
182 }
183
184 /**
185 * @description Not. like the `^` inside regex of [^f].
186 * returns a function `F(x)` such that if `f(x)` is `None`,
187 * returns the x consuming a char; if `f(x)` is not None, F(x)
188 * returns `None`.
189 * @param f: the function forbidden to be matched.
190 * @returns: combined function `F`.
191 */
192 export function notDo<T>(f : Function): (x : T) => Maybe<T>{
193 return (x)=>{
194 let wrapped_x : Maybe<T> = {
195 _tag : "Some",
196 value : x
197 };
198 let f_x = thenDo(wrapped_x, f);
199
200 if (f_x._tag != "None"){
201 return {_tag:"None"};
202 }else{
203 return thenDo(wrapped_x, matchAny);
204 }
205 };
206 }
207
208 /**
209 * if `x` is matched by `f` once, returns `f(x)`. Otherwise,
210 * returns x
211 * similar to `?` in regex `f?`.
212 * @param f : the function to be matched
213 * @returns return wrapped f(x)
214 */
215 export function zeroOrOnceDo<T>(f : Function): (x : T) => Maybe<T>{
216 return (x)=>{
217 var wrapped_old_x : Maybe<T> = {_tag: "Some", value : x};
218 var wrapped_new_x = thenDo(wrapped_old_x, f);
219
220 if (wrapped_new_x._tag != "None"){
221 return wrapped_new_x;
222 }else{
223 return wrapped_old_x;
224 }
225 };
226 }
227
228
229 export function tokenize(input : string){
230 var input_matchee_pair : Maybe<MatcheePair> = toSome(
231 {matched:"",
232 remained: input});
233
234 // integer = ([+]|[-])\d\d?
235 let integer = (x : MatcheePair) =>
236 { let wrapped_x = toSome(x);
237 let plusMinus = orDo(match1Char('+'), match1Char('-')); // ([+]|[-])
238 let d = matchRange('0','9'); // \d
239 return thenDo(thenDo(thenDo(wrapped_x,
240 zeroOrOnceDo(plusMinus)),d),
241 zeroOrMoreDo(d));
242 }
243 console.log(input+", result: ");
244 console.log(thenDo(input_matchee_pair, integer));
245
246 }
247
248 tokenize("+123");
249 tokenize("123");
250 tokenize("-123");
251 tokenize(" 123");
252 tokenize("c123");