| package parser |
| |
| import ( |
| "fmt" |
| "math" |
| "mojom/mojom_tool/lexer" |
| "mojom/mojom_tool/mojom" |
| "strconv" |
| ) |
| |
| // The code in this file implements recursive-descent, predictive parsing |
| // for the context-free grammar listed below. |
| // |
| // The grammar is similar to the grammar in the document "Mojom Language |
| // Specification", but it has been modified in order to make it LL(1). This |
| // is necessary in order to be able to use it to do predictive top-down |
| // parsing. (See Section 4.4.3 of "Compilers: Principles, Techniques and Tools" |
| // 2nd Edition, by Aho et al."). This requirement explains the slight awkwardness |
| // seen below regarding the handling of Mojom attributes. (Disclaimer: There are |
| // a few productions that technically render the grammar LL(n) for some small |
| // integer n rather than strictly LL(1). But this does not affect the parsing |
| // in any significant way.) |
| // |
| // Our recursive descent logic is implemented in the methods below with names of |
| // the form "parseX()" where "X" is (an altered spelling of) one of our |
| // non-terminal symbols. Each of the productions below has been copied to the |
| // piece of code responsible for implementing the logic associated with the |
| // production. |
| // |
| // Key: |
| // Upper case means non-terminals. |
| // Lower case means terminals and refers to the TokenKind enum in lexer/tokens.go. |
| // Vertical bar | means alternatives. |
| // Braces {} means zero or more. |
| // Brackets [] means zero or one. |
| // |
| // ATTR_MOJOM_FILE -> [ATTRIBUTES] MOJOM_FILE |
| // MOJOM_FILE -> MODULE_DECL {IMPORT_STMNT} {ATTR_MOJOM_DECL} |
| // MOJOM_FILE -> IMPORT_STMNT {IMPORT_STMNT} {ATTR_MOJOM_DECL} |
| // MOJOM_FILE -> MOJOM_DECL {ATTR_MOJOM_DECL} |
| |
| // MODULE_DECL -> module IDENTIFIER semi |
| // IMPORT_STMNT -> import string_literal |
| |
| // ATTR_MOJOM_DECL -> [ATTRIBUTES] MOJOM_DECL |
| // MOJOM_DECL -> INTRFC_DECL | STRUCT_DECL | UNION_DECL | ENUM_DECL | CONSTANT_DECL |
| |
| // ATTRIBUTES -> lbracket ATTR_ASSIGNMENT { comma, ATTR_ASSIGNMENT} |
| // ATTR_ASSIGNMENT -> name equals name | name equals LITERAL_VALUE |
| |
| // INTRFC_DECL -> interface name lbrace INTRFC_BODY rbrace semi |
| // INTRFC_BODY -> {ATTR_INTRFC_ELEMENT} |
| // ATTR_INTRFC_ELEMENT -> [ATTRIBUTES] INTRFC_ELEMENT |
| // INTRFC_ELEMENT -> METHOD_DECL | ENUM_DECL | CONSTANT_DECL |
| |
| // METHOD_DECL -> name [ordinal] lparen [PARAM_LIST] rparen [response lparen [PARAM_LIST] rparen] semi |
| // PARAM_LIST -> PARAM_DECL {comma, PARAM_DECL} |
| // PARAM_DECL -> [ATTRIBUTES] TYPE NAME [ordinal] |
| |
| // STRUCT_DECL -> struct name lbrace STRUCT_BODY rbrace semi |
| // STRUCT_BODY -> {ATTR_STRUCT_ELEMENT} |
| // ATTR_STRUCT_ELEMENT -> [ATTRIBUTES] STRUCT_ELEMENT |
| // STRUCT_ELEMENT -> STRUCT_FIELD | ENUM_DECL | CONSTANT_DECL |
| // STRUCT_FIELD -> TYPE name [ordinal] [equals DEFAULT_VALUE] semi |
| // DEFAULT_VALUE -> COMPATIBLE_VALUE_REF | default |
| // COMPATIBLE_VALUE_REF -> VALUE_REF {{that resolves to a concrete value whose type is assignment compatible with the type of the assignee}} |
| |
| // UNION_DECL -> union name lbrace UNION_BODY rbrace semi |
| // UNION_BODY -> {UNION_FIELD_DECL} |
| // UNION_FIELD_DECL -> [ATTRIBUTES] TYPE name [ordinal] semi |
| |
| // ENUM_DECL -> enum name lbrace ENUM_BODY rbrace semi |
| // ENUM_BODY -> [ ENUN_VALUE {, ENUM_VALUE} [,] ] |
| // ENUM_VALUE -> [ATTRIBUTES] name [equals ENUM_VAL_INITIALIZER] |
| // ENUM_VAL_INITIALIZER -> INT32_VAL | APPROPRIATE_ENUM_VALUE_REF |
| // INT32_VAL -> VALUE_REF {{that resolves to a concrete value of integer type that may be assigned to an int32.}} |
| // APPROPRIATE_ENUM_VALUE_REF |
| // -> USER_VALUE_REF {{that resolves to an enum value of the same enum type as the initializee |
| // and which occurs earlier in declaration order than the initializee.}} |
| |
| // CONSTANT_DECL -> const CONST_OK_TYPE name equals COMPATIBLE_VALUE_REF semi |
| // CONST_OK_TYPE -> SIMPLE_TYPE | string | ENUM_TYPE {{See https://github.com/domokit/mojo/issues/607}} |
| |
| // VALUE_REF -> USER_VALUE_REF | LITERAL_VALUE | BUILT_IN_FLOAT_CONST |
| // USER_VALUE_REF -> IDENTIFIER {{that resolves to a user-defined constant or enum value}} |
| // BUILT_IN_FLOAT_CONST -> IDENTIFIER {{that does not resolve to a user-defined constant or enum value |
| // and that is equal to one of the following strings: |
| // "float.INFINITY", "float.NEGATIVE_INFINITY", "float.NAN", |
| // "double.INFINITY", "double.NEGATIVE_INFINITY","double.NAN"} |
| // LITERAL_VALUE -> BOOL_LITERAL | string_literal | NUMBER_LITERAL |
| // BOOL_LITERAL -> true | false |
| // NUMBER_LITERAL -> [plus | minus] POS_NUM_LITERAL |
| // INTEGER_LITERAL -> [plus | minus] POS_INT_LITERAL |
| // POS_NUM_LITERAL -> POS_INT_LITERAL | POS_FLOAT_LITERAL |
| // POS_INT_LITERAL -> int_const_dec | int_const_hex |
| // POS_FLOAT_LITERAL -> float_const |
| |
| // TYPE -> BUILT_IN_TYPE | ARRAY_TYPE | MAP_TYPE | USER_TYPE_REF |
| // BUILT_IN_TYPE -> SIMPLE_TYPE | STRING_TYPE | HANDLE_TYPE |
| // SIMPLE_TYPE -> bool | FLOAT_TYPE | INTEGER_TYPE |
| // FLOAT_TYPE -> float | double |
| // HANDLE_TYPE -> handle langle [HANDLE_KIND] rangle [qstn] |
| // HANDLE_KIND -> message_pipe | data_pipe_consumer | data_pipe_producer | shared_buffer |
| // INTEGER_TYPE -> int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
| // STRING_TYPE -> string [qstn] |
| // ARRAY_TYPE -> array langle TYPE [comma int_const_dec] rangle [qstn] |
| // MAP_TYPE -> map langle MAP_KEY_TYPE comma TYPE rangle [qstn] |
| // MAP_KEY_TYPE -> SIMPLE_TYPE | string | ENUM_TYPE |
| // USER_TYPE_REF -> INTERFACE_TYPE | STRUCT_TYPE | UNION_TYPE | ENUM_TYPE |
| // INTERFACE_TYPE -> IDENTIFIER [amp] [qstn] {{where IDENTIFIER resolves to an interface}} |
| // STRUCT_TYPE -> IDENTIFIER [qstn] {{where IDENTIFIER resolves to a struct}} |
| // UNION_TYPE -> IDENTIFIER [qstn] {{where IDENTIFIER resolves to a union}} |
| // ENUM_TYPE -> IDENTIFIER {{where IDENTIFIER resolves to an enum}} |
| |
| // IDENTIFIER -> name {dot name} |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // parseX() methods follow. |
| //////////////////////////////////////////////////////////////////////////// |
| |
| // Note about the input and output of the parseX() methods: The method |
| // Parser.OK() indicates whether or not there has been a parsing error. |
| // All of the methods start with the check: |
| // if !p.OK() |
| // return |
| // } |
| // and sometimes perform this check midway through their logic too. To make |
| // this more concise some of the methods return the value of Parser.OK() so that |
| // a caller can check the value without an additional 'if' clause. If a parseX() |
| // method has no other value it needs to return it always returns the value of |
| // Parser.OK(). |
| // |
| // Many of the methods construct a Mojom object and return it. For example |
| // parseInterfaceDecl() returns a MojomInterface. If the method is responsible |
| // for parsing a block containing arbitrarily many elements then instead the |
| // method is passed a container that it fills up. For example |
| // parseInterfaceBody() is passed a MojomInterface that it fills up and it |
| // returns the bool Parser.OK(). If a Mojom object can take optional attributes |
| // then the attributes are parsed first via parseAttributes() and then the |
| // attributes are passed into the method that parses the object. For example |
| // parseInterfaceDecl() takes the parameter attributes *mojom.Attributes. |
| // |
| // Some of the method names start with the word "try": for example tryParseArrayType(). |
| // The word "try" in the name is a hint that the corresponding structure to |
| // be parsed may or may not occur next and there should be no error if |
| // it turns out the production does not match. For example tryParseArrayType() |
| // returns a TypeRef that will be non-nil just in case an ARRAY_TYPE was |
| // successfully parsed. But if it turns out that the token "array" does not occur |
| // next then it is not an error and tryParseArrayType() simply returns nil without |
| // setting an error condition. |
| // |
| // The helper methods that read in the terminals of the grammar are given |
| // names of the form readFoo() instead of parseFoo(). For example there is a |
| // method readName() whose job is to read a name. This method is not called |
| // parseName() because name is a terminal of our grammar. |
| |
| // ATTR_MOJOM_FILE -> [ATTRIBUTES] MOJOM_FILE |
| // MOJOM_FILE -> MODULE_DECL {IMPORT_STMNT} {ATTR_MOJOM_DECL} |
| // MOJOM_FILE -> IMPORT_STMNT {IMPORT_STMNT} {ATTR_MOJOM_DECL} |
| // MOJOM_FILE -> MOJOM_DECL {ATTR_MOJOM_DECL} |
| // |
| // Returns Parser.OK() |
| func (p *Parser) parseMojomFile() bool { |
| p.pushRootNode("MojomFile") |
| defer p.popNode() |
| |
| initialAttributes := p.parseAttributes() |
| |
| moduleNamespace := p.parseModuleDecl() |
| |
| if !p.OK() { |
| return false |
| } |
| |
| if moduleNamespace.Identifier != "" { |
| p.mojomFile.Attributes = initialAttributes |
| initialAttributes = nil |
| } |
| |
| // Set up the root scope |
| p.pushScope(p.mojomFile.InitializeFileScope(moduleNamespace)) |
| defer p.popScope() |
| |
| if p.checkEOF() { |
| if initialAttributes != nil && moduleNamespace.Identifier == "" { |
| message := "The .mojom file contains an attributes section but nothing else." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| return false |
| } |
| // Accept an empty .mojom file. |
| return true |
| } |
| |
| importedFiles := p.parseImportStatements() |
| if !p.OK() { |
| return false |
| } |
| for _, importedFile := range importedFiles { |
| p.mojomFile.AddImport(importedFile) |
| } |
| |
| if moduleNamespace.Identifier == "" && len(importedFiles) > 0 && initialAttributes != nil { |
| message := "Attributes are not allowed before an import statement." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| return false |
| } |
| |
| attributes := p.parseAttributes() |
| if !p.OK() { |
| return false |
| } |
| if initialAttributes != nil { |
| if attributes != nil { |
| // This is impossible because parseModuleDecl() would have complained |
| // about not expecting to see a "[". |
| panic("Internal logic error.") |
| } else { |
| attributes = initialAttributes |
| } |
| } |
| |
| if p.metaDataOnlyMode { |
| // In meta-data-only mode we do not parse any of the mojom declarations. |
| p.discardRemaining = true |
| return true |
| } |
| |
| // ATTR_MOJOM_DECL -> [ATTRIBUTES] MOJOM_DECL |
| // MOJOM_DECL -> INTRFC_DECL | STRUCT_DECL | UNION_DECL | ENUM_DECL | CONSTANT_DECL |
| for ; ; attributes = p.parseAttributes() { |
| if !p.OK() { |
| return false |
| } |
| if p.checkEOF() { |
| if attributes != nil { |
| message := "File ends with extraneouss attributes." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| } |
| return false |
| } |
| nextToken := p.peekNextToken("") |
| var duplicateNameError error = nil |
| switch nextToken.Kind { |
| case lexer.Interface: |
| if mojomInterface := p.parseInterfaceDecl(attributes); mojomInterface != nil { |
| duplicateNameError = p.mojomFile.AddInterface(mojomInterface) |
| } |
| case lexer.Struct: |
| if mojomStruct := p.parseStructDecl(attributes); mojomStruct != nil { |
| duplicateNameError = p.mojomFile.AddStruct(mojomStruct) |
| } |
| case lexer.Union: |
| if mojomUnion := p.parseUnionDecl(attributes); mojomUnion != nil { |
| duplicateNameError = p.mojomFile.AddUnion(mojomUnion) |
| } |
| case lexer.Enum: |
| if mojomEnum := p.parseEnumDecl(attributes); mojomEnum != nil { |
| duplicateNameError = p.mojomFile.AddEnum(mojomEnum) |
| } |
| case lexer.Const: |
| if constant := p.parseConstDecl(attributes); constant != nil { |
| duplicateNameError = p.mojomFile.AddConstant(constant) |
| } |
| default: |
| p.unexpectedTokenError(nextToken, "interface, struct, union, enum or const") |
| return false |
| } |
| |
| if p.OK() && duplicateNameError != nil { |
| p.err = duplicateNameError |
| return false |
| } |
| } |
| return p.OK() |
| } |
| |
| // ATTRIBUTES -> lbracket ATTR_ASSIGNMENT { comma, ATTR_ASSIGNMENT} |
| // ATTR_ASSIGNMENT -> name equals name | name equals literal |
| func (p *Parser) parseAttributes() (attributes *mojom.Attributes) { |
| if !p.OK() { |
| return |
| } |
| |
| if !p.tryMatch(lexer.LBracket) { |
| // There is no attributes section here |
| return |
| } |
| |
| p.pushChildNode("attributes") |
| defer p.popNode() |
| |
| attributes = mojom.NewAttributes(p.lastConsumed) |
| |
| nextToken := p.lastConsumed |
| for nextToken.Kind != lexer.RBracket { |
| key := p.readName() |
| keyToken := p.lastConsumed |
| p.attachToken() |
| if !p.OK() { |
| return |
| } |
| if !p.match(lexer.Equals) { |
| return |
| } |
| |
| var value mojom.LiteralValue |
| if p.peekNextToken("Expecting to find an attribute value.").Kind == lexer.Name { |
| text := p.readName() |
| // TODO(rudominer) Decide if we support attribute values as Names. |
| // Currently we convert a name into a string literal here. |
| // See https://github.com/domokit/mojo/issues/561 |
| valueToken := p.lastConsumed |
| value = mojom.MakeStringLiteralValue(text, &valueToken) |
| } else { |
| value = p.parseLiteral() |
| } |
| p.attachToken() |
| |
| if !p.OK() { |
| return |
| } |
| attributes.List = append(attributes.List, mojom.NewMojomAttribute(key, &keyToken, value)) |
| |
| nextToken = p.peekNextToken("I was reading an attributes section.") |
| if !p.OK() { |
| return |
| } |
| p.consumeNextToken() |
| if nextToken.Kind != lexer.RBracket && nextToken.Kind != lexer.Comma { |
| switch nextToken.Kind { |
| case lexer.Module, lexer.Interface, lexer.Struct, lexer.Union, lexer.Enum: |
| message := fmt.Sprintf("The attribute section is missing a closing ] before %s.", nextToken) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, nextToken) |
| return |
| default: |
| p.unexpectedTokenError(nextToken, "comma or ]") |
| return |
| } |
| } |
| } |
| |
| return |
| } |
| |
| //MODULE_DECL -> module identifier semi |
| // |
| // parseModuleDecl returns a pointer to a ModuleNamespace. If there is not a |
| // module declaration, its Identifier is an empty string. Check p.OK() for errors. |
| func (p *Parser) parseModuleDecl() (moduleNamespace *mojom.ModuleNamespace) { |
| moduleNamespace = mojom.NewModuleNamespace("", nil) |
| if !p.OK() { |
| return |
| } |
| if p.checkEOF() { |
| return |
| } |
| nextToken := p.peekNextToken("No Mojom declarations found.") |
| if !p.OK() { |
| return |
| } |
| switch nextToken.Kind { |
| case lexer.Module: |
| p.consumeNextToken() // Consume the MODULE token. |
| break |
| case lexer.Import, lexer.Interface, lexer.Struct, lexer.Union, lexer.Enum, lexer.Const: |
| return // There is no module declaration. |
| default: |
| p.unexpectedTokenError(nextToken, "module, import, interface, struct, union, enum or constant") |
| return |
| } |
| |
| p.pushChildNode("moduleDecl") |
| defer p.popNode() |
| |
| moduleIdentifier, identifierToken := p.parseIdentifier() |
| moduleNamespace.Identifier = moduleIdentifier |
| moduleNamespace.Token = &identifierToken |
| p.matchSemicolon() |
| return |
| } |
| |
| // IMPORT_STMNT -> import string_literal |
| // |
| // Returns a non-nil, but possibly empty, slice of ImportedFiles. |
| func (p *Parser) parseImportStatements() (imports []*mojom.ImportedFile) { |
| imports = make([]*mojom.ImportedFile, 0) |
| if !p.OK() { |
| return |
| } |
| |
| nextToken := p.peekNextToken("") |
| if !p.OK() { |
| return |
| } |
| for nextToken.Kind == lexer.Import { |
| p.pushChildNode("importStmnt") |
| defer p.popNode() |
| |
| p.consumeNextToken() // consume the IMPORT token. |
| |
| fileName := p.readStringLiteral() |
| fileNameToken := p.lastConsumed |
| p.attachToken() |
| if !p.OK() { |
| return |
| } |
| |
| if !p.matchSemicolon() { |
| return |
| } |
| |
| imports = append(imports, mojom.NewImportedFile(fileName, &fileNameToken)) |
| |
| if p.checkEOF() { |
| // Allow a .mojom file containing only import statements. |
| return |
| } |
| nextToken = p.peekNextToken("") |
| if !p.OK() { |
| return |
| } |
| } |
| |
| switch nextToken.Kind { |
| case lexer.Module: |
| message := "The module declaration must come before the import statements." |
| p.parseError(ParserErrorCodeUnexpectedToken, message) |
| return |
| case lexer.Interface, lexer.Struct, lexer.Union, lexer.Enum, lexer.Const, lexer.LBracket: |
| return |
| default: |
| p.unexpectedTokenError(nextToken, "import, attributes, interface, struct, union, enum or constant") |
| return |
| } |
| } |
| |
| // INTRFC_DECL -> interface name lbrace INTRFC_BODY rbrace semi |
| func (p *Parser) parseInterfaceDecl(attributes *mojom.Attributes) (mojomInterface *mojom.MojomInterface) { |
| if !p.OK() { |
| return |
| } |
| p.pushChildNode("interfaceDecl") |
| defer p.popNode() |
| |
| if !p.match(lexer.Interface) { |
| return |
| } |
| |
| simpleName := p.readName() |
| if !p.OK() { |
| return |
| } |
| nameToken := p.lastConsumed |
| mojomInterface = mojom.NewMojomInterface(p.DeclData(simpleName, nameToken, attributes)) |
| |
| if !p.match(lexer.LBrace) { |
| return |
| } |
| |
| if !p.parseInterfaceBody(mojomInterface) { |
| return |
| } |
| |
| if !p.match(lexer.RBrace) { |
| return |
| } |
| |
| closingBraceToken := p.lastConsumed |
| mojomInterface.SetClosingBraceToken(&closingBraceToken) |
| |
| p.matchSemicolon() |
| |
| return |
| } |
| |
| // INTRFC_BODY -> {ATTR_INTRFC_ELEMENT} |
| // ATTR_INTRFC_ELEMENT -> [ATTRIBUTES] INTRFC_ELEMENT |
| // INTRFC_ELEMENT -> METHOD_DECL | ENUM_DECL | CONSTANT_DECL |
| func (p *Parser) parseInterfaceBody(mojomInterface *mojom.MojomInterface) bool { |
| if !p.OK() { |
| return p.OK() |
| } |
| p.pushChildNode("interfaceBody") |
| defer p.popNode() |
| |
| // The interface body forms a new scope. |
| p.pushScope(mojomInterface.InitAsScope(p.currentScope)) |
| defer p.popScope() |
| |
| rbraceFound := false |
| for attributes := p.parseAttributes(); !rbraceFound; attributes = p.parseAttributes() { |
| if !p.OK() { |
| return false |
| } |
| nextToken := p.peekNextToken("I was parsing an interface body.") |
| var duplicateNameError error = nil |
| switch nextToken.Kind { |
| case lexer.Name: |
| if method := p.parseMethodDecl(attributes); p.OK() { |
| duplicateNameError = mojomInterface.AddMethod(method) |
| break |
| } |
| return false |
| case lexer.Enum: |
| if mojomEnum := p.parseEnumDecl(attributes); mojomEnum != nil { |
| duplicateNameError = mojomInterface.AddEnum(mojomEnum) |
| } |
| case lexer.Const: |
| if constant := p.parseConstDecl(attributes); constant != nil { |
| duplicateNameError = mojomInterface.AddConstant(constant) |
| } |
| case lexer.RBrace: |
| rbraceFound = true |
| if attributes != nil { |
| message := "Interface body ends with extraneouss attributes." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| } |
| break |
| default: |
| p.unexpectedTokenError(nextToken, "union, enum, const or an identifier") |
| return false |
| } |
| if p.OK() && duplicateNameError != nil { |
| p.err = duplicateNameError |
| return false |
| } |
| } |
| if p.OK() { |
| if err := mojomInterface.ComputeMethodOrdinals(); err != nil { |
| p.err = err |
| return false |
| } |
| } |
| return p.OK() |
| } |
| |
| // METHOD_DECL -> name [ordinal] lparen [PARAM_LIST] rparen [response lparen [PARAM_LIST] rparen]semi |
| func (p *Parser) parseMethodDecl(attributes *mojom.Attributes) *mojom.MojomMethod { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("methodDecl") |
| defer p.popNode() |
| |
| methodName := p.readName() |
| nameToken := p.lastConsumed |
| if !p.OK() { |
| return nil |
| } |
| |
| ordinalValue, err := p.readOrdinal() |
| if err != nil { |
| p.invalidOrdinalError("method", methodName, nameToken, err) |
| return nil |
| } |
| |
| if !p.match(lexer.LParen) { |
| return nil |
| } |
| |
| params := p.parseParamList(methodName, true) |
| if !p.OK() { |
| return nil |
| } |
| |
| if !p.match(lexer.RParen) { |
| return nil |
| } |
| parenBeforeSemicolon := p.lastConsumed |
| |
| // Check for a response message |
| var responseParams *mojom.MojomStruct = nil |
| if p.tryMatch(lexer.Response) { |
| if !p.match(lexer.LParen) { |
| return nil |
| } |
| |
| responseParams = p.parseParamList(methodName, false) |
| if !p.OK() { |
| return nil |
| } |
| |
| if !p.match(lexer.RParen) { |
| return nil |
| } |
| parenBeforeSemicolon = p.lastConsumed |
| } |
| |
| if !p.matchSemicolonToken(parenBeforeSemicolon) { |
| return nil |
| } |
| |
| declData := p.DeclDataWithOrdinal(methodName, nameToken, attributes, ordinalValue) |
| mojomMethod := mojom.NewMojomMethod(declData, params, responseParams) |
| return mojomMethod |
| } |
| |
| // PARAM_LIST -> PARAM_DECL {, PARAM_DECL} |
| // PARAM_DECL -> [ATTRIBUTES] TYPE name [ordinal] |
| // |
| // Returns a MojomStruct containing the list of parameters. This may |
| // be nil in case of an early error. Check Parser.OK(). |
| func (p *Parser) parseParamList(methodName string, isRequest bool) (paramStruct *mojom.MojomStruct) { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("paramList") |
| defer p.popNode() |
| |
| if isRequest { |
| paramStruct = mojom.NewSyntheticRequestStruct(methodName, lexer.Token{}, p.mojomFile) |
| } else { |
| paramStruct = mojom.NewSyntheticResponseStruct(methodName, lexer.Token{}, p.mojomFile) |
| } |
| nextToken := p.peekNextToken("I was parsing method parameters.") |
| for nextToken.Kind != lexer.RParen { |
| |
| attributes := p.parseAttributes() |
| fieldType := p.parseType() |
| if !p.OK() { |
| return |
| } |
| name := p.readName() |
| nameToken := p.lastConsumed |
| |
| if !p.OK() { |
| return |
| } |
| |
| ordinalValue, err := p.readOrdinal() |
| if err != nil { |
| p.invalidOrdinalError("parameter", name, nameToken, err) |
| return |
| } |
| |
| declData := p.DeclDataWithOrdinal(name, nameToken, attributes, ordinalValue) |
| if duplicateNameError := paramStruct.AddField(mojom.NewStructField( |
| declData, fieldType, nil)); duplicateNameError != nil { |
| p.err = duplicateNameError |
| return nil |
| } |
| |
| nextToken = p.peekNextToken("I was parsing method parameters.") |
| switch nextToken.Kind { |
| case lexer.Comma: |
| p.consumeNextToken() |
| continue |
| case lexer.RParen: |
| continue |
| default: |
| p.unexpectedTokenError(nextToken, "comma or )") |
| return nil |
| } |
| } |
| |
| if p.OK() { |
| if err := paramStruct.ComputeFieldOrdinals(); err != nil { |
| p.err = err |
| return nil |
| } |
| } |
| return |
| } |
| |
| // STRUCT_DECL -> struct name lbrace STRUCT_BODY rbrace semi |
| func (p *Parser) parseStructDecl(attributes *mojom.Attributes) (mojomStruct *mojom.MojomStruct) { |
| if !p.OK() { |
| return |
| } |
| p.pushChildNode("structDecl") |
| defer p.popNode() |
| |
| if !p.match(lexer.Struct) { |
| return |
| } |
| |
| simpleName := p.readName() |
| p.attachToken() |
| if !p.OK() { |
| return |
| } |
| nameToken := p.lastConsumed |
| mojomStruct = mojom.NewMojomStruct(p.DeclData(simpleName, nameToken, attributes)) |
| |
| if !p.match(lexer.LBrace) { |
| return |
| } |
| |
| if !p.parseStructBody(mojomStruct) { |
| return |
| } |
| |
| if !p.match(lexer.RBrace) { |
| return |
| } |
| |
| closingBraceToken := p.lastConsumed |
| mojomStruct.SetClosingBraceToken(&closingBraceToken) |
| |
| p.matchSemicolon() |
| |
| return |
| } |
| |
| // STRUCT_BODY -> {ATTR_STRUCT_ELEMENT} |
| // ATTR_STRUCT_ELEMENT -> [ATTRIBUTES] STRUCT_ELEMENT |
| // STRUCT_ELEMENT -> STRUCT_FIELD | ENUM_DECL | CONSTANT_DECL |
| func (p *Parser) parseStructBody(mojomStruct *mojom.MojomStruct) bool { |
| if !p.OK() { |
| return p.OK() |
| } |
| p.pushChildNode("structBody") |
| defer p.popNode() |
| |
| // The struct body forms a new scope. |
| p.pushScope(mojomStruct.InitAsScope(p.currentScope)) |
| defer p.popScope() |
| |
| rbraceFound := false |
| for attributes := p.parseAttributes(); !rbraceFound; attributes = p.parseAttributes() { |
| if !p.OK() { |
| return false |
| } |
| nextToken := p.peekNextToken("I was parsing a struct body.") |
| var duplicateNameError error = nil |
| switch nextToken.Kind { |
| case lexer.Name: |
| if field := p.parseStructField(attributes); field != nil { |
| duplicateNameError = mojomStruct.AddField(field) |
| } |
| case lexer.Enum: |
| if mojomEnum := p.parseEnumDecl(attributes); mojomEnum != nil { |
| duplicateNameError = mojomStruct.AddEnum(mojomEnum) |
| } |
| case lexer.Const: |
| if constant := p.parseConstDecl(attributes); constant != nil { |
| duplicateNameError = mojomStruct.AddConstant(constant) |
| } |
| case lexer.RBrace: |
| rbraceFound = true |
| if attributes != nil { |
| message := "Struct body ends with extraneouss attributes." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| } |
| break |
| default: |
| p.unexpectedTokenError(nextToken, "field, enum or constant declaration") |
| return false |
| } |
| if p.OK() && duplicateNameError != nil { |
| p.err = duplicateNameError |
| return false |
| } |
| } |
| if p.OK() { |
| if err := mojomStruct.ComputeFieldOrdinals(); err != nil { |
| p.err = err |
| return false |
| } |
| } |
| |
| return p.OK() |
| } |
| |
| // STRUCT_FIELD -> TYPE name [ordinal] [equals DEFAULT_VALUE] semi |
| // DEFAULT_VALUE -> COMPATIBLE_VALUE_REF | default |
| func (p *Parser) parseStructField(attributes *mojom.Attributes) *mojom.StructField { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("structField") |
| defer p.popNode() |
| |
| fieldType := p.parseType() |
| fieldName := p.readName() |
| nameToken := p.lastConsumed |
| p.attachToken() |
| |
| ordinalValue, err := p.readOrdinal() |
| if err != nil { |
| p.invalidOrdinalError("field", fieldName, nameToken, err) |
| return nil |
| } |
| |
| var defaultValue mojom.ValueRef |
| var defaultValueToken lexer.Token |
| defaultKeywordUsed := false |
| if p.tryMatch(lexer.Equals) { |
| defaultValueToken = p.peekNextToken("Expecting a default value.") |
| if defaultValueToken.Kind == lexer.Default { |
| p.consumeNextToken() |
| defaultToken := p.lastConsumed |
| defaultValue = mojom.MakeDefaultLiteral(&defaultToken) |
| defaultKeywordUsed = true |
| } else { |
| assigneeSpec := mojom.AssigneeSpec{ |
| fieldName, |
| fieldType, |
| } |
| defaultValue = p.parseValue(assigneeSpec) |
| } |
| } |
| if !p.matchSemicolon() { |
| return nil |
| } |
| |
| declData := p.DeclDataWithOrdinal(fieldName, nameToken, attributes, ordinalValue) |
| field := mojom.NewStructField(declData, fieldType, defaultValue) |
| if !field.ValidateDefaultValue() { |
| var message string |
| if defaultKeywordUsed { |
| message = fmt.Sprintf("Illegal assignment: The 'default' keyword may not be used with the field %s of type %s.", |
| fieldName, fieldType.TypeName()) |
| } else { |
| valueString := fmt.Sprintf("%v", defaultValue) |
| valueTypeString := "" |
| concreteValue := defaultValue.ResolvedConcreteValue() |
| if concreteValue != nil { |
| valueString = fmt.Sprintf("%v", concreteValue) |
| valueTypeString = fmt.Sprintf(" of type %s", concreteValue.ValueType()) |
| } |
| message = fmt.Sprintf("Illegal assignment: Field %s of type %s may not be assigned the value %s%s.", |
| fieldName, fieldType.TypeName(), valueString, valueTypeString) |
| } |
| p.parseErrorT(ParserErrorCodeNotAssignmentCompatible, message, defaultValueToken) |
| return nil |
| } |
| |
| return field |
| } |
| |
| // UNION_DECL -> union name lbrace UNION_BODY rbrace semi |
| func (p *Parser) parseUnionDecl(attributes *mojom.Attributes) (union *mojom.MojomUnion) { |
| if !p.OK() { |
| return |
| } |
| p.pushChildNode("unionDecl") |
| defer p.popNode() |
| |
| if !p.match(lexer.Union) { |
| return |
| } |
| |
| simpleName := p.readName() |
| p.attachToken() |
| if !p.OK() { |
| return |
| } |
| nameToken := p.lastConsumed |
| union = mojom.NewMojomUnion(p.DeclData(simpleName, nameToken, attributes)) |
| |
| if !p.match(lexer.LBrace) { |
| return |
| } |
| |
| if !p.parseUnionBody(union) { |
| return |
| } |
| |
| if !p.match(lexer.RBrace) { |
| return |
| } |
| |
| closingBraceToken := p.lastConsumed |
| union.SetClosingBraceToken(&closingBraceToken) |
| |
| if p.matchSemicolon() { |
| if err := union.ComputeFieldTags(); err != nil { |
| p.err = err |
| return |
| } |
| } |
| |
| return |
| } |
| |
| // UNION_BODY -> {UNION_FIELD_DECL} |
| // UNION_FIELD_DECL -> [ATTRIBUTES] TYPE name [ordinal] semi |
| func (p *Parser) parseUnionBody(union *mojom.MojomUnion) bool { |
| if !p.OK() { |
| return p.OK() |
| } |
| p.pushChildNode("unionBody") |
| defer p.popNode() |
| |
| rbraceFound := false |
| for attributes := p.parseAttributes(); !rbraceFound; attributes = p.parseAttributes() { |
| if !p.OK() { |
| return false |
| } |
| nextToken := p.peekNextToken("I was parsing a union body.") |
| switch nextToken.Kind { |
| case lexer.Name: |
| fieldType := p.parseType() |
| fieldName := p.readName() |
| nameToken := p.lastConsumed |
| |
| tag, err := p.readOrdinal() |
| if err != nil { |
| p.invalidOrdinalError("union field", fieldName, nameToken, err) |
| return false |
| } |
| |
| if !p.matchSemicolon() { |
| return false |
| } |
| if duplicateNameError := union.AddField(p.DeclDataWithOrdinal( |
| fieldName, nameToken, attributes, tag), fieldType); duplicateNameError != nil { |
| p.err = duplicateNameError |
| return false |
| |
| } |
| case lexer.RBrace: |
| rbraceFound = true |
| if attributes != nil { |
| message := "Enum body ends with extraneouss attributes." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| } |
| break |
| default: |
| p.unexpectedTokenError(nextToken, "either another union field or }") |
| return false |
| } |
| } |
| return p.OK() |
| } |
| |
| // ENUM_DECL -> enum name lbrace ENUM_BODY rbrace semi |
| func (p *Parser) parseEnumDecl(attributes *mojom.Attributes) (enum *mojom.MojomEnum) { |
| if !p.OK() { |
| return |
| } |
| p.pushChildNode("enumDecl") |
| defer p.popNode() |
| |
| if !p.match(lexer.Enum) { |
| return |
| } |
| |
| simpleName := p.readName() |
| p.attachToken() |
| if !p.OK() { |
| return |
| } |
| nameToken := p.lastConsumed |
| enum = mojom.NewMojomEnum(p.DeclData(simpleName, nameToken, attributes)) |
| |
| if !p.match(lexer.LBrace) { |
| return |
| } |
| |
| if !p.parseEnumBody(enum) { |
| return |
| } |
| |
| if !p.match(lexer.RBrace) { |
| return |
| } |
| |
| closingBraceToken := p.lastConsumed |
| enum.SetClosingBraceToken(&closingBraceToken) |
| |
| p.matchSemicolon() |
| |
| return |
| } |
| |
| // ENUM_BODY -> [ ENUN_VALUE {, ENUM_VALUE} [,] ] |
| // ENUM_VALUE -> [ATTRIBUTES] name [equals ENUM_VAL_INITIALIZER] |
| func (p *Parser) parseEnumBody(mojomEnum *mojom.MojomEnum) bool { |
| if !p.OK() { |
| return p.OK() |
| } |
| p.pushChildNode("enumBody") |
| defer p.popNode() |
| |
| // The enum body forms a new scope in which it's enun values are defined. |
| p.pushScope(mojomEnum.InitAsScope(p.currentScope)) |
| defer p.popScope() |
| |
| rbraceFound := false |
| trailingCommaFound := false |
| firstValue := true |
| for attributes := p.parseAttributes(); !rbraceFound; attributes = p.parseAttributes() { |
| if !p.OK() { |
| return false |
| } |
| nextToken := p.peekNextToken("I was parsing an enum body.") |
| var duplicateNameError error = nil |
| switch nextToken.Kind { |
| case lexer.Name: |
| if !firstValue && !trailingCommaFound { |
| message := fmt.Sprintf("Expecting a comma after %s before the next value %s.", |
| p.lastConsumed, nextToken) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, p.lastConsumed) |
| return false |
| } |
| firstValue = false |
| name := p.readName() |
| p.attachToken() |
| nameToken := p.lastConsumed |
| var valueRef mojom.ValueRef |
| if p.tryMatch(lexer.Equals) { |
| valueRef = p.parseEnumValueInitializer(mojomEnum, name) |
| } |
| declData := p.DeclData(name, nameToken, attributes) |
| duplicateNameError = mojomEnum.AddEnumValue(declData, valueRef) |
| trailingCommaFound = p.tryMatch(lexer.Comma) |
| case lexer.RBrace: |
| rbraceFound = true |
| if attributes != nil { |
| message := "Enum body ends with extraneouss attributes." |
| p.parseError(ParserErrorCodeBadAttributeLocation, message) |
| } |
| break |
| case lexer.Comma: |
| break |
| default: |
| p.unexpectedTokenError(nextToken, "either another enum value or }") |
| return false |
| } |
| if p.OK() && duplicateNameError != nil { |
| p.err = duplicateNameError |
| return false |
| } |
| } |
| return p.OK() |
| } |
| |
| // ENUM_VAL_INITIALIZER -> INT32_VAL | APPROPRIATE_ENUM_VALUE_REF |
| func (p *Parser) parseEnumValueInitializer(mojoEnum *mojom.MojomEnum, valueName string) mojom.ValueRef { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("enumValueInitializer") |
| defer p.popNode() |
| |
| // We need to manufacture an instance of Type to act as the "assigneeType" |
| // for the new ValueSpec we are creating. This is because unlike |
| // other types of value assignment, an enum value initializer is not |
| // preceded by a type reference for the assignee. Rather the type of |
| // the assignee is implicit in the scope. |
| enumType := mojom.NewResolvedUserTypeRef(mojoEnum.FullyQualifiedName(), mojoEnum) |
| |
| valueToken := p.peekNextToken("Parsing an enum value initializer type.") |
| valueRef := p.parseValue(mojom.AssigneeSpec{valueName, enumType}) |
| if valueRef == nil { |
| return nil |
| } |
| if !valueRef.MarkUsedAsEnumValueInitializer() { |
| message := fmt.Sprintf("Illegal value: %s. An enum value initializer must be a signed 32-bit integer value.", |
| valueToken) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, valueToken) |
| return nil |
| } |
| |
| return valueRef |
| } |
| |
| // CONSTANT_DECL -> const CONST_OK_TYPE name equals COMPATIBLE_VALUE_REF semi |
| // CONST_OK_TYPE -> SIMPLE_TYPE | string | ENUM_TYPE |
| func (p *Parser) parseConstDecl(attributes *mojom.Attributes) (constant *mojom.UserDefinedConstant) { |
| if !p.OK() { |
| return |
| } |
| |
| p.pushChildNode("constDecl") |
| defer p.popNode() |
| |
| p.match(lexer.Const) |
| declaredTypeToken := p.peekNextToken("Parsing a type.") |
| declaredType := p.parseType() |
| name := p.readName() |
| if !p.OK() { |
| return |
| } |
| nameToken := p.lastConsumed |
| p.match(lexer.Equals) |
| valueToken := p.peekNextToken("Parsing a value.") |
| assigneeSpec := mojom.AssigneeSpec{ |
| name, |
| declaredType, |
| } |
| value := p.parseValue(assigneeSpec) |
| if !p.OK() { |
| return |
| } |
| |
| if !declaredType.MarkUsedAsConstantType() { |
| message := fmt.Sprintf("The type %s is not allowed as the type of a declared constant. "+ |
| "Only simple types, strings and enum types may be the types of constants.", declaredType) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, declaredTypeToken) |
| return |
| } |
| |
| constant = mojom.NewUserDefinedConstant(p.DeclData(name, nameToken, attributes), declaredType, value) |
| p.matchSemicolon() |
| |
| if !constant.ValidateValue() { |
| valueString := fmt.Sprintf("%v", value) |
| valueTypeString := "" |
| concreteValue := value.ResolvedConcreteValue() |
| if concreteValue != nil { |
| valueString = fmt.Sprintf("%v", concreteValue) |
| valueTypeString = fmt.Sprintf(" of type %s", concreteValue.ValueType()) |
| } |
| message := fmt.Sprintf("Illegal assignment: Constant %s of type %s may not be assigned the value %s%s.", |
| name, declaredType.TypeName(), valueString, valueTypeString) |
| p.parseErrorT(ParserErrorCodeNotAssignmentCompatible, message, valueToken) |
| return |
| } |
| |
| return |
| } |
| |
| // VALUE_REF -> USER_VALUE_REF | LITERAL_VALUE | BUILT_IN_FLOAT_CONST |
| func (p *Parser) parseValue(assigneeSpec mojom.AssigneeSpec) mojom.ValueRef { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("value") |
| defer p.popNode() |
| |
| nextToken := p.peekNextToken("I was parsing a value.") |
| p.attachToken() |
| if nextToken.Kind == lexer.Name { |
| // Note that we do not distinguish between a USER_VALUE_REF and a |
| // BUILT_IN_FLOAT_CONST at parsing time. At this stage we consider both |
| // to be a UserValueRef. Only at resolution time do we decide if it |
| // turns out to be a BUILT_IN_FLOAT_CONST. |
| return p.parseUserValueRef(assigneeSpec) |
| } |
| literalValue := p.parseLiteral() |
| p.attachToken() |
| if !p.OK() || literalValue.ValueType() == nil { |
| return nil |
| } |
| return literalValue |
| } |
| |
| //LITERAL_VALUE -> BOOL_LITERAL | string_literal | NUMBER_LITERAL |
| // BOOL_LITERAL -> true | false |
| func (p *Parser) parseLiteral() mojom.LiteralValue { |
| if !p.OK() { |
| return mojom.LiteralValue{} |
| } |
| p.pushChildNode("literal") |
| defer p.popNode() |
| |
| nextToken := p.peekNextToken("I was parsing a literal.") |
| switch nextToken.Kind { |
| case lexer.StringLiteral: |
| literal := p.readStringLiteral() |
| valueToken := p.lastConsumed |
| return mojom.MakeStringLiteralValue(literal, &valueToken) |
| case lexer.True: |
| p.consumeNextToken() |
| valueToken := p.lastConsumed |
| return mojom.MakeBoolLiteralValue(true, &valueToken) |
| case lexer.False: |
| p.consumeNextToken() |
| valueToken := p.lastConsumed |
| return mojom.MakeBoolLiteralValue(false, &valueToken) |
| case lexer.Plus, lexer.Minus, lexer.FloatConst, lexer.IntConstDec, lexer.IntConstHex: |
| return p.parseNumberLiteral() |
| |
| default: |
| p.unexpectedTokenError(nextToken, "a string, numeric or boolean literal") |
| return mojom.LiteralValue{} |
| } |
| } |
| |
| // NUMBER_LITERAL -> [plus | minus] POS_NUM_LITERAL |
| // POS_NUM_LITERAL -> POS_INT_LITERAL | POS_FLOAT_LITERAL |
| func (p *Parser) parseNumberLiteral() mojom.LiteralValue { |
| if !p.OK() { |
| return mojom.LiteralValue{} |
| } |
| p.pushChildNode("numberLiteral") |
| defer p.popNode() |
| |
| initialMinus := p.tryMatch(lexer.Minus) |
| initialPlus := p.tryMatch(lexer.Plus) |
| if initialMinus && initialPlus { |
| p.unexpectedTokenError(p.lastConsumed, "a number") |
| return mojom.LiteralValue{} |
| } |
| |
| nextToken := p.peekNextToken("I was parsing a numberliteral.") |
| switch nextToken.Kind { |
| case lexer.IntConstDec, lexer.IntConstHex: |
| absoluteValue, numBits, signed, ok := p.parsePositiveIntegerLiteral(initialMinus, true, !initialMinus, 64) |
| valueToken := p.lastConsumed |
| if !ok { |
| return mojom.LiteralValue{} |
| } |
| var value int64 |
| if signed { |
| value = int64(absoluteValue) |
| if initialMinus { |
| value = -1 * value |
| } |
| } |
| switch numBits { |
| case 8: |
| if signed { |
| return mojom.MakeInt8LiteralValue(int8(value), &valueToken) |
| } else { |
| return mojom.MakeUint8LiteralValue(uint8(absoluteValue), &valueToken) |
| } |
| case 16: |
| if signed { |
| return mojom.MakeInt16LiteralValue(int16(value), &valueToken) |
| } else { |
| return mojom.MakeUint16LiteralValue(uint16(absoluteValue), &valueToken) |
| } |
| case 32: |
| if signed { |
| return mojom.MakeInt32LiteralValue(int32(value), &valueToken) |
| } else { |
| return mojom.MakeUint32LiteralValue(uint32(absoluteValue), &valueToken) |
| } |
| case 64: |
| if signed { |
| return mojom.MakeInt64LiteralValue(value, &valueToken) |
| } else { |
| return mojom.MakeUint64LiteralValue(absoluteValue, &valueToken) |
| } |
| default: |
| panic(fmt.Sprintf("Unexpected value for numBits: %d", numBits)) |
| } |
| case lexer.FloatConst: |
| value, _ := p.parsePositiveFloatLiteral(initialMinus) |
| valueToken := p.lastConsumed |
| return mojom.MakeDoubleLiteralValue(value, &valueToken) |
| |
| default: |
| p.unexpectedTokenError(nextToken, "a number") |
| return mojom.LiteralValue{} |
| } |
| } |
| |
| // IDENTIFIER -> name {dot name} |
| func (p *Parser) parseIdentifier() (identifier string, firstToken lexer.Token) { |
| if !p.OK() { |
| return |
| } |
| p.pushChildNode("identifier") |
| defer p.popNode() |
| |
| firstToken = p.peekNextToken("Expecting an identifier.") |
| if !p.OK() { |
| return |
| } |
| if firstToken.Kind != lexer.Name { |
| p.unexpectedTokenError(firstToken, "an identifier") |
| return |
| } |
| |
| for p.tryMatch(lexer.Name) { |
| identifier += p.lastConsumed.Text |
| if !p.tryMatch(lexer.Dot) { |
| return |
| } |
| identifier += "." |
| } |
| message := fmt.Sprintf("Invalid identifier: %q. Identifiers may not end with a dot.", identifier) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, firstToken) |
| return |
| } |
| |
| // TYPE -> BUILT_IN_TYPE | ARRAY_TYPE | MAP_TYPE | USER_TYPE_REF |
| func (p *Parser) parseType() mojom.TypeRef { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("type") |
| defer p.popNode() |
| |
| mojomType := p.tryParseBuiltInType() |
| if mojomType == nil { |
| mojomType = p.tryParseArrayType() |
| } |
| if mojomType == nil { |
| mojomType = p.tryParseMapType() |
| } |
| if mojomType == nil { |
| mojomType = p.parseTypeReference() |
| } |
| |
| return mojomType |
| } |
| |
| ///////////////// Methods for parsing types and values //////// |
| |
| // BUILT_IN_TYPE -> SIMPLE_TYPE | STRING_TYPE | HANDLE_TYPE |
| // SIMPLE_TYPE -> bool | FLOAT_TYPE | INTEGER_TYPE |
| // FLOAT_TYPE -> float | double |
| // HANDLE_TYPE -> handle langle [HANDLE_KIND] rangle [qstn] |
| // HANDLE_KIND -> message_pipe | data_pipe_consumer | data_pipe_producer | shared_buffer |
| // INTEGER_TYPE -> int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
| // STRING_TYPE -> string [qstn] |
| func (p *Parser) tryParseBuiltInType() mojom.TypeRef { |
| if !p.OK() { |
| return nil |
| } |
| |
| typeNameToken := p.peekNextToken("I was reading a type.") |
| if typeNameToken.Kind != lexer.Name { |
| return nil |
| } |
| typeName := typeNameToken.Text |
| builtInType := mojom.BuiltInType(typeName) |
| if builtInType == nil { |
| return nil |
| } |
| p.consumeNextToken() |
| |
| // handle<*> types |
| if typeName == "handle" { |
| if p.tryMatch(lexer.LAngle) { |
| handleType := p.readText(lexer.Name) |
| if !p.OK() { |
| token := p.lastSeen |
| p.unexpectedTokenError(token, "a type of handle") |
| return nil |
| } |
| if p.match(lexer.RAngle) { |
| typeName = fmt.Sprintf("%s<%s>", typeName, handleType) |
| if builtInType = mojom.BuiltInType(typeName); builtInType == nil { |
| message := fmt.Sprintf("Unrecognized type of handle: %s.", typeName) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, typeNameToken) |
| return nil |
| } |
| } |
| } |
| if !p.OK() { |
| return nil |
| } |
| } |
| |
| // Check for nullable marker |
| if p.tryMatch(lexer.Qstn) { |
| if builtInType = mojom.BuiltInType(typeName + "?"); builtInType == nil { |
| message := fmt.Sprintf("The type %s? is invalid because the type %s may not be made nullable.", |
| typeName, typeName) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, typeNameToken) |
| return nil |
| } |
| } |
| |
| return builtInType |
| } |
| |
| // ARRAY_TYPE -> array langle TYPE [comma int_const_dec] rangle [qstn] |
| func (p *Parser) tryParseArrayType() mojom.TypeRef { |
| if !p.OK() { |
| return nil |
| } |
| |
| nextToken := p.peekNextToken("Trying to read a type.") |
| if nextToken.Kind != lexer.Name || nextToken.Text != "array" { |
| return nil |
| } |
| p.consumeNextToken() |
| if !p.match(lexer.LAngle) { |
| return nil |
| } |
| |
| elementType := p.parseType() |
| if !p.OK() { |
| return nil |
| } |
| |
| isFixedSize := p.tryMatch(lexer.Comma) |
| fixedSize := int32(-1) |
| if isFixedSize { |
| if size, _, _, ok := p.parsePositiveIntegerLiteral(false, false, false, 32); ok { |
| fixedSize = int32(size) |
| } else { |
| return nil |
| } |
| |
| } |
| if !p.match(lexer.RAngle) { |
| return nil |
| } |
| nullable := p.tryMatch(lexer.Qstn) |
| |
| return mojom.NewArrayTypeRef(elementType, fixedSize, nullable) |
| } |
| |
| // MAP_TYPE -> map langle MAP_KEY_TYPE comma TYPE rangle [qstn] |
| // MAP_KEY_TYPE -> SIMPLE_TYPE | string | ENUM_TYPE |
| func (p *Parser) tryParseMapType() mojom.TypeRef { |
| if !p.OK() { |
| return nil |
| } |
| |
| nextToken := p.peekNextToken("Trying to read a type.") |
| if nextToken.Kind != lexer.Name || nextToken.Text != "map" { |
| return nil |
| } |
| p.consumeNextToken() |
| if !p.match(lexer.LAngle) { |
| return nil |
| } |
| |
| keyToken := p.peekNextToken("Trying to read a type.") |
| keyType := p.parseType() |
| p.match(lexer.Comma) |
| valueType := p.parseType() |
| if !p.match(lexer.RAngle) { |
| return nil |
| } |
| nullable := p.tryMatch(lexer.Qstn) |
| |
| if !keyType.MarkUsedAsMapKey() { |
| message := fmt.Sprintf("The type %s is not allowed as the key type of a map. "+ |
| "Only simple types, strings and enum types may be map keys.", keyType) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, keyToken) |
| return nil |
| } |
| |
| return mojom.NewMapTypeRef(keyType, valueType, nullable) |
| } |
| |
| // USER_TYPE_REF -> INTERFACE_TYPE | STRUCT_TYPE | UNION_TYPE | ENUM_TYPE |
| // INTERFACE_TYPE -> IDENTIFIER [amp] [qstn] {{where IDENTIFIER resolves to an interface}} |
| // STRUCT_TYPE -> IDENTIFIER [qstn] {{where IDENTIFIER resolves to a struct}} |
| // UNION_TYPE -> IDENTIFIER [qstn] {{where IDENTIFIER resolves to a union}} |
| // ENUM_TYPE -> IDENTIFIER [qstn] {{where IDENTIFIER resolves to an interface}} |
| func (p *Parser) parseTypeReference() mojom.TypeRef { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("typeReference") |
| defer p.popNode() |
| |
| identifier, identifierToken := p.parseIdentifier() |
| if !p.OK() { |
| return nil |
| } |
| // Note we must check for '&' before we check for '?'. |
| // The other order is invalid and if it occurrs the extraneous |
| // '&' will be detected later. |
| interfaceRequest := p.tryMatch(lexer.Amp) |
| nullable := p.tryMatch(lexer.Qstn) |
| if !p.OK() { |
| return nil |
| } |
| typeReference := mojom.NewUserTypeRef(identifier, nullable, |
| interfaceRequest, p.currentScope, identifierToken) |
| p.mojomDescriptor.RegisterUnresolvedTypeReference(typeReference) |
| return typeReference |
| } |
| |
| // USER_VALUE_REF -> IDENTIFIER {{that resolves to an enum value or constant}} |
| func (p *Parser) parseUserValueRef(assigneeSpec mojom.AssigneeSpec) *mojom.UserValueRef { |
| if !p.OK() { |
| return nil |
| } |
| p.pushChildNode("userValueRef") |
| defer p.popNode() |
| |
| identifier, identifierToken := p.parseIdentifier() |
| if !p.OK() { |
| return nil |
| } |
| valueReference := mojom.NewUserValueRef(assigneeSpec, identifier, |
| p.currentScope, identifierToken) |
| p.mojomDescriptor.RegisterUnresolvedValueReference(valueReference) |
| return valueReference |
| } |
| |
| // POS_INT_LITERAL -> int_const_dec | int_const_hex |
| // |
| // parsePositiveIntegerLiteral() parses the next token as an integer using the smallest |
| // size integer possible given the input constraints. |
| // |
| // |initialMinus| should the string to be parsed be prepended with an initial minus sign? |
| // If this is true then |allowUnsigned| must also be false. |
| // |
| // |acceptHex| should the string to be parsed be allowed to be expressed in "0x" notation? |
| // |
| // |allowUnsigned| If the string cannot be parsed into a signed integer with a certain number of |
| // bits then before trying a larger number of bits we will try an unsigned integer if this is true. |
| // If |allowUnsigned| is not true then the returned |signed| will never be false if |ok| is true. |
| // |
| // |maxNumBits| This value should be 8, 16, 32 or 64 and represents the maximum size of an integer |
| // variable into which the parsed value may fit. If |ok| is true then |numBits| <= |maxNumBits|. |
| // |
| // |absoluteValue| The absolute value of the parsed integer, stored in a uint64. If |ok| |
| // is true then the value, times -1 if |initialMinus| is true, is guaranteed to be |
| // convertible without loss to the signed or unsigned integer type given by |signed| and |
| // |numBits|. For example if |numBits| = 16 and |signed| is true and |initialMinus| is true |
| // then -1 * |absoluteValue| may be cast to an int16. If |numBits| = 64 and |signed| is false then |
| // |absoluteValue| may be cast to a uint64. |
| // |
| // |numBits| is the smalles number of bits, 8, 16, 32, or 64, that the parsed value |
| // may fit into. If |ok| is true then |numBits| <= |maxNumBits|. |
| // |
| // |signed| modifies |numBits| to indicate what type of integer variable the result may be cast to. |
| // |
| // |ok| If this is false then there was a parsing error stored in p.err. |
| func (p *Parser) parsePositiveIntegerLiteral(initialMinus, acceptHex, allowUnsigned bool, maxNumBits int) (absoluteValue uint64, numBits int, signed, ok bool) { |
| p.pushChildNode("positive integer literal") |
| defer p.popNode() |
| |
| if maxNumBits != 8 && maxNumBits != 16 && maxNumBits != 32 && maxNumBits != 64 { |
| panic(fmt.Sprintf("maxNumBits must be 8, 16, 32 or 64: %d", maxNumBits)) |
| } |
| |
| if initialMinus && allowUnsigned { |
| panic("If initialMinus is true then allowUnsigned must be false.") |
| } |
| |
| nextToken := p.peekNextToken("I was parsing an integer literal.") |
| p.consumeNextToken() |
| intText := p.lastConsumed.Text |
| |
| // Set the base to 10 or 16. |
| base := 10 |
| switch nextToken.Kind { |
| case lexer.IntConstDec: |
| break |
| case lexer.IntConstHex: |
| if !acceptHex { |
| message := fmt.Sprintf("Illegal value: %s. Only a decimal integer literal is allowed here. "+ |
| "Hexadecimal is not valid.", nextToken) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, nextToken) |
| return |
| } |
| if len(intText) < 3 { |
| message := fmt.Sprintf("Invalid hex integer literal: %q.", intText) |
| p.parseErrorT(ParserErrorCodeIntegerParseError, message, nextToken) |
| return |
| } |
| intText = intText[2:] |
| base = 16 |
| |
| default: |
| p.unexpectedTokenError(nextToken, "a positive integer literal") |
| return |
| } |
| |
| // Add the initial minus sign if present. |
| if initialMinus { |
| intText = "-" + intText |
| } |
| |
| var parseError error |
| for _, numBits := range []int{8, 16, 32, 64} { |
| if numBits > maxNumBits { |
| break |
| } |
| // First try to parse as signed. |
| signed = true |
| intVal, err := strconv.ParseInt(intText, base, numBits) |
| if err == nil { |
| if intVal < 0 { |
| intVal = -intVal |
| } |
| return uint64(intVal), numBits, signed, true |
| } |
| |
| parseError = err |
| // Then try unsigned. |
| if allowUnsigned { |
| signed = false |
| intVal, err := strconv.ParseUint(intText, base, numBits) |
| if err == nil { |
| return intVal, numBits, signed, true |
| } |
| parseError = err |
| } |
| } |
| |
| switch parseError.(*strconv.NumError).Err { |
| case strconv.ErrRange: |
| message := fmt.Sprintf("Integer literal value out of range: %s.", intText) |
| p.parseErrorT(ParserErrorCodeIntegerOutOfRange, message, nextToken) |
| case strconv.ErrSyntax: |
| panic(fmt.Sprintf("Lexer allowed unparsable integer literal: %s. "+ |
| "Kind = %s. error=%s.", nextToken.Text, nextToken.Kind, parseError)) |
| } |
| return 0, 0, false, false |
| } |
| |
| // POS_FLOAT_LITERAL -> float_const |
| func (p *Parser) parsePositiveFloatLiteral(initialMinus bool) (float64, bool) { |
| p.pushChildNode("positive float literal") |
| defer p.popNode() |
| |
| nextToken := p.peekNextToken("I was parsing a float literal.") |
| p.match(lexer.FloatConst) |
| floatText := p.lastConsumed.Text |
| if initialMinus { |
| floatText = "-" + floatText |
| } |
| floatVal, err := strconv.ParseFloat(floatText, 64) |
| if err == nil { |
| return floatVal, true |
| } |
| switch err.(*strconv.NumError).Err { |
| case strconv.ErrRange: |
| message := fmt.Sprintf("Float literal value out of range: %s.", floatText) |
| p.parseErrorT(ParserErrorCodeIntegerOutOfRange, message, nextToken) |
| case strconv.ErrSyntax: |
| panic(fmt.Sprintf("Lexer allowed unparsable float literal: %s. "+ |
| "Kind = %s. error=%s.", nextToken.Text, nextToken.Kind, err)) |
| } |
| return 0, false |
| } |
| |
| ///////////////// Parsing Helper Functions //////// |
| |
| func (p *Parser) match(expectedKind lexer.TokenKind) bool { |
| if !p.OK() { |
| return false |
| } |
| message := fmt.Sprintf("Expecting %s.", expectedKind) |
| nextToken := p.peekNextToken(message) |
| if !p.OK() { |
| return false |
| } |
| if nextToken.Kind != expectedKind { |
| expected := fmt.Sprintf("%s", expectedKind) |
| p.unexpectedTokenError(nextToken, expected) |
| return false |
| } |
| p.consumeNextToken() |
| return true |
| } |
| |
| func (p *Parser) tryMatch(expectedKind lexer.TokenKind) bool { |
| if p.checkEOF() { |
| return false |
| } |
| if !p.OK() { |
| return false |
| } |
| nextToken := p.peekNextToken("") |
| if nextToken.Kind != expectedKind { |
| return false |
| } |
| p.consumeNextToken() |
| return true |
| } |
| |
| func (p *Parser) matchSemicolonToken(previousToken lexer.Token) bool { |
| if !p.OK() { |
| return false |
| } |
| if p.match(lexer.Semi) { |
| return true |
| } |
| message := fmt.Sprintf("Missing semicolon after %s.", previousToken) |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, previousToken) |
| return false |
| } |
| |
| func (p *Parser) matchSemicolon() bool { |
| return p.matchSemicolonToken(p.lastConsumed) |
| } |
| |
| func (p *Parser) readText(kind lexer.TokenKind) (text string) { |
| if !p.OK() { |
| return |
| } |
| |
| if !p.match(kind) { |
| return |
| } |
| return p.lastConsumed.Text |
| } |
| |
| func (p *Parser) readStringLiteral() (literal string) { |
| if !p.OK() { |
| return |
| } |
| |
| if !p.match(lexer.StringLiteral) { |
| return |
| } |
| |
| return lexer.StringLiteralTokenToText(p.lastConsumed) |
| } |
| |
| func (p *Parser) readName() (name string) { |
| if !p.OK() { |
| return |
| } |
| name = p.readText(lexer.Name) |
| return |
| } |
| |
| // Returns (-1, nil) if there was no specified ordinal. |
| func (p *Parser) readOrdinal() (ordinalValue int64, err error) { |
| ordinalValue = -1 |
| if !p.OK() { |
| return |
| } |
| |
| if p.tryMatch(lexer.Ordinal) { |
| intTextToParse := p.lastConsumed.Text[1:] |
| ordinalValue, err = strconv.ParseInt(intTextToParse, 10, 64) |
| if err != nil || ordinalValue < 0 || ordinalValue >= math.MaxUint32 { |
| err = fmt.Errorf("Invalid ordinal string following '@': %q. Ordinals must be decimal integers between 0 and %d.", |
| intTextToParse, math.MaxUint32-1) |
| return |
| } |
| } |
| return |
| } |
| |
| func (p *Parser) DeclData(name string, nameToken lexer.Token, attributes *mojom.Attributes) mojom.DeclarationData { |
| return mojom.DeclData(name, p.mojomFile, nameToken, attributes) |
| } |
| |
| func (p *Parser) DeclDataWithOrdinal(name string, nameToken lexer.Token, attributes *mojom.Attributes, declaredOrdinal int64) mojom.DeclarationData { |
| return mojom.DeclDataWithOrdinal(name, p.mojomFile, nameToken, attributes, declaredOrdinal) |
| } |
| |
| ////////////////// Scope Stack ///////////////////// |
| /// |
| func (p *Parser) pushScope(scope *mojom.Scope) { |
| if p.currentScope == nil { |
| p.currentScope = scope |
| } else { |
| if scope.Parent() != p.currentScope { |
| panic("Can only push child of current scope.") |
| } |
| p.currentScope = scope |
| } |
| } |
| |
| func (p *Parser) popScope() { |
| if p.currentScope != nil { |
| p.currentScope = p.currentScope.Parent() |
| } |
| } |
| |
| /////////// Utility functions |
| |
| func (p *Parser) invalidOrdinalError(objectType, objectName string, nameToken lexer.Token, err error) { |
| message := fmt.Sprintf("%s %q: %s", objectType, objectName, err.Error()) |
| p.parseErrorT(ParserErrorCodeBadOrdinal, message, nameToken) |
| } |
| |
| // unexpectedTokenError() sets the parser's current error to a ParseError |
| // with error code |ParserErrorCodeUnexpectedToken| and an error message |
| // of the form: |
| // |
| // Unexpected '}'. Expecting a semicolon. |
| // |
| // |expected| should be a noun-phrase such as "a semicolon". |
| func (p *Parser) unexpectedTokenError(token lexer.Token, expected string) { |
| var message string |
| switch token.Kind { |
| case lexer.ErrorUnterminatedStringLiteral, lexer.ErrorUnterminatedComment: |
| message = fmt.Sprintf("%s", token) |
| default: |
| message = fmt.Sprintf("Unexpected %s. Expecting %s.", token, expected) |
| } |
| p.parseErrorT(ParserErrorCodeUnexpectedToken, message, token) |
| } |