diff --git a/analyzer/assignment.go b/analyzer/assignment.go index 05fe2bc..7e0718a 100644 --- a/analyzer/assignment.go +++ b/analyzer/assignment.go @@ -381,7 +381,17 @@ func (this *Tree) isLocationExpression (expression entity.Expression) error { case *entity.MemberAccess: return this.isLocationExpression(expression.Source) default: - return errors.Errorf(pos, "cannot assign to %s", expression.Description()) + return errors.Errorf(expression.Position(), "cannot assign to %s", expression.Description()) + } +} + +// isConstant returns whether or not an expression can be evaluated at compile +// time. +func (this *Tree) isConstant (expression entity.Expression) error { + if expression.IsConstant() { + return nil + } else { + return errors.Errorf(expression.Position(), "can not use %s as constant", expression.Description()) } } diff --git a/analyzer/constant_test.go b/analyzer/constant_test.go index 0b59f6d..d5d8392 100644 --- a/analyzer/constant_test.go +++ b/analyzer/constant_test.go @@ -73,7 +73,7 @@ Weekday: String // name? func TestErrConstantStringUnspecified (test *testing.T) { testStringErr (test, -"cannot fill in constant values for non-numeric type Weekday", 5, 11 +"cannot fill in constant values for non-numeric type Weekday", 5, 11, ` Weekday: String | sunday diff --git a/analyzer/expression.go b/analyzer/expression.go index 1c34061..2b4eeaf 100644 --- a/analyzer/expression.go +++ b/analyzer/expression.go @@ -363,6 +363,7 @@ func (this *Tree) analyzeReference ( 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 @@ -1015,13 +1016,15 @@ func (this *Tree) analyzeReturn ( entity.Expression, error, ) { - ret.Declaration, _ = this.topDeclaration() + declaration, _ := this.topDeclaration() var ty entity.Type - switch ret.Declaration.(type) { + switch declaration := declaration.(type) { case *entity.Function: - ty = ret.Declaration.(*entity.Function).Signature.Return + ret.Declaration = declaration + ty = declaration.Signature.Return case *entity.Method: - ty = ret.Declaration.(*entity.Method).Signature.Return + ret.Declaration = declaration + ty = declaration.Signature.Return } if ty != nil && ret.Value == nil { diff --git a/analyzer/scope.go b/analyzer/scope.go index 374b3c1..a5e58ef 100644 --- a/analyzer/scope.go +++ b/analyzer/scope.go @@ -7,7 +7,7 @@ import "git.tebibyte.media/fspl/fspl/entity" // entity causes the analysis of another. type scopeContextManager []scopeContext -func (this *scopeContextManager) pushScopeContext (declaration entity.TopLevel) { +func (this *scopeContextManager) pushScopeContext (declaration any) { *this = append(*this, scopeContext { declaration: declaration }) } @@ -31,7 +31,7 @@ func (this *scopeContextManager) topLoop () (entity.Breakable, bool) { return (*this)[len(*this) - 1].topLoop() } -func (this *scopeContextManager) topDeclaration () (entity.TopLevel, bool) { +func (this *scopeContextManager) topDeclaration () (any, bool) { if len(*this) < 1 { return nil, false } return (*this)[len(*this) - 1].declaration, true } @@ -72,7 +72,7 @@ func (this *scopeContextManager) assertPopulated () { type scopeContext struct { scopes []entity.Scoped loops []entity.Breakable - declaration entity.TopLevel + declaration any // TODO rename to "origin" or smth } func (this *scopeContext) pushLoop (loop entity.Breakable) { diff --git a/analyzer/type.go b/analyzer/type.go index 422c373..0269983 100644 --- a/analyzer/type.go +++ b/analyzer/type.go @@ -33,22 +33,81 @@ func (this *Tree) analyzeTypedef ( } } - // analyze if it still needs to be analyzed - if definition, exists := this.rawTypes[key]; exists { - // update unit - definition.Unt = key.Unit - - // analyze - var err error - definition.Type, err = this.analyzeTypeInternal ( - definition.Type, definition, false) - return definition, err + // check if it is even real + definition, exists := this.rawTypes[key] + if !exists { + return nil, errors.Errorf(pos, "no type named %s", key.Name) } + + // update unit + definition.Unt = key.Unit - // if we couldn't get the type, error - return nil, errors.Errorf(pos, "no type named %s", key.Name) + // analyze + var err error + definition.Type, err = this.analyzeTypeInternal ( + definition.Type, definition, false) + + // assemble constant map + definition, err = this.assembleTypedefConstantMap(definition) + if err != nil { return nil, err } + + // analyze constants + fillerValue := 0 + for index, constant := range definition.Constants { + constant, err := this.analyzeConstantDeclaration(constant, definition.Type) + if err != nil { return nil, err } + if constant.Value == nil { + if !isNumeric(definition.Type) { + return nil, errors.Errorf ( + pos, "cannot fill in constant values for non-numeric type %v", + definition.Type) + } + constant.Value = &entity.LiteralInt { + Pos: pos, + Value: fillerValue, + Ty: definition.Type, + } + fillerValue ++ + } + + definition.ConstantMap[constant.Name] = constant + definition.ConstantOrder[index] = constant.Name + definition.Constants[index] = constant + } + + return definition, err } +func (this *Tree) analyzeConstantDeclaration ( + constant *entity.ConstantDeclaration, + into entity.Type, +) ( + *entity.ConstantDeclaration, + error, +) { + constant.Ty = into + + // analyze the value, if given + if constant.Value != nil { + // create new scope context for this constant + this.pushScopeContext(constant) + this.pushScope(constant) + defer this.popScopeContext() + defer this.popScope() + + // analyze value + value, err := this.analyzeExpression(into, strict, constant.Value) + if err != nil { return nil, err } + + // only allow values that can be used as constants + err = this.isConstant(value) + if err != nil { return nil, err } + } + + // TODO after analysis, check if constant + return constant, nil +} + func (this *Tree) analyzeType ( ty entity.Type, acceptIncomplete bool, @@ -303,3 +362,24 @@ func (this *Tree) analyzeBehavior (behavior *entity.Signature) (*entity.Signatur behavior.Return, err = this.analyzeType(behavior.Return, false) return behavior, err } + +func (this *Tree) assembleTypedefConstantMap ( + typedef *entity.Typedef, +) ( + *entity.Typedef, + error, +) { + typedef.ConstantMap = make(map[string] *entity.ConstantDeclaration) + typedef.ConstantOrder = make([]string, len(typedef.Constants)) + for index, constant := range typedef.Constants { + if previous, exists := typedef.ConstantMap[constant.Name]; exists { + return nil, errors.Errorf ( + constant.Position(), "%s already defined at %v", + constant.Name, previous.Position()) + } + typedef.ConstantMap [constant.Name] = constant + typedef.ConstantOrder[index] = constant.Name + typedef.Constants [index] = constant + } + return typedef, nil +} diff --git a/entity/toplevel.go b/entity/toplevel.go index adc39fe..4e40fb5 100644 --- a/entity/toplevel.go +++ b/entity/toplevel.go @@ -31,9 +31,10 @@ type Typedef struct { Constants []*ConstantDeclaration // Semantics - Unt uuid.UUID - Methods map[string] *Method - ConstantsMap map[string] *ConstantDeclaration + Unt uuid.UUID + Methods map[string] *Method + ConstantOrder []string + ConstantMap map[string] *ConstantDeclaration } func (*Typedef) topLevel(){} func (this *Typedef) Position () errors.Position { return this.Pos }