package analyzer import "fmt" import "git.tebibyte.media/fspl/fspl/errors" import "git.tebibyte.media/fspl/fspl/entity" // All expression analysis routines must take in the type they are being // assigned to and return an error if they can't be assigned to it. if the type // is nil, the expression must ignore it unless it can do upwards type // inference. func (this *Tree) analyzeAssignment ( assignment *entity.Assignment, ) ( entity.Expression, error, ) { // analyze location location, err := this.analyzeExpression(nil, strict, assignment.Location) if err != nil { return nil, err } assignment.Location = location // ensure location is location expression err = this.isLocationExpression(location) if err != nil { return nil, err } // analyze value value, err := this.analyzeExpression(location.Type(), strict, assignment.Value) if err != nil { return nil, err } assignment.Value = value return assignment, nil } func (this *Tree) analyzeVariable ( into entity.Type, mode strictness, variable *entity.Variable, ) ( entity.Expression, error, ) { declaration := this.variable(variable.Name) if declaration == nil { return nil, errors.Errorf ( variable.Position, "no variable named %s", variable.Name) } err := this.canAssign(variable.Position, into, mode, declaration.Type()) if err != nil { return nil, err } variable.Declaration = declaration return variable, nil } func (this *Tree) analyzeDeclaration ( into entity.Type, mode strictness, declaration *entity.Declaration, ) ( entity.Expression, error, ) { scope, _ := this.topScope() existing := scope.Variable(declaration.Name) if existing != nil { return nil, errors.Errorf ( declaration.Position, "%s already declared in block at %v", declaration.Name, existing.Position) } ty, err := this.analyzeType(declaration.Ty, false) declaration.Ty = ty if err != nil { return nil, err } err = this.canAssign(declaration.Position, into, mode, declaration.Type()) if err != nil { return nil, err } this.addVariable(declaration) return declaration, nil } func (this *Tree) analyzeCall ( into entity.Type, mode strictness, call *entity.Call, ) ( entity.Expression, error, ) { // get function unit, err := this.resolveNickname(call.Position, call.UnitNickname) if err != nil { return nil, err } function, err := this.analyzeFunction(call.Position, entity.Key { Unit: unit, Name: call.Name, }) if err != nil { return nil, err } call.Function = function call.Unit = function.Unit // check access permissions if function.Acc == entity.AccessPrivate && function.Unit != this.unit { return nil, errors.Errorf ( call.Position, "function %v::[%v] is private", call.UnitNickname, call.Name) } // check return result err = this.canAssign(call.Position, into, mode, function.Signature.Return) if err != nil { return nil, err } // check arg count if len(call.Arguments) > len(function.Signature.Arguments) { return nil, errors.Errorf ( call.Position, "too many arguments in call to %s", call.Name) } else if len(call.Arguments) < len(function.Signature.Arguments) { return nil, errors.Errorf ( call.Position, "too few arguments in call to %s", call.Name) } // check arg types for index, argument := range call.Arguments { signature := function.Signature correct := signature.ArgumentMap[signature.ArgumentOrder[index]] argument, err := this.analyzeExpression(correct.Type(), strict, argument) if err != nil { return nil, err } call.Arguments[index] = argument } return call, nil } func (this *Tree) analyzeMethodCall ( into entity.Type, mode strictness, call *entity.MethodCall, ) ( entity.Expression, error, ) { // get method source, err := this.analyzeExpression(nil, strict, call.Source) if err != nil { return nil, err } method, err := this.analyzeMethodOrBehavior ( call.Position, source.Type(), call.Name) if err != nil { return nil, err } // extract signature var signature *entity.Signature switch method := method.(type) { case *entity.Signature: signature = method call.Behavior = signature // since this is a behavior, check access permissions of the // interface err = this.typeRestricted(call.Position, source.Type()) if err != nil { return nil, err } case *entity.Method: signature = method.Signature call.Method = method // since this is a method, check access permissions of the // method if method.Acc == entity.AccessPrivate && method.Unit != this.unit { return nil, errors.Errorf ( call.Position, "method %v.[%v] is private", method.TypeName, method.Signature.Name) } default: panic(fmt.Sprint ( "Tree.analyzeMethodOrBehavior returned ", method)) } // check return result err = this.canAssign(call.Position, into, mode, signature.Return) if err != nil { return nil, err } // check arg count if len(call.Arguments) > len(signature.Arguments) { return nil, errors.Errorf ( call.Position, "too many arguments in call to %s", call.Name) } else if len(call.Arguments) < len(signature.Arguments) { return nil, errors.Errorf ( call.Position, "too few arguments in call to %s", call.Name) } // check arg types for index, argument := range call.Arguments { correct := signature.ArgumentMap[signature.ArgumentOrder[index]] argument, err := this.analyzeExpression(correct.Type(), strict, argument) if err != nil { return nil, err } call.Arguments[index] = argument } return call, nil } func (this *Tree) analyzeSubscript ( into entity.Type, mode strictness, subscript *entity.Subscript, ) ( entity.Expression, error, ) { slice, err := this.analyzeExpression ( &entity.TypeSlice { Position: subscript.Position, Element: into, }, weak, subscript.Slice) if err != nil { return nil, err } subscript.Slice = slice subscript.Ty = into // check permissions err = this.typeRestricted(subscript.Position, slice.Type()) if err != nil { return nil, err } offset, err := this.analyzeExpression ( builtinType("Index"), weak, subscript.Offset) if err != nil { return nil, err } subscript.Offset = offset var frailType bool switch into := into.(type) { case nil: frailType = true case *entity.TypeSlice: frailType = into.Element == nil } if frailType { ty := ReduceToBase(subscript.Slice.Type()) switch ty := ty.(type) { case *entity.TypeSlice: subscript.Ty = ty.Element case *entity.TypeArray: subscript.Ty = ty.Element } } return subscript, nil } func (this *Tree) analyzeSlice ( into entity.Type, mode strictness, slice *entity.Slice, ) ( entity.Expression, error, ) { value, err := this.analyzeExpression(into, weak, slice.Slice) if err != nil { return nil, err } slice.Slice = value // check permissions err = this.typeRestricted(slice.Position, value.Type()) if err != nil { return nil, err } if slice.Start != nil { start, err := this.analyzeExpression ( builtinType("Index"), weak, slice.Start) if err != nil { return nil, err } slice.Start = start } if slice.End != nil { end, err := this.analyzeExpression ( builtinType("Index"), weak, slice.End) if err != nil { return nil, err } slice.End = end } return slice, nil } func (this *Tree) analyzeLength ( into entity.Type, mode strictness, length *entity.Length, ) ( entity.Expression, error, ) { value, err := this.analyzeExpression(nil, strict, length.Slice) if err != nil { return nil, err } length.Slice = value length.Ty = builtinType("Index") return length, nil } func (this *Tree) analyzeDereference ( into entity.Type, mode strictness, dereference *entity.Dereference, ) ( entity.Expression, error, ) { pointer, err := this.analyzeExpression ( &entity.TypePointer { Position: dereference.Position, Referenced: into, }, weak, dereference.Pointer) if err != nil { return nil, err } dereference.Pointer = pointer dereference.Ty = into var frailType bool switch into := into.(type) { case nil: frailType = true case *entity.TypePointer: frailType = into.Referenced == nil } if frailType { ty := ReduceToBase(dereference.Pointer.Type()) switch ty := ty.(type) { case *entity.TypePointer: dereference.Ty = ty.Referenced } } return dereference, nil } func (this *Tree) analyzeReference ( into entity.Type, mode strictness, reference *entity.Reference, ) ( entity.Expression, error, ) { referenced, ok := into.(*entity.TypePointer) if !ok { return nil, errors.Errorf(reference.Position, "expected %v", into) } value, err := this.analyzeExpression ( referenced.Referenced, weak, reference.Value) if err != nil { return nil, err } err = this.isLocationExpression(reference.Value) if err != nil { return nil, err } reference.Value = value reference.Ty = referenced.Referenced return reference, nil } func (this *Tree) analyzeValueCast ( into entity.Type, mode strictness, cast *entity.ValueCast, ) ( entity.Expression, error, ) { ty, err := this.analyzeType(cast.Ty, false) if err != nil { return nil, err } cast.Ty = ty err = this.canAssign(cast.Position, into, mode, cast.Type()) if err != nil { return nil, err } value, err := this.analyzeExpression(cast.Ty, coerce, cast.Value) if err != nil { return nil, err } cast.Value = value return cast, nil } func (this *Tree) analyzeBitCast ( into entity.Type, mode strictness, cast *entity.BitCast, ) ( entity.Expression, error, ) { ty, err := this.analyzeType(cast.Ty, false) if err != nil { return nil, err } cast.Ty = ty err = this.canAssign(cast.Position, into, mode, cast.Type()) if err != nil { return nil, err } value, err := this.analyzeExpression(cast.Ty, force, cast.Value) if err != nil { return nil, err } cast.Value = value return cast, nil } func (this *Tree) analyzeOperation ( into entity.Type, mode strictness, operation *entity.Operation, ) ( entity.Expression, error, ) { // check permissions err := this.typeRestricted(operation.Position, into) if err != nil { return nil, err } wrongInto := func () (entity.Expression, error) { return nil, errors.Errorf ( operation.Position, "expected %v", entity.FormatType(into)) } wrongArgCount := func () (entity.Expression, error) { return nil, errors.Errorf ( operation.Position, "wrong argument count for %v", operation.Operator) } nSameType := func (n int, constraint func (entity.Type) bool) (entity.Expression, error) { if n > 0 { if len(operation.Arguments) != n { return wrongArgCount() } } else { if len(operation.Arguments) < 1 { return wrongArgCount() } } n = len(operation.Arguments) if !constraint(into) { return wrongInto() } left, err := this.analyzeExpression(into, mode, operation.Arguments[0]) if err != nil { return nil, err } operation.Arguments[0] = left operation.Ty = left.Type() for index := 1; index < n; index ++ { argument, err := this.analyzeExpression ( left.Type(), strict, operation.Arguments[index]) if err != nil { return nil, err } operation.Arguments[index] = argument } return operation, nil } comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) { if len(operation.Arguments) < 2 { return wrongArgCount() } if !isBoolean(into) { return wrongInto() } operation.Ty = builtinType("Bool") // find the first argument that has explicit type information. // TODO: possibly make a method of expressions to check this // without analyzing anything boss := -1 var argumentType entity.Type for index, argument := range operation.Arguments { argument, err := this.analyzeExpression(nil, strict, argument) if err == nil { boss = index operation.Arguments[index] = argument argumentType = argument.Type() break } } if argumentType == nil { return nil, errors.Errorf ( operation.Position, "operation arguments have ambiguous type") } // analyze all remaining arguments for index, argument := range operation.Arguments { if index == boss { continue } argument, err := this.analyzeExpression ( argumentType, strict, argument) if err != nil { boss = index operation.Arguments[index] = argument break } operation.Arguments[index] = argument } return operation, nil } switch operation.Operator { // math case entity.OperatorAdd, entity.OperatorSubtract, entity.OperatorMultiply, entity.OperatorDivide, entity.OperatorIncrement, entity.OperatorDecrement: return nSameType(-1, isNumeric) case entity.OperatorModulo: return nSameType(2, isNumeric) // logic case entity.OperatorLogicalNot: return nSameType(1, isBoolean) case entity.OperatorLogicalOr, entity.OperatorLogicalAnd, entity.OperatorLogicalXor: return nSameType(-1, isBoolean) // bit manipulation case entity.OperatorNot: return nSameType(1, isInteger) case entity.OperatorOr, entity.OperatorAnd, entity.OperatorXor: return nSameType(-1, isInteger) case entity.OperatorLeftShift, entity.OperatorRightShift: if len(operation.Arguments) != 2 { return wrongArgCount() } if !isInteger(into) { return wrongInto() } arg, err := this.analyzeExpression(into, mode, operation.Arguments[0]) if err != nil { return nil, err } operation.Arguments[0] = arg operation.Ty = arg.Type() offset, err := this.analyzeExpression ( builtinType("Index"), weak, operation.Arguments[1]) if err != nil { return nil, err } operation.Arguments[1] = offset return operation, nil // comparison case entity.OperatorLess, entity.OperatorGreater, entity.OperatorLessEqual, entity.OperatorGreaterEqual: return comparison(isOrdered) case entity.OperatorEqual: return comparison(isOrdered) default: panic(fmt.Sprint ( "BUG: analyzer doesnt know about operator ", operation.Operator)) } } func (this *Tree) analyzeBlock ( into entity.Type, mode strictness, block *entity.Block, ) ( entity.Expression, error, ) { this.pushScope(block) defer this.popScope() if len(block.Steps) == 0 && into != nil { return nil, errors.Errorf ( block.Position, "block must have at least one statement") } final := len(block.Steps) - 1 for index, step := range block.Steps { if index == final && into != nil { expression, ok := step.(entity.Expression) if !ok { return nil, errors.Errorf ( block.Position, "expected expression") } step, err := this.analyzeExpression(into, strict, expression) if err != nil { return nil, err } block.Steps[index] = step block.Ty = step.Type() } else { step, err := this.analyzeExpression(nil, strict, step) if err != nil { return nil, err } block.Steps[index] = step } } return block, nil } func (this *Tree) analyzeMemberAccess ( into entity.Type, mode strictness, access *entity.MemberAccess, ) ( entity.Expression, error, ) { source, err := this.analyzeExpression(nil, strict, access.Source) if err != nil { return nil, err } // determine type with the members var sourceType *entity.TypeStruct var qualifiedSourceType entity.Type switch sourceTypeAny := ReduceToBase(source.Type()).(type) { case *entity.TypeStruct: sourceType = sourceTypeAny qualifiedSourceType = source.Type() case *entity.TypePointer: referenced, ok := ReduceToBase(sourceTypeAny.Referenced).(*entity.TypeStruct) if !ok { return nil, errors.Errorf ( access.Position, "cannot access members of %v", source) } sourceType = referenced qualifiedSourceType = sourceTypeAny.Referenced default: return nil, errors.Errorf ( access.Position, "cannot access members of %v", source) } // check permissions err = this.typeRestricted(access.Position, qualifiedSourceType) if err != nil { return nil, err } // get member member, ok := sourceType.MemberMap[access.Member] if !ok { return nil, errors.Errorf ( access.Position, "no member %v", access) } err = this.canAssign(access.Position, into, mode, member.Type()) if err != nil { return nil, err } access.Ty = member.Type() return access, nil } func (this *Tree) analyzeIfElse ( into entity.Type, mode strictness, ifelse *entity.IfElse, ) ( entity.Expression, error, ) { condition, err := this.analyzeExpression ( &entity.TypeNamed { Name: "Bool", Type: builtinType("Bool"), }, weak, ifelse.Condition) if err != nil { return nil, err } ifelse.Condition = condition trueBranch, err := this.analyzeExpression(into, strict, ifelse.True) if err != nil { return nil, err } ifelse.True = trueBranch if ifelse.False == nil { if into != nil { return nil, errors.Errorf ( ifelse.Position, "else case required when using value of if ") } } else { falseBranch, err := this.analyzeExpression(into, strict, ifelse.False) if err != nil { return nil, err } ifelse.False = falseBranch } ifelse.Ty = into return ifelse, nil } func (this *Tree) analyzeLoop ( into entity.Type, mode strictness, loop *entity.Loop, ) ( entity.Expression, error, ) { loop.Ty = into this.pushLoop(loop) defer this.popLoop() body, err := this.analyzeExpression(nil, strict, loop.Body) if err != nil { return nil, err } loop.Body = body return loop, nil } func (this *Tree) analyzeBreak ( into entity.Type, mode strictness, brk *entity.Break, ) ( entity.Expression, error, ) { if into != nil { return nil, errors.Errorf ( brk.Position, "expected %v", entity.FormatType(into)) } loop, ok := this.topLoop() if !ok { return nil, errors.Errorf ( brk.Position, "break statement must be within loop") } brk.Loop = loop if loop.Type() != nil && brk.Value == nil { return nil, errors.Errorf ( brk.Position, "break statement must have value") } if brk.Value != nil { value, err := this.analyzeExpression(loop.Type(), strict, brk.Value) if err != nil { return nil, err } brk.Value = value } return brk, nil } func (this *Tree) analyzeReturn ( into entity.Type, mode strictness, ret *entity.Return, ) ( entity.Expression, error, ) { if into != nil { return nil, errors.Errorf ( ret.Position, "expected %v", entity.FormatType(into)) } ret.Declaration, _ = this.topDeclaration() var ty entity.Type switch ret.Declaration.(type) { case *entity.Function: ty = ret.Declaration.(*entity.Function).Signature.Return case *entity.Method: ty = ret.Declaration.(*entity.Method).Signature.Return } if ty != nil && ret.Value == nil { return nil, errors.Errorf ( ret.Position, "break statement must have value") } if ret.Value != nil { value, err := this.analyzeExpression(ty, strict, ret.Value) if err != nil { return nil, err } ret.Value = value } return ret, nil }