// Copyright 2017 The Wuffs Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package parse // TODO: write a formal grammar for the language. import ( "fmt" a "github.com/google/wuffs/lang/ast" t "github.com/google/wuffs/lang/token" ) type Options struct { AllowBuiltInNames bool AllowDoubleUnderscoreNames bool } func isDoubleUnderscore(s string) bool { return len(s) >= 2 && s[0] == '_' && s[1] == '_' } func Parse(tm *t.Map, filename string, src []t.Token, opts *Options) (*a.File, error) { p := &parser{ tm: tm, filename: filename, src: src, } if len(src) > 0 { p.lastLine = src[len(src)-1].Line } if opts != nil { p.opts = *opts } return p.parseFile() } func ParseExpr(tm *t.Map, filename string, src []t.Token, opts *Options) (*a.Expr, error) { p := &parser{ tm: tm, filename: filename, src: src, } if len(src) > 0 { p.lastLine = src[len(src)-1].Line } if opts != nil { p.opts = *opts } return p.parseExpr() } type parser struct { tm *t.Map filename string src []t.Token opts Options lastLine uint32 funcEffect a.Effect allowVar bool } func (p *parser) line() uint32 { if len(p.src) != 0 { return p.src[0].Line } return p.lastLine } func (p *parser) peek1() t.ID { if len(p.src) > 0 { return p.src[0].ID } return 0 } func (p *parser) parseFile() (*a.File, error) { topLevelDecls := []*a.Node(nil) for len(p.src) > 0 { d, err := p.parseTopLevelDecl() if err != nil { return nil, err } topLevelDecls = append(topLevelDecls, d) } return a.NewFile(p.filename, topLevelDecls), nil } func (p *parser) parseTopLevelDecl() (*a.Node, error) { flags := a.Flags(0) line := p.src[0].Line switch k := p.peek1(); k { case t.IDUse: p.src = p.src[1:] path := p.peek1() if !path.IsStrLiteral(p.tm) { got := p.tm.ByID(path) return nil, fmt.Errorf(`parse: expected string literal, got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return a.NewUse(p.filename, line, path).AsNode(), nil case t.IDPub: flags |= a.FlagsPublic fallthrough case t.IDPri: p.src = p.src[1:] switch p.peek1() { case t.IDConst: p.src = p.src[1:] id, err := p.parseIdent() if err != nil { return nil, err } // TODO: check AllowDoubleUnderscoreNames? typ, err := p.parseTypeExpr() if err != nil { return nil, err } if p.peek1() != t.IDEq { return nil, fmt.Errorf(`parse: const %q has no value at %s:%d`, p.tm.ByID(id), p.filename, p.line()) } p.src = p.src[1:] value, err := p.parsePossibleListExpr() if err != nil { return nil, err } if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return a.NewConst(flags, p.filename, line, id, typ, value).AsNode(), nil case t.IDFunc: p.src = p.src[1:] id0, id1, err := p.parseQualifiedIdent() if err != nil { return nil, err } if !p.opts.AllowBuiltInNames { switch id1 { case t.IDInitialize, t.IDReset: return nil, fmt.Errorf(`parse: cannot have a method named %q at %s:%d`, id1.Str(p.tm), p.filename, p.line()) } } // TODO: should we require id0 != 0? In other words, always methods // (attached to receivers) and never free standing functions? if !p.opts.AllowDoubleUnderscoreNames && isDoubleUnderscore(p.tm.ByID(id1)) { return nil, fmt.Errorf(`parse: double-underscore %q used for func name at %s:%d`, p.tm.ByID(id1), p.filename, p.line()) } p.funcEffect = p.parseEffect() flags |= p.funcEffect.AsFlags() argFields, err := p.parseList(t.IDCloseParen, (*parser).parseFieldNode) if err != nil { return nil, err } out := (*a.TypeExpr)(nil) if p.peek1() != t.IDOpenCurly { out, err = p.parseTypeExpr() if err != nil { return nil, err } } asserts := []*a.Node(nil) if p.peek1() == t.IDComma { p.src = p.src[1:] asserts, err = p.parseList(t.IDOpenCurly, (*parser).parseAssertNode) if err != nil { return nil, err } if err := p.assertsSorted(asserts); err != nil { return nil, err } } p.allowVar = true body, err := p.parseBlock() if err != nil { return nil, err } p.allowVar = false if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] p.funcEffect = 0 in := a.NewStruct(0, p.filename, line, t.IDArgs, argFields) return a.NewFunc(flags, p.filename, line, id0, id1, in, out, asserts, body).AsNode(), nil case t.IDStatus: p.src = p.src[1:] message := p.peek1() if !message.IsStrLiteral(p.tm) { got := p.tm.ByID(message) return nil, fmt.Errorf(`parse: expected string literal, got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return a.NewStatus(flags, p.filename, line, message).AsNode(), nil case t.IDStruct: p.src = p.src[1:] name, err := p.parseIdent() if err != nil { return nil, err } if !p.opts.AllowDoubleUnderscoreNames && isDoubleUnderscore(p.tm.ByID(name)) { return nil, fmt.Errorf(`parse: double-underscore %q used for struct name at %s:%d`, p.tm.ByID(name), p.filename, p.line()) } if p.peek1() == t.IDQuestion { flags |= a.FlagsClassy p.src = p.src[1:] } fields, err := p.parseList(t.IDCloseParen, (*parser).parseFieldNode) if err != nil { return nil, err } if x := p.peek1(); x == t.IDOpenParen { extraFields, err := p.parseList(t.IDCloseParen, (*parser).parseExtraFieldNode) if err != nil { return nil, err } fields = append(fields, extraFields...) } if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return a.NewStruct(flags, p.filename, line, name, fields).AsNode(), nil } } return nil, fmt.Errorf(`parse: unrecognized top level declaration at %s:%d`, p.filename, line) } // parseQualifiedIdent parses "foo.bar" or "bar". func (p *parser) parseQualifiedIdent() (t.ID, t.ID, error) { x, err := p.parseIdent() if err != nil { return 0, 0, err } if p.peek1() != t.IDDot { return 0, x, nil } p.src = p.src[1:] y, err := p.parseIdent() if err != nil { return 0, 0, err } return x, y, nil } func (p *parser) parseIdent() (t.ID, error) { if len(p.src) == 0 { return 0, fmt.Errorf(`parse: expected identifier at %s:%d`, p.filename, p.line()) } x := p.src[0] if !x.ID.IsIdent(p.tm) { got := p.tm.ByID(x.ID) return 0, fmt.Errorf(`parse: expected identifier, got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return x.ID, nil } func (p *parser) parseList(stop t.ID, parseElem func(*parser) (*a.Node, error)) ([]*a.Node, error) { if stop == t.IDCloseParen { if x := p.peek1(); x != t.IDOpenParen { return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`, p.tm.ByID(x), p.filename, p.line()) } p.src = p.src[1:] } ret := []*a.Node(nil) for len(p.src) > 0 { if p.src[0].ID == stop { if stop == t.IDCloseParen || stop == t.IDCloseBracket { p.src = p.src[1:] } return ret, nil } elem, err := parseElem(p) if err != nil { return nil, err } ret = append(ret, elem) switch x := p.peek1(); x { case stop: if stop == t.IDCloseParen || stop == t.IDCloseBracket { p.src = p.src[1:] } return ret, nil case t.IDComma: p.src = p.src[1:] default: return nil, fmt.Errorf(`parse: expected %q, got %q at %s:%d`, p.tm.ByID(stop), p.tm.ByID(x), p.filename, p.line()) } } return nil, fmt.Errorf(`parse: expected %q at %s:%d`, p.tm.ByID(stop), p.filename, p.line()) } func (p *parser) parseFieldNode() (*a.Node, error) { return p.parseFieldNode1(0) } func (p *parser) parseExtraFieldNode() (*a.Node, error) { n, err := p.parseFieldNode1(a.FlagsPrivateData) if err != nil { return nil, err } typ := n.AsField().XType() for typ.Decorator() == t.IDArray { typ = typ.Inner() } if (typ.Decorator() != 0) || (typ.QID()[0] == t.IDBase) && (!typ.IsNumType() || typ.IsRefined()) { return nil, fmt.Errorf(`parse: invalid extra-field type %q at %s:%d`, n.AsField().XType().Str(p.tm), p.filename, p.line()) } return n, nil } func (p *parser) parseFieldNode1(flags a.Flags) (*a.Node, error) { name, err := p.parseIdent() if err != nil { return nil, err } typ, err := p.parseTypeExpr() if err != nil { return nil, err } if pkg := typ.Innermost().QID()[0]; (pkg != 0) && (pkg != t.IDBase) { flags |= a.FlagsPrivateData } return a.NewField(flags, name, typ).AsNode(), nil } func (p *parser) parseTypeExpr() (*a.TypeExpr, error) { if x := p.peek1(); x == t.IDNptr || x == t.IDPtr { p.src = p.src[1:] rhs, err := p.parseTypeExpr() if err != nil { return nil, err } return a.NewTypeExpr(x, 0, 0, nil, nil, rhs), nil } decorator, arrayLength := t.ID(0), (*a.Expr)(nil) switch p.peek1() { case t.IDArray: decorator = t.IDArray p.src = p.src[1:] if x := p.peek1(); x != t.IDOpenBracket { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "[", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] var err error arrayLength, err = p.parseExpr() if err != nil { return nil, err } if x := p.peek1(); x != t.IDCloseBracket { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "]", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] case t.IDSlice: decorator = t.IDSlice p.src = p.src[1:] case t.IDTable: decorator = t.IDTable p.src = p.src[1:] } if decorator != 0 { rhs, err := p.parseTypeExpr() if err != nil { return nil, err } return a.NewTypeExpr(decorator, 0, 0, arrayLength.AsNode(), nil, rhs), nil } pkg, name, err := p.parseQualifiedIdent() if err != nil { return nil, err } lhs, mhs := (*a.Expr)(nil), (*a.Expr)(nil) if p.peek1() == t.IDOpenBracket { _, lhs, mhs, err = p.parseBracket(t.IDDotDot) if err != nil { return nil, err } } return a.NewTypeExpr(0, pkg, name, lhs.AsNode(), mhs, nil), nil } // parseBracket parses "[i:j]", "[i:]", "[:j]" and "[:]". A double dot replaces // the colon if sep is t.IDDotDot instead of t.IDColon. If sep is t.IDColon, it // also parses "[x]". The returned op is sep for a range or refinement and // t.IDOpenBracket for an index. func (p *parser) parseBracket(sep t.ID) (op t.ID, ei *a.Expr, ej *a.Expr, err error) { if x := p.peek1(); x != t.IDOpenBracket { got := p.tm.ByID(x) return 0, nil, nil, fmt.Errorf(`parse: expected "[", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if p.peek1() != sep { ei, err = p.parseExpr() if err != nil { return 0, nil, nil, err } } switch x := p.peek1(); { case x == sep: p.src = p.src[1:] case x == t.IDCloseBracket && sep == t.IDColon: p.src = p.src[1:] return t.IDOpenBracket, nil, ei, nil default: extra := `` if sep == t.IDColon { extra = ` or "]"` } got := p.tm.ByID(x) return 0, nil, nil, fmt.Errorf(`parse: expected %q%s, got %q at %s:%d`, p.tm.ByID(sep), extra, got, p.filename, p.line()) } if p.peek1() != t.IDCloseBracket { ej, err = p.parseExpr() if err != nil { return 0, nil, nil, err } } if x := p.peek1(); x != t.IDCloseBracket { got := p.tm.ByID(x) return 0, nil, nil, fmt.Errorf(`parse: expected "]", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return sep, ei, ej, nil } func (p *parser) parseBlock() ([]*a.Node, error) { if x := p.peek1(); x != t.IDOpenCurly { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "{", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] block := []*a.Node(nil) for len(p.src) > 0 { if p.src[0].ID == t.IDCloseCurly { p.src = p.src[1:] return block, nil } s, err := p.parseStatement() if err != nil { return nil, err } block = append(block, s) if x := p.peek1(); x != t.IDSemicolon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] } return nil, fmt.Errorf(`parse: expected "}" at %s:%d`, p.filename, p.line()) } func (p *parser) assertsSorted(asserts []*a.Node) error { seenInv, seenPost := false, false for _, a := range asserts { switch a.AsAssert().Keyword() { case t.IDAssert: return fmt.Errorf(`parse: assertion chain cannot contain "assert", `+ `only "pre", "inv" and "post" at %s:%d`, p.filename, p.line()) case t.IDPre: if seenPost || seenInv { break } continue case t.IDInv: if seenPost { break } seenInv = true continue default: seenPost = true continue } return fmt.Errorf(`parse: assertion chain not in "pre", "inv", "post" order at %s:%d`, p.filename, p.line()) } return nil } func (p *parser) parseAssertNode() (*a.Node, error) { switch x := p.peek1(); x { case t.IDAssert, t.IDPre, t.IDInv, t.IDPost: p.src = p.src[1:] condition, err := p.parseExpr() if err != nil { return nil, err } if condition.Effect() != 0 { return nil, fmt.Errorf(`parse: assert-condition %q is not effect-free at %s:%d`, condition.Str(p.tm), p.filename, p.line()) } reason, args := t.ID(0), []*a.Node(nil) if p.peek1() == t.IDVia { p.src = p.src[1:] reason = p.peek1() if !reason.IsStrLiteral(p.tm) { got := p.tm.ByID(reason) return nil, fmt.Errorf(`parse: expected string literal, got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] args, err = p.parseList(t.IDCloseParen, (*parser).parseArgNode) if err != nil { return nil, err } } return a.NewAssert(x, condition, reason, args).AsNode(), nil } return nil, fmt.Errorf(`parse: expected "assert", "pre" or "post" at %s:%d`, p.filename, p.line()) } func (p *parser) parseStatement() (*a.Node, error) { line := uint32(0) if len(p.src) > 0 { line = p.src[0].Line } n, err := p.parseStatement1() if n != nil { n.AsRaw().SetFilenameLine(p.filename, line) if n.Kind() == a.KIterate { for _, o := range n.AsIterate().Assigns() { o.AsRaw().SetFilenameLine(p.filename, line) } } } return n, err } func (p *parser) parseLabel() (t.ID, error) { if p.peek1() == t.IDColon { p.src = p.src[1:] return p.parseIdent() } return 0, nil } func (p *parser) parseStatement1() (*a.Node, error) { x := p.peek1() if x == t.IDVar { if !p.allowVar { return nil, fmt.Errorf(`parse: var statement not at the top of a function at %s:%d`, p.filename, p.line()) } p.src = p.src[1:] return p.parseVarNode() } p.allowVar = false switch x { case t.IDAssert, t.IDPre, t.IDPost: return p.parseAssertNode() case t.IDBreak, t.IDContinue: p.src = p.src[1:] label, err := p.parseLabel() if err != nil { return nil, err } return a.NewJump(x, label).AsNode(), nil case t.IDIOBind, t.IDIOLimit: return p.parseIOBindNode() case t.IDIf: o, err := p.parseIf() return o.AsNode(), err case t.IDIterate: return p.parseIterateNode() case t.IDReturn, t.IDYield: p.src = p.src[1:] if x == t.IDYield { if !p.funcEffect.Coroutine() { return nil, fmt.Errorf(`parse: yield within non-coroutine at %s:%d`, p.filename, p.line()) } if p.peek1() != t.IDQuestion { return nil, fmt.Errorf(`parse: yield not followed by '?' at %s:%d`, p.filename, p.line()) } p.src = p.src[1:] } value, err := p.parseExpr() if err != nil { return nil, err } return a.NewRet(x, value).AsNode(), nil case t.IDWhile: p.src = p.src[1:] label, err := p.parseLabel() if err != nil { return nil, err } condition, err := p.parseExpr() if err != nil { return nil, err } if condition.Effect() != 0 { return nil, fmt.Errorf(`parse: while-condition %q is not effect-free at %s:%d`, condition.Str(p.tm), p.filename, p.line()) } asserts, err := p.parseAsserts() if err != nil { return nil, err } body, err := p.parseBlock() if err != nil { return nil, err } return a.NewWhile(label, condition, asserts, body).AsNode(), nil } return p.parseAssignNode() } func (p *parser) parseAssignNode() (*a.Node, error) { lhs := (*a.Expr)(nil) rhs, err := p.parseExpr() if err != nil { return nil, err } op := p.peek1() if op.IsAssign() { p.src = p.src[1:] lhs = rhs if lhs.Effect() != 0 { return nil, fmt.Errorf(`parse: assignment LHS %q is not effect-free at %s:%d`, lhs.Str(p.tm), p.filename, p.line()) } for l := lhs; l != nil; l = l.LHS().AsExpr() { switch l.Operator() { case 0: if id := l.Ident(); id.IsLiteral(p.tm) { return nil, fmt.Errorf(`parse: assignment LHS %q is a literal at %s:%d`, l.Str(p.tm), p.filename, p.line()) } else if id.IsCannotAssignTo() { if l == lhs { return nil, fmt.Errorf(`parse: cannot assign to %q at %s:%d`, id.Str(p.tm), p.filename, p.line()) } if !p.funcEffect.Impure() { return nil, fmt.Errorf(`parse: cannot assign to %q in a pure function at %s:%d`, lhs.Str(p.tm), p.filename, p.line()) } } case t.IDDot, t.IDOpenBracket: // No-op. default: return nil, fmt.Errorf(`parse: invalid assignment LHS %q at %s:%d`, lhs.Str(p.tm), p.filename, p.line()) } } rhs, err = p.parseExpr() if err != nil { return nil, err } if op == t.IDEqQuestion { if (rhs.Operator() != t.IDOpenParen) || (!rhs.Effect().Coroutine()) { return nil, fmt.Errorf(`parse: expected ?-function call after "=?", got %q at %s:%d`, rhs.Str(p.tm), p.filename, p.line()) } } } else { op = t.IDEq } if p.funcEffect.WeakerThan(rhs.Effect()) { return nil, fmt.Errorf(`parse: value %q's effect %q is stronger than the func's effect %q at %s:%d`, rhs.Str(p.tm), rhs.Effect(), p.funcEffect, p.filename, p.line()) } return a.NewAssign(op, lhs, rhs).AsNode(), nil } func (p *parser) parseIterateAssignNode() (*a.Node, error) { n, err := p.parseAssignNode() if err != nil { return nil, err } o := n.AsAssign() if op := o.Operator(); op != t.IDEq { return nil, fmt.Errorf(`parse: expected "=", got %q at %s:%d`, op.Str(p.tm), p.filename, p.line()) } if lhs := o.LHS(); lhs.Operator() != 0 { return nil, fmt.Errorf(`parse: expected variable, got %q at %s:%d`, lhs.Str(p.tm), p.filename, p.line()) } if rhs := o.RHS(); rhs.Effect() != 0 { return nil, fmt.Errorf(`parse: value %q is not effect-free at %s:%d`, rhs.Str(p.tm), p.filename, p.line()) } return o.AsNode(), nil } func (p *parser) parseAsserts() ([]*a.Node, error) { asserts := []*a.Node(nil) if p.peek1() == t.IDComma { p.src = p.src[1:] var err error if asserts, err = p.parseList(t.IDOpenCurly, (*parser).parseAssertNode); err != nil { return nil, err } if err := p.assertsSorted(asserts); err != nil { return nil, err } } return asserts, nil } func (p *parser) parseIOBindNode() (*a.Node, error) { keyword := p.peek1() p.src = p.src[1:] if x := p.peek1(); x != t.IDOpenParen { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDIO { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "io", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDColon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] io, err := p.parseExpr() if err != nil { return nil, err } if io.Effect() != 0 { return nil, fmt.Errorf(`parse: argument %q is not effect-free at %s:%d`, io.Str(p.tm), p.filename, p.line()) } if x := p.peek1(); x != t.IDComma { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] arg1Name := t.IDData if keyword == t.IDIOLimit { arg1Name = t.IDLimit } if x := p.peek1(); x != arg1Name { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected %q, got %q at %s:%d`, arg1Name.Str(p.tm), got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDColon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] arg1, err := p.parseExpr() if err != nil { return nil, err } if arg1.Effect() != 0 { return nil, fmt.Errorf(`parse: argument %q is not effect-free at %s:%d`, io.Str(p.tm), p.filename, p.line()) } if x := p.peek1(); x != t.IDCloseParen { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] body, err := p.parseBlock() if err != nil { return nil, err } return a.NewIOBind(keyword, io, arg1, body).AsNode(), nil } func (p *parser) parseIf() (*a.If, error) { if x := p.peek1(); x != t.IDIf { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "if", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] condition, err := p.parseExpr() if err != nil { return nil, err } if condition.Effect() != 0 { return nil, fmt.Errorf(`parse: if-condition %q is not effect-free at %s:%d`, condition.Str(p.tm), p.filename, p.line()) } bodyIfTrue, err := p.parseBlock() if err != nil { return nil, err } elseIf, bodyIfFalse := (*a.If)(nil), ([]*a.Node)(nil) if p.peek1() == t.IDElse { p.src = p.src[1:] if p.peek1() == t.IDIf { elseIf, err = p.parseIf() if err != nil { return nil, err } } else { bodyIfFalse, err = p.parseBlock() if err != nil { return nil, err } } } return a.NewIf(condition, bodyIfTrue, bodyIfFalse, elseIf), nil } func (p *parser) parseIterateNode() (*a.Node, error) { if x := p.peek1(); x != t.IDIterate { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "iterate", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] label, err := p.parseLabel() if err != nil { return nil, err } assigns, err := p.parseList(t.IDCloseParen, (*parser).parseIterateAssignNode) if err != nil { return nil, err } n, err := p.parseIterateBlock(label, assigns) if err != nil { return nil, err } return n.AsNode(), nil } func (p *parser) parseIterateBlock(label t.ID, assigns []*a.Node) (*a.Iterate, error) { if x := p.peek1(); x != t.IDOpenParen { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDLength { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "length", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDColon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] length := p.peek1() if length.SmallPowerOf2Value() == 0 { return nil, fmt.Errorf(`parse: expected power-of-2 length count in [1..256], got %q at %s:%d`, p.tm.ByID(length), p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDComma { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDUnroll { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected "unroll", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDColon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] unroll := p.peek1() if unroll.SmallPowerOf2Value() == 0 { return nil, fmt.Errorf(`parse: expected power-of-2 unroll count in [1..256], got %q at %s:%d`, p.tm.ByID(unroll), p.filename, p.line()) } p.src = p.src[1:] if x := p.peek1(); x != t.IDCloseParen { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] asserts, err := p.parseAsserts() if err != nil { return nil, err } body, err := p.parseBlock() if err != nil { return nil, err } elseIterate := (*a.Iterate)(nil) if x := p.peek1(); x == t.IDElse { p.src = p.src[1:] elseIterate, err = p.parseIterateBlock(0, nil) if err != nil { return nil, err } } return a.NewIterate(label, assigns, length, unroll, asserts, body, elseIterate), nil } func (p *parser) parseArgNode() (*a.Node, error) { name, err := p.parseIdent() if err != nil { return nil, err } if x := p.peek1(); x != t.IDColon { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] value, err := p.parseExpr() if err != nil { return nil, err } if value.Effect() != 0 { return nil, fmt.Errorf(`parse: arg-value %q is not effect-free at %s:%d`, value.Str(p.tm), p.filename, p.line()) } return a.NewArg(name, value).AsNode(), nil } func (p *parser) parseIOBindExprNode() (*a.Node, error) { e, err := p.parseExpr() if err != nil { return nil, err } switch e.Operator() { case 0: return e.AsNode(), nil case t.IDDot: if lhs := e.LHS().AsExpr(); lhs.Operator() == 0 && lhs.Ident() == t.IDArgs { return e.AsNode(), nil } } return nil, fmt.Errorf(`parse: expected "args.something", got %q at %s:%d`, e.Str(p.tm), p.filename, p.line()) } func (p *parser) parseVarNode() (*a.Node, error) { id, err := p.parseIdent() if err != nil { return nil, err } typ, err := p.parseTypeExpr() if err != nil { return nil, err } return a.NewVar(id, typ).AsNode(), nil } func (p *parser) parsePossibleListExprNode() (*a.Node, error) { n, err := p.parsePossibleListExpr() if err != nil { return nil, err } return n.AsNode(), err } func (p *parser) parsePossibleListExpr() (*a.Expr, error) { // TODO: put the [ and ] parsing into parseExpr. if x := p.peek1(); x != t.IDOpenBracket { return p.parseExpr() } p.src = p.src[1:] args, err := p.parseList(t.IDCloseBracket, (*parser).parsePossibleListExprNode) if err != nil { return nil, err } return a.NewExpr(0, t.IDComma, 0, 0, nil, nil, nil, args), nil } func (p *parser) parseExpr() (*a.Expr, error) { e, err := p.parseExpr1() if err != nil { return nil, err } if e.SubExprHasEffect() { return nil, fmt.Errorf(`parse: expression %q has an effect-ful sub-expression at %s:%d`, e.Str(p.tm), p.filename, p.line()) } return e, nil } func (p *parser) parseExpr1() (*a.Expr, error) { lhs, err := p.parseOperand() if err != nil { return nil, err } if x := p.peek1(); x.IsBinaryOp() { p.src = p.src[1:] rhs := (*a.Node)(nil) if x == t.IDAs { o, err := p.parseTypeExpr() if err != nil { return nil, err } rhs = o.AsNode() } else { o, err := p.parseOperand() if err != nil { return nil, err } rhs = o.AsNode() } if !x.IsAssociativeOp() || x != p.peek1() { op := x.BinaryForm() if op == 0 { return nil, fmt.Errorf(`parse: internal error: no binary form for token 0x%02X`, x) } return a.NewExpr(0, op, 0, 0, lhs.AsNode(), nil, rhs, nil), nil } args := []*a.Node{lhs.AsNode(), rhs} for p.peek1() == x { p.src = p.src[1:] arg, err := p.parseOperand() if err != nil { return nil, err } args = append(args, arg.AsNode()) } op := x.AssociativeForm() if op == 0 { return nil, fmt.Errorf(`parse: internal error: no associative form for token 0x%02X`, x) } return a.NewExpr(0, op, 0, 0, nil, nil, nil, args), nil } return lhs, nil } func (p *parser) parseOperand() (*a.Expr, error) { switch x := p.peek1(); { case x.IsUnaryOp(): p.src = p.src[1:] rhs, err := p.parseOperand() if err != nil { return nil, err } op := x.UnaryForm() if op == 0 { return nil, fmt.Errorf(`parse: internal error: no unary form for token 0x%02X`, x) } return a.NewExpr(0, op, 0, 0, nil, nil, rhs.AsNode(), nil), nil case x.IsLiteral(p.tm): p.src = p.src[1:] return a.NewExpr(0, 0, 0, x, nil, nil, nil, nil), nil case x == t.IDOpenParen: p.src = p.src[1:] expr, err := p.parseExpr() if err != nil { return nil, err } if x := p.peek1(); x != t.IDCloseParen { got := p.tm.ByID(x) return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line()) } p.src = p.src[1:] return expr, nil } id, err := p.parseIdent() if err != nil { return nil, err } lhs := a.NewExpr(0, 0, 0, id, nil, nil, nil, nil) for first := true; ; first = false { flags := a.Flags(0) switch p.peek1() { default: return lhs, nil case t.IDExclam, t.IDQuestion: flags |= p.parseEffect().AsFlags() fallthrough case t.IDOpenParen: args, err := p.parseList(t.IDCloseParen, (*parser).parseArgNode) if err != nil { return nil, err } lhs = a.NewExpr(flags, t.IDOpenParen, 0, 0, lhs.AsNode(), nil, nil, args) case t.IDOpenBracket: id0, mhs, rhs, err := p.parseBracket(t.IDColon) if err != nil { return nil, err } lhs = a.NewExpr(0, id0, 0, 0, lhs.AsNode(), mhs.AsNode(), rhs.AsNode(), nil) case t.IDDot: p.src = p.src[1:] if x := p.peek1(); x.IsLiteral(p.tm) { if x.IsNumLiteral(p.tm) { return nil, fmt.Errorf(`parse: dot followed by numeric literal at %s:%d`, p.filename, p.line()) } if !first { return nil, fmt.Errorf(`parse: string literal %s has too many package qualifiers at %s:%d`, x.Str(p.tm), p.filename, p.line()) } p.src = p.src[1:] return a.NewExpr(0, 0, id, x, nil, nil, nil, nil), nil } selector, err := p.parseIdent() if err != nil { return nil, err } lhs = a.NewExpr(0, t.IDDot, 0, selector, lhs.AsNode(), nil, nil, nil) } } } func (p *parser) parseEffect() a.Effect { switch p.peek1() { case t.IDExclam: p.src = p.src[1:] return a.EffectImpure case t.IDQuestion: p.src = p.src[1:] return a.EffectImpureCoroutine } return 0 }