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: 's avatarMatthew Dempsky <mdempsky@google.com>
parent b1accced
......@@ -650,14 +650,14 @@ var knownFormats = map[string]string{
"cmd/compile/internal/syntax.Expr %#v": "",
"cmd/compile/internal/syntax.Node %T": "",
"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.token %q": "",
"cmd/compile/internal/syntax.token %s": "",
"cmd/compile/internal/types.EType %d": "",
"cmd/compile/internal/types.EType %s": "",
"cmd/compile/internal/types.EType %v": "",
"cmd/internal/src.Pos %s": "",
"cmd/internal/src.Pos %v": "",
"error %v": "",
"float64 %.2f": "",
"float64 %.3f": "",
......
......@@ -53,7 +53,7 @@ func (p *noder) funcLit(expr *syntax.FuncLit) *Node {
body := p.stmts(expr.Body.List)
lineno = Ctxt.PosTable.XPos(expr.Body.Rbrace)
lineno = p.makeXPos(expr.Body.Rbrace)
if len(body) == 0 {
body = []*Node{nod(OEMPTY, nil, nil)}
}
......
......@@ -105,7 +105,7 @@ func pragmaValue(verb string) syntax.Pragma {
}
// 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)
verb := f[0][3:] // skip "go:"
......
......@@ -5,7 +5,7 @@
package gc
import (
"cmd/internal/src"
"cmd/compile/internal/syntax"
"testing"
)
......@@ -22,7 +22,6 @@ func eq(a, b []string) bool {
}
func TestPragmaFields(t *testing.T) {
var tests = []struct {
in string
want []string
......@@ -49,7 +48,6 @@ func TestPragmaFields(t *testing.T) {
}
func TestPragcgo(t *testing.T) {
var tests = []struct {
in string
want string
......@@ -73,8 +71,9 @@ func TestPragcgo(t *testing.T) {
}
var p noder
var nopos syntax.Pos
for _, tt := range tests {
got := p.pragcgo(src.NoPos, tt.in)
got := p.pragcgo(nopos, tt.in)
if got != tt.want {
t.Errorf("pragcgo(%q) = %q; want %q", tt.in, got, tt.want)
continue
......
......@@ -25,30 +25,33 @@ func parseFiles(filenames []string) uint {
sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
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)
go func(filename string) {
sem <- struct{}{}
defer func() { <-sem }()
defer close(p.err)
base := src.NewFileBase(filename, absFilename(filename))
base := syntax.NewFileBase(filename)
f, err := os.Open(filename)
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
}
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)
}
var lines uint
for _, p := range noders {
for e := range p.err {
yyerrorpos(e.Pos, "%s", e.Msg)
p.yyerrorpos(e.Pos, "%s", e.Msg)
}
p.node()
......@@ -65,12 +68,54 @@ func parseFiles(filenames []string) uint {
return lines
}
func yyerrorpos(pos src.Pos, format string, args ...interface{}) {
yyerrorl(Ctxt.PosTable.XPos(pos), format, args...)
// makeSrcPosBase translates from a *syntax.PosBase to a *src.PosBase.
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
// TODO(gri) Can we eliminate fileh in favor of absFilename?
func fileh(name string) string {
return objabi.AbsFile("", name, pathPrefix)
}
......@@ -81,6 +126,12 @@ func absFilename(name string) string {
// noder transforms package syntax's AST into a Node tree.
type noder struct {
basemap map[*syntax.PosBase]*src.PosBase
basecache struct {
last *syntax.PosBase
base *src.PosBase
}
file *syntax.File
linknames []linkname
pragcgobuf string
......@@ -100,7 +151,7 @@ func (p *noder) funcbody(old ScopeID) {
p.scope = old
}
func (p *noder) openScope(pos src.Pos) {
func (p *noder) openScope(pos syntax.Pos) {
types.Markdcl()
if trackScopes {
......@@ -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()
if trackScopes {
......@@ -121,8 +172,8 @@ func (p *noder) closeScope(pos src.Pos) {
}
}
func (p *noder) markScope(pos src.Pos) {
xpos := Ctxt.PosTable.XPos(pos)
func (p *noder) markScope(pos syntax.Pos) {
xpos := p.makeXPos(pos)
if i := len(Curfn.Func.Marks); i > 0 && Curfn.Func.Marks[i-1].Pos == xpos {
Curfn.Func.Marks[i-1].Scope = p.scope
} else {
......@@ -145,7 +196,7 @@ func (p *noder) closeAnotherScope() {
// linkname records a //go:linkname directive.
type linkname struct {
pos src.Pos
pos syntax.Pos
local string
remote string
}
......@@ -163,7 +214,7 @@ func (p *noder) node() {
if imported_unsafe {
lookup(n.local).Linkname = n.remote
} 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 {
}
f.Nbody.Set(body)
lineno = Ctxt.PosTable.XPos(fun.Body.Rbrace)
lineno = p.makeXPos(fun.Body.Rbrace)
f.Func.Endlineno = lineno
} else {
if pure_go || strings.HasPrefix(f.funcname(), "init.") {
......@@ -497,7 +548,7 @@ func (p *noder) expr(expr syntax.Expr) *Node {
l[i] = p.wrapname(expr.ElemList[i], e)
}
n.List.Set(l)
lineno = Ctxt.PosTable.XPos(expr.Rbrace)
lineno = p.makeXPos(expr.Rbrace)
return n
case *syntax.KeyValueExpr:
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 {
name, ok := expr.(*syntax.Name)
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
continue
}
......@@ -954,7 +1005,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node {
}
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
continue
}
......@@ -1057,7 +1108,7 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node {
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
for i, clause := range clauses {
p.lineno(clause)
......@@ -1113,7 +1164,7 @@ func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node {
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
for i, clause := range clauses {
p.lineno(clause)
......@@ -1298,7 +1349,7 @@ func (p *noder) setlineno(src_ syntax.Node, dst *Node) *Node {
// TODO(mdempsky): Shouldn't happen. Fix package syntax.
return dst
}
dst.Pos = Ctxt.PosTable.XPos(pos)
dst.Pos = p.makeXPos(pos)
return dst
}
......@@ -1311,7 +1362,7 @@ func (p *noder) lineno(n syntax.Node) {
// TODO(mdempsky): Shouldn't happen. Fix package syntax.
return
}
lineno = Ctxt.PosTable.XPos(pos)
lineno = p.makeXPos(pos)
}
// error is called concurrently if files are parsed concurrently.
......@@ -1332,7 +1383,7 @@ var allowedStdPragmas = map[string]bool{
}
// 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 {
case strings.HasPrefix(text, "line "):
// line directives are handled by syntax package
......@@ -1393,8 +1444,8 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
// contain cgo directives, and for security reasons
// (primarily misuse of linker flags), other files are not.
// See golang.org/issue/23672.
func isCgoGeneratedFile(pos src.Pos) bool {
return strings.HasPrefix(filepath.Base(filepath.Clean(pos.AbsFilename())), "_cgo_")
func isCgoGeneratedFile(pos syntax.Pos) bool {
return strings.HasPrefix(filepath.Base(filepath.Clean(fileh(pos.Base().Filename()))), "_cgo_")
}
// safeArg reports whether arg is a "safe" command-line argument,
......
......@@ -568,7 +568,7 @@ Outer:
if !ok {
// First entry for this hash.
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
}
for _, n := range prev {
......
......@@ -4,10 +4,7 @@
package syntax
import (
"cmd/internal/src"
"fmt"
)
import "fmt"
// TODO(gri) consider making this part of the parser code
......@@ -62,11 +59,11 @@ type label struct {
type block struct {
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
}
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...)})
}
......@@ -132,14 +129,14 @@ type targets struct {
// list of unresolved (forward) gotos. parent is the immediately enclosing
// block (or nil), ctxt provides information about the enclosing statements,
// 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}
var varPos src.Pos
var varPos Pos
var varName Expr
var fwdGotos, badGotos []*BranchStmt
recordVarDecl := func(pos src.Pos, name Expr) {
recordVarDecl := func(pos Pos, name Expr) {
varPos = pos
varName = name
// Any existing forward goto jumping over the variable
......@@ -160,7 +157,7 @@ func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledS
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
// become forward gotos for the current block.
fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...)
......
......@@ -4,8 +4,6 @@
package syntax
import "cmd/internal/src"
// ----------------------------------------------------------------------------
// Nodes
......@@ -18,18 +16,18 @@ type Node interface {
// (IndexExpr, IfStmt, etc.) is the position of a token uniquely
// associated with that production; usually the left-most one
// ('[' for IndexExpr, 'if' for IfStmt, etc.)
Pos() src.Pos
Pos() Pos
aNode()
}
type node struct {
// commented out for now since not yet used
// doc *Comment // nil means no comment(s) attached
pos src.Pos
pos Pos
}
func (n *node) Pos() src.Pos { return n.pos }
func (*node) aNode() {}
func (n *node) Pos() Pos { return n.pos }
func (*node) aNode() {}
// ----------------------------------------------------------------------------
// Files
......@@ -149,7 +147,7 @@ type (
Type Expr // nil means no literal type
ElemList []Expr
NKeys int // number of elements with keys
Rbrace src.Pos
Rbrace Pos
expr
}
......@@ -328,7 +326,7 @@ type (
BlockStmt struct {
List []Stmt
Rbrace src.Pos
Rbrace Pos
stmt
}
......@@ -396,13 +394,13 @@ type (
Init SimpleStmt
Tag Expr
Body []*CaseClause
Rbrace src.Pos
Rbrace Pos
stmt
}
SelectStmt struct {
Body []*CommClause
Rbrace src.Pos
Rbrace Pos
stmt
}
)
......@@ -425,14 +423,14 @@ type (
CaseClause struct {
Cases Expr // nil means default clause
Body []Stmt
Colon src.Pos
Colon Pos
node
}
CommClause struct {
Comm SimpleStmt // send or receive stmt; nil means default clause
Body []Stmt
Colon src.Pos
Colon Pos
node
}
)
......
......@@ -291,7 +291,7 @@ func testPos(t *testing.T, list []test, prefix, suffix string, extract func(*Fil
}
// 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 {
t.Errorf("parse error: %s: %v (%s)", src, err, test.nodetyp)
continue
......
......@@ -5,7 +5,6 @@
package syntax
import (
"cmd/internal/src"
"fmt"
"io"
"strconv"
......@@ -16,26 +15,24 @@ const debug = false
const trace = false
type parser struct {
file *src.PosBase
errh ErrorHandler
fileh FilenameHandler
mode Mode
file *PosBase
errh ErrorHandler
mode Mode
scanner
base *src.PosBase // current position base
first error // first error encountered
errcnt int // number of errors encountered
pragma Pragma // pragma flags
base *PosBase // current position base
first error // first error encountered
errcnt int // number of errors encountered
pragma Pragma // pragma flags
fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution)
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.errh = errh
p.fileh = fileh
p.mode = mode
p.scanner.init(
r,
......@@ -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
text := commentText(msg)
col += 2 // text starts after // or /*
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
}
// go: directive (but be conservative and test)
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,
......@@ -76,9 +83,7 @@ func (p *parser) init(file *src.PosBase, r io.Reader, errh ErrorHandler, pragh P
p.indent = nil
}
const lineMax = 1<<24 - 1 // TODO(gri) this limit is defined for src.Pos - fix
func (p *parser) updateBase(line, col uint, text string) {
func (p *parser) updateBase(pos Pos, line, col uint, text string) {
i, n, ok := trailingDigits(text)
if i == 0 {
return // ignore (not a line directive)
......@@ -96,26 +101,25 @@ func (p *parser) updateBase(line, col uint, text string) {
//line filename:line:col
i, i2 = i2, i
n, n2 = n2, n
if n2 == 0 {
if n2 == 0 || n2 > PosMax {
p.errorAt(p.posAt(line, col+i2), "invalid column number: "+text[i2:])
return
}
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:])
return
}
filename := text[:i-1] // lop off :line
absFilename := filename
if p.fileh != nil {
absFilename = p.fileh(filename)
}
// TODO(gri) handle case where filename doesn't change (see #22662)
// TODO(gri) pass column n2 to NewLinePragmaBase
p.base = src.NewLinePragmaBase(src.MakePos(p.file, line, col), filename, absFilename, uint(n) /*uint(n2)*/)
p.base = NewLineBase(pos, filename, n, n2)
}
func commentText(s string) string {
......@@ -162,12 +166,12 @@ func (p *parser) want(tok token) {
// Error handling
// posAt returns the Pos value for (line, col) and the current position base.
func (p *parser) posAt(line, col uint) src.Pos {
return src.MakePos(p.base, line, col)
func (p *parser) posAt(line, col uint) Pos {
return MakePos(p.base, line, col)
}
// 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}
if p.first == nil {
p.first = err
......@@ -180,7 +184,7 @@ func (p *parser) errorAt(pos src.Pos, msg string) {
}
// 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 {
p.print("syntax error: " + msg)
}
......@@ -237,7 +241,7 @@ func tokstring(tok token) string {
}
// 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) }
// The stopset contains keywords that start a statement.
......@@ -417,7 +421,7 @@ func isEmptyFuncDecl(dcl Decl) bool {
// list = "(" { f sep } ")" |
// "{" { 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)
var done bool
......@@ -1064,7 +1068,7 @@ func (p *parser) type_() Expr {
return typ
}
func newIndirect(pos src.Pos, typ Expr) Expr {
func newIndirect(pos Pos, typ Expr) Expr {
o := new(Operation)
o.pos = pos
o.Op = Mul
......@@ -1276,7 +1280,7 @@ func (p *parser) funcResult() []*Field {
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 {
for i := len(styp.FieldList) - len(styp.TagList); i > 0; i-- {
styp.TagList = append(styp.TagList, nil)
......@@ -1694,7 +1698,7 @@ func (p *parser) newRangeClause(lhs Expr, def bool) *RangeClause {
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.pos = pos
a.Op = op
......@@ -1818,7 +1822,7 @@ func (p *parser) header(keyword token) (init SimpleStmt, cond Expr, post SimpleS
var condStmt SimpleStmt
var semi struct {
pos src.Pos
pos Pos
lit string // valid if pos.IsKnown()
}
if p.tok != _Lbrace {
......
......@@ -6,7 +6,6 @@ package syntax
import (
"bytes"
"cmd/internal/src"
"flag"
"fmt"
"io/ioutil"
......@@ -131,7 +130,7 @@ func verifyPrint(filename string, ast1 *File) {
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 {
panic(err)
}
......@@ -155,7 +154,7 @@ func verifyPrint(filename string, ast1 *File) {
}
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 {
t.Errorf("no error reported")
}
......@@ -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) {
// valid line directives lead to a syntax error after them
const valid = "syntax error: package statement must be first"
......@@ -191,11 +195,11 @@ func TestLineDirectives(t *testing.T) {
line, col uint // 0-based
}{
// ignored //line directives
{"//\n", valid, "", 2 - linebase, 0}, // no directive
{"//line\n", valid, "", 2 - linebase, 0}, // missing colon
{"//line foo\n", valid, "", 2 - linebase, 0}, // missing colon
{" //line foo:\n", valid, "", 2 - linebase, 0}, // not a line start
{"// line foo:\n", valid, "", 2 - linebase, 0}, // space between // and line
{"//\n", valid, "", 1, 0}, // no directive
{"//line\n", valid, "", 1, 0}, // missing colon
{"//line foo\n", valid, "", 1, 0}, // missing colon
{" //line foo:\n", valid, "", 1, 0}, // not a line start
{"// line foo:\n", valid, "", 1, 0}, // space between // and line
// invalid //line directives with one colon
{"//line :\n", "invalid line number: ", "", 0, 8},
......@@ -206,7 +210,7 @@ func TestLineDirectives(t *testing.T) {
{"//line foo:1 \n", "invalid line number: 1 ", "", 0, 11},
{"//line foo:-12\n", "invalid line number: -12", "", 0, 11},
{"//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
{"//line ::\n", "invalid line number: ", "", 0, 9},
......@@ -217,27 +221,33 @@ func TestLineDirectives(t *testing.T) {
{"//line :123:0\n", "invalid column number: 0", "", 0, 12},
{"//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//line bar:345\nfoo", valid, "bar", 345 - 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 :x:1\n", valid, ":x", 0, 0},
{"//line foo ::1\n", valid, "foo :", 0, 0},
{"//line /src/a/a.go:123\n foo", valid, "/src/a/a.go", 123 - linebase, 3},
{"//line :x:1\n", valid, ":x", 1 - linebase, 0},
{"//line foo ::1\n", valid, "foo :", 1 - linebase, 0},
{"//line foo:123abc:1\n", valid, "foo:123abc", 0, 0},
{"//line foo :123:1\n", valid, "foo ", 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
{"/**/", valid, "", 1 - linebase, 4}, // no directive
{"/*line*/", valid, "", 1 - linebase, 8}, // missing colon
{"/*line foo*/", valid, "", 1 - linebase, 12}, // missing colon
{" //line foo:*/", valid, "", 1 - linebase, 15}, // not a line start
{"/* line foo:*/", valid, "", 1 - linebase, 15}, // space between // and line
{"/**/", valid, "", 0, 4}, // no directive
{"/*line*/", valid, "", 0, 8}, // missing colon
{"/*line foo*/", valid, "", 0, 12}, // missing colon
{" //line foo:*/", valid, "", 0, 15}, // not a line start
{"/* line foo:*/", valid, "", 0, 15}, // space between // and line
// invalid /*line directives with one colon
{"/*line :*/", "invalid line number: ", "", 0, 8},
......@@ -247,7 +257,7 @@ func TestLineDirectives(t *testing.T) {
{"/*line foo:0*/", "invalid line number: 0", "", 0, 11},
{"/*line foo:1 */", "invalid line number: 1 ", "", 0, 11},
{"/*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
{"/*line ::*/", "invalid line number: ", "", 0, 9},
......@@ -258,31 +268,27 @@ func TestLineDirectives(t *testing.T) {
{"/*line :123:0*/", "invalid column number: 0", "", 0, 12},
{"/*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
// TODO(gri) remove \n after directives once line number is computed correctly
{"/*line foo:123*/\n foo", valid, "foo", 123 - linebase, 3},
// effect of valid /*line directives on lines
{"/*line foo:123*/ foo", valid, "foo", 123 - linebase, 3},
{"/*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 " + runtime.GOROOT() + "/src/a/a.go:123*/\n foo", valid, "$GOROOT/src/a/a.go", 123 - linebase, 3},
{"/*line :x:1*/\n", valid, ":x", 1 - linebase, 0},
{"/*line foo ::1*/\n", valid, "foo :", 1 - linebase, 0},
{"/*line foo:123abc:1*/\n", valid, "foo:123abc", 1 - linebase, 0},
{"/*line foo :123:1*/\n", valid, "foo ", 123 - linebase, 0},
{"/*line ::123*/\n", valid, ":", 123 - linebase, 0},
// test effect of /*line directive on (relative) position information for this line
// TODO(gri) add these tests
// TODO(gri) add tests to verify correct column changes, once implemented
{"/*line C:foo:123*/", valid, "C:foo", 123 - linebase, 0},
{"/*line /src/a/a.go:123*/ foo", valid, "/src/a/a.go", 123 - linebase, 3},
{"/*line :x:1*/", valid, ":x", 1 - linebase, 0},
{"/*line foo ::1*/", valid, "foo :", 1 - linebase, 0},
{"/*line foo:123abc:1*/", valid, "foo:123abc", 1 - linebase, 0},
{"/*line foo :123:10*/", valid, "foo ", 123 - linebase, 10 - colbase},
{"/*line ::123*/", valid, ":", 123 - linebase, 0},
// effect of valid /*line directives on columns
{"/*line :x:1:10*/", valid, ":x", 1 - linebase, 10 - colbase},
{"/*line foo ::1:2*/", valid, "foo :", 1 - linebase, 2 - colbase},
{"/*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 {
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)
_, err := Parse(nil, strings.NewReader(test.src), nil, nil, 0)
if err == nil {
t.Errorf("%s: no error reported", test.src)
continue
......@@ -295,14 +301,16 @@ func TestLineDirectives(t *testing.T) {
if msg := perr.Msg; 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)
}
if line := perr.Pos.RelLine(); line-linebase != test.line {
t.Errorf("%s: got line = %d; want %d", test.src, line-linebase, test.line)
if line := pos.RelLine(); line != test.line+linebase {
t.Errorf("%s: got line = %d; want %d", test.src, line, test.line+linebase)
}
if col := perr.Pos.Col(); col-colbase != test.col {
t.Errorf("%s: got col = %d; want %d", test.src, col-colbase, test.col)
if col := pos.RelCol(); col != test.col+colbase {
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) {
"package p; type _ = int; type T1 = struct{}; type ( _ = *struct{}; T2 = float32 )",
// 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 {
t.Error(err)
continue
......
......@@ -5,7 +5,6 @@
package syntax
import (
"cmd/internal/src"
"fmt"
"io"
"os"
......@@ -21,7 +20,7 @@ const (
// Error describes a syntax error. Error implements the error interface.
type Error struct {
Pos src.Pos
Pos Pos
Msg string
}
......@@ -42,11 +41,7 @@ type Pragma uint16
// A PragmaHandler is used to process //go: directives as
// they're scanned. The returned Pragma value will be unioned into the
// next FuncDecl node.
type PragmaHandler func(pos src.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
type PragmaHandler func(pos Pos, text string) Pragma
// 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,
......@@ -60,10 +55,7 @@ type FilenameHandler func(name string) string
//
// If pragh != nil, it is called with each pragma encountered.
//
// If fileh != nil, it is called to process each filename
// encountered in //line directives.
//
func Parse(base *src.PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, fileh FilenameHandler, mode Mode) (_ *File, first error) {
func Parse(base *PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, first error) {
defer func() {
if p := recover(); p != nil {
if err, ok := p.(Error); ok {
......@@ -75,7 +67,7 @@ func Parse(base *src.PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHand
}()
var p parser
p.init(base, src, errh, pragh, fileh, mode)
p.init(base, src, errh, pragh, mode)
p.next()
return p.fileOrNil(), p.first
}
......@@ -90,5 +82,5 @@ func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mod
return nil, err
}
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