package sema
import (
"fmt"
"github.com/marzeq/qk/parser"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/tokeniser"
"github.com/marzeq/qk/types"
)
type Attributor struct {
analyser *Analyser
errors []error
}
func (a *Analyser) NewAttributor() *Attributor {
return &Attributor{analyser: a}
}
func (a *Attributor) errorf(node parser.Node, format string, args ...any) {
a.errors = append(a.errors, shared.NewError(node.GetLoc(), format, args...))
}
func (a *Attributor) AttributeModule(root *parser.RootNode) {
a.attributeNode(root)
}
func (a *Attributor) Errors() []error {
return a.errors
}
func (a *Attributor) attributeNode(node parser.Node) {
switch n := node.(type) {
case *parser.RootNode:
for _, stmt := range n.Body {
a.attributeNode(stmt)
}
case *parser.ImportNode, *parser.ModuleNode:
// pass
case *parser.TypeAliasNode:
// pass
case *parser.FunctionDefNode:
if n.Body != nil {
a.attributeNode(n.Body)
}
if n.Symbol.Signature.ReturnType == nil {
switch b := n.Body.(type) {
case *parser.BlockNode:
returnNodes := collectFunctionReturnNodes(b.Body)
if len(returnNodes) > 0 {
var current types.Type
first := returnNodes[0]
if first.ReturnValue == nil {
current = types.PrimitiveVoid
} else {
current = first.ReturnValue.GetType()
}
for _, returnNode := range returnNodes[1:] {
var t types.Type
if returnNode.ReturnValue == nil {
t = types.PrimitiveVoid
} else {
t = returnNode.ReturnValue.GetType()
}
if types.IsNumeric(current) && types.IsNumeric(t) {
got := types.PromoteNumeric(current, t)
if current.Equals(types.ErrorType{}) {
a.errorf(returnNode, "inconsistent return types: %v and %v", got, t)
}
current = got
continue
}
if !current.Equals(t) {
a.errorf(returnNode, "inconsistent return types: %v and %v", current, t)
current = types.ErrorType{}
break
}
}
n.Symbol.Signature.ReturnType = current
} else {
n.Symbol.Signature.ReturnType = types.PrimitiveVoid
}
case parser.ExpressionNode:
n.Symbol.Signature.ReturnType = b.GetType()
case nil:
default:
panic(fmt.Sprintf("unexpected function body type: %T\n", n.Body))
}
}
case *parser.BlockNode:
for _, stmt := range n.Body {
a.attributeNode(stmt)
}
case *parser.DeclarationNode:
if n.Value != nil {
a.attributeExpr(n.Value)
}
if n.Value != nil && n.Symbol.Type == nil {
n.Symbol.Type = n.Value.GetType()
}
case *parser.AssignmentNode:
a.attributeExpr(n.Value)
case *parser.IndexAssignmentNode:
a.attributeExpr(n.Assignee)
a.attributeExpr(n.Value)
a.attributeExpr(n.Index)
case *parser.IfNode:
a.attributeExpr(n.IfBranch.Condition)
a.attributeNode(n.IfBranch.Node)
for _, elif := range n.ElseIfBranches {
a.attributeExpr(elif.Condition)
a.attributeNode(elif.Node)
}
if n.ElseBranch != nil {
a.attributeNode(n.ElseBranch)
}
case *parser.ForNode:
for _, node := range n.ExprsOrStmts {
a.attributeNode(node)
}
a.attributeNode(n.Body)
case *parser.ControlKeywordNode:
if n.ReturnValue != nil {
a.attributeExpr(n.ReturnValue)
}
case parser.ExpressionNode:
a.attributeExpr(n)
default:
panic(fmt.Sprintf("unexpected node type: %T\n", node))
}
}
func (a *Attributor) attributeExpr(node parser.ExpressionNode) {
switch n := node.(type) {
case *parser.IdentifierNode:
if n.Symbol == nil || n.Symbol.Type == nil {
a.errorf(n, "undefined identifier: %s", n.Name)
n.SetType(types.ErrorType{})
} else {
n.SetType(n.Symbol.Type)
}
case *parser.ModuleAccessNode:
if n.Symbol == nil || n.Symbol.Type == nil {
a.errorf(n, "undefined identifier: %s:%s", n.ModName, n.Ident)
n.SetType(types.ErrorType{})
} else {
n.SetType(n.Symbol.Type)
}
case *parser.IntegerLiteralNode:
n.SetType(types.UntypedInt{})
case *parser.FloatLiteralNode:
n.SetType(types.UntypedFloat{})
case *parser.BoolLiteralNode:
n.SetType(types.PrimitiveBool)
case *parser.StringLiteralNode:
n.SetType(types.PointerType{
Base: types.PrimitiveChar,
})
case *parser.CharLiteralNode:
n.SetType(types.PrimitiveChar)
case *parser.NilLiteralNode:
n.SetType(types.PointerType{
Base: types.PrimitiveVoid,
})
case *parser.StructLiteralNode:
if n.Symbol != nil {
n.SetType(n.Symbol.TypeInfo)
for _, field := range n.Fields {
a.attributeExpr(field.R)
}
} else {
panic("todo for anonymous struct literals")
}
case *parser.SliceLiteralNode:
typs := []types.Type{}
for _, elem := range n.Elements {
a.attributeExpr(elem)
typs = append(typs, elem.GetType())
}
if len(typs) > 0 {
currentType := typs[0]
for _, t := range typs[1:] {
got := types.PromoteNumeric(currentType, t)
if currentType.Equals(types.ErrorType{}) {
a.errorf(n, "inconsistent slice element types: %v and %v", got, t)
}
currentType = got
}
n.SetType(types.SliceType{
Base: currentType,
Size: len(n.Elements),
})
} else {
n.SetType(types.SliceType{
Base: types.PrimitiveVoid,
Size: 0,
})
}
case *parser.FunctionCallNode:
if n.Symbol.Signature.ReturnType != nil {
n.SetType(n.Symbol.Signature.ReturnType)
} else {
n.SetType(types.PrimitiveVoid)
}
for _, arg := range n.Args {
a.attributeExpr(arg)
}
case *parser.IfExprNode:
a.attributeExpr(n.IfBranch.Condition)
var current types.Type = types.ErrorType{}
if n.IfBranch.Node != nil {
a.attributeExpr(n.IfBranch.Node)
current = n.IfBranch.Node.GetType()
}
for _, elif := range n.ElseIfBranches {
a.attributeExpr(elif.Condition)
a.attributeExpr(elif.Node)
t := elif.Node.GetType()
if types.IsNumeric(current) && types.IsNumeric(t) {
current = types.PromoteNumeric(current, t)
continue
}
if !current.Equals(t) {
current = types.ErrorType{}
break
}
}
if n.ElseBranch != nil {
a.attributeExpr(n.ElseBranch)
t := n.ElseBranch.GetType()
if types.IsNumeric(current) && types.IsNumeric(t) {
current = types.PromoteNumeric(current, t)
} else if !current.Equals(t) {
current = types.ErrorType{}
}
}
if current.Equals(types.ErrorType{}) {
a.errorf(n, "inconsistent types in if expression branches")
}
n.SetType(current)
case *parser.GivenExprNode:
a.attributeNode(n.Block)
a.attributeExpr(n.FinalExpr)
n.SetType(n.FinalExpr.GetType())
case *parser.UnaryOpNode:
a.attributeExpr(n.Operand)
switch n.Op {
case parser.UnaryOpNegate:
if types.IsSigned(n.Operand.GetType()) || types.IsFloat(n.Operand.GetType()) {
n.SetType(n.Operand.GetType())
}
case parser.UnaryOpLogicalNot:
if n.Operand.GetType().Equals(types.PrimitiveBool) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "logical not operator requires a boolean operand")
n.SetType(types.ErrorType{})
}
case parser.UnaryOpReference:
n.SetType(types.PointerType{
Base: n.Operand.GetType(),
})
case parser.UnaryOpDereference:
if ptr, ok := n.Operand.GetType().(types.PointerType); ok {
n.SetType(ptr.Base)
} else {
a.errorf(n, "cannot dereference non-pointer type")
n.SetType(types.ErrorType{})
}
case parser.UnaryOpSliceLen:
switch n.Operand.GetType().(type) {
case types.SliceType:
n.SetType(types.PrimitiveUsz)
default:
a.errorf(n, "slice length operator requires a slice operand")
n.SetType(types.ErrorType{})
}
default:
panic("unhandled unary operator")
}
case *parser.IndexExprNode:
a.attributeExpr(n.Subject)
a.attributeExpr(n.Index)
switch t := n.Subject.GetType().(type) {
case types.SliceType:
n.SetType(t.Base)
case types.PointerType:
n.SetType(t.Base)
default:
a.errorf(n, "cannot index type %v", n.Subject.GetType())
n.SetType(types.ErrorType{})
}
case *parser.BinaryOpNode:
a.attributeExpr(n.Operand1)
a.attributeExpr(n.Operand2)
t1 := n.Operand1.GetType()
t2 := n.Operand2.GetType()
switch n.Op {
case parser.BinaryOpAdd, parser.BinaryOpSubtract, parser.BinaryOpMultiply, parser.BinaryOpDivide, parser.BinaryOpModulo:
if types.IsNumeric(t1) && types.IsNumeric(t2) {
got := types.PromoteNumeric(t1, t2)
if got.Equals(types.ErrorType{}) {
a.errorf(n, "incompatible types for binary operator: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
} else {
n.SetType(got)
}
} else {
a.errorf(n, "arithmetic operators require numeric operands")
n.SetType(types.ErrorType{})
}
case parser.BinaryOpEqual, parser.BinaryOpNotEqual, parser.BinaryOpLess, parser.BinaryOpLessEqual, parser.BinaryOpGreater, parser.BinaryOpGreaterEqual:
if types.IsNumeric(t1) && types.IsNumeric(t2) {
n.SetType(types.PrimitiveBool)
} else if t1.Equals(t2) {
n.SetType(types.PrimitiveBool)
} else if types.IsPointer(t1) && types.IsPointer(t2) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "comparison operators require operands of the same type or both numeric types")
n.SetType(types.ErrorType{})
}
case parser.BinaryOpLogicalAnd, parser.BinaryOpLogicalOr:
if t1.Equals(types.PrimitiveBool) && t2.Equals(types.PrimitiveBool) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "logical operators require boolean operands")
n.SetType(types.ErrorType{})
}
default:
panic("unhandled binary operator")
}
case *parser.CastNode:
a.attributeExpr(n.Operand)
target := a.analyser.resolveTypeNode(n.ToType)
n.SetType(target)
case *parser.SizeOfNode:
n.SetType(types.PrimitiveUsz)
default:
panic(fmt.Sprintf("unexpected expression type: %T\n", node))
}
if node.GetType() == nil {
panic("expression without type")
}
}
func collectFunctionReturnNodes(body []parser.Node) []*parser.ControlKeywordNode {
var returnNodes []*parser.ControlKeywordNode
for _, stmt := range body {
switch n := stmt.(type) {
case *parser.ControlKeywordNode:
if n.Keyword == tokeniser.KeywordReturn {
returnNodes = append(returnNodes, n)
}
case *parser.IfNode:
returnNodes = append(returnNodes, collectFunctionReturnNodes(n.IfBranch.Node.Body)...)
for _, elif := range n.ElseIfBranches {
returnNodes = append(returnNodes, collectFunctionReturnNodes(elif.Node.Body)...)
}
if n.ElseBranch != nil {
returnNodes = append(returnNodes, collectFunctionReturnNodes(n.ElseBranch.Body)...)
}
case *parser.ForNode:
returnNodes = append(returnNodes, collectFunctionReturnNodes(n.Body.Body)...)
case *parser.BlockNode:
returnNodes = append(returnNodes, collectFunctionReturnNodes(n.Body)...)
}
}
return returnNodes
}