package parser
import (
"errors"
"github.com/marzeq/qk/attributes"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/tokeniser"
)
func (p *Parser) parseLinkItems(close tokeniser.TokenKind) ([]LinkItemNode, bool, error) {
var items []LinkItemNode
conditional := false
for {
for p.Match(tokeniser.TokenNewline, tokeniser.TokenComma) {
p.Inc()
}
if p.Match(close) {
return items, conditional, nil
}
if p.Match(tokeniser.TokenEof) {
return nil, false, shared.NewError(p.CurrLoc(), "expected closing delimiter in @link")
}
if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
when, err := p.parseLinkWhen()
if err != nil {
return nil, false, err
}
items = append(items, LinkItemNode{When: when})
conditional = true
continue
}
if p.MatchBuiltin("compiler_error") {
directive, err := p.parseCompilerDirective()
if err != nil {
return nil, false, err
}
items = append(items, LinkItemNode{Directive: directive})
conditional = true
continue
}
kindToken, ok := p.ExpectGet(tokeniser.TokenIdentifier)
if !ok {
return nil, false, shared.NewError(p.PrevLoc(), "expected complete link entry or 'when' in @link")
}
var kind attributes.LinkKind
switch kindToken.Value {
case "system":
kind = attributes.LinkSystem
case "path":
kind = attributes.LinkPath
case "search":
kind = attributes.LinkSearchPath
case "framework":
kind = attributes.LinkFramework
default:
return nil, false, shared.NewError(kindToken.Loc, "unknown @link entry kind %q", kindToken.Value)
}
value, ok := p.ExpectGet(tokeniser.TokenString)
if !ok {
return nil, false, shared.NewError(p.PrevLoc(), "expected string after %s in @link", kindToken.Value)
}
if value.Value == "" {
return nil, false, shared.NewError(value.Loc, "@link values cannot be empty")
}
link := attributes.Link{Kind: kind, Value: value.Value}
items = append(items, LinkItemNode{Link: &link})
}
}
func (p *Parser) parseLinkWhen() (*LinkWhenNode, error) {
begin := p.CurrLoc()
node := &LinkWhenNode{}
for {
if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordWhen) {
return nil, shared.NewError(p.CurrLoc(), "expected 'when' in @link")
}
p.Inc()
condition, err := p.parseWhenCondition()
if err != nil {
return nil, err
}
if !p.Expect(tokeniser.TokenOpenCurly) {
return nil, shared.NewError(p.PrevLoc(), "expected '{' after @link condition")
}
items, _, err := p.parseLinkItems(tokeniser.TokenCloseCurly)
if err != nil {
return nil, err
}
p.Inc()
node.Branches = append(node.Branches, LinkWhenBranchNode{Condition: condition, Items: items})
branchEnd := p.pos
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordElse) {
p.pos = branchEnd
break
}
p.Inc()
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
continue
}
if !p.Expect(tokeniser.TokenOpenCurly) {
return nil, shared.NewError(p.PrevLoc(), "expected '{' after else in @link")
}
node.ElseItems, _, err = p.parseLinkItems(tokeniser.TokenCloseCurly)
if err != nil {
return nil, err
}
p.Inc()
break
}
node.Loc = p.SpanFrom(begin)
return node, nil
}
func (p *Parser) parseWhen(context WhenContext) (*WhenNode, error) {
begin := p.CurrLoc()
node := &WhenNode{Context: context}
for {
if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordWhen) {
return nil, shared.NewError(p.CurrLoc(), "expected 'when'")
}
branchBegin := p.CurrLoc()
p.Inc()
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
condition, err := p.parseWhenCondition()
if err != nil {
return nil, err
}
body, value, err := p.parseWhenBody(context)
if err != nil {
return nil, err
}
node.Branches = append(node.Branches, WhenBranchNode{
Condition: condition, Body: body, Value: value, Loc: branchBegin.WithEnd(p.PrevLoc()),
})
branchEnd := p.pos
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if !p.Match(tokeniser.TokenKeyword) || p.Peek().Value != string(tokeniser.KeywordElse) {
p.pos = branchEnd
break
}
p.Inc()
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if p.Match(tokeniser.TokenKeyword) && p.Peek().Value == string(tokeniser.KeywordWhen) {
continue
}
body, value, err = p.parseWhenBody(context)
if err != nil {
return nil, err
}
node.ElseBody, node.ElseValue = body, value
break
}
node.Loc = p.SpanFrom(begin)
return node, nil
}
func (p *Parser) parseWhenCondition() (ExpressionNode, error) {
start := p.pos
parenDepth, squareDepth := 0, 0
for !p.Match(tokeniser.TokenEof) {
switch p.Peek().Type {
case tokeniser.TokenOpenParen:
parenDepth++
case tokeniser.TokenCloseParen:
parenDepth--
case tokeniser.TokenOpenSquare:
squareDepth++
case tokeniser.TokenCloseSquare:
squareDepth--
case tokeniser.TokenOpenCurly:
if parenDepth == 0 && squareDepth == 0 {
tokens := make([]tokeniser.Token, 0, p.pos-start+1)
for _, token := range p.tokens[start:p.pos] {
if token.Type != tokeniser.TokenNewline {
tokens = append(tokens, token)
}
}
if len(tokens) == 0 {
return nil, shared.NewError(p.CurrLoc(), "expected condition after 'when'")
}
tokens = append(tokens, tokeniser.Token{Type: tokeniser.TokenEof, Loc: tokens[len(tokens)-1].Loc})
return NewParser(tokens).ParseWholeExpression()
}
}
p.Inc()
}
return nil, shared.NewError(p.CurrLoc(), "expected '{' after compile-time condition")
}
func (p *Parser) parseWhenBody(context WhenContext) ([]Node, Node, error) {
switch context {
case WhenDeclarations:
body, err := p.parseWhenDeclarationBody()
return body, nil, err
case WhenStatements:
block, err := p.ParseBlock()
if err != nil {
return nil, nil, err
}
return block.Body, nil, nil
case WhenExpression:
block, err := p.ParseBlockExpression()
return nil, block, err
case WhenType:
if !p.Expect(tokeniser.TokenOpenCurly) {
return nil, nil, shared.NewError(p.PrevLoc(), "expected '{' after compile-time condition")
}
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if p.MatchBuiltin("compiler_error") || p.MatchBuiltin("compiler_assert") {
directive, err := p.parseCompilerDirective()
if err != nil {
return nil, nil, err
}
for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
p.Inc()
}
if !p.Expect(tokeniser.TokenCloseCurly) {
return nil, nil, shared.NewError(p.PrevLoc(), "expected '}' after compiler directive")
}
return nil, directive, nil
}
typeNode, err := p.ParseType()
if err != nil {
return nil, nil, err
}
for p.Match(tokeniser.TokenNewline) {
p.Inc()
}
if !p.Expect(tokeniser.TokenCloseCurly) {
return nil, nil, shared.NewError(p.PrevLoc(), "expected '}' after selected type")
}
return nil, typeNode, nil
default:
panic("unknown when context")
}
}
func (p *Parser) parseWhenDeclarationBody() ([]Node, error) {
if !p.Expect(tokeniser.TokenOpenCurly) {
return nil, shared.NewError(p.PrevLoc(), "expected '{' after compile-time condition")
}
var body []Node
var parseErrors []error
for !p.Match(tokeniser.TokenCloseCurly, tokeniser.TokenEof) {
for p.Match(tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
p.Inc()
}
if p.Match(tokeniser.TokenCloseCurly) {
break
}
node, err := p.parseTopLevel()
if err != nil {
parseErrors = append(parseErrors, err)
p.synchroniseTopLevel()
continue
}
body = append(body, node)
if !p.Match(tokeniser.TokenCloseCurly, tokeniser.TokenNewline, tokeniser.TokenSemicolon) {
parseErrors = append(parseErrors, shared.NewError(p.CurrLoc(), "expected declaration separator in when block"))
p.synchroniseTopLevel()
}
}
if !p.Expect(tokeniser.TokenCloseCurly) {
parseErrors = append(parseErrors, shared.NewError(p.PrevLoc(), "expected '}' after when declarations"))
}
return body, errors.Join(parseErrors...)
}
func (p *Parser) parseCompilerDirective() (*CompilerDirectiveNode, error) {
begin := p.CurrLoc()
name := "compiler_error"
if p.MatchBuiltin("compiler_assert") {
name = "compiler_assert"
}
if !p.ConsumeBuiltin(name) || !p.Expect(tokeniser.TokenOpenParen) {
return nil, shared.NewError(p.PrevLoc(), "expected '(' after @%s", name)
}
nameEnd := p.tokens[p.pos-2].Loc
node := &CompilerDirectiveNode{Name: name, NameLoc: begin.WithEnd(nameEnd)}
if name == "compiler_assert" {
condition, err := p.ParseExpression()
if err != nil {
return nil, err
}
node.Condition = condition
if !p.Expect(tokeniser.TokenComma) {
return nil, shared.NewError(p.PrevLoc(), "expected ',' after @compiler_assert condition")
}
}
message, ok := p.ExpectGet(tokeniser.TokenString)
if !ok {
return nil, shared.NewError(p.PrevLoc(), "expected string message in @%s", name)
}
node.Message = message.Value
if !p.Expect(tokeniser.TokenCloseParen) {
return nil, shared.NewError(p.PrevLoc(), "expected ')' after @%s", name)
}
node.Loc = p.SpanFrom(begin)
return node, nil
}