Improved lexer error messages

This commit is contained in:
Demian Ferreiro
2013-02-25 14:41:34 -03:00
parent c39723c053
commit 25091fb2a0
10 changed files with 123 additions and 27 deletions

View File

@@ -5,15 +5,16 @@
# interactive REPL.
# External dependencies.
fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events'
fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
{CompilerError} = require './error'
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events'
exists = fs.exists or path.exists
exists = fs.exists or path.exists
# Allow CoffeeScript to emit Node.js events.
helpers.extend CoffeeScript, new EventEmitter
@@ -138,9 +139,17 @@ compileScript = (file, input, base) ->
catch err
CoffeeScript.emit 'failure', err, task
return if CoffeeScript.listeners('failure').length
return printLine err.message + '\x07' if o.watch
printWarn err instanceof Error and err.stack or "ERROR: #{err}"
process.exit 1
message = if err instanceof CompilerError
err.prettyMessage file or '[stdin]', input
else
err.stack or "ERROR: #{err}"
if o.watch
printLine message + '\x07' if o.watch
else
printWarn message
process.exit 1
# Attach the appropriate listeners to compile scripts incoming over **stdin**,
# and write them back to **stdout**.

27
src/error.coffee Normal file
View File

@@ -0,0 +1,27 @@
{repeat} = require './helpers'
# A common error class used throughout the compiler to indicate compilation
# errors at a given location in the source code.
exports.CompilerError = class CompilerError extends Error
name: 'CompilerError'
constructor: (@message, @startLine, @startColumn,
@endLine = @startLine, @endColumn = @startColumn) ->
# Add a stack trace in V8.
Error.captureStackTrace? @, CompilerError
# Creates a nice error message like, following the "standard" format
# <filename>:<line>:<col>: <message> plus the line with the error and a marker
# showing where the error is.
# TODO: tests
prettyMessage: (fileName, code) ->
message = "#{fileName}:#{@startLine}:#{@startColumn}: #{@message}"
if @startLine is @endLine
errorLine = code.split('\n')[@startLine - 1]
errorLength = @endColumn - @startColumn + 1
marker = (repeat ' ', @startColumn - 1) + (repeat '^', errorLength)
message += "\n#{errorLine}\n#{marker}"
else
# TODO: How do we show multi-line errors?
undefined
message

View File

@@ -11,6 +11,10 @@ exports.ends = (string, literal, back) ->
len = literal.length
literal is string.substr string.length - len - (back or 0), len
# Repeat a string `n` times.
exports.repeat = (string, n) ->
(Array n + 1).join string
# Trim out all falsy values from an array.
exports.compact = (array) ->
item for item in array when item
@@ -71,8 +75,9 @@ buildLocationData = (first, last) ->
last_line: last.last_line
last_column: last.last_column
# This returns a function which takes an object as a parameter, and if that object is an AST node,
# updates that object's locationData. The object is returned either way.
# This returns a function which takes an object as a parameter, and if that
# object is an AST node, updates that object's locationData.
# The object is returned either way.
exports.addLocationDataFn = (first, last) ->
(obj) ->
if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
@@ -91,5 +96,3 @@ exports.locationDataToString = (obj) ->
"#{locationData.last_line + 1}:#{locationData.last_column + 1}"
else
"No location data"

View File

@@ -13,6 +13,7 @@
# Import the helpers we need.
{count, starts, compact, last, locationDataToString} = require './helpers'
{CompilerError} = require './error'
# The Lexer Class
# ---------------
@@ -41,7 +42,7 @@ exports.Lexer = class Lexer
@outdebt = 0 # The under-outdentation at the current level.
@indents = [] # The stack of all current indentation levels.
@ends = [] # The stack for pairing up tokens.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, location data]`.
@chunkLine =
opts.line or 0 # The start line for the current @chunk.
@@ -693,11 +694,11 @@ exports.Lexer = class Lexer
body = body.replace /// #{quote} ///g, '\\$&'
quote + @escapeLines(body, heredoc) + quote
# Throws a syntax error on the current `@line`.
# Throws a compiler error on the current position.
error: (message) ->
# TODO: Are there some cases we could improve the error line number by
# passing the offset in the chunk where the error happened?
throw SyntaxError "#{message} on line #{ @chunkLine + 1 }"
throw new CompilerError message, @chunkLine + 1, @chunkColumn + 1
# Constants
# ---------