package parse import "fmt" // Parser is an embeddable type that contains parsing utilities. type Parser struct { // Lexer is the lexer that the parser will pull tokens from. This must // be set before use. Lexer Lexer // TokenNames assigns names to token kinds. This should be set before // use. TokenNames map[TokenKind] string // Token is the current token the parser is operating on. Token Token } // Expect checks the current token to see if it matches a list of token kind(s), // else it returns an error describing what it expected. func (this *Parser) Expect (allowed ...TokenKind) error { if !this.Token.Is(allowed...) { return Errorf ( this.Token.Position, "unexpected %s; expected %s", this.tokenName(this.Token.Kind), commaList(this.tokenNames(allowed...)...)) } return nil } // ExpectDesc is like Expect, but the expected entitie(s) are described // manually. This can be helpful when a large syntactical entity is expected and // the first token(s) of it would offer useless information to the user. func (this *Parser) ExpectDesc (description string, allowed ...TokenKind) error { if !this.Token.Is(allowed...) { return Errorf ( this.Token.Position, "unexpected %s; expected %s", this.tokenName(this.Token.Kind), description) } return nil } // ExpectNext is like Expect, but gets the next token first. func (this *Parser) ExpectNext (allowed ...TokenKind) error { err := this.Next() if err != nil { return err } return this.Expect(allowed...) } // ExpectNextDesc is like ExpectDesc, but gets the next token first. func (this *Parser) ExpectNextDesc (description string, allowed ...TokenKind) error { err := this.Next() if err != nil { return err } return this.ExpectDesc(description, allowed...) } // ExpectValue returns an error if the current token's value does not match the // allowed values. func (this *Parser) ExpectValue (kind TokenKind, allowed ...string) error { if !((this.Token.Is(kind) || kind == 0) && this.Token.ValueIs(allowed...)) { return Errorf ( this.Token.Position, "unexpected %s; expected %s", this.tokenName(this.Token.Kind), commaList(allowed)) } return nil } // ExpectValueDesc is like ExpectValue, but the expected value(s) are described // manually. func (this *Parser) ExpectValueDesc (description string, kind TokenKind, allowed ...string) error { if !this.Token.Is(kind) || !this.Token.ValueIs(allowed...) { return Errorf ( this.Token.Position, "unexpected %s; expected %s", this.tokenName(this.Token.Kind), description) } return nil } // Next consumes a token from the lexer, and sets it as the current token. func (this *Parser) Next () error { token, err := this.Lexer.Next() if err != nil { return err } this.Token = token return nil } // Kind returns the token kind of the current token. func (this *Parser) Kind () TokenKind { return this.Token.Kind } // Value returns the value of the current token. func (this *Parser) Value () string { return this.Token.Value } // Is returns whether or not the current token matches any of the given kinds. func (this *Parser) Is (allowed ...TokenKind) bool { return this.Token.Is(allowed...) } // ValueIs returns whether or not the current token matches any of the given // values. func (this *Parser) ValueIs (allowed ...string) bool { return this.Token.ValueIs(allowed...) } // Pos returns the position of the current token. func (this *Parser) Pos () Position { return this.Token.Position } // EOF returns whether or not the lexer has been exhausted. func (this *Parser) EOF () bool { return this.Token.EOF() } func (this *Parser) tokenName (kind TokenKind) string { if this.TokenNames != nil { if name, ok := this.TokenNames[kind]; ok { return name } } return fmt.Sprintf("TokenKind(%d)", kind) } func (this *Parser) tokenNames (kinds ...TokenKind) []string { names := make([]string, len(kinds)) for index, kind := range kinds { names[index] = this.tokenName(kind) } return names } func commaList[ELEMENT any] (items ...ELEMENT) string { list := "" switch { case len(items) == 1: list = fmt.Sprint(items[0]) case len(items) == 2: list = fmt.Sprint(items[0], " or ", items[1]) default: for index, item := range items { if index > 0 { list += ", " if index == len(items) - 1 { list += " or " } } list += fmt.Sprintf("%v", item) } } return list }