This repository has been archived on 2024-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
arf/analyzer/analyzer.go

243 lines
6.6 KiB
Go

package analyzer
import "os"
import "fmt"
import "path/filepath"
import "git.tebibyte.media/arf/arf/parser"
import "git.tebibyte.media/arf/arf/infoerr"
// AnalysisOperation holds information about an ongoing analysis operation.
type AnalysisOperation struct {
sectionTable SectionTable
modulePath string
currentPosition locator
currentSection parser.Section
currentTree parser.SyntaxTree
}
// Analyze performs a semantic analyisys on the module specified by path, and
// returns a SectionTable that can be translated into C.
func Analyze (modulePath string, skim bool) (table SectionTable, err error) {
if modulePath[0] != '/' {
cwd, _ := os.Getwd()
modulePath = filepath.Join(cwd, modulePath)
}
analyzer := AnalysisOperation {
sectionTable: make(SectionTable),
modulePath: modulePath,
}
err = analyzer.analyze()
table = analyzer.sectionTable
return
}
// analyze performs an analysis operation given the state of the operation
// struct.
func (analyzer *AnalysisOperation) analyze () (err error) {
tree, err := parser.Fetch(analyzer.modulePath, false)
sections := tree.Sections()
for !sections.End() {
_, err = analyzer.fetchSection(locator {
modulePath: analyzer.modulePath,
name: sections.Value().Name(),
})
if err != nil { return err }
sections.Next()
}
return
}
// fetchSection returns a section from the section table. If it has not already
// been analyzed, it analyzes it first. If the section does not actually exist,
// a nil section is returned. When this happens, an error should be created on
// whatever syntax tree node "requested" the section be analyzed.
func (analyzer *AnalysisOperation) fetchSection (
where locator,
) (
section Section,
err error,
) {
var exists bool
section, exists = analyzer.resolvePrimitive(where)
if exists { return }
section, exists = analyzer.sectionTable[where]
if exists { return }
// fetch the module. since we already have our main module parsed fully
// and not skimmed, we can just say "yeah lets skim stuff here".
var tree parser.SyntaxTree
tree, err = parser.Fetch(where.modulePath, true)
if err != nil {
section = nil
return
}
var parsedSection = tree.LookupSection(where.name)
if parsedSection == nil {
section = nil
return
}
previousPosition := analyzer.currentPosition
previousSection := analyzer.currentSection
previousTree := analyzer.currentTree
analyzer.currentPosition = where
analyzer.currentSection = parsedSection
analyzer.currentTree = tree
defer func () {
analyzer.currentPosition = previousPosition
analyzer.currentSection = previousSection
analyzer.currentTree = previousTree
} ()
// TODO: analyze section. have analysis methods work on currentPosition
// and currentSection.
//
// while building an analyzed section, add it to the section
// table as soon as the vital details are acquired, and mark it as
// incomplete. that way, it can still be referenced by itself in certain
// scenarios.
switch parsedSection.(type) {
case parser.TypeSection:
section, err = analyzer.analyzeTypeSection()
if err != nil { return}
case parser.EnumSection:
case parser.FaceSection:
case parser.DataSection:
case parser.FuncSection:
}
return
}
// fetchSectionFromIdentifier is like fetchSection, but takes in an identifier
// referring to a section and returns the section. This works within the context
// of whatever module is currently being analyzed. The identifier in question
// may have more items than 1 or 2, but those will be ignored. This method
// "consumes" items from the identifier, it will return an identifier without
// those items.
func (analyzer *AnalysisOperation) fetchSectionFromIdentifier (
which parser.Identifier,
) (
section Section,
bitten parser.Identifier,
err error,
) {
item, bitten := which.Bite()
path, exists := analyzer.currentTree.ResolveRequire(item)
if exists {
// we have our module path, so get the section name
item, bitten = bitten.Bite()
} else {
// that wasn't a module name, so the module path must be the our
// current one
path = analyzer.currentPosition.modulePath
}
section, err = analyzer.fetchSection (locator {
name: item,
modulePath: path,
})
if err != nil { return }
if section == nil {
err = which.NewError (
"section \"" + item + "\" does not exist",
infoerr.ErrorKindError,
)
return
}
return
}
// resolvePrimitive checks to see if the locator is in the current module, and
// refers to a primitive. If it does, it returns a pointer to that primitive
// and true for exists. If it doesn't, it returns nil and false.
func (analyzer *AnalysisOperation) resolvePrimitive (
where locator,
) (
section Section,
exists bool,
) {
// primitives are scoped as if they are contained within the current
// module, so if the location refers to something outside of the current
// module, it is definetly not referring to a primitive.
if where.modulePath != analyzer.currentPosition.modulePath {
return
}
exists = true
switch where.name {
case "Int": section = &PrimitiveInt
case "UInt": section = &PrimitiveUInt
case "I8": section = &PrimitiveI8
case "I16": section = &PrimitiveI16
case "I32": section = &PrimitiveI32
case "I64": section = &PrimitiveI64
case "U8": section = &PrimitiveU8
case "U16": section = &PrimitiveU16
case "U32": section = &PrimitiveU32
case "U64": section = &PrimitiveU64
case "Obj": section = &PrimitiveObj
case "Face": section = &PrimitiveFace
case "Func": section = &PrimitiveFunc
case "String": section = &BuiltInString
default:
exists = false
}
return
}
// addSection adds a section to the analyzer's section table. If a section with
// that name already exists, it panics because the parser should not have given
// that to us.
func (analyzer *AnalysisOperation) addSection (section Section) {
_, exists := analyzer.sectionTable[section.locator()]
if exists {
panic (
"invalid state: duplicate section " +
section.locator().ToString())
}
analyzer.sectionTable[section.locator()] = section
return
}
// typeCheck checks to see if source can fit as an argument into a slot of type
// destination. If it can, it retunrs nil. If it can't, it returns an error
// explaining why.
func (analyzer *AnalysisOperation) typeCheck (
source Argument,
destination Type,
) (
err error,
) {
if !source.canBePassedAs(destination) {
err = source.NewError (
typeMismatchErrorMessage (
source.What(),
destination),
infoerr.ErrorKindError)
}
return
}
func doIndent (indent int, input ...any) (output string) {
for index := 0; index < indent; index ++ {
output += "\t"
}
output += fmt.Sprint(input...)
return
}