Address Pascal's comments from fxr/365760

https://fuchsia-review.googlesource.com/c/fuchsia/+/365760
diff --git a/backend/library.go b/backend/library.go
index 5b2f7d8..a2ea51a 100644
--- a/backend/library.go
+++ b/backend/library.go
@@ -12,7 +12,6 @@
 	"os"
 	"path/filepath"
 	"regexp"
-	"sort"
 	"strings"
 	"sync"
 )
@@ -244,15 +243,13 @@
 	return args, nil
 }
 
-const jsonDepIndentSize = 4
-
-// jsonDependencyGraph returns a JSON object encoded as a string representing
-// the dependency graph of m. Each key is a library name, and each value is
-// another object whose keys are its direct dependencies.
-func (m libraryMap) jsonDependencyGraph() (string, error) {
+// dependencyGraph returns a map representing the the dependency graph of m.
+// Each key is a library name, and each value is another map whose keys are its
+// direct dependencies.
+func (m libraryMap) dependencyGraph() (map[string]interface{}, error) {
 	// Make sure there are no cycles, otherwise this will never terminate.
 	if _, err := m.topologicalSort(); err != nil {
-		return "", err
+		return nil, err
 	}
 
 	sources := map[library]struct{}{}
@@ -264,44 +261,20 @@
 			delete(sources, dep)
 		}
 	}
-	var start []string
+	var start []library
 	for lib := range sources {
-		start = append(start, string(lib))
+		start = append(start, lib)
 	}
-	sort.Strings(start)
 
-	var b strings.Builder
-	indent := 0
-	var gen func(libs []string)
-	gen = func(libs []string) {
-		b.WriteString("{\n")
-		indent++
-		for i, lib := range libs {
-			b.WriteString(strings.Repeat(" ", indent*jsonDepIndentSize))
-			b.WriteString(fmt.Sprintf(`"%s": `, lib))
-			deps := m[library(lib)].deps
-			if len(deps) == 0 {
-				b.WriteString("{}")
-			} else {
-				var next []string
-				for _, dep := range deps {
-					next = append(next, string(dep))
-				}
-				sort.Strings(next)
-				gen(next)
-			}
-			if i == len(libs)-1 {
-				b.WriteString("\n")
-			} else {
-				b.WriteString(",\n")
-			}
+	var gen func(libs []library) map[string]interface{}
+	gen = func(libs []library) map[string]interface{} {
+		g := map[string]interface{}{}
+		for _, lib := range libs {
+			g[string(lib)] = gen(m[library(lib)].deps)
 		}
-		indent--
-		b.WriteString(strings.Repeat(" ", indent*jsonDepIndentSize))
-		b.WriteString("}")
+		return g
 	}
-	gen(start)
-	return b.String(), nil
+	return gen(start), nil
 }
 
 // topologicalSort runs topological sort on m, whose deps fields define a
diff --git a/backend/server.go b/backend/server.go
index a9b3934..2208cc0 100644
--- a/backend/server.go
+++ b/backend/server.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"log"
@@ -79,8 +80,6 @@
 	OutputMode Mode `json:"outputMode"`
 	// Options for configuring the conversion.
 	Options Options `json:"options"`
-	// Request context (not serialized).
-	ctx context.Context
 }
 
 // A Response contains the result of converting to an output mode.
@@ -161,34 +160,28 @@
 	Bytes:    {},
 }
 
-func outputModes(input Mode) modeSet {
-	switch input {
-	case FIDL:
-		return modeSet{
-			FIDL:  {},
-			JSON:  {},
-			C:     {},
-			LLCPP: {},
-			HLCPP: {},
-			Rust:  {},
-			Go:    {},
-			Dart:  {},
-		}
-	case FIDLText:
-		return modeSet{
-			FIDLText:  {},
-			Bytes:     {},
-			BytesPlus: {},
-		}
-	case Bytes:
-		return modeSet{
-			Bytes:     {},
-			Diff:      {},
-			FIDLText:  {},
-			BytesPlus: {},
-		}
-	}
-	return modeSet{}
+var outputModes = map[Mode]modeSet{
+	FIDL: {
+		FIDL:  {},
+		JSON:  {},
+		C:     {},
+		LLCPP: {},
+		HLCPP: {},
+		Rust:  {},
+		Go:    {},
+		Dart:  {},
+	},
+	FIDLText: {
+		FIDLText:  {},
+		Bytes:     {},
+		BytesPlus: {},
+	},
+	Bytes: {
+		Bytes:     {},
+		Diff:      {},
+		FIDLText:  {},
+		BytesPlus: {},
+	},
 }
 
 // Validate validates a request. Once validated, any errors in processing the
@@ -197,7 +190,7 @@
 	if err := r.InputMode.validate(inputModes); err != nil {
 		return err
 	}
-	if err := r.OutputMode.validate(outputModes(r.InputMode)); err != nil {
+	if err := r.OutputMode.validate(outputModes[r.InputMode]); err != nil {
 		return err
 	}
 
@@ -254,25 +247,24 @@
 // Serve processes a request and serves a response. It returns an error if the
 // request could not be processed for some reason (e.g., an IO failure).
 func (s *Server) Serve(ctx context.Context, r *Request) (Response, error) {
-	r.ctx = ctx
 	switch r.InputMode {
 	case FIDL:
-		return s.handleFIDL(r)
+		return s.handleFIDL(ctx, r)
 	case FIDLText:
-		return s.handleFIDLText(r)
+		return s.handleFIDLText(ctx, r)
 	case Bytes:
-		return s.handleBytes(r)
+		return s.handleBytes(ctx, r)
 	}
 	return internalErrorf("invalid input mode: %q", r.InputMode)
 }
 
-func (s *Server) handleFIDL(r *Request) (Response, error) {
+func (s *Server) handleFIDL(ctx context.Context, r *Request) (Response, error) {
 	temp, err := newTempDir()
 	if err != nil {
 		return internalError(err)
 	}
 	defer os.RemoveAll(temp.path)
-	res, err := s.handleFIDLWithTemp(r, temp)
+	res, err := s.handleFIDLWithTemp(ctx, r, temp)
 	if err != nil {
 		return res, err
 	}
@@ -287,7 +279,9 @@
 	return res, nil
 }
 
-func (s *Server) handleFIDLWithTemp(r *Request, temp tempDir) (Response, error) {
+const jsonDepIndentSize = 4
+
+func (s *Server) handleFIDLWithTemp(ctx context.Context, r *Request, temp tempDir) (Response, error) {
 	fidl, err := temp.createFile("fidlbolt.fidl", r.Content)
 	if err != nil {
 		return internalError(err)
@@ -302,7 +296,7 @@
 			msg := fmt.Sprintf("%s:1:1: error: %s", fidl, err)
 			return Response{Ok: false, Content: msg, Annotations: anns}
 		}
-		res := s.fidlc.run(r.ctx, append(args, fileArgs...)...)
+		res := s.fidlc.run(ctx, append(args, fileArgs...)...)
 		res.Annotations = anns
 		return res
 	}
@@ -317,30 +311,45 @@
 	switch r.OutputMode {
 	case FIDL:
 		if r.Options.Lint {
-			if res := s.fidlLint.run(r.ctx, fidl); !res.Ok {
+			if res := s.fidlLint.run(ctx, fidl); !res.Ok {
 				return res, nil
 			}
+			return Response{Ok: true, Content: "// Lint passed!"}, nil
 		}
-		return s.fidlFormat.run(r.ctx, fidl), nil
+		return s.fidlFormat.run(ctx, fidl), nil
 	case JSON:
 		switch r.Options.File {
 		case "ir":
 			return fidlc("--json", "/dev/stdout"), nil
 		case "schema":
-			return s.fidlc.run(r.ctx, "--json-schema"), nil
+			return s.fidlc.run(ctx, "--json-schema"), nil
 		case "deps":
 			libs, anns := s.libraryAnalyzer.analyze(fidl, r.Content)
-			graph, err := libs.jsonDependencyGraph()
+			graph, err := libs.dependencyGraph()
 			if err != nil {
 				msg := fmt.Sprintf("%s:1:1: error: %s", fidl, err)
 				return Response{Ok: false, Content: msg, Annotations: anns}, nil
 			}
-			return Response{Ok: true, Content: graph, Annotations: anns}, nil
+			b, err := json.MarshalIndent(graph, "", strings.Repeat(" ", jsonDepIndentSize))
+			if err != nil {
+				msg := fmt.Sprintf("%s:1:1: error: %s", fidl, err)
+				return Response{Ok: false, Content: msg, Annotations: anns}, nil
+			}
+			return Response{Ok: true, Content: string(b), Annotations: anns}, nil
 		default:
 			return internalErrorf("invalid file: %q", r.Options.File)
 		}
 	case C:
-		return fidlc("--c-"+r.Options.File, "/dev/stdout"), nil
+		switch r.Options.File {
+		case "header":
+			return fidlc("--c-header", "/dev/stdout"), nil
+		case "client":
+			return fidlc("--c-client", "/dev/stdout"), nil
+		case "server":
+			return fidlc("--c-server", "/dev/stdout"), nil
+		default:
+			return internalErrorf("invalid file: %q", r.Options.File)
+		}
 	}
 
 	jsonIR := fidl + ".json"
@@ -348,7 +357,7 @@
 	if !fidlcRes.Ok {
 		return fidlcRes, nil
 	}
-	res, err := s.handleFIDLWithIR(r, temp, jsonIR)
+	res, err := s.handleFIDLWithIR(ctx, r, temp, jsonIR)
 	if err != nil {
 		return internalError(err)
 	}
@@ -356,11 +365,10 @@
 	return res, nil
 }
 
-func (s *Server) handleFIDLWithIR(r *Request, temp tempDir, jsonIR string) (Response, error) {
+func (s *Server) handleFIDLWithIR(ctx context.Context, r *Request, temp tempDir, jsonIR string) (Response, error) {
 	switch r.OutputMode {
 	case HLCPP:
-		if res := s.fidlgenHlcpp.run(r.ctx,
-			"-generators", "cpp",
+		if res := s.fidlgenHlcpp.run(ctx,
 			"-json", jsonIR,
 			"-include-base", temp.path,
 			"-output-base", temp.join("impl"),
@@ -376,7 +384,7 @@
 			return internalErrorf("invalid file: %q", r.Options.File)
 		}
 	case LLCPP:
-		if res := s.fidlgenLlcpp.run(r.ctx,
+		if res := s.fidlgenLlcpp.run(ctx,
 			"-json", jsonIR,
 			"-header", temp.join("impl.h"),
 			"-source", temp.join("impl.cc"),
@@ -392,7 +400,7 @@
 			return internalErrorf("invalid file: %q", r.Options.File)
 		}
 	case Rust:
-		if res := s.fidlgenRust.run(r.ctx,
+		if res := s.fidlgenRust.run(ctx,
 			"-json", jsonIR,
 			"-output-filename", temp.join("impl.rs"),
 		); !res.Ok {
@@ -400,7 +408,7 @@
 		}
 		return temp.readFile("impl.rs")
 	case Go:
-		if res := s.fidlgenGo.run(r.ctx,
+		if res := s.fidlgenGo.run(ctx,
 			"-json", jsonIR,
 			"-output-impl", temp.join("impl.go"),
 		); !res.Ok {
@@ -417,7 +425,7 @@
 		default:
 			return internalErrorf("invalid file: %q", r.Options.File)
 		}
-		if res := s.fidlgenDart.run(r.ctx,
+		if res := s.fidlgenDart.run(ctx,
 			"-json", jsonIR,
 			"-output-"+name, temp.join("impl.dart"),
 		); !res.Ok {
@@ -428,11 +436,11 @@
 	return internalErrorf("invalid output mode: %q", r.OutputMode)
 }
 
-func (s *Server) handleFIDLText(r *Request) (Response, error) {
+func (s *Server) handleFIDLText(ctx context.Context, r *Request) (Response, error) {
 	return Response{Ok: false, Content: "Not implemented"}, nil
 }
 
-func (s *Server) handleBytes(r *Request) (Response, error) {
+func (s *Server) handleBytes(ctx context.Context, r *Request) (Response, error) {
 	switch r.OutputMode {
 	case Bytes:
 		b, pos, err := parseBytes(r.Content)
@@ -486,6 +494,8 @@
 
 // run runs a program with arguments and produces a response. It stops the
 // program and returns if ctx is cancelled.
+// TODO: Consider returning a string and error here, and letting the top-level
+// handlers produce responses instead.
 func (p program) run(ctx context.Context, arg ...string) Response {
 	// TODO: use exec.CommandContext!
 	cmd := exec.Command(p.path, arg...)
@@ -540,10 +550,10 @@
 	if err != nil {
 		return "", err
 	}
+	defer f.Close()
 	if _, err = f.WriteString(content); err != nil {
 		return "", err
 	}
-	f.Close()
 	return f.Name(), nil
 }
 
@@ -556,8 +566,15 @@
 	return Response{Ok: true, Content: string(b)}, nil
 }
 
-var fidlAnnotationRegexp = regexp.MustCompile(
-	`(?m)^fidlbolt\.fidl:(\d+):(\d+).*?(info|error|warning):\s*(.*)$`)
+var fidlAnnotationRegexp = regexp.MustCompile(`` +
+	// Multi-line mode: ^ and $ match begin/end of line OR text.
+	`(?m)` +
+	`^fidlbolt\.fidl:(\d+):(\d+).*?` +
+	`(` +
+	string(Info) + `|` +
+	string(Warning) + `|` +
+	string(Error) + `)` +
+	`:\s*(.*)$`)
 
 // parseFidlAnnotations parses a list of annotations from error output. It
 // assumes that the output refers to a file called "fidlbolt.fidl".