package scanner import ( "os" "path/filepath" "strings" "sync" "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/core" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" ) // Scan scans the directory. func Scan(files []string) (<-chan *core.Function, <-chan error) { functions := make(chan *core.Function) errors := make(chan error) go func() { scanFiles(files, functions, errors) close(functions) close(errors) }() return functions, errors } // scanFiles scans the list of files without channel allocations. func scanFiles(files []string, functions chan<- *core.Function, errors chan<- error) { wg := sync.WaitGroup{} for _, file := range files { stat, err := os.Stat(file) if err != nil { errors <- err return } if stat.IsDir() { err = fs.Walk(file, func(name string) { if !strings.HasSuffix(name, ".q") { return } fullPath := filepath.Join(file, name) wg.Add(1) go func() { defer wg.Done() err := scanFile(fullPath, functions) if err != nil { errors <- err } }() }) if err != nil { errors <- err } } else { wg.Add(1) go func() { defer wg.Done() err := scanFile(file, functions) if err != nil { errors <- err } }() } } wg.Wait() } // scanFile scans a single file. func scanFile(path string, functions chan<- *core.Function) error { contents, err := os.ReadFile(path) if err != nil { return err } tokens := token.Tokenize(contents) file := &fs.File{ Tokens: tokens, Path: path, } var ( i = 0 groupLevel = 0 blockLevel = 0 nameStart = -1 paramsStart = -1 paramsEnd = -1 bodyStart = -1 ) for { // Function name for i < len(tokens) { if tokens[i].Kind == token.Identifier { nameStart = i i++ break } if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { i++ continue } if tokens[i].Kind == token.Invalid { return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { return nil } return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) } // Function parameters for i < len(tokens) { if tokens[i].Kind == token.GroupStart { groupLevel++ i++ if groupLevel == 1 { paramsStart = i } continue } if tokens[i].Kind == token.GroupEnd { groupLevel-- if groupLevel < 0 { return errors.New(errors.MissingGroupStart, file, tokens[i].Position) } if groupLevel == 0 { paramsEnd = i i++ break } i++ continue } if tokens[i].Kind == token.Invalid { return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { if groupLevel > 0 { return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) } if paramsStart == -1 { return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } return nil } if groupLevel > 0 { i++ continue } return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } // Function definition for i < len(tokens) { if tokens[i].Kind == token.BlockStart { blockLevel++ i++ if blockLevel == 1 { bodyStart = i } continue } if tokens[i].Kind == token.BlockEnd { blockLevel-- if blockLevel < 0 { return errors.New(errors.MissingBlockStart, file, tokens[i].Position) } if blockLevel == 0 { break } i++ continue } if tokens[i].Kind == token.Invalid { return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { if blockLevel > 0 { return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } if bodyStart == -1 { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } return nil } if blockLevel > 0 { i++ continue } return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } name := tokens[nameStart].Text() body := tokens[bodyStart:i] function := core.NewFunction(name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 err := expression.EachParameter(parameters, func(tokens token.List) error { if len(tokens) != 1 { return errors.New(errors.NotImplemented, file, tokens[0].Position) } name := tokens[0].Text() register := x64.CallRegisters[count] uses := token.Count(function.Body, token.Identifier, name) if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } variable := &core.Variable{ Name: name, Register: register, Alive: uses, } function.AddVariable(variable) count++ return nil }) if err != nil { return err } functions <- function nameStart = -1 paramsStart = -1 bodyStart = -1 i++ } }