Improved lexer error messages
This commit is contained in:
parent
c39723c053
commit
25091fb2a0
2
Cakefile
2
Cakefile
|
@ -91,7 +91,7 @@ task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter'
|
||||||
|
|
||||||
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
|
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
|
||||||
code = ''
|
code = ''
|
||||||
for name in ['helpers', 'rewriter', 'lexer', 'parser', 'scope', 'nodes', 'coffee-script', 'browser']
|
for name in ['helpers', 'error', 'rewriter', 'lexer', 'parser', 'scope', 'nodes', 'coffee-script', 'browser']
|
||||||
code += """
|
code += """
|
||||||
require['./#{name}'] = new function() {
|
require['./#{name}'] = new function() {
|
||||||
var exports = this;
|
var exports = this;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generated by CoffeeScript 1.5.0
|
// Generated by CoffeeScript 1.5.0
|
||||||
(function() {
|
(function() {
|
||||||
var BANNER, CoffeeScript, EventEmitter, SWITCHES, coffee_exts, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, exists, forkNode, fs, helpers, hidden, joinTimeout, lint, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, unwatchDir, usage, version, wait, watch, watchDir, watchers, writeJs, _ref,
|
var BANNER, CoffeeScript, CompilerError, EventEmitter, SWITCHES, coffee_exts, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, exists, forkNode, fs, helpers, hidden, joinTimeout, lint, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, unwatchDir, usage, version, wait, watch, watchDir, watchers, writeJs, _ref,
|
||||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||||
|
|
||||||
fs = require('fs');
|
fs = require('fs');
|
||||||
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
CoffeeScript = require('./coffee-script');
|
CoffeeScript = require('./coffee-script');
|
||||||
|
|
||||||
|
CompilerError = require('./error').CompilerError;
|
||||||
|
|
||||||
_ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec;
|
_ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec;
|
||||||
|
|
||||||
EventEmitter = require('events').EventEmitter;
|
EventEmitter = require('events').EventEmitter;
|
||||||
|
@ -159,7 +161,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
compileScript = function(file, input, base) {
|
compileScript = function(file, input, base) {
|
||||||
var o, options, t, task;
|
var message, o, options, t, task;
|
||||||
o = opts;
|
o = opts;
|
||||||
options = compileOptions(file);
|
options = compileOptions(file);
|
||||||
try {
|
try {
|
||||||
|
@ -194,11 +196,15 @@
|
||||||
if (CoffeeScript.listeners('failure').length) {
|
if (CoffeeScript.listeners('failure').length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
message = err instanceof CompilerError ? err.prettyMessage(file || '[stdin]', input) : err.stack || ("ERROR: " + err);
|
||||||
if (o.watch) {
|
if (o.watch) {
|
||||||
return printLine(err.message + '\x07');
|
if (o.watch) {
|
||||||
|
return printLine(message + '\x07');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printWarn(message);
|
||||||
|
return process.exit(1);
|
||||||
}
|
}
|
||||||
printWarn(err instanceof Error && err.stack || ("ERROR: " + err));
|
|
||||||
return process.exit(1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Generated by CoffeeScript 1.5.0
|
||||||
|
(function() {
|
||||||
|
var CompilerError, repeat,
|
||||||
|
__hasProp = {}.hasOwnProperty,
|
||||||
|
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||||
|
|
||||||
|
repeat = require('./helpers').repeat;
|
||||||
|
|
||||||
|
exports.CompilerError = CompilerError = (function(_super) {
|
||||||
|
|
||||||
|
__extends(CompilerError, _super);
|
||||||
|
|
||||||
|
CompilerError.prototype.name = 'CompilerError';
|
||||||
|
|
||||||
|
function CompilerError(message, startLine, startColumn, endLine, endColumn) {
|
||||||
|
this.message = message;
|
||||||
|
this.startLine = startLine;
|
||||||
|
this.startColumn = startColumn;
|
||||||
|
this.endLine = endLine != null ? endLine : this.startLine;
|
||||||
|
this.endColumn = endColumn != null ? endColumn : this.startColumn;
|
||||||
|
if (typeof Error.captureStackTrace === "function") {
|
||||||
|
Error.captureStackTrace(this, CompilerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompilerError.prototype.prettyMessage = function(fileName, code) {
|
||||||
|
var errorLength, errorLine, marker, message;
|
||||||
|
message = "" + fileName + ":" + this.startLine + ":" + this.startColumn + ": " + this.message;
|
||||||
|
if (this.startLine === this.endLine) {
|
||||||
|
errorLine = code.split('\n')[this.startLine - 1];
|
||||||
|
errorLength = this.endColumn - this.startColumn + 1;
|
||||||
|
marker = (repeat(' ', this.startColumn - 1)) + (repeat('^', errorLength));
|
||||||
|
message += "\n" + errorLine + "\n" + marker;
|
||||||
|
} else {
|
||||||
|
void 0;
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
return CompilerError;
|
||||||
|
|
||||||
|
})(Error);
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -12,6 +12,10 @@
|
||||||
return literal === string.substr(string.length - len - (back || 0), len);
|
return literal === string.substr(string.length - len - (back || 0), len);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.repeat = function(string, n) {
|
||||||
|
return (Array(n + 1)).join(string);
|
||||||
|
};
|
||||||
|
|
||||||
exports.compact = function(array) {
|
exports.compact = function(array) {
|
||||||
var item, _i, _len, _results;
|
var item, _i, _len, _results;
|
||||||
_results = [];
|
_results = [];
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
// Generated by CoffeeScript 1.5.0
|
// Generated by CoffeeScript 1.5.0
|
||||||
(function() {
|
(function() {
|
||||||
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LITERATE, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, key, last, locationDataToString, starts, _ref, _ref1,
|
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, CompilerError, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LITERATE, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, key, last, locationDataToString, starts, _ref, _ref1,
|
||||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||||
|
|
||||||
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
|
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
|
||||||
|
|
||||||
_ref1 = require('./helpers'), count = _ref1.count, starts = _ref1.starts, compact = _ref1.compact, last = _ref1.last, locationDataToString = _ref1.locationDataToString;
|
_ref1 = require('./helpers'), count = _ref1.count, starts = _ref1.starts, compact = _ref1.compact, last = _ref1.last, locationDataToString = _ref1.locationDataToString;
|
||||||
|
|
||||||
|
CompilerError = require('./error').CompilerError;
|
||||||
|
|
||||||
exports.Lexer = Lexer = (function() {
|
exports.Lexer = Lexer = (function() {
|
||||||
|
|
||||||
function Lexer() {}
|
function Lexer() {}
|
||||||
|
@ -786,7 +788,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
Lexer.prototype.error = function(message) {
|
Lexer.prototype.error = function(message) {
|
||||||
throw SyntaxError("" + message + " on line " + (this.chunkLine + 1));
|
throw new CompilerError(message, this.chunkLine + 1, this.chunkColumn + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
return Lexer;
|
return Lexer;
|
||||||
|
|
|
@ -5,15 +5,16 @@
|
||||||
# interactive REPL.
|
# interactive REPL.
|
||||||
|
|
||||||
# External dependencies.
|
# External dependencies.
|
||||||
fs = require 'fs'
|
fs = require 'fs'
|
||||||
path = require 'path'
|
path = require 'path'
|
||||||
helpers = require './helpers'
|
helpers = require './helpers'
|
||||||
optparse = require './optparse'
|
optparse = require './optparse'
|
||||||
CoffeeScript = require './coffee-script'
|
CoffeeScript = require './coffee-script'
|
||||||
{spawn, exec} = require 'child_process'
|
{CompilerError} = require './error'
|
||||||
{EventEmitter} = require 'events'
|
{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.
|
# Allow CoffeeScript to emit Node.js events.
|
||||||
helpers.extend CoffeeScript, new EventEmitter
|
helpers.extend CoffeeScript, new EventEmitter
|
||||||
|
@ -138,9 +139,17 @@ compileScript = (file, input, base) ->
|
||||||
catch err
|
catch err
|
||||||
CoffeeScript.emit 'failure', err, task
|
CoffeeScript.emit 'failure', err, task
|
||||||
return if CoffeeScript.listeners('failure').length
|
return if CoffeeScript.listeners('failure').length
|
||||||
return printLine err.message + '\x07' if o.watch
|
|
||||||
printWarn err instanceof Error and err.stack or "ERROR: #{err}"
|
message = if err instanceof CompilerError
|
||||||
process.exit 1
|
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**,
|
# Attach the appropriate listeners to compile scripts incoming over **stdin**,
|
||||||
# and write them back to **stdout**.
|
# and write them back to **stdout**.
|
||||||
|
|
|
@ -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
|
|
@ -11,6 +11,10 @@ exports.ends = (string, literal, back) ->
|
||||||
len = literal.length
|
len = literal.length
|
||||||
literal is string.substr string.length - len - (back or 0), len
|
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.
|
# Trim out all falsy values from an array.
|
||||||
exports.compact = (array) ->
|
exports.compact = (array) ->
|
||||||
item for item in array when item
|
item for item in array when item
|
||||||
|
@ -71,8 +75,9 @@ buildLocationData = (first, last) ->
|
||||||
last_line: last.last_line
|
last_line: last.last_line
|
||||||
last_column: last.last_column
|
last_column: last.last_column
|
||||||
|
|
||||||
# This returns a function which takes an object as a parameter, and if that object is an AST node,
|
# This returns a function which takes an object as a parameter, and if that
|
||||||
# updates that object's locationData. The object is returned either way.
|
# object is an AST node, updates that object's locationData.
|
||||||
|
# The object is returned either way.
|
||||||
exports.addLocationDataFn = (first, last) ->
|
exports.addLocationDataFn = (first, last) ->
|
||||||
(obj) ->
|
(obj) ->
|
||||||
if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
|
if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
|
||||||
|
@ -91,5 +96,3 @@ exports.locationDataToString = (obj) ->
|
||||||
"#{locationData.last_line + 1}:#{locationData.last_column + 1}"
|
"#{locationData.last_line + 1}:#{locationData.last_column + 1}"
|
||||||
else
|
else
|
||||||
"No location data"
|
"No location data"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
# Import the helpers we need.
|
# Import the helpers we need.
|
||||||
{count, starts, compact, last, locationDataToString} = require './helpers'
|
{count, starts, compact, last, locationDataToString} = require './helpers'
|
||||||
|
{CompilerError} = require './error'
|
||||||
|
|
||||||
# The Lexer Class
|
# The Lexer Class
|
||||||
# ---------------
|
# ---------------
|
||||||
|
@ -41,7 +42,7 @@ exports.Lexer = class Lexer
|
||||||
@outdebt = 0 # The under-outdentation at the current level.
|
@outdebt = 0 # The under-outdentation at the current level.
|
||||||
@indents = [] # The stack of all current indentation levels.
|
@indents = [] # The stack of all current indentation levels.
|
||||||
@ends = [] # The stack for pairing up tokens.
|
@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 =
|
@chunkLine =
|
||||||
opts.line or 0 # The start line for the current @chunk.
|
opts.line or 0 # The start line for the current @chunk.
|
||||||
|
@ -693,11 +694,11 @@ exports.Lexer = class Lexer
|
||||||
body = body.replace /// #{quote} ///g, '\\$&'
|
body = body.replace /// #{quote} ///g, '\\$&'
|
||||||
quote + @escapeLines(body, heredoc) + quote
|
quote + @escapeLines(body, heredoc) + quote
|
||||||
|
|
||||||
# Throws a syntax error on the current `@line`.
|
# Throws a compiler error on the current position.
|
||||||
error: (message) ->
|
error: (message) ->
|
||||||
# TODO: Are there some cases we could improve the error line number by
|
# TODO: Are there some cases we could improve the error line number by
|
||||||
# passing the offset in the chunk where the error happened?
|
# 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
|
# Constants
|
||||||
# ---------
|
# ---------
|
||||||
|
|
|
@ -143,4 +143,4 @@ test "#1299: Disallow token misnesting", ->
|
||||||
'''
|
'''
|
||||||
ok no
|
ok no
|
||||||
catch e
|
catch e
|
||||||
eq 'unmatched ] on line 2', e.message
|
eq 'unmatched ]', e.message
|
||||||
|
|
Loading…
Reference in New Issue