package sema
import (
"fmt"
"github.com/marzeq/qk/parser"
"github.com/marzeq/qk/symbols"
"github.com/marzeq/qk/types"
)
type Analyser struct {
universe *symbols.Scope
current *symbols.Scope
modules map[string]*symbols.Module
errors []error
}
func NewAnalyser() *Analyser {
u := symbols.NewScope(nil)
a := &Analyser{
universe: u,
modules: make(map[string]*symbols.Module),
}
a.predefineBuiltins()
return a
}
func (a *Analyser) Errors() []error {
return a.errors
}
func (a *Analyser) errorf(node parser.Node, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
a.errors = append(a.errors, fmt.Errorf("%s: %s", node.GetLoc(), msg))
}
func (a *Analyser) predefineBuiltins() {
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_I8), types.PRIMITIVE_I8))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_I16), types.PRIMITIVE_I16))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_I32), types.PRIMITIVE_I32))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_I64), types.PRIMITIVE_I64))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_U8), types.PRIMITIVE_U8))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_U16), types.PRIMITIVE_U16))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_U32), types.PRIMITIVE_U32))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_U64), types.PRIMITIVE_U64))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_F32), types.PRIMITIVE_F32))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_F64), types.PRIMITIVE_F64))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_ISZ), types.PRIMITIVE_ISZ))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_USZ), types.PRIMITIVE_USZ))
a.universe.Define(symbols.NewType(string(types.PRIMITIVE_VOID), types.PRIMITIVE_VOID))
}
func (a *Analyser) AnalyseModule(root *parser.RootNode, name string) {
modScope := symbols.NewScope(a.universe)
mod := &symbols.Module{
Name: name,
Scope: modScope,
}
a.modules[name] = mod
a.current = modScope
a.collectTopLevel(root)
a.resolveBodies(root)
}
func (a *Analyser) collectTopLevel(root *parser.RootNode) {
for _, node := range root.Body {
switch n := node.(type) {
case *parser.FunctionDefNode:
a.collectFunctionSignature(n)
case *parser.TypeAliasNode:
a.collectTypeAlias(n)
case *parser.DeclarationNode:
a.collectGlobalVariable(n)
case *parser.ImportNode:
a.collectImport(n)
}
}
}
func (a *Analyser) collectFunctionSignature(n *parser.FunctionDefNode) {
paramTypes := make([]types.Type, len(n.Args))
for i, arg := range n.Args {
paramTypes[i] = a.resolveTypeNode(arg.Type)
}
retType := a.resolveTypeNode(n.RetType.Type)
sig := &symbols.FunctionSignature{
Parameters: paramTypes,
ReturnType: retType,
Variadic: n.HasVariadic,
}
sym := &symbols.Symbol{
Name: n.Name,
Kind: symbols.SymbolKindFunction,
Signature: sig,
}
if err := a.current.Define(sym); err != nil {
a.errors = append(a.errors, err)
}
n.Symbol = sym
}
func (a *Analyser) collectImport(n *parser.ImportNode) {
for _, name := range n.Modules {
mod, ok := a.modules[name]
if !ok {
a.errorf(n, "unknown module %q", name)
continue
}
sym := &symbols.Symbol{
Name: name,
Kind: symbols.SymbolKindModule,
Module: mod,
}
if err := a.current.Define(sym); err != nil {
a.errors = append(a.errors, err)
}
}
}
func (a *Analyser) resolveBodies(root *parser.RootNode) {
for _, node := range root.Body {
a.visit(node)
}
}
func (a *Analyser) resolveTypeNode(n parser.TypeNode) types.Type {
switch t := n.(type) {
case *parser.NamedTypeNode:
if t.ModName == "" {
sym, ok := a.current.Resolve(t.Name)
if !ok || sym.Kind != symbols.SymbolKindType {
a.errorf(t, "unknown type %q", t.Name)
return types.ErrorType
}
return sym.TypeInfo
}
modSym, ok := a.current.Resolve(t.ModName)
if !ok || modSym.Kind != symbols.SymbolKindModule {
a.errorf(t, "unknown module %q", t.ModName)
return types.ErrorType
}
sym, ok := modSym.Module.Scope.Resolve(t.Name)
if !ok || sym.Kind != symbols.SymbolKindType {
a.errorf(t, "unknown type %q in module %q", t.Name, t.ModName)
return types.ErrorType
}
return sym.TypeInfo
case *parser.PointerTypeNode:
return &types.PointerType{
Base: a.resolveTypeNode(t.BaseType),
}
case *parser.ArrayTypeNode:
return &types.ArrayType{
Base: a.resolveTypeNode(t.ElementType),
Size: t.Size,
}
}
a.errorf(n, "unsupported type node")
return types.ErrorType
}
func (a *Analyser) collectTypeAlias(n *parser.TypeAliasNode) {
aliased := a.resolveTypeNode(n.Type)
sym := &symbols.Symbol{
Name: n.Name,
Kind: symbols.SymbolKindType,
TypeInfo: aliased,
}
if err := a.current.Define(sym); err != nil {
a.errors = append(a.errors, err)
}
n.Symbol = sym
}
func (a *Analyser) collectGlobalVariable(n *parser.DeclarationNode) {
var varType types.Type
if n.Type != nil {
varType = a.resolveTypeNode(n.Type)
}
sym := &symbols.Symbol{
Name: n.Name,
Kind: symbols.SymbolKindVariable,
Type: varType,
}
if err := a.current.Define(sym); err != nil {
a.errors = append(a.errors, err)
}
n.Symbol = sym
}
func (a *Analyser) visit(node parser.Node) {
switch n := node.(type) {
case *parser.FunctionDefNode:
a.visitFunction(n)
case *parser.BlockNode:
a.visitBlock(n)
case *parser.DeclarationNode:
a.visitLocalDeclaration(n)
case *parser.AssignmentNode:
a.visitAssignment(n)
case *parser.ArrayAssignmentNode:
a.visitArrayAssignment(n)
case *parser.IfNode:
a.visitIf(n)
case *parser.ForNode:
a.visitFor(n)
case *parser.ControlKeywordNode:
a.visitControlKeyword(n)
case parser.ExpressionNode:
a.visitExpression(n)
default:
a.errorf(n, "unsupported node type %T", n)
}
}
func (a *Analyser) visitFunction(n *parser.FunctionDefNode) {
if n.Symbol == nil {
return
}
prev := a.current
a.current = symbols.NewScope(prev)
for i, arg := range n.Args {
paramSym := &symbols.Symbol{
Name: arg.Name,
Kind: symbols.SymbolKindVariable,
Type: n.Symbol.Signature.Parameters[i],
}
if err := a.current.Define(paramSym); err != nil {
a.errors = append(a.errors, err)
}
}
a.visit(n.Body)
a.current = prev
}
func (a *Analyser) visitBlock(n *parser.BlockNode) {
prev := a.current
a.current = symbols.NewScope(prev)
for _, stmt := range n.Body {
a.visit(stmt)
}
a.current = prev
}
func (a *Analyser) visitLocalDeclaration(n *parser.DeclarationNode) {
var varType types.Type
if n.Type != nil {
varType = a.resolveTypeNode(n.Type)
}
sym := &symbols.Symbol{
Name: n.Name,
Kind: symbols.SymbolKindVariable,
Type: varType,
}
if err := a.current.Define(sym); err != nil {
a.errors = append(a.errors, err)
}
n.Symbol = sym
if n.Value != nil {
a.visitExpression(n.Value)
}
}
func (a *Analyser) visitExpression(expr parser.ExpressionNode) {
switch e := expr.(type) {
case *parser.IdentifierNode:
a.resolveIdentifier(e)
case *parser.BinaryOpNode:
a.visitExpression(e.Operand1)
a.visitExpression(e.Operand2)
case *parser.UnaryOpNode:
a.visitExpression(e.Operand)
case *parser.FunctionCallNode:
a.resolveFunctionCall(e)
case *parser.IndexExprNode:
a.visitExpression(e.Subject)
a.visitExpression(e.Index)
case *parser.IfExprNode:
a.visitExpression(e.IfBranch.Condition)
a.visitExpression(e.IfBranch.Node)
for _, br := range e.ElseIfBranches {
a.visitExpression(br.Condition)
a.visitExpression(br.Node)
}
if e.ElseBranch != nil {
a.visitExpression(e.ElseBranch)
}
case *parser.StructLiteralNode:
a.visitStructLiteral(e)
case *parser.ArrayLiteralNode:
for _, el := range e.Elements {
a.visitExpression(el)
}
case *parser.CastNode:
a.resolveTypeNode(e.ToType)
a.visitExpression(e.Operand)
case *parser.SizeOfNode:
a.resolveTypeNode(e.Operand)
case *parser.GivenExprNode:
a.visitBlock(e.Block)
a.visitExpression(e.FinalExpr)
}
}
func (a *Analyser) resolveIdentifier(n *parser.IdentifierNode) {
sym, ok := a.current.Resolve(n.Name)
if !ok {
a.errorf(n, "undefined identifier %q", n.Name)
return
}
n.Symbol = sym
}
func (a *Analyser) resolveFunctionCall(n *parser.FunctionCallNode) {
sym, ok := a.resolveModuleAccess(n.Name)
if !ok {
return
}
if sym.Kind != symbols.SymbolKindFunction {
a.errorf(n, "%q is not a function", sym.Name)
return
}
n.Symbol = sym
for _, arg := range n.Args {
a.visitExpression(arg)
}
}
func (a *Analyser) resolveModuleAccess(n *parser.ModuleAccessNode) (*symbols.Symbol, bool) {
if n.ModName == "" {
sym, ok := a.current.Resolve(n.Ident.Name)
if !ok {
a.errorf(n, "undefined identifier %q", n.Ident.Name)
return nil, false
}
n.Symbol = sym
return sym, true
}
modSym, ok := a.current.Resolve(n.ModName)
if !ok || modSym.Kind != symbols.SymbolKindModule {
a.errorf(n, "unknown module %q", n.ModName)
return nil, false
}
sym, ok := modSym.Module.Scope.Resolve(n.Ident.Name)
if !ok {
a.errorf(n, "undefined symbol %q in module %q", n.Ident.Name, n.ModName)
return nil, false
}
n.Symbol = sym
return sym, true
}
func (a *Analyser) visitAssignment(n *parser.AssignmentNode) {
a.visit(n.Assignee)
a.visitExpression(n.Value)
}
func (a *Analyser) visitArrayAssignment(n *parser.ArrayAssignmentNode) {
a.visitExpression(n.Assignee)
a.visitExpression(n.Index)
a.visitExpression(n.Value)
}
func (a *Analyser) visitIf(n *parser.IfNode) {
a.visitExpression(n.IfBranch.Condition)
a.visitBlock(n.IfBranch.Node)
for _, br := range n.ElseIfBranches {
a.visitExpression(br.Condition)
a.visitBlock(br.Node)
}
if n.ElseBranch != nil {
a.visitBlock(n.ElseBranch)
}
}
func (a *Analyser) visitFor(n *parser.ForNode) {
prev := a.current
a.current = symbols.NewScope(prev)
for _, node := range n.ExprsOrStmts {
a.visit(node)
}
a.visitBlock(n.Body)
a.current = prev
}
func (a *Analyser) visitControlKeyword(n *parser.ControlKeywordNode) {
if n.ReturnValue != nil {
a.visitExpression(n.ReturnValue)
}
}
func (a *Analyser) visitStructLiteral(n *parser.StructLiteralNode) {
sym, ok := a.resolveModuleAccess(n.Name)
if !ok {
return
}
if sym.Kind != symbols.SymbolKindType {
a.errorf(n, "%q is not a type", sym.Name)
return
}
n.Symbol = sym
for _, field := range n.Fields {
a.visitExpression(field.R)
}
}