115 lines
3.4 KiB
Go
115 lines
3.4 KiB
Go
package compiler
|
|
|
|
import "os"
|
|
import "fmt"
|
|
import "io/fs"
|
|
import "errors"
|
|
import "strings"
|
|
import "path/filepath"
|
|
import "git.tebibyte.media/fspl/fspl/entity"
|
|
|
|
// Resolver turns addresses into absolute filepaths.
|
|
type Resolver struct {
|
|
// FS specifies a filesystem to search.
|
|
FS fs.FS
|
|
|
|
// Path specifies a list of paths that a unit may exist directly in. The
|
|
// Resolver will search the FS for each path listed, starting at the
|
|
// first and ending at the last. Thus, paths nearer the start will have
|
|
// a higher priority.
|
|
Path []string
|
|
}
|
|
|
|
// NewResolver creates a new resolver with os.DirFS("/").
|
|
func NewResolver (path ...string) Resolver {
|
|
return Resolver {
|
|
FS: os.DirFS("/"),
|
|
Path: path,
|
|
}
|
|
}
|
|
|
|
// AddPath adds one or more items to the resolver's search path.
|
|
func (resolver *Resolver) AddPath (path ...string) {
|
|
resolver.Path = append(resolver.Path, path...)
|
|
}
|
|
|
|
// AddPathFront adds one or more items to the beginning of the resolver's search
|
|
// path.
|
|
func (resolver *Resolver) AddPathFront (path ...string) {
|
|
// i know how memory works in go babyyyyyy
|
|
newPath := make([]string, len(path))
|
|
copy(newPath, path)
|
|
resolver.Path = append(newPath, resolver.Path...)
|
|
}
|
|
|
|
// Resolve resolves an address into an absolute filepath starting at the
|
|
// filesystem root. It follows these rules:
|
|
// - If the address starts with '.', '..', it is joined with context
|
|
// - If the address starts with '/', it is treated as an absolute path from
|
|
// the fs root
|
|
// - Else, the address is searched for in the resolver's paths
|
|
func (resolver Resolver) Resolve (context string, address entity.Address) (string, error) {
|
|
strAddr := string(address)
|
|
switch {
|
|
case strings.HasPrefix(strAddr, "."):
|
|
return filepath.Join(context, strAddr), nil
|
|
case strings.HasPrefix(strAddr, "/"):
|
|
return strAddr, nil
|
|
default:
|
|
if resolver.Path == nil || resolver.FS == nil {
|
|
return "", errors.New(fmt.Sprintf (
|
|
"could not find unit %v: %v",
|
|
address, "no search path specified"))
|
|
}
|
|
location, err := resolver.search(strAddr)
|
|
if err != nil {
|
|
return "", errors.New(fmt.Sprintf (
|
|
"could not find unit %v: %v",
|
|
address, err))
|
|
}
|
|
return location, nil
|
|
}
|
|
}
|
|
|
|
func (resolver Resolver) search (needle string) (string, error) {
|
|
for _, dirPath := range resolver.Path {
|
|
// attempt to open the file as dir.
|
|
// if we can't open the dir, just skip it, because it is
|
|
// perfectly reasonable that the user might not have
|
|
// /usr/local/include/fspl etc.
|
|
file, err := openAbsolute(resolver.FS, dirPath)
|
|
if err != nil { continue }
|
|
dir, ok := file.(fs.ReadDirFile)
|
|
if !ok { continue }
|
|
entries, err := dir.ReadDir(0)
|
|
if err != nil { continue }
|
|
dir.Close()
|
|
|
|
// search through the entries
|
|
for _, entry := range entries {
|
|
if entry.Name() == needle {
|
|
return filepath.Join(dirPath, entry.Name()), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fs.ErrNotExist
|
|
}
|
|
|
|
// ResolveCwd resolves the address within the context of the current working
|
|
// directory.
|
|
func (resolver Resolver) ResolveCwd (address entity.Address) (string, error) {
|
|
wd, err := os.Getwd()
|
|
if err != nil { return "", err }
|
|
return resolver.Resolve(wd, address)
|
|
}
|
|
|
|
// openAbsolute exists because fs.FS implementations do not understand absolute
|
|
// paths, which the FSPL compiler runs on. It converts an absolute path to a
|
|
// path relative to "/" and opens the file.
|
|
func openAbsolute (filesystem fs.FS, path string) (fs.File, error) {
|
|
path, err := filepath.Rel("/", path)
|
|
if err != nil { return nil, err }
|
|
return filesystem.Open(path)
|
|
}
|