package parser import ( "errors" "strings" "github.com/marzeq/qk/attributes" "github.com/marzeq/qk/shared" "github.com/marzeq/qk/tokeniser" ) func (p *Parser) ParseBlock() (*BlockNode, error) { return p.parseBlock(false) } func (p *Parser) ParseBlockExpression() (*BlockNode, error) { return p.parseBlock(true) } func (p *Parser) parseBlock(expression bool) (*BlockNode, error) { beginLoc := p.CurrLoc() if !p.Expect(tokeniser.TokenOpenCurly) { return nil, shared.NewError(p.PrevLoc(), "expected '{' to start block") } for p.Match(tokeniser.TokenNewline) { p.Inc() } var children []Node var parseErrors []error for !p.Match(tokeniser.TokenCloseCurly, tokeniser.TokenEof) { if expression { start := p.pos posStack := append([]int(nil), p.posStack...) expr, err := p.ParseExpression() if err == nil { for p.Match(tokeniser.TokenSemicolon, tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseCurly) { children = append(children, expr) break } } p.pos = start p.posStack = posStack } stmt, semiNeeded, err := p.ParseStatement() if err != nil { parseErrors = append(parseErrors, err) p.synchroniseStatement() continue } children = append(children, stmt) if p.Match(tokeniser.TokenCloseCurly) { break } if semiNeeded && !p.Match(tokeniser.TokenSemicolon, tokeniser.TokenNewline) { parseErrors = append(parseErrors, shared.NewError(p.CurrLoc(), "expected ';' or '\\n' to end statement")) p.synchroniseStatement() continue } for p.Match(tokeniser.TokenSemicolon, tokeniser.TokenNewline) { p.Inc() } } if !p.Expect(tokeniser.TokenCloseCurly) { return nil, shared.NewError(p.PrevLoc(), "expected '}' to close block") } return &BlockNode{ Body: children, Expression: expression, Loc: p.SpanFrom(beginLoc), }, errors.Join(parseErrors...) } func (p *Parser) synchroniseStatement() { // Failed speculative parses must not affect the next statement. p.posStack = nil if p.atStatementBoundary() && isStatementStart(p.Peek()) { return } depth := 0 for !p.Match(tokeniser.TokenEof) { if depth == 0 && p.Match(tokeniser.TokenCloseCurly) { return } tok := p.Consume() switch tok.Type { case tokeniser.TokenOpenParen, tokeniser.TokenOpenSquare, tokeniser.TokenOpenCurly: depth++ case tokeniser.TokenCloseParen, tokeniser.TokenCloseSquare, tokeniser.TokenCloseCurly: if depth > 0 { depth-- } case tokeniser.TokenNewline, tokeniser.TokenSemicolon: if depth == 0 { return } } } } func (p *Parser) atStatementBoundary() bool { return p.pos == 0 || p.tokens[p.pos-1].Type == tokeniser.TokenNewline || p.tokens[p.pos-1].Type == tokeniser.TokenSemicolon } func isStatementStart(tok tokeniser.Token) bool { if tok.Type == tokeniser.TokenIdentifier || tok.Type == tokeniser.TokenOpenCurly { return true } if tok.Type != tokeniser.TokenKeyword { return false } switch tok.Value { case string(tokeniser.KeywordLet), string(tokeniser.KeywordReturn), string(tokeniser.KeywordBreak), string(tokeniser.KeywordContinue), string(tokeniser.KeywordDefer), string(tokeniser.KeywordIf), string(tokeniser.KeywordMatch), string(tokeniser.KeywordFor): return true default: return false } } func (p *Parser) ParseFunctionDefinition() (*FunctionDefNode, error) { beginLoc := p.CurrLoc() if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordLet) { return nil, shared.NewError(p.PrevLoc(), "expected 'let' keyword") } p.Inc() var name *tokeniser.Token var err error methodOwner := "" var methodOwnerType TypeNode var methodOwnerGenericParameters []GenericParameterNode var genericParameters []GenericParameterNode if p.Match(tokeniser.TokenOpenParen) { p.Inc() previousCaptureMode := p.allowTypeCapture p.allowTypeCapture = true methodOwnerType, err = p.ParseType() p.allowTypeCapture = previousCaptureMode if err != nil { return nil, err } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "expected ')' after method owner type") } genericParameters, err = capturedTypeParameters(methodOwnerType) if err != nil { return nil, err } if !p.Expect(tokeniser.TokenDot) { return nil, shared.NewError(p.PrevLoc(), "expected '.' after method owner type") } var ok bool name, ok = p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected method name after '.'") } } else { var ok bool name, ok = p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected function name") } if p.Match(tokeniser.TokenDot) { p.Inc() methodOwner = name.Value methodName, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected method name after '.'") } name = methodName } } if !p.Expect(tokeniser.TokenOpenParen) { return nil, shared.NewError(p.PrevLoc(), "expected '('") } for p.Match(tokeniser.TokenNewline) { p.Inc() } var args []*FunctionNodeArg variadic := false typedVariadic := false receiver := MethodReceiverNone hasReceiver := p.Match(tokeniser.TokenAsterisk) || (p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "self") if (methodOwner != "" || methodOwnerType != nil) && hasReceiver { pointer, mutablePointer := false, false if p.Match(tokeniser.TokenAsterisk) { p.Inc() pointer = true if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() mutablePointer = true } } self, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok || self.Value != "self" { return nil, shared.NewError(p.PrevLoc(), "method's first parameter must be self, *self, or *mut self") } receiver = MethodReceiverValue var selfType TypeNode if methodOwnerType != nil { selfType = methodOwnerType } else { ownerTypeArguments := make([]TypeNode, len(methodOwnerGenericParameters)) for i, parameter := range methodOwnerGenericParameters { ownerTypeArguments[i] = &NamedTypeNode{Name: parameter.Name, Loc: parameter.Loc} } selfType = &NamedTypeNode{Name: methodOwner, TypeArguments: ownerTypeArguments, Loc: self.Loc} } if pointer { receiver = MethodReceiverPointer selfType = &PointerTypeNode{BaseType: selfType, Mutable: mutablePointer, Loc: self.Loc} if mutablePointer { receiver = MethodReceiverMutablePointer } } args = append(args, &FunctionNodeArg{Name: "self", Type: selfType, Mutable: mutablePointer, Loc: self.Loc}) if p.Match(tokeniser.TokenComma) { p.Inc() } else if !p.Match(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.CurrLoc(), "expected ',' after method receiver") } } for !p.Match(tokeniser.TokenCloseParen) { for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { break } if p.Match(tokeniser.Token3Dots) { p.Inc() variadic = true if p.Match(tokeniser.TokenComma) { p.Inc() } break } if parameter, matched, parseErr := p.parseTypeParameter(); matched || parseErr != nil { if parseErr != nil { return nil, parseErr } for _, existing := range genericParameters { if existing.Name == parameter.Name { return nil, shared.NewError(parameter.Loc, "duplicate type parameter %q", parameter.Name) } } genericParameters = append(genericParameters, parameter) if !p.Match(tokeniser.TokenComma) { if !p.Match(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.CurrLoc(), "expected ',' or ')' after type parameter") } } else { p.Inc() } continue } mutable := false if p.Match(tokeniser.TokenKeyword) { kw := p.Consume().Value if kw == string(tokeniser.KeywordMut) { mutable = true } else { return nil, shared.NewError(p.CurrLoc(), "expected either 'mut' or argument name") } } arg, err := p.ParseIdent() if err != nil { return nil, err } group := []*FunctionNodeArg{{ Name: arg.Name, Mutable: mutable, Loc: arg.Loc, }} if p.Match(tokeniser.TokenEquals) { p.Inc() group[0].Default, err = p.ParseExpression() if err != nil { return nil, err } } for !p.Match(tokeniser.TokenColon) { if !p.Expect(tokeniser.TokenComma) { return nil, shared.NewError(p.PrevLoc(), "expected ':' or ','") } for p.Match(tokeniser.TokenNewline) { p.Inc() } mutable = false if p.Match(tokeniser.TokenKeyword) { kw := p.Consume().Value if kw == string(tokeniser.KeywordMut) { mutable = true } else { return nil, shared.NewError(p.CurrLoc(), "expected either 'mut' or argument name") } } arg, err = p.ParseIdent() if err != nil { return nil, err } group = append(group, &FunctionNodeArg{ Name: arg.Name, Mutable: mutable, Loc: arg.Loc, }) if p.Match(tokeniser.TokenEquals) { p.Inc() group[len(group)-1].Default, err = p.ParseExpression() if err != nil { return nil, err } } } p.Inc() isTypedVariadic := p.Match(tokeniser.Token3Dots) if isTypedVariadic { if len(group) != 1 { return nil, shared.NewError(p.CurrLoc(), "typed variadic parameter cannot use a grouped declaration") } p.Inc() } argType, err := p.ParseType() if err != nil { return nil, err } if isTypedVariadic { if group[0].Default != nil { return nil, shared.NewError(beginLoc, "typed variadic parameter cannot have a default") } argType = &SliceTypeNode{ElementType: argType, Loc: argType.GetLoc()} typedVariadic = true } if p.Match(tokeniser.TokenEquals) { if group[len(group)-1].Default != nil { return nil, shared.NewError(p.CurrLoc(), "parameter default cannot be specified both before and after its type") } p.Inc() group[len(group)-1].Default, err = p.ParseExpression() if err != nil { return nil, err } } for _, groupedArg := range group { groupedArg.Type = argType } args = append(args, group...) if isTypedVariadic { if p.Match(tokeniser.TokenComma) { return nil, shared.NewError(p.CurrLoc(), "typed variadic parameter must be last") } break } if !p.Match(tokeniser.TokenComma) { break } p.Consume() } for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "expected ')'") } var retType TypeNode if p.Match(tokeniser.TokenColon) { p.Inc() argType, err := p.parseFunctionReturnType() if err != nil { return nil, err } retType = argType } for p.Match(tokeniser.TokenNewline) { p.Inc() } expectsBody := true attrs, err := p.parseAttributes(name.Value) if err != nil { return nil, err } if attrs.Get(attributes.AttributeTypeForeign) != nil { expectsBody = false } var body Node expressionBody := false if expectsBody { for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenOpenCurly) { b, err := p.ParseBlock() if err != nil { return nil, err } body = b } else if p.Match(tokeniser.TokenEquals) { p.Inc() // consume '=' expressionBody = true for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenOpenCurly) { b, err := p.ParseBlockExpression() if err != nil { return nil, err } body = b } else { b, err := p.ParseExpression() if err != nil { return nil, err } body = b } } else { return nil, shared.NewError(p.PrevLoc(), "expected function body as either block or expression after '='") } } return &FunctionDefNode{ Name: name.Value, MethodOwner: methodOwner, MethodOwnerType: methodOwnerType, MethodOwnerGenericParameters: methodOwnerGenericParameters, Receiver: receiver, GenericParameters: genericParameters, Args: args, RetTypeNode: retType, Body: body, ExpressionBody: expressionBody, Loc: p.SpanFrom(beginLoc), Attributes: attrs, HasVariadic: variadic, TypedVariadic: typedVariadic, }, nil } func (p *Parser) parseAttributes(defaultName string) (attributes.Attributes, error) { var attrs attributes.Attributes for { pos := p.pos for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.MatchBuiltin("compiler_error") || p.MatchBuiltin("compiler_assert") { p.pos = pos break } if !p.Match(tokeniser.TokenAt) { p.pos = pos break } attr, err := p.parseAttribute(defaultName) if err != nil { return nil, err } attrs = append(attrs, attr) } return attrs, nil } func (p *Parser) parseAttribute(defaultName string) (attributes.Attribute, error) { if !p.Expect(tokeniser.TokenAt) { return nil, shared.NewError(p.PrevLoc(), "expected '@' for attribute") } nameIdent, err := p.ParseIdent() if err != nil { return nil, err } switch attributes.AttributeType(nameIdent.Name) { case attributes.AttributeTypeLink: return p.parseLinkAttribute() case attributes.AttributeTypeInline: return p.parseInlineAttribute() case attributes.AttributeTypeNoInline: return p.parseNoInlineAttribute() case attributes.AttributeTypeNoReturn: return p.parseNoReturnAttribute() case attributes.AttributeTypeForeign: return p.parseForeignAttribute(defaultName) case attributes.AttributeTypeExport: return p.parseExportAttribute(defaultName) case attributes.AttributeTypePacked: return p.parsePackedAttribute() default: return nil, shared.NewError(nameIdent.Loc, "unknown attribute: %s", nameIdent.Name) } } func (p *Parser) parseLinkAttribute() (attributes.Attribute, error) { if !p.Expect(tokeniser.TokenOpenParen) { return nil, shared.NewError(p.PrevLoc(), "expected '(' after @link") } items, conditional, err := p.parseLinkItems(tokeniser.TokenCloseParen) if err != nil { return nil, err } p.Inc() if !conditional { links := make([]attributes.Link, 0, len(items)) for _, item := range items { links = append(links, *item.Link) } return attributes.ModuleAttributeLink{Links: links}, nil } return ParsedLinkAttribute{Items: items}, nil } func (p *Parser) parseInlineAttribute() (attributes.Attribute, error) { if p.Match(tokeniser.TokenOpenParen) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "inline attribute does not take any arguments") } } return attributes.AttributeInline{}, nil } func (p *Parser) parseNoInlineAttribute() (attributes.Attribute, error) { if p.Match(tokeniser.TokenOpenParen) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "noinline attribute does not take any arguments") } } return attributes.AttributeNoInline{}, nil } func (p *Parser) parseNoReturnAttribute() (attributes.Attribute, error) { if p.Match(tokeniser.TokenOpenParen) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "noreturn attribute does not take any arguments") } } return attributes.AttributeNoReturn{}, nil } func (p *Parser) parsePackedAttribute() (attributes.Attribute, error) { if p.Match(tokeniser.TokenOpenParen) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "packed attribute does not take any arguments") } } return attributes.AttributePacked{}, nil } func (p *Parser) parseForeignAttribute(defaultName string) (attributes.Attribute, error) { result := attributes.FunctionAttributeForeign{From: defaultName, ABI: attributes.ForeignABIC} if !p.Match(tokeniser.TokenOpenParen) { return result, nil } p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() return result, nil } seenABI, seenSymbol := false, false for { for p.Match(tokeniser.TokenNewline) { p.Inc() } option, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected 'abi' or 'symbol' in @foreign") } for p.Match(tokeniser.TokenNewline) { p.Inc() } for p.Match(tokeniser.TokenNewline) { p.Inc() } value, ok := p.ExpectGet(tokeniser.TokenString) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected string after %s in @foreign", option.Value) } switch option.Value { case "abi": if seenABI { return nil, shared.NewError(option.Loc, "duplicate abi option in @foreign") } seenABI = true switch value.Value { case "c": result.ABI = attributes.ForeignABIC case "qk": result.ABI = attributes.ForeignABIQK default: return nil, shared.NewError(value.Loc, "unknown foreign ABI %q; expected 'c' or 'qk'", value.Value) } case "symbol": if seenSymbol { return nil, shared.NewError(option.Loc, "duplicate symbol option in @foreign") } seenSymbol = true if value.Value == "" { return nil, shared.NewError(value.Loc, "foreign symbol cannot be empty") } result.From = value.Value default: return nil, shared.NewError(option.Loc, "unknown @foreign option %q; expected 'abi' or 'symbol'", option.Value) } for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } if !p.Expect(tokeniser.TokenComma) { return nil, shared.NewError(p.PrevLoc(), "expected ',' or ')' in @foreign") } for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } } return result, nil } func (p *Parser) parseExportAttribute(defaultName string) (attributes.Attribute, error) { result := attributes.FunctionAttributeExport{As: defaultName, ABI: attributes.ForeignABIC} if !p.Match(tokeniser.TokenOpenParen) { return result, nil } p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() return result, nil } if p.Match(tokeniser.TokenString) { name := p.Consume() if name.Value == "" { return nil, shared.NewError(name.Loc, "export attribute argument must be a non-empty string") } result.As = name.Value for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "export attribute takes either one string argument or named options") } return result, nil } seenABI, seenSymbol := false, false for { option, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected 'abi' or 'symbol' in @export") } value, ok := p.ExpectGet(tokeniser.TokenString) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected string after %s in @export", option.Value) } switch option.Value { case "abi": if seenABI { return nil, shared.NewError(option.Loc, "duplicate abi option in @export") } seenABI = true switch value.Value { case "c": result.ABI = attributes.ForeignABIC case "qk": result.ABI = attributes.ForeignABIQK default: return nil, shared.NewError(value.Loc, "unknown export ABI %q; expected 'c' or 'qk'", value.Value) } case "symbol": if seenSymbol { return nil, shared.NewError(option.Loc, "duplicate symbol option in @export") } seenSymbol = true if value.Value == "" { return nil, shared.NewError(value.Loc, "export symbol must be non-empty") } result.As = value.Value default: return nil, shared.NewError(option.Loc, "unknown @export option %q; expected 'abi' or 'symbol'", option.Value) } for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } if !p.Expect(tokeniser.TokenComma) { return nil, shared.NewError(p.PrevLoc(), "expected ',' or ')' in @export") } } return result, nil } func (p *Parser) ParseTypeAlias() (*TypeAliasNode, error) { beginLoc := p.CurrLoc() if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordLet) { return nil, shared.NewError(p.PrevLoc(), "expected 'let' keyword") } p.Inc() name, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected type alias name") } genericParameters := []GenericParameterNode{} if p.Match(tokeniser.TokenOpenParen) { p.Inc() for !p.Match(tokeniser.TokenCloseParen) { parameter, matched, err := p.parseTypeParameter() if err != nil { return nil, err } if !matched { return nil, shared.NewError(p.CurrLoc(), "type-producing bindings only accept parameters of type 'type'") } genericParameters = append(genericParameters, parameter) if !p.Match(tokeniser.TokenComma) { break } p.Inc() } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "expected ')' after type parameters") } } if !p.Expect(tokeniser.TokenEquals) { return nil, shared.NewError(p.PrevLoc(), "expected '='") } if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordType) { return nil, shared.NewError(p.PrevLoc(), "expected 'type' keyword") } p.Inc() transparent := false if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordAlias) { transparent = true p.Inc() } tpe, err := p.ParseType() if err != nil { return nil, err } return &TypeAliasNode{ Name: name.Value, GenericParameters: genericParameters, Type: tpe, Transparent: transparent, Loc: p.SpanFrom(beginLoc), }, nil } func (p *Parser) ParseImport() (*ImportNode, error) { beginLoc := p.CurrLoc() if kw, ok := p.ExpectGet(tokeniser.TokenKeyword); !ok || kw.Value != string(tokeniser.KeywordImport) { return nil, shared.NewError(p.PrevLoc(), "expected 'import' keyword") } modules := []string{} aliases := []string{} if p.Match(tokeniser.TokenOpenParen) { p.Inc() for { for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } name, _, err := p.parseModulePath() if err != nil { return nil, err } modules = append(modules, name) alias := "" if p.Match(tokeniser.TokenIdentifier) { alias = p.Consume().Value } aliases = append(aliases, alias) if p.Match(tokeniser.TokenComma) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } continue } if p.Match(tokeniser.TokenNewline) { p.Inc() continue } if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } return nil, shared.NewError(p.PrevLoc(), "expected ',', newline, or ')'") } } else { name, _, err := p.parseModulePath() if err != nil { return nil, err } modules = append(modules, name) alias := "" if p.Match(tokeniser.TokenIdentifier) { alias = p.Consume().Value } aliases = append(aliases, alias) } return &ImportNode{ Modules: modules, Aliases: aliases, Loc: p.SpanFrom(beginLoc), }, nil } func (p *Parser) ParseModule() (*ModuleNode, error) { beginLoc := p.CurrLoc() if kw, ok := p.ExpectGet(tokeniser.TokenKeyword); !ok || kw.Value != string(tokeniser.KeywordModule) { return nil, shared.NewError(p.PrevLoc(), "expected 'module' keyword") } name, _, err := p.parseModulePath() if err != nil { return nil, err } attrs, err := p.parseAttributes(name) if err != nil { return nil, err } return &ModuleNode{ Name: name, Attributes: attrs, Loc: p.SpanFrom(beginLoc), }, nil } func (p *Parser) parseModulePath() (string, string, error) { first, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return "", "", shared.NewError(p.PrevLoc(), "expected module name") } parts := []string{first.Value} for p.Match(tokeniser.TokenDot) { p.Inc() part, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return "", "", shared.NewError(p.PrevLoc(), "expected module name after '.'") } parts = append(parts, part.Value) } return strings.Join(parts, "."), parts[len(parts)-1], nil } func (p *Parser) ParseStatement() (Node, bool, error) { if p.MatchBuiltin("compiler_error") || p.MatchBuiltin("compiler_assert") { node, err := p.parseCompilerDirective() return node, true, err } if p.Match(tokeniser.TokenKeyword) { kw := p.Peek().Value switch kw { case string(tokeniser.KeywordLet): p.PushPos() p.Inc() // consume `let` mutable := false if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() mutable = true } if p.Match(tokeniser.TokenDollar) { p.PopPos() node, err := p.ParseDeclaration() return node, true, err } if p.Match(tokeniser.TokenOpenParen) { p.PopPos() if mutable { node, err := p.ParseDeclaration() return node, true, err } node, err := p.ParseFunctionDefinition() return node, true, err } if !p.Expect(tokeniser.TokenIdentifier) { return nil, false, shared.NewError(p.PrevLoc(), "expected name") } switch { case p.Match(tokeniser.TokenOpenParen): p.PopPos() if p.bindingParametersIntroduce("type") { node, err := p.ParseTypeAlias() return node, true, err } // let fn(...) = ... if mutable { node, err := p.ParseDeclaration() return node, true, err } node, err := p.ParseFunctionDefinition() return node, true, err case p.Match(tokeniser.TokenDot): p.PopPos() if mutable { node, err := p.ParseDeclaration() return node, true, err } node, err := p.ParseFunctionDefinition() return node, true, err case p.Match(tokeniser.TokenColon): // let x: T = ... p.PopPos() node, err := p.ParseDeclaration() return node, true, err case p.Match(tokeniser.TokenEquals): p.Inc() // look past '=' if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordType) { // let A = type ... p.PopPos() node, err := p.ParseTypeAlias() return node, true, err } // let x = ... p.PopPos() node, err := p.ParseDeclaration() return node, true, err case p.Match(tokeniser.TokenComma): p.PopPos() node, err := p.parseMultiDeclaration() return node, true, err } return nil, false, shared.NewError(p.PrevLoc(), "invalid let statement") case string(tokeniser.KeywordReturn), string(tokeniser.KeywordBreak), string(tokeniser.KeywordContinue): node, err := p.ParseControlKeyword() return node, true, err case string(tokeniser.KeywordDefer): node, err := p.ParseDefer() return node, true, err case string(tokeniser.KeywordIf): node, err := p.ParseIfStatement() return node, false, err case string(tokeniser.KeywordMatch): start := p.pos node, err := p.ParseMatch(false) if err == nil && p.tokenContinuesExpression() { p.pos = start goto expressionStatement } return node, false, err case string(tokeniser.KeywordFor): node, err := p.ParseForLoop() return node, false, err case string(tokeniser.KeywordWhen): node, err := p.parseWhen(WhenStatements) return node, false, err default: return nil, false, shared.NewError(p.CurrLoc(), "unexpected keyword: %s", kw) } } if p.Match(tokeniser.TokenOpenCurly) { node, err := p.ParseBlock() return node, true, err } expressionStatement: expr, err := p.ParseExpression() if err != nil { return nil, false, err } if p.Match(tokeniser.TokenComma) { assignees := []ExpressionNode{expr} for p.Match(tokeniser.TokenComma) { p.Inc() ident, err := p.ParseIdent() if err != nil { return nil, false, err } assignees = append(assignees, ident) } if !p.Expect(tokeniser.TokenEquals) { return nil, false, shared.NewError(p.PrevLoc(), "expected '=' after assignment targets") } value, err := p.ParseExpression() if err != nil { return nil, false, err } if !isMultiResultSource(value) { return nil, false, shared.NewError(value.GetLoc(), "multiple assignment requires a function call or checked cast") } return &AssignmentNode{Assignees: assignees, Value: value, Loc: expr.GetLoc().WithEnd(value.GetLoc())}, true, nil } if p.Match(tokeniser.TokenEquals) { p.Inc() parsed, err := p.ParseAssignment(expr) if err != nil { return nil, false, err } return parsed, true, nil } if p.Match(tokeniser.TokenIncBy, tokeniser.TokenDecBy, tokeniser.TokenMulBy, tokeniser.TokenDivBy, tokeniser.TokenModBy, tokeniser.TokenBitwiseAndBy, tokeniser.TokenBitwiseOrBy, tokeniser.TokenBitwiseXorBy, tokeniser.TokenShiftLeftBy, tokeniser.TokenShiftRightBy) { parsed, err := p.ParseCompoundAssignment(p.Consume(), expr) if err != nil { return nil, false, err } return parsed, true, nil } return expr, true, nil } func (p *Parser) bindingParametersIntroduce(initializer string) bool { p.PushPos() defer p.PopPos() if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordLet) { return false } p.Inc() if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() } if !p.Expect(tokeniser.TokenIdentifier) || !p.Match(tokeniser.TokenOpenParen) { return false } depth := 0 for !p.Match(tokeniser.TokenEof) { switch p.Peek().Type { case tokeniser.TokenOpenParen: depth++ case tokeniser.TokenCloseParen: depth-- if depth == 0 { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } if !p.Match(tokeniser.TokenEquals) { return false } p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } return p.Peek().Value == initializer } } p.Inc() } return false } func (p *Parser) tokenContinuesExpression() bool { if p.Match(tokeniser.TokenOpenParen, tokeniser.TokenOpenSquare, tokeniser.TokenDot, tokeniser.TokenAsterisk, tokeniser.TokenSlash, tokeniser.TokenPercent, tokeniser.TokenPlus, tokeniser.TokenMinus, tokeniser.TokenShiftLeft, tokeniser.TokenShiftRight, tokeniser.TokenLess, tokeniser.TokenLessEquals, tokeniser.TokenGreater, tokeniser.TokenGreaterEquals, tokeniser.TokenEqualsEquals, tokeniser.TokenNotEquals, tokeniser.TokenAmpersand, tokeniser.TokenLogicalAnd, tokeniser.TokenPipe, tokeniser.TokenLogicalOr, tokeniser.TokenCaret) { return true } return false } func (p *Parser) ParseDefer() (*DeferNode, error) { loc := p.CurrLoc() kw, ok := p.ExpectGet(tokeniser.TokenKeyword) if !ok || kw.Value != string(tokeniser.KeywordDefer) { return nil, shared.NewError(p.PrevLoc(), "expected 'defer'") } for p.Match(tokeniser.TokenNewline) { p.Inc() } var action Node if p.Match(tokeniser.TokenOpenCurly) { block, err := p.ParseBlock() if err != nil { return nil, err } action = block } else { expr, err := p.ParseExpression() if err != nil { return nil, err } action = expr } return &DeferNode{Action: action, Loc: p.SpanFrom(loc)}, nil } func (p *Parser) ParseDeclaration() (*DeclarationNode, error) { beginLoc := p.CurrLoc() kw, ok := p.ExpectGet(tokeniser.TokenKeyword) if !ok || kw.Value != string(tokeniser.KeywordLet) { return nil, shared.NewError(p.PrevLoc(), "expected `let` keyword") } mutable := false if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() mutable = true } compileTime := false if p.Match(tokeniser.TokenDollar) { p.Inc() compileTime = true } if mutable && compileTime { return nil, shared.NewError(p.PrevLoc(), "compile-time bindings cannot be mutable") } ident, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected name") } var tpe TypeNode if p.Match(tokeniser.TokenColon) { p.Inc() t, err := p.ParseType() if err != nil { return nil, err } tpe = t } var value ExpressionNode var attrs attributes.Attributes var err error if p.Match(tokeniser.TokenEquals) { p.Inc() for p.Match(tokeniser.TokenNewline) { p.Inc() } expr, err := p.ParseExpression() if err != nil { return nil, err } value = expr } attrs, err = p.parseAttributes(ident.Value) if err != nil { return nil, err } if value == nil && len(attrs) == 0 { return nil, shared.NewError(p.PrevLoc(), "expected '=' or declaration attribute") } if value == nil && attrs.Get(attributes.AttributeTypeForeign) != nil && tpe == nil { return nil, shared.NewError(ident.Loc, "external declaration requires a type annotation") } return &DeclarationNode{ Name: ident.Value, NameLoc: ident.Loc, TypeNode: tpe, Mutable: mutable, Value: value, Comptime: compileTime, Attributes: attrs, Loc: p.SpanFrom(beginLoc), }, nil } // parseTypeParameter recognizes a compile-time type parameter in an ordinary // binding argument list. Constraints use type(Trait), for example // $T: type(PartialEq). func (p *Parser) parseTypeParameter() (GenericParameterNode, bool, error) { p.PushPos() begin := p.CurrLoc() if !p.Match(tokeniser.TokenDollar) { p.PopPos() return GenericParameterNode{}, false, nil } p.Inc() name, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { p.PopPos() return GenericParameterNode{}, true, shared.NewError(begin, "expected type parameter name after '$'") } if !p.Expect(tokeniser.TokenColon) || !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordType) { p.PopPos() return GenericParameterNode{}, true, shared.NewError(name.Loc, "compile-time parameter %q must have type 'type'", name.Value) } p.Inc() parameter := GenericParameterNode{Name: name.Value, Loc: p.SpanFrom(begin)} if p.Match(tokeniser.TokenOpenParen) { p.Inc() constraint, err := p.ParseType() if err != nil { p.CommitPos() return GenericParameterNode{}, true, err } if !p.Expect(tokeniser.TokenCloseParen) { p.CommitPos() return GenericParameterNode{}, true, shared.NewError(p.PrevLoc(), "expected ')' after type parameter constraint") } parameter.Constraint = constraint parameter.Loc = p.SpanFrom(begin) } p.CommitPos() return parameter, true, nil } func capturedTypeParameters(owner TypeNode) ([]GenericParameterNode, error) { var result []GenericParameterNode seen := make(map[string]bool) var visit func(TypeNode) error visit = func(node TypeNode) error { switch n := node.(type) { case *NamedTypeNode: if n.Capture { if !seen[n.Name] { seen[n.Name] = true result = append(result, GenericParameterNode{Name: n.Name, Constraint: n.CaptureConstraint, Loc: n.Loc}) } else if n.CaptureConstraint != nil { return shared.NewError(n.Loc, "constraint for repeated type capture %q must be declared on its first occurrence", n.Name) } } for _, argument := range n.TypeArguments { if err := visit(argument); err != nil { return err } } case *PointerTypeNode: return visit(n.BaseType) case *SliceTypeNode: return visit(n.ElementType) case *ArrayTypeNode: return visit(n.ElementType) case *DynTypeNode: return visit(n.TraitType) case *ReprTypeNode: return visit(n.Operand) case *FunctionTypeNode: for _, parameter := range n.Parameters { if err := visit(parameter); err != nil { return err } } return visit(n.ReturnType) case *MultipleReturnTypeNode: for _, item := range n.Types { if err := visit(item); err != nil { return err } } } return nil } if err := visit(owner); err != nil { return nil, err } return result, nil } func (p *Parser) parseMultiDeclaration() (*MultiDeclarationNode, error) { loc := p.CurrLoc() p.Inc() // let mutable := false if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() mutable = true } names := []string{} nameLocs := []shared.Location{} for { ident, ok := p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected declaration name") } names = append(names, ident.Value) nameLocs = append(nameLocs, ident.Loc) if !p.Match(tokeniser.TokenComma) { break } p.Inc() } if !p.Expect(tokeniser.TokenEquals) { return nil, shared.NewError(p.PrevLoc(), "expected '=' after declaration names") } for p.Match(tokeniser.TokenNewline) { p.Inc() } value, err := p.ParseExpression() if err != nil { return nil, err } if !isMultiResultSource(value) { return nil, shared.NewError(value.GetLoc(), "multiple declaration requires a function call, checked cast, or inline assembly expression") } return &MultiDeclarationNode{ Names: names, NameLocs: nameLocs, Mutable: mutable, Value: value, Loc: p.SpanFrom(loc), }, nil } func isMultiResultSource(value ExpressionNode) bool { switch value.(type) { case *FunctionCallNode, *CastNode, *InlineAsmNode: return true } return false } func (p *Parser) parseFunctionReturnType() (TypeNode, error) { if !p.Match(tokeniser.TokenOpenParen) { return p.ParseType() } loc := p.CurrLoc() p.Inc() items := []TypeNode{} for { t, err := p.ParseType() if err != nil { return nil, err } items = append(items, t) if !p.Match(tokeniser.TokenComma) { break } p.Inc() } if len(items) < 2 { return nil, shared.NewError(loc, "multiple return type requires at least two types") } if !p.Expect(tokeniser.TokenCloseParen) { return nil, shared.NewError(p.PrevLoc(), "expected ')' after return types") } return &MultipleReturnTypeNode{Types: items, Loc: p.SpanFrom(loc)}, nil } func (p *Parser) ParseAssignment(subj ExpressionNode) (*AssignmentNode, error) { for p.Match(tokeniser.TokenNewline) { p.Inc() } expr, err := p.ParseExpression() if err != nil { return nil, err } return &AssignmentNode{ Assignee: subj, Value: expr, Loc: subj.GetLoc().WithEnd(expr.GetLoc()), }, err } func (p *Parser) ParseCompoundAssignment(opTok tokeniser.Token, subj ExpressionNode) (*AssignmentNode, error) { var op BinaryOpKind switch opTok.Type { case tokeniser.TokenIncBy: op = BinaryOpAdd case tokeniser.TokenDecBy: op = BinaryOpSubtract case tokeniser.TokenMulBy: op = BinaryOpMultiply case tokeniser.TokenDivBy: op = BinaryOpDivide case tokeniser.TokenModBy: op = BinaryOpModulo case tokeniser.TokenBitwiseAndBy: op = BinaryOpBitwiseAnd case tokeniser.TokenBitwiseOrBy: op = BinaryOpBitwiseOr case tokeniser.TokenBitwiseXorBy: op = BinaryOpBitwiseXor case tokeniser.TokenShiftLeftBy: op = BinaryOpShiftLeft case tokeniser.TokenShiftRightBy: op = BinaryOpShiftRight default: return nil, shared.NewError(opTok.Loc, "unexpected compound assignment operator %s", opTok) } for p.Match(tokeniser.TokenNewline) { p.Inc() } expr, err := p.ParseExpression() if err != nil { return nil, err } return &AssignmentNode{ Assignee: subj, Compound: true, Value: &BinaryOpNode{ Op: op, Operand1: subj, Operand2: expr, Loc: subj.GetLoc().WithEnd(expr.GetLoc()), }, Loc: subj.GetLoc().WithEnd(expr.GetLoc()), }, nil } func (p *Parser) ParseIfStatement() (*IfNode, error) { return p.parseIf(false) } func (p *Parser) ParseIfExpression() (*IfNode, error) { return p.parseIf(true) } func (p *Parser) parseIf(expression bool) (*IfNode, error) { beginLoc := p.CurrLoc() if !p.Expect(tokeniser.TokenKeyword) { return nil, shared.NewError(p.PrevLoc(), "expected 'if'") } for p.Match(tokeniser.TokenNewline) { p.Inc() } condition, err := p.ParseExpression() if err != nil { return nil, err } for p.Match(tokeniser.TokenNewline) { p.Inc() } thenBlock, err := p.parseBlock(expression) if err != nil { return nil, err } for p.Match(tokeniser.TokenNewline) { p.Inc() } node := &IfNode{ Loc: p.SpanFrom(beginLoc), Expression: expression, IfBranch: IfBranch{ Condition: condition, Node: thenBlock, }, } for p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordElse) { p.Consume() for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordIf) { p.Consume() for p.Match(tokeniser.TokenNewline) { p.Inc() } elseifCondition, err := p.ParseExpression() if err != nil { return nil, err } for p.Match(tokeniser.TokenNewline) { p.Inc() } elseifBlock, err := p.parseBlock(expression) if err != nil { return nil, err } elseIfBranch := IfBranch{ Condition: elseifCondition, Node: elseifBlock, } node.ElseIfBranches = append(node.ElseIfBranches, elseIfBranch) for p.Match(tokeniser.TokenNewline) { p.Inc() } } else { elseBlock, err := p.parseBlock(expression) if err != nil { return nil, err } node.ElseBranch = elseBlock break } } if expression && node.ElseBranch == nil { return nil, shared.NewError(beginLoc, "if expression requires an else branch") } endLoc := node.IfBranch.Node.GetLoc() if len(node.ElseIfBranches) != 0 { endLoc = node.ElseIfBranches[len(node.ElseIfBranches)-1].Node.GetLoc() } if node.ElseBranch != nil { endLoc = node.ElseBranch.GetLoc() } node.Loc = beginLoc.WithEnd(endLoc) return node, nil } func (p *Parser) ParseControlKeyword() (*ControlKeywordNode, error) { loc := p.CurrLoc() kw, ok := p.ExpectGet(tokeniser.TokenKeyword) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected 'return', 'break' or 'continue'") } if kw.Value != string(tokeniser.KeywordReturn) && kw.Value != string(tokeniser.KeywordBreak) && kw.Value != string(tokeniser.KeywordContinue) { return nil, shared.NewError(p.PrevLoc(), "expected 'return', 'break' or 'continue'") } var expr ExpressionNode var exprs []ExpressionNode if kw.Value == string(tokeniser.KeywordReturn) && !p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) { got, err := p.ParseExpression() if err != nil { return nil, err } expr = got exprs = append(exprs, got) for p.Match(tokeniser.TokenComma) { p.Inc() got, err = p.ParseExpression() if err != nil { return nil, err } exprs = append(exprs, got) } } return &ControlKeywordNode{ Keyword: tokeniser.KeywordKind(kw.Value), ReturnValue: expr, ReturnValues: exprs, Loc: p.SpanFrom(loc), }, nil } func (p *Parser) ParseForLoop() (Node, error) { beginLoc := p.CurrLoc() if !p.Expect(tokeniser.TokenKeyword) { return nil, shared.NewError(p.PrevLoc(), "expected 'for'") } for p.Match(tokeniser.TokenNewline) { p.Inc() } if p.matchesRangeOrForEachHeader() { return p.parseRangeOrForEach(beginLoc) } var err error exprsOrStmts := []Node{} for !p.Match(tokeniser.TokenOpenCurly) { ogPos := p.pos exOrSt, _, err := p.ParseStatement() if err != nil { p.pos = ogPos exOrSt, err = p.ParseExpression() if err != nil { return nil, err } } exprsOrStmts = append(exprsOrStmts, exOrSt) if p.Match(tokeniser.TokenOpenCurly) { break } if !p.Expect(tokeniser.TokenSemicolon) { return nil, shared.NewError(p.PrevLoc(), "expected ';' to end statement or expression") } for p.Match(tokeniser.TokenNewline) { p.Inc() } } for p.Match(tokeniser.TokenNewline) { p.Inc() } body, err := p.ParseBlock() if err != nil { return nil, err } return &ForNode{ ExprsOrStmts: exprsOrStmts, Body: body, Loc: p.SpanFrom(beginLoc), }, nil } func (p *Parser) parseRangeOrForEach(beginLoc shared.Location) (Node, error) { var name *tokeniser.Token var destructure []ForEachBinding var ok bool if p.Match(tokeniser.TokenOpenParen) { p.Inc() for { binding, found := p.ExpectGet(tokeniser.TokenIdentifier) if !found { return nil, shared.NewError(p.PrevLoc(), "expected destructuring binding name") } destructure = append(destructure, ForEachBinding{Name: binding.Value, Loc: binding.Loc}) if p.Match(tokeniser.TokenCloseParen) { p.Inc() break } if !p.Expect(tokeniser.TokenComma) { return nil, shared.NewError(p.PrevLoc(), "expected ',' or ')' in destructuring pattern") } } } else { name, ok = p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected loop variable name") } } elementKind := ForEachElementValue if name != nil && p.Match(tokeniser.TokenDot) && p.Next().Type == tokeniser.TokenAmpersand { p.Inc().Inc() elementKind = ForEachElementPointer if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) { p.Inc() elementKind = ForEachElementMutablePointer } } var indexName *tokeniser.Token if p.Match(tokeniser.TokenComma) { p.Inc() indexName, ok = p.ExpectGet(tokeniser.TokenIdentifier) if !ok { return nil, shared.NewError(p.PrevLoc(), "expected index variable name after ','") } } if kw, ok := p.ExpectGet(tokeniser.TokenKeyword); !ok || kw.Value != string(tokeniser.KeywordIn) { return nil, shared.NewError(p.PrevLoc(), "expected 'in' in for loop") } iterable, err := p.ParseExpression() if err != nil { return nil, err } if p.Match(tokeniser.Token2Dots) { p.Inc() inclusive := p.Match(tokeniser.TokenEquals) if inclusive { p.Inc() } end, err := p.ParseExpression() if err != nil { return nil, err } if indexName != nil { return nil, shared.NewError(indexName.Loc, "range iteration accepts exactly one variable") } if len(destructure) != 0 { return nil, shared.NewError(destructure[0].Loc, "range iteration does not support destructuring") } if elementKind != ForEachElementValue { return nil, shared.NewError(name.Loc, "range iteration does not support element pointer bindings") } reversed, err := p.parseIterationAttributes() if err != nil { return nil, err } body, err := p.ParseBlock() if err != nil { return nil, err } return &RangeForNode{ Name: name.Value, NameLoc: name.Loc, Start: iterable, End: end, Inclusive: inclusive, Reversed: reversed, Body: body, Loc: p.SpanFrom(beginLoc), }, nil } reversed, err := p.parseIterationAttributes() if err != nil { return nil, err } body, err := p.ParseBlock() if err != nil { return nil, err } node := &ForEachNode{ Destructure: destructure, ElementKind: elementKind, Iterable: iterable, Reversed: reversed, Body: body, Loc: p.SpanFrom(beginLoc), } if name != nil { node.Name = name.Value node.NameLoc = name.Loc } if indexName != nil { node.IndexName = indexName.Value node.IndexNameLoc = indexName.Loc } return node, nil } func (p *Parser) matchesRangeOrForEachHeader() bool { pos := p.pos if pos >= len(p.tokens) { return false } if p.tokens[pos].Type == tokeniser.TokenOpenParen { pos++ if pos >= len(p.tokens) || p.tokens[pos].Type != tokeniser.TokenIdentifier { return false } for { pos++ if pos >= len(p.tokens) { return false } if p.tokens[pos].Type == tokeniser.TokenCloseParen { pos++ break } if p.tokens[pos].Type != tokeniser.TokenComma { return false } pos++ if pos >= len(p.tokens) || p.tokens[pos].Type != tokeniser.TokenIdentifier { return false } } } else if p.tokens[pos].Type == tokeniser.TokenIdentifier { pos++ } else { return false } if pos+1 < len(p.tokens) && p.tokens[pos].Type == tokeniser.TokenDot && p.tokens[pos+1].Type == tokeniser.TokenAmpersand { pos += 2 if pos < len(p.tokens) && p.tokens[pos].Type == tokeniser.TokenKeyword && p.tokens[pos].Value == string(tokeniser.KeywordMut) { pos++ } } if pos < len(p.tokens) && p.tokens[pos].Type == tokeniser.TokenComma { pos++ if pos >= len(p.tokens) || p.tokens[pos].Type != tokeniser.TokenIdentifier { return false } pos++ } return pos < len(p.tokens) && p.tokens[pos].Type == tokeniser.TokenKeyword && p.tokens[pos].Value == string(tokeniser.KeywordIn) } func (p *Parser) parseIterationAttributes() (bool, error) { reversed := false for p.Match(tokeniser.TokenAt) { p.Inc() name, err := p.ParseIdent() if err != nil { return false, err } if name.Name != "reversed" { return false, shared.NewError(name.Loc, "unknown iteration attribute @%s", name.Name) } if reversed { return false, shared.NewError(name.Loc, "duplicate iteration attribute @reversed") } reversed = true } return reversed, nil }