fspl/analyzer/expression.go

1014 lines
24 KiB
Go

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.typeOpaque(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 {
Pos: subscript.Position(),
Element: into,
}, weak,
subscript.Slice)
if err != nil { return nil, err }
subscript.Slice = slice
subscript.Ty = into
// check permissions
err = this.typeOpaque(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.typeOpaque(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 {
Pos: 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.typeOpaque(operation.Position(), into)
if err != nil { return nil, err }
intoUntrustworthy := into == nil || mode == force || mode == coerce
undefined := func (ty entity.Type) (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position(), "operator %v undefined for %v",
operation.Operator, entity.FormatType(ty))
}
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)
}
determineArgumentType := func () (entity.Type, int, error) {
// find the first argument that has explicit type information.
boss := -1
var argumentType entity.Type
for index, argument := range operation.Arguments {
if argument.HasExplicitType() {
argument, err := this.analyzeExpression(nil, strict, argument)
if err != nil { return nil, -1, err }
boss = index
operation.Arguments[index] = argument
argumentType = argument.Type()
break
}
}
if argumentType == nil {
return nil, -1, errors.Errorf (
operation.Position(),
"operation arguments have ambiguous type")
}
return argumentType, boss, err
}
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)
boss := -1
var argumentType entity.Type
if intoUntrustworthy {
// determine the type of all arguments (and the return
// type) using determineArgumentType
var err error
argumentType, boss, err = determineArgumentType()
if err != nil { return nil, err }
if !constraint(argumentType) { return undefined(argumentType) }
} else {
// into is trustworthy, so we don't need to determine
// anything
argumentType = into
}
if !constraint(argumentType) { return undefined(argumentType) }
operation.Ty = argumentType
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
operation.Ty, strict,
argument)
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) && !intoUntrustworthy { return wrongInto() }
operation.Ty = builtinType("Bool")
// determine argument type
argumentType, boss, err := determineArgumentType()
if err != nil { return nil, err }
if !argConstraint(argumentType) { return undefined(argumentType) }
// analyze all remaining arguments
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
argumentType, strict, argument)
if err != nil { return nil, err }
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.typeOpaque(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) analyzeMatch (
into entity.Type,
mode strictness,
match *entity.Match,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(nil, strict, match.Value)
if err != nil { return nil, err }
match.Value = value
rawUnion, ok := this.isUnion(value.Type())
if !ok {
// FIXME: add Position() method to Expression, then change this
// error message (and others) #53
return nil, errors.Errorf (
match.Position(), "type %v is not a union",
value.Type())
}
match, err = this.assembleMatchMap(match)
if err != nil { return nil, err }
for index, cas := range match.Cases {
hash := match.CaseOrder[index]
if _, ok := rawUnion.AllowedMap[hash]; !ok {
return nil, errors.Errorf (
cas.Declaration.Ty.Position(),
"%v is not included within %v",
cas.Declaration.Ty, value.Type())
}
this.pushScope(cas)
declaration, err := this.analyzeDeclaration (
nil, strict,
cas.Declaration)
if err != nil { this.popScope(); return nil, err }
cas.Declaration = declaration.(*entity.Declaration)
expression, err := this.analyzeExpression (
into, mode,
cas.Expression)
if err != nil { this.popScope(); return nil, err }
cas.Expression = expression
this.popScope()
match.Cases[index] = cas
match.CaseMap[hash] = cas
}
if match.Default != nil {
this.pushScope(match.Default)
expression, err := this.analyzeExpression (
into, mode,
match.Default.Expression)
this.popScope()
if err != nil { return nil, err }
match.Default.Expression = expression
}
allCasesCovered :=
len(match.Cases) == len(rawUnion.Allowed) ||
match.Default != nil
if into != nil && !allCasesCovered {
return nil, errors.Errorf (
match.Position(),
"match does not cover all types within %v",
value.Type())
}
match.Ty = into
return match, nil
}
func (this *Tree) assembleMatchMap (match *entity.Match) (*entity.Match, error) {
match.CaseMap = make(map[entity.Hash] *entity.MatchCase)
match.CaseOrder = make([]entity.Hash, len(match.Cases))
for index, cas := range match.Cases {
hash := cas.Declaration.Ty.Hash()
if previous, exists := match.CaseMap[hash]; exists {
return match, errors.Errorf (
cas.Declaration.Ty.Position(),
"%v already listed in match at %v",
cas.Declaration.Ty, previous.Declaration.Ty.Position())
}
match.CaseMap [hash] = cas
match.CaseOrder[index] = hash
match.Cases [index] = cas
}
return match, nil
}
func (this *Tree) analyzeSwitch (
into entity.Type,
mode strictness,
switc *entity.Switch,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(nil, strict, switc.Value)
if err != nil { return nil, err }
switc.Value = value
width, integerOk := integerWidth(value.Type())
if !integerOk {
return nil, errors.Errorf (
value.Position(), "cannot switch on type %v",
value.Type())
}
switc.CaseOrder = make([]int64, len(switc.Cases))
switc.CaseMap = make(map[int64] *entity.SwitchCase)
for index, cas := range switc.Cases {
this.pushScope(cas)
key, err := this.analyzeExpression (
value.Type(), strict,
cas.Key)
if err != nil { this.popScope(); return nil, err }
cas.Key = key
expression, err := this.analyzeExpression (
into, mode,
cas.Expression)
if err != nil { this.popScope(); return nil, err }
cas.Expression = expression
this.popScope()
var keyInt int64
switch key := key.(type) {
case *entity.LiteralInt:
keyInt = int64(key.Value)
case *entity.LiteralString:
switch {
case width >= 32:
keyInt = int64(key.ValueUTF32[0])
case width >= 16:
keyInt = int64(key.ValueUTF16[0])
default:
keyInt = int64(key.ValueUTF8[0])
}
default:
return nil, errors.Errorf (
key.Position(), "%v cannot represent a constant integer",
key)
}
switc.Cases[index] = cas
switc.CaseMap[keyInt] = cas
switc.CaseOrder[index] = keyInt
}
if switc.Default != nil {
this.pushScope(switc.Default)
expression, err := this.analyzeExpression (
into, mode,
switc.Default.Expression)
this.popScope()
if err != nil { return nil, err }
switc.Default.Expression = expression
}
if into != nil && switc.Default != nil {
return nil, errors.Errorf (
switc.Position(),
"switch must have a default case")
}
switc.Ty = into
return switc, 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) analyzeFor (
into entity.Type,
mode strictness,
loop *entity.For,
) (
entity.Expression,
error,
) {
loop.Ty = into
this.pushScope(loop)
defer this.popScope()
// index
if loop.Index != nil {
index, err := this.analyzeDeclaration (
builtinType("Index"),
strict, loop.Index)
if err != nil { return nil, err }
loop.Index = index.(*entity.Declaration)
}
// element
element, err := this.analyzeDeclaration(nil, strict, loop.Element)
if err != nil { return nil, err }
loop.Element = element.(*entity.Declaration)
// over
over, err := this.analyzeExpression (
&entity.TypeSlice {
Pos: loop.Position(),
Element: element.Type(),
}, weak, loop.Over)
if err != nil { return nil, err }
loop.Over = over
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,
) {
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,
) {
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
}