| // Copyright 2019 The Fuchsia 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 parser |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "text/scanner" |
| |
| "gidl/ir" |
| ) |
| |
| type Parser struct { |
| scanner scanner.Scanner |
| lookaheads []token |
| // All supported languages, used to validate bindings allowlist/denylist. |
| allLanguages ir.LanguageList |
| } |
| |
| func NewParser(name string, input io.Reader, allLanguages []string) *Parser { |
| var p Parser |
| p.scanner.Position.Filename = name |
| p.scanner.Init(input) |
| p.allLanguages = allLanguages |
| return &p |
| } |
| |
| func (p *Parser) Parse() (ir.All, error) { |
| var result ir.All |
| for !p.peekTokenKind(tEof) { |
| if err := p.parseSection(&result); err != nil { |
| return ir.All{}, err |
| } |
| } |
| // TODO(FIDL-754) Add validation checks for error codes after parsing. |
| return result, nil |
| } |
| |
| type tokenKind uint |
| |
| const ( |
| _ tokenKind = iota |
| tEof |
| tText |
| tString |
| tLacco |
| tRacco |
| tComma |
| tColon |
| tNeg |
| tLparen |
| tRparen |
| tEqual |
| tLsquare |
| tRsquare |
| ) |
| |
| var tokenKindStrings = []string{ |
| "<invalid>", |
| "<eof>", |
| "<text>", |
| "<string>", |
| "{", |
| "}", |
| ",", |
| ":", |
| "-", |
| "(", |
| ")", |
| "=", |
| "[", |
| "]", |
| } |
| |
| var ( |
| isUnitTokenKind = make(map[tokenKind]bool) |
| textToTokenKind = make(map[string]tokenKind) |
| ) |
| |
| func init() { |
| for index, text := range tokenKindStrings { |
| if strings.HasPrefix(text, "<") && strings.HasSuffix(text, ">") { |
| continue |
| } |
| kind := tokenKind(index) |
| isUnitTokenKind[kind] = true |
| textToTokenKind[text] = kind |
| } |
| } |
| |
| func (kind tokenKind) String() string { |
| if index := int(kind); index < len(tokenKindStrings) { |
| return tokenKindStrings[index] |
| } |
| return fmt.Sprintf("%d", kind) |
| } |
| |
| type token struct { |
| kind tokenKind |
| value string |
| line, column int |
| } |
| |
| func (t token) String() string { |
| if isUnitTokenKind[t.kind] { |
| return t.kind.String() |
| } else { |
| return t.value |
| } |
| } |
| |
| type bodyElement uint |
| |
| const ( |
| _ bodyElement = iota |
| isType |
| isValue |
| isBytes |
| isErr |
| isBindingsAllowlist |
| isBindingsDenylist |
| ) |
| |
| func (kind bodyElement) String() string { |
| switch kind { |
| case isType: |
| return "type" |
| case isValue: |
| return "value" |
| case isBytes: |
| return "bytes" |
| case isErr: |
| return "err" |
| case isBindingsAllowlist: |
| return "bindings_allowlist" |
| case isBindingsDenylist: |
| return "bindings_denylist" |
| default: |
| panic("unsupported kind") |
| } |
| } |
| |
| type body struct { |
| Type string |
| Value ir.Value |
| Encodings []ir.Encoding |
| Err ir.ErrorCode |
| BindingsAllowlist *ir.LanguageList |
| BindingsDenylist *ir.LanguageList |
| } |
| |
| type sectionMetadata struct { |
| requiredKinds map[bodyElement]bool |
| optionalKinds map[bodyElement]bool |
| setter func(name string, body body, all *ir.All) |
| } |
| |
| var sections = map[string]sectionMetadata{ |
| "success": { |
| requiredKinds: map[bodyElement]bool{isValue: true, isBytes: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| encodeSuccess := ir.EncodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: body.Encodings, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.EncodeSuccess = append(all.EncodeSuccess, encodeSuccess) |
| decodeSuccess := ir.DecodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: body.Encodings, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeSuccess = append(all.DecodeSuccess, decodeSuccess) |
| }, |
| }, |
| "encode_success": { |
| requiredKinds: map[bodyElement]bool{isValue: true, isBytes: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.EncodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: body.Encodings, |
| BindingsAllowlist: body.BindingsAllowlist, |
| } |
| all.EncodeSuccess = append(all.EncodeSuccess, result) |
| }, |
| }, |
| "decode_success": { |
| requiredKinds: map[bodyElement]bool{isValue: true, isBytes: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.DecodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: body.Encodings, |
| BindingsAllowlist: body.BindingsAllowlist, |
| } |
| all.DecodeSuccess = append(all.DecodeSuccess, result) |
| }, |
| }, |
| "encode_failure": { |
| requiredKinds: map[bodyElement]bool{isValue: true, isErr: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.EncodeFailure{ |
| Name: name, |
| Value: body.Value, |
| WireFormats: ir.AllWireFormats(), |
| Err: body.Err, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.EncodeFailure = append(all.EncodeFailure, result) |
| }, |
| }, |
| "decode_failure": { |
| requiredKinds: map[bodyElement]bool{isType: true, isBytes: true, isErr: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.DecodeFailure{ |
| Name: name, |
| Type: body.Type, |
| Encodings: body.Encodings, |
| Err: body.Err, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeFailure = append(all.DecodeFailure, result) |
| }, |
| }, |
| "benchmark": { |
| requiredKinds: map[bodyElement]bool{isValue: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| encodeBenchmark := ir.EncodeBenchmark{ |
| Name: name, |
| Value: body.Value, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.EncodeBenchmark = append(all.EncodeBenchmark, encodeBenchmark) |
| decodeBenchmark := ir.DecodeBenchmark{ |
| Name: name, |
| Value: body.Value, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeBenchmark = append(all.DecodeBenchmark, decodeBenchmark) |
| }, |
| }, |
| "encode_benchmark": { |
| requiredKinds: map[bodyElement]bool{isValue: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.EncodeBenchmark{ |
| Name: name, |
| Value: body.Value, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.EncodeBenchmark = append(all.EncodeBenchmark, result) |
| }, |
| }, |
| "decode_benchmark": { |
| requiredKinds: map[bodyElement]bool{isValue: true}, |
| optionalKinds: map[bodyElement]bool{isBindingsAllowlist: true, isBindingsDenylist: true}, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.DecodeBenchmark{ |
| Name: name, |
| Value: body.Value, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeBenchmark = append(all.DecodeBenchmark, result) |
| }, |
| }, |
| } |
| |
| func (p *Parser) parseSection(all *ir.All) error { |
| section, name, err := p.parsePreamble() |
| if err != nil { |
| return err |
| } |
| body, err := p.parseBody(section.requiredKinds, section.optionalKinds) |
| if err != nil { |
| return err |
| } |
| section.setter(name, body, all) |
| return nil |
| } |
| |
| func (p *Parser) parsePreamble() (sectionMetadata, string, error) { |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return sectionMetadata{}, "", p.failExpectedToken(tText, tok) |
| } |
| |
| section, ok := sections[tok.value] |
| if !ok { |
| return sectionMetadata{}, "", p.newParseError(tok, "unknown section %s", tok.value) |
| } |
| |
| tok, ok = p.consumeToken(tLparen) |
| if !ok { |
| return sectionMetadata{}, "", p.failExpectedToken(tLparen, tok) |
| } |
| |
| tok, ok = p.consumeToken(tString) |
| if !ok { |
| return sectionMetadata{}, "", p.failExpectedToken(tString, tok) |
| } |
| name := tok.value |
| |
| tok, ok = p.consumeToken(tRparen) |
| if !ok { |
| return sectionMetadata{}, "", p.failExpectedToken(tRparen, tok) |
| } |
| |
| return section, name, nil |
| } |
| |
| func (p *Parser) parseBody(requiredKinds map[bodyElement]bool, optionalKinds map[bodyElement]bool) (body, error) { |
| var ( |
| result body |
| parsedKinds = make(map[bodyElement]bool) |
| bodyTok = p.peekToken() |
| ) |
| err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| return p.parseSingleBodyElement(&result, parsedKinds) |
| }) |
| if err != nil { |
| return result, err |
| } |
| for requiredKind := range requiredKinds { |
| if !parsedKinds[requiredKind] { |
| return result, p.newParseError(bodyTok, "missing required parameter '%s'", requiredKind) |
| } |
| } |
| for parsedKind := range parsedKinds { |
| if !requiredKinds[parsedKind] && !optionalKinds[parsedKind] { |
| return result, p.newParseError(bodyTok, "parameter '%s' does not apply to element", parsedKind) |
| } |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseSingleBodyElement(result *body, all map[bodyElement]bool) error { |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tok) |
| } |
| if tok, ok := p.consumeToken(tEqual); !ok { |
| return p.failExpectedToken(tEqual, tok) |
| } |
| var kind bodyElement |
| switch tok.value { |
| case "type": |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tok) |
| } |
| result.Type = tok.value |
| kind = isType |
| case "value": |
| val, err := p.parseValue() |
| if err != nil { |
| return err |
| } |
| result.Value = val |
| kind = isValue |
| case "bytes": |
| encodings, err := p.parseByteSection() |
| if err != nil { |
| return err |
| } |
| result.Encodings = encodings |
| kind = isBytes |
| case "err": |
| errorCode, err := p.parseErrorCode() |
| if err != nil { |
| return err |
| } |
| result.Err = errorCode |
| kind = isErr |
| case "bindings_allowlist": |
| languages, err := p.parseLanguageList() |
| if err != nil { |
| return err |
| } |
| result.BindingsAllowlist = &languages |
| kind = isBindingsAllowlist |
| case "bindings_denylist": |
| languages, err := p.parseLanguageList() |
| if err != nil { |
| return err |
| } |
| result.BindingsDenylist = &languages |
| kind = isBindingsDenylist |
| default: |
| return p.newParseError(tok, "must be type, value, bytes, err, bindings_allowlist or bindings_denylist") |
| } |
| if all[kind] { |
| return p.newParseError(tok, "duplicate %s found", kind) |
| } |
| all[kind] = true |
| return nil |
| } |
| |
| func (p *Parser) parseValue() (interface{}, error) { |
| switch p.peekToken().kind { |
| case tText: |
| tok := p.nextToken() |
| if '0' <= tok.value[0] && tok.value[0] <= '9' { |
| return parseNum(tok, false) |
| } |
| if tok.value == "null" { |
| return nil, nil |
| } |
| if tok.value == "true" { |
| return true, nil |
| } |
| if tok.value == "false" { |
| return false, nil |
| } |
| return p.parseRecord(tok.value) |
| case tLsquare: |
| return p.parseSlice() |
| case tString: |
| return p.nextToken().value, nil |
| case tNeg: |
| p.nextToken() |
| if tok, ok := p.consumeToken(tText); !ok { |
| return nil, p.failExpectedToken(tText, tok) |
| } else { |
| return parseNum(tok, true) |
| } |
| default: |
| return nil, p.newParseError(p.peekToken(), "expected value") |
| } |
| } |
| |
| func parseNum(tok token, neg bool) (interface{}, error) { |
| if strings.Contains(tok.value, ".") { |
| val, err := strconv.ParseFloat(tok.value, 64) |
| if err != nil { |
| return nil, err |
| } |
| if neg { |
| return -val, nil |
| } else { |
| return val, nil |
| } |
| } else { |
| val, err := strconv.ParseUint(tok.value, 0, 64) |
| if err != nil { |
| return nil, err |
| } |
| if neg { |
| return -int64(val), nil |
| } else { |
| return uint64(val), nil |
| } |
| } |
| } |
| |
| func (p *Parser) parseRecord(name string) (interface{}, error) { |
| obj := ir.Record{Name: name} |
| err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| tokFieldName, ok := p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tokFieldName) |
| } |
| if tok, ok := p.consumeToken(tColon); !ok { |
| return p.failExpectedToken(tColon, tok) |
| } |
| val, err := p.parseValue() |
| if err != nil { |
| return err |
| } |
| obj.Fields = append(obj.Fields, ir.Field{Key: decodeFieldKey(tokFieldName.value), Value: val}) |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return obj, nil |
| } |
| |
| // Field can be referenced by either name or ordinal. |
| func decodeFieldKey(field string) ir.FieldKey { |
| if ord, err := strconv.ParseInt(field, 0, 64); err == nil { |
| return ir.FieldKey{UnknownOrdinal: uint64(ord)} |
| } |
| return ir.FieldKey{Name: field} |
| } |
| |
| func (p *Parser) parseErrorCode() (ir.ErrorCode, error) { |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return "", p.failExpectedToken(tText, tok) |
| } |
| code := ir.ErrorCode(tok.value) |
| if _, ok := ir.AllErrorCodes[code]; !ok { |
| return "", p.newParseError(tok, "unknown error code: %s", tok.value) |
| } |
| return code, nil |
| } |
| |
| func (p *Parser) parseSlice() ([]interface{}, error) { |
| var result []interface{} |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| val, err := p.parseValue() |
| if err != nil { |
| return err |
| } |
| result = append(result, val) |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseTextSlice() ([]string, error) { |
| var result []string |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| if tok, ok := p.consumeToken(tText); !ok { |
| return p.failExpectedToken(tText, tok) |
| } else { |
| result = append(result, tok.value) |
| return nil |
| } |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseLanguageList() (ir.LanguageList, error) { |
| var result ir.LanguageList |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| if tok, ok := p.consumeToken(tText); !ok { |
| return p.failExpectedToken(tText, tok) |
| } else if !p.allLanguages.Includes(tok.value) { |
| return p.newParseError(tok, "invalid language '%s'; must be one of: %s", |
| tok.value, strings.Join(p.allLanguages, ", ")) |
| } else { |
| result = append(result, tok.value) |
| return nil |
| } |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseByteSection() ([]ir.Encoding, error) { |
| firstTok := p.peekToken() |
| if firstTok.kind == tLsquare { |
| if b, err := p.parseByteList(); err == nil { |
| return []ir.Encoding{{ |
| // Default to the V1 wire format. |
| WireFormat: ir.V1WireFormat, |
| Bytes: b, |
| }}, nil |
| } else { |
| return nil, err |
| } |
| } |
| var res []ir.Encoding |
| wireFormats := map[ir.WireFormat]struct{}{} |
| err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tok) |
| } |
| if teq, ok := p.consumeToken(tEqual); !ok { |
| return p.failExpectedToken(tEqual, teq) |
| } |
| b, err := p.parseByteList() |
| if err != nil { |
| return err |
| } |
| wf, err := ir.WireFormatByName(tok.value) |
| if err != nil { |
| return err |
| } |
| if _, ok := wireFormats[wf]; ok { |
| return p.newParseError(tok, "duplicate wire format: %s", tok.value) |
| } |
| wireFormats[wf] = struct{}{} |
| res = append(res, ir.Encoding{ |
| WireFormat: wf, |
| Bytes: b, |
| }) |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| if len(res) == 0 { |
| return nil, p.newParseError(firstTok, "no bytes provided for any wire format") |
| } |
| return res, nil |
| } |
| |
| func (p *Parser) parseByteList() ([]byte, error) { |
| var bytes []byte |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| // Read the byte size. |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tok) |
| } |
| if p.peekTokenKind(tText) { |
| // First token was the label. Now get the byte size. |
| tok, _ = p.consumeToken(tText) |
| } |
| |
| byteSize, err := strconv.ParseUint(tok.value, 10, 64) |
| if err != nil { |
| return p.newParseError(tok, "error parsing byte block size: %v", err) |
| } |
| if byteSize == 0 { |
| return p.newParseError(tok, "expected non-zero byte size") |
| } |
| |
| if tok, ok := p.consumeToken(tColon); !ok { |
| return p.failExpectedToken(tColon, tok) |
| } |
| |
| // Read the type. |
| tok, ok = p.consumeToken(tText) |
| if !ok { |
| return p.failExpectedToken(tText, tok) |
| } |
| var handler func(byteSize int) ([]byte, error) |
| switch tok.value { |
| case "raw": |
| handler = p.parseByteBlockRaw |
| case "num": |
| handler = p.parseByteBlockNum |
| case "padding": |
| handler = p.parseByteBlockPadding |
| default: |
| return p.newParseError(tok, "unknown byte block type %q", tok.value) |
| } |
| if b, err := handler(int(byteSize)); err != nil { |
| return err |
| } else if len(b) != int(byteSize) { |
| return p.newParseError(tok, "byte block produced of incorrect size. got %d, want %d", len(b), byteSize) |
| } else { |
| bytes = append(bytes, b...) |
| } |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return bytes, nil |
| } |
| |
| func (p *Parser) parseByteBlockRaw(byteSize int) ([]byte, error) { |
| res := make([]byte, 0, byteSize) |
| err := p.parseCommaSeparated(tLparen, tRparen, func() error { |
| b, err := p.parseByte() |
| if err != nil { |
| return err |
| } |
| res = append(res, b) |
| return nil |
| }) |
| return res, err |
| } |
| |
| func (p *Parser) parseByteBlockNum(byteSize int) ([]byte, error) { |
| if tok, ok := p.consumeToken(tLparen); !ok { |
| return nil, p.failExpectedToken(tLparen, tok) |
| } |
| neg := p.peekTokenKind(tNeg) |
| if neg { |
| p.consumeToken(tNeg) |
| } |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return nil, p.failExpectedToken(tText, tok) |
| } |
| buf := make([]byte, 8) |
| uintVal, err := strconv.ParseUint(tok.value, 0, 64) |
| if err != nil { |
| return nil, p.newParseError(tok, "error parsing unsigned integer num: %v", err) |
| } |
| if neg { |
| intVal := -int64(uintVal) |
| if intVal>>(uint(byteSize)*8-1) < -1 { |
| return nil, p.newParseError(tok, "num value %d exceeds byte size %d", intVal, byteSize) |
| } |
| uintVal = uint64(intVal) |
| } else { |
| if uintVal>>(uint(byteSize)*8) > 0 { |
| return nil, p.newParseError(tok, "num value %d exceeds byte size %d", uintVal, byteSize) |
| } |
| } |
| binary.LittleEndian.PutUint64(buf, uintVal) |
| if tok, ok := p.consumeToken(tRparen); !ok { |
| return nil, p.failExpectedToken(tRparen, tok) |
| } |
| return buf[:byteSize], nil |
| } |
| |
| func (p *Parser) parseByteBlockPadding(byteSize int) ([]byte, error) { |
| if !p.peekTokenKind(tLparen) { |
| return bytes.Repeat([]byte{0}, byteSize), nil |
| } |
| if tok, ok := p.consumeToken(tLparen); !ok { |
| return nil, p.failExpectedToken(tLparen, tok) |
| } |
| b, err := p.parseByte() |
| if err != nil { |
| return nil, err |
| } |
| if tok, ok := p.consumeToken(tRparen); !ok { |
| return nil, p.failExpectedToken(tRparen, tok) |
| } |
| return bytes.Repeat([]byte{b}, byteSize), nil |
| } |
| |
| func (p *Parser) parseByte() (byte, error) { |
| tok, ok := p.consumeToken(tText) |
| if !ok { |
| return 0, p.failExpectedToken(tText, tok) |
| } |
| if len(tok.value) == 3 && tok.value[0] == '\'' && tok.value[2] == '\'' { |
| return tok.value[1], nil |
| } |
| b, err := strconv.ParseUint(tok.value, 0, 8) |
| if err != nil { |
| return 0, p.newParseError(tok, err.Error()) |
| } |
| return byte(b), nil |
| } |
| |
| func (p *Parser) parseCommaSeparated(beginTok, endTok tokenKind, handler func() error) error { |
| if tok, ok := p.consumeToken(beginTok); !ok { |
| return p.failExpectedToken(beginTok, tok) |
| } |
| for !p.peekTokenKind(endTok) { |
| if err := handler(); err != nil { |
| return err |
| } |
| if !p.peekTokenKind(endTok) { |
| if tok, ok := p.consumeToken(tComma); !ok { |
| return p.failExpectedToken(tComma, tok) |
| } |
| } |
| } |
| if tok, ok := p.consumeToken(endTok); !ok { |
| return p.failExpectedToken(endTok, tok) |
| } |
| return nil |
| } |
| |
| func (p *Parser) consumeToken(kind tokenKind) (token, bool) { |
| tok := p.nextToken() |
| return tok, tok.kind == kind |
| } |
| |
| func (p *Parser) peekTokenKind(kind tokenKind) bool { |
| return p.peekToken().kind == kind |
| } |
| |
| func (p *Parser) peekToken() token { |
| if len(p.lookaheads) == 0 { |
| tok := p.nextToken() |
| p.lookaheads = append(p.lookaheads, tok) |
| } |
| return p.lookaheads[0] |
| } |
| |
| func (p *Parser) nextToken() token { |
| if len(p.lookaheads) != 0 { |
| var tok token |
| tok, p.lookaheads = p.lookaheads[0], p.lookaheads[1:] |
| return tok |
| } |
| return p.scanToken() |
| } |
| |
| func (p *Parser) scanToken() token { |
| // eof |
| if tok := p.scanner.Scan(); tok == scanner.EOF { |
| return token{tEof, "", 0, 0} |
| } |
| pos := p.scanner.Position |
| |
| // unit tokens |
| text := p.scanner.TokenText() |
| if kind, ok := textToTokenKind[text]; ok { |
| return token{kind, text, pos.Line, pos.Column} |
| } |
| |
| // string |
| if text[0] == '"' { |
| // TODO escape |
| return token{tString, text[1 : len(text)-1], pos.Line, pos.Column} |
| } |
| |
| // text |
| return token{tText, text, pos.Line, pos.Column} |
| } |
| |
| type parseError struct { |
| input string |
| line, column int |
| message string |
| } |
| |
| // Assert parseError implements error interface |
| var _ error = &parseError{} |
| |
| func (err *parseError) Error() string { |
| return fmt.Sprintf("%s:%d:%d: %s", err.input, err.line, err.column, err.message) |
| } |
| |
| func (p *Parser) failExpectedToken(want tokenKind, got token) error { |
| return p.newParseError(got, "unexpected tokenKind: want %q, got %q (value: %q)", want, got.kind, got.value) |
| } |
| |
| func (p *Parser) newParseError(tok token, format string, a ...interface{}) error { |
| return &parseError{ |
| input: p.scanner.Position.Filename, |
| line: tok.line, |
| column: tok.column, |
| message: fmt.Sprintf(format, a...), |
| } |
| } |