diff --git a/moo.js b/moo.js index cf3a27e..79e9b12 100644 --- a/moo.js +++ b/moo.js @@ -15,6 +15,10 @@ /***************************************************************************/ + function pad(text, width) { + return Array(width - text.length + 1).join(" ") + text + } + function isRegExp(o) { return o && toString.call(o) === '[object RegExp]' } function isObject(o) { return o && typeof o === 'object' && !isRegExp(o) && !Array.isArray(o) } @@ -562,21 +566,49 @@ Lexer.prototype.formatError = function(token, message) { if (token == null) { // An undefined token indicates EOF - var text = this.buffer.slice(this.index) var token = { - text: text, + text: "", offset: this.index, - lineBreaks: text.indexOf('\n') === -1 ? 0 : 1, + lineBreaks: 0, line: this.line, col: this.col, } } - var start = Math.max(0, token.offset - token.col + 1) - var eol = token.lineBreaks ? token.text.indexOf('\n') : token.text.length - var firstLine = this.buffer.substring(start, token.offset + eol) + + var lines = this.buffer + .split("\n") + .slice(Math.max(0, token.line - 5), token.line) + message += " at line " + token.line + " col " + token.col + ":\n\n" - message += " " + firstLine + "\n" - message += " " + Array(token.col).join(" ") + "^" + + message += lines + .map(function (line) { return line.replace(/\t/g, " ") }) + .map(function(curLine, i) { + return pad(String(token.line - (lines.length - i) + 1), 6) + "\t" + curLine + "\n" + }) + .join("") + + var lastLine = lines[lines.length - 1] + var lastLinePrepend = lastLine.slice(0, token.col - 1) + var lastLinePrependExpanded = lastLinePrepend.replace(/\t/g, " ") + + var tokenTextFirstLine = token.text.split("\n")[0] + var tokenTextFirstLineExpanded = tokenTextFirstLine.replace(/\t/g, " ") + + var highlightIndentation = lastLine.replace(/[^ \t]/g, " ").replace(/\t/g, "\\tab") + var highlightLength = !tokenTextFirstLine || lastLinePrependExpanded.length >= highlightIndentation.length ? + 0 : + tokenTextFirstLineExpanded.length + + var highlight = highlightLength ? + Array(highlightLength + 1).join(token.lineBreaks ? "^" : "~") : + (token.offset >= this.buffer.length ? "^EOF" : "^") + + message += " \t" + + highlightIndentation.slice(0, lastLinePrependExpanded.length) + + highlight + + highlightIndentation.slice(lastLinePrependExpanded.length + highlight.length) + "\n" + return message } diff --git a/test/test.js b/test/test.js index 2e20d18..c9d4d0e 100644 --- a/test/test.js +++ b/test/test.js @@ -905,8 +905,9 @@ describe('errors', () => { expect(lexer.next()).toMatchObject({value: '456'}) expect(() => lexer.next()).toThrow( "invalid syntax at line 2 col 4:\n\n" + - " 456baa\n" + - " ^" + " 1\t123\n" + + " 2\t456baa\n" + + " \t ~~~\n" ) }) @@ -921,8 +922,10 @@ describe('errors', () => { expect(tok).toMatchObject({type: 'error', value: ' 12\n345\n6', lineBreaks: 2}) expect(lexer.formatError(tok, "numbers!")).toBe( "numbers! at line 3 col 2:\n\n" + - " g 12\n" + - " ^" + " 1\tabc\n" + + " 2\tdef\n" + + " 3\tg 12\n" + + " \t ^^^\n" ) }) @@ -937,8 +940,9 @@ describe('errors', () => { expect(lexer.col).toBe(9) expect(lexer.formatError(undefined, "EOF!")).toBe( "EOF! at line 2 col 9:\n\n" + - " def quxx\n" + - " ^" + " 1\tabc\n" + + " 2\tdef quxx\n" + + " \t ^EOF\n" ) }) @@ -954,8 +958,9 @@ describe('errors', () => { expect(lexer.col).toBe(1) expect(lexer.formatError(undefined, "oh no!")).toBe( "oh no! at line 2 col 1:\n\n" + - " def quxx\n" + - " ^" + " 1\tabc\n" + + " 2\tdef quxx\n" + + " \t^ \n" ) })