diff --git a/src/parser.ts b/src/parser.ts index 43b62708..164badbd 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -27,8 +27,9 @@ function createToken(buffer: string, type: TokenType) { if (type === TokenType.NUM) { // This can happen if users simply type ".", which get parsed as number. - if (isNaN(+buffer)) throw ExprError.invalidExpression(); - return new ExprNumber(+buffer); + const numberWithoutCommas = buffer.replace(/,/g, ''); + if (isNaN(+numberWithoutCommas)) throw ExprError.invalidExpression(); + return new ExprNumber(+numberWithoutCommas); } if (type === TokenType.VAR) { @@ -54,9 +55,16 @@ export function tokenize(str: string) { const tokens = []; let buffer = ''; let type = TokenType.UNKNOWN; + let parenDepth = 0; for (const s of str) { + if (['(', '[', '{'].includes(s)) { + parenDepth++; + } else if ([')', ']', '}'].includes(s)) { + parenDepth--; + } + // Handle Strings if (s === '"') { const newType: TokenType = (((type as TokenType) === TokenType.STR) ? TokenType.UNKNOWN : TokenType.STR); @@ -70,6 +78,22 @@ export function tokenize(str: string) { continue; } + // Special handling for commas + if (s === ',') { + // If we're in a number and not inside parenthesis include the comma + if (type === TokenType.NUM && parenDepth === 0) { + buffer += s; + continue; + } + // Otherwise treat it as an operator + const token = createToken(buffer, type); + if (token) tokens.push(token); + tokens.push(new ExprOperator(',')); + buffer = ''; + type = TokenType.UNKNOWN; + continue; + } + const sType = s.match(/[0-9.]/) ? TokenType.NUM : IDENTIFIER_SYMBOLS.includes(s) ? TokenType.VAR : OPERATOR_SYMBOLS.includes(s) ? TokenType.OP : diff --git a/test/parsing-test.ts b/test/parsing-test.ts index a3447aff..1d1c3063 100644 --- a/test/parsing-test.ts +++ b/test/parsing-test.ts @@ -34,6 +34,19 @@ tape('functions', (test) => { test.end(); }); +tape('commas', (test) => { + test.equal(str('1,1'), '11'); + test.equal(str('1,1 + 2,2'), '11 + 22'); + test.equal(str('1,000 + 2,000'), '1000 + 2000'); + test.equal(str('1,'), '1'); // Trailing comma + // test.equal(str(',1'), '1'); // Leading comma TODO: failing + test.equal(str('1,2,3'), '123'); // Multiple commas + test.equal(str('1,234,567.89'), '1234567.89'); // Multiple commas with a decimal point + test.equal(str('1,,2'), '12'); // Multiple consecutive commas + test.equal(str('1,2,'), '12'); // Multiple commas with trailing + test.end(); +}); + tape('strings', (test) => { test.equal(str('"A" + "B"'), '"A" + "B"'); test.equal(str('"A"_"B"'), '"A"_"B"');