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
}