~mna/snow unlisted

21ab2b8d2001eeb8a7bbc674b8c0822dbd7f0310 — Martin Angers 1 year, 5 months ago aac573e
pkg/compiler: parse function attributes
46 files changed, 351 insertions(+), 166 deletions(-)

M pkg/compiler/ast.go
M pkg/compiler/parser.go
M pkg/compiler/printer.go
M pkg/compiler/printer_test.go
M pkg/compiler/scanner.go
M pkg/compiler/testdata/parser/comment.snow.want
M pkg/compiler/testdata/parser/comments.snow.want
M pkg/compiler/testdata/parser/empty.snow.want
M pkg/compiler/testdata/parser/fn.snow.want
M pkg/compiler/testdata/parser/fn_add.snow.want
M pkg/compiler/testdata/parser/fn_assign.snow.want
M pkg/compiler/testdata/parser/fn_assign_invalid_lhs.snow.want
A pkg/compiler/testdata/parser/fn_attr_invalid.snow
A pkg/compiler/testdata/parser/fn_attr_invalid.snow.err
A pkg/compiler/testdata/parser/fn_attr_invalid.snow.want
A pkg/compiler/testdata/parser/fn_attr_many.snow
A pkg/compiler/testdata/parser/fn_attr_many.snow.err
A pkg/compiler/testdata/parser/fn_attr_many.snow.want
M pkg/compiler/testdata/parser/fn_expr_stmt.snow.want
A pkg/compiler/testdata/parser/fn_extern.snow
A pkg/compiler/testdata/parser/fn_extern.snow.err
A pkg/compiler/testdata/parser/fn_extern.snow.want
M pkg/compiler/testdata/parser/fn_mul.snow.want
M pkg/compiler/testdata/parser/fn_naked_return.snow.want
M pkg/compiler/testdata/parser/fn_nested_blocks.snow.want
M pkg/compiler/testdata/parser/fn_nested_paren.snow.want
M pkg/compiler/testdata/parser/fn_nested_paren_newline.snow.want
M pkg/compiler/testdata/parser/fn_nested_unary.snow.want
M pkg/compiler/testdata/parser/fn_paren_expr.snow.want
M pkg/compiler/testdata/parser/fn_return_return.snow.want
M pkg/compiler/testdata/parser/fn_unary.snow.want
M pkg/compiler/testdata/parser/let.snow.want
M pkg/compiler/testdata/parser/let_full.snow.want
M pkg/compiler/testdata/parser/let_infer.snow.want
M pkg/compiler/testdata/parser/let_question.snow.want
M pkg/compiler/testdata/parser/return.snow.want
M pkg/compiler/testdata/parser/return_return.snow.want
M pkg/compiler/testdata/parser/var.snow.want
M pkg/compiler/testdata/parser/var_bang.snow.want
M pkg/compiler/testdata/parser/var_comma_for_semi.snow.want
M pkg/compiler/testdata/parser/var_full.snow.want
M pkg/compiler/testdata/parser/var_infer.snow.want
M pkg/compiler/testdata/parser/var_invalid_type.snow.want
M pkg/compiler/token.go
M pkg/compiler/visitor.go
M pkg/grammar/grammar.ebnf
M pkg/compiler/ast.go => pkg/compiler/ast.go +41 -3
@@ 38,14 38,26 @@ func (f *file) End() gotoken.Pos {

// represents a FuncDef production.
type fnDef struct {
	attrs     []*attr // possibly empty
	fn        gotoken.Pos
	name      *ident
	signature *fnSig
	body      *block
	body      *block // possibly nil for external func declarations
}

func (f *fnDef) Pos() gotoken.Pos { return f.fn }
func (f *fnDef) End() gotoken.Pos { return f.body.End() }
func (f *fnDef) Pos() gotoken.Pos {
	if len(f.attrs) > 0 {
		return f.attrs[0].Pos()
	}
	return f.fn
}

func (f *fnDef) End() gotoken.Pos {
	if f.body == nil {
		return f.signature.End()
	}
	return f.body.End()
}

type fnSig struct {
	lparen gotoken.Pos // (


@@ 87,6 99,32 @@ type block struct {
func (b *block) Pos() gotoken.Pos { return b.lbrace }
func (b *block) End() gotoken.Pos { return b.rbrace + 1 }

type attr struct {
	at     gotoken.Pos
	name   *ident
	lbrace gotoken.Pos
	fields []*keyValue
	rbrace gotoken.Pos
}

func (a *attr) Pos() gotoken.Pos { return a.at }
func (a *attr) End() gotoken.Pos { return a.rbrace + 1 }

type keyValue struct {
	key   *ident
	colon gotoken.Pos
	value *basicLit
	comma gotoken.Pos // gotoken.NoPos if no trailing comma
}

func (k *keyValue) Pos() gotoken.Pos { return k.key.Pos() }
func (k *keyValue) End() gotoken.Pos {
	if k.comma.IsValid() {
		return k.comma + 1
	}
	return k.value.End()
}

// represents a VarDef production.
type varDef struct {
	kw     token

M pkg/compiler/parser.go => pkg/compiler/parser.go +143 -78
@@ 43,79 43,6 @@ func (p *parser) next() {
	}
}

func (p *parser) error(pos gotoken.Pos, msg string) {
	lpos := p.file.Position(pos)
	p.errors.Add(lpos, msg)
}

func (p *parser) errorExpected(pos gotoken.Pos, msg string) {
	msg = "expected " + msg
	if pos == p.pos {
		// the error happened at the current position;
		// make the error message more specific
		switch {
		case p.tok.literal():
			// print 123 rather than 'INT', etc.
			msg += ", found " + p.lit
		default:
			msg += ", found '" + p.tok.String() + "'"
		}
	}
	p.error(pos, msg)
}

func (p *parser) expect(tok token) gotoken.Pos {
	pos := p.pos
	if p.tok != tok {
		p.errorExpected(pos, "'"+tok.String()+"'")
	}
	p.next() // make progress
	return pos
}

func (p *parser) expectSemi() {
	// semicolon is optional before a closing ')' or '}'
	if p.tok != Rparen && p.tok != Rbrace {
		switch p.tok {
		case Comma:
			// properly parse a ',' instead of a ';' but add error
			p.errorExpected(p.pos, "';'")
			fallthrough
		case Semicolon:
			p.next()
		default:
			p.errorExpected(p.pos, "';'")
			p.advance(stmtStart)
		}
	}
}

// sets of tokens that can be sync'd to (in a call to p.advance)
var (
	// TopLevelStmt production in the grammar.
	topLevelStmtStart = map[token]bool{
		Var: true,
		Let: true,
		Fn:  true,
	}

	stmtStart = map[token]bool{
		Var:    true,
		Let:    true,
		Fn:     true,
		Return: true,
	}
)

func (p *parser) advance(to map[token]bool) {
	for p.tok != EOF {
		if to[p.tok] {
			return
		}
		p.next()
	}
}

func (p *parser) parseFile() *file {
	var stmts []stmt
	for p.tok != EOF {


@@ 147,7 74,7 @@ func (p *parser) parseStmt(topLevel bool) stmt {
	switch p.tok {
	case Let, Var:
		return p.parseVarDef()
	case Fn:
	case At, Fn:
		return p.parseFuncDef()
	default:
		pos := p.pos


@@ 247,11 174,15 @@ func (p *parser) parseIdent() *ident {
}

func (p *parser) parseBasicLit() *basicLit {
	lit := p.lit
	tok := p.tok
	var lit string
	tok := Illegal
	if p.tok == String || p.tok == Int {
		lit = p.lit
		tok = p.tok
	}
	return &basicLit{
		literal: tok,
		pos:     p.expect(p.tok),
		pos:     p.expect(String, Int),
		value:   lit,
	}
}


@@ 327,14 258,59 @@ func (p *parser) parseAtom() expr {
	}
}

func (p *parser) parseAttr() *attr {
	a := &attr{
		at: p.expect(At),
	}
	a.name = p.parseIdent()
	a.lbrace = p.expect(Lbrace)
	a.fields = p.parseKeyValueList()
	a.rbrace = p.expect(Rbrace)
	p.expectSemi()
	return a
}

func (p *parser) parseKeyValueList() []*keyValue {
	var list []*keyValue
	for p.tok != Rbrace && p.tok != EOF {
		kv := &keyValue{
			key: p.parseIdent(),
		}
		kv.colon = p.expect(Colon)
		kv.value = p.parseBasicLit()
		list = append(list, kv)

		if p.tok == Comma {
			kv.comma = p.expect(Comma)
		} else {
			break
		}
	}
	return list
}

func (p *parser) parseFuncDef() *fnDef {
	var attrs []*attr
	for p.tok == At {
		attrs = append(attrs, p.parseAttr())
	}

	fd := &fnDef{
		fn: p.pos,
		attrs: attrs,
		fn:    p.pos,
	}

	p.next()
	fd.name = p.parseIdent()
	fd.signature = p.parseFuncSig()

	// stop here if this is a function declaration (no body)
	if p.tok != Lbrace {
		p.expectSemi()
		return fd
	}

	// continue with function body (function definition)
	fd.body = p.parseBlock()
	p.expectSemi()
	return fd


@@ 391,3 367,92 @@ func (p *parser) parseParamsList() []*paramDef {
	}
	return params
}

func (p *parser) error(pos gotoken.Pos, msg string) {
	lpos := p.file.Position(pos)
	p.errors.Add(lpos, msg)
}

func (p *parser) errorExpected(pos gotoken.Pos, msg string) {
	msg = "expected " + msg
	if pos == p.pos {
		// the error happened at the current position;
		// make the error message more specific
		switch {
		case p.tok.literal():
			// print 123 rather than 'INT', etc.
			msg += ", found " + p.lit
		default:
			msg += ", found '" + p.tok.String() + "'"
		}
	}
	p.error(pos, msg)
}

func (p *parser) expect(toks ...token) gotoken.Pos {
	pos := p.pos

	var lbl string
	var ok bool
	for i, tok := range toks {
		if p.tok == tok {
			ok = true
			break
		}
		if i > 0 {
			lbl += ", "
		}
		lbl += "'" + tok.String() + "'"
	}
	if !ok {
		if len(toks) > 1 {
			lbl = "one of " + lbl
		}
		p.errorExpected(pos, lbl)
	}
	p.next() // make progress
	return pos
}

func (p *parser) expectSemi() {
	// semicolon is optional before a closing ')' or '}'
	if p.tok != Rparen && p.tok != Rbrace {
		switch p.tok {
		case Comma:
			// properly parse a ',' instead of a ';' but add error
			p.errorExpected(p.pos, "';'")
			fallthrough
		case Semicolon:
			p.next()
		default:
			p.errorExpected(p.pos, "';'")
			p.advance(stmtStart)
		}
	}
}

// sets of tokens that can be sync'd to (in a call to p.advance)
var (
	// TopLevelStmt production in the grammar.
	topLevelStmtStart = map[token]bool{
		Var: true,
		Let: true,
		Fn:  true,
	}

	stmtStart = map[token]bool{
		Var:    true,
		Let:    true,
		Fn:     true,
		Return: true,
	}
)

func (p *parser) advance(to map[token]bool) {
	for p.tok != EOF {
		if to[p.tok] {
			return
		}
		p.next()
	}
}

M pkg/compiler/printer.go => pkg/compiler/printer.go +12 -4
@@ 102,15 102,23 @@ func (p *printer) Visit(n node) visitor {

	switch n := n.(type) {
	case *file:
		p.printMsg("file", true)
		p.printMsg(fmt.Sprintf("file [%d, #%d]", len(n.stmts), len(n.comments)), true)
	case *fnDef:
		p.printMsg("fn", true)
	case *fnSig:
		p.printMsg("sig", true)
		ret := 0
		if n.retTyp != nil {
			ret = 1
		}
		p.printMsg(fmt.Sprintf("sig [%d->%d]", len(n.params), ret), true)
	case *paramDef:
		p.printMsg("param", true)
	case *block:
		p.printMsg("block", true)
		p.printMsg(fmt.Sprintf("block [%d]", len(n.stmts)), true)
	case *attr:
		p.printMsg(fmt.Sprintf("@ [%d]", len(n.fields)), true)
	case *keyValue:
		p.printMsg("field", true)
	case *varDef:
		lbl := n.kw.String()
		if n.colon.IsValid() {


@@ 144,7 152,7 @@ func (p *printer) Visit(n node) visitor {
		if !p.withComments {
			return nil
		}
		p.printMsg("comments", true)
		p.printMsg(fmt.Sprintf("comments [%d]", len(n.list)), true)
	case *comment:
		if !p.withComments {
			return nil

M pkg/compiler/printer_test.go => pkg/compiler/printer_test.go +13 -13
@@ 110,14 110,14 @@ func TestPrinter(t *testing.T) {
	}

	wantNoPos := `
file
file [2, #1]
    var:=
      ident [a]
      ident [int]
      int [1]
    fn
      ident [add]
      sig
      sig [2->1]
        param
          ident [x]
          ident [int]


@@ 125,15 125,15 @@ file
          ident [y]
          ident [int]
        ident [int]
      block
      block [1]
        return
          binary [+]
            ident [x]
            ident [y]
`
	wantComments := `
file
  comments
file [2, #1]
  comments [2]
    comment [#abc]
    comment [#def]
  var:=


@@ 142,7 142,7 @@ file
    int [1]
  fn
    ident [add]
    sig
    sig [2->1]
      param
        ident [x]
        ident [int]


@@ 150,21 150,21 @@ file
        ident [y]
        ident [int]
      ident [int]
    block
    block [1]
      return
        binary [+]
          ident [x]
          ident [y]
`
	wantShort := `
file [0:69]
file [2, #1] [0:69]
    var:= [8:22]
      ident [a] [12:13]
      ident [int] [15:18]
      int [1] [21:22]
    fn [23:69]
      ident [add] [26:29]
      sig [30:51]
      sig [2->1] [30:51]
        param [31:38]
          ident [x] [31:32]
          ident [int] [34:37]


@@ 172,21 172,21 @@ file [0:69]
          ident [y] [39:40]
          ident [int] [42:45]
        ident [int] [48:51]
      block [52:69]
      block [1] [52:69]
        return [56:67]
          binary [+] [64:67]
            ident [x] [64:65]
            ident [y] [66:67]
`
	wantLong := `
file [test:1:1::1:70]
file [2, #1] [test:1:1::1:70]
    var:= [test:1:9::1:23]
      ident [a] [test:1:13::1:14]
      ident [int] [test:1:16::1:19]
      int [1] [test:1:22::1:23]
    fn [test:1:24::1:70]
      ident [add] [test:1:27::1:30]
      sig [test:1:31::1:52]
      sig [2->1] [test:1:31::1:52]
        param [test:1:32::1:39]
          ident [x] [test:1:32::1:33]
          ident [int] [test:1:35::1:38]


@@ 194,7 194,7 @@ file [test:1:1::1:70]
          ident [y] [test:1:40::1:41]
          ident [int] [test:1:43::1:46]
        ident [int] [test:1:49::1:52]
      block [test:1:53::1:70]
      block [1] [test:1:53::1:70]
        return [test:1:57::1:68]
          binary [+] [test:1:65::1:68]
            ident [x] [test:1:65::1:66]

M pkg/compiler/scanner.go => pkg/compiler/scanner.go +1 -1
@@ 176,7 176,7 @@ func (s *scanner) scan() (pos gotoken.Pos, tok token, lit string) {
			lit = s.string()
			insertSemi = true

		case ':', ';', ',', '(', ')', '{', '}', '+', '*', '/', '%', '=':
		case ':', ';', ',', '(', ')', '{', '}', '+', '*', '/', '%', '=', '@':
			tok = lookupOp(string(cur))
			insertSemi = (tok == Rparen || tok == Rbrace)
			// special-case for semicolons, set the literal to be able to tell apart the

M pkg/compiler/testdata/parser/comment.snow.want => pkg/compiler/testdata/parser/comment.snow.want +2 -2
@@ 1,3 1,3 @@
file [0:7]
  comments [0:7]
file [0, #1] [0:7]
  comments [1] [0:7]
    comment [# hello] [0:7]

M pkg/compiler/testdata/parser/comments.snow.want => pkg/compiler/testdata/parser/comments.snow.want +3 -3
@@ 1,10 1,10 @@
file [0:79]
  comments [0:50]
file [1, #2] [0:79]
  comments [4] [0:50]
    comment [# group a.1] [0:11]
    comment [# group a.2] [12:23]
    comment [# group a.3] [26:37]
    comment [# group a.4] [39:50]
  comments [55:78]
  comments [2] [55:78]
    comment [# group b.1] [55:66]
    comment [# group b.2] [67:78]
  bad stmt [52:79]

M pkg/compiler/testdata/parser/empty.snow.want => pkg/compiler/testdata/parser/empty.snow.want +1 -1
@@ 1,1 1,1 @@
file [-:-]
file [0, #0] [-:-]

M pkg/compiler/testdata/parser/fn.snow.want => pkg/compiler/testdata/parser/fn.snow.want +3 -3
@@ 1,5 1,5 @@
file [0:14]
file [1, #0] [0:14]
  fn [0:14]
    ident [empty] [3:8]
    sig [8:10]
    block [11:14]
    sig [0->0] [8:10]
    block [0] [11:14]

M pkg/compiler/testdata/parser/fn_add.snow.want => pkg/compiler/testdata/parser/fn_add.snow.want +3 -3
@@ 1,7 1,7 @@
file [0:48]
file [1, #0] [0:48]
  fn [0:48]
    ident [add] [3:6]
    sig [6:29]
    sig [2->1] [6:29]
      param [7:14]
        ident [x] [7:8]
        ident [int] [10:13]


@@ 9,7 9,7 @@ file [0:48]
        ident [y] [15:16]
        ident [int] [18:21]
      ident [int] [26:29]
    block [30:48]
    block [1] [30:48]
      return [34:46]
        binary [+] [41:46]
          ident [x] [41:42]

M pkg/compiler/testdata/parser/fn_assign.snow.want => pkg/compiler/testdata/parser/fn_assign.snow.want +3 -3
@@ 1,11 1,11 @@
file [0:40]
file [1, #0] [0:40]
  fn [0:40]
    ident [test] [3:7]
    sig [7:15]
    sig [1->0] [7:15]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
    block [16:40]
    block [2] [16:40]
      var: [20:30]
        ident [y] [24:25]
        ident [int] [27:30]

M pkg/compiler/testdata/parser/fn_assign_invalid_lhs.snow.want => pkg/compiler/testdata/parser/fn_assign_invalid_lhs.snow.want +3 -3
@@ 1,14 1,14 @@
file [0:43]
file [1, #0] [0:43]
  fn [0:43]
    ident [test] [3:7]
    sig [7:23]
    sig [2->0] [7:23]
      param [8:15]
        ident [x] [8:9]
        ident [int] [11:14]
      param [16:22]
        ident [y] [16:17]
        ident [int] [19:22]
    block [24:43]
    block [1] [24:43]
      assign [28:41]
        bad expr [28:33]
        binary [+] [36:41]

A pkg/compiler/testdata/parser/fn_attr_invalid.snow => pkg/compiler/testdata/parser/fn_attr_invalid.snow +3 -0
@@ 0,0 1,3 @@

@extern{x: y}
fn test()

A pkg/compiler/testdata/parser/fn_attr_invalid.snow.err => pkg/compiler/testdata/parser/fn_attr_invalid.snow.err +1 -0
@@ 0,0 1,1 @@
fn_attr_invalid.snow:2:12: expected one of 'string', 'int', found y

A pkg/compiler/testdata/parser/fn_attr_invalid.snow.want => pkg/compiler/testdata/parser/fn_attr_invalid.snow.want +9 -0
@@ 0,0 1,9 @@
file [1, #0] [1:24]
  fn [1:24]
    @ [1] [1:14]
      ident [extern] [2:8]
      field [9:12]
        ident [x] [9:10]
        illegal [] [12:12]
    ident [test] [18:22]
    sig [0->0] [22:24]

A pkg/compiler/testdata/parser/fn_attr_many.snow => pkg/compiler/testdata/parser/fn_attr_many.snow +6 -0
@@ 0,0 1,6 @@
@attr1{x: 1, y: 2}
@attr2{}
@attr3{
  x: "z",
}
fn test() -> int

A pkg/compiler/testdata/parser/fn_attr_many.snow.err => pkg/compiler/testdata/parser/fn_attr_many.snow.err +0 -0
A pkg/compiler/testdata/parser/fn_attr_many.snow.want => pkg/compiler/testdata/parser/fn_attr_many.snow.want +20 -0
@@ 0,0 1,20 @@
file [1, #0] [0:64]
  fn [0:64]
    @ [2] [0:18]
      ident [attr1] [1:6]
      field [7:12]
        ident [x] [7:8]
        int [1] [10:11]
      field [13:17]
        ident [y] [13:14]
        int [2] [16:17]
    @ [0] [19:27]
      ident [attr2] [20:25]
    @ [1] [28:47]
      ident [attr3] [29:34]
      field [38:45]
        ident [x] [38:39]
        string ["z"] [41:44]
    ident [test] [51:55]
    sig [0->1] [55:64]
      ident [int] [61:64]

M pkg/compiler/testdata/parser/fn_expr_stmt.snow.want => pkg/compiler/testdata/parser/fn_expr_stmt.snow.want +3 -3
@@ 1,11 1,11 @@
file [0:27]
file [1, #0] [0:27]
  fn [0:27]
    ident [test] [3:7]
    sig [7:15]
    sig [1->0] [7:15]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
    block [16:27]
    block [1] [16:27]
      expr [20:25]
        binary [+] [20:25]
          ident [x] [20:21]

A pkg/compiler/testdata/parser/fn_extern.snow => pkg/compiler/testdata/parser/fn_extern.snow +2 -0
@@ 0,0 1,2 @@
@extern{a: "x", b: 1}
fn test()

A pkg/compiler/testdata/parser/fn_extern.snow.err => pkg/compiler/testdata/parser/fn_extern.snow.err +0 -0
A pkg/compiler/testdata/parser/fn_extern.snow.want => pkg/compiler/testdata/parser/fn_extern.snow.want +12 -0
@@ 0,0 1,12 @@
file [1, #0] [0:31]
  fn [0:31]
    @ [2] [0:21]
      ident [extern] [1:7]
      field [8:15]
        ident [a] [8:9]
        string ["x"] [11:14]
      field [16:20]
        ident [b] [16:17]
        int [1] [19:20]
    ident [test] [25:29]
    sig [0->0] [29:31]

M pkg/compiler/testdata/parser/fn_mul.snow.want => pkg/compiler/testdata/parser/fn_mul.snow.want +3 -3
@@ 1,7 1,7 @@
file [0:49]
file [1, #0] [0:49]
  fn [0:49]
    ident [test] [3:7]
    sig [7:30]
    sig [2->1] [7:30]
      param [8:15]
        ident [x] [8:9]
        ident [int] [11:14]


@@ 9,7 9,7 @@ file [0:49]
        ident [y] [16:17]
        ident [int] [19:22]
      ident [int] [27:30]
    block [31:49]
    block [1] [31:49]
      return [35:47]
        binary [*] [42:47]
          ident [x] [42:43]

M pkg/compiler/testdata/parser/fn_naked_return.snow.want => pkg/compiler/testdata/parser/fn_naked_return.snow.want +3 -3
@@ 1,12 1,12 @@
file [0:40]
file [1, #0] [0:40]
  fn [0:40]
    ident [print] [3:8]
    sig [8:27]
    sig [2->0] [8:27]
      param [9:16]
        ident [x] [9:10]
        ident [int] [12:15]
      param [17:26]
        ident [y] [17:18]
        ident [string] [20:26]
    block [28:40]
    block [1] [28:40]
      return [32:38]

M pkg/compiler/testdata/parser/fn_nested_blocks.snow.want => pkg/compiler/testdata/parser/fn_nested_blocks.snow.want +8 -8
@@ 1,10 1,10 @@
file [0:54]
file [1, #0] [0:54]
  fn [0:54]
    ident [blocks] [3:9]
    sig [9:11]
    block [12:54]
      block [16:52]
        block [17:51]
          block [24:46]
            block [25:45]
              block [35:37]
    sig [0->0] [9:11]
    block [1] [12:54]
      block [1] [16:52]
        block [1] [17:51]
          block [1] [24:46]
            block [1] [25:45]
              block [0] [35:37]

M pkg/compiler/testdata/parser/fn_nested_paren.snow.want => pkg/compiler/testdata/parser/fn_nested_paren.snow.want +3 -3
@@ 1,11 1,11 @@
file [0:29]
file [1, #0] [0:29]
  fn [0:29]
    ident [test] [3:7]
    sig [7:15]
    sig [1->0] [7:15]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
    block [16:29]
    block [1] [16:29]
      expr [20:27]
        paren [20:27]
          paren [21:26]

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

M pkg/compiler/testdata/parser/fn_nested_unary.snow.want => pkg/compiler/testdata/parser/fn_nested_unary.snow.want +3 -3
@@ 1,12 1,12 @@
file [0:46]
file [1, #0] [0:46]
  fn [0:46]
    ident [test] [3:7]
    sig [7:22]
    sig [1->1] [7:22]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
      ident [int] [19:22]
    block [23:46]
    block [1] [23:46]
      return [27:44]
        unary [-] [34:44]
          unary [+] [35:44]

M pkg/compiler/testdata/parser/fn_paren_expr.snow.want => pkg/compiler/testdata/parser/fn_paren_expr.snow.want +3 -3
@@ 1,7 1,7 @@
file [0:66]
file [1, #0] [0:66]
  fn [0:66]
    ident [test] [3:7]
    sig [7:30]
    sig [2->1] [7:30]
      param [8:15]
        ident [x] [8:9]
        ident [int] [11:14]


@@ 9,7 9,7 @@ file [0:66]
        ident [y] [16:17]
        ident [int] [19:22]
      ident [int] [27:30]
    block [31:66]
    block [1] [31:66]
      return [35:64]
        binary [*] [42:64]
          paren [42:50]

M pkg/compiler/testdata/parser/fn_return_return.snow.want => pkg/compiler/testdata/parser/fn_return_return.snow.want +3 -3
@@ 1,8 1,8 @@
file [0:29]
file [1, #0] [0:29]
  fn [0:29]
    ident [test] [3:7]
    sig [7:9]
    block [10:29]
    sig [0->0] [7:9]
    block [2] [10:29]
      return [14:21]
        bad expr [21:21]
      return [21:27]

M pkg/compiler/testdata/parser/fn_unary.snow.want => pkg/compiler/testdata/parser/fn_unary.snow.want +3 -3
@@ 1,11 1,11 @@
file [0:31]
file [1, #0] [0:31]
  fn [0:31]
    ident [test] [3:7]
    sig [7:15]
    sig [1->0] [7:15]
      param [8:14]
        ident [x] [8:9]
        ident [int] [11:14]
    block [16:31]
    block [1] [16:31]
      return [20:29]
        unary [-] [27:29]
          ident [x] [28:29]

M pkg/compiler/testdata/parser/let.snow.want => pkg/compiler/testdata/parser/let.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:10]
file [1, #0] [0:10]
  let: [0:10]
    ident [x] [4:5]
    ident [int] [7:10]

M pkg/compiler/testdata/parser/let_full.snow.want => pkg/compiler/testdata/parser/let_full.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:21]
file [1, #0] [0:21]
  let:= [0:21]
    ident [x] [4:5]
    ident [string] [7:13]

M pkg/compiler/testdata/parser/let_infer.snow.want => pkg/compiler/testdata/parser/let_infer.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:11]
file [1, #0] [0:11]
  let= [0:11]
    ident [x] [4:5]
    string ["a"] [8:11]

M pkg/compiler/testdata/parser/let_question.snow.want => pkg/compiler/testdata/parser/let_question.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:18]
file [1, #0] [0:18]
  let: [0:18]
    ident [results?] [4:12]
    ident [bool] [14:18]

M pkg/compiler/testdata/parser/return.snow.want => pkg/compiler/testdata/parser/return.snow.want +1 -1
@@ 1,2 1,2 @@
file [0:7]
file [1, #0] [0:7]
  bad stmt [0:7]

M pkg/compiler/testdata/parser/return_return.snow.want => pkg/compiler/testdata/parser/return_return.snow.want +1 -1
@@ 1,2 1,2 @@
file [0:14]
file [1, #0] [0:14]
  bad stmt [0:14]

M pkg/compiler/testdata/parser/var.snow.want => pkg/compiler/testdata/parser/var.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:10]
file [1, #0] [0:10]
  var: [0:10]
    ident [x] [4:5]
    ident [int] [7:10]

M pkg/compiler/testdata/parser/var_bang.snow.want => pkg/compiler/testdata/parser/var_bang.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:17]
file [1, #0] [0:17]
  var= [0:17]
    ident [ready!] [4:10]
    ident [true] [13:17]

M pkg/compiler/testdata/parser/var_comma_for_semi.snow.want => pkg/compiler/testdata/parser/var_comma_for_semi.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:10]
file [1, #0] [0:10]
  var: [0:10]
    ident [x] [4:5]
    ident [int] [7:10]

M pkg/compiler/testdata/parser/var_full.snow.want => pkg/compiler/testdata/parser/var_full.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:16]
file [1, #0] [0:16]
  var:= [0:16]
    ident [x] [4:5]
    ident [int] [7:10]

M pkg/compiler/testdata/parser/var_infer.snow.want => pkg/compiler/testdata/parser/var_infer.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:10]
file [1, #0] [0:10]
  var= [0:10]
    ident [x] [4:5]
    int [10] [8:10]

M pkg/compiler/testdata/parser/var_invalid_type.snow.want => pkg/compiler/testdata/parser/var_invalid_type.snow.want +1 -1
@@ 1,4 1,4 @@
file [0:7]
file [1, #0] [0:7]
  var: [0:7]
    ident [x] [4:5]
    ident [] [7:7]

M pkg/compiler/token.go => pkg/compiler/token.go +2 -0
@@ 34,6 34,7 @@ const (
	Rbrace    // }
	Colon     // :
	Rarrow    // ->
	At        // @
	Semicolon // ;
	opEnd



@@ 69,6 70,7 @@ var tokens = [...]string{
	Rbrace:    "}",
	Colon:     ":",
	Rarrow:    "->",
	At:        "@",
	Semicolon: ";",

	Let:    "let",

M pkg/compiler/visitor.go => pkg/compiler/visitor.go +19 -0
@@ 21,6 21,9 @@ func walk(v visitor, node node) {
		}

	case *fnDef:
		for _, attr := range n.attrs {
			walk(v, attr)
		}
		if n.name != nil {
			walk(v, n.name)
		}


@@ 52,6 55,22 @@ func walk(v visitor, node node) {
			walk(v, stmt)
		}

	case *attr:
		if n.name != nil {
			walk(v, n.name)
		}
		for _, field := range n.fields {
			walk(v, field)
		}

	case *keyValue:
		if n.key != nil {
			walk(v, n.key)
		}
		if n.value != nil {
			walk(v, n.value)
		}

	case *varDef:
		if n.name != nil {
			walk(v, n.name)

M pkg/grammar/grammar.ebnf => pkg/grammar/grammar.ebnf +2 -2
@@ 1,13 1,13 @@
// NOTE: comment production omitted for brevity.

SourceFile = { TopLevelStmt } .

// TODO(VarDef): eventually support groups of var/let definitions.
// TODO(ArgsList): eventually support grouping multiple argument names of same type
// TODO: type will not be just an identifier, more complex (e.g. func type, tuple type, generics, etc.)
// TODO(string): support a raw string mode, escapes like Go? or an extendable prefix-based literal? (e.g. r"a\b\c")
// TODO: char/rune literal type?

SourceFile = { TopLevelStmt } .

TopLevelStmt = VarDef | FuncDef | FuncDecl .
Stmt = TopLevelStmt | SimpleStmt | Block | ReturnStmt .
SimpleStmt = ExprStmt | AssignStmt .