fspl/compiler/compiler.go

251 lines
6.3 KiB
Go

package compiler
import "fmt"
import "sort"
import "io/fs"
import "errors"
import "path/filepath"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/cli"
import "git.tebibyte.media/fspl/fspl/lexer"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/analyzer"
import "git.tebibyte.media/fspl/fspl/generator"
import "git.tebibyte.media/fspl/fspl/parser/fspl"
import "git.tebibyte.media/fspl/fspl/parser/meta"
import ferrors "git.tebibyte.media/fspl/fspl/errors"
type Compiler struct {
*Resolver
cli.Logger
Target *generator.Target
Output string
Optimization string
Filetype Filetype
}
func (this *Compiler) bug (err error) error {
return errors.New(fmt.Sprintln (
"Bug detected in the compiler!\n" +
"The FSPL compiler has experienced an error that should not",
"happen.\n" +
"Please submit a report with this info and the code you were",
"compiling to:",
"https://git.tebibyte.media/sashakoshka/fspl/issues\n" +
"The error is:", err))
}
func (this *Compiler) AnalyzeUnit (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering unit analysis", path)
filePath, isFile := entity.Address(path).SourceFile()
modulePath, isModule := entity.Address(path).Module()
if !isFile && !isModule {
return uuid.UUID { }, errors.New(fmt.Sprintf (
"%v is not a module, nor a source file",
path))
}
if isModule {
return this.AnalyzeModule(semanticTree, modulePath, skim)
} else {
return this.AnalyzeSourceFile(semanticTree, filePath, skim)
}
}
func (this *Compiler) AnalyzeModule (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering module analysis", path)
// parse module metadata file
var metaTree metaParser.Tree
metaPath := filepath.Join(path, "fspl.mod")
metaFile, err := openAbsolute(this.FS, metaPath)
if err != nil { return uuid.UUID { }, err }
defer metaFile.Close()
lx, err := lexer.LexReader(metaPath, metaFile)
if err != nil { return uuid.UUID { }, err }
err = metaTree.Parse(lx)
if err != nil { return uuid.UUID { }, err }
// ensure metadata is well formed
dependencies := make(map[string] *entity.Dependency)
for _, dependency := range metaTree.Dependencies {
this.Debugln(dependency)
nickname := dependency.Nickname
if nickname == "" {
newNickname, ok := dependency.Address.Nickname()
if !ok {
return uuid.UUID { }, ferrors.Errorf (
dependency.Position(),
"cannot generate nickname for %v, " +
"please add one after the address",
dependency.Address)
}
nickname = newNickname
}
if previous, exists := dependencies[nickname]; exists {
return uuid.UUID { }, ferrors.Errorf (
dependency.Position(),
"unit with nickname %v already listed at %v",
nickname, previous.Position())
}
dependencies[nickname] = dependency
}
// analyze dependency units, building a nickname translation table
nicknames := make(map[string] uuid.UUID)
dependencyKeys := sortMapKeys(dependencies)
for _, nickname := range dependencyKeys {
dependency := dependencies[nickname]
resolved, err := this.Resolve(path, dependency.Address)
if err != nil { return uuid.UUID { }, err }
dependencyUUID, err := this.AnalyzeUnit(semanticTree, resolved, true)
if err != nil { return uuid.UUID { }, err }
nicknames[nickname] = dependencyUUID
}
// parse this unit
var syntaxTree fsplParser.Tree
err = this.ParseUnit(&syntaxTree, path, skim)
if err != nil { return uuid.UUID { }, err}
// analyze this unit
this.Debugln("analyzing", path, metaTree.UUID)
err = semanticTree.Analyze(metaTree.UUID, nicknames, syntaxTree)
if err != nil { return uuid.UUID { }, err}
return metaTree.UUID, nil
}
func (this *Compiler) AnalyzeSourceFile (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering source file analysis", path)
// parse this unit
var syntaxTree fsplParser.Tree
err := this.ParseUnit(&syntaxTree, path, skim)
if err != nil { return uuid.UUID { }, err}
// analyze this unit
unitId := entity.Address(path).UUID()
this.Debugln("analyzing", path, unitId)
err = semanticTree.Analyze(unitId, nil, syntaxTree)
if err != nil { return uuid.UUID { }, err}
return unitId, nil
}
func (this *Compiler) ParseUnit (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
filePath, isFile := entity.Address(path).SourceFile()
modulePath, isModule := entity.Address(path).Module()
if !isFile && !isModule {
return errors.New(fmt.Sprintf (
"%v is not a module, nor a source file",
path))
}
if isModule {
return this.ParseModule(syntaxTree, modulePath, skim)
} else {
return this.ParseSourceFile(syntaxTree, filePath, skim)
}
}
func (this *Compiler) ParseModule (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
this.Debugln("parsing module", path)
// parse all files in the module
file, err := openAbsolute(this.FS, path)
if err != nil { return err }
defer file.Close()
dir, ok := file.(fs.ReadDirFile)
if !ok { return errors.New(fmt.Sprintf("%s is not a directory", path)) }
entries, err := dir.ReadDir(0)
if err != nil { return err }
for _, entry := range entries {
if filepath.Ext(entry.Name()) != ".fspl" { continue }
filePath := filepath.Join(path, entry.Name())
file, err := openAbsolute(this.FS, filePath)
if err != nil { return err }
defer file.Close()
lx, err := lexer.LexReader(filePath, file)
if err != nil { return err }
if skim {
err = syntaxTree.Skim(lx)
} else {
err = syntaxTree.Parse(lx)
}
if err != nil { return err }
}
return nil
}
func (this *Compiler) ParseSourceFile (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
this.Debugln("parsing source file", path)
file, err := openAbsolute(this.FS, path)
if err != nil { return err }
defer file.Close()
lx, err := lexer.LexReader(path, file)
if err != nil { return err }
if skim {
err = syntaxTree.Skim(lx)
} else {
err = syntaxTree.Parse(lx)
}
if err != nil { return err }
return nil
}
func sortMapKeys[T any] (unsorted map[string] T) []string {
keys := make([]string, len(unsorted))
index := 0
for key := range unsorted {
keys[index] = key
index ++
}
sort.Strings(keys)
return keys
}