package typechecker
import (
"runtime"
"slices"
"github.com/marzeq/qk/parser"
"github.com/marzeq/qk/shared"
)
var _ = runtime.GOOS
type ModuleSignatures struct {
TypeTable shared.TypeTable
Functions FuncTable
}
type ModulesSignatures map[string]*ModuleSignatures
func (ms ModulesSignatures) LookupType(
tn *parser.TypeNode,
lookingFrom string,
canImportMods []string,
ignorePubCheckA ...bool,
) (shared.Type, bool) {
ignorePubCheck := len(ignorePubCheckA) > 0 && ignorePubCheckA[0]
base := tn
for {
if base.PointerTo != nil {
base = base.PointerTo
continue
}
if base.ArrayTo != nil {
base = base.ArrayTo
continue
}
break
}
if base.Name == "" {
return nil, false
}
var (
t shared.Type
ok bool
)
module := base.ModName
typeName := base.Name
if module == "" && lookingFrom == "" {
if m, exists := ms[""]; exists {
t, ok = m.TypeTable.Lookup(typeName)
ignorePubCheck = true
}
} else if module == "" {
if m, exists := ms[lookingFrom]; exists {
t, ok = m.TypeTable.Lookup(typeName)
ignorePubCheck = true
}
if !ok {
if m, exists := ms[""]; exists {
t, ok = m.TypeTable.Lookup(typeName)
}
}
} else {
if canImportMods != nil && !slices.Contains(canImportMods, module) {
return nil, false
}
if m, exists := ms[module]; exists {
t, ok = m.TypeTable.Lookup(typeName)
}
}
if !ok {
t, ok = shared.GetPrimitive(typeName)
if !ok {
return nil, false
}
}
if s, isStruct := t.(*shared.Struct); isStruct {
if !s.Pub && !ignorePubCheck {
return nil, false
}
}
var build func(n *parser.TypeNode) shared.Type
build = func(n *parser.TypeNode) shared.Type {
if n == nil {
return t
}
inner := build(n.PointerTo)
inner = build(n.ArrayTo)
if n.ArrayTo != nil {
return shared.Array{
Of: inner,
Len: n.ArrayLen,
}
}
if n.PointerTo != nil {
return shared.Pointer{
To: inner,
}
}
return inner
}
t = build(tn)
return t, true
}
func (ms ModulesSignatures) LookupFlatType(
module string,
typeName string,
lookingFrom string,
canImportMods []string,
ignorePubCheckA ...bool,
) (shared.Type, bool) {
ignorePubCheck := len(ignorePubCheckA) > 0 && ignorePubCheckA[0]
var (
t shared.Type
ok bool
)
if module == "" && lookingFrom == "" {
if m, exists := ms[""]; exists {
t, ok = m.TypeTable.Lookup(typeName)
ignorePubCheck = true
}
} else if module == "" {
if m, exists := ms[lookingFrom]; exists {
t, ok = m.TypeTable.Lookup(typeName)
ignorePubCheck = true
}
if !ok {
if m, exists := ms[""]; exists {
t, ok = m.TypeTable.Lookup(typeName)
}
}
} else {
if canImportMods != nil && !slices.Contains(canImportMods, module) {
return nil, false
}
if m, exists := ms[module]; exists {
t, ok = m.TypeTable.Lookup(typeName)
}
}
if !ok {
t, ok = shared.GetPrimitive(typeName)
if !ok {
return nil, false
}
}
if s, isStruct := t.(*shared.Struct); isStruct {
if !s.Pub && !ignorePubCheck {
return nil, false
}
}
return t, true
}
func (ms ModulesSignatures) LookupFunction(module string, funcName string, lookingFrom string,
canImportMods []string, ignorePubCheckA ...bool,
) (*FunctionSig, bool) {
ignorePubCheck := len(ignorePubCheckA) > 0 && ignorePubCheckA[0]
var f *FunctionSig
var ok bool
if module == "" && lookingFrom == "" {
if m, exists := ms[""]; exists {
f, ok = m.Functions.Lookup(funcName)
ignorePubCheck = true
}
} else if module == "" {
if m, exists := ms[lookingFrom]; exists {
f, ok = m.Functions.Lookup(funcName)
ignorePubCheck = true
}
if !ok {
if m, exists := ms[""]; exists {
f, ok = m.Functions.Lookup(funcName)
}
}
} else if m, exists := ms[module]; exists {
f, ok = m.Functions.Lookup(funcName)
}
if !ok || f == nil || (!f.Pub && !ignorePubCheck) {
return nil, false
}
return f, true
}
func (ms ModulesSignatures) DefineType(module string, typeName string, typ shared.Type) bool {
m, ok := ms[module]
if !ok {
return false
}
return m.TypeTable.Define(typeName, typ)
}
func (ms ModulesSignatures) DefineFunction(module string, funcSig *FunctionSig) bool {
m, ok := ms[module]
if !ok {
return false
}
return m.Functions.Define(funcSig.Name, funcSig)
}
func (ms ModulesSignatures) CollectSignaturesFromRootNode(ast *parser.RootNode, canImportMods []string) (string, error) {
moduleForAst := ""
for i, n := range ast.Body {
switch n.(type) {
case *parser.ModuleNode:
default:
if _, exists := ms[moduleForAst]; !exists && i == 0 {
ms[""] = &ModuleSignatures{
TypeTable: make(shared.TypeTable),
Functions: make(FuncTable),
}
}
}
switch node := n.(type) {
case *parser.ModuleNode:
if moduleForAst != "" {
return "", shared.NewError(node.Loc, "multiple module declarations found")
}
if i != 0 {
return "", shared.NewError(node.Loc, "module declaration must be the first statement in the file")
}
moduleForAst = node.Name
if _, exists := ms[moduleForAst]; !exists {
ms[moduleForAst] = &ModuleSignatures{
TypeTable: make(shared.TypeTable),
Functions: make(FuncTable),
}
}
case *parser.FunctionDefNode:
sig, err := ms.ExtractFunctionSig(node, moduleForAst, canImportMods)
if err != nil {
return "", err
}
if !ms.DefineFunction(moduleForAst, sig) {
return "", shared.NewError(node.Loc, "function '%s' is already defined", sig.Name)
}
case *parser.StructDefNode:
if _, ok := ms.LookupFlatType(moduleForAst, node.Name, moduleForAst, canImportMods); ok {
return "", shared.NewError(node.Loc, "struct '%s' is already defined", node.Name)
}
fields := make([]shared.Pair[string, shared.Type], len(node.Fields))
for i, field := range node.Fields {
resolved, ok := ms.LookupType(field.Type, moduleForAst, canImportMods)
if !ok {
return "", shared.NewError(field.Type.Loc,
"field '%s' has undefined type '%s'", field.Name, field.Type)
}
fields[i] = shared.Pair[string, shared.Type]{L: field.Name, R: resolved}
}
if !ms.DefineType(moduleForAst, node.Name, shared.Struct{
Fields: fields,
}) {
return "", shared.NewError(node.Loc, "struct '%s' is already defined", node.Name)
}
}
}
return moduleForAst, nil
}
func (ms ModulesSignatures) ExtractFunctionSig(functionNode *parser.FunctionDefNode, currMod string, canImportMods []string) (*FunctionSig, error) {
var retType shared.Type
rtNode := functionNode.RetType.Type
if rtNode != nil {
r, ok := ms.LookupType(rtNode, currMod, canImportMods)
if !ok {
return nil, shared.NewError(functionNode.RetType.Type.Loc,
"function '%s' has undefined return type '%s'", functionNode.Name, rtNode)
}
retType = r
} else {
retType = shared.PRIMITIVE_VOID
}
argTypes := make([]FunctionSigArg, len(functionNode.Args))
for i, arg := range functionNode.Args {
resolved, ok := ms.LookupType(arg.Type, currMod, canImportMods)
if !ok {
return nil, shared.NewError(arg.Type.Loc,
"parameter '%s' has undefined type '%s'", arg.Name, arg.Type)
}
argTypes[i] = FunctionSigArg{
Name: arg.Name,
Type: resolved,
Mutable: arg.Mutable,
}
}
return &FunctionSig{
ArgTypes: argTypes,
RetType: retType,
Name: functionNode.Name,
HasVariadic: functionNode.HasVariadic,
ExternFrom: functionNode.ExternFrom,
Pub: functionNode.Pub,
}, nil
}