private void CheckInitializer(ValidationState state, Instance.IInstance parent, IEnumerable <Declaration> declarations, Dictionary <AST.ConstantDeclaration, Instance.IInstance> constParentMap) { foreach (var item in declarations) { if (item is AST.ConstantDeclaration cdecl) { var dependson = new HashSet <AST.ConstantDeclaration>(); var visited = new HashSet <AST.ConstantDeclaration>(); CheckInitializer(state, parent, cdecl.Expression, dependson); // Repeat lookup while (dependson.Count != 0) { if (dependson.Contains(cdecl)) { throw new ParserException($"Cannot have {(visited.Count == 0 ? "self" : "circular")}-refrence in a constant initializer", cdecl); } var work = dependson.Where(x => !visited.Contains(x)).ToList(); dependson = new HashSet <ConstantDeclaration>(); foreach (var nc in work) { CheckInitializer(state, constParentMap[nc], nc.Expression, dependson); visited.Add(nc); } } } else if (item is AST.VariableDeclaration vdecl) { if (vdecl.Initializer != null) { var visited = new HashSet <AST.ConstantDeclaration>(); CheckInitializer(state, parent, vdecl.Initializer, visited); } } else if (item is AST.BusDeclaration bus) { foreach (var signal in bus.Signals) { if (signal.Initializer != null) { var visited = new HashSet <AST.ConstantDeclaration>(); CheckInitializer(state, parent, signal.Initializer, visited); } } } } }
private void CheckInitializer(ValidationState state, Instance.IInstance parent, Expression expr, HashSet <AST.ConstantDeclaration> visited) { if (expr is LiteralExpression) { return; } else if (expr is NameExpression ne) { var scope = state.LocalScopes[parent]; var s = state.FindSymbol(ne.Name, scope); if (s is Instance.Literal || s is Instance.EnumFieldReference) { return; } if (s is Instance.ConstantReference cref) { visited.Add(cref.Source); return; } throw new ParserException($"Symbol {ne.Name.AsString} resolves to {s?.GetType()} but must be either a constant or a literal", ne); } else if (expr is TypeCast te) { CheckInitializer(state, parent, te.Expression, visited); } else if (expr is UnaryExpression ue) { CheckInitializer(state, parent, ue.Expression, visited); } else if (expr is BinaryExpression be) { CheckInitializer(state, parent, be.Left, visited); CheckInitializer(state, parent, be.Right, visited); } }
/// <summary> /// Creates a new <see ref="ParentVisitor" /> /// </summary> /// <param name="parent">The parent item</param> /// <param name="self">The item</param> public ParentVisitor(ParentVisitor parent, Instance.IInstance self) { Parent = parent; Self = self ?? throw new ArgumentNullException(nameof(self)); }
/// <summary> /// Performs the type assignment to a process instance /// </summary> /// <param name="state">The validation state to use</param> /// <param name="instance">The process instance to use</param> private static void AssignProcessTypes(ValidationState state, Instance.IInstance parent, AST.Statement[] statements, Dictionary <Expression, DataType> assignedTypes) { // Get the scope for the intance var defaultScope = state.LocalScopes[parent]; // Extra expression that needs examining var extras = new AST.Expression[0].AsEnumerable(); if (parent is Instance.IDeclarationContainer pdecl1) { extras = extras.Concat( pdecl1.Declarations // Functions are handled elsewhere and have their own scopes .Where(x => !(x is AST.FunctionDefinition)) .SelectMany( x => x.All().OfType <AST.Expression>().Select(y => y.Current) ) ); } if (parent is Instance.IParameterizedInstance pp) { extras = extras.Concat( pp.MappedParameters .Select(x => x.MappedItem) .OfType <Instance.Bus>() .SelectMany(x => x.Instances .OfType <Instance.Signal>() .Select(y => y.Source.Initializer) .Where(y => y != null) ) ); } if (parent is Instance.IChildContainer ck) { extras = extras.Concat( ck.Instances .OfType <Instance.Bus>() .SelectMany(x => x.Instances .OfType <Instance.Signal>() .Select(y => y.Source.Initializer) .Where(y => y != null) ) ); } // List of statement expressions to examine for literal/constant type items var allExpressions = statements .All() .OfType <AST.Expression>() .Select(x => new { Item = x.Current, Scope = state.TryFindScopeForItem(x) ?? defaultScope }) .Concat(extras.Select(x => new { Item = x, Scope = defaultScope })) .Concat( extras .SelectMany(x => x.All().OfType <AST.Expression>().Select(y => y.Current)) .Select(x => new { Item = x, Scope = defaultScope }) ) .ToArray() .AsEnumerable(); // We use multiple iterations to assign types // The first iteration assigns types to all literal, bus, signal and variable expressions foreach (var nn in allExpressions) { var item = nn.Item; var scope = nn.Scope; // Skip duplicate assignments if (assignedTypes.ContainsKey(item)) { continue; } if (item is AST.LiteralExpression literal) { if (literal.Value is AST.BooleanConstant) { assignedTypes[literal] = new AST.DataType(literal.SourceToken, ILType.Bool, 1); } else if (literal.Value is AST.IntegerConstant) { assignedTypes[literal] = new AST.DataType(literal.SourceToken, ILType.SignedInteger, -1); } else if (literal.Value is AST.FloatingConstant) { assignedTypes[literal] = new AST.DataType(literal.SourceToken, ILType.Float, -1); } } else if (item is AST.NameExpression name) { var symbol = state.FindSymbol(name.Name, scope); var dt = FindDataType(state, name, scope); if (dt != null) { if (name.Name.Index.LastOrDefault() != null && dt.IsArray) { assignedTypes[name] = dt.ElementType; } else { assignedTypes[name] = dt; } if (parent is Instance.IParameterizedInstance ip) { state.RegisterItemUsageDirection(ip, symbol, ItemUsageDirection.Read, item); } } } } // Handle variables not used in normal expressions foreach (var item in statements.All().Select(x => x.Current)) { var scope = defaultScope; if (item is AST.AssignmentStatement assignmentStatement) { var symbol = state.FindSymbol(assignmentStatement.Name, scope); if (symbol is Instance.Variable var) { if (var.ResolvedType == null) { var.ResolvedType = state.ResolveTypeName(var.Source.Type, scope); } } else if (symbol is Instance.Signal sig) { if (sig.ResolvedType == null) { sig.ResolvedType = state.ResolveTypeName(sig.Source.Type, scope); } } else if (symbol == null) { throw new ParserException($"Symbol not found: \"{assignmentStatement.Name.AsString}\"", assignmentStatement.Name.SourceToken); } else { throw new ParserException($"Can only assign to signal or variable, {assignmentStatement.Name.AsString} is {symbol.GetType().Name}", assignmentStatement.Name.SourceToken); } } else if (item is AST.ForStatement forStatement) { var forScope = state.LocalScopes[forStatement]; var symbol = state.FindSymbol(forStatement.Variable.Name, forScope); if (symbol is Instance.Variable var) { if (var.ResolvedType == null) { var.ResolvedType = state.ResolveTypeName(var.Source.Type, scope); } } else if (symbol == null) { throw new ParserException($"Symbol not found: \"{forStatement.Variable.Name}\"", forStatement.Variable.SourceToken); } else { throw new ParserException($"Can only use variable as the counter in a for loop, {forStatement.Variable.Name} is {symbol.GetType().Name}", forStatement.Variable.SourceToken); } } } allExpressions = statements .All(AST.TraverseOrder.DepthFirstPostOrder) .OfType <AST.Expression>() .Select(x => new { Item = x.Current, Scope = state.TryFindScopeForItem(x) ?? defaultScope }) .Concat( extras .SelectMany(x => x.All(AST.TraverseOrder.DepthFirstPostOrder).OfType <AST.Expression>().Select(y => y.Current)) .Select(x => new { Item = x, Scope = defaultScope }) ) .Concat(extras.Select(x => new { Item = x, Scope = defaultScope })); // We are only concerned with expressions, working from leafs and up // At this point all literals, variables, signals, etc. should have a resolved type foreach (var nn in allExpressions) { var item = nn.Item; var scope = nn.Scope; // Skip duplicate assignments if (assignedTypes.ContainsKey(item)) { continue; } if (item is AST.UnaryExpression unaryExpression) { var sourceType = assignedTypes[unaryExpression.Expression]; switch (unaryExpression.Operation.Operation) { case AST.UnaryOperation.UnOp.LogicalNegation: if (!sourceType.IsBoolean) { throw new ParserException($"Cannot perform {unaryExpression.Operation.Operation} on {sourceType}", unaryExpression); } break; case AST.UnaryOperation.UnOp.Identity: case AST.UnaryOperation.UnOp.Negation: if (!sourceType.IsNumeric) { throw new ParserException($"Cannot perform {unaryExpression.Operation.Operation} on {sourceType}", unaryExpression); } break; case AST.UnaryOperation.UnOp.BitwiseInvert: if (!sourceType.IsInteger) { throw new ParserException($"Cannot perform {unaryExpression.Operation.Operation} on {sourceType}", unaryExpression); } break; default: throw new ParserException($"Unsupported unary operation: {unaryExpression.Operation.Operation}", unaryExpression); } // Unary operations do not change the type assignedTypes[item] = sourceType; } else if (item is AST.BinaryExpression binaryExpression) { var leftType = assignedTypes[binaryExpression.Left]; var rightType = assignedTypes[binaryExpression.Right]; // If we have a numerical operation, verify that the operands are numeric if (binaryExpression.Operation.IsNumericOperation) { if (!leftType.IsNumeric) { throw new ParserException($"The operand {binaryExpression.Left} must be numerical to be used with {binaryExpression.Operation.Operation}", binaryExpression.Left); } if (!rightType.IsNumeric) { throw new ParserException($"The operand {binaryExpression.Right} must be numerical to be used with {binaryExpression.Operation.Operation}", binaryExpression.Right); } } // If we have a logical operation, verify that the operands are boolean if (binaryExpression.Operation.IsLogicalOperation) { if (!leftType.IsBoolean) { throw new ParserException($"The operand {binaryExpression.Left} must be boolean to be used with {binaryExpression.Operation.Operation}", binaryExpression.Left); } if (!rightType.IsBoolean) { throw new ParserException($"The operand {binaryExpression.Right} must be boolean to be used with {binaryExpression.Operation.Operation}", binaryExpression.Right); } } // If we are doing a compare operation, verify that the types can be compared if (binaryExpression.Operation.IsEqualityOperation) { if (!state.CanEqualityCompare(leftType, rightType, scope)) { throw new ParserException($"Cannot perform boolean operation {binaryExpression.Operation.Operation} on types {leftType} and {rightType}", binaryExpression); } } // Special handling of bitshift, where the type of the shift count does not change they type on the input if (binaryExpression.Operation.Operation == BinOp.ShiftLeft || binaryExpression.Operation.Operation == BinOp.ShiftRight) { if (!leftType.IsInteger) { throw new ParserException($"The value being shifted must be an integer type but has type {leftType}", binaryExpression.Left); } if (!rightType.IsInteger) { throw new ParserException($"The shift operand must be an integer type but has type {rightType}", binaryExpression.Right); } assignedTypes[binaryExpression] = leftType; } else { // Make sure we can unify the types if (!state.CanUnifyTypes(leftType, rightType, scope)) { throw new ParserException($"The types types {leftType} and {rightType} cannot be unified for use with the operation {binaryExpression.Operation.Operation}", binaryExpression); } // Compute the unified type var unified = state.UnifiedType(leftType, rightType, scope); // If the source operands do not have the unified types, inject an implicit type-cast if (!object.Equals(leftType, unified)) { assignedTypes[binaryExpression.Left = new AST.TypeCast(binaryExpression.Left, unified, false)] = unified; } if (!object.Equals(rightType, unified)) { assignedTypes[binaryExpression.Right = new AST.TypeCast(binaryExpression.Right, unified, false)] = unified; } // Assign the type to this operation switch (binaryExpression.Operation.Operation) { // These operations just use the unified type case BinOp.Add: case BinOp.Subtract: case BinOp.Multiply: case BinOp.Divide: case BinOp.Modulo: case BinOp.BitwiseAnd: case BinOp.BitwiseOr: case BinOp.BitwiseXor: assignedTypes[binaryExpression] = unified; break; // These operations return a boolean result case BinOp.Equal: case BinOp.NotEqual: case BinOp.LessThan: case BinOp.LessThanOrEqual: case BinOp.GreaterThan: case BinOp.GreaterThanOrEqual: case BinOp.LogicalAnd: case BinOp.LogicalOr: assignedTypes[binaryExpression] = new AST.DataType(binaryExpression.SourceToken, ILType.Bool, 1); break; default: throw new ParserException($"Unable to handle operation: {binaryExpression.Operation.Operation}", binaryExpression); } } } else if (item is AST.TypeCast typecastExpression) { // Implicit typecasts are made by the parser so we do not validate those if (!typecastExpression.Explicit) { continue; } var sourceType = assignedTypes[typecastExpression.Expression]; var targetType = state.ResolveTypeName(typecastExpression.TargetName, scope); if (!state.CanTypeCast(sourceType, targetType, scope)) { throw new ParserException($"Cannot cast from {sourceType} to {typecastExpression.TargetName}", typecastExpression); } assignedTypes[typecastExpression] = targetType; } // Carry parenthesis expression types else if (item is AST.ParenthesizedExpression parenthesizedExpression) { assignedTypes[item] = assignedTypes[parenthesizedExpression.Expression]; } } // Then make sure we have assigned all targets foreach (var item in statements.All().OfType <AST.Statement>().Select(x => x.Current)) { var scope = defaultScope; if (item is AST.AssignmentStatement assignmentStatement) { var symbol = state.FindSymbol(assignmentStatement.Name, scope); var exprType = assignedTypes[assignmentStatement.Value]; DataType targetType; if (symbol is Instance.Variable variableInstance) { targetType = state.ResolveTypeName(variableInstance.Source.Type, scope); } else if (symbol is Instance.Signal signalInstance) { targetType = state.ResolveTypeName(signalInstance.Source.Type, scope); } else { throw new ParserException($"Assignment must be to a variable or a signal", item); } if (targetType.IsArray && assignmentStatement.Name.Index?.LastOrDefault() != null) { targetType = targetType.ElementType; } if (!state.CanUnifyTypes(targetType, exprType, scope)) { throw new ParserException($"Cannot assign \"{assignmentStatement.Value.SourceToken.Text}\" (with type {exprType}) to {assignmentStatement.Name.SourceToken} (with type {targetType})", item); } //var unified = state.UnifiedType(targetType, exprType, scope); // Force the right-hand side to be the type we are assigning to if (!object.Equals(exprType, targetType)) { // Make sure we do not loose bits with implicit typecasting if (exprType.BitWidth > targetType.BitWidth && targetType.BitWidth > 0) { throw new ParserException($"Assignment would loose precision from {exprType.BitWidth} bits to {targetType.BitWidth}", item); } assignedTypes[assignmentStatement.Value = new AST.TypeCast(assignmentStatement.Value, targetType, false)] = targetType; } if (parent is Instance.IParameterizedInstance ip) { state.RegisterItemUsageDirection(ip, symbol, ItemUsageDirection.Write, item); } } else if (item is AST.ForStatement forStatement) { var fromType = assignedTypes[forStatement.FromExpression]; var toType = assignedTypes[forStatement.ToExpression]; if (!fromType.IsInteger) { throw new ParserException("The from/to arguments in a for loop must be integer types", forStatement.FromExpression); } if (!toType.IsInteger) { throw new ParserException("The from/to arguments in a for loop must be integer types", forStatement.ToExpression); } var inttype = new DataType(forStatement.Variable.Name.SourceToken, ILType.SignedInteger, -1); if (fromType.BitWidth != -1) { assignedTypes[forStatement.FromExpression = new AST.TypeCast(forStatement.FromExpression, inttype, false)] = inttype; } if (toType.BitWidth != -1) { assignedTypes[forStatement.ToExpression = new AST.TypeCast(forStatement.ToExpression, inttype, false)] = inttype; } } } }