| // 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 ( |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "text/scanner" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| type Parser struct { |
| scanner scanner.Scanner |
| lookaheads []token |
| config Config |
| // Used to validate that handles are defined and used exactly once. |
| handles map[ir.Handle]handleInfo |
| } |
| |
| type Config struct { |
| // All supported languages, used to validate bindings allowlist/denylist. |
| Languages ir.LanguageList |
| // All supported wire formats, used to validate `bytes` sections. |
| WireFormats ir.WireFormatList |
| } |
| |
| type handleInfo struct { |
| defined bool |
| usesInValue int |
| usesInHandles int |
| } |
| |
| func mergeHandleInfo(a, b handleInfo) handleInfo { |
| return handleInfo{ |
| defined: a.defined || b.defined, |
| usesInValue: a.usesInValue + b.usesInValue, |
| usesInHandles: a.usesInHandles + b.usesInHandles, |
| } |
| } |
| |
| func NewParser(name string, input io.Reader, config Config) *Parser { |
| var p Parser |
| p.scanner.Position.Filename = name |
| p.scanner.Init(input) |
| p.config = config |
| // This is reset in parseBody. We need to set it here too so that unit tests |
| // for functions like parseHandleDefs don't panic on nil map assignment. |
| p.handles = make(map[ir.Handle]handleInfo) |
| 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 |
| } |
| } |
| return result, nil |
| } |
| |
| type tokenKind uint |
| |
| const ( |
| _ tokenKind = iota |
| tEof |
| tText |
| tString |
| tLacco |
| tRacco |
| tComma |
| tColon |
| tPlus |
| tNeg |
| tLparen |
| tRparen |
| tEqual |
| tLsquare |
| tRsquare |
| tHash |
| ) |
| |
| var tokenKindStrings = []string{ |
| "<invalid>", |
| "<eof>", |
| "<text>", |
| "<string>", |
| "{", |
| "}", |
| ",", |
| ":", |
| "+", |
| "-", |
| "(", |
| ")", |
| "=", |
| "[", |
| "]", |
| "#", |
| } |
| |
| var ( |
| isUnitTokenKind = make(map[tokenKind]struct{}) |
| 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] = struct{}{} |
| 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 _, ok := isUnitTokenKind[t.kind]; ok { |
| return t.kind.String() |
| } else { |
| return t.value |
| } |
| } |
| |
| type bodyElement uint |
| |
| const ( |
| _ bodyElement = iota |
| isType |
| isValue |
| isBytes |
| isHandles |
| isHandleDispositions |
| isHandleDefs |
| isErr |
| isBindingsAllowlist |
| isBindingsDenylist |
| isEnableSendEventBenchmark |
| isEnableEchoCallBenchmark |
| ) |
| |
| func (kind bodyElement) String() string { |
| switch kind { |
| case isType: |
| return "type" |
| case isValue: |
| return "value" |
| case isBytes: |
| return "bytes" |
| case isHandles: |
| return "handles" |
| case isHandleDispositions: |
| return "handle_dispositions" |
| case isHandleDefs: |
| return "handle_defs" |
| case isErr: |
| return "err" |
| case isBindingsAllowlist: |
| return "bindings_allowlist" |
| case isBindingsDenylist: |
| return "bindings_denylist" |
| case isEnableSendEventBenchmark: |
| return "enable_send_event_benchmark" |
| case isEnableEchoCallBenchmark: |
| return "enable_echo_call_benchmark" |
| default: |
| panic("unsupported kind") |
| } |
| } |
| |
| type encodingData struct { |
| WireFormat ir.WireFormat |
| Bytes []byte |
| |
| // At most one of Handle or HandleDispositions will be non-empty. |
| Handles []ir.Handle |
| HandleDispositions []ir.HandleDisposition |
| } |
| |
| func toIrEncodings(v []encodingData) []ir.Encoding { |
| var out []ir.Encoding |
| for _, e := range v { |
| if e.HandleDispositions != nil { |
| out = append(out, ir.Encoding{ |
| WireFormat: e.WireFormat, |
| Bytes: e.Bytes, |
| Handles: ir.GetHandlesFromHandleDispositions(e.HandleDispositions), |
| }) |
| } else { |
| out = append(out, ir.Encoding{ |
| WireFormat: e.WireFormat, |
| Bytes: e.Bytes, |
| Handles: e.Handles, |
| }) |
| } |
| } |
| return out |
| } |
| |
| func toIrHandleDispositionEncodings(v []encodingData) []ir.HandleDispositionEncoding { |
| var out []ir.HandleDispositionEncoding |
| for _, e := range v { |
| if e.HandleDispositions != nil { |
| out = append(out, ir.HandleDispositionEncoding{ |
| WireFormat: e.WireFormat, |
| Bytes: e.Bytes, |
| HandleDispositions: e.HandleDispositions, |
| }) |
| } else { |
| var handleDispositions []ir.HandleDisposition |
| for _, handle := range e.Handles { |
| handleDispositions = append(handleDispositions, ir.HandleDisposition{ |
| Handle: handle, |
| Type: fidlgen.ObjectTypeNone, |
| Rights: fidlgen.HandleRightsSameRights, |
| }) |
| } |
| out = append(out, ir.HandleDispositionEncoding{ |
| WireFormat: e.WireFormat, |
| Bytes: e.Bytes, |
| HandleDispositions: handleDispositions, |
| }) |
| } |
| } |
| return out |
| } |
| |
| type body struct { |
| Type string |
| Value ir.Record |
| Encodings []encodingData |
| HandleDefs []ir.HandleDef |
| Err ir.ErrorCode |
| BindingsAllowlist *ir.LanguageList |
| BindingsDenylist *ir.LanguageList |
| EnableSendEventBenchmark bool |
| EnableEchoCallBenchmark bool |
| } |
| |
| func (b *body) addEncoding(e encodingData) error { |
| for i := range b.Encodings { |
| if b.Encodings[i].WireFormat == e.WireFormat { |
| if e.Bytes != nil { |
| if b.Encodings[i].Bytes == nil { |
| return fmt.Errorf("bytes already set") |
| } |
| b.Encodings[i].Bytes = e.Bytes |
| } |
| if e.Handles != nil { |
| if b.Encodings[i].Handles != nil { |
| return fmt.Errorf("handles already set") |
| } |
| if b.Encodings[i].HandleDispositions != nil { |
| return fmt.Errorf("cannot add handles when handle dispositions is set") |
| } |
| b.Encodings[i].Handles = e.Handles |
| } |
| if e.HandleDispositions != nil { |
| if b.Encodings[i].Handles != nil { |
| return fmt.Errorf("cannot add handles when handle dispositions is set") |
| } |
| if b.Encodings[i].HandleDispositions != nil { |
| return fmt.Errorf("handle dispositions already set") |
| } |
| b.Encodings[i].HandleDispositions = e.HandleDispositions |
| } |
| return nil |
| } |
| } |
| b.Encodings = append(b.Encodings, e) |
| return nil |
| } |
| |
| type rightsConfiguration struct { |
| allowRights bool |
| } |
| |
| type sectionMetadata struct { |
| requiredKinds map[bodyElement]struct{} |
| optionalKinds map[bodyElement]struct{} |
| rightsConfiguration rightsConfiguration |
| setter func(name string, body body, all *ir.All) |
| } |
| |
| var sections = map[string]sectionMetadata{ |
| "success": { |
| requiredKinds: map[bodyElement]struct{}{isValue: {}, isBytes: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandles: {}, isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: false, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| encodeSuccess := ir.EncodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: toIrHandleDispositionEncodings(body.Encodings), |
| HandleDefs: body.HandleDefs, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| CheckHandleRights: false, |
| } |
| all.EncodeSuccess = append(all.EncodeSuccess, encodeSuccess) |
| decodeSuccess := ir.DecodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: toIrEncodings(body.Encodings), |
| HandleDefs: body.HandleDefs, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeSuccess = append(all.DecodeSuccess, decodeSuccess) |
| }, |
| }, |
| "encode_success": { |
| requiredKinds: map[bodyElement]struct{}{isValue: {}, isBytes: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandleDispositions: {}, isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: true, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.EncodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: toIrHandleDispositionEncodings(body.Encodings), |
| HandleDefs: body.HandleDefs, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| CheckHandleRights: true, |
| } |
| all.EncodeSuccess = append(all.EncodeSuccess, result) |
| }, |
| }, |
| "decode_success": { |
| requiredKinds: map[bodyElement]struct{}{isValue: {}, isBytes: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandles: {}, isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: true, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.DecodeSuccess{ |
| Name: name, |
| Value: body.Value, |
| Encodings: toIrEncodings(body.Encodings), |
| HandleDefs: body.HandleDefs, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeSuccess = append(all.DecodeSuccess, result) |
| }, |
| }, |
| "encode_failure": { |
| requiredKinds: map[bodyElement]struct{}{isValue: {}, isErr: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: true, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.EncodeFailure{ |
| Name: name, |
| Value: body.Value, |
| HandleDefs: body.HandleDefs, |
| Err: body.Err, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.EncodeFailure = append(all.EncodeFailure, result) |
| }, |
| }, |
| "decode_failure": { |
| requiredKinds: map[bodyElement]struct{}{isType: {}, isBytes: {}, isErr: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandles: {}, isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: true, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| result := ir.DecodeFailure{ |
| Name: name, |
| Type: body.Type, |
| Encodings: toIrEncodings(body.Encodings), |
| HandleDefs: body.HandleDefs, |
| Err: body.Err, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| } |
| all.DecodeFailure = append(all.DecodeFailure, result) |
| }, |
| }, |
| "benchmark": { |
| requiredKinds: map[bodyElement]struct{}{isValue: {}}, |
| optionalKinds: map[bodyElement]struct{}{ |
| isHandleDefs: {}, isBindingsAllowlist: {}, isBindingsDenylist: {}, |
| isEnableSendEventBenchmark: {}, isEnableEchoCallBenchmark: {}, |
| }, |
| rightsConfiguration: rightsConfiguration{ |
| allowRights: false, |
| }, |
| setter: func(name string, body body, all *ir.All) { |
| benchmark := ir.Benchmark{ |
| Name: name, |
| Value: body.Value, |
| HandleDefs: body.HandleDefs, |
| BindingsAllowlist: body.BindingsAllowlist, |
| BindingsDenylist: body.BindingsDenylist, |
| EnableSendEventBenchmark: body.EnableSendEventBenchmark, |
| EnableEchoCallBenchmark: body.EnableEchoCallBenchmark, |
| } |
| all.Benchmark = append(all.Benchmark, benchmark) |
| }, |
| }, |
| } |
| |
| 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, section.rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| section.setter(name, body, all) |
| return nil |
| } |
| |
| func (p *Parser) parsePreamble() (sectionMetadata, string, error) { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return sectionMetadata{}, "", err |
| } |
| |
| section, ok := sections[tok.value] |
| if !ok { |
| return sectionMetadata{}, "", p.newParseError(tok, "unknown section %s", tok.value) |
| } |
| |
| tok, err = p.consumeToken(tLparen) |
| if err != nil { |
| return sectionMetadata{}, "", err |
| } |
| |
| tok, err = p.consumeToken(tString) |
| if err != nil { |
| return sectionMetadata{}, "", err |
| } |
| name := tok.value |
| |
| tok, err = p.consumeToken(tRparen) |
| if err != nil { |
| return sectionMetadata{}, "", err |
| } |
| |
| return section, name, nil |
| } |
| |
| func (p *Parser) parseBody(requiredKinds map[bodyElement]struct{}, optionalKinds map[bodyElement]struct{}, rightsConfiguration rightsConfiguration) (body, error) { |
| var ( |
| result body |
| parsedKinds = make(map[bodyElement]struct{}) |
| ) |
| bodyTok, err := p.peekToken() |
| if err != nil { |
| return result, err |
| } |
| p.handles = make(map[ir.Handle]handleInfo) |
| if err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| return p.parseSingleBodyElement(&result, parsedKinds, rightsConfiguration) |
| }); err != nil { |
| return result, err |
| } |
| for requiredKind := range requiredKinds { |
| if _, ok := parsedKinds[requiredKind]; !ok { |
| return result, p.newParseError(bodyTok, "missing required parameter '%s'", requiredKind) |
| } |
| } |
| for parsedKind := range parsedKinds { |
| _, requiredKind := requiredKinds[parsedKind] |
| _, optionalKind := optionalKinds[parsedKind] |
| if !requiredKind && !optionalKind { |
| return result, p.newParseError(bodyTok, "parameter '%s' does not apply to element", parsedKind) |
| } |
| } |
| for h, info := range p.handles { |
| if !info.defined { |
| return result, p.newParseError(bodyTok, "missing definition for handle #%d", h) |
| } |
| if info.usesInValue > 1 { |
| return result, p.newParseError(bodyTok, "handle #%d used more than once in 'value' section", h) |
| } |
| if info.usesInHandles > 1 { |
| return result, p.newParseError(bodyTok, "handle #%d used more than once in 'handles' section", h) |
| } |
| if info.usesInValue == 0 && info.usesInHandles == 0 { |
| return result, p.newParseError(bodyTok, "unused handle #%d", h) |
| } |
| } |
| p.handles = nil |
| return result, nil |
| } |
| |
| func (p *Parser) parseSingleBodyElement(result *body, all map[bodyElement]struct{}, rightsConfiguration rightsConfiguration) error { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| if _, err := p.consumeToken(tEqual); err != nil { |
| return err |
| } |
| var kind bodyElement |
| switch tok.value { |
| case "type": |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| result.Type = tok.value |
| kind = isType |
| case "value": |
| val, err := p.parseValue(rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| record, ok := val.(ir.Record) |
| if !ok { |
| return fmt.Errorf("top-level value must be a struct; got %T", val) |
| } |
| result.Value = record |
| kind = isValue |
| case "bytes": |
| encodings, err := p.parseByteSection() |
| if err != nil { |
| return err |
| } |
| for _, e := range encodings { |
| if err := result.addEncoding(e); err != nil { |
| return err |
| } |
| } |
| kind = isBytes |
| case "handles": |
| encodings, err := p.parseHandleSection() |
| if err != nil { |
| return err |
| } |
| for _, e := range encodings { |
| if err := result.addEncoding(e); err != nil { |
| return err |
| } |
| } |
| kind = isHandles |
| case "handle_dispositions": |
| encodings, err := p.parseHandleDispositionSection() |
| if err != nil { |
| return err |
| } |
| for _, e := range encodings { |
| if err := result.addEncoding(e); err != nil { |
| return err |
| } |
| } |
| kind = isHandleDispositions |
| case "handle_defs": |
| handleDefs, err := p.parseHandleDefSection(rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| result.HandleDefs = handleDefs |
| kind = isHandleDefs |
| 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 |
| case "enable_send_event_benchmark": |
| value, err := p.parseValue(rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| boolValue, ok := value.(bool) |
| if !ok { |
| return p.newParseError(tok, "expected boolean value") |
| } |
| result.EnableSendEventBenchmark = boolValue |
| kind = isEnableSendEventBenchmark |
| case "enable_echo_call_benchmark": |
| value, err := p.parseValue(rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| boolValue, ok := value.(bool) |
| if !ok { |
| return p.newParseError(tok, "expected boolean value") |
| } |
| result.EnableEchoCallBenchmark = boolValue |
| kind = isEnableEchoCallBenchmark |
| default: |
| return p.newParseError(tok, "must be type, value, bytes, err, bindings_allowlist or bindings_denylist") |
| } |
| if kind == 0 { |
| panic("kind must be set") |
| } |
| if _, ok := all[kind]; ok { |
| return p.newParseError(tok, "duplicate %s found", kind) |
| } |
| all[kind] = struct{}{} |
| return nil |
| } |
| |
| func (p *Parser) parseHandleRestrict(rightsConfiguration rightsConfiguration) (ir.HandleWithRights, error) { |
| if _, err := p.consumeToken(tLparen); err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| handle, err := p.parseHandle(handleInfo{usesInValue: 1}) |
| if err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| objectType := fidlgen.ObjectTypeNone |
| rights := fidlgen.HandleRightsSameRights |
| for p.peekTokenKind(tComma) { |
| p.nextToken() |
| |
| labelTok, err := p.consumeToken(tText) |
| if err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| |
| if _, err := p.consumeToken(tColon); err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| |
| switch labelTok.value { |
| case "rights": |
| if !rightsConfiguration.allowRights { |
| return ir.HandleWithRights{}, fmt.Errorf("rights are disabled for this section") |
| } |
| |
| rights, err = p.parseHandleRights() |
| if err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| default: |
| return ir.HandleWithRights{}, fmt.Errorf("unknown restrict label: %s", labelTok.value) |
| } |
| } |
| if _, err := p.consumeToken(tRparen); err != nil { |
| return ir.HandleWithRights{}, err |
| } |
| return ir.HandleWithRights{ |
| Handle: handle, |
| Type: objectType, |
| Rights: rights, |
| }, nil |
| } |
| |
| func (p *Parser) parseValue(rightsConfiguration rightsConfiguration) (ir.Value, error) { |
| tok, err := p.peekToken() |
| if err != nil { |
| return nil, err |
| } |
| switch tok.kind { |
| case tText: |
| tok, err := p.nextToken() |
| if err != nil { |
| return nil, err |
| } |
| 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 |
| } |
| if tok.value == "raw_float" { |
| return p.parseRawFloat() |
| } |
| if tok.value == "restrict" { |
| return p.parseHandleRestrict(rightsConfiguration) |
| } |
| return p.parseRecord(tok.value, rightsConfiguration) |
| case tLsquare: |
| return p.parseSlice(rightsConfiguration) |
| case tString: |
| tok, err := p.nextToken() |
| if err != nil { |
| return nil, err |
| } |
| return tok.value, nil |
| case tNeg: |
| if _, err := p.nextToken(); err != nil { |
| return nil, err |
| } |
| if tok, err := p.consumeToken(tText); err != nil { |
| return nil, err |
| } else { |
| return parseNum(tok, true) |
| } |
| case tHash: |
| handle, err := p.parseHandle(handleInfo{usesInValue: 1}) |
| if err != nil { |
| return nil, err |
| } |
| return ir.HandleWithRights{ |
| Handle: handle, |
| Type: fidlgen.ObjectTypeNone, |
| Rights: fidlgen.HandleRightsSameRights, |
| }, nil |
| default: |
| tok, err := p.peekToken() |
| if err != nil { |
| return nil, err |
| } |
| return nil, p.newParseError(tok, "expected value") |
| } |
| } |
| |
| func parseNum(tok token, neg bool) (ir.Value, 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) parseRawFloat() (ir.RawFloat, error) { |
| // Already parsed raw_float token. |
| if _, err := p.consumeToken(tLparen); err != nil { |
| return 0, err |
| } |
| tok, err := p.nextToken() |
| if err != nil { |
| return 0, err |
| } |
| raw, err := strconv.ParseUint(tok.value, 0, 64) |
| if err != nil { |
| return 0, err |
| } |
| if _, err := p.consumeToken(tRparen); err != nil { |
| return 0, err |
| } |
| return ir.RawFloat(raw), nil |
| } |
| |
| func (p *Parser) parseRecord(name string, rightsConfiguration rightsConfiguration) (ir.Record, error) { |
| obj := ir.Record{Name: name} |
| err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| tokFieldName, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| key := decodeFieldKey(tokFieldName.value) |
| if _, err := p.consumeToken(tColon); err != nil { |
| return err |
| } |
| var val ir.Value |
| if key.IsKnown() { |
| val, err = p.parseValue(rightsConfiguration) |
| } else { |
| val, err = p.parseUnknownData() |
| } |
| if err != nil { |
| return err |
| } |
| obj.Fields = append(obj.Fields, ir.Field{Key: key, Value: val}) |
| return nil |
| }) |
| if err != nil { |
| return ir.Record{}, 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} |
| } |
| |
| // This is not expressed in terms of parseBody because it parses bytes/handles |
| // without wire formats (e.g. `bytes = [...]`, not `bytes = { v1 = [...] }`). |
| func (p *Parser) parseUnknownData() (ir.UnknownData, error) { |
| var result ir.UnknownData |
| parsedKinds := make(map[bodyElement]struct{}) |
| bodyTok, err := p.peekToken() |
| if err != nil { |
| return result, err |
| } |
| if err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| if _, err := p.consumeToken(tEqual); err != nil { |
| return err |
| } |
| var kind bodyElement |
| switch tok.value { |
| case "bytes": |
| bytes, err := p.parseByteList() |
| if err != nil { |
| return err |
| } |
| result.Bytes = bytes |
| kind = isBytes |
| case "handles": |
| handles, err := p.parseHandleList(handleInfo{usesInValue: 1}) |
| if err != nil { |
| return err |
| } |
| result.Handles = handles |
| kind = isHandles |
| default: |
| return p.newParseError(tok, "parameter '%s' does not apply to unknown data, must be bytes", tok) |
| } |
| if kind == 0 { |
| panic("kind must be set") |
| } |
| if _, ok := parsedKinds[kind]; ok { |
| return p.newParseError(tok, "duplicate parameter '%s' found", kind) |
| } |
| parsedKinds[kind] = struct{}{} |
| return nil |
| }); err != nil { |
| return result, err |
| } |
| for _, requiredKind := range []bodyElement{isBytes} { |
| if _, ok := parsedKinds[requiredKind]; !ok { |
| return result, p.newParseError(bodyTok, "missing required parameter '%s'", requiredKind) |
| } |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseErrorCode() (ir.ErrorCode, error) { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return "", err |
| } |
| 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(rightsConfiguration rightsConfiguration) ([]ir.Value, error) { |
| result := make([]ir.Value, 0) |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| val, err := p.parseValue(rightsConfiguration) |
| 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, err := p.consumeToken(tText); err != nil { |
| return err |
| } 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, err := p.consumeToken(tText); err != nil { |
| return err |
| } else if !p.config.Languages.Includes(tok.value) { |
| return p.newParseError(tok, "invalid language '%s'; must be one of: %s", |
| tok.value, strings.Join(p.config.Languages, ", ")) |
| } else { |
| result = append(result, tok.value) |
| return nil |
| } |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return result, nil |
| } |
| |
| func (p *Parser) parseByteSection() ([]encodingData, error) { |
| var res []encodingData |
| firstTok, err := p.peekToken() |
| if err != nil { |
| return nil, err |
| } |
| err = p.parseWireFormatMapping(func(wireFormats []ir.WireFormat) error { |
| b, err := p.parseByteList() |
| if err != nil { |
| return err |
| } |
| for _, wf := range wireFormats { |
| res = append(res, encodingData{ |
| 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 { |
| tok, err := p.peekToken() |
| if err != nil { |
| return err |
| } |
| if tok.kind == tText && 'a' <= tok.value[0] && tok.value[0] <= 'z' { |
| p.nextToken() |
| parse, ok := p.getByteGenParser(tok.value) |
| if !ok { |
| return p.newParseError(tok, "invalid byte syntax: %s", tok.value) |
| } |
| b, err := parse() |
| if err != nil { |
| return err |
| } |
| bytes = append(bytes, b...) |
| } else { |
| b, err := p.parseByte() |
| if err != nil { |
| return err |
| } |
| bytes = append(bytes, b) |
| } |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return bytes, nil |
| } |
| |
| func (p *Parser) parseByte() (byte, error) { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return 0, err |
| } |
| 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, "invalid byte syntax: %s", tok.value) |
| } |
| return byte(b), nil |
| } |
| |
| func (p *Parser) parseHandleSection() ([]encodingData, error) { |
| var res []encodingData |
| err := p.parseWireFormatMapping(func(wireFormats []ir.WireFormat) error { |
| h, err := p.parseHandleList(handleInfo{usesInHandles: 1}) |
| if err != nil { |
| return err |
| } |
| for _, wf := range wireFormats { |
| res = append(res, encodingData{ |
| WireFormat: wf, |
| Handles: h, |
| }) |
| } |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| } |
| |
| func (p *Parser) parseHandleDispositionSection() ([]encodingData, error) { |
| var res []encodingData |
| err := p.parseWireFormatMapping(func(wireFormats []ir.WireFormat) error { |
| h, err := p.parseHandleDispositionList(handleInfo{usesInHandles: 1}) |
| if err != nil { |
| return err |
| } |
| for _, wf := range wireFormats { |
| res = append(res, encodingData{ |
| WireFormat: wf, |
| HandleDispositions: h, |
| }) |
| } |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| } |
| |
| func (p *Parser) parseHandleList(info handleInfo) ([]ir.Handle, error) { |
| var handles []ir.Handle |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| h, err := p.parseHandle(info) |
| if err != nil { |
| return err |
| } |
| handles = append(handles, h) |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return handles, nil |
| } |
| |
| func (p *Parser) parseHandleDispositionList(info handleInfo) ([]ir.HandleDisposition, error) { |
| var handleDispositions []ir.HandleDisposition |
| err := p.parseCommaSeparated(tLsquare, tRsquare, func() error { |
| if _, err := p.consumeToken(tLacco); err != nil { |
| return err |
| } |
| handle, err := p.parseHandle(handleInfo{usesInHandles: 1}) |
| if err != nil { |
| return err |
| } |
| objectType := fidlgen.ObjectTypeNone |
| rights := fidlgen.HandleRightsSameRights |
| for p.peekTokenKind(tComma) { |
| p.nextToken() |
| |
| labelTok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := p.consumeToken(tColon); err != nil { |
| return err |
| } |
| |
| switch labelTok.value { |
| case "type": |
| valueTok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| objectType = fidlgen.ObjectTypeFromHandleSubtype(fidlgen.HandleSubtype(valueTok.value)) |
| case "rights": |
| rights, err = p.parseHandleRights() |
| if err != nil { |
| return err |
| } |
| default: |
| return fmt.Errorf("unknown handle disposition label: %s", labelTok.value) |
| } |
| } |
| handleDispositions = append(handleDispositions, ir.HandleDisposition{ |
| Handle: handle, |
| Type: objectType, |
| Rights: rights, |
| }) |
| if _, err := p.consumeToken(tRacco); err != nil { |
| return err |
| } |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return handleDispositions, nil |
| } |
| |
| func (p *Parser) parseHandle(info handleInfo) (ir.Handle, error) { |
| tok, err := p.consumeToken(tHash) |
| if err != nil { |
| return 0, err |
| } |
| tok, err = p.consumeToken(tText) |
| if err != nil { |
| return 0, err |
| } |
| index, err := strconv.ParseInt(tok.value, 10, 0) |
| if err != nil { |
| return 0, p.newParseError(tok, "invalid handle syntax: %s", tok.value) |
| } |
| if index < 0 { |
| panic("impossible because tok is tText not tNeg") |
| } |
| h := ir.Handle(index) |
| p.handles[h] = mergeHandleInfo(p.handles[h], info) |
| return h, nil |
| } |
| |
| func (p *Parser) parseHandleDefSection(rightsConfiguration rightsConfiguration) ([]ir.HandleDef, error) { |
| var res []ir.HandleDef |
| expected := ir.Handle(0) |
| err := p.parseCommaSeparated(tLacco, tRacco, func() error { |
| tok, err := p.peekToken() |
| if err != nil { |
| return err |
| } |
| h, err := p.parseHandle(handleInfo{defined: true}) |
| if err != nil { |
| return err |
| } |
| if h != expected { |
| return p.newParseError( |
| tok, "want #%d, got #%d (handle_defs must be #0, #1, #2, etc.)", expected, h) |
| } |
| expected++ |
| tok, err = p.consumeToken(tEqual) |
| if err != nil { |
| return err |
| } |
| hd, err := p.parseHandleDef(rightsConfiguration) |
| if err != nil { |
| return err |
| } |
| res = append(res, hd) |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| } |
| |
| func (p *Parser) parseHandleRights() (fidlgen.HandleRights, error) { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return 0, err |
| } |
| rights, ok := ir.HandleRightsByName(tok.value) |
| if !ok { |
| return 0, p.newParseError(tok, "expected handle right: %s", tok.value) |
| } |
| for p.peekTokenKind(tPlus) || p.peekTokenKind(tNeg) { |
| opTok, err := p.nextToken() |
| if err != nil { |
| return 0, err |
| } |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return 0, err |
| } |
| opRights, ok := ir.HandleRightsByName(tok.value) |
| if !ok { |
| return 0, p.newParseError(tok, "expected handle right: %s", tok.value) |
| } |
| switch opTok.kind { |
| case tPlus: |
| rights |= opRights |
| case tNeg: |
| rights &= ^opRights |
| default: |
| panic("unexpected state") |
| } |
| } |
| return rights, nil |
| } |
| |
| func (p *Parser) parseHandleDef(rightsConfiguration rightsConfiguration) (ir.HandleDef, error) { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| subtype, ok := ir.HandleSubtypeByName(tok.value) |
| if !ok { |
| return ir.HandleDef{}, p.newParseError(tok, "invalid handle subtype: %s", tok.value) |
| } |
| tok, err = p.consumeToken(tLparen) |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| rights, ok := ir.HandleRightsByName("same_rights") |
| if !ok { |
| panic("'same_rights' is missing") |
| } |
| if p.peekTokenKind(tText) { |
| |
| if !rightsConfiguration.allowRights { |
| return ir.HandleDef{}, fmt.Errorf("rights are disabled for this section") |
| } |
| |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| if tok.value != "rights" { |
| return ir.HandleDef{}, p.newParseError(tok, "expected 'rights', got %s", tok.value) |
| } |
| tok, err = p.consumeToken(tColon) |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| rights, err = p.parseHandleRights() |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| } |
| tok, err = p.consumeToken(tRparen) |
| if err != nil { |
| return ir.HandleDef{}, err |
| } |
| return ir.HandleDef{ |
| Subtype: subtype, |
| Rights: rights, |
| }, nil |
| } |
| |
| func (p *Parser) parseWireFormatMapping(handler func([]ir.WireFormat) error) error { |
| seenWireFormats := map[ir.WireFormat]struct{}{} |
| return p.parseCommaSeparated(tLacco, tRacco, func() error { |
| var wireFormats []ir.WireFormat |
| for { |
| tok, err := p.consumeToken(tText) |
| if err != nil { |
| return err |
| } |
| wf := ir.WireFormat(tok.value) |
| if !p.config.WireFormats.Includes(wf) { |
| return p.newParseError(tok, "invalid wire format '%s'; must be one of: %s", |
| tok.value, p.config.WireFormats.Join(", ")) |
| } |
| if _, ok := seenWireFormats[wf]; ok { |
| return p.newParseError(tok, "duplicate wire format: %s", tok.value) |
| } |
| seenWireFormats[wf] = struct{}{} |
| wireFormats = append(wireFormats, wf) |
| if p.peekTokenKind(tEqual) { |
| break |
| } |
| if _, err := p.consumeToken(tComma); err != nil { |
| return err |
| } |
| } |
| if _, err := p.consumeToken(tEqual); err != nil { |
| return err |
| } |
| return handler(wireFormats) |
| }) |
| } |
| |
| func (p *Parser) parseCommaSeparated(beginTok, endTok tokenKind, handler func() error) error { |
| if _, err := p.consumeToken(beginTok); err != nil { |
| return err |
| } |
| for !p.peekTokenKind(endTok) { |
| if err := handler(); err != nil { |
| return err |
| } |
| if !p.peekTokenKind(endTok) { |
| if _, err := p.consumeToken(tComma); err != nil { |
| return err |
| } |
| } |
| } |
| if _, err := p.consumeToken(endTok); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (p *Parser) consumeToken(kind tokenKind) (token, error) { |
| tok, err := p.nextToken() |
| if err != nil { |
| return token{}, err |
| } else if tok.kind != kind { |
| return token{}, p.newParseError(tok, "unexpected tokenKind: want %q, got %q (value: %q)", kind, tok.kind, tok.value) |
| } |
| return tok, nil |
| } |
| |
| func (p *Parser) peekTokenKind(kind tokenKind) bool { |
| tok, err := p.peekToken() |
| if err != nil { |
| return false |
| } |
| return tok.kind == kind |
| } |
| |
| func (p *Parser) peekToken() (token, error) { |
| if len(p.lookaheads) == 0 { |
| tok, err := p.nextToken() |
| if err != nil { |
| return token{}, err |
| } |
| p.lookaheads = append(p.lookaheads, tok) |
| } |
| return p.lookaheads[0], nil |
| } |
| |
| func (p *Parser) nextToken() (token, error) { |
| if len(p.lookaheads) != 0 { |
| var tok token |
| tok, p.lookaheads = p.lookaheads[0], p.lookaheads[1:] |
| return tok, nil |
| } |
| return p.scanToken() |
| } |
| |
| func (p *Parser) scanToken() (token, error) { |
| // eof |
| if tok := p.scanner.Scan(); tok == scanner.EOF { |
| return token{tEof, "", 0, 0}, nil |
| } |
| pos := p.scanner.Position |
| |
| // unit tokens |
| text := p.scanner.TokenText() |
| if kind, ok := textToTokenKind[text]; ok { |
| return token{kind, text, pos.Line, pos.Column}, nil |
| } |
| |
| // string |
| if text[0] == '"' { |
| tok := token{tString, "", pos.Line, pos.Column} |
| s, err := strconv.Unquote(text) |
| if err != nil { |
| return tok, p.newParseError(tok, "improperly escaped string, %s: %s", err, text) |
| } |
| tok.value = s |
| return tok, nil |
| } |
| |
| // text |
| return token{tText, text, pos.Line, pos.Column}, nil |
| } |
| |
| 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) 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...), |
| } |
| } |