Improved scanner
All checks were successful
/ test (push) Successful in 13s

This commit is contained in:
Eduard Urbach 2025-06-19 21:39:04 +02:00
parent adfcd4b60c
commit 9b51680af5
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
8 changed files with 202 additions and 0 deletions

5
lib/io/write.q Normal file
View file

@ -0,0 +1,5 @@
import os
write(buffer []byte) -> int {
return os.write(0, buffer, len(buffer))
}

3
lib/os/os_linux.q Normal file
View file

@ -0,0 +1,3 @@
write(fd int, buffer *byte, length int) -> int {
return syscall(1, fd, buffer, length)
}

11
src/errors/String.go Normal file
View file

@ -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
}

69
src/global/global.go Normal file
View file

@ -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)
}
}

41
src/scanner/errors.go Normal file
View file

@ -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)
}

View file

@ -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
}

35
src/scanner/scanImport.go Normal file
View file

@ -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
}

View file

@ -7,6 +7,7 @@ import (
"sync" "sync"
"git.urbach.dev/cli/q/src/build" "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/fs"
"git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/token"
) )
@ -141,5 +142,27 @@ func (s *scanner) scanFile(path string, pkg string) error {
} }
s.files <- file 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 return nil
} }