From 8313692991797dc702b931f67066055032e2e10d Mon Sep 17 00:00:00 2001 From: Joan Varvenne Date: Wed, 21 Sep 2016 15:15:20 +0100 Subject: [PATCH] Improve error message and span calculation. Change-Id: I2c319e97329bd31eae875ab48eae0476bbf979e2 --- monasca_analytics/banana/emitter.py | 24 +++++---- monasca_analytics/banana/grammar/ast.py | 25 +++++++-- monasca_analytics/banana/grammar/base_ast.py | 36 ++++++++++--- .../banana/typeck/connections.py | 1 + monasca_analytics/exception/banana.py | 54 +++++++++---------- 5 files changed, 92 insertions(+), 48 deletions(-) diff --git a/monasca_analytics/banana/emitter.py b/monasca_analytics/banana/emitter.py index 2a96695..4b81954 100644 --- a/monasca_analytics/banana/emitter.py +++ b/monasca_analytics/banana/emitter.py @@ -74,17 +74,21 @@ class JsonEmitter(Emitter): } def emit_error(self, span, message): - self.result["errors"].append({ - "line": span.get_lineno(), - "col": 0, - "byteRange": [span.lo, span.hi], - "message": message - }) + error = JsonEmitter._gen_message_structure(span, message) + self.result["errors"].append(error) def emit_warning(self, span, message): - self.result["warnings"].append({ - "line": span.get_lineno(), - "col": 0, + warning = JsonEmitter._gen_message_structure(span, message) + self.result["warnings"].append(warning) + + @staticmethod + def _gen_message_structure(span, message): + spanrange = span.get_range() + return { + "startLineNumber": spanrange[0][0], + "startColumn": spanrange[0][1], + "endLineNumber": spanrange[1][0], + "endColumn": spanrange[1][1], "byteRange": [span.lo, span.hi], "message": message - }) + } diff --git a/monasca_analytics/banana/grammar/ast.py b/monasca_analytics/banana/grammar/ast.py index 09ab617..fde6958 100644 --- a/monasca_analytics/banana/grammar/ast.py +++ b/monasca_analytics/banana/grammar/ast.py @@ -128,10 +128,25 @@ class BananaFile(object): def make_span(s, l, t): + + def compute_hi(init_loc, tokens): + hi = init_loc + for tok in tokens: + if isinstance(tok, ASTNode): + hi = max(hi, tok.span.hi) + elif isinstance(tok, basestring): + hi += len(tok) + elif isinstance(tok, p.ParseResults): + hi = max(hi, compute_hi(init_loc, tok)) + else: + raise exception.BananaGrammarBug( + "Couldn't create span for: {}".format(tok) + ) + return hi + if len(t) > 0: - if isinstance(t[0], ASTNode): - return Span(s, l, t[0].span.hi) - return Span(s, l, len(t[0]) + l) + span_hi = compute_hi(l, t) + return Span(s, l, span_hi) else: return Span(s, l, 2) @@ -238,7 +253,7 @@ class DotPath(ASTNode): :return: Returns the next dot path. """ return DotPath( - self.span.new_with_offset(len(self.varname.val)), + self.span.new_with_lo(self.properties[0].span.lo), self.properties[0], self.properties[1:] ) @@ -415,6 +430,8 @@ class Connection(ASTNode): :type emitter: emit.Emitter :param emitter: Emitter. """ + self.span.hi = max(other_con.span.hi, self.span.hi) + self.span.lo = min(other_con.span.lo, self.span.lo) old_outputs = self.outputs self.outputs = other_con.outputs diff --git a/monasca_analytics/banana/grammar/base_ast.py b/monasca_analytics/banana/grammar/base_ast.py index f11044f..0885d7a 100644 --- a/monasca_analytics/banana/grammar/base_ast.py +++ b/monasca_analytics/banana/grammar/base_ast.py @@ -70,17 +70,17 @@ class Span(object): else: return '?SPAN?' - def new_with_offset(self, offset): + def new_with_lo(self, lo_val): """ - Construct a new Span with an offset applied - to lo. + Construct a new Span with an new value for + lo. - :type offset: int - :param offset: Offset to apply to lo. + :type lo_val: int + :param lo_val: New value for lo. :rtype: Span :return: Returns a new span """ - return Span(self._text, self.lo + offset, self.hi) + return Span(self._text, lo_val, self.hi) def str_from_to(self, to_span): """ @@ -107,6 +107,30 @@ class Span(object): else: return '?LINE?' + def get_range(self): + """ + Returns the start and end (line number, column number) of this span. + """ + if self._text is not None: + splitted = self._text.splitlines() + current_pos = 0 + startlineno = 0 + startcolno = 0 + endlineno = 0 + endcolno = 0 + for lineno in xrange(0, len(splitted)): + line = splitted[lineno] + if current_pos <= self.lo <= len(line) + current_pos: + startlineno = lineno + 1 + startcolno = self.lo - current_pos + 1 + if current_pos <= self.hi <= len(line) + current_pos: + endlineno = lineno + 1 + endcolno = self.hi - current_pos + 1 + current_pos += len(line) + 1 + return (startlineno, startcolno), (endlineno, endcolno) + else: + return (0, 0), (0, 0) + def get_lineno(self): """ Returns the line number of this span. diff --git a/monasca_analytics/banana/typeck/connections.py b/monasca_analytics/banana/typeck/connections.py index 6d77521..ff78fe6 100644 --- a/monasca_analytics/banana/typeck/connections.py +++ b/monasca_analytics/banana/typeck/connections.py @@ -55,5 +55,6 @@ def typeck_connections(connection, type_table): possible_types = map(lambda x: x.__name__, valid_connections_types[type(type_from)]) raise exception.BananaConnectionError( + connection.span, ident_from, ident_to, type_from, possible_types ) diff --git a/monasca_analytics/exception/banana.py b/monasca_analytics/exception/banana.py index 3ad6341..96a54c9 100644 --- a/monasca_analytics/exception/banana.py +++ b/monasca_analytics/exception/banana.py @@ -77,11 +77,10 @@ class BananaArgumentTypeError(BananaException): def __init__(self, where, expected_type, received_type): if isinstance(where, ast.ASTNode): self._span = where.span - where = where.span else: self._span = where - self._value = "'{}': Wrong type of argument. Expected '{}' got '{}'"\ - .format(where.get_line(), expected_type, received_type) + self._value = "Wrong type of argument. Expected '{}' got '{}'."\ + .format(expected_type, received_type) def __str__(self): return self._value @@ -93,7 +92,7 @@ class BananaArgumentTypeError(BananaException): class BananaComponentTooManyParams(BananaException): def __init__(self, span): self._span = span - self._value = "Too many params provided to '{}' (line {})".format( + self._value = "Too many params provided to '{}'.".format( span, span.get_lineno() ) @@ -145,7 +144,7 @@ class BananaComponentAlreadyDefined(BananaException): def __init__(self, first_def, second_def): self._value = "Component already defined!\n" \ " First definition: '{}'\n" \ - " Second definition: '{}'"\ + " Second definition: '{}'."\ .format(first_def, second_def) def __str__(self): @@ -171,7 +170,7 @@ class BananaShadowingComponentError(BananaException): class BananaAssignmentError(BananaException): def __init__(self, lhs, rhs): - self._value = "You can't assign '{}' to '{}'".format(lhs, rhs) + self._value = "You can't assign '{}' to '{}'.".format(lhs, rhs) def __str__(self): return self._value @@ -196,7 +195,7 @@ class BananaGrammarBug(BananaException, p.ParseFatalException): class BananaJsonObjShadowingError(BananaException, p.ParseFatalException): def __init__(self, span, error): self._span = span - error = "Can't shadow property already defined in {}".format(error) + error = "Can't shadow property already defined in {}.".format(error) super(BananaJsonObjShadowingError, self).__init__(pstr=error) def __str__(self): @@ -233,7 +232,7 @@ class BananaEvalBug(BananaException): class BananaUnknown(BananaException): def __init__(self, ident): self._span = ident.span - self._value = "Unknown '{}'".format( + self._value = "Unknown '{}'.".format( ident.into_unmodified_str() ) @@ -247,7 +246,7 @@ class BananaUnknown(BananaException): class BananaUnknownOperator(BananaException): def __init__(self, span, operator, for_type): self._span = span - self._value = "Unknown operator '{}' for type '{}'".format( + self._value = "Unknown operator '{}' for type '{}'.".format( operator, for_type ) @@ -263,17 +262,15 @@ class BananaPropertyDoesNotExists(BananaException): def __init__(self, dotpath, on_type=None): self._span = dotpath.span if on_type is None: - self._value = "Error at '{}': Property '{}' " \ - "does not exists"\ + self._value = "Property '{}' " \ + "does not exists."\ .format( - dotpath.span.get_line(), dotpath.into_unmodified_str() ) else: - self._value = "Error at '{}': Property '{}' " \ - "does not exists on type '{}'"\ + self._value = "Property '{}' " \ + "does not exists on type '{}'."\ .format( - dotpath.span.get_line(), dotpath.into_unmodified_str(), str(on_type) ) @@ -296,21 +293,21 @@ class BananaTypeError(BananaException): if found_type is None: if isinstance(expected_type, list): self._value = "Type error found. Expected" \ - " one among '{}'"\ + " one among '{}'."\ .format(', '.join(map(lambda x: str(x), expected_type))) else: - self._value = "Type error found. Expected '{}'".format( + self._value = "Type error found. Expected '{}'.".format( str(expected_type) ) else: if isinstance(expected_type, list): self._value = "Type error found. Expected" \ - " one among '{}', found '{}'"\ + " one among '{}', found '{}'."\ .format(', '.join(map(lambda x: str(x), expected_type)), str(found_type)) else: self._value = "Type error found. Expected" \ - " '{}', found '{}'"\ + " '{}', found '{}'."\ .format(str(expected_type), str(found_type)) def __str__(self): @@ -327,7 +324,7 @@ class BananaAssignCompError(BananaException): self._span = span self._value = "Component objects " \ "can't be assigned to " \ - "properties of other objects" + "properties of other objects." def __str__(self): return self._value @@ -338,15 +335,16 @@ class BananaAssignCompError(BananaException): class BananaConnectionError(BananaException): - def __init__(self, ident_from, ident_to, type_from, possible_connections): - self._span = ident_to.span - self._value = "Can't connect '{}' (line:{})" \ - " to '{}' (line:{})," \ - " '{}' can only be connected to {}"\ + def __init__(self, span, ident_from, ident_to, type_from, + possible_connections): + self._value = "Can't connect '{}'" \ + " to '{}'," \ + " '{}' can only be connected to a {}."\ .format( - ident_from.val, ident_from.span.get_lineno(), - ident_to.val, ident_to.span.get_lineno(), - type_from, possible_connections) + ident_from.val, + ident_to.val, + type_from.class_name, ' or a '.join(possible_connections)) + self._span = span def __str__(self): return self._value