2022-10-11 22:48:55 -06:00
|
|
|
/*
|
|
|
|
Package analyzer implements a semantic analyzer for the ARF language. In it,
|
|
|
|
there is a function called Analyze which takes in a module path and returns a
|
|
|
|
table of sections representative of that module. The result of this operation is
|
|
|
|
not cached.
|
|
|
|
|
|
|
|
The section table returned by Analyze can be expected to be both syntactically
|
|
|
|
correct and semantically sound.
|
|
|
|
|
|
|
|
This package automatically invokes the parser and lexer packages.
|
|
|
|
*/
|
2022-09-07 10:20:34 -06:00
|
|
|
package analyzer
|
|
|
|
|
2022-09-07 13:50:37 -06:00
|
|
|
import "os"
|
2022-09-10 17:50:18 -06:00
|
|
|
import "fmt"
|
2022-09-07 13:50:37 -06:00
|
|
|
import "path/filepath"
|
|
|
|
import "git.tebibyte.media/arf/arf/parser"
|
2022-09-19 10:17:12 -06:00
|
|
|
import "git.tebibyte.media/arf/arf/infoerr"
|
2022-09-07 13:50:37 -06:00
|
|
|
|
2022-10-11 21:53:38 -06:00
|
|
|
// analysisOperation holds information about an ongoing analysis operation.
|
|
|
|
type analysisOperation struct {
|
2022-09-07 10:20:34 -06:00
|
|
|
sectionTable SectionTable
|
2022-09-07 13:50:37 -06:00
|
|
|
modulePath string
|
2022-09-08 23:09:17 -06:00
|
|
|
|
2022-09-09 18:55:03 -06:00
|
|
|
currentPosition locator
|
|
|
|
currentSection parser.Section
|
2022-09-19 10:17:12 -06:00
|
|
|
currentTree parser.SyntaxTree
|
2022-09-07 13:50:37 -06:00
|
|
|
}
|
|
|
|
|
2022-10-11 22:48:55 -06:00
|
|
|
// Analyze performs a semantic analysis on the module specified by path, and
|
|
|
|
// returns a SectionTable that can be translated into C. The result of this is
|
|
|
|
// not cached.
|
2022-09-07 13:50:37 -06:00
|
|
|
func Analyze (modulePath string, skim bool) (table SectionTable, err error) {
|
|
|
|
if modulePath[0] != '/' {
|
|
|
|
cwd, _ := os.Getwd()
|
|
|
|
modulePath = filepath.Join(cwd, modulePath)
|
|
|
|
}
|
|
|
|
|
2022-10-11 21:53:38 -06:00
|
|
|
analyzer := analysisOperation {
|
2022-09-07 13:50:37 -06:00
|
|
|
sectionTable: make(SectionTable),
|
|
|
|
modulePath: modulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = analyzer.analyze()
|
|
|
|
table = analyzer.sectionTable
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// analyze performs an analysis operation given the state of the operation
|
|
|
|
// struct.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) analyze () (err error) {
|
2022-10-12 11:14:53 -06:00
|
|
|
var tree parser.SyntaxTree
|
|
|
|
tree, err = parser.Fetch(analyzer.modulePath, false)
|
|
|
|
if err != nil { return }
|
2022-09-07 13:50:37 -06:00
|
|
|
sections := tree.Sections()
|
|
|
|
|
|
|
|
for !sections.End() {
|
2022-09-08 23:09:17 -06:00
|
|
|
_, err = analyzer.fetchSection(locator {
|
|
|
|
modulePath: analyzer.modulePath,
|
|
|
|
name: sections.Value().Name(),
|
|
|
|
})
|
2022-10-11 15:13:37 -06:00
|
|
|
if err != nil { return err }
|
2022-09-07 13:50:37 -06:00
|
|
|
sections.Next()
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2022-09-07 10:20:34 -06:00
|
|
|
}
|
2022-09-08 23:09:17 -06:00
|
|
|
|
|
|
|
// 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.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) fetchSection (
|
2022-09-08 23:09:17 -06:00
|
|
|
where locator,
|
|
|
|
) (
|
|
|
|
section Section,
|
|
|
|
err error,
|
|
|
|
) {
|
|
|
|
var exists bool
|
2022-09-29 22:04:28 -06:00
|
|
|
section, exists = analyzer.resolvePrimitive(where)
|
|
|
|
if exists { return }
|
|
|
|
|
2022-09-08 23:09:17 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:55:03 -06:00
|
|
|
previousPosition := analyzer.currentPosition
|
|
|
|
previousSection := analyzer.currentSection
|
2022-09-19 10:17:12 -06:00
|
|
|
previousTree := analyzer.currentTree
|
2022-09-09 18:55:03 -06:00
|
|
|
analyzer.currentPosition = where
|
|
|
|
analyzer.currentSection = parsedSection
|
2022-09-19 10:17:12 -06:00
|
|
|
analyzer.currentTree = tree
|
2022-09-08 23:53:53 -06:00
|
|
|
|
2022-09-18 00:41:06 -06:00
|
|
|
defer func () {
|
|
|
|
analyzer.currentPosition = previousPosition
|
|
|
|
analyzer.currentSection = previousSection
|
2022-09-19 10:17:12 -06:00
|
|
|
analyzer.currentTree = previousTree
|
2022-09-18 00:41:06 -06:00
|
|
|
} ()
|
|
|
|
|
2022-10-13 16:02:35 -06:00
|
|
|
// analyze section. have analysis methods work on currentPosition
|
2022-09-09 18:55:03 -06:00
|
|
|
// 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.
|
2022-09-21 19:25:48 -06:00
|
|
|
switch parsedSection.(type) {
|
|
|
|
case parser.TypeSection:
|
2022-09-18 00:41:06 -06:00
|
|
|
section, err = analyzer.analyzeTypeSection()
|
|
|
|
if err != nil { return}
|
2022-09-21 19:25:48 -06:00
|
|
|
case parser.EnumSection:
|
2022-10-13 16:02:35 -06:00
|
|
|
section, err = analyzer.analyzeEnumSection()
|
|
|
|
if err != nil { return}
|
2022-09-21 19:25:48 -06:00
|
|
|
case parser.FaceSection:
|
|
|
|
case parser.DataSection:
|
|
|
|
case parser.FuncSection:
|
2022-09-08 23:09:17 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2022-09-09 18:55:03 -06:00
|
|
|
}
|
2022-09-10 17:50:18 -06:00
|
|
|
|
2022-09-19 10:17:12 -06:00
|
|
|
// 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
|
2022-10-12 13:48:22 -06:00
|
|
|
// those items. If the section comes from a different module (and permissions
|
|
|
|
// should therefore be respected), external will be set to true.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) fetchSectionFromIdentifier (
|
2022-09-19 10:17:12 -06:00
|
|
|
which parser.Identifier,
|
|
|
|
) (
|
2022-10-12 13:48:22 -06:00
|
|
|
section Section,
|
|
|
|
external bool,
|
|
|
|
bitten parser.Identifier,
|
|
|
|
err error,
|
2022-09-19 10:17:12 -06:00
|
|
|
) {
|
2022-09-29 20:54:32 -06:00
|
|
|
item, bitten := which.Bite()
|
2022-10-12 13:48:22 -06:00
|
|
|
|
2022-09-19 10:17:12 -06:00
|
|
|
path, exists := analyzer.currentTree.ResolveRequire(item)
|
|
|
|
if exists {
|
|
|
|
// we have our module path, so get the section name
|
2022-09-29 20:54:32 -06:00
|
|
|
item, bitten = bitten.Bite()
|
2022-10-12 13:48:22 -06:00
|
|
|
external = true
|
2022-09-19 10:17:12 -06:00
|
|
|
} else {
|
2022-10-12 13:48:22 -06:00
|
|
|
// that wasn't a module name, so the module path must be our
|
2022-09-19 10:17:12 -06:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-09-29 22:04:28 -06:00
|
|
|
// 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.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) resolvePrimitive (
|
2022-09-29 22:04:28 -06:00
|
|
|
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
|
2022-10-01 15:21:17 -06:00
|
|
|
case "Obj": section = &PrimitiveObj
|
2022-10-13 13:08:47 -06:00
|
|
|
// case "Face": section = &PrimitiveFace
|
|
|
|
// case "Func": section = &PrimitiveFunc
|
2022-09-29 22:04:28 -06:00
|
|
|
case "String": section = &BuiltInString
|
|
|
|
default:
|
|
|
|
exists = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-29 16:09:52 -06:00
|
|
|
// 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.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) addSection (section Section) {
|
2022-09-29 16:09:52 -06:00
|
|
|
_, exists := analyzer.sectionTable[section.locator()]
|
|
|
|
if exists {
|
|
|
|
panic (
|
|
|
|
"invalid state: duplicate section " +
|
|
|
|
section.locator().ToString())
|
|
|
|
}
|
|
|
|
analyzer.sectionTable[section.locator()] = section
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-11 16:03:44 -06:00
|
|
|
// 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.
|
2022-10-11 21:53:38 -06:00
|
|
|
func (analyzer *analysisOperation) typeCheck (
|
2022-10-11 16:03:44 -06:00
|
|
|
source Argument,
|
|
|
|
destination Type,
|
|
|
|
) (
|
|
|
|
err error,
|
|
|
|
) {
|
|
|
|
if !source.canBePassedAs(destination) {
|
2022-10-11 16:31:37 -06:00
|
|
|
err = source.NewError (
|
2022-10-11 16:03:44 -06:00
|
|
|
typeMismatchErrorMessage (
|
|
|
|
source.What(),
|
|
|
|
destination),
|
|
|
|
infoerr.ErrorKindError)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-13 00:20:47 -06:00
|
|
|
// inCurrentModule returns whether or not the specified section resides within
|
|
|
|
// the current module.
|
|
|
|
func (analyzer *analysisOperation) inCurrentModule (
|
|
|
|
section Section,
|
|
|
|
) (
|
|
|
|
inCurrent bool,
|
|
|
|
){
|
|
|
|
inCurrent =
|
|
|
|
section.locator().modulePath ==
|
|
|
|
analyzer.currentPosition.modulePath
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-13 16:48:38 -06:00
|
|
|
// TODO: make a method of analyzer that, given a name, searches through all
|
|
|
|
// accessible scopes and returns the thing the name references. when analyzing
|
|
|
|
// a function, the analyzer should remember a trail of scopes.
|
|
|
|
|
2022-10-11 21:51:55 -06:00
|
|
|
// doIndent perfroms a fmt.Sprint operation on input, indenting the string. This
|
|
|
|
// does not add a trailing newline.
|
2022-09-10 17:50:18 -06:00
|
|
|
func doIndent (indent int, input ...any) (output string) {
|
|
|
|
for index := 0; index < indent; index ++ {
|
|
|
|
output += "\t"
|
|
|
|
}
|
|
|
|
|
|
|
|
output += fmt.Sprint(input...)
|
|
|
|
return
|
|
|
|
}
|