go.tools/oracle: support -format=xml (for Eclipse)

This CL is mostly a renaming s/json/serial/, abstracting the
oracle package away from any particular data syntax.  (The
encoding/* machinery is very clean; clearly I should have
structured it this way from the outset.)

Supporting XML then becomes a one-liner in cmd/oracle/main.go.

Also: call MarshalIndent(), not Marshall() then Indent().

R=crawshaw
CC=golang-dev
https://golang.org/cl/13858046
This commit is contained in:
Alan Donovan 2013-09-24 15:08:14 -04:00
parent 84cae5a52d
commit 37f76edde8
13 changed files with 86 additions and 83 deletions

View File

@ -14,8 +14,8 @@ package main
import (
"bufio"
"bytes"
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"go/build"
@ -39,7 +39,7 @@ var modeFlag = flag.String("mode", "",
var ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.")
var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.")
var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.")
// TODO(adonovan): eliminate or flip this flag after PTA presolver is implemented.
var reflectFlag = flag.Bool("reflect", true, "Analyze reflection soundly (slow).")
@ -53,6 +53,7 @@ The -format flag controls the output format:
plain an editor-friendly format in which every line of output
is of the form "pos: text", where pos is "-" if unknown.
json structured data in JSON syntax.
xml structured data in XML syntax.
The -pos flag is required in all modes except 'callgraph'.
@ -140,7 +141,10 @@ func main() {
}
// -format flag
if *formatFlag != "json" && *formatFlag != "plain" {
switch *formatFlag {
case "json", "plain", "xml":
// ok
default:
fmt.Fprintf(os.Stderr, "Error: illegal -format value: %q\n"+useHelp, *formatFlag)
os.Exit(2)
}
@ -161,17 +165,20 @@ func main() {
// Print the result.
switch *formatFlag {
case "json":
b, err := json.Marshal(res)
b, err := json.MarshalIndent(res.Serial(), "", "\t")
if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err)
os.Exit(1)
}
var buf bytes.Buffer
if err := json.Indent(&buf, b, "", "\t"); err != nil {
fmt.Fprintf(os.Stderr, "json.Indent failed: %s", err)
os.Stdout.Write(b)
case "xml":
b, err := xml.MarshalIndent(res.Serial(), "", "\t")
if err != nil {
fmt.Fprintf(os.Stderr, "XML error: %s\n", err)
os.Exit(1)
}
os.Stdout.Write(buf.Bytes())
os.Stdout.Write(b)
case "plain":
res.WriteTo(os.Stdout)

View File

@ -10,7 +10,7 @@ import (
"sort"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -113,13 +113,13 @@ func (r *calleesResult) display(printf printfFunc) {
}
}
func (r *calleesResult) toJSON(res *json.Result, fset *token.FileSet) {
j := &json.Callees{
func (r *calleesResult) toSerial(res *serial.Result, fset *token.FileSet) {
j := &serial.Callees{
Pos: fset.Position(r.site.Pos()).String(),
Desc: r.site.Description(),
}
for _, callee := range r.funcs {
j.Callees = append(j.Callees, &json.CalleesItem{
j.Callees = append(j.Callees, &serial.CalleesItem{
Name: callee.String(),
Pos: fset.Position(callee.Pos()).String(),
})

View File

@ -7,7 +7,7 @@ package oracle
import (
"go/token"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -73,10 +73,10 @@ func (r *callersResult) display(printf printfFunc) {
}
}
func (r *callersResult) toJSON(res *json.Result, fset *token.FileSet) {
var callers []json.Caller
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
var callers []serial.Caller
for _, site := range r.calls {
var c json.Caller
var c serial.Caller
c.Caller = site.Caller().Func().String()
if site.Caller() == r.root {
c.Desc = "synthetic call"

View File

@ -8,7 +8,7 @@ import (
"go/token"
"strings"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
)
@ -90,8 +90,8 @@ Some nodes may appear multiple times due to context-sensitive
print(r.root, 0)
}
func (r *callgraphResult) toJSON(res *json.Result, fset *token.FileSet) {
cg := make([]json.CallGraph, len(r.numbering))
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
cg := make([]serial.CallGraph, len(r.numbering))
for n, i := range r.numbering {
j := &cg[i]
fn := n.Func()

View File

@ -7,7 +7,7 @@ package oracle
import (
"go/token"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -98,16 +98,16 @@ func (r *callstackResult) display(printf printfFunc) {
}
}
func (r *callstackResult) toJSON(res *json.Result, fset *token.FileSet) {
var callers []json.Caller
func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) {
var callers []serial.Caller
for _, site := range r.callstack {
callers = append(callers, json.Caller{
callers = append(callers, serial.Caller{
Pos: fset.Position(site.Pos()).String(),
Caller: site.Caller().Func().String(),
Desc: site.Description(),
})
}
res.Callstack = &json.CallStack{
res.Callstack = &serial.CallStack{
Pos: fset.Position(r.target.Pos()).String(),
Target: r.target.String(),
Callers: callers,

View File

@ -17,7 +17,7 @@ import (
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -70,8 +70,8 @@ func (r *describeUnknownResult) display(printf printfFunc) {
printf(r.node, "%s", importer.NodeDescription(r.node))
}
func (r *describeUnknownResult) toJSON(res *json.Result, fset *token.FileSet) {
res.Describe = &json.Describe{
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) {
res.Describe = &serial.Describe{
Desc: importer.NodeDescription(r.node),
Pos: fset.Position(r.node.Pos()).String(),
}
@ -553,7 +553,7 @@ func (r *describeValueResult) display(printf printfFunc) {
}
}
func (r *describeValueResult) toJSON(res *json.Result, fset *token.FileSet) {
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
var value, objpos, ptaerr string
if r.constVal != nil {
value = r.constVal.String()
@ -565,31 +565,31 @@ func (r *describeValueResult) toJSON(res *json.Result, fset *token.FileSet) {
ptaerr = r.ptaErr.Error()
}
var pts []*json.DescribePointer
var pts []*serial.DescribePointer
for _, ptr := range r.ptrs {
var namePos string
if nt, ok := deref(ptr.typ).(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
}
var labels []json.DescribePTALabel
var labels []serial.DescribePTALabel
for _, l := range ptr.labels {
labels = append(labels, json.DescribePTALabel{
labels = append(labels, serial.DescribePTALabel{
Pos: fset.Position(l.Pos()).String(),
Desc: l.String(),
})
}
pts = append(pts, &json.DescribePointer{
pts = append(pts, &serial.DescribePointer{
Type: ptr.typ.String(),
NamePos: namePos,
Labels: labels,
})
}
res.Describe = &json.Describe{
res.Describe = &serial.Describe{
Desc: importer.NodeDescription(r.expr),
Pos: fset.Position(r.expr.Pos()).String(),
Detail: "value",
Value: &json.DescribeValue{
Value: &serial.DescribeValue{
Type: r.typ.String(),
Value: value,
ObjPos: objpos,
@ -689,21 +689,21 @@ func (r *describeTypeResult) display(printf printfFunc) {
}
}
func (r *describeTypeResult) toJSON(res *json.Result, fset *token.FileSet) {
func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
var namePos, nameDef string
if nt, ok := r.typ.(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
nameDef = nt.Underlying().String()
}
res.Describe = &json.Describe{
res.Describe = &serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "type",
Type: &json.DescribeType{
Type: &serial.DescribeType{
Type: r.typ.String(),
NamePos: namePos,
NameDef: nameDef,
Methods: methodsToJSON(r.methods, fset),
Methods: methodsToSerial(r.methods, fset),
},
}
}
@ -831,8 +831,8 @@ func formatMember(obj types.Object, maxname int) string {
return buf.String()
}
func (r *describePackageResult) toJSON(res *json.Result, fset *token.FileSet) {
var members []*json.DescribeMember
func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) {
var members []*serial.DescribeMember
for _, mem := range r.members {
typ := mem.obj.Type()
var val string
@ -842,20 +842,20 @@ func (r *describePackageResult) toJSON(res *json.Result, fset *token.FileSet) {
case *types.TypeName:
typ = typ.Underlying()
}
members = append(members, &json.DescribeMember{
members = append(members, &serial.DescribeMember{
Name: mem.obj.Name(),
Type: typ.String(),
Value: val,
Pos: fset.Position(mem.obj.Pos()).String(),
Kind: tokenOf(mem.obj),
Methods: methodsToJSON(mem.methods, fset),
Methods: methodsToSerial(mem.methods, fset),
})
}
res.Describe = &json.Describe{
res.Describe = &serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "package",
Package: &json.DescribePackage{
Package: &serial.DescribePackage{
Path: r.pkg.Path(),
Members: members,
},
@ -907,8 +907,8 @@ func (r *describeStmtResult) display(printf printfFunc) {
printf(r.node, "%s", r.description)
}
func (r *describeStmtResult) toJSON(res *json.Result, fset *token.FileSet) {
res.Describe = &json.Describe{
func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
res.Describe = &serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "unknown",
@ -946,10 +946,10 @@ func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
}
func methodsToJSON(methods []*types.Selection, fset *token.FileSet) []json.DescribeMethod {
var jmethods []json.DescribeMethod
func methodsToSerial(methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
var jmethods []serial.DescribeMethod
for _, meth := range methods {
jmethods = append(jmethods, json.DescribeMethod{
jmethods = append(jmethods, serial.DescribeMethod{
Name: meth.String(),
Pos: fset.Position(meth.Obj().Pos()).String(),
})

View File

@ -10,7 +10,7 @@ import (
"sort"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
)
// freevars displays the lexical (not package-level) free variables of
@ -169,11 +169,11 @@ func (r *freevarsResult) display(printf printfFunc) {
}
}
func (r *freevarsResult) toJSON(res *json.Result, fset *token.FileSet) {
var refs []*json.FreeVar
func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) {
var refs []*serial.FreeVar
for _, ref := range r.refs {
refs = append(refs,
&json.FreeVar{
&serial.FreeVar{
Pos: fset.Position(ref.obj.Pos()).String(),
Kind: ref.kind,
Ref: ref.ref,

View File

@ -8,7 +8,7 @@ import (
"go/token"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
)
// Implements displays the 'implements" relation among all
@ -89,10 +89,10 @@ func (r *implementsResult) display(printf printfFunc) {
}
}
func (r *implementsResult) toJSON(res *json.Result, fset *token.FileSet) {
var facts []*json.Implements
func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
var facts []*serial.Implements
for _, fact := range r.facts {
facts = append(facts, &json.Implements{
facts = append(facts, &serial.Implements{
I: fact.iface.String(),
IPos: fset.Position(fact.iface.Obj().Pos()).String(),
C: fact.conc.String(),

View File

@ -18,7 +18,6 @@ package oracle
import (
"bytes"
encjson "encoding/json"
"errors"
"fmt"
"go/ast"
@ -34,7 +33,7 @@ import (
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -97,7 +96,7 @@ type printfFunc func(pos interface{}, format string, args ...interface{})
// queryResult is the interface of each query-specific result type.
type queryResult interface {
toJSON(res *json.Result, fset *token.FileSet)
toSerial(res *serial.Result, fset *token.FileSet)
display(printf printfFunc)
}
@ -119,9 +118,6 @@ type QueryPos struct {
}
// A Result encapsulates the result of an oracle.Query.
//
// Result instances implement the json.Marshaler interface, i.e. they
// can be JSON-serialized.
type Result struct {
fset *token.FileSet
// fprintf is a closure over the oracle's fileset and start/end position.
@ -131,16 +127,20 @@ type Result struct {
warnings []warning // pointer analysis warnings
}
func (res *Result) MarshalJSON() ([]byte, error) {
resj := &json.Result{Mode: res.mode}
res.q.toJSON(resj, res.fset)
// Serial returns an instance of serial.Result, which implements the
// {xml,json}.Marshaler interfaces so that query results can be
// serialized as JSON or XML.
//
func (res *Result) Serial() *serial.Result {
resj := &serial.Result{Mode: res.mode}
res.q.toSerial(resj, res.fset)
for _, w := range res.warnings {
resj.Warnings = append(resj.Warnings, json.PTAWarning{
resj.Warnings = append(resj.Warnings, serial.PTAWarning{
Pos: res.fset.Position(w.pos).String(),
Message: fmt.Sprintf(w.format, w.args...),
})
}
return encjson.Marshal(resj)
return resj
}
// Query runs a single oracle query.

View File

@ -175,17 +175,12 @@ func doQuery(out io.Writer, q *query, useJson bool) {
if useJson {
// JSON output
b, err := json.Marshal(res)
b, err := json.MarshalIndent(res.Serial(), "", "\t")
if err != nil {
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
return
}
var buf bytes.Buffer
if err := json.Indent(&buf, b, "", "\t"); err != nil {
fmt.Fprintf(out, "json.Indent failed: %s", err)
return
}
out.Write(buf.Bytes())
out.Write(b)
} else {
// "plain" (compiler diagnostic format) output
capture := new(bytes.Buffer) // capture standard output

View File

@ -10,7 +10,7 @@ import (
"sort"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
"code.google.com/p/go.tools/pointer"
"code.google.com/p/go.tools/ssa"
)
@ -174,8 +174,8 @@ func (r *peersResult) display(printf printfFunc) {
}
}
func (r *peersResult) toJSON(res *json.Result, fset *token.FileSet) {
peers := &json.Peers{
func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) {
peers := &serial.Peers{
Pos: fset.Position(r.queryPos).String(),
Type: r.queryType.String(),
}

View File

@ -10,7 +10,7 @@ import (
"sort"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/oracle/json"
"code.google.com/p/go.tools/oracle/serial"
)
// Referrers reports all identifiers that resolve to the same object
@ -84,8 +84,8 @@ func (r *referrersResult) display(printf printfFunc) {
}
}
func (r *referrersResult) toJSON(res *json.Result, fset *token.FileSet) {
referrers := &json.Referrers{
func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
referrers := &serial.Referrers{
Pos: fset.Position(r.query).String(),
Desc: r.obj.String(),
}

View File

@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package json defines the oracle's JSON schema.
package json
// Package serial defines the oracle's schema for structured data
// serialization using JSON, XML, etc.
package serial
// All 'pos' strings are of the form "file:line:col".
// TODO(adonovan): improve performance by sharing filename strings.