fromlists... -> fromlistzippers...
[lambda.git] / code / tokens.js
1 // Based on tokens.js
2 //      2009-05-17
3 //      (c) 2006 Douglas Crockford
4
5 //      Produce an array of simple token objects from a string.
6 //      A simple token object contains these members:
7 //           type: 'name', 'string', 'number', 'operator'
8 //           value: string or number value of the token
9 //           from: index of first character of the token
10 //           to: index of the last character + 1
11
12 //      Comments of the ; type are ignored.
13
14 //      Operators are by default single characters. Multicharacter
15 //      operators can be made by supplying a string of multi_start and
16 //      multi_continue characters.
17 //      characters. For example,
18 //           '<>+-&', '=>&:'
19 //      will match any of these:
20 //           <=  >>  >>>  <>  >=  +: -: &: &&: &&
21
22 /*jslint onevar: false
23  */
24
25 String.prototype.tokens = function (multi_start, multi_continue) {
26     var c;                      // The current character.
27     var from;                   // The index of the start of the token.
28     var i = 0;                  // The index of the current character.
29     var length = this.length;
30     var n;                      // The number value.
31     var q;                      // The quote character.
32     var str;                    // The string value.
33
34     var result = [];            // An array to hold the results.
35
36     var make = function (type, value) {
37
38 // Make a token object.
39
40         return {
41             type: type,
42             value: value,
43             from: from,
44             to: i
45         };
46     };
47
48 // Begin tokenization. If the source string is empty, return nothing.
49
50     if (!this) {
51         return;
52     }
53
54 // If multi_start and multi_continue strings are not provided, supply defaults.
55
56     if (typeof multi_start !== 'string') {
57         multi_start = '';
58     }
59     if (typeof multi_continue !== 'string') {
60         multi_continue = '';
61     }
62
63
64 // Loop through this text, one character at a time.
65
66     c = this.charAt(i);
67     while (c) {
68         from = i;
69
70 // Ignore whitespace.
71
72         if (c <= ' ') {
73             i += 1;
74             c = this.charAt(i);
75
76 // name.
77
78         } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
79             str = c;
80             i += 1;
81             for (;;) {
82                 c = this.charAt(i);
83                 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
84                         (c >= '0' && c <= '9') || c === '_' || c === '-' || c === '/') {
85                     str += c;
86                     i += 1;
87                                 } else if (c === '?' || c === '!') {
88                                 // should only be terminal
89                     str += c;
90                     i += 1;
91                                         c = this.charAt(i);
92                                 // make sure next character is not an identifier
93                                         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
94                                                 (c >= '0' && c <= '9') || c === '_' || c === '-' || c === '/' || c === '?' || c === '!') {
95                                                 str += c;
96                                                 i += 1;
97                                                 make('name', str).error("Bad identifier");
98                                         }
99                                 } else {
100                     break;
101                 }
102             }
103             result.push(make('name', str));
104
105 // number.
106
107 // A number cannot start with a decimal point. It must start with a digit,
108 // possibly '0'.
109
110         } else if (c >= '0' && c <= '9') {
111             str = c;
112             i += 1;
113
114 // Look for more digits.
115
116             for (;;) {
117                 c = this.charAt(i);
118                 if (c < '0' || c > '9') {
119                     break;
120                 }
121                 i += 1;
122                 str += c;
123             }
124
125 // Make sure the next character is not a letter.
126
127             if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '_') {
128                 str += c;
129                 i += 1;
130                 make('number', str).error("Bad number");
131             }
132
133 // Convert the string value to a number. If it is finite, then it is a good
134 // token.
135
136             n = +str;
137             if (isFinite(n)) {
138                 result.push(make('number', n));
139             } else {
140                 make('number', str).error("Bad number");
141             }
142
143 // comment.
144
145         } else if (c === ';') {
146             for (;;) {
147                 c = this.charAt(i);
148                 if (c === '\n' || c === '\r' || c === '') {
149                     break;
150                 }
151                 i += 1;
152             }
153
154 // multi-char operator.
155
156         } else if (multi_start.indexOf(c) >= 0) {
157             str = c;
158             i += 1;
159             while (i < length) {
160                 c = this.charAt(i);
161                 if (multi_continue.indexOf(c) < 0) {
162                     break;
163                 }
164                 str += c;
165                 i += 1;
166             }
167             result.push(make('operator', str));
168
169 // single-character operator.
170
171         } else {
172             i += 1;
173             result.push(make('operator', c));
174             c = this.charAt(i);
175         }
176     }
177     return result;
178 };
179
180