Commit 5c08b9e8 authored by Robert Griesemer's avatar Robert Griesemer

cmd/compile/internal/syntax: remove dependency on cmd/internal/src

For dependency reasons, the data structure implementing source
positions in the compiler is in cmd/internal/src. It contains
highly compiler specific details (e.g. inlining index).

This change introduces a parallel but simpler position
representation, defined in the syntax package, which removes
that package's dependency on cmd/internal/src, and also removes
the need to deal with certain filename-specific operations
(defined by the needs of the compiler) in the syntax package.
As a result, the syntax package becomes again a compiler-
independent, stand-alone package that at some point might
replace (or augment) the existing top-level go/* syntax-related
packages.

Additionally, line directives that update column numbers
are now correctly tracked through the syntax package, with
additional tests added. (The respective changes also need to
be made in cmd/internal/src; i.e., the compiler accepts but
still ignores column numbers in line directives.)

This change comes at the cost of a new position translation
step, but that step is cheap because it only needs to do real
work if the position base changed (i.e., if there is a new file,
or new line directive).

There is no noticeable impact on overall compiler performance
measured with `compilebench -count 5 -alloc`:

name       old time/op       new time/op       delta
Template         220ms ± 8%        228ms ±18%    ~     (p=0.548 n=5+5)
Unicode          119ms ±11%        113ms ± 5%    ~     (p=0.056 n=5+5)
GoTypes          684ms ± 6%        677ms ± 3%    ~     (p=0.841 n=5+5)
Compiler         3.19s ± 7%        3.01s ± 1%    ~     (p=0.095 n=5+5)
SSA              7.92s ± 8%        7.79s ± 1%    ~     (p=0.690 n=5+5)
Flate            141ms ± 7%        139ms ± 4%    ~     (p=0.548 n=5+5)
GoParser         173ms ±12%        171ms ± 4%    ~     (p=1.000 n=5+5)
Reflect          417ms ± 5%        411ms ± 3%    ~     (p=0.548 n=5+5)
Tar              205ms ± 5%        198ms ± 2%    ~     (p=0.690 n=5+5)
XML              232ms ± 4%        229ms ± 4%    ~     (p=0.690 n=5+5)
StdCmd           28.7s ± 5%        28.2s ± 2%    ~     (p=0.421 n=5+5)

name       old user-time/op  new user-time/op  delta
Template         269ms ± 4%        265ms ± 5%    ~     (p=0.421 n=5+5)
Unicode          153ms ± 7%        149ms ± 3%    ~     (p=0.841 n=5+5)
GoTypes          850ms ± 7%        862ms ± 4%    ~     (p=0.841 n=5+5)
Compiler         4.01s ± 5%        3.86s ± 0%    ~     (p=0.190 n=5+4)
SSA              10.9s ± 4%        10.8s ± 2%    ~     (p=0.548 n=5+5)
Flate            166ms ± 7%        167ms ± 6%    ~     (p=1.000 n=5+5)
GoParser         204ms ± 8%        206ms ± 7%    ~     (p=0.841 n=5+5)
Reflect          514ms ± 5%        508ms ± 4%    ~     (p=0.548 n=5+5)
Tar              245ms ± 6%        244ms ± 3%    ~     (p=0.690 n=5+5)
XML              280ms ± 4%        278ms ± 4%    ~     (p=0.841 n=5+5)

name       old alloc/op      new alloc/op      delta
Template        37.9MB ± 0%       37.9MB ± 0%    ~     (p=0.841 n=5+5)
Unicode         28.8MB ± 0%       28.8MB ± 0%    ~     (p=0.841 n=5+5)
GoTypes          113MB ± 0%        113MB ± 0%    ~     (p=0.151 n=5+5)
Compiler         468MB ± 0%        468MB ± 0%  -0.01%  (p=0.032 n=5+5)
SSA             1.50GB ± 0%       1.50GB ± 0%    ~     (p=0.548 n=5+5)
Flate           24.4MB ± 0%       24.4MB ± 0%    ~     (p=1.000 n=5+5)
GoParser        30.7MB ± 0%       30.7MB ± 0%    ~     (p=1.000 n=5+5)
Reflect         76.5MB ± 0%       76.5MB ± 0%    ~     (p=0.548 n=5+5)
Tar             38.9MB ± 0%       38.9MB ± 0%    ~     (p=0.222 n=5+5)
XML             41.6MB ± 0%       41.6MB ± 0%    ~     (p=0.548 n=5+5)

name       old allocs/op     new allocs/op     delta
Template          382k ± 0%         382k ± 0%  +0.01%  (p=0.008 n=5+5)
Unicode           343k ± 0%         343k ± 0%    ~     (p=0.841 n=5+5)
GoTypes          1.19M ± 0%        1.19M ± 0%  +0.01%  (p=0.008 n=5+5)
Compiler         4.53M ± 0%        4.53M ± 0%  +0.03%  (p=0.008 n=5+5)
SSA              12.4M ± 0%        12.4M ± 0%  +0.00%  (p=0.008 n=5+5)
Flate             235k ± 0%         235k ± 0%    ~     (p=0.079 n=5+5)
GoParser          318k ± 0%         318k ± 0%    ~     (p=0.730 n=5+5)
Reflect           978k ± 0%         978k ± 0%    ~     (p=1.000 n=5+5)
Tar               393k ± 0%         393k ± 0%    ~     (p=0.056 n=5+5)
XML               405k ± 0%         405k ± 0%    ~     (p=0.548 n=5+5)

name       old text-bytes    new text-bytes    delta
HelloSize        672kB ± 0%        672kB ± 0%    ~     (all equal)
CmdGoSize       7.12MB ± 0%       7.12MB ± 0%    ~     (all equal)

name       old data-bytes    new data-bytes    delta
HelloSize        133kB ± 0%        133kB ± 0%    ~     (all equal)
CmdGoSize        390kB ± 0%        390kB ± 0%    ~     (all equal)

name       old exe-bytes     new exe-bytes     delta
HelloSize       1.07MB ± 0%       1.07MB ± 0%    ~     (all equal)
CmdGoSize       11.2MB ± 0%       11.2MB ± 0%    ~     (all equal)

Passes toolstash compare.

For #22662.

Change-Id: I19edb53dd9675af57f7122cb7dba2a6d8bdcc3da
Reviewed-on: https://go-review.googlesource.com/94515Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
parent b1accced
...@@ -650,14 +650,14 @@ var knownFormats = map[string]string{ ...@@ -650,14 +650,14 @@ var knownFormats = map[string]string{
"cmd/compile/internal/syntax.Expr %#v": "", "cmd/compile/internal/syntax.Expr %#v": "",
"cmd/compile/internal/syntax.Node %T": "", "cmd/compile/internal/syntax.Node %T": "",
"cmd/compile/internal/syntax.Operator %s": "", "cmd/compile/internal/syntax.Operator %s": "",
"cmd/compile/internal/syntax.Pos %s": "",
"cmd/compile/internal/syntax.Pos %v": "",
"cmd/compile/internal/syntax.position %s": "", "cmd/compile/internal/syntax.position %s": "",
"cmd/compile/internal/syntax.token %q": "", "cmd/compile/internal/syntax.token %q": "",
"cmd/compile/internal/syntax.token %s": "", "cmd/compile/internal/syntax.token %s": "",
"cmd/compile/internal/types.EType %d": "", "cmd/compile/internal/types.EType %d": "",
"cmd/compile/internal/types.EType %s": "", "cmd/compile/internal/types.EType %s": "",
"cmd/compile/internal/types.EType %v": "", "cmd/compile/internal/types.EType %v": "",
"cmd/internal/src.Pos %s": "",
"cmd/internal/src.Pos %v": "",
"error %v": "", "error %v": "",
"float64 %.2f": "", "float64 %.2f": "",
"float64 %.3f": "", "float64 %.3f": "",
......
...@@ -53,7 +53,7 @@ func (p *noder) funcLit(expr *syntax.FuncLit) *Node { ...@@ -53,7 +53,7 @@ func (p *noder) funcLit(expr *syntax.FuncLit) *Node {
body := p.stmts(expr.Body.List) body := p.stmts(expr.Body.List)
lineno = Ctxt.PosTable.XPos(expr.Body.Rbrace) lineno = p.makeXPos(expr.Body.Rbrace)
if len(body) == 0 { if len(body) == 0 {
body = []*Node{nod(OEMPTY, nil, nil)} body = []*Node{nod(OEMPTY, nil, nil)}
} }
......
...@@ -105,7 +105,7 @@ func pragmaValue(verb string) syntax.Pragma { ...@@ -105,7 +105,7 @@ func pragmaValue(verb string) syntax.Pragma {
} }
// pragcgo is called concurrently if files are parsed concurrently. // pragcgo is called concurrently if files are parsed concurrently.
func (p *noder) pragcgo(pos src.Pos, text string) string { func (p *noder) pragcgo(pos syntax.Pos, text string) string {
f := pragmaFields(text) f := pragmaFields(text)
verb := f[0][3:] // skip "go:" verb := f[0][3:] // skip "go:"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
package gc package gc
import ( import (
"cmd/internal/src" "cmd/compile/internal/syntax"
"testing" "testing"
) )
...@@ -22,7 +22,6 @@ func eq(a, b []string) bool { ...@@ -22,7 +22,6 @@ func eq(a, b []string) bool {
} }
func TestPragmaFields(t *testing.T) { func TestPragmaFields(t *testing.T) {
var tests = []struct { var tests = []struct {
in string in string
want []string want []string
...@@ -49,7 +48,6 @@ func TestPragmaFields(t *testing.T) { ...@@ -49,7 +48,6 @@ func TestPragmaFields(t *testing.T) {
} }
func TestPragcgo(t *testing.T) { func TestPragcgo(t *testing.T) {
var tests = []struct { var tests = []struct {
in string in string
want string want string
...@@ -73,8 +71,9 @@ func TestPragcgo(t *testing.T) { ...@@ -73,8 +71,9 @@ func TestPragcgo(t *testing.T) {
} }
var p noder var p noder
var nopos syntax.Pos
for _, tt := range tests { for _, tt := range tests {
got := p.pragcgo(src.NoPos, tt.in) got := p.pragcgo(nopos, tt.in)
if got != tt.want { if got != tt.want {
t.Errorf("pragcgo(%q) = %q; want %q", tt.in, got, tt.want) t.Errorf("pragcgo(%q) = %q; want %q", tt.in, got, tt.want)
continue continue
......
...@@ -25,30 +25,33 @@ func parseFiles(filenames []string) uint { ...@@ -25,30 +25,33 @@ func parseFiles(filenames []string) uint {
sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10) sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
for _, filename := range filenames { for _, filename := range filenames {
p := &noder{err: make(chan syntax.Error)} p := &noder{
basemap: make(map[*syntax.PosBase]*src.PosBase),
err: make(chan syntax.Error),
}
noders = append(noders, p) noders = append(noders, p)
go func(filename string) { go func(filename string) {
sem <- struct{}{} sem <- struct{}{}
defer func() { <-sem }() defer func() { <-sem }()
defer close(p.err) defer close(p.err)
base := src.NewFileBase(filename, absFilename(filename)) base := syntax.NewFileBase(filename)
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
p.error(syntax.Error{Pos: src.MakePos(base, 0, 0), Msg: err.Error()}) p.error(syntax.Error{Pos: syntax.MakePos(base, 0, 0), Msg: err.Error()})
return return
} }
defer f.Close() defer f.Close()
p.file, _ = syntax.Parse(base, f, p.error, p.pragma, fileh, syntax.CheckBranches) // errors are tracked via p.error p.file, _ = syntax.Parse(base, f, p.error, p.pragma, syntax.CheckBranches) // errors are tracked via p.error
}(filename) }(filename)
} }
var lines uint var lines uint
for _, p := range noders { for _, p := range noders {
for e := range p.err { for e := range p.err {
yyerrorpos(e.Pos, "%s", e.Msg) p.yyerrorpos(e.Pos, "%s", e.Msg)
} }
p.node() p.node()
...@@ -65,12 +68,54 @@ func parseFiles(filenames []string) uint { ...@@ -65,12 +68,54 @@ func parseFiles(filenames []string) uint {
return lines return lines
} }
func yyerrorpos(pos src.Pos, format string, args ...interface{}) { // makeSrcPosBase translates from a *syntax.PosBase to a *src.PosBase.
yyerrorl(Ctxt.PosTable.XPos(pos), format, args...) func (p *noder) makeSrcPosBase(b0 *syntax.PosBase) *src.PosBase {
// fast path: most likely PosBase hasn't changed
if p.basecache.last == b0 {
return p.basecache.base
}
b1, ok := p.basemap[b0]
if !ok {
fn := b0.Filename()
if p0 := b0.Pos(); p0.IsKnown() {
// line directive base
//
// (A syntax.PosBase position is the position at which the PosBase's
// new line and column are starting. For //line directives, that is
// the position of the line following the directive. src.PosBases
// on the other hand use the position of the line directive instead.
// Hence the `p0.Line()-1` below.)
//
// TODO(gri) Once we implement /*line directives, we need to adjust
// src.MakePos accordingly.
p1 := src.MakePos(p.makeSrcPosBase(p0.Base()), p0.Line()-1, p0.Col())
b1 = src.NewLinePragmaBase(p1, fn, fileh(fn), b0.Line())
} else {
// file base
b1 = src.NewFileBase(fn, absFilename(fn))
}
p.basemap[b0] = b1
}
// update cache
p.basecache.last = b0
p.basecache.base = b1
return b1
}
func (p *noder) makeXPos(pos syntax.Pos) (_ src.XPos) {
return Ctxt.PosTable.XPos(src.MakePos(p.makeSrcPosBase(pos.Base()), pos.Line(), pos.Col()))
}
func (p *noder) yyerrorpos(pos syntax.Pos, format string, args ...interface{}) {
yyerrorl(p.makeXPos(pos), format, args...)
} }
var pathPrefix string var pathPrefix string
// TODO(gri) Can we eliminate fileh in favor of absFilename?
func fileh(name string) string { func fileh(name string) string {
return objabi.AbsFile("", name, pathPrefix) return objabi.AbsFile("", name, pathPrefix)
} }
...@@ -81,6 +126,12 @@ func absFilename(name string) string { ...@@ -81,6 +126,12 @@ func absFilename(name string) string {
// noder transforms package syntax's AST into a Node tree. // noder transforms package syntax's AST into a Node tree.
type noder struct { type noder struct {
basemap map[*syntax.PosBase]*src.PosBase
basecache struct {
last *syntax.PosBase
base *src.PosBase
}
file *syntax.File file *syntax.File
linknames []linkname linknames []linkname
pragcgobuf string pragcgobuf string
...@@ -100,7 +151,7 @@ func (p *noder) funcbody(old ScopeID) { ...@@ -100,7 +151,7 @@ func (p *noder) funcbody(old ScopeID) {
p.scope = old p.scope = old
} }
func (p *noder) openScope(pos src.Pos) { func (p *noder) openScope(pos syntax.Pos) {
types.Markdcl() types.Markdcl()
if trackScopes { if trackScopes {
...@@ -111,7 +162,7 @@ func (p *noder) openScope(pos src.Pos) { ...@@ -111,7 +162,7 @@ func (p *noder) openScope(pos src.Pos) {
} }
} }
func (p *noder) closeScope(pos src.Pos) { func (p *noder) closeScope(pos syntax.Pos) {
types.Popdcl() types.Popdcl()
if trackScopes { if trackScopes {
...@@ -121,8 +172,8 @@ func (p *noder) closeScope(pos src.Pos) { ...@@ -121,8 +172,8 @@ func (p *noder) closeScope(pos src.Pos) {
} }
} }
func (p *noder) markScope(pos src.Pos) { func (p *noder) markScope(pos syntax.Pos) {
xpos := Ctxt.PosTable.XPos(pos) xpos := p.makeXPos(pos)
if i := len(Curfn.Func.Marks); i > 0 && Curfn.Func.Marks[i-1].Pos == xpos { if i := len(Curfn.Func.Marks); i > 0 && Curfn.Func.Marks[i-1].Pos == xpos {
Curfn.Func.Marks[i-1].Scope = p.scope Curfn.Func.Marks[i-1].Scope = p.scope
} else { } else {
...@@ -145,7 +196,7 @@ func (p *noder) closeAnotherScope() { ...@@ -145,7 +196,7 @@ func (p *noder) closeAnotherScope() {
// linkname records a //go:linkname directive. // linkname records a //go:linkname directive.
type linkname struct { type linkname struct {
pos src.Pos pos syntax.Pos
local string local string
remote string remote string
} }
...@@ -163,7 +214,7 @@ func (p *noder) node() { ...@@ -163,7 +214,7 @@ func (p *noder) node() {
if imported_unsafe { if imported_unsafe {
lookup(n.local).Linkname = n.remote lookup(n.local).Linkname = n.remote
} else { } else {
yyerrorpos(n.pos, "//go:linkname only allowed in Go files that import \"unsafe\"") p.yyerrorpos(n.pos, "//go:linkname only allowed in Go files that import \"unsafe\"")
} }
} }
...@@ -403,7 +454,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { ...@@ -403,7 +454,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node {
} }
f.Nbody.Set(body) f.Nbody.Set(body)
lineno = Ctxt.PosTable.XPos(fun.Body.Rbrace) lineno = p.makeXPos(fun.Body.Rbrace)
f.Func.Endlineno = lineno f.Func.Endlineno = lineno
} else { } else {
if pure_go || strings.HasPrefix(f.funcname(), "init.") { if pure_go || strings.HasPrefix(f.funcname(), "init.") {
...@@ -497,7 +548,7 @@ func (p *noder) expr(expr syntax.Expr) *Node { ...@@ -497,7 +548,7 @@ func (p *noder) expr(expr syntax.Expr) *Node {
l[i] = p.wrapname(expr.ElemList[i], e) l[i] = p.wrapname(expr.ElemList[i], e)
} }
n.List.Set(l) n.List.Set(l)
lineno = Ctxt.PosTable.XPos(expr.Rbrace) lineno = p.makeXPos(expr.Rbrace)
return n return n
case *syntax.KeyValueExpr: case *syntax.KeyValueExpr:
return p.nod(expr, OKEY, p.expr(expr.Key), p.wrapname(expr.Value, p.expr(expr.Value))) return p.nod(expr, OKEY, p.expr(expr.Key), p.wrapname(expr.Value, p.expr(expr.Value)))
...@@ -943,7 +994,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node { ...@@ -943,7 +994,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node {
name, ok := expr.(*syntax.Name) name, ok := expr.(*syntax.Name)
if !ok { if !ok {
yyerrorpos(expr.Pos(), "non-name %v on left side of :=", p.expr(expr)) p.yyerrorpos(expr.Pos(), "non-name %v on left side of :=", p.expr(expr))
newOrErr = true newOrErr = true
continue continue
} }
...@@ -954,7 +1005,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node { ...@@ -954,7 +1005,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node {
} }
if seen[sym] { if seen[sym] {
yyerrorpos(expr.Pos(), "%v repeated on left side of :=", sym) p.yyerrorpos(expr.Pos(), "%v repeated on left side of :=", sym)
newOrErr = true newOrErr = true
continue continue
} }
...@@ -1057,7 +1108,7 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { ...@@ -1057,7 +1108,7 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node {
return n return n
} }
func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace src.Pos) []*Node { func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace syntax.Pos) []*Node {
var nodes []*Node var nodes []*Node
for i, clause := range clauses { for i, clause := range clauses {
p.lineno(clause) p.lineno(clause)
...@@ -1113,7 +1164,7 @@ func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node { ...@@ -1113,7 +1164,7 @@ func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node {
return n return n
} }
func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace src.Pos) []*Node { func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace syntax.Pos) []*Node {
var nodes []*Node var nodes []*Node
for i, clause := range clauses { for i, clause := range clauses {
p.lineno(clause) p.lineno(clause)
...@@ -1298,7 +1349,7 @@ func (p *noder) setlineno(src_ syntax.Node, dst *Node) *Node { ...@@ -1298,7 +1349,7 @@ func (p *noder) setlineno(src_ syntax.Node, dst *Node) *Node {
// TODO(mdempsky): Shouldn't happen. Fix package syntax. // TODO(mdempsky): Shouldn't happen. Fix package syntax.
return dst return dst
} }
dst.Pos = Ctxt.PosTable.XPos(pos) dst.Pos = p.makeXPos(pos)
return dst return dst
} }
...@@ -1311,7 +1362,7 @@ func (p *noder) lineno(n syntax.Node) { ...@@ -1311,7 +1362,7 @@ func (p *noder) lineno(n syntax.Node) {
// TODO(mdempsky): Shouldn't happen. Fix package syntax. // TODO(mdempsky): Shouldn't happen. Fix package syntax.
return return
} }
lineno = Ctxt.PosTable.XPos(pos) lineno = p.makeXPos(pos)
} }
// error is called concurrently if files are parsed concurrently. // error is called concurrently if files are parsed concurrently.
...@@ -1332,7 +1383,7 @@ var allowedStdPragmas = map[string]bool{ ...@@ -1332,7 +1383,7 @@ var allowedStdPragmas = map[string]bool{
} }
// pragma is called concurrently if files are parsed concurrently. // pragma is called concurrently if files are parsed concurrently.
func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma { func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma {
switch { switch {
case strings.HasPrefix(text, "line "): case strings.HasPrefix(text, "line "):
// line directives are handled by syntax package // line directives are handled by syntax package
...@@ -1393,8 +1444,8 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma { ...@@ -1393,8 +1444,8 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
// contain cgo directives, and for security reasons // contain cgo directives, and for security reasons
// (primarily misuse of linker flags), other files are not. // (primarily misuse of linker flags), other files are not.
// See golang.org/issue/23672. // See golang.org/issue/23672.
func isCgoGeneratedFile(pos src.Pos) bool { func isCgoGeneratedFile(pos syntax.Pos) bool {
return strings.HasPrefix(filepath.Base(filepath.Clean(pos.AbsFilename())), "_cgo_") return strings.HasPrefix(filepath.Base(filepath.Clean(fileh(pos.Base().Filename()))), "_cgo_")
} }
// safeArg reports whether arg is a "safe" command-line argument, // safeArg reports whether arg is a "safe" command-line argument,
......
...@@ -568,7 +568,7 @@ Outer: ...@@ -568,7 +568,7 @@ Outer:
if !ok { if !ok {
// First entry for this hash. // First entry for this hash.
nn = append(nn, c.node) nn = append(nn, c.node)
seen[c.hash] = nn[len(nn)-1 : len(nn):len(nn)] seen[c.hash] = nn[len(nn)-1 : len(nn) : len(nn)]
continue continue
} }
for _, n := range prev { for _, n := range prev {
......
...@@ -4,10 +4,7 @@ ...@@ -4,10 +4,7 @@
package syntax package syntax
import ( import "fmt"
"cmd/internal/src"
"fmt"
)
// TODO(gri) consider making this part of the parser code // TODO(gri) consider making this part of the parser code
...@@ -62,11 +59,11 @@ type label struct { ...@@ -62,11 +59,11 @@ type label struct {
type block struct { type block struct {
parent *block // immediately enclosing block, or nil parent *block // immediately enclosing block, or nil
start src.Pos // start of block start Pos // start of block
lstmt *LabeledStmt // labeled statement associated with this block, or nil lstmt *LabeledStmt // labeled statement associated with this block, or nil
} }
func (ls *labelScope) err(pos src.Pos, format string, args ...interface{}) { func (ls *labelScope) err(pos Pos, format string, args ...interface{}) {
ls.errh(Error{pos, fmt.Sprintf(format, args...)}) ls.errh(Error{pos, fmt.Sprintf(format, args...)})
} }
...@@ -132,14 +129,14 @@ type targets struct { ...@@ -132,14 +129,14 @@ type targets struct {
// list of unresolved (forward) gotos. parent is the immediately enclosing // list of unresolved (forward) gotos. parent is the immediately enclosing
// block (or nil), ctxt provides information about the enclosing statements, // block (or nil), ctxt provides information about the enclosing statements,
// and lstmt is the labeled statement associated with this block, or nil. // and lstmt is the labeled statement associated with this block, or nil.
func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start src.Pos, body []Stmt) []*BranchStmt { func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start Pos, body []Stmt) []*BranchStmt {
b := &block{parent: parent, start: start, lstmt: lstmt} b := &block{parent: parent, start: start, lstmt: lstmt}
var varPos src.Pos var varPos Pos
var varName Expr var varName Expr
var fwdGotos, badGotos []*BranchStmt var fwdGotos, badGotos []*BranchStmt
recordVarDecl := func(pos src.Pos, name Expr) { recordVarDecl := func(pos Pos, name Expr) {
varPos = pos varPos = pos
varName = name varName = name
// Any existing forward goto jumping over the variable // Any existing forward goto jumping over the variable
...@@ -160,7 +157,7 @@ func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledS ...@@ -160,7 +157,7 @@ func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledS
return false return false
} }
innerBlock := func(ctxt targets, start src.Pos, body []Stmt) { innerBlock := func(ctxt targets, start Pos, body []Stmt) {
// Unresolved forward gotos from the inner block // Unresolved forward gotos from the inner block
// become forward gotos for the current block. // become forward gotos for the current block.
fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...) fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...)
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
package syntax package syntax
import "cmd/internal/src"
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Nodes // Nodes
...@@ -18,18 +16,18 @@ type Node interface { ...@@ -18,18 +16,18 @@ type Node interface {
// (IndexExpr, IfStmt, etc.) is the position of a token uniquely // (IndexExpr, IfStmt, etc.) is the position of a token uniquely
// associated with that production; usually the left-most one // associated with that production; usually the left-most one
// ('[' for IndexExpr, 'if' for IfStmt, etc.) // ('[' for IndexExpr, 'if' for IfStmt, etc.)
Pos() src.Pos Pos() Pos
aNode() aNode()
} }
type node struct { type node struct {
// commented out for now since not yet used // commented out for now since not yet used
// doc *Comment // nil means no comment(s) attached // doc *Comment // nil means no comment(s) attached
pos src.Pos pos Pos
} }
func (n *node) Pos() src.Pos { return n.pos } func (n *node) Pos() Pos { return n.pos }
func (*node) aNode() {} func (*node) aNode() {}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Files // Files
...@@ -149,7 +147,7 @@ type ( ...@@ -149,7 +147,7 @@ type (
Type Expr // nil means no literal type Type Expr // nil means no literal type
ElemList []Expr ElemList []Expr
NKeys int // number of elements with keys NKeys int // number of elements with keys
Rbrace src.Pos Rbrace Pos
expr expr
} }
...@@ -328,7 +326,7 @@ type ( ...@@ -328,7 +326,7 @@ type (
BlockStmt struct { BlockStmt struct {
List []Stmt List []Stmt
Rbrace src.Pos Rbrace Pos
stmt stmt
} }
...@@ -396,13 +394,13 @@ type ( ...@@ -396,13 +394,13 @@ type (
Init SimpleStmt Init SimpleStmt
Tag Expr Tag Expr
Body []*CaseClause Body []*CaseClause
Rbrace src.Pos Rbrace Pos
stmt stmt
} }
SelectStmt struct { SelectStmt struct {
Body []*CommClause Body []*CommClause
Rbrace src.Pos Rbrace Pos
stmt stmt
} }
) )
...@@ -425,14 +423,14 @@ type ( ...@@ -425,14 +423,14 @@ type (
CaseClause struct { CaseClause struct {
Cases Expr // nil means default clause Cases Expr // nil means default clause
Body []Stmt Body []Stmt
Colon src.Pos Colon Pos
node node
} }
CommClause struct { CommClause struct {
Comm SimpleStmt // send or receive stmt; nil means default clause Comm SimpleStmt // send or receive stmt; nil means default clause
Body []Stmt Body []Stmt
Colon src.Pos Colon Pos
node node
} }
) )
......
...@@ -291,7 +291,7 @@ func testPos(t *testing.T, list []test, prefix, suffix string, extract func(*Fil ...@@ -291,7 +291,7 @@ func testPos(t *testing.T, list []test, prefix, suffix string, extract func(*Fil
} }
// build syntax tree // build syntax tree
file, err := Parse(nil, strings.NewReader(src), nil, nil, nil, 0) file, err := Parse(nil, strings.NewReader(src), nil, nil, 0)
if err != nil { if err != nil {
t.Errorf("parse error: %s: %v (%s)", src, err, test.nodetyp) t.Errorf("parse error: %s: %v (%s)", src, err, test.nodetyp)
continue continue
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package syntax package syntax
import ( import (
"cmd/internal/src"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
...@@ -16,26 +15,24 @@ const debug = false ...@@ -16,26 +15,24 @@ const debug = false
const trace = false const trace = false
type parser struct { type parser struct {
file *src.PosBase file *PosBase
errh ErrorHandler errh ErrorHandler
fileh FilenameHandler mode Mode
mode Mode
scanner scanner
base *src.PosBase // current position base base *PosBase // current position base
first error // first error encountered first error // first error encountered
errcnt int // number of errors encountered errcnt int // number of errors encountered
pragma Pragma // pragma flags pragma Pragma // pragma flags
fnest int // function nesting level (for error handling) fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution) xnest int // expression nesting level (for complit ambiguity resolution)
indent []byte // tracing support indent []byte // tracing support
} }
func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh PragmaHandler, fileh FilenameHandler, mode Mode) { func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) {
p.file = file p.file = file
p.errh = errh p.errh = errh
p.fileh = fileh
p.mode = mode p.mode = mode
p.scanner.init( p.scanner.init(
r, r,
...@@ -52,15 +49,25 @@ func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh P ...@@ -52,15 +49,25 @@ func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh P
// otherwise it must be a comment containing a line or go: directive // otherwise it must be a comment containing a line or go: directive
text := commentText(msg) text := commentText(msg)
col += 2 // text starts after // or /*
if strings.HasPrefix(text, "line ") { if strings.HasPrefix(text, "line ") {
p.updateBase(line, col+5, text[5:]) var pos Pos // position immediately following the comment
if msg[1] == '/' {
// line comment
pos = MakePos(p.file, line+1, colbase)
} else {
// regular comment
// (if the comment spans multiple lines it's not
// a valid line directive and will be discarded
// by updateBase)
pos = MakePos(p.file, line, col+uint(len(msg)))
}
p.updateBase(pos, line, col+2+5, text[5:]) // +2 to skip over // or /*
return return
} }
// go: directive (but be conservative and test) // go: directive (but be conservative and test)
if pragh != nil && strings.HasPrefix(text, "go:") { if pragh != nil && strings.HasPrefix(text, "go:") {
p.pragma |= pragh(p.posAt(line, col), text) p.pragma |= pragh(p.posAt(line, col+2), text) // +2 to skip over // or /*
} }
}, },
directives, directives,
...@@ -76,9 +83,7 @@ func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh P ...@@ -76,9 +83,7 @@ func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh P
p.indent = nil p.indent = nil
} }
const lineMax = 1<<24 - 1 // TODO(gri) this limit is defined for src.Pos - fix func (p *parser) updateBase(pos Pos, line, col uint, text string) {
func (p *parser) updateBase(line, col uint, text string) {
i, n, ok := trailingDigits(text) i, n, ok := trailingDigits(text)
if i == 0 { if i == 0 {
return // ignore (not a line directive) return // ignore (not a line directive)
...@@ -96,26 +101,25 @@ func (p *parser) updateBase(line, col uint, text string) { ...@@ -96,26 +101,25 @@ func (p *parser) updateBase(line, col uint, text string) {
//line filename:line:col //line filename:line:col
i, i2 = i2, i i, i2 = i2, i
n, n2 = n2, n n, n2 = n2, n
if n2 == 0 { if n2 == 0 || n2 > PosMax {
p.errorAt(p.posAt(line, col+i2), "invalid column number: "+text[i2:]) p.errorAt(p.posAt(line, col+i2), "invalid column number: "+text[i2:])
return return
} }
text = text[:i2-1] // lop off :col text = text[:i2-1] // lop off :col
} else {
//line filename:line
n2 = colbase // use start of line for column
} }
if n == 0 || n > lineMax { if n == 0 || n > PosMax {
p.errorAt(p.posAt(line, col+i), "invalid line number: "+text[i:]) p.errorAt(p.posAt(line, col+i), "invalid line number: "+text[i:])
return return
} }
filename := text[:i-1] // lop off :line filename := text[:i-1] // lop off :line
absFilename := filename // TODO(gri) handle case where filename doesn't change (see #22662)
if p.fileh != nil {
absFilename = p.fileh(filename)
}
// TODO(gri) pass column n2 to NewLinePragmaBase p.base = NewLineBase(pos, filename, n, n2)
p.base = src.NewLinePragmaBase(src.MakePos(p.file, line, col), filename, absFilename, uint(n) /*uint(n2)*/)
} }
func commentText(s string) string { func commentText(s string) string {
...@@ -162,12 +166,12 @@ func (p *parser) want(tok token) { ...@@ -162,12 +166,12 @@ func (p *parser) want(tok token) {
// Error handling // Error handling
// posAt returns the Pos value for (line, col) and the current position base. // posAt returns the Pos value for (line, col) and the current position base.
func (p *parser) posAt(line, col uint) src.Pos { func (p *parser) posAt(line, col uint) Pos {
return src.MakePos(p.base, line, col) return MakePos(p.base, line, col)
} }
// error reports an error at the given position. // error reports an error at the given position.
func (p *parser) errorAt(pos src.Pos, msg string) { func (p *parser) errorAt(pos Pos, msg string) {
err := Error{pos, msg} err := Error{pos, msg}
if p.first == nil { if p.first == nil {
p.first = err p.first = err
...@@ -180,7 +184,7 @@ func (p *parser) errorAt(pos src.Pos, msg string) { ...@@ -180,7 +184,7 @@ func (p *parser) errorAt(pos src.Pos, msg string) {
} }
// syntaxErrorAt reports a syntax error at the given position. // syntaxErrorAt reports a syntax error at the given position.
func (p *parser) syntaxErrorAt(pos src.Pos, msg string) { func (p *parser) syntaxErrorAt(pos Pos, msg string) {
if trace { if trace {
p.print("syntax error: " + msg) p.print("syntax error: " + msg)
} }
...@@ -237,7 +241,7 @@ func tokstring(tok token) string { ...@@ -237,7 +241,7 @@ func tokstring(tok token) string {
} }
// Convenience methods using the current token position. // Convenience methods using the current token position.
func (p *parser) pos() src.Pos { return p.posAt(p.line, p.col) } func (p *parser) pos() Pos { return p.posAt(p.line, p.col) }
func (p *parser) syntaxError(msg string) { p.syntaxErrorAt(p.pos(), msg) } func (p *parser) syntaxError(msg string) { p.syntaxErrorAt(p.pos(), msg) }
// The stopset contains keywords that start a statement. // The stopset contains keywords that start a statement.
...@@ -417,7 +421,7 @@ func isEmptyFuncDecl(dcl Decl) bool { ...@@ -417,7 +421,7 @@ func isEmptyFuncDecl(dcl Decl) bool {
// list = "(" { f sep } ")" | // list = "(" { f sep } ")" |
// "{" { f sep } "}" . // sep is optional before ")" or "}" // "{" { f sep } "}" . // sep is optional before ")" or "}"
// //
func (p *parser) list(open, sep, close token, f func() bool) src.Pos { func (p *parser) list(open, sep, close token, f func() bool) Pos {
p.want(open) p.want(open)
var done bool var done bool
...@@ -1064,7 +1068,7 @@ func (p *parser) type_() Expr { ...@@ -1064,7 +1068,7 @@ func (p *parser) type_() Expr {
return typ return typ
} }
func newIndirect(pos src.Pos, typ Expr) Expr { func newIndirect(pos Pos, typ Expr) Expr {
o := new(Operation) o := new(Operation)
o.pos = pos o.pos = pos
o.Op = Mul o.Op = Mul
...@@ -1276,7 +1280,7 @@ func (p *parser) funcResult() []*Field { ...@@ -1276,7 +1280,7 @@ func (p *parser) funcResult() []*Field {
return nil return nil
} }
func (p *parser) addField(styp *StructType, pos src.Pos, name *Name, typ Expr, tag *BasicLit) { func (p *parser) addField(styp *StructType, pos Pos, name *Name, typ Expr, tag *BasicLit) {
if tag != nil { if tag != nil {
for i := len(styp.FieldList) - len(styp.TagList); i > 0; i-- { for i := len(styp.FieldList) - len(styp.TagList); i > 0; i-- {
styp.TagList = append(styp.TagList, nil) styp.TagList = append(styp.TagList, nil)
...@@ -1694,7 +1698,7 @@ func (p *parser) newRangeClause(lhs Expr, def bool) *RangeClause { ...@@ -1694,7 +1698,7 @@ func (p *parser) newRangeClause(lhs Expr, def bool) *RangeClause {
return r return r
} }
func (p *parser) newAssignStmt(pos src.Pos, op Operator, lhs, rhs Expr) *AssignStmt { func (p *parser) newAssignStmt(pos Pos, op Operator, lhs, rhs Expr) *AssignStmt {
a := new(AssignStmt) a := new(AssignStmt)
a.pos = pos a.pos = pos
a.Op = op a.Op = op
...@@ -1818,7 +1822,7 @@ func (p *parser) header(keyword token) (init SimpleStmt, cond Expr, post SimpleS ...@@ -1818,7 +1822,7 @@ func (p *parser) header(keyword token) (init SimpleStmt, cond Expr, post SimpleS
var condStmt SimpleStmt var condStmt SimpleStmt
var semi struct { var semi struct {
pos src.Pos pos Pos
lit string // valid if pos.IsKnown() lit string // valid if pos.IsKnown()
} }
if p.tok != _Lbrace { if p.tok != _Lbrace {
......
...@@ -6,7 +6,6 @@ package syntax ...@@ -6,7 +6,6 @@ package syntax
import ( import (
"bytes" "bytes"
"cmd/internal/src"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -131,7 +130,7 @@ func verifyPrint(filename string, ast1 *File) { ...@@ -131,7 +130,7 @@ func verifyPrint(filename string, ast1 *File) {
panic(err) panic(err)
} }
ast2, err := Parse(src.NewFileBase(filename, filename), &buf1, nil, nil, nil, 0) ast2, err := Parse(NewFileBase(filename), &buf1, nil, nil, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }
...@@ -155,7 +154,7 @@ func verifyPrint(filename string, ast1 *File) { ...@@ -155,7 +154,7 @@ func verifyPrint(filename string, ast1 *File) {
} }
func TestIssue17697(t *testing.T) { func TestIssue17697(t *testing.T) {
_, err := Parse(nil, bytes.NewReader(nil), nil, nil, nil, 0) // return with parser error, don't panic _, err := Parse(nil, bytes.NewReader(nil), nil, nil, 0) // return with parser error, don't panic
if err == nil { if err == nil {
t.Errorf("no error reported") t.Errorf("no error reported")
} }
...@@ -181,6 +180,11 @@ func TestParseFile(t *testing.T) { ...@@ -181,6 +180,11 @@ func TestParseFile(t *testing.T) {
} }
} }
// Make sure (PosMax + 1) doesn't overflow when converted to default
// type int (when passed as argument to fmt.Sprintf) on 32bit platforms
// (see test cases below).
var tooLarge int = PosMax + 1
func TestLineDirectives(t *testing.T) { func TestLineDirectives(t *testing.T) {
// valid line directives lead to a syntax error after them // valid line directives lead to a syntax error after them
const valid = "syntax error: package statement must be first" const valid = "syntax error: package statement must be first"
...@@ -191,11 +195,11 @@ func TestLineDirectives(t *testing.T) { ...@@ -191,11 +195,11 @@ func TestLineDirectives(t *testing.T) {
line, col uint // 0-based line, col uint // 0-based
}{ }{
// ignored //line directives // ignored //line directives
{"//\n", valid, "", 2 - linebase, 0}, // no directive {"//\n", valid, "", 1, 0}, // no directive
{"//line\n", valid, "", 2 - linebase, 0}, // missing colon {"//line\n", valid, "", 1, 0}, // missing colon
{"//line foo\n", valid, "", 2 - linebase, 0}, // missing colon {"//line foo\n", valid, "", 1, 0}, // missing colon
{" //line foo:\n", valid, "", 2 - linebase, 0}, // not a line start {" //line foo:\n", valid, "", 1, 0}, // not a line start
{"// line foo:\n", valid, "", 2 - linebase, 0}, // space between // and line {"// line foo:\n", valid, "", 1, 0}, // space between // and line
// invalid //line directives with one colon // invalid //line directives with one colon
{"//line :\n", "invalid line number: ", "", 0, 8}, {"//line :\n", "invalid line number: ", "", 0, 8},
...@@ -206,7 +210,7 @@ func TestLineDirectives(t *testing.T) { ...@@ -206,7 +210,7 @@ func TestLineDirectives(t *testing.T) {
{"//line foo:1 \n", "invalid line number: 1 ", "", 0, 11}, {"//line foo:1 \n", "invalid line number: 1 ", "", 0, 11},
{"//line foo:-12\n", "invalid line number: -12", "", 0, 11}, {"//line foo:-12\n", "invalid line number: -12", "", 0, 11},
{"//line C:foo:0\n", "invalid line number: 0", "", 0, 13}, {"//line C:foo:0\n", "invalid line number: 0", "", 0, 13},
{fmt.Sprintf("//line foo:%d\n", lineMax+1), fmt.Sprintf("invalid line number: %d", lineMax+1), "", 0, 11}, {fmt.Sprintf("//line foo:%d\n", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), "", 0, 11},
// invalid //line directives with two colons // invalid //line directives with two colons
{"//line ::\n", "invalid line number: ", "", 0, 9}, {"//line ::\n", "invalid line number: ", "", 0, 9},
...@@ -217,27 +221,33 @@ func TestLineDirectives(t *testing.T) { ...@@ -217,27 +221,33 @@ func TestLineDirectives(t *testing.T) {
{"//line :123:0\n", "invalid column number: 0", "", 0, 12}, {"//line :123:0\n", "invalid column number: 0", "", 0, 12},
{"//line foo:123:0\n", "invalid column number: 0", "", 0, 15}, {"//line foo:123:0\n", "invalid column number: 0", "", 0, 15},
{fmt.Sprintf("//line foo:10:%d\n", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), "", 0, 14},
// effect of valid //line directives on positions // effect of valid //line directives on lines
{"//line foo:123\n foo", valid, "foo", 123 - linebase, 3}, {"//line foo:123\n foo", valid, "foo", 123 - linebase, 3},
{"//line foo:123\n foo", valid, " foo", 123 - linebase, 3}, {"//line foo:123\n foo", valid, " foo", 123 - linebase, 3},
{"//line foo:123\n//line bar:345\nfoo", valid, "bar", 345 - linebase, 0}, {"//line foo:123\n//line bar:345\nfoo", valid, "bar", 345 - linebase, 0},
{"//line C:foo:123\n", valid, "C:foo", 123 - linebase, 0}, {"//line C:foo:123\n", valid, "C:foo", 123 - linebase, 0},
{"//line " + runtime.GOROOT() + "/src/a/a.go:123\n foo", valid, "$GOROOT/src/a/a.go", 123 - linebase, 3}, {"//line /src/a/a.go:123\n foo", valid, "/src/a/a.go", 123 - linebase, 3},
{"//line :x:1\n", valid, ":x", 0, 0}, {"//line :x:1\n", valid, ":x", 1 - linebase, 0},
{"//line foo ::1\n", valid, "foo :", 0, 0}, {"//line foo ::1\n", valid, "foo :", 1 - linebase, 0},
{"//line foo:123abc:1\n", valid, "foo:123abc", 0, 0}, {"//line foo:123abc:1\n", valid, "foo:123abc", 0, 0},
{"//line foo :123:1\n", valid, "foo ", 123 - linebase, 0}, {"//line foo :123:1\n", valid, "foo ", 123 - linebase, 0},
{"//line ::123\n", valid, ":", 123 - linebase, 0}, {"//line ::123\n", valid, ":", 123 - linebase, 0},
// TODO(gri) add tests to verify correct column changes, once implemented // effect of valid //line directives on columns
{"//line :x:1:10\n", valid, ":x", 1 - linebase, 10 - colbase},
{"//line foo ::1:2\n", valid, "foo :", 1 - linebase, 2 - colbase},
{"//line foo:123abc:1:1000\n", valid, "foo:123abc", 1 - linebase, 1000 - colbase},
{"//line foo :123:1000\n\n", valid, "foo ", 124 - linebase, 0},
{"//line ::123:1234\n", valid, ":", 123 - linebase, 1234 - colbase},
// ignored /*line directives // ignored /*line directives
{"/**/", valid, "", 1 - linebase, 4}, // no directive {"/**/", valid, "", 0, 4}, // no directive
{"/*line*/", valid, "", 1 - linebase, 8}, // missing colon {"/*line*/", valid, "", 0, 8}, // missing colon
{"/*line foo*/", valid, "", 1 - linebase, 12}, // missing colon {"/*line foo*/", valid, "", 0, 12}, // missing colon
{" //line foo:*/", valid, "", 1 - linebase, 15}, // not a line start {" //line foo:*/", valid, "", 0, 15}, // not a line start
{"/* line foo:*/", valid, "", 1 - linebase, 15}, // space between // and line {"/* line foo:*/", valid, "", 0, 15}, // space between // and line
// invalid /*line directives with one colon // invalid /*line directives with one colon
{"/*line :*/", "invalid line number: ", "", 0, 8}, {"/*line :*/", "invalid line number: ", "", 0, 8},
...@@ -247,7 +257,7 @@ func TestLineDirectives(t *testing.T) { ...@@ -247,7 +257,7 @@ func TestLineDirectives(t *testing.T) {
{"/*line foo:0*/", "invalid line number: 0", "", 0, 11}, {"/*line foo:0*/", "invalid line number: 0", "", 0, 11},
{"/*line foo:1 */", "invalid line number: 1 ", "", 0, 11}, {"/*line foo:1 */", "invalid line number: 1 ", "", 0, 11},
{"/*line C:foo:0*/", "invalid line number: 0", "", 0, 13}, {"/*line C:foo:0*/", "invalid line number: 0", "", 0, 13},
{fmt.Sprintf("/*line foo:%d*/", lineMax+1), fmt.Sprintf("invalid line number: %d", lineMax+1), "", 0, 11}, {fmt.Sprintf("/*line foo:%d*/", tooLarge), fmt.Sprintf("invalid line number: %d", tooLarge), "", 0, 11},
// invalid /*line directives with two colons // invalid /*line directives with two colons
{"/*line ::*/", "invalid line number: ", "", 0, 9}, {"/*line ::*/", "invalid line number: ", "", 0, 9},
...@@ -258,31 +268,27 @@ func TestLineDirectives(t *testing.T) { ...@@ -258,31 +268,27 @@ func TestLineDirectives(t *testing.T) {
{"/*line :123:0*/", "invalid column number: 0", "", 0, 12}, {"/*line :123:0*/", "invalid column number: 0", "", 0, 12},
{"/*line foo:123:0*/", "invalid column number: 0", "", 0, 15}, {"/*line foo:123:0*/", "invalid column number: 0", "", 0, 15},
{fmt.Sprintf("/*line foo:10:%d*/", tooLarge), fmt.Sprintf("invalid column number: %d", tooLarge), "", 0, 14},
// effect of valid /*line directives on positions // effect of valid /*line directives on lines
// TODO(gri) remove \n after directives once line number is computed correctly {"/*line foo:123*/ foo", valid, "foo", 123 - linebase, 3},
{"/*line foo:123*/\n foo", valid, "foo", 123 - linebase, 3},
{"/*line foo:123*/\n//line bar:345\nfoo", valid, "bar", 345 - linebase, 0}, {"/*line foo:123*/\n//line bar:345\nfoo", valid, "bar", 345 - linebase, 0},
{"/*line C:foo:123*/\n", valid, "C:foo", 123 - linebase, 0}, {"/*line C:foo:123*/", valid, "C:foo", 123 - linebase, 0},
{"/*line " + runtime.GOROOT() + "/src/a/a.go:123*/\n foo", valid, "$GOROOT/src/a/a.go", 123 - linebase, 3}, {"/*line /src/a/a.go:123*/ foo", valid, "/src/a/a.go", 123 - linebase, 3},
{"/*line :x:1*/\n", valid, ":x", 1 - linebase, 0}, {"/*line :x:1*/", valid, ":x", 1 - linebase, 0},
{"/*line foo ::1*/\n", valid, "foo :", 1 - linebase, 0}, {"/*line foo ::1*/", valid, "foo :", 1 - linebase, 0},
{"/*line foo:123abc:1*/\n", valid, "foo:123abc", 1 - linebase, 0}, {"/*line foo:123abc:1*/", valid, "foo:123abc", 1 - linebase, 0},
{"/*line foo :123:1*/\n", valid, "foo ", 123 - linebase, 0}, {"/*line foo :123:10*/", valid, "foo ", 123 - linebase, 10 - colbase},
{"/*line ::123*/\n", valid, ":", 123 - linebase, 0}, {"/*line ::123*/", valid, ":", 123 - linebase, 0},
// test effect of /*line directive on (relative) position information for this line // effect of valid /*line directives on columns
// TODO(gri) add these tests {"/*line :x:1:10*/", valid, ":x", 1 - linebase, 10 - colbase},
{"/*line foo ::1:2*/", valid, "foo :", 1 - linebase, 2 - colbase},
// TODO(gri) add tests to verify correct column changes, once implemented {"/*line foo:123abc:1:1000*/", valid, "foo:123abc", 1 - linebase, 1000 - colbase},
{"/*line foo :123:1000*/\n", valid, "foo ", 124 - linebase, 0},
{"/*line ::123:1234*/", valid, ":", 123 - linebase, 1234 - colbase},
} { } {
fileh := func(name string) string { _, err := Parse(nil, strings.NewReader(test.src), nil, nil, 0)
if strings.HasPrefix(name, runtime.GOROOT()) {
return "$GOROOT" + name[len(runtime.GOROOT()):]
}
return name
}
_, err := Parse(nil, strings.NewReader(test.src), nil, nil, fileh, 0)
if err == nil { if err == nil {
t.Errorf("%s: no error reported", test.src) t.Errorf("%s: no error reported", test.src)
continue continue
...@@ -295,14 +301,16 @@ func TestLineDirectives(t *testing.T) { ...@@ -295,14 +301,16 @@ func TestLineDirectives(t *testing.T) {
if msg := perr.Msg; msg != test.msg { if msg := perr.Msg; msg != test.msg {
t.Errorf("%s: got msg = %q; want %q", test.src, msg, test.msg) t.Errorf("%s: got msg = %q; want %q", test.src, msg, test.msg)
} }
if filename := perr.Pos.AbsFilename(); filename != test.filename {
pos := perr.Pos
if filename := pos.RelFilename(); filename != test.filename {
t.Errorf("%s: got filename = %q; want %q", test.src, filename, test.filename) t.Errorf("%s: got filename = %q; want %q", test.src, filename, test.filename)
} }
if line := perr.Pos.RelLine(); line-linebase != test.line { if line := pos.RelLine(); line != test.line+linebase {
t.Errorf("%s: got line = %d; want %d", test.src, line-linebase, test.line) t.Errorf("%s: got line = %d; want %d", test.src, line, test.line+linebase)
} }
if col := perr.Pos.Col(); col-colbase != test.col { if col := pos.RelCol(); col != test.col+colbase {
t.Errorf("%s: got col = %d; want %d", test.src, col-colbase, test.col) t.Errorf("%s: got col = %d; want %d", test.src, col, test.col+colbase)
} }
} }
} }
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syntax
import "fmt"
// PosMax is the largest line or column value that can be represented without loss.
// Incoming values (arguments) larger than PosMax will be set to PosMax.
const PosMax = 1 << 30
// A Pos represents an absolute (line, col) source position
// with a reference to position base for computing relative
// (to a file, or line directive) position information.
// Pos values are intentionally light-weight so that they
// can be created without too much concern about space use.
type Pos struct {
base *PosBase
line, col uint32
}
// MakePos returns a new Pos for the given PosBase, line and column.
func MakePos(base *PosBase, line, col uint) Pos { return Pos{base, sat32(line), sat32(col)} }
// TODO(gri) IsKnown makes an assumption about linebase < 1.
// Maybe we should check for Base() != nil instead.
func (pos Pos) IsKnown() bool { return pos.line > 0 }
func (pos Pos) Base() *PosBase { return pos.base }
func (pos Pos) Line() uint { return uint(pos.line) }
func (pos Pos) Col() uint { return uint(pos.col) }
func (pos Pos) RelFilename() string { b := pos.Base(); return b.Filename() }
func (pos Pos) RelLine() uint { b := pos.Base(); return b.Line() + (pos.Line() - b.Pos().Line()) }
func (pos Pos) RelCol() uint {
b := pos.Base()
if pos.Line() == b.Pos().Line() {
// pos on same line as pos base => column is relative to pos base
return b.Col() + (pos.Col() - b.Pos().Col())
}
return pos.Col()
}
func (pos Pos) String() string {
s := fmt.Sprintf("%s:%d:%d", pos.RelFilename(), pos.RelLine(), pos.RelCol())
if bpos := pos.Base().Pos(); bpos.IsKnown() {
s += fmt.Sprintf("[%s:%d:%d]", bpos.RelFilename(), pos.Line(), pos.Col())
}
return s
}
// A PosBase represents the base for relative position information:
// At position pos, the relative position is filename:line:col.
type PosBase struct {
pos Pos
filename string
line, col uint32
}
// NewFileBase returns a new PosBase for the given filename.
// The PosBase position is unknown in this case.
func NewFileBase(filename string) *PosBase {
return &PosBase{filename: filename}
}
// NewLineBase returns a new PosBase for a line directive "line filename:line:col"
// relative to pos, which is the position of the character immediately following
// the comment containing the line directive. For a directive in a line comment,
// that position is the beginning of the next line (i.e., the newline character
// belongs to the line comment).
func NewLineBase(pos Pos, filename string, line, col uint) *PosBase {
return &PosBase{pos, filename, sat32(line), sat32(col)}
}
func (base *PosBase) Pos() (_ Pos) {
if base == nil {
return
}
return base.pos
}
func (base *PosBase) Filename() string {
if base == nil {
return ""
}
return base.filename
}
func (base *PosBase) Line() uint {
if base == nil {
return 0
}
return uint(base.line)
}
func (base *PosBase) Col() uint {
if base == nil {
return 0
}
return uint(base.col)
}
func sat32(x uint) uint32 {
if x > PosMax {
return PosMax
}
return uint32(x)
}
...@@ -34,7 +34,7 @@ func TestPrintString(t *testing.T) { ...@@ -34,7 +34,7 @@ func TestPrintString(t *testing.T) {
"package p; type _ = int; type T1 = struct{}; type ( _ = *struct{}; T2 = float32 )", "package p; type _ = int; type T1 = struct{}; type ( _ = *struct{}; T2 = float32 )",
// TODO(gri) expand // TODO(gri) expand
} { } {
ast, err := Parse(nil, strings.NewReader(want), nil, nil, nil, 0) ast, err := Parse(nil, strings.NewReader(want), nil, nil, 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package syntax package syntax
import ( import (
"cmd/internal/src"
"fmt" "fmt"
"io" "io"
"os" "os"
...@@ -21,7 +20,7 @@ const ( ...@@ -21,7 +20,7 @@ const (
// Error describes a syntax error. Error implements the error interface. // Error describes a syntax error. Error implements the error interface.
type Error struct { type Error struct {
Pos src.Pos Pos Pos
Msg string Msg string
} }
...@@ -42,11 +41,7 @@ type Pragma uint16 ...@@ -42,11 +41,7 @@ type Pragma uint16
// A PragmaHandler is used to process //go: directives as // A PragmaHandler is used to process //go: directives as
// they're scanned. The returned Pragma value will be unioned into the // they're scanned. The returned Pragma value will be unioned into the
// next FuncDecl node. // next FuncDecl node.
type PragmaHandler func(pos src.Pos, text string) Pragma type PragmaHandler func(pos Pos, text string) Pragma
// A FilenameHandler is used to process each filename encountered
// in //line directives. The returned value is used as the absolute filename.
type FilenameHandler func(name string) string
// Parse parses a single Go source file from src and returns the corresponding // Parse parses a single Go source file from src and returns the corresponding
// syntax tree. If there are errors, Parse will return the first error found, // syntax tree. If there are errors, Parse will return the first error found,
...@@ -60,10 +55,7 @@ type FilenameHandler func(name string) string ...@@ -60,10 +55,7 @@ type FilenameHandler func(name string) string
// //
// If pragh != nil, it is called with each pragma encountered. // If pragh != nil, it is called with each pragma encountered.
// //
// If fileh != nil, it is called to process each filename func Parse(base *PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, first error) {
// encountered in //line directives.
//
func Parse(base *src.PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, fileh FilenameHandler, mode Mode) (_ *File, first error) {
defer func() { defer func() {
if p := recover(); p != nil { if p := recover(); p != nil {
if err, ok := p.(Error); ok { if err, ok := p.(Error); ok {
...@@ -75,7 +67,7 @@ func Parse(base *src.PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHand ...@@ -75,7 +67,7 @@ func Parse(base *src.PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHand
}() }()
var p parser var p parser
p.init(base, src, errh, pragh, fileh, mode) p.init(base, src, errh, pragh, mode)
p.next() p.next()
return p.fileOrNil(), p.first return p.fileOrNil(), p.first
} }
...@@ -90,5 +82,5 @@ func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mod ...@@ -90,5 +82,5 @@ func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mod
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
return Parse(src.NewFileBase(filename, filename), f, errh, pragh, nil, mode) return Parse(NewFileBase(filename), f, errh, pragh, mode)
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment