From 9b51680af5def40aacff3166f5dd4201bfe68777 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 19 Jun 2025 21:39:04 +0200 Subject: [PATCH] Improved scanner --- lib/io/write.q | 5 +++ lib/os/os_linux.q | 3 ++ src/errors/String.go | 11 ++++++ src/global/global.go | 69 +++++++++++++++++++++++++++++++++++++ src/scanner/errors.go | 41 ++++++++++++++++++++++ src/scanner/scanFunction.go | 15 ++++++++ src/scanner/scanImport.go | 35 +++++++++++++++++++ src/scanner/scanner.go | 23 +++++++++++++ 8 files changed, 202 insertions(+) create mode 100644 lib/io/write.q create mode 100644 lib/os/os_linux.q create mode 100644 src/errors/String.go create mode 100644 src/global/global.go create mode 100644 src/scanner/errors.go create mode 100644 src/scanner/scanFunction.go create mode 100644 src/scanner/scanImport.go diff --git a/lib/io/write.q b/lib/io/write.q new file mode 100644 index 0000000..6ee5598 --- /dev/null +++ b/lib/io/write.q @@ -0,0 +1,5 @@ +import os + +write(buffer []byte) -> int { + return os.write(0, buffer, len(buffer)) +} \ No newline at end of file diff --git a/lib/os/os_linux.q b/lib/os/os_linux.q new file mode 100644 index 0000000..b3f7ebb --- /dev/null +++ b/lib/os/os_linux.q @@ -0,0 +1,3 @@ +write(fd int, buffer *byte, length int) -> int { + return syscall(1, fd, buffer, length) +} \ No newline at end of file diff --git a/src/errors/String.go b/src/errors/String.go new file mode 100644 index 0000000..c283b37 --- /dev/null +++ b/src/errors/String.go @@ -0,0 +1,11 @@ +package errors + +// String is used for static errors that have no parameters. +type String struct { + Message string +} + +// Error implements the error interface. +func (err *String) Error() string { + return err.Message +} \ No newline at end of file diff --git a/src/global/global.go b/src/global/global.go new file mode 100644 index 0000000..bbdcdef --- /dev/null +++ b/src/global/global.go @@ -0,0 +1,69 @@ +package global + +import ( + "os" + "path/filepath" + "runtime/debug" +) + +// Global variables that are useful in all packages. +var ( + Executable string + Library string + Root string + WorkingDirectory string +) + +// init is the very first thing that's executed. +// It disables the GC and initializes global variables. +func init() { + debug.SetGCPercent(-1) + + var err error + Executable, err = os.Executable() + + if err != nil { + panic(err) + } + + Executable, err = filepath.EvalSymlinks(Executable) + + if err != nil { + panic(err) + } + + WorkingDirectory, err = os.Getwd() + + if err != nil { + panic(err) + } + + Root = filepath.Dir(Executable) + Library = filepath.Join(Root, "lib") + stat, err := os.Stat(Library) + + if err != nil || !stat.IsDir() { + findLibrary() + } +} + +// findLibrary tries to go up each directory from the working directory and check for the existence of a "lib" directory. +// This is needed for tests to work correctly. +func findLibrary() { + dir := WorkingDirectory + + for { + Library = filepath.Join(dir, "lib") + stat, err := os.Stat(Library) + + if err == nil && stat.IsDir() { + return + } + + if dir == "/" { + panic("standard library not found") + } + + dir = filepath.Dir(dir) + } +} \ No newline at end of file diff --git a/src/scanner/errors.go b/src/scanner/errors.go new file mode 100644 index 0000000..4d46d6d --- /dev/null +++ b/src/scanner/errors.go @@ -0,0 +1,41 @@ +package scanner + +import ( + "fmt" + + "git.urbach.dev/cli/q/src/errors" +) + +var ( + expectedPackageName = &errors.String{Message: "Expected package name"} +) + +// invalidCharacter is created when an invalid character appears. +type invalidCharacter struct { + Character string +} + +// Error implements the error interface. +func (err *invalidCharacter) Error() string { + return fmt.Sprintf("Invalid character '%s'", err.Character) +} + +// invalidTopLevel error is created when a top-level instruction is not valid. +type invalidTopLevel struct { + Instruction string +} + +// Error generates the string representation. +func (err *invalidTopLevel) Error() string { + return fmt.Sprintf("Invalid top level instruction '%s'", err.Instruction) +} + +// isNotDirectory error is created when a path is not a directory. +type isNotDirectory struct { + Path string +} + +// Error generates the string representation. +func (err *isNotDirectory) Error() string { + return fmt.Sprintf("Import path '%s' is not a directory", err.Path) +} \ No newline at end of file diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go new file mode 100644 index 0000000..c971fdb --- /dev/null +++ b/src/scanner/scanFunction.go @@ -0,0 +1,15 @@ +package scanner + +import ( + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" +) + +// scanFunction scans a function. +func (s *scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) { + for i < len(tokens) && tokens[i].Kind != token.BlockEnd { + i++ + } + + return i, nil +} \ No newline at end of file diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go new file mode 100644 index 0000000..9cfbaec --- /dev/null +++ b/src/scanner/scanImport.go @@ -0,0 +1,35 @@ +package scanner + +import ( + "os" + "path/filepath" + + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/global" + "git.urbach.dev/cli/q/src/token" +) + +// scanImport scans an import. +func (s *scanner) scanImport(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(expectedPackageName, file, tokens[i].Position) + } + + packageName := tokens[i].String(file.Bytes) + fullPath := filepath.Join(global.Library, packageName) + stat, err := os.Stat(fullPath) + + if err != nil { + return i, errors.New(err, file, tokens[i].Position) + } + + if !stat.IsDir() { + return i, errors.New(&isNotDirectory{Path: fullPath}, file, tokens[i].Position) + } + + s.queueDirectory(fullPath, packageName) + return i, nil +} \ No newline at end of file diff --git a/src/scanner/scanner.go b/src/scanner/scanner.go index 897e91c..37b657b 100644 --- a/src/scanner/scanner.go +++ b/src/scanner/scanner.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -141,5 +142,27 @@ func (s *scanner) scanFile(path string, pkg string) error { } 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 } \ No newline at end of file