Use front end and back end libraries directly

Rather than spwaning 2 subprocesses and serializing IR data between them
we can just use the front end and back end libraries directly. This is a
code simplification as well as a performance improvement showing up a 3%
reduction in overhead for larger emb files.

Fixes #122
diff --git a/embossc b/embossc
index cee92d8..0dac79f 100755
--- a/embossc
+++ b/embossc
@@ -18,10 +18,8 @@
 
 import argparse
 import os
-import subprocess
 import sys
 
-
 def _parse_args(argv):
   parser = argparse.ArgumentParser(description="Emboss compiler")
   parser.add_argument("--color-output",
@@ -59,48 +57,28 @@
 
 
 def main(argv):
-
   flags = _parse_args(argv)
   base_path = os.path.dirname(__file__) or "."
-  subprocess_environment = os.environ.copy()
+  sys.path.append(base_path)
 
-  if subprocess_environment.get("PYTHONPATH"):
-    subprocess_environment["PYTHONPATH"] = (
-      base_path + ":" + subprocess_environment.get("PYTHONPATH"))
-  else:
-    subprocess_environment["PYTHONPATH"] = base_path
-
-  front_end_args = [
-      sys.executable,
-      os.path.join(base_path, "compiler", "front_end", "emboss_front_end.py"),
-      "--output-ir-to-stdout",
-      "--color-output", flags.color_output,
-  ]
-
-  for import_dir in flags.import_dirs:
-    front_end_args.extend(["--import-dir", import_dir])
-
-  front_end_args.append(flags.input_file[0])
-  front_end_status = subprocess.run(front_end_args,
-                                    stdout=subprocess.PIPE,
-                                    env=subprocess_environment)
-
-  if front_end_status.returncode != 0:
-    return front_end_status.returncode
-
-  back_end_status = subprocess.run(
-    [
-      sys.executable,
-      os.path.join(base_path, "compiler", "back_end", "cpp",
-        "emboss_codegen_cpp.py"),
-    ],
-    input=front_end_status.stdout,
-    stdout=subprocess.PIPE,
-    env=subprocess_environment
+  from compiler.back_end.cpp import ( # pylint:disable=import-outside-toplevel
+    emboss_codegen_cpp
+  )
+  from compiler.front_end import ( # pylint:disable=import-outside-toplevel
+    emboss_front_end
   )
 
-  if back_end_status.returncode != 0:
-    return back_end_status.returncode
+  ir, _, errors =  emboss_front_end.parse_and_log_errors(
+      flags.input_file[0], flags.import_dirs, flags.color_output)
+
+  if errors:
+    return 1
+
+  header, errors = emboss_codegen_cpp.generate_headers_and_log_errors(
+      ir, flags.color_output)
+
+  if errors:
+    return 1
 
   if flags.output_file:
     output_file = flags.output_file[0]
@@ -110,8 +88,8 @@
   output_filepath = os.path.join(flags.output_path[0], output_file)
   os.makedirs(os.path.dirname(output_filepath), exist_ok=True)
 
-  with open(output_filepath, "wb") as output:
-    output.write(back_end_status.stdout)
+  with open(output_filepath, "w") as output:
+    output.write(header)
   return 0