~mna/snow

312f82aa74262ef61bc38df8a053385f424416ce — Martin Angers 1 year, 10 months ago c0bc63b
pkg/compiler: fix bad pos panic
M pkg/compiler/parser.go => pkg/compiler/parser.go +2 -12
@@ 64,16 64,6 @@ func (p *parser) errorExpected(pos gotoken.Pos, msg string) {
	p.error(pos, msg)
}

func (p *parser) safePos(pos gotoken.Pos) (res gotoken.Pos) {
	defer func() {
		if recover() != nil {
			res = gotoken.Pos(p.file.Base() + p.file.Size()) // EOF position
		}
	}()
	_ = p.file.Offset(pos) // trigger a panic if position is out-of-range
	return pos
}

func (p *parser) expect(tok token) gotoken.Pos {
	pos := p.pos
	if p.tok != tok {


@@ 169,7 159,7 @@ func (p *parser) parseStmt(topLevel bool) stmt {
		}
		p.errorExpected(pos, lbl)
		p.advance(sync)
		return &badStmt{from: pos, to: p.safePos(p.pos)}
		return &badStmt{from: pos, to: p.pos}
	}
}



@@ 333,7 323,7 @@ func (p *parser) parseAtom() expr {
	default:
		pos := p.pos
		p.errorExpected(pos, "expression")
		return &badExpr{from: pos, to: p.safePos(p.pos)}
		return &badExpr{from: pos, to: p.pos}
	}
}


M pkg/compiler/parser_test.go => pkg/compiler/parser_test.go +5 -5
@@ 10,7 10,6 @@ import (
	"path/filepath"
	"testing"

	"github.com/kr/pretty"
	"github.com/kylelemons/godebug/diff"
)



@@ 43,15 42,16 @@ func TestParser(t *testing.T) {
			p.init(fset, fi.Name(), b)

			f := p.parseFile()
			p.errors.RemoveMultiples()
			// for the test, keep all errors per line, in the order they occurred
			//p.errors.RemoveMultiples()
			parseErr := p.errors.Err()
			pretty.Print(f)
			//pretty.Print(f)

			var ebuf bytes.Buffer
			goscanner.PrintError(&ebuf, parseErr)

			var buf bytes.Buffer
			pp := printer{w: &buf, withPos: true, withComments: true}
			pp := printer{w: &buf, posMode: posOffsets, withComments: true}
			if err := pp.Print(f, p.file); err != nil {
				t.Fatal(err)
			}


@@ 77,7 77,7 @@ func TestParser(t *testing.T) {
			if patch := diff.Diff(buf.String(), string(want)); patch != "" {
				t.Logf("got:\n%s\n", buf.String())
				t.Logf("want:\n%s\n", string(want))
				t.Errorf("diff:\n%s\n", patch)
				t.Errorf("diff (input eof=%d):\n%s\n", len(b), patch)
			}

			// validate errors output

M pkg/compiler/printer.go => pkg/compiler/printer.go +46 -23
@@ 8,19 8,31 @@ import (
	"strings"
)

type posMode int

const (
	posNone    posMode = iota
	posOffsets         // [startoffset:endoffset]
	posLong            // [filename:startline:col:endline:col]
	posRaw             // [%d:%d] of the raw uninterpreted gotoken.Pos values
)

type printer struct {
	// options to set before use
	w            io.Writer
	file         *gotoken.File
	depth        int
	withPos      bool // print start and end position in [startoffset:endoffset] format
	longPos      bool // print position in filename:startline:col:endline:col format
	posMode      posMode
	withComments bool

	err error
	// set during printing
	file   *gotoken.File
	maxPos gotoken.Pos
	depth  int
	err    error
}

func (p *printer) Print(n node, file *gotoken.File) error {
	p.file = file
	p.maxPos = gotoken.Pos(p.file.Base() + p.file.Size())
	walk(p, n)
	return p.err
}


@@ 36,6 48,13 @@ func (p *printer) printMsg(s string, indent bool) {
	_, p.err = fmt.Fprintf(p.w, "%s%s", strings.Repeat("  ", repeat), s)
}

func (p *printer) clamp(pos gotoken.Pos) gotoken.Pos {
	if pos > p.maxPos {
		return p.maxPos
	}
	return pos
}

func (p *printer) printPosln(n node) {
	if p.err != nil {
		return


@@ 43,31 62,35 @@ func (p *printer) printPosln(n node) {

	var startLbl, endLbl string
	start, end := n.Pos(), n.End()
	if !p.longPos {
	switch p.posMode {
	case posOffsets:
		startLbl, endLbl = "-", "-"
		if start.IsValid() {
			startLbl = strconv.Itoa(p.file.Offset(start))
		}
		if end.IsValid() {
			endLbl = strconv.Itoa(p.file.Offset(end))
			endLbl = strconv.Itoa(p.file.Offset(p.clamp(end)))
		}
		_, p.err = fmt.Fprintf(p.w, " [%s:%s]\n", startLbl, endLbl)
		return
	}

	if start.IsValid() {
		lstart := p.file.Position(start)
		startLbl = fmt.Sprintf("%s:%d:%d", lstart.Filename, lstart.Line, lstart.Column)
	} else {
		startLbl = fmt.Sprintf("%s:-:-", p.file.Name())
	}
	if end.IsValid() {
		lend := p.file.Position(end)
		endLbl = fmt.Sprintf("%d:%d", lend.Line, lend.Column)
	} else {
		endLbl = "-:-"
	case posLong:
		if start.IsValid() {
			lstart := p.file.Position(start)
			startLbl = fmt.Sprintf("%s:%d:%d", lstart.Filename, lstart.Line, lstart.Column)
		} else {
			startLbl = fmt.Sprintf("%s:-:-", p.file.Name())
		}
		if end.IsValid() {
			lend := p.file.Position(p.clamp(end))
			endLbl = fmt.Sprintf(":%d:%d", lend.Line, lend.Column)
		} else {
			endLbl = ":-:-"
		}

	case posRaw:
		startLbl, endLbl = strconv.Itoa(int(start)), strconv.Itoa(int(end))
	}
	_, p.err = fmt.Fprintf(p.w, " [%s::%s]\n", startLbl, endLbl)

	_, p.err = fmt.Fprintf(p.w, " [%s:%s]\n", startLbl, endLbl)
}

func (p *printer) Visit(n node) visitor {


@@ 132,7 155,7 @@ func (p *printer) Visit(n node) visitor {
		panic(fmt.Sprintf("printer: unexpected node type %T", n))
	}

	if p.withPos {
	if p.posMode != posNone {
		p.printPosln(n)
	} else {
		p.printMsg("\n", false)

M pkg/compiler/printer_test.go => pkg/compiler/printer_test.go +7 -9
@@ 202,24 202,22 @@ file [test:1:1::1:70]
`

	cases := []struct {
		withPos      bool
		longPos      bool
		posMode      posMode
		withComments bool
		want         string
	}{
		{false, false, false, wantNoPos},
		{true, false, false, wantShort},
		{true, true, false, wantLong},
		{false, false, true, wantComments},
		{posNone, false, wantNoPos},
		{posOffsets, false, wantShort},
		{posLong, false, wantLong},
		{posNone, true, wantComments},
	}
	for _, c := range cases {
		t.Run(fmt.Sprintf("%v %v %v", c.withPos, c.longPos, c.withComments), func(t *testing.T) {
		t.Run(fmt.Sprintf("%v %v", c.posMode, c.withComments), func(t *testing.T) {
			var buf bytes.Buffer
			p := printer{
				w:            &buf,
				withComments: c.withComments,
				withPos:      c.withPos,
				longPos:      c.longPos,
				posMode:      c.posMode,
			}
			if err := p.Print(f, fsf); err != nil {
				t.Fatal(err)

M pkg/compiler/testdata/parser/comments.snow.err => pkg/compiler/testdata/parser/comments.snow.err +1 -0
@@ 1,1 1,2 @@
comments.snow:7:1: illegal character U+0024 '$'
comments.snow:7:1: expected top-level statement, found 'illegal'

M pkg/compiler/testdata/parser/fn_nested_paren_newline.snow => pkg/compiler/testdata/parser/fn_nested_paren_newline.snow +0 -1
@@ 1,4 1,3 @@
# panics because of the semicolon insertion (after ident, then illegal ')', I believe?)
fn test(x: int) {
  (
   x

A pkg/compiler/testdata/parser/fn_nested_paren_newline.snow.err => pkg/compiler/testdata/parser/fn_nested_paren_newline.snow.err +4 -0
@@ 0,0 1,4 @@
fn_nested_paren_newline.snow:3:5: expected ')', found ';'
fn_nested_paren_newline.snow:4:3: expected statement, found ')'
fn_nested_paren_newline.snow:5:3: expected '}', found 'eof'
fn_nested_paren_newline.snow:5:3: expected ';', found 'eof'

A pkg/compiler/testdata/parser/fn_nested_paren_newline.snow.want => pkg/compiler/testdata/parser/fn_nested_paren_newline.snow.want +12 -0
@@ 0,0 1,12 @@
file [0:33]
  fn [0:33]
    ident [test] [3:7]
    sig [7:15]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
    block [16:33]
      expr [20:27]
        paren [20:27]
          ident [x] [25:26]
      bad stmt [29:33]

M pkg/compiler/testdata/parser/fn_return_return.snow.err => pkg/compiler/testdata/parser/fn_return_return.snow.err +1 -0
@@ 1,1 1,2 @@
fn_return_return.snow:2:10: expected expression, found 'return'
fn_return_return.snow:2:10: expected ';', found 'return'