6 NOT_EQUALS = 'NOT_EQUALS',
10 IDENTIFIER = 'IDENTIFIER',
11 OPEN_PAREN = 'OPEN_PAREN',
12 CLOSE_PAREN = 'CLOSE_PAREN',
13 EXPRESSION = 'EXPRESSION',
21 const isAlpha = (char: string) => {
22 if (!char) return false;
23 const code = char.charCodeAt(0);
24 return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
27 const isAlphaNumeric = (char: string) => {
28 if (!char) return false;
29 const code = char.charCodeAt(0);
32 (code >= 48 && code <= 57) ||
33 code === 95 || // underscore
34 code === 45 || // hyphen
35 code === 47 || // slash
36 code === 58 || // colon
41 const isOperator = (char: string) => {
42 if (!char) return false;
43 const code = char.charCodeAt(0);
44 return code === 33 || code === 38 || code === 124 || code === 61;
47 const lex = (input: string) : Token[] => {
48 let tokens = [] as any[];
51 while (current < input.length) {
52 let char = input[current];
60 tokens.push({ type: WhenTokenType.OPEN_PAREN, value: char });
66 tokens.push({ type: WhenTokenType.CLOSE_PAREN, value: char });
72 tokens.push({ type: WhenTokenType.EQUALS, value: char });
78 tokens.push({ type: WhenTokenType.COMMA, value: char });
83 if (char === '\"' || char === '\'') {
88 while (current < input.length) {
89 let innerChar = input[current];
90 if (innerChar === '\\') {
91 value += input[current] + input[current + 1];
93 } else if (innerChar === input[start]) {
102 tokens.push({ type: WhenTokenType.STRING, value });
108 while (isAlpha(char)) {
110 char = input[++current];
115 tokens.push({ type: WhenTokenType.AND });
118 tokens.push({ type: WhenTokenType.OR });
121 tokens.push({ type: WhenTokenType.NOT });
124 tokens.push({ type: WhenTokenType.EQUALS });
127 while (isAlphaNumeric(char)) {
129 char = input[++current];
131 tokens.push({ type: WhenTokenType.IDENTIFIER, value });
137 if (isAlphaNumeric(char)) {
139 while (isAlphaNumeric(char)) {
141 char = input[++current];
144 tokens.push({ type: WhenTokenType.IDENTIFIER, value });
148 if (isOperator(char)) {
150 while (isOperator(char)) {
152 char = input[++current];
157 tokens.push({ type: WhenTokenType.AND });
160 tokens.push({ type: WhenTokenType.OR });
163 tokens.push({ type: WhenTokenType.NOT });
166 tokens.push({ type: WhenTokenType.EQUALS });
169 tokens.push({ type: WhenTokenType.NOT_EQUALS });
172 throw new TypeError(`I don't know what this operator is: ${value}`);
177 throw new TypeError(`I don't know what this character is: ${char}`);
186 value?: string | WhenAST;
191 const precedence : { [index: string] : number } = {
198 const parseWhen = (whenExpression: string) => {
199 const tokens = lex(whenExpression);
202 const walk = (precedenceLevel = 0) : WhenAST => {
203 let token = tokens[current];
204 let node: WhenAST | null = null;
206 if (token.type === WhenTokenType.OPEN_PAREN) {
207 token = tokens[++current];
208 let innerNode: WhenAST = { type: WhenTokenType.EXPRESSION, value: walk() };
209 token = tokens[current];
211 while (token.type !== WhenTokenType.CLOSE_PAREN) {
218 token = tokens[current];
224 if (token.type === WhenTokenType.STRING ) {
226 node = { type: token.type, value: token.value };
229 if (token.type === WhenTokenType.NOT) {
230 token = tokens[++current];
231 node = { type: WhenTokenType.NOT, value: token.value, right: walk() };
234 if (token.type === WhenTokenType.IDENTIFIER) {
235 const nextToken = tokens[current + 1];
236 if (nextToken.type === WhenTokenType.OPEN_PAREN) {
237 let name = token.value;
238 token = tokens[++current];
241 token = tokens[++current];
243 while (token.type !== WhenTokenType.CLOSE_PAREN) {
244 if (token.type === WhenTokenType.COMMA) {
249 token = tokens[current];
253 node = { type: WhenTokenType.FUNCTION, name, args };
256 node = { type: WhenTokenType.IDENTIFIER, value: token.value };
260 if (!node) throw new TypeError('Unexpected token: ' + token.type);
262 token = tokens[current];
263 while (current < tokens.length && precedence[token.type] >= precedenceLevel) {
264 console.log(current, tokens[current], tokens[current].type, precedenceLevel, precedence[token.type]);
265 token = tokens[current];
266 if (token.type === WhenTokenType.EQUALS || token.type === WhenTokenType.AND || token.type === WhenTokenType.OR) {
271 right: walk(precedence[token.type]),