Initial open-source commit of Emboss.
diff --git a/util/error.py b/util/error.py
new file mode 100644
index 0000000..5a49e70
--- /dev/null
+++ b/util/error.py
@@ -0,0 +1,244 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Error and warning message support for Emboss.
+
+This module exports the error, warn, and note functions, which return a _Message
+representing the error, warning, or note, respectively.  The format method of
+the returned object can be used to render the message with source code snippets.
+
+Throughout Emboss, messages are passed around as lists of lists of _Messages.
+Each inner list represents a group of messages which should either all be
+printed, or not printed; i.e., an error message and associated informational
+messages.  For example, to indicate both a duplicate definition error and a
+warning that a field is a reserved word, one might return:
+
+    return [
+        [
+            error.error(file_name, location, "Duplicate definition),
+            error.note(original_file_name, original_location,
+                       "Original definition"),
+        ],
+        [
+            error.warn(file_name, location, "Field name is a C reserved word.")
+        ],
+    ]
+"""
+
+from util import parser_types
+
+# Error levels; represented by the strings that will be included in messages.
+ERROR = "error"
+WARNING = "warning"
+NOTE = "note"
+
+# Colors; represented by the terminal escape sequences used to switch to them.
+# These work out-of-the-box on Unix derivatives (Linux, *BSD, Mac OS X), and
+# work on Windows using colorify.
+BLACK = "\033[0;30m"
+RED = "\033[0;31m"
+GREEN = "\033[0;32m"
+YELLOW = "\033[0;33m"
+BLUE = "\033[0;34m"
+MAGENTA = "\033[0;35m"
+CYAN = "\033[0;36m"
+WHITE = "\033[0;37m"
+BRIGHT_BLACK = "\033[0;1;30m"
+BRIGHT_RED = "\033[0;1;31m"
+BRIGHT_GREEN = "\033[0;1;32m"
+BRIGHT_YELLOW = "\033[0;1;33m"
+BRIGHT_BLUE = "\033[0;1;34m"
+BRIGHT_MAGENTA = "\033[0;1;35m"
+BRIGHT_CYAN = "\033[0;1;36m"
+BRIGHT_WHITE = "\033[0;1;37m"
+BOLD = "\033[0;1m"
+RESET = "\033[0m"
+
+
+def error(source_file, location, message):
+  """Returns an object representing an error message."""
+  return _Message(source_file, location, ERROR, message)
+
+
+def warn(source_file, location, message):
+  """Returns an object representing a warning."""
+  return _Message(source_file, location, WARNING, message)
+
+
+def note(source_file, location, message):
+  """Returns and object representing an informational note."""
+  return _Message(source_file, location, NOTE, message)
+
+
+class _Message(object):
+  """_Message holds a human-readable message."""
+  __slots__ = ("location", "source_file", "severity", "message")
+
+  def __init__(self, source_file, location, severity, message):
+    self.location = location
+    self.source_file = source_file
+    self.severity = severity
+    self.message = message
+
+  def format(self, source_code):
+    """Formats the _Message for display.
+
+    Arguments:
+      source_code: A dict of file names to source texts.  This is used to
+        render source snippets.
+
+    Returns:
+      A list of tuples.
+
+      The first element of each tuple is an escape sequence used to put a Unix
+      terminal into a particular color mode.  For use in non-Unix-terminal
+      output, the string will match one of the color names exported by this
+      module.
+
+      The second element is a string containing text to show to the user.
+
+      The text will not end with a newline character, nor will it include a
+      RESET color element.
+
+      To show non-colorized output, simply write the second element of each
+      tuple, then a newline at the end.
+
+      To show colorized output, write both the first and second element of each
+      tuple, then a newline at the end.  Before exiting to the operating system,
+      a RESET sequence should be emitted.
+    """
+    # TODO(bolms): Figure out how to get Vim, Emacs, etc. to parse Emboss error
+    #     messages.
+    severity_colors = {
+        ERROR: (BRIGHT_RED, BOLD),
+        WARNING: (BRIGHT_MAGENTA, BOLD),
+        NOTE: (BRIGHT_BLACK, WHITE)
+    }
+
+    result = []
+    if self.location.is_synthetic:
+      pos = "[compiler bug]"
+    else:
+      pos = parser_types.format_position(self.location.start)
+    source_name = self.source_file or "[prelude]"
+    if not self.location.is_synthetic and self.source_file in source_code:
+      source_lines = source_code[self.source_file].splitlines()
+      source_line = source_lines[self.location.start.line - 1]
+    else:
+      source_line = ""
+    lines = self.message.splitlines()
+    for i in range(len(lines)):
+      line = lines[i]
+      # This is a little awkward, but we want to suppress the final newline in
+      # the message.  This newline is final if and only if it is the last line
+      # of the message and there is no source snippet.
+      if i != len(lines) - 1 or source_line:
+        line += "\n"
+      result.append((BOLD, "{}:{}: ".format(source_name, pos)))
+      if i == 0:
+        severity = self.severity
+      else:
+        severity = NOTE
+      result.append((severity_colors[severity][0], "{}: ".format(severity)))
+      result.append((severity_colors[severity][1], line))
+    if source_line:
+      result.append((WHITE, source_line + "\n"))
+      indicator_indent = " " * (self.location.start.column - 1)
+      if self.location.start.line == self.location.end.line:
+        indicator_caret = "^" * max(
+            1, self.location.end.column - self.location.start.column)
+      else:
+        indicator_caret = "^"
+      result.append((BRIGHT_GREEN, indicator_indent + indicator_caret))
+    return result
+
+  def __repr__(self):
+    return ("Message({source_file!r}, make_location(({start_line!r}, "
+            "{start_column!r}), ({end_line!r}, {end_column!r}), "
+            "{is_synthetic!r}), {severity!r}, {message!r})").format(
+                source_file=self.source_file,
+                start_line=self.location.start.line,
+                start_column=self.location.start.column,
+                end_line=self.location.end.line,
+                end_column=self.location.end.column,
+                is_synthetic=self.location.is_synthetic,
+                severity=self.severity,
+                message=self.message)
+
+  def __eq__(self, other):
+    return (
+        self.__class__ == other.__class__ and self.location == other.location
+        and self.source_file == other.source_file and
+        self.severity == other.severity and self.message == other.message)
+
+  def __ne__(self, other):
+    return not self == other
+
+
+def split_errors(errors):
+  """Splits errors into (user_errors, synthetic_errors).
+
+  Arguments:
+      errors: A list of lists of _Message, which is a list of bundles of
+          associated messages.
+
+  Returns:
+      (user_errors, synthetic_errors), where both user_errors and
+      synthetic_errors are lists of lists of _Message.  synthetic_errors will
+      contain all bundles that reference any synthetic source_location, and
+      user_errors will contain the rest.
+
+      The intent is that user_errors can be shown to end users, while
+      synthetic_errors should generally be suppressed.
+  """
+  synthetic_errors = []
+  user_errors = []
+  for error_block in errors:
+    if any(message.location.is_synthetic for message in error_block):
+      synthetic_errors.append(error_block)
+    else:
+      user_errors.append(error_block)
+  return user_errors, synthetic_errors
+
+
+def filter_errors(errors):
+  """Returns the non-synthetic errors from `errors`."""
+  return split_errors(errors)[0]
+
+
+def format_errors(errors, source_codes, use_color=False):
+  """Formats error messages with source code snippets."""
+  result = []
+  for error_group in errors:
+    assert error_group, "Found empty error_group!"
+    for message in error_group:
+      if use_color:
+        result.append("".join(e[0] + e[1] + RESET
+                              for e in message.format(source_codes)))
+      else:
+        result.append("".join(e[1] for e in message.format(source_codes)))
+  return "\n".join(result)
+
+
+def make_error_from_parse_error(file_name, parse_error):
+  return [error(file_name,
+                parse_error.token.source_location,
+                "{code}\n"
+                "Found {text!r} ({symbol}), expected {expected}.".format(
+                    code=parse_error.code or "Syntax error",
+                    text=parse_error.token.text,
+                    symbol=parse_error.token.symbol,
+                    expected=", ".join(parse_error.expected_tokens)))]
+
+