package build import ( "os" "path/filepath" "strings" "sync" "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) // Scan scans the directory. func Scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error) go func() { scan(files, functions, errors) close(functions) close(errors) }() return functions, errors } // scan scans the directory without channel allocations. func scan(files []string, functions chan<- *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 = directory.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<- *Function) error { contents, err := os.ReadFile(path) if err != nil { return err } tokens := token.Tokenize(contents) var ( i = 0 groupLevel = 0 blockLevel = 0 nameStart = -1 paramsStart = -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 { i++ continue } if tokens[i].Kind == token.EOF { return nil } return errors.New(errors.ExpectedFunctionName, path, tokens, i) } // Function parameters for i < len(tokens) { if tokens[i].Kind == token.GroupStart { groupLevel++ if groupLevel == 1 { paramsStart = i } i++ continue } if tokens[i].Kind == token.GroupEnd { groupLevel-- if groupLevel < 0 { return errors.New(errors.MissingGroupStart, path, tokens, i) } i++ if groupLevel == 0 { break } continue } if tokens[i].Kind == token.EOF { if groupLevel > 0 { return errors.New(errors.MissingGroupEnd, path, tokens, i) } if paramsStart == -1 { return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) } return nil } if groupLevel > 0 { i++ continue } return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) } // Function definition for i < len(tokens) { if tokens[i].Kind == token.BlockStart { blockLevel++ if blockLevel == 1 { bodyStart = i } i++ continue } if tokens[i].Kind == token.BlockEnd { blockLevel-- if blockLevel < 0 { return errors.New(errors.MissingBlockStart, path, tokens, i) } i++ if blockLevel == 0 { break } continue } if tokens[i].Kind == token.EOF { if blockLevel > 0 { return errors.New(errors.MissingBlockEnd, path, tokens, i) } if bodyStart == -1 { return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) } return nil } if blockLevel > 0 { i++ continue } return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) } functions <- &Function{ Name: tokens[nameStart].Text(), Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart : i+1], Variables: map[string]*Variable{}, } nameStart = -1 paramsStart = -1 bodyStart = -1 } }