From 00ed5e867d700944b500986e2b026b025f3c8d5a Mon Sep 17 00:00:00 2001 From: pca006132 Date: Tue, 22 Dec 2020 16:53:33 +0800 Subject: [PATCH] better error message --- toy-impl/helper.py | 14 +++++++- toy-impl/parse_expr.py | 77 ++++++++++++++++++++++++++---------------- toy-impl/parse_stmt.py | 38 +++++++++++---------- 3 files changed, 81 insertions(+), 48 deletions(-) diff --git a/toy-impl/helper.py b/toy-impl/helper.py index c49ac06..14020a6 100644 --- a/toy-impl/helper.py +++ b/toy-impl/helper.py @@ -1,9 +1,21 @@ +import ast + class CustomError(Exception): - def __init__(self, msg): + def __init__(self, msg, node = None): self.msg = msg + if node is not None: + self.at(node) + + def at(self, node): + self.msg = f'Error at {node.lineno}:{node.col_offset+1}-' \ + f'{node.end_lineno}:{node.end_col_offset+1} ' \ + f'"{ast.unparse(node)}"\n{self.msg}' + return self def stringify_subst(subst): if isinstance(subst, str): return subst elements = [f"{key}: {str(value)}" for key, value in subst.items()] return "{" + ', '.join(elements) + "}" + + diff --git a/toy-impl/parse_expr.py b/toy-impl/parse_expr.py index 1015c3a..23251db 100644 --- a/toy-impl/parse_expr.py +++ b/toy-impl/parse_expr.py @@ -5,7 +5,7 @@ from type_def import * from inference import * # we assume having the following types: -# bool, int32 with associated operations +# bool, int32 and float with associated operations # not handled now: named expression, type guard @@ -42,7 +42,7 @@ def parse_expr(ctx: Context, return parse_if_expr(ctx, sym_table, body) if isinstance(body, ast.ListComp): return parse_list_comprehension(ctx, sym_table, body) - raise CustomError(f'{body} is not yet supported') + raise CustomError(f'{body} is not yet supported', body) def get_unary_op(op): @@ -73,8 +73,10 @@ def parse_constant(ctx: Context, return ctx.types['bool'] elif isinstance(v, int): return ctx.types['int32'] + elif isinstance(v, float): + return ctx.types['float'] else: - raise CustomError(f'unknown constant {v}') + raise CustomError(f'unknown constant {v}', node) def parse_name(ctx: Context, sym_table: dict[str, Type], @@ -82,7 +84,7 @@ def parse_name(ctx: Context, if node.id in sym_table: return sym_table[node.id] else: - raise CustomError(f'unbounded variable {node.id}') + raise CustomError(f'unbounded variable {node.id}', node) def parse_list(ctx: Context, sym_table: dict[str, Type], @@ -92,7 +94,7 @@ def parse_list(ctx: Context, return ListType(BotType()) for t in types[1:]: if t != types[0]: - raise CustomError(f'inhomogeneous list is not allowed') + raise CustomError(f'inhomogeneous list is not allowed', node) return ListType(types[0]) def parse_tuple(ctx: Context, @@ -107,7 +109,7 @@ def parse_attribute(ctx: Context, obj = parse_expr(node.value) if node.attr in obj.fields: return obj.fields[node.attr] - raise CustomError(f'unknown field {node.attr} in {obj}') + raise CustomError(f'unknown field {node.attr} in {obj}', node) def parse_bool_ops(ctx: Context, sym_table: dict[str, Type], @@ -117,7 +119,7 @@ def parse_bool_ops(ctx: Context, right = parse_expr(ctx, sym_table, node.values[1]) b = ctx.types['bool'] if left != b or right != b: - raise CustomError('operands of bool ops must be booleans') + raise CustomError('operands of bool ops must be booleans', node) return b def parse_bin_ops(ctx: Context, @@ -126,7 +128,10 @@ def parse_bin_ops(ctx: Context, left = parse_expr(ctx, sym_table, node.left) right = parse_expr(ctx, sym_table, node.right) op = get_bin_ops(node.op) - return resolve_call(left, op, [right], {}, ctx) + try: + return resolve_call(left, op, [right], {}, ctx) + except CustomError as e: + raise e.at(node) def parse_unary_ops(ctx: Context, sym_table: dict[str, Type], @@ -135,9 +140,12 @@ def parse_unary_ops(ctx: Context, if isinstance(node.op, ast.Not): b = ctx.types['bool'] if t != b: - raise CustomError('operands of bool ops must be booleans') + raise CustomError('operands of bool ops must be booleans', node) return b - return resolve_call(t, get_unary_op(node.op), [], {}, ctx) + try: + return resolve_call(t, get_unary_op(node.op), [], {}, ctx) + except CustomError as e: + raise e.at(node) def parse_compare(ctx: Context, sym_table: dict[str, Type], @@ -147,17 +155,21 @@ def parse_compare(ctx: Context, boolean = ctx.types['bool'] ops = [get_bin_ops(v) for v in node.ops] for a, b, op in zip(items[:-1], items[1:], ops): - result = resolve_call(a, op, [b], {}, ctx) - if result != boolean: - raise CustomError( - f'result of comparison must be bool instead of {result}') + try: + result = resolve_call(a, op, [b], {}, ctx) + if result != boolean: + raise CustomError( + f'result of comparison must be bool instead of {result}') + except CustomError as e: + raise e.at(node) + return boolean def parse_call(ctx: Context, sym_table: dict[str, Type], node): if len(node.keywords) > 0: - raise CustomError('keyword arguments are not supported') + raise CustomError('keyword arguments are not supported', node) args = [parse_expr(ctx, sym_table, v) for v in node.args] obj = None f = None @@ -166,32 +178,35 @@ def parse_call(ctx: Context, f = node.func.attr elif isinstance(node.func, ast.Name): f = node.func.id - return resolve_call(obj, f, args, {}, ctx) + try: + return resolve_call(obj, f, args, {}, ctx) + except CustomError as e: + raise e.at(node) def parse_subscript(ctx: Context, sym_table: dict[str, Type], node): value = parse_expr(ctx, sym_table, node.value) if not isinstance(value, ListType): - raise CustomError(f'cannot take index of {value}') + raise CustomError(f'cannot take index of {value}', node) i32 = ctx.types['int32'] if isinstance(node.slice, ast.Slice): if node.slice.lower is not None: if parse_expr(ctx, sym_table, node.slice.lower) != i32: - raise CustomError(f'slice index must be int32') + raise CustomError(f'slice index must be int32', node.slice.lower) if node.slice.upper is not None: if parse_expr(ctx, sym_table, node.slice.upper) != i32: - raise CustomError(f'slice index must be int32') + raise CustomError(f'slice index must be int32', node.slice.upper) if node.slice.step is not None: if parse_expr(ctx, sym_table, node.slice.step) != i32: - raise CustomError(f'slice index must be int32') + raise CustomError(f'slice index must be int32', node.slice.step) return value else: s = parse_expr(ctx, sym_table, node.slice) if s == i32: return value.params[0] else: - raise CustomError(f'index of type {s} is not supported') + raise CustomError(f'index of type {s} is not supported', node) def parse_if_expr(ctx: Context, sym_table: dict[str, Type], @@ -199,11 +214,11 @@ def parse_if_expr(ctx: Context, b = ctx.types['bool'] t = parse_expr(ctx, sym_table, node.test) if t != b: - raise CustomError(f'type of conditional must be bool instead of {t}') + raise CustomError(f'type of conditional must be bool instead of {t}', node) ty1 = parse_expr(ctx, sym_table, node.body) ty2 = parse_expr(ctx, sym_table, node.orelse) if ty1 != ty2: - raise CustomError(f'divergent type for if expression: {ty1} != {ty2}') + raise CustomError(f'divergent type for if expression: {ty1} != {ty2}', node) return ty1 def parse_simple_binding(name, ty): @@ -222,7 +237,7 @@ def parse_simple_binding(name, ty): expected = len(result) + len(binding) result |= parse_simple_binding(x, y) if len(result) != expected: - raise CustomError('variable name clash') + raise CustomError('variable name clash', x) return result else: raise CustomError(f'binding to {name} is not supported') @@ -231,16 +246,20 @@ def parse_list_comprehension(ctx: Context, sym_table: dict[str, Type], node): if len(node.generators) != 1: - raise CustomError('list comprehension with more than 1 for loop is not supported') + raise CustomError( + 'list comprehension with more than 1 for loop is not supported', node) if node.generators[0].is_async: - raise CustomError('async list comprehension is not supported') + raise CustomError('async list comprehension is not supported', node) ty = parse_expr(ctx, sym_table, node.generators[0].iter) if not isinstance(ty, ListType): - raise CustomError(f'unable to iterate over {ty}') - sym_table2 = sym_table | parse_simple_binding(node.generators[0].target, ty.params[0]) + raise CustomError(f'unable to iterate over {ty}', node) + try: + sym_table2 = sym_table | parse_simple_binding(node.generators[0].target, ty.params[0]) + except CustomError as e: + raise e.at(node) b = ctx.types['bool'] for c in node.generators[0].ifs: if parse_expr(ctx, sym_table2, c) != b: - raise CustomError(f'condition should be of boolean type') + raise CustomError(f'condition should be of boolean type', c) return ListType(parse_expr(ctx, sym_table2, node.elt)) diff --git a/toy-impl/parse_stmt.py b/toy-impl/parse_stmt.py index 8df2dd8..f8d1cab 100644 --- a/toy-impl/parse_stmt.py +++ b/toy-impl/parse_stmt.py @@ -26,7 +26,7 @@ def parse_stmts(ctx: Context, elif isinstance(node, ast.Break) or isinstance(node, ast.Continue): continue else: - raise CustomError(f'{node} is not supported yet') + raise CustomError(f'{node} is not supported yet', node) sym_table2 |= a used_sym_table2 |= b if returned: @@ -40,24 +40,24 @@ def get_target_type(ctx: Context, if isinstance(target, ast.Subscript): t = get_target_type(ctx, sym_table, used_sym_table, target.value) if not isinstance(t, ListType): - raise CustomError(f'cannot index through type {t}') + raise CustomError(f'cannot index through type {t}', target) if isinstance(target.slice, ast.Slice): - raise CustomError(f'assignment to slice is not supported') + raise CustomError(f'assignment to slice is not supported', target) i = parse_expr(ctx, sym_table, target.slice) if i != ctx.types['int32']: - raise CustomError(f'index must be int32') + raise CustomError(f'index must be int32', target.slice) return t.params[0] elif isinstance(target, ast.Attribute): t = get_target_type(ctx, sym_table, used_sym_table, target.value) if target.attr not in t.fields: - raise CustomError(f'{t} has no field {target.attr}') + raise CustomError(f'{t} has no field {target.attr}', target) return t.fields[target.attr] elif isinstance(target, ast.Name): if target.id not in sym_table: - raise CustomError(f'unbounded {target.id}') + raise CustomError(f'unbounded {target.id}', target) return sym_table[target.id] else: - raise CustomError(f'assignment to {target} is not supported') + raise CustomError(f'assignment to {target} is not supported', target) def parse_stmt_binding(ctx: Context, sym_table: dict[str, Type], @@ -67,15 +67,17 @@ def parse_stmt_binding(ctx: Context, if isinstance(target, ast.Name): if target.id in used_sym_table: if used_sym_table[target.id] != ty: - raise CustomError('inconsistent type') + raise CustomError(f'inconsistent type, {target.id} was ' \ + f'defined to be {used_sym_table[target.id]}, ' \ + f'but is now {ty}', target) if target.id == '_': return {} return {target.id: ty} elif isinstance(target, ast.Tuple): if not isinstance(ty, TupleType): - raise CustomError(f'cannot pattern match over {ty}') + raise CustomError(f'cannot pattern match over {ty}', target) if len(target.elts) != len(ty.params): - raise CustomError(f'pattern matching length mismatch') + raise CustomError(f'pattern matching length mismatch', target) result = {} for x, y in zip(target.elts, ty.params): new = parse_stmt_binding(ctx, sym_table, used_sym_table, x, y) @@ -83,12 +85,12 @@ def parse_stmt_binding(ctx: Context, result |= new used_sym_table |= new if len(result) != old_len + len(new): - raise CustomError(f'variable name clash') + raise CustomError(f'variable name clash', target) return result else: t = get_target_type(ctx, sym_table, used_sym_table, target) if ty != t: - raise CustomError(f'inconsistent type') + raise CustomError(f'type mismatch', target) return {} def parse_assign(ctx: Context, @@ -113,7 +115,7 @@ def parse_if_stmt(ctx: Context, node): test = parse_expr(ctx, sym_table, node.test) if test != ctx.types['bool']: - raise CustomError(f'condition must be bool instead of {test}') + raise CustomError(f'condition must be bool instead of {test}', node.test) a, b, r = parse_stmts(ctx, sym_table, used_sym_table, return_ty, node.body) used_sym_table |= b a1, b1, r1 = parse_stmts(ctx, sym_table, used_sym_table, return_ty, node.orelse) @@ -127,12 +129,12 @@ def parse_for_stmt(ctx: Context, node): ty = parse_expr(ctx, sym_table, node.iter) if not isinstance(ty, ListType): - raise CustomError('only iteration over list is supported') + raise CustomError('only iteration over list is supported', node.iter) binding = parse_simple_binding(node.target, ty.params[0]) for key, value in binding.items(): if key in used_sym_table: if value != used_sym_table[key]: - raise CustomError('inconsistent type') + raise CustomError('inconsistent type', node) # more sophisticated return analysis is needed... a, b, _ = parse_stmts(ctx, sym_table | binding, used_sym_table | binding, return_ty, node.body) @@ -148,7 +150,7 @@ def parse_while_stmt(ctx: Context, node): ty = parse_expr(ctx, sym_table, node.test) if ty != ctx.types['bool']: - raise CustomError('condition must be bool') + raise CustomError('condition must be bool', node.test) # more sophisticated return analysis is needed... a, b, _ = parse_stmts(ctx, sym_table, used_sym_table, return_ty, node.body) a1, b1, _ = parse_stmts(ctx, sym_table, used_sym_table | b, @@ -163,10 +165,10 @@ def parse_return_stmt(ctx: Context, node): if return_ty is None: if node.value is not None: - raise CustomError('no return value is allowed') + raise CustomError('no return value is allowed', node) return {}, {}, True ty = parse_expr(ctx, sym_table, node.value) if ty != return_ty: - raise CustomError(f'expected returning {return_ty} but got {ty}') + raise CustomError(f'expected returning {return_ty} but got {ty}', node) return {}, {}, True