week 4: added html comment
[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 prefix and
16 //      suffix 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 (prefix, suffix) {
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 prefix and suffix strings are not provided, supply defaults.
55
56     if (typeof prefix !== 'string') {
57         prefix = '';
58     }
59     if (typeof suffix !== 'string') {
60         suffix = '';
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                                 // make sure next character is not an identifier
92                                         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
93                                                 (c >= '0' && c <= '9') || c === '_' || c === '-' || c === '/' || c === '?' || c === '!') {
94                                                 str += c;
95                                                 i += 1;
96                                                 make('name', str).error("Bad identifier");
97                                         }
98                                 } else {
99                     break;
100                 }
101             }
102             result.push(make('name', str));
103
104 // number.
105
106 // A number cannot start with a decimal point. It must start with a digit,
107 // possibly '0'.
108
109         } else if (c >= '0' && c <= '9') {
110             str = c;
111             i += 1;
112
113 // Look for more digits.
114
115             for (;;) {
116                 c = this.charAt(i);
117                 if (c < '0' || c > '9') {
118                     break;
119                 }
120                 i += 1;
121                 str += c;
122             }
123
124 // Make sure the next character is not a letter.
125
126             if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '_') {
127                 str += c;
128                 i += 1;
129                 make('number', str).error("Bad number");
130             }
131
132 // Convert the string value to a number. If it is finite, then it is a good
133 // token.
134
135             n = +str;
136             if (isFinite(n)) {
137                 result.push(make('number', n));
138             } else {
139                 make('number', str).error("Bad number");
140             }
141
142 // comment.
143
144         } else if (c === ';') {
145             for (;;) {
146                 c = this.charAt(i);
147                 if (c === '\n' || c === '\r' || c === '') {
148                     break;
149                 }
150                 i += 1;
151             }
152
153 // multi-char operator.
154
155         } else if (prefix.indexOf(c) >= 0) {
156             str = c;
157             i += 1;
158             while (i < length) {
159                 c = this.charAt(i);
160                 if (suffix.indexOf(c) < 0) {
161                     break;
162                 }
163                 str += c;
164                 i += 1;
165             }
166             result.push(make('operator', str));
167
168 // single-character operator.
169
170         } else {
171             i += 1;
172             result.push(make('operator', c));
173             c = this.charAt(i);
174         }
175     }
176     return result;
177 };
178
179