package parser

import (
	"math/big"
	"strconv"
	"strings"

	"github.com/marzeq/qk/shared"
	"github.com/marzeq/qk/tokeniser"
)

func (p *Parser) ParseExpression() (ExpressionNode, error) {
	return p.ParseLogicalOr()
}

func (p *Parser) ParseWholeExpression() (ExpressionNode, error) {
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	expr, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Match(tokeniser.TokenEof) {
		return nil, shared.NewError(p.CurrLoc(), "unexpected token %s after expression", p.Peek())
	}
	return expr, nil
}

func (p *Parser) ParseSizeOfExpression() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	if !p.ConsumeBuiltin("sizeof") {
		return nil, shared.NewError(p.CurrLoc(), "expected '@sizeof' builtin")
	}

	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@sizeof'")
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	var node Node
	var err error

	p.PushPos()
	node, err = p.ParseType()
	if err == nil {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	if err != nil || !p.Match(tokeniser.TokenCloseParen) {
		p.PopPos()
		node, err = p.ParseExpression()
		if err != nil {
			return nil, shared.NewError(p.PrevLoc(), "expected type or expression after '@sizeof'")
		}
	} else {
		p.CommitPos()
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@sizeof' operand")
	}

	switch node := node.(type) {
	case *NamedTypeNode:
		// A bare name can denote either a type or a value. Semantic resolution
		// chooses the visible binding without evaluating the value expression.
		return &SizeOfNode{
			Operand: node, Expression: &IdentifierNode{Name: node.Name, Module: node.ModName, Loc: node.Loc},
			Loc: p.SpanFrom(beginLoc),
		}, nil
	case TypeNode:
		return &SizeOfNode{
			Operand: node,
			Loc:     p.SpanFrom(beginLoc),
		}, nil
	case ExpressionNode:
		return &SizeOfExprNode{
			Operand: node,
			Loc:     p.SpanFrom(beginLoc),
		}, nil
	}

	panic("unreachable")
}

func (p *Parser) ParseAlignOfExpression() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	p.ConsumeBuiltin("alignof")
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@alignof'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	var operand Node
	p.PushPos()
	operand, err := p.ParseType()
	if err == nil {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	if err != nil || !p.Match(tokeniser.TokenCloseParen) {
		p.PopPos()
		operand, err = p.ParseExpression()
		if err != nil {
			return nil, shared.NewError(p.PrevLoc(), "expected type or expression after '@alignof('")
		}
	} else {
		p.CommitPos()
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@alignof' operand")
	}
	node := &AlignOfNode{Loc: p.SpanFrom(beginLoc)}
	switch operand := operand.(type) {
	case *NamedTypeNode:
		// A bare name can denote either a type or a value. Semantic resolution
		// chooses the visible binding without evaluating the value expression.
		node.Operand = operand
		node.Expression = &IdentifierNode{Name: operand.Name, Module: operand.ModName, Loc: operand.Loc}
	case TypeNode:
		node.Operand = operand
	case ExpressionNode:
		node.Expression = operand
	default:
		panic("unreachable")
	}
	return node, nil
}

func (p *Parser) ParseOffsetOfExpression() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	p.ConsumeBuiltin("offsetof")
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@offsetof'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	operand, err := p.ParseType()
	if err != nil {
		return nil, shared.NewError(p.PrevLoc(), "expected type after '@offsetof('")
	}
	if !p.Expect(tokeniser.TokenComma) {
		return nil, shared.NewError(p.PrevLoc(), "expected ',' after type in '@offsetof'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	field, ok := p.ExpectGet(tokeniser.TokenIdentifier)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected field name in '@offsetof'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@offsetof' field")
	}
	return &OffsetOfNode{Operand: operand, Field: field.Value, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) ParseLogicalOr() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseLogicalAnd()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TokenLogicalOr) {
		p.Inc()

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		right, err := p.ParseLogicalAnd()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{
			Op:       BinaryOpLogicalOr,
			Operand1: left,
			Operand2: right,
			Loc:      p.SpanFrom(beginLoc),
		}
	}

	return left, nil
}

func (p *Parser) ParseLogicalAnd() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseBitwiseOr()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TokenLogicalAnd) {
		p.Inc()

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		right, err := p.ParseBitwiseOr()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{
			Op:       BinaryOpLogicalAnd,
			Operand1: left,
			Operand2: right,
			Loc:      p.SpanFrom(beginLoc),
		}
	}

	return left, nil
}

func (p *Parser) ParseBitwiseOr() (ExpressionNode, error) {
	return p.parseLeftAssociative(p.ParseBitwiseXor, []tokeniser.TokenKind{tokeniser.TokenPipe}, map[tokeniser.TokenKind]BinaryOpKind{
		tokeniser.TokenPipe: BinaryOpBitwiseOr,
	})
}

func (p *Parser) ParseBitwiseXor() (ExpressionNode, error) {
	return p.parseLeftAssociative(p.ParseBitwiseAnd, []tokeniser.TokenKind{tokeniser.TokenCaret}, map[tokeniser.TokenKind]BinaryOpKind{
		tokeniser.TokenCaret: BinaryOpBitwiseXor,
	})
}

func (p *Parser) ParseBitwiseAnd() (ExpressionNode, error) {
	return p.parseLeftAssociative(p.ParseComparison, []tokeniser.TokenKind{tokeniser.TokenAmpersand}, map[tokeniser.TokenKind]BinaryOpKind{
		tokeniser.TokenAmpersand: BinaryOpBitwiseAnd,
	})
}

func (p *Parser) parseLeftAssociative(
	parseOperand func() (ExpressionNode, error),
	tokens []tokeniser.TokenKind,
	operators map[tokeniser.TokenKind]BinaryOpKind,
) (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := parseOperand()
	if err != nil {
		return nil, err
	}
	for p.Match(tokens...) {
		op := operators[p.Consume().Type]
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		right, err := parseOperand()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{Op: op, Operand1: left, Operand2: right, Loc: p.SpanFrom(beginLoc)}
	}
	return left, nil
}

func (p *Parser) ParseUnary() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	if p.MatchAdjacentPair(tokeniser.TokenPlus) {
		return nil, shared.NewError(beginLoc, "use ... += 1 instead")
	}
	if p.MatchAdjacentPair(tokeniser.TokenMinus) {
		return nil, shared.NewError(beginLoc, "use ... -= 1 instead")
	}

	if p.Match(tokeniser.TokenExclam, tokeniser.TokenMinus, tokeniser.TokenTilde) {
		op := p.Consume()

		var val UnaryOpKind
		switch op.Type {
		case tokeniser.TokenExclam:
			val = UnaryOpLogicalNot
		case tokeniser.TokenMinus:
			val = UnaryOpNegate
		case tokeniser.TokenTilde:
			val = UnaryOpBitwiseNot
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		expr, err := p.ParseUnary()
		if err != nil {
			return nil, err
		}

		return &UnaryOpNode{
			Op:      val,
			Operand: expr,
			Loc:     p.SpanFrom(beginLoc),
		}, nil
	}

	return p.ParsePostfix()
}

func (p *Parser) ParsePostfix() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()

	expr, err := p.ParseTerm()

	if err != nil {
		return nil, err
	}

	for {
		switch {
		case p.Match(tokeniser.TokenDot) && p.Next().Type == tokeniser.TokenOpenCurly:
			qualified, ok := expr.(*IdentifierNode)
			if !ok {
				qualified, ok = dottedIdentifier(expr)
			}
			if !ok {
				return expr, nil
			}
			p.Inc()
			return p.ParseStructLiteral(qualified)
		case p.Match(tokeniser.TokenOpenParen):
			// A type-producing binding uses ordinary call syntax. Before a
			// structural literal the following '.{' makes that interpretation
			// unambiguous, so retain the arguments on the type identifier.
			p.PushPos()
			typeArguments, typeErr := p.parseCallTypeArguments()
			if typeErr == nil && p.Match(tokeniser.TokenDot) && p.Next().Type == tokeniser.TokenOpenCurly {
				qualified, ok := expr.(*IdentifierNode)
				if !ok {
					qualified, ok = dottedIdentifier(expr)
				}
				if ok {
					p.CommitPos()
					qualified.TypeArguments = typeArguments
					qualified.Loc = p.SpanFrom(qualified.Loc)
					expr = qualified
					continue
				}
			}
			p.PopPos()
			call, err := p.ParseCall(expr)
			if err != nil {
				return nil, err
			}
			if identifier, ok := expr.(*IdentifierNode); ok {
				call.Name = identifier
			}
			expr = call
		case p.MatchAdjacentPair(tokeniser.TokenPlus):
			return nil, shared.NewError(p.CurrLoc(), "use ... += 1 instead")
		case p.MatchAdjacentPair(tokeniser.TokenMinus):
			return nil, shared.NewError(p.CurrLoc(), "use ... -= 1 instead")
		case p.Match(tokeniser.TokenOpenSquare):
			p.Inc()

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			var first ExpressionNode
			if !p.Match(tokeniser.TokenColon) {
				var err error
				first, err = p.ParseExpression()
				if err != nil {
					return nil, err
				}
			}

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			if p.Match(tokeniser.TokenColon) {
				p.Inc()
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}

				var end ExpressionNode
				if !p.Match(tokeniser.TokenCloseSquare) {
					var err error
					end, err = p.ParseExpression()
					if err != nil {
						return nil, err
					}
				}

				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				if !p.Expect(tokeniser.TokenCloseSquare) {
					return nil, shared.NewError(p.PrevLoc(), "expected ']'")
				}
				expr = &SliceExprNode{
					Subject: expr,
					Start:   first,
					End:     end,
					Loc:     p.SpanFrom(beginLoc),
				}
				continue
			}

			if first == nil {
				return nil, shared.NewError(p.CurrLoc(), "expected index or ':'")
			}
			if !p.Expect(tokeniser.TokenCloseSquare) {
				return nil, shared.NewError(p.PrevLoc(), "expected ']'")
			}

			expr = &IndexExprNode{
				Subject: expr,
				Index:   first,
				Loc:     p.SpanFrom(beginLoc),
			}
		case p.Match(tokeniser.TokenDot):
			p.Inc()

			if p.Match(tokeniser.TokenAsterisk) {
				p.Inc()
				expr = &UnaryOpNode{
					Op:      UnaryOpDereference,
					Operand: expr,
					Loc:     p.SpanFrom(beginLoc),
				}
				continue
			}

			if p.Match(tokeniser.TokenAmpersand) {
				p.Inc()
				op := UnaryOpReference
				if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) {
					p.Inc()
					op = UnaryOpMutableReference
				}
				expr = &UnaryOpNode{
					Op:      op,
					Operand: expr,
					Loc:     p.SpanFrom(beginLoc),
				}
				continue
			}

			if p.Match(tokeniser.TokenOpenParen) {
				p.Inc()
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				typ, err := p.ParseType()
				if err != nil {
					return nil, err
				}
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				if !p.Expect(tokeniser.TokenCloseParen) {
					return nil, shared.NewError(p.PrevLoc(), "expected ')'")
				}
				expr = &CastNode{
					ToType:  typ,
					Operand: expr,
					Loc:     p.SpanFrom(beginLoc),
				}
				continue
			}

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			field, err := p.ParseIdent()
			if err != nil {
				return nil, err
			}

			expr = &FieldAccessNode{
				Subject: expr,
				Field:   field,
				Loc:     p.SpanFrom(beginLoc),
			}
		default:
			return expr, nil
		}
	}

}

func dottedIdentifier(expr ExpressionNode) (*IdentifierNode, bool) {
	var parts []string
	var typeArguments []TypeNode
	for {
		switch n := expr.(type) {
		case *FieldAccessNode:
			if len(parts) == 0 {
				typeArguments = n.Field.TypeArguments
			}
			parts = append([]string{n.Field.Name}, parts...)
			expr = n.Subject
		case *IdentifierNode:
			parts = append([]string{n.Name}, parts...)
			if len(parts) < 2 {
				return nil, false
			}
			return &IdentifierNode{
				Name: parts[len(parts)-1], Module: strings.Join(parts[:len(parts)-1], "."),
				TypeArguments: typeArguments, Loc: n.Loc,
			}, true
		default:
			return nil, false
		}
	}
}

func (p *Parser) ParseComparison() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseShift()
	if err != nil {
		return nil, err
	}
	for p.Match(tokeniser.TokenEqualsEquals, tokeniser.TokenNotEquals,
		tokeniser.TokenLess, tokeniser.TokenGreater, tokeniser.TokenLessEquals, tokeniser.TokenGreaterEquals) {
		op := p.Consume()
		var val BinaryOpKind
		switch op.Type {
		case tokeniser.TokenEqualsEquals:
			val = BinaryOpEqual
		case tokeniser.TokenNotEquals:
			val = BinaryOpNotEqual
		case tokeniser.TokenLess:
			val = BinaryOpLess
		case tokeniser.TokenGreater:
			val = BinaryOpGreater
		case tokeniser.TokenLessEquals:
			val = BinaryOpLessEqual
		case tokeniser.TokenGreaterEquals:
			val = BinaryOpGreaterEqual
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		right, err := p.ParseShift()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{
			Op:       val,
			Operand1: left,
			Operand2: right,
			Loc:      p.SpanFrom(beginLoc),
		}
	}

	return left, nil
}

func (p *Parser) ParseShift() (ExpressionNode, error) {
	return p.parseLeftAssociative(p.ParseAddSub,
		[]tokeniser.TokenKind{tokeniser.TokenShiftLeft, tokeniser.TokenShiftRight},
		map[tokeniser.TokenKind]BinaryOpKind{
			tokeniser.TokenShiftLeft:  BinaryOpShiftLeft,
			tokeniser.TokenShiftRight: BinaryOpShiftRight,
		})
}

func (p *Parser) ParseAddSub() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseMulDiv()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TokenPlus, tokeniser.TokenMinus) {
		op := p.Consume()
		var val BinaryOpKind
		if op.Type == tokeniser.TokenPlus {
			val = BinaryOpAdd
		} else {
			val = BinaryOpSubtract
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		right, err := p.ParseMulDiv()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{
			Op:       val,
			Operand1: left,
			Operand2: right,
			Loc:      p.SpanFrom(beginLoc),
		}
	}

	return left, nil
}

func (p *Parser) ParseMulDiv() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	left, err := p.ParseUnary()
	if err != nil {
		return nil, err
	}

	for p.Match(tokeniser.TokenAsterisk, tokeniser.TokenSlash, tokeniser.TokenPercent) {
		op := p.Consume()
		var val BinaryOpKind
		switch op.Type {
		case tokeniser.TokenAsterisk:
			val = BinaryOpMultiply
		case tokeniser.TokenSlash:
			val = BinaryOpDivide
		case tokeniser.TokenPercent:
			val = BinaryOpModulo
		default:
			return nil, shared.NewError(p.PrevLoc(), "unexpected operator %s", op)
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		right, err := p.ParseUnary()
		if err != nil {
			return nil, err
		}
		left = &BinaryOpNode{
			Op:       val,
			Operand1: left,
			Operand2: right,
			Loc:      p.SpanFrom(beginLoc),
		}
	}

	return left, nil
}

func (p *Parser) ParseTerm() (ExpressionNode, error) {
	beginLoc := p.CurrLoc()
	if p.Match(tokeniser.TokenPipe, tokeniser.TokenLogicalOr) {
		return p.ParseLambdaExpression()
	}
	if p.Match(tokeniser.TokenDot) && p.Next().Type == tokeniser.TokenOpenCurly {
		p.Inc()
		return p.ParseStructLiteral(nil)
	}
	if p.Match(tokeniser.TokenDot) && p.Next().Type == tokeniser.TokenIdentifier {
		p.Inc()
		variant := p.Consume()
		return &EnumLiteralNode{Variant: variant.Value, Loc: p.SpanFrom(beginLoc)}, nil
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordIf) {
		expr, err := p.ParseIfExpression()
		if err != nil {
			return nil, err
		}

		return expr, nil
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
		node, err := p.parseWhen(WhenExpression)
		if err != nil {
			return nil, err
		}
		return node, nil
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMatch) {
		return p.ParseMatch(true)
	}

	if p.Match(tokeniser.TokenOpenCurly) {
		expr, err := p.ParseBlockExpression()
		if err != nil {
			return nil, err
		}
		return expr, nil
	}

	if p.MatchBuiltin("sizeof") {
		expr, err := p.ParseSizeOfExpression()
		if err != nil {
			return nil, err
		}
		return expr, nil
	}

	if p.MatchBuiltin("alignof") {
		return p.ParseAlignOfExpression()
	}

	if p.MatchBuiltin("offsetof") {
		return p.ParseOffsetOfExpression()
	}
	if p.MatchBuiltin("embed") {
		return p.ParseEmbedExpression()
	}
	if p.MatchBuiltin("asm") {
		return p.ParseInlineAsmExpression()
	}
	if p.MatchBuiltin("repr") {
		begin := p.CurrLoc()
		p.ConsumeBuiltin("repr")
		if !p.Expect(tokeniser.TokenOpenParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@repr'")
		}
		operand, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@repr' operand")
		}
		return &ReprNode{Operand: operand, Loc: p.SpanFrom(begin)}, nil
	}

	if p.Match(tokeniser.TokenOpenParen) {
		p.Consume()

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		expr, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')'")
		}

		return expr, nil
	}

	if p.Match(tokeniser.TokenNoInitializer) {
		p.Inc()
		return &NoInitializerNode{Loc: p.SpanFrom(beginLoc)}, nil
	}

	if p.Match(tokeniser.TokenIdentifier) {
		ident, err := p.ParseIdent()
		if err != nil {
			return nil, err
		}
		return ident, nil
	}

	if p.MatchBuiltin("len") {
		p.ConsumeBuiltin("len")

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		if !p.Expect(tokeniser.TokenOpenParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@len'")
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		expr, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@len' expression")
		}

		return &UnaryOpNode{
			Op:      UnaryOpSliceLen,
			Operand: expr,
			Loc:     p.SpanFrom(beginLoc),
		}, nil
	}

	if p.Match(tokeniser.TokenOpenSquare) {
		return p.ParseSliceLiteral()
	}

	if p.Match(tokeniser.TokenKeyword) {
		val := p.Peek().Value

		switch val {
		case string(tokeniser.KeywordTrue), string(tokeniser.KeywordFalse):
			bLit := p.Consume()
			return &BoolLiteralNode{
				Value: bLit.Value,
				Loc:   p.SpanFrom(beginLoc),
			}, nil
		case string(tokeniser.KeywordNil):
			p.Inc()
			return &NilLiteralNode{
				Loc: p.SpanFrom(beginLoc),
			}, nil
		}
	}

	if p.Match(tokeniser.TokenNumber) {
		nLit := p.Consume()
		return &IntegerLiteralNode{
			Value: nLit.Value,
			Loc:   p.SpanFrom(beginLoc),
		}, nil
	}

	if p.Match(tokeniser.TokenFloat) {
		nLit := p.Consume()
		return &FloatLiteralNode{
			Value: nLit.Value,
			Loc:   p.SpanFrom(beginLoc),
		}, nil
	}

	if p.Match(tokeniser.TokenChar) {
		cLit := p.Consume()
		return &CharLiteralNode{
			Value: []byte(cLit.Value)[0],
			Loc:   p.SpanFrom(beginLoc),
		}, nil
	}

	if p.Match(tokeniser.TokenString) {
		sLit := p.Consume()
		return &StringLiteralNode{
			Value: sLit.Value,
			Loc:   p.SpanFrom(beginLoc),
		}, nil
	}

	if p.Match(tokeniser.TokenCString) {
		literal := p.Consume()
		return &CStringLiteralNode{Value: literal.Value, Loc: p.SpanFrom(beginLoc)}, nil
	}

	return nil, shared.NewError(p.CurrLoc(), "unexpected token %s", p.Peek())
}

func (p *Parser) ParseLambdaExpression() (*LambdaNode, error) {
	beginLoc := p.CurrLoc()
	var args []*FunctionNodeArg
	typedVariadic := false

	if p.Match(tokeniser.TokenLogicalOr) {
		p.Inc()
	} else {
		p.Inc() // opening '|'
		for {
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenPipe) {
				p.Inc()
				break
			}
			name, ok := p.ExpectGet(tokeniser.TokenIdentifier)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected lambda parameter name or '|'")
			}
			group := []*FunctionNodeArg{{Name: name.Value, Loc: name.Loc}}
			if p.Match(tokeniser.TokenEquals) {
				return nil, shared.NewError(p.CurrLoc(), "lambda parameters cannot have default values")
			}
			for !p.Match(tokeniser.TokenColon, tokeniser.TokenPipe) {
				if !p.Expect(tokeniser.TokenComma) {
					return nil, shared.NewError(p.PrevLoc(), "expected ':', ',' or '|' after lambda parameter")
				}
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				if p.Match(tokeniser.TokenPipe) {
					break
				}
				name, ok = p.ExpectGet(tokeniser.TokenIdentifier)
				if !ok {
					return nil, shared.NewError(p.PrevLoc(), "expected lambda parameter name")
				}
				group = append(group, &FunctionNodeArg{Name: name.Value, Loc: name.Loc})
				if p.Match(tokeniser.TokenEquals) {
					return nil, shared.NewError(p.CurrLoc(), "lambda parameters cannot have default values")
				}
			}

			if p.Match(tokeniser.TokenColon) {
				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()
				}
				typeNode, err := p.ParseType()
				if err != nil {
					return nil, err
				}
				if isTypedVariadic {
					typeNode = &SliceTypeNode{ElementType: typeNode, Loc: typeNode.GetLoc()}
					typedVariadic = true
				}
				for _, arg := range group {
					arg.Type = typeNode
				}
			}
			if p.Match(tokeniser.TokenEquals) {
				return nil, shared.NewError(p.CurrLoc(), "lambda parameters cannot have default values")
			}
			args = append(args, group...)
			if typedVariadic {
				if p.Match(tokeniser.TokenComma) {
					return nil, shared.NewError(p.CurrLoc(), "typed variadic parameter must be last")
				}
				if !p.Expect(tokeniser.TokenPipe) {
					return nil, shared.NewError(p.PrevLoc(), "expected '|' after lambda parameters")
				}
				break
			}
			if p.Match(tokeniser.TokenComma) {
				p.Inc()
				continue
			}
			if !p.Expect(tokeniser.TokenPipe) {
				return nil, shared.NewError(p.PrevLoc(), "expected ',' or '|' after lambda parameter")
			}
			break
		}
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenFatArrow) {
		return nil, shared.NewError(p.PrevLoc(), "expected '=>' after lambda parameters")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	var body ExpressionNode
	var err error
	if p.Match(tokeniser.TokenOpenCurly) {
		body, err = p.ParseBlockExpression()
	} else {
		body, err = p.ParseExpression()
	}
	if err != nil {
		return nil, err
	}
	return &LambdaNode{Args: args, Body: body, TypedVariadic: typedVariadic, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) ParseEmbedExpression() (*EmbedNode, error) {
	begin := p.CurrLoc()
	p.ConsumeBuiltin("embed")
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@embed'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Match(tokeniser.TokenString) {
		return nil, shared.NewError(p.CurrLoc(), "expected file path string in '@embed'")
	}
	path := p.Consume().Value
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@embed' path")
	}
	return &EmbedNode{Path: path, Loc: p.SpanFrom(begin)}, nil
}

// ParseInlineAsmExpression parses:
//
//	@asm("template",
//	  out u64 "=r",
//	  in value "r",
//	  clobber "cc",
//	  volatile)
//
// Outputs are intentionally listed before inputs because their positions form
// the leading operands in LLVM's inline-assembly constraint string.
func (p *Parser) ParseInlineAsmExpression() (*InlineAsmNode, error) {
	begin := p.CurrLoc()
	p.ConsumeBuiltin("asm")
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@asm'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	template, ok := p.ExpectGet(tokeniser.TokenString)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "@asm requires a string literal template")
	}
	node := &InlineAsmNode{Template: template.Value}
	seenInput := false
	seenClobber := false
	for {
		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 @asm")
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseParen) {
			p.Inc()
			break
		}
		entryLoc := p.CurrLoc()
		if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "out" {
			if seenInput || seenClobber {
				return nil, shared.NewError(entryLoc, "@asm outputs must precede inputs and clobbers")
			}
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			typeNode, err := p.ParseType()
			if err != nil {
				return nil, err
			}
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			constraint, ok := p.ExpectGet(tokeniser.TokenString)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected output constraint string after @asm output type")
			}
			node.Outputs = append(node.Outputs, InlineAsmOutput{TypeNode: typeNode, Constraint: constraint.Value, Loc: p.SpanFrom(entryLoc)})
			continue
		}
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordIn) {
			if seenClobber {
				return nil, shared.NewError(entryLoc, "@asm inputs must precede clobbers")
			}
			seenInput = true
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			value, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			constraint, ok := p.ExpectGet(tokeniser.TokenString)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected input constraint string after @asm input value")
			}
			node.Inputs = append(node.Inputs, InlineAsmInput{Value: value, Constraint: constraint.Value, Loc: p.SpanFrom(entryLoc)})
			continue
		}
		if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "clobber" {
			seenClobber = true
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			name, ok := p.ExpectGet(tokeniser.TokenString)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected clobber name string")
			}
			node.Clobbers = append(node.Clobbers, name.Value)
			continue
		}
		if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "volatile" {
			p.Inc()
			node.Volatile = true
			continue
		}
		return nil, shared.NewError(entryLoc, "expected 'out', 'in', 'clobber', or 'volatile' in @asm")
	}
	node.Loc = p.SpanFrom(begin)
	return node, nil
}

func (p *Parser) ParseFunctionCall(name *IdentifierNode) (*FunctionCallNode, error) {
	call, err := p.ParseCall(name)
	if err != nil {
		return nil, err
	}
	call.Name = name
	return call, nil
}

func (p *Parser) ParseCall(callee ExpressionNode) (*FunctionCallNode, error) {
	var args []ExpressionNode
	expanded := false

	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '('")
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	if !p.Match(tokeniser.TokenCloseParen) {
		for {
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenCloseParen) {
				break
			}

			arg, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}
			args = append(args, arg)
			if p.Match(tokeniser.Token3Dots) {
				p.Inc()
				expanded = true
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				if !p.Match(tokeniser.TokenCloseParen) {
					return nil, shared.NewError(p.CurrLoc(), "expanded slice must be the final call argument")
				}
				break
			}

			if !p.Match(tokeniser.TokenComma) {
				break
			}
			p.Inc()

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
		}
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')'")
	}

	return &FunctionCallNode{
		Callee:            callee,
		Args:              args,
		VariadicExpansion: expanded,
		Loc:               callee.GetLoc().WithEnd(p.PrevLoc()),
	}, nil
}

func (p *Parser) ParseStructLiteral(name *IdentifierNode) (*StructLiteralNode, error) {
	beginLoc := p.CurrLoc()
	if name != nil {
		beginLoc = name.GetLoc()
	}

	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' to start struct literal")
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	node := &StructLiteralNode{
		Name: name,
		Loc:  p.SpanFrom(beginLoc),
	}
	if p.Match(tokeniser.TokenDot) {
		for {
			if !p.Expect(tokeniser.TokenDot) {
				return nil, shared.NewError(p.PrevLoc(), "expected '.' before flag name")
			}
			member, ok := p.ExpectGet(tokeniser.TokenIdentifier)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected flag name")
			}
			node.FlagMembers = append(node.FlagMembers, member.Value)
			if !p.Match(tokeniser.TokenComma, tokeniser.TokenNewline) {
				break
			}
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenCloseCurly) {
				break
			}
		}
		if !p.Expect(tokeniser.TokenCloseCurly) {
			return nil, shared.NewError(p.PrevLoc(), "expected '}' to end flags literal")
		}
		node.Loc = p.SpanFrom(beginLoc)
		return node, nil
	}
	for {
		if p.Match(tokeniser.TokenCloseCurly) {
			break
		}
		if p.Match(tokeniser.TokenNoInitializer) {
			node.NoInitRemaining = true
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenComma) {
				p.Inc()
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
			}
			if !p.Match(tokeniser.TokenCloseCurly) {
				return nil, shared.NewError(p.CurrLoc(), "'---' must be the final struct initializer entry")
			}
			break
		}

		fieldName, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected field name")
		}

		if !p.Expect(tokeniser.TokenEquals) {
			return nil, shared.NewError(p.PrevLoc(), "expected '='")
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		fieldValue, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}

		node.Fields = append(node.Fields, shared.Pair[string, ExpressionNode]{L: fieldName.Value, R: fieldValue})

		if !p.Match(tokeniser.TokenComma, tokeniser.TokenNewline) {
			break
		}
		p.Inc()

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}

	if !p.Expect(tokeniser.TokenCloseCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' to end struct literal")
	}

	node.Loc = p.SpanFrom(beginLoc)
	return node, nil
}

func (p *Parser) ParseSliceLiteral() (*SliceLiteralNode, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TokenOpenSquare) {
		return nil, shared.NewError(p.PrevLoc(), "expected '[' to start slice literal")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	var elements []ExpressionNode
	for {
		if p.Match(tokeniser.TokenCloseSquare) {
			break
		}
		elem, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		if len(elements) == 0 && p.Match(tokeniser.TokenSemicolon) {
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			amount, err := p.ParseExpression()
			if err != nil {
				return nil, err
			}
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if !p.Expect(tokeniser.TokenCloseSquare) {
				return nil, shared.NewError(p.PrevLoc(), "expected ']' to end repeated slice literal")
			}
			return &SliceLiteralNode{
				RepeatValue:  elem,
				RepeatAmount: amount,
				Loc:          p.SpanFrom(beginLoc),
			}, nil
		}
		elements = append(elements, elem)
		if !p.Match(tokeniser.TokenComma, tokeniser.TokenNewline) {
			break
		}
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	if !p.Expect(tokeniser.TokenCloseSquare) {
		return nil, shared.NewError(p.PrevLoc(), "expected ']' to end slice literal")
	}
	return &SliceLiteralNode{
		Elements: elements,
		Loc:      p.SpanFrom(beginLoc),
	}, nil
}

func (p *Parser) ParseIdent() (*IdentifierNode, error) {
	beginLoc := p.CurrLoc()
	firstIdent, ok := p.ExpectGet(tokeniser.TokenIdentifier)
	if !ok {
		return nil, shared.NewError(p.PrevLoc(), "expected identifier")
	}
	node := &IdentifierNode{
		Name: firstIdent.Value,
		Loc:  p.SpanFrom(beginLoc),
	}

	return node, nil
}

func (p *Parser) ParseType() (TypeNode, error) {
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
		return p.parseWhen(WhenType)
	}
	if p.MatchBuiltin("reprof") {
		begin := p.CurrLoc()
		p.ConsumeBuiltin("reprof")
		if !p.Expect(tokeniser.TokenOpenParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected '(' after '@reprof'")
		}
		operand, err := p.ParseType()
		if err != nil {
			return nil, err
		}
		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')' after '@reprof' operand")
		}
		return &ReprTypeNode{Operand: operand, Loc: p.SpanFrom(begin)}, nil
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordDyn) {
		return nil, shared.NewError(p.CurrLoc(), "dynamic trait pointer type must start with '*'")
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) &&
		p.Next().Type == tokeniser.TokenKeyword && p.Next().Value == string(tokeniser.KeywordDyn) {
		return nil, shared.NewError(p.CurrLoc(), "mutable dynamic trait pointer type must start with '*mut'")
	}
	if p.Match(tokeniser.TokenAsterisk) {
		return p.ParsePointerType()
	}

	if p.Match(tokeniser.TokenOpenSquare) {
		return p.ParseArrayOrSliceType()
	}

	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordStruct) {
		return p.ParseStructType()
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordEnum) {
		return p.ParseEnumType()
	}
	if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "flags" {
		if p.Next().Type == tokeniser.TokenOpenParen {
			return p.ParseFlagsType()
		}
		if p.Next().Type == tokeniser.TokenOpenCurly {
			return nil, shared.NewError(p.Next().Loc, "expected backing type in parentheses after 'flags'")
		}
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordUnion) {
		return p.ParseUnionType()
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordOpaque) {
		loc := p.CurrLoc()
		p.Inc()
		return &OpaqueTypeNode{Loc: loc}, nil
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordTrait) {
		return p.ParseTraitType()
	}

	return p.ParseNamedType()
}

func (p *Parser) ParseDynType(begin shared.Location, mutable bool) (*DynTypeNode, error) {
	if !p.Expect(tokeniser.TokenKeyword) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'dyn' to start dynamic trait type")
	}
	trait, err := p.ParseNamedType()
	if err != nil {
		return nil, err
	}
	return &DynTypeNode{TraitType: trait, Mutable: mutable, Loc: p.SpanFrom(begin)}, nil
}

func (p *Parser) ParseTraitType() (*TraitTypeNode, error) {
	begin := p.CurrLoc()
	p.Inc()
	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after trait")
	}
	methods := []TraitMethodNode{}
	seen := map[string]struct{}{}
	for {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseCurly) {
			p.Inc()
			break
		}
		loc := p.CurrLoc()
		kw, ok := p.ExpectGet(tokeniser.TokenKeyword)
		if !ok || kw.Value != string(tokeniser.KeywordLet) {
			return nil, shared.NewError(p.PrevLoc(), "expected trait method declaration")
		}
		name, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected trait method name")
		}
		if _, duplicate := seen[name.Value]; duplicate {
			return nil, shared.NewError(name.Loc, "duplicate trait method %q", name.Value)
		}
		seen[name.Value] = struct{}{}
		genericParameters := []GenericParameterNode{}
		if !p.Expect(tokeniser.TokenOpenParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected '('")
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		receiver := MethodReceiverNone
		if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "self" {
			p.Inc()
			receiver = MethodReceiverValue
		} else if p.Match(tokeniser.TokenAsterisk) {
			p.Inc()
			receiver = MethodReceiverPointer
			if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) {
				p.Inc()
				receiver = MethodReceiverMutablePointer
			}
			self, ok := p.ExpectGet(tokeniser.TokenIdentifier)
			if !ok || self.Value != "self" {
				return nil, shared.NewError(p.PrevLoc(), "trait method receiver must be self, *self, or *mut self")
			}
		} else {
			return nil, shared.NewError(p.CurrLoc(), "trait method must have a self, *self, or *mut self receiver")
		}
		args := []*FunctionNodeArg{}
		if p.Match(tokeniser.TokenComma) {
			p.Inc()
		}
		for {
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenCloseParen) {
				break
			}
			if parameter, matched, parseErr := p.parseTypeParameter(); matched || parseErr != nil {
				if parseErr != nil {
					return nil, parseErr
				}
				genericParameters = append(genericParameters, parameter)
				if p.Match(tokeniser.TokenComma) {
					p.Inc()
				}
				continue
			}
			arg, err := p.ParseIdent()
			if err != nil {
				return nil, err
			}

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			if !p.Expect(tokeniser.TokenColon) {
				return nil, shared.NewError(
					p.PrevLoc(),
					"expected ':' after trait method parameter",
				)
			}

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			t, err := p.ParseType()
			if err != nil {
				return nil, err
			}

			args = append(args, &FunctionNodeArg{
				Name: arg.Name,
				Type: t,
				Loc:  arg.Loc,
			})

			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}

			if !p.Match(tokeniser.TokenComma) {
				break
			}

			p.Inc()
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}

		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')'")
		}
		var ret TypeNode = &NamedTypeNode{Name: "void", Loc: loc}
		if p.Match(tokeniser.TokenColon) {
			p.Inc()
			var err error
			ret, err = p.parseFunctionReturnType()
			if err != nil {
				return nil, err
			}
		}
		var body Node
		var err error
		expressionBody := false
		if p.Match(tokeniser.TokenOpenCurly) {
			body, err = p.ParseBlock()
			if err != nil {
				return nil, err
			}
		} else if p.Match(tokeniser.TokenEquals) {
			p.Inc()
			expressionBody = true
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			if p.Match(tokeniser.TokenOpenCurly) {
				body, err = p.ParseBlockExpression()
			} else {
				body, err = p.ParseExpression()
			}
			if err != nil {
				return nil, err
			}
		}
		methods = append(methods, TraitMethodNode{
			Name: name.Value, GenericParameters: genericParameters, Receiver: receiver,
			Args: args, ReturnType: ret, Body: body, ExpressionBody: expressionBody,
			Loc: p.SpanFrom(loc),
		})
		if p.Match(tokeniser.TokenComma, tokeniser.TokenSemicolon) {
			p.Inc()
		}
	}
	return &TraitTypeNode{Methods: methods, Loc: p.SpanFrom(begin)}, nil
}

func (p *Parser) ParseUnionType() (*UnionTypeNode, error) {
	beginLoc := p.CurrLoc()
	if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordUnion) {
		return nil, shared.NewError(p.CurrLoc(), "expected 'union'")
	}
	p.Inc()
	var tagType TypeNode
	autoTag := false
	if p.Match(tokeniser.TokenOpenParen) {
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenAt) {
			p.Inc()
			attribute, err := p.ParseIdent()
			if err != nil {
				return nil, err
			}
			if attribute.Name != "auto" {
				return nil, shared.NewError(attribute.Loc, "unknown tagged union attribute @%s; expected @auto", attribute.Name)
			}
			autoTag = true
		} else {
			var err error
			tagType, err = p.ParseType()
			if err != nil {
				return nil, err
			}
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if !p.Expect(tokeniser.TokenCloseParen) {
			return nil, shared.NewError(p.PrevLoc(), "expected ')' after tagged union tag type")
		}
	}
	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after union")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	fields := []StructField{}
	variants := []TaggedUnionVariantNode{}
	seen := map[string]struct{}{}
	for !p.Match(tokeniser.TokenCloseCurly) {
		name, err := p.ParseIdent()
		if err != nil {
			return nil, err
		}
		if _, exists := seen[name.Name]; exists {
			return nil, shared.NewError(name.Loc, "duplicate union field %q", name.Name)
		}
		seen[name.Name] = struct{}{}
		if tagType != nil || autoTag {
			variant := TaggedUnionVariantNode{Name: name.Name, Loc: name.Loc}
			if p.Match(tokeniser.TokenOpenParen) {
				p.Inc()
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
				fieldNames := map[string]struct{}{}
				for !p.Match(tokeniser.TokenCloseParen) {
					fieldName := ""
					fieldLoc := p.CurrLoc()
					if p.Match(tokeniser.TokenIdentifier) && p.Next().Type == tokeniser.TokenColon {
						fieldName = p.Consume().Value
						if _, duplicate := fieldNames[fieldName]; duplicate {
							return nil, shared.NewError(fieldLoc, "duplicate tagged union payload field %q", fieldName)
						}
						fieldNames[fieldName] = struct{}{}
						p.Inc()
					}
					fieldType, err := p.ParseType()
					if err != nil {
						return nil, err
					}
					variant.Fields = append(variant.Fields, StructField{Name: fieldName, Type: fieldType})
					if p.Match(tokeniser.TokenCloseParen) {
						break
					}
					if !p.Expect(tokeniser.TokenComma) {
						return nil, shared.NewError(p.PrevLoc(), "expected ',' after tagged union payload field")
					}
					for p.Match(tokeniser.TokenNewline) {
						p.Inc()
					}
				}
				if !p.Expect(tokeniser.TokenCloseParen) {
					return nil, shared.NewError(p.PrevLoc(), "expected ')' after tagged union payload")
				}
				variant.Loc = name.Loc.WithEnd(p.PrevLoc())
			}
			variants = append(variants, variant)
			if p.Match(tokeniser.TokenCloseCurly) {
				break
			}
			if !p.Expect(tokeniser.TokenComma) {
				return nil, shared.NewError(p.PrevLoc(), "expected ',' after tagged union variant")
			}
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			continue
		}
		if !p.Expect(tokeniser.TokenColon) {
			return nil, shared.NewError(p.PrevLoc(), "expected ':' after union field")
		}
		fieldType, err := p.ParseType()
		if err != nil {
			return nil, err
		}
		fields = append(fields, StructField{Name: name.Name, Type: fieldType})
		if p.Match(tokeniser.TokenCloseCurly) {
			break
		}
		if !p.Expect(tokeniser.TokenComma) {
			return nil, shared.NewError(p.PrevLoc(), "expected ',' after union field")
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	if !p.Expect(tokeniser.TokenCloseCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' after union")
	}
	if tagType == nil && !autoTag && len(fields) == 0 {
		return nil, shared.NewError(beginLoc, "union must declare at least one field")
	}
	if autoTag && len(variants) == 0 {
		return nil, shared.NewError(beginLoc, "auto-tagged union must declare at least one variant")
	}
	return &UnionTypeNode{TagType: tagType, AutoTag: autoTag, Fields: fields, Variants: variants, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) ParseMatch(expression bool) (*MatchNode, error) {
	begin := p.CurrLoc()
	kw, ok := p.ExpectGet(tokeniser.TokenKeyword)
	if !ok || kw.Value != string(tokeniser.KeywordMatch) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'match'")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	var subjects []ExpressionNode
	for {
		subject, err := p.ParseExpression()
		if err != nil {
			return nil, err
		}
		subjects = append(subjects, subject)
		if !p.Match(tokeniser.TokenComma) {
			break
		}
		p.Inc()
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	node := &MatchNode{Subjects: subjects, Expression: expression}
	var err error
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordAs) {
		if len(subjects) != 1 {
			return nil, shared.NewError(p.CurrLoc(), "a multi-subject match cannot use an 'as' binding")
		}
		p.Inc()
		binding, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected binding name after 'as'")
		}
		node.BindingName, node.BindingLoc = binding.Value, binding.Loc
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after match subject")
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	for !p.Match(tokeniser.TokenCloseCurly) {
		armBegin := p.CurrLoc()
		var patterns []*MatchPatternNode
		for {
			pattern, err := p.parseMatchPattern()
			if err != nil {
				return nil, err
			}
			patterns = append(patterns, pattern)
			if !p.Match(tokeniser.TokenComma) {
				break
			}
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
		}
		var guard ExpressionNode
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordIf) {
			p.Inc()
			guard, err = p.ParseExpression()
			if err != nil {
				return nil, err
			}
		}
		if !p.Expect(tokeniser.TokenFatArrow) {
			return nil, shared.NewError(p.PrevLoc(), "expected '=>' after match pattern")
		}
		if len(patterns) != len(subjects) && !(len(patterns) == 1 && patterns[0].Kind == MatchPatternWildcard) {
			return nil, shared.NewError(armBegin, "match arm has %d patterns for %d subjects", len(patterns), len(subjects))
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		bodyStartsWithBlock := p.Match(tokeniser.TokenOpenCurly)
		var body ExpressionNode
		if !expression && p.Match(tokeniser.TokenOpenCurly) {
			body, err = p.ParseBlock()
		} else {
			body, err = p.ParseExpression()
		}
		if err != nil {
			return nil, err
		}
		node.Arms = append(node.Arms, MatchArmNode{
			Patterns: patterns, Guard: guard, Body: body, Loc: armBegin.WithEnd(body.GetLoc()),
		})
		if p.Match(tokeniser.TokenCloseCurly) {
			break
		}
		_, blockBody := body.(*BlockNode)
		blockBody = blockBody && bodyStartsWithBlock
		if p.Match(tokeniser.TokenComma) {
			p.Inc()
		} else if !blockBody {
			return nil, shared.NewError(p.PrevLoc(), "expected ',' after match arm")
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}
	if len(node.Arms) == 0 {
		return nil, shared.NewError(begin, "match must declare at least one arm")
	}
	if !p.Expect(tokeniser.TokenCloseCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}' after match arms")
	}
	node.Loc = p.SpanFrom(begin)
	return node, nil
}

func (p *Parser) parseMatchPattern() (*MatchPatternNode, error) {
	begin := p.CurrLoc()
	first, err := p.parseMatchPatternTerm()
	if err != nil {
		return nil, err
	}
	alternatives := []*MatchPatternNode{first}
	for p.Match(tokeniser.TokenPipe) {
		p.Inc()
		alternative, err := p.parseMatchPatternTerm()
		if err != nil {
			return nil, err
		}
		alternatives = append(alternatives, alternative)
	}
	if len(alternatives) == 1 {
		return first, nil
	}
	return &MatchPatternNode{Kind: MatchPatternAlternative, Alternatives: alternatives, Loc: begin.WithEnd(alternatives[len(alternatives)-1].Loc)}, nil
}

func (p *Parser) parseMatchPatternTerm() (*MatchPatternNode, error) {
	begin := p.CurrLoc()
	pattern, err := p.parseMatchPatternAtom()
	if err != nil {
		return nil, err
	}
	if !p.Match(tokeniser.Token2Dots) {
		return pattern, nil
	}
	if pattern.Kind != MatchPatternLiteral {
		return nil, shared.NewError(pattern.Loc, "range pattern must start with a literal")
	}
	p.Inc()
	end, err := p.parseMatchPatternLiteral()
	if err != nil {
		return nil, shared.NewError(p.CurrLoc(), "expected literal after '..' in match range")
	}
	return &MatchPatternNode{Kind: MatchPatternRange, Start: pattern.Literal, End: end, Loc: begin.WithEnd(end.GetLoc())}, nil
}

func (p *Parser) parseMatchPatternAtom() (*MatchPatternNode, error) {
	begin := p.CurrLoc()
	if p.Match(tokeniser.TokenIdentifier) && p.Peek().Value == "_" {
		p.Inc()
		return &MatchPatternNode{Kind: MatchPatternWildcard, Loc: p.SpanFrom(begin)}, nil
	}
	if p.Match(tokeniser.TokenDot) {
		p.Inc()
		variant, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected variant name after '.'")
		}
		pattern := &MatchPatternNode{Kind: MatchPatternVariant, Variant: variant.Value, Loc: p.SpanFrom(begin)}
		if p.Match(tokeniser.TokenOpenParen) {
			pattern.Payload = true
			p.Inc()
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			for !p.Match(tokeniser.TokenCloseParen) {
				binding, ok := p.ExpectGet(tokeniser.TokenIdentifier)
				if !ok {
					return nil, shared.NewError(p.PrevLoc(), "expected payload binding in variant pattern")
				}
				entry := MatchBinding{Name: binding.Value, Loc: binding.Loc}
				if p.Match(tokeniser.TokenEquals) {
					p.Inc()
					entry.Field = entry.Name
					bound, ok := p.ExpectGet(tokeniser.TokenIdentifier)
					if !ok {
						return nil, shared.NewError(p.PrevLoc(), "expected binding name after '=' in variant pattern")
					}
					entry.Name, entry.Loc = bound.Value, bound.Loc
				}
				pattern.Bindings = append(pattern.Bindings, entry)
				if p.Match(tokeniser.TokenCloseParen) {
					break
				}
				if !p.Expect(tokeniser.TokenComma) {
					return nil, shared.NewError(p.PrevLoc(), "expected ',' after variant pattern binding")
				}
				for p.Match(tokeniser.TokenNewline) {
					p.Inc()
				}
			}
			if !p.Expect(tokeniser.TokenCloseParen) {
				return nil, shared.NewError(p.PrevLoc(), "expected ')' after variant pattern")
			}
			pattern.Loc = p.SpanFrom(begin)
		}
		return pattern, nil
	}
	literal, err := p.parseMatchPatternLiteral()
	if err != nil {
		return nil, err
	}
	return &MatchPatternNode{Kind: MatchPatternLiteral, Literal: literal, Loc: literal.GetLoc()}, nil
}

func (p *Parser) parseMatchPatternLiteral() (ExpressionNode, error) {
	begin := p.CurrLoc()
	switch {
	case p.Match(tokeniser.TokenNumber):
		tok := p.Consume()
		if strings.Contains(tok.Value, ".") {
			return &FloatLiteralNode{Value: tok.Value, Loc: p.SpanFrom(begin)}, nil
		}
		return &IntegerLiteralNode{Value: tok.Value, Loc: p.SpanFrom(begin)}, nil
	case p.Match(tokeniser.TokenChar):
		tok := p.Consume()
		return &CharLiteralNode{Value: tok.Value[0], Loc: p.SpanFrom(begin)}, nil
	case p.Match(tokeniser.TokenString):
		tok := p.Consume()
		return &StringLiteralNode{Value: tok.Value, Loc: p.SpanFrom(begin)}, nil
	case p.Match(tokeniser.TokenCString):
		tok := p.Consume()
		return &CStringLiteralNode{Value: tok.Value, Loc: p.SpanFrom(begin)}, nil
	case p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordTrue):
		p.Inc()
		return &BoolLiteralNode{Value: string(tokeniser.KeywordTrue), Loc: p.SpanFrom(begin)}, nil
	case p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordFalse):
		p.Inc()
		return &BoolLiteralNode{Value: string(tokeniser.KeywordFalse), Loc: p.SpanFrom(begin)}, nil
	default:
		return nil, shared.NewError(begin, "expected match pattern")
	}
}

func (p *Parser) ParseEnumType() (*EnumTypeNode, error) {
	beginLoc := p.CurrLoc()
	if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordEnum) {
		return nil, shared.NewError(p.CurrLoc(), "expected 'enum'")
	}
	p.Inc()
	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after enum")
	}
	variants := []string{}
	values := []string{}
	seen := map[string]struct{}{}
	valueMode := -1 // 0 is implicit, 1 is explicit.
	for {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseCurly) {
			p.Inc()
			break
		}
		variant, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected enum variant")
		}
		if _, exists := seen[variant.Value]; exists {
			return nil, shared.NewError(variant.Loc, "duplicate enum variant %q", variant.Value)
		}
		seen[variant.Value] = struct{}{}
		variants = append(variants, variant.Value)
		hasExplicitValue := p.Match(tokeniser.TokenEquals)
		mode := 0
		if hasExplicitValue {
			mode = 1
		}
		if valueMode != -1 && valueMode != mode {
			return nil, shared.NewError(variant.Loc, "cannot mix implicit and explicit enum values")
		}
		valueMode = mode
		if hasExplicitValue {
			p.Inc()
			value, ok := p.ExpectGet(tokeniser.TokenNumber)
			if !ok {
				return nil, shared.NewError(p.PrevLoc(), "expected integer literal after '=' in enum variant")
			}
			if !enumValueFits32Bits(value.Value) {
				return nil, shared.NewError(value.Loc, "enum value %s does not fit in 32 bits", value.Value)
			}
			values = append(values, value.Value)
		} else {
			values = append(values, strconv.Itoa(len(variants)-1))
		}
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenComma) {
			p.Inc()
			continue
		}
		if !p.Match(tokeniser.TokenCloseCurly) {
			return nil, shared.NewError(p.CurrLoc(), "expected ',' or '}' after enum variant")
		}
	}
	if len(variants) == 0 {
		return nil, shared.NewError(beginLoc, "enum must declare at least one variant")
	}
	return &EnumTypeNode{Variants: variants, Values: values, Loc: p.SpanFrom(beginLoc)}, nil
}

func enumValueFits32Bits(value string) bool {
	n, ok := new(big.Int).SetString(value, 10)
	if !ok {
		return false
	}
	min := big.NewInt(-1 << 31)
	max := new(big.Int).SetUint64(1<<32 - 1)
	return n.Cmp(min) >= 0 && n.Cmp(max) <= 0
}

func (p *Parser) ParseFlagsType() (*FlagsTypeNode, error) {
	beginLoc := p.CurrLoc()
	p.Inc()
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '(' after flags")
	}
	underlying, err := p.ParseNamedType()
	if err != nil {
		return nil, err
	}
	if !p.Expect(tokeniser.TokenCloseParen) || !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{' after flags underlying type")
	}
	known := map[string]*big.Int{}
	var variants, values []string
	valueMode := -1 // 0 is implicit, 1 is explicit.
	for {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseCurly) {
			p.Inc()
			break
		}
		name, ok := p.ExpectGet(tokeniser.TokenIdentifier)
		if !ok {
			return nil, shared.NewError(p.PrevLoc(), "expected flag name")
		}
		if _, exists := known[name.Value]; exists {
			return nil, shared.NewError(name.Loc, "duplicate flag %q", name.Value)
		}
		hasExplicitValue := p.Match(tokeniser.TokenEquals)
		mode := 0
		if hasExplicitValue {
			mode = 1
		}
		if valueMode != -1 && valueMode != mode {
			return nil, shared.NewError(name.Loc, "cannot mix implicit and explicit flag values")
		}
		valueMode = mode
		var value *big.Int
		if hasExplicitValue {
			p.Inc()
			var err error
			value, err = p.parseFlagValue(known)
			if err != nil {
				return nil, err
			}
		} else {
			value = new(big.Int).Lsh(big.NewInt(1), uint(len(variants)))
		}
		known[name.Value] = value
		variants, values = append(variants, name.Value), append(values, value.String())
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenComma) {
			p.Inc()
			continue
		}
		if !p.Match(tokeniser.TokenCloseCurly) {
			return nil, shared.NewError(p.CurrLoc(), "expected ',' or '}' after flag")
		}
	}
	if len(variants) == 0 {
		return nil, shared.NewError(beginLoc, "flags must declare at least one member")
	}
	return &FlagsTypeNode{Underlying: underlying, Variants: variants, Values: values, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) parseFlagValue(known map[string]*big.Int) (*big.Int, error) {
	result := new(big.Int)
	for {
		var term *big.Int
		if p.Match(tokeniser.TokenIdentifier) {
			tok := p.Consume()
			value, ok := known[tok.Value]
			if !ok {
				return nil, shared.NewError(tok.Loc, "unknown earlier flag %q", tok.Value)
			}
			term = new(big.Int).Set(value)
		} else if p.Match(tokeniser.TokenNumber) {
			tok := p.Consume()
			if tok.NumberBase == 16 {
				term, _ = new(big.Int).SetString(tok.Value, 10)
			} else if tok.Value == "1" && p.Match(tokeniser.TokenShiftLeft) {
				p.Inc()
				shift, ok := p.ExpectGet(tokeniser.TokenNumber)
				if !ok || shift.NumberBase != 10 {
					return nil, shared.NewError(p.PrevLoc(), "expected decimal bit position after '1 <<'")
				}
				bit, ok := new(big.Int).SetString(shift.Value, 10)
				if !ok || !bit.IsUint64() {
					return nil, shared.NewError(shift.Loc, "invalid flag bit position")
				}
				term = new(big.Int).Lsh(big.NewInt(1), uint(bit.Uint64()))
			} else {
				return nil, shared.NewError(tok.Loc, "flag values must use hexadecimal, '1 << bit', or earlier flag names")
			}
		} else {
			return nil, shared.NewError(p.CurrLoc(), "expected hexadecimal value, '1 << bit', or earlier flag name")
		}
		result.Or(result, term)
		if !p.Match(tokeniser.TokenPipe) {
			break
		}
		p.Inc()
	}
	return result, nil
}

func (p *Parser) ParseStructType() (*StructTypeNode, error) {
	beginLoc := p.CurrLoc()

	if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordStruct) {
		return nil, shared.NewError(p.PrevLoc(), "expected 'struct' keyword")
	}
	p.Inc()

	attrs, err := p.parseAttributes("")
	if err != nil {
		return nil, err
	}

	if !p.Expect(tokeniser.TokenOpenCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '{'")
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	var fields []StructField
	for !p.Match(tokeniser.TokenCloseCurly) {
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordUnion) {
			fieldType, err := p.ParseUnionType()
			if err != nil {
				return nil, err
			}
			fields = append(fields, StructField{Type: fieldType})
			if p.Match(tokeniser.TokenCloseCurly) {
				break
			}
			if !p.Expect(tokeniser.TokenComma) {
				return nil, shared.NewError(p.PrevLoc(), "expected ',' after struct field")
			}
			for p.Match(tokeniser.TokenNewline) {
				p.Inc()
			}
			continue
		}
		fieldName, err := p.ParseIdent()
		if err != nil {
			return nil, err
		}

		if !p.Expect(tokeniser.TokenColon) {
			return nil, shared.NewError(p.PrevLoc(), "expected ':'")
		}

		fieldType, err := p.ParseType()
		if err != nil {
			return nil, err
		}

		fields = append(fields, StructField{
			Name: fieldName.Name,
			Type: fieldType,
		})

		if p.Match(tokeniser.TokenCloseCurly) {
			break
		}
		if !p.Expect(tokeniser.TokenComma) {
			return nil, shared.NewError(p.PrevLoc(), "expected ',' after struct field")
		}

		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
	}

	if !p.Expect(tokeniser.TokenCloseCurly) {
		return nil, shared.NewError(p.PrevLoc(), "expected '}'")
	}

	return &StructTypeNode{
		Fields:     fields,
		Loc:        p.SpanFrom(beginLoc),
		Attributes: attrs,
	}, nil
}

func (p *Parser) ParseArrayOrSliceType() (TypeNode, error) {
	beginLoc := p.CurrLoc()

	if !p.Expect(tokeniser.TokenOpenSquare) {
		return nil, shared.NewError(p.PrevLoc(), "expected '[' to start slice type")
	}

	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}

	if p.Match(tokeniser.TokenCloseSquare) {
		p.Inc()
		mutable := false
		if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) {
			mutable = true
			p.Inc()
		}
		elementType, err := p.ParseType()
		if err != nil {
			return nil, err
		}
		return &SliceTypeNode{ElementType: elementType, Mutable: mutable, Loc: p.SpanFrom(beginLoc)}, nil
	}

	length, err := p.ParseExpression()
	if err != nil {
		return nil, err
	}
	for p.Match(tokeniser.TokenNewline) {
		p.Inc()
	}
	if !p.Expect(tokeniser.TokenCloseSquare) {
		return nil, shared.NewError(p.PrevLoc(), "expected ']' after array length")
	}
	elementType, err := p.ParseType()
	if err != nil {
		return nil, err
	}
	return &ArrayTypeNode{ElementType: elementType, Length: length, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) ParsePointerType() (TypeNode, error) {
	beginLoc := p.CurrLoc()
	if !p.Expect(tokeniser.TokenAsterisk) {
		return nil, shared.NewError(p.PrevLoc(), "expected '*' to start pointer type")
	}

	mutable := false
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordMut) {
		p.Inc()
		mutable = true
	}
	if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordDyn) {
		return p.ParseDynType(beginLoc, mutable)
	}

	var tpe TypeNode
	var err error
	if p.Match(tokeniser.TokenOpenParen) {
		tpe, err = p.ParseFunctionType()
	} else {
		tpe, err = p.ParseType()
	}
	if err != nil {
		return nil, err
	}
	if mutable {
		if _, ok := tpe.(*FunctionTypeNode); ok {
			return nil, shared.NewError(beginLoc, "function pointers cannot be mutable pointers")
		}
	}

	return &PointerTypeNode{
		BaseType: tpe,
		Mutable:  mutable,
		Loc:      p.SpanFrom(beginLoc),
	}, nil
}

func (p *Parser) ParseFunctionType() (*FunctionTypeNode, error) {
	beginLoc := p.CurrLoc()
	p.Inc() // (
	var params []TypeNode
	typedVariadic := false
	for {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseParen) {
			break
		}
		isTypedVariadic := p.Match(tokeniser.Token3Dots)
		if isTypedVariadic {
			p.Inc()
		}
		param, err := p.ParseType()
		if err != nil {
			return nil, err
		}
		if isTypedVariadic {
			param = &SliceTypeNode{ElementType: param, Loc: param.GetLoc()}
			typedVariadic = true
		}
		params = append(params, param)
		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.Inc()
	}
	if !p.Expect(tokeniser.TokenCloseParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected ')' in function pointer type")
	}
	if !p.Expect(tokeniser.TokenColon) {
		return nil, shared.NewError(p.PrevLoc(), "expected ':' and return type in function pointer type")
	}
	ret, err := p.ParseType()
	if err != nil {
		return nil, err
	}
	return &FunctionTypeNode{Parameters: params, ReturnType: ret, TypedVariadic: typedVariadic, Loc: p.SpanFrom(beginLoc)}, nil
}

func (p *Parser) ParseNamedType() (*NamedTypeNode, error) {
	beginLoc := p.CurrLoc()
	capture := false
	if p.Match(tokeniser.TokenDollar) {
		if !p.allowTypeCapture {
			return nil, shared.NewError(p.CurrLoc(), "type captures are only valid in method owner patterns")
		}
		capture = true
		p.Inc()
	}
	ident, err := p.ParseIdent()
	if err != nil {
		return nil, err
	}

	if !p.Match(tokeniser.TokenDot) {
		node := &NamedTypeNode{
			ModName: "",
			Name:    ident.Name,
			Capture: capture,
			Loc:     p.SpanFrom(beginLoc),
		}
		if p.Match(tokeniser.TokenOpenParen) {
			if capture {
				return nil, shared.NewError(node.Loc, "captured type parameter %q cannot accept type arguments", node.Name)
			}
			arguments, err := p.parseCallTypeArguments()
			if err != nil {
				return nil, err
			}
			node.TypeArguments = arguments
			node.Loc = p.SpanFrom(beginLoc)
		}
		if capture && p.Match(tokeniser.TokenColon) {
			p.Inc()
			constraint, err := p.ParseType()
			if err != nil {
				return nil, err
			}
			node.CaptureConstraint = constraint
			node.Loc = p.SpanFrom(beginLoc)
		}
		return node, nil
	}
	if capture {
		return nil, shared.NewError(beginLoc, "captured type parameter cannot be module-qualified")
	}
	parts := []string{ident.Name}
	for p.Match(tokeniser.TokenDot) {
		p.Inc()
		realIdent, err := p.ParseIdent()
		if err != nil {
			return nil, err
		}
		parts = append(parts, realIdent.Name)
	}

	node := &NamedTypeNode{
		ModName: strings.Join(parts[:len(parts)-1], "."),
		Name:    parts[len(parts)-1],
		Loc:     p.SpanFrom(beginLoc),
	}
	if p.Match(tokeniser.TokenOpenParen) {
		arguments, err := p.parseCallTypeArguments()
		if err != nil {
			return nil, err
		}
		node.TypeArguments = arguments
		node.Loc = p.SpanFrom(beginLoc)
	}
	return node, nil
}

func (p *Parser) parseCallTypeArguments() ([]TypeNode, error) {
	if !p.Expect(tokeniser.TokenOpenParen) {
		return nil, shared.NewError(p.PrevLoc(), "expected '('")
	}
	var arguments []TypeNode
	for {
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseParen) {
			p.Inc()
			return arguments, nil
		}
		argument, err := p.ParseType()
		if err != nil {
			return nil, err
		}
		arguments = append(arguments, argument)
		for p.Match(tokeniser.TokenNewline) {
			p.Inc()
		}
		if p.Match(tokeniser.TokenCloseParen) {
			p.Inc()
			return arguments, nil
		}
		if !p.Expect(tokeniser.TokenComma) {
			return nil, shared.NewError(p.PrevLoc(), "expected ',' or ')' in type argument list")
		}
	}
}