q/src/scanner/scanner.go
Eduard Urbach 9b51680af5
All checks were successful
/ test (push) Successful in 13s
Improved scanner
2025-06-19 21:39:04 +02:00

168 lines
No EOL
2.9 KiB
Go

package scanner
import (
"os"
"path/filepath"
"strings"
"sync"
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/errors"
"git.urbach.dev/cli/q/src/fs"
"git.urbach.dev/cli/q/src/token"
)
// scanner is used to scan files before the actual compilation step.
type scanner struct {
files chan *fs.File
errors chan error
build *build.Build
queued sync.Map
group sync.WaitGroup
}
// queue scans the list of files.
func (s *scanner) queue(files ...string) {
for _, file := range files {
stat, err := os.Stat(file)
if err != nil {
s.errors <- err
return
}
if stat.IsDir() {
s.queueDirectory(file, "main")
} else {
s.queueFile(file, "main")
}
}
}
// queueDirectory queues an entire directory to be scanned.
func (s *scanner) queueDirectory(directory string, pkg string) {
_, loaded := s.queued.LoadOrStore(directory, nil)
if loaded {
return
}
err := fs.Walk(directory, func(name string) {
if !strings.HasSuffix(name, ".q") {
return
}
tmp := name[:len(name)-2]
for {
underscore := strings.LastIndexByte(tmp, '_')
if underscore == -1 {
break
}
condition := tmp[underscore+1:]
switch condition {
case "linux":
if s.build.OS != build.Linux {
return
}
case "mac":
if s.build.OS != build.Mac {
return
}
case "unix":
if s.build.OS != build.Linux && s.build.OS != build.Mac {
return
}
case "windows":
if s.build.OS != build.Windows {
return
}
case "x86":
if s.build.Arch != build.X86 {
return
}
case "arm":
if s.build.Arch != build.ARM {
return
}
default:
return
}
tmp = tmp[:underscore]
}
fullPath := filepath.Join(directory, name)
s.queueFile(fullPath, pkg)
})
if err != nil {
s.errors <- err
}
}
// queueFile queues a single file to be scanned.
func (s *scanner) queueFile(file string, pkg string) {
s.group.Add(1)
go func() {
defer s.group.Done()
err := s.scanFile(file, pkg)
if err != nil {
s.errors <- err
}
}()
}
// scanFile scans a single file.
func (s *scanner) scanFile(path string, pkg string) error {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
tokens := token.Tokenize(contents)
file := &fs.File{
Path: path,
Package: pkg,
Bytes: contents,
Tokens: tokens,
}
s.files <- file
for i := 0; i < len(tokens); i++ {
switch tokens[i].Kind {
case token.NewLine:
case token.Comment:
case token.Identifier:
i, err = s.scanFunction(file, tokens, i)
case token.Import:
i, err = s.scanImport(file, tokens, i)
case token.EOF:
return nil
case token.Invalid:
return errors.New(&invalidCharacter{Character: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
default:
return errors.New(&invalidTopLevel{Instruction: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
}
if err != nil {
return err
}
}
return nil
}