1085 lines
26 KiB
Go
1085 lines
26 KiB
Go
package analyzer
|
|
|
|
import "fmt"
|
|
import "git.tebibyte.media/fspl/fspl/errors"
|
|
import "git.tebibyte.media/fspl/fspl/entity"
|
|
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
|
|
|
// 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) analyzeConstant (
|
|
into entity.Type,
|
|
mode strictness,
|
|
constant *entity.Constant,
|
|
) (
|
|
entity.Expression,
|
|
error,
|
|
) {
|
|
// get typedef
|
|
unit, err := this.resolveNickname(constant.Position(), constant.UnitNickname)
|
|
if err != nil { return nil, err }
|
|
typedef, err := this.analyzeTypedef(constant.Position(), entity.Key {
|
|
Unit: unit,
|
|
Name: constant.TypeName,
|
|
}, false) // TODO perhaps we should accept incomplete ones?
|
|
if err != nil { return nil, err }
|
|
constant.Unit = typedef.Unit()
|
|
constant.Ty = into
|
|
|
|
// check access permissions
|
|
if typedef.Acc == entity.AccessPrivate && typedef.Unit() != this.unit {
|
|
return nil, errors.Errorf (
|
|
constant.Position(), "type %v::%v is private",
|
|
constant.UnitNickname, constant.TypeName)
|
|
}
|
|
|
|
// get declaration
|
|
declaration, ok := typedef.ConstantMap[constant.Name]
|
|
if !ok {
|
|
return nil, errors.Errorf (
|
|
constant.Position(), "no constant %v",
|
|
constant)
|
|
}
|
|
constant.Declaration = declaration
|
|
|
|
err = this.canAssign(constant.Position(), into, mode, declaration.Type())
|
|
if err != nil { return nil, err }
|
|
|
|
return constant, 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,
|
|
) {
|
|
if fsplParser.IsReserved(declaration.Name) {
|
|
return nil, errors.Errorf (
|
|
declaration.Position(),
|
|
"cannot shadow reserved identifier %s",
|
|
declaration.Name)
|
|
}
|
|
|
|
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 }
|
|
// TODO: only allow constants to be referenced as immutable data
|
|
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 {
|
|
return nil, errors.Errorf (
|
|
match.Value.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())
|
|
wordOk := isWord(value.Type())
|
|
if !integerOk && !wordOk {
|
|
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:
|
|
if !integerOk {
|
|
return nil, errors.Errorf (
|
|
key.Position(),
|
|
"cannot use string literal when " +
|
|
"switching on %v",
|
|
value.Type())
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if previous, exists := switc.CaseMap[keyInt]; exists {
|
|
return nil, errors.Errorf (
|
|
cas.Key.Position(),
|
|
"%v already listed in match at %v",
|
|
keyInt, previous.Position())
|
|
}
|
|
|
|
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,
|
|
) {
|
|
declaration, _ := this.topDeclaration()
|
|
var ty entity.Type
|
|
switch declaration := declaration.(type) {
|
|
case *entity.Function:
|
|
ret.Declaration = declaration
|
|
ty = declaration.Signature.Return
|
|
case *entity.Method:
|
|
ret.Declaration = declaration
|
|
ty = declaration.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
|
|
}
|