MLBEDSW-2528: MLCE-219: Custom operator pass through

 - Fixed custom operator pass through
 - Added error printing functions for operators and tensor
 - Minor cleanup of custom exception handling

Signed-off-by: Tim Hall <tim.hall@arm.com>
Change-Id: Idf295df1e4c544381dc480244d880c32fb285e38
diff --git a/ethosu/vela/errors.py b/ethosu/vela/errors.py
index efe64d5..2c93fbc 100644
--- a/ethosu/vela/errors.py
+++ b/ethosu/vela/errors.py
@@ -15,6 +15,10 @@
 # limitations under the License.
 # Description:
 # Defines custom exceptions.
+import sys
+
+from .operation import Operation
+from .tensor import Tensor
 
 
 class VelaError(Exception):
@@ -31,7 +35,7 @@
     """Raised when reading the input file results in errors"""
 
     def __init__(self, file_name, msg):
-        self.data = "Error reading {}: {}".format(file_name, msg)
+        self.data = "Error reading input file {}: {}".format(file_name, msg)
 
 
 class UnsupportedFeatureError(VelaError):
@@ -45,4 +49,75 @@
     """Raised when an incorrect command line option is used"""
 
     def __init__(self, option, option_value, msg):
-        self.data = "Incorrect argument: {} {}: {}".format(option, option_value, msg)
+        self.data = "Incorrect argument to CLI option: {} {}: {}".format(option, option_value, msg)
+
+
+def OperatorError(op, msg):
+    """Called when parsing an operator results in errors"""
+
+    assert isinstance(op, Operation)
+
+    if op.op_index is None:
+        data = "Invalid {} (name = {}) operator in the internal representation.".format(op.type, op.name)
+    else:
+        data = "Invalid {} (op_index = {}) operator in the input network.".format(op.type, op.op_index)
+
+    data += " {}\n".format(msg)
+
+    data += "   Input tensors:\n"
+    for idx, tens in enumerate(op.inputs):
+        if isinstance(tens, Tensor):
+            tens_name = tens.name
+        else:
+            tens_name = "Not a Tensor"
+
+        data += "      {} = {}\n".format(idx, tens_name)
+
+    data += "   Output tensors:\n"
+    for idx, tens in enumerate(op.outputs):
+        if isinstance(tens, Tensor):
+            tens_name = tens.name
+        else:
+            tens_name = "Not a Tensor"
+
+        data += "      {} = {}\n".format(idx, tens_name)
+
+    data = data[:-1]  # remove last newline
+
+    print("Error: {}".format(data))
+    sys.exit(1)
+
+
+def TensorError(tens, msg):
+    """Called when parsing a tensor results in errors"""
+
+    assert isinstance(tens, Tensor)
+
+    data = "Invalid {} tensor. {}\n".format(tens.name, msg)
+
+    data += "   Driving operators:\n"
+    for idx, op in enumerate(tens.ops):
+        if isinstance(op, Operation):
+            op_type = op.type
+            op_id = op.op_index
+        else:
+            op_type = "Not an Operation"
+            op_id = ""
+
+        data += "      {} = {} ({})\n".format(idx, op_type, op_id)
+
+    data += "   Consuming operators:\n"
+    for idx, op in enumerate(tens.consumer_list):
+        if isinstance(op, Operation):
+            op_type = op.type
+            op_id = op.op_index
+        else:
+            op_type = "Not an Operation"
+            op_id = ""
+
+        data += "      {} = {} ({})\n".format(idx, op_type, op_id)
+
+    data = data[:-1]  # remove last newline
+
+    print("Error: {}".format(data))
+    sys.exit(1)