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