package parser import ( "strings" "github.com/marzeq/qk/shared" "github.com/marzeq/qk/tokeniser" ) // SourceHeader contains the dependency information that can be read before // compile-time expansion. Imports are collected lexically, so imports inside a // compile-time branch are available while resolving that branch. type SourceHeader struct { Module string Imports []string Aliases []string } func ScanSourceHeader(tokens []tokeniser.Token) (SourceHeader, error) { var header SourceHeader for i := 0; i < len(tokens); i++ { tok := tokens[i] if tok.Type != tokeniser.TokenKeyword { continue } switch tok.Value { case string(tokeniser.KeywordModule): if header.Module != "" { continue } name, _, err := scanHeaderModulePath(tokens, i+1) if err != nil { return SourceHeader{}, err } header.Module = name case string(tokeniser.KeywordImport): imports, aliases, end, err := scanHeaderImport(tokens, i+1) if err != nil { return SourceHeader{}, err } header.Imports = append(header.Imports, imports...) header.Aliases = append(header.Aliases, aliases...) i = end - 1 } } return header, nil } func scanHeaderModulePath(tokens []tokeniser.Token, start int) (string, int, error) { if start >= len(tokens) || tokens[start].Type != tokeniser.TokenIdentifier { loc := shared.Location{} if start < len(tokens) { loc = tokens[start].Loc } return "", start, shared.NewError(loc, "expected module name") } parts := []string{tokens[start].Value} i := start + 1 for i < len(tokens) && tokens[i].Type == tokeniser.TokenDot { i++ if i >= len(tokens) || tokens[i].Type != tokeniser.TokenIdentifier { return "", i, shared.NewError(tokens[i-1].Loc, "expected module name after '.'") } parts = append(parts, tokens[i].Value) i++ } return strings.Join(parts, "."), i, nil } func scanHeaderImport(tokens []tokeniser.Token, start int) ([]string, []string, int, error) { i := start for i < len(tokens) && tokens[i].Type == tokeniser.TokenNewline { i++ } grouped := i < len(tokens) && tokens[i].Type == tokeniser.TokenOpenParen if grouped { i++ } var imports, aliases []string for i < len(tokens) { for i < len(tokens) && (tokens[i].Type == tokeniser.TokenNewline || tokens[i].Type == tokeniser.TokenComma) { i++ } if grouped && i < len(tokens) && tokens[i].Type == tokeniser.TokenCloseParen { return imports, aliases, i + 1, nil } name, end, err := scanHeaderModulePath(tokens, i) if err != nil { return nil, nil, i, err } i = end alias := "" if i < len(tokens) && tokens[i].Type == tokeniser.TokenIdentifier { alias = tokens[i].Value i++ } imports = append(imports, name) aliases = append(aliases, alias) if !grouped { return imports, aliases, i, nil } for i < len(tokens) && tokens[i].Type != tokeniser.TokenComma && tokens[i].Type != tokeniser.TokenNewline && tokens[i].Type != tokeniser.TokenCloseParen { i++ } } return imports, aliases, i, nil }