236 lines
5.7 KiB
Go
236 lines
5.7 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/parser/fspl"
|
|
import "git.tebibyte.media/fspl/fspl/parser/meta"
|
|
import ferrors "git.tebibyte.media/fspl/fspl/errors"
|
|
|
|
type Compiler struct {
|
|
Resolver
|
|
cli.Logger
|
|
|
|
Output string
|
|
Optimization string
|
|
Format string
|
|
}
|
|
|
|
func (this *Compiler) AnalyzeUnit (
|
|
semanticTree *analyzer.Tree,
|
|
path string,
|
|
skim bool,
|
|
) (
|
|
uuid.UUID,
|
|
error,
|
|
) {
|
|
this.Debugln("analyzing unit", 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("analyzing module", 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
|
|
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("analyzing source file", 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()
|
|
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
|
|
}
|