/// <summary> /// Validates the current state /// </summary> /// <param name="state">The state to validate</param> public void Validate(ValidationState state) { foreach (var item in state.Modules.Values.SelectMany(x => x.All())) { if (item.Current is AST.Module m) { CheckDeclarations(m.Declarations); } else if (item.Current is AST.Network n) { CheckDeclarations(n.Declarations); } else if (item.Current is AST.Process p) { CheckDeclarations(p.Declarations); } else if (item.Current is AST.FunctionDefinition f) { CheckDeclarations(f.Declarations); } else if (item.Current is AST.TypeDefinition t) { CheckIdentifier(t.Name); } } }
/// <summary> /// Returns all output signals for a process or connection /// </summary> /// <param name="state">The state object</param> /// <returns>The output signals</returns> public IEnumerable <Instance.Signal> OutputSignals(ValidationState state, Instance.Process process) { return(process.MappedParameters .Where(x => x.MappedItem is Instance.Bus) .SelectMany(x => { var isOut = x.MatchedParameter.Direction == AST.ParameterDirection.Out; var parentBus = x.MappedItem as Instance.Bus; return parentBus .Instances .OfType <Instance.Signal>() .Where(y => y.Source.Direction == SignalDirection.Normal ? isOut : !isOut ); }) .Concat( process.Instances .OfType <Instance.Bus>() .SelectMany(x => x.Instances.OfType <Instance.Signal>()) .Where(x => state.ItemDirection[process].ContainsKey(x) && state.ItemDirection[process][x] == ItemUsageDirection.Write ) ) .Distinct()); }
/// <summary> /// Validates the module /// </summary> /// <param name="state">The validation state</param> public void Validate(ValidationState state) { // Start the process with the top-level module, // and capture the existing instances as well var inst = state.TopLevel.ModuleInstance.Instances; state.TopLevel.ModuleInstance = CreateAndRegisterInstance(state, state.TopLevel.Module); state.TopLevel.ModuleInstance.Instances.AddRange(inst); }
/// <summary> /// Creates all sub-instances for a network /// </summary> /// <param name="state">The state to use</param> /// <param name="network">The parent network instance</param> private Instance.Network CreateAndRegisterInstance(ValidationState state, AST.InstanceDeclaration instDecl, AST.Network network) { // Create the network instance var netinstance = new Instance.Network(instDecl, network); // We have registered the network by this name already //state.TryAddSymbol(netinstance.Name, netinstance); using (state.StartScope(network, netinstance)) CreateAndRegisterInstances(state, netinstance.NetworkDefinition.Declarations, netinstance.Instances); return(netinstance); }
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); } } } } }
/// <summary> /// Creates all sub-instances for a module /// </summary> /// <param name="state">The state to use</param> /// <param name="module">The parent module</param> /// <returns></returns> private Instance.Module CreateAndRegisterInstance(ValidationState state, AST.Module module) { var modinstance = new Instance.Module(module); var parentCollection = modinstance.Instances; using (var scope = state.StartScope(module, modinstance)) { // TODO: Create module instances here foreach (var imp in module.Imports) { } foreach (var decl in module.Declarations) { if (decl is AST.ConstantDeclaration cdecl) { var cref = new Instance.ConstantReference(cdecl); scope.TryAddSymbol(cref.Name, cref, cdecl.Name); parentCollection.Add(cref); } else if (decl is AST.EnumDeclaration edecl) { var e = new Instance.EnumTypeReference(edecl); scope.TryAddSymbol(e.Name, e, edecl.Name); using (state.StartScope(e)) CreateAndRegisterInstance(state, e); parentCollection.Add(e); } else if (decl is AST.FunctionDefinition fdecl) { scope.TryAddSymbol(fdecl.Name.Name, fdecl, fdecl.Name); } } // The entry-level network is not user-instantiated if (module == state.TopLevel.Module) { modinstance.Instances.Add( state.TopLevel.NetworkInstance = CreateAndRegisterInstance(state, state.TopLevel.NetworkDeclaration, state.TopLevel.SourceNetwork) ); } } return(modinstance); }
/// <summary> /// The instances /// </summary> /// <param name="state">The validation state</param> /// <param name="instances">The instances to assign types to</param> /// <param name="scope">The scope to use for lookup</param> private void ResolveTypes(ValidationState state, IEnumerable <Instance.IInstance> instances, ScopeState scope) { foreach (var r in instances) { if (r is Instance.ConstantReference cref) { if (cref.ResolvedType == null) { cref.ResolvedType = state.ResolveTypeName(cref.Source.DataType, scope); } } else if (r is Instance.Variable v) { if (v.ResolvedType == null) { v.ResolvedType = state.ResolveTypeName(v.Source.Type, scope); } } } }
/// <summary> /// Recursively loads import statements and registers them in the symbol tables /// </summary> /// <param name="sourcepath"></param> /// <param name="state"></param> /// <param name="module"></param> private static void LoadImports(string sourcepath, Validation.ValidationState state, AST.Module module) { var scope = state.CurrentScope; foreach (var imp in module.Imports) { var p = GetModulePath(sourcepath, imp.ModuleName); if (!state.Modules.ContainsKey(p)) { var m = LoadModule(p); using (var sc = state.StartScope(m)) { // Recursively load the modules LoadImports(p, state, m); // Register that we have now loaded this module state.Modules.Add(p, m); // Register symbols in this scope state.RegisterSymbols(m, sc); } } // Inject imported names into the symbol tables if (imp.SourceNames == null) { // Import the entire module as scope.TryAddSymbol(imp.LocalName.Name, state.Modules[p], imp.LocalName); } else { // Import only the requested names, but import them without the module name foreach (var n in imp.SourceNames) { scope.SymbolTable[n.Name] = state.FindSymbol(n, state.LocalScopes[state.Modules[p]]); } } } }
public void Validate(ValidationState state) { var constParentMap = state.AllInstances .OfType <Instance.IDeclarationContainer>() .SelectMany(x => x.Declarations .OfType <AST.ConstantDeclaration>() .Select(y => new { Constant = y, Parent = (Instance.IInstance)x }) ) .GroupBy(x => x.Constant) .ToDictionary( x => x.Key, x => x.First().Parent ); foreach (var e in state.AllInstances.OfType <Instance.IDeclarationContainer>()) { CheckInitializer(state, e, e.Declarations, constParentMap); } }
/// <summary> /// Creates all enum fields for an enum /// </summary> /// <param name="state">The state to use</param> /// <param name="parent">The enum instance</param> private void CreateAndRegisterInstance(ValidationState state, Instance.EnumTypeReference parent) { var scope = state.CurrentScope; var cur = 0; foreach (var field in parent.Source.Fields) { var ix = field.Value; if (ix < 0) { ix = cur; } var s = new Instance.EnumFieldReference(parent, field, ix); parent.Fields.Add(field.Name.Name, s); scope.TryAddSymbol(field.Name.Name, s, field.Name); parent.Instances.Add(s); cur = ix + 1; } }
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> /// Loads the module and its imports /// </summary> /// <param name="file">The main module</param> /// <param name="toplevel">The top-level network or null</param> /// <returns>The state for the loaded modules</returns> public static Validation.ValidationState LoadModuleAndImports(string file, string toplevel, string[] arguments) { // Basic setup var state = new Validation.ValidationState(); var rootscope = state.CurrentScope; var toResolve = new Stack <AST.Module>(); state.TopLevel.Module = LoadModule(file); state.TopLevel.ModuleInstance = new Instance.Module(state.TopLevel.Module); // Find the entry network var networks = state.TopLevel.Module.Entities.OfType <AST.Network>().ToList(); if (string.IsNullOrWhiteSpace(toplevel)) { if (networks.Count == 0) { throw new ArgumentException("The main module contains no networks?"); } if (networks.Count != 1) { throw new ArgumentException($"The main module contains {networks.Count} networks, please specify a network name"); } state.TopLevel.SourceNetwork = networks.First(); } else { var namednetworks = networks.Where(x => string.Equals(x.Name.Name, toplevel, StringComparison.OrdinalIgnoreCase)).ToList(); if (networks.Count == 0) { throw new ArgumentException($"The main module contains no networks named \"{toplevel}\""); } if (networks.Count != 1) { throw new ArgumentException($"The main module contains {networks.Count} network named \"{toplevel}\""); } state.TopLevel.SourceNetwork = namednetworks.First(); } // Wire up the top-level network parameters var dummyparsetoken = new ParseToken(0, 0, 0, "__commandline__"); var name = new AST.Identifier(new ParseToken(0, 0, 0, "__main__")); state.TopLevel.CommandlineArguments = arguments = arguments ?? new string[0]; state.Modules[file] = state.TopLevel.Module; state.LocalScopes[state.TopLevel.Module] = rootscope; // Recursively load and resolve imports LoadImports(file, state, state.TopLevel.Module); // Register the symbols from the main module in the root scope state.RegisterSymbols(state.TopLevel.Module, rootscope); // Check that all parameters in the top-level network are explicitly typed var untypedparam = state.TopLevel.SourceNetwork.Parameters.FirstOrDefault(x => x.ExplictType == null); if (untypedparam != null) { throw new ParserException("All parameters to the top-level network must have an explict type", untypedparam); } // Prepare for the parameters to the top-level network var pmap = new AST.ParameterMap[state.TopLevel.SourceNetwork.Parameters.Length]; var externalargumentindex = 0; for (var i = 0; i < pmap.Length; i++) { var p = state.TopLevel.SourceNetwork.Parameters[i]; var realtype = state.ResolveTypeName(p.ExplictType, rootscope); if (realtype == null) { throw new ParserException($"Unable to find type: {p.ExplictType.SourceToken.Text}", p); } if (realtype.IsValue) { if (p.Direction == AST.ParameterDirection.Out) { throw new ParserException($"A value-type parameter cannot be sent as output: {p.Name}", p); } if (externalargumentindex >= state.TopLevel.CommandlineArguments.Length) { throw new ParserException($"No value provided for the parameter {p.Name} in the commandline inputs", p); } var argtext = state.TopLevel.CommandlineArguments[externalargumentindex++]; var literal = ParseAsLiteral(argtext); var littype = new AST.DataType(new ParseToken(0, 0, 0, argtext), literal.Value.Type, -1); if (!state.CanTypeCast(littype, realtype, rootscope)) { throw new ParserException($"Parsed {argtext} to {littype} but cannot interpret as {realtype} which is required for parameter {p.Name}", p); } pmap[i] = new AST.ParameterMap(p.SourceToken, p.Name, literal); rootscope.TryAddSymbol(p.Name.Name, literal, p.Name); } else if (realtype.IsBus) { var typedef = (AST.TypeDefinition)state.FindTypeDefinition(p.ExplictType.Alias, rootscope); // Create a new bus as a stand-in for the input or output var newbus = new Instance.Bus( new AST.BusDeclaration( dummyparsetoken, p.Name, realtype .Shape .Signals .Select(x => new AST.BusSignalDeclaration( dummyparsetoken, new AST.Identifier(new ParseToken(0, 0, 0, x.Key)), x.Value.Type, typedef.Initializers[x.Key], x.Value.Direction ) ).ToArray(), null ) ); newbus.Instances.AddRange( newbus .Source .Signals .Select(x => new Instance.Signal(newbus, x) { ResolvedType = state.ResolveTypeName(x.Type, rootscope) } ) ); newbus.ResolvedSignalTypes = newbus .Instances .OfType <Instance.Signal>() .ToDictionary(x => x.Name, x => x.ResolvedType); if (p.Direction == AST.ParameterDirection.Out) { state.TopLevel.OutputBusses.Add(newbus); } else if (p.Direction == AST.ParameterDirection.In) { state.TopLevel.InputBusses.Add(newbus); } else { throw new ParserException($"Cannot use a top-level bus with direction {p.Direction}", p); } pmap[i] = new AST.ParameterMap(p.SourceToken, p.Name, AST.EnumerationExtensions.AsExpression(p.Name)); rootscope.TryAddSymbol(p.Name.Name, newbus, p.Name); // Register signals using (var sc = state.StartScope(newbus)) foreach (var s in newbus.Instances.OfType <Instance.Signal>()) { sc.TryAddSymbol(s.Name, s, s.Source); } state.TopLevel.ModuleInstance.Instances.Add(newbus); } else { throw new ParserException($"Unexpected type: {realtype}", p); } } // Check that we have at least one output bus if (state.TopLevel.OutputBusses.Count == 0) { throw new ParserException("The top-level network must have at least one output bus", state.TopLevel.SourceNetwork); } if (state.TopLevel.CommandlineArguments.Length > externalargumentindex) { throw new ParserException($"Too many arguments on commandline, expected {externalargumentindex} but got {state.TopLevel.CommandlineArguments.Length}", state.TopLevel.SourceNetwork); } state.TopLevel.NetworkDeclaration = new AST.InstanceDeclaration( dummyparsetoken, new AST.InstanceName(dummyparsetoken, name, null), name, pmap ); state.TopLevel.NetworkInstance = new Instance.Network( state.TopLevel.NetworkDeclaration, state.TopLevel.SourceNetwork ); return(state); }
/// <summary> /// Wires up parameters for a parameterized instance /// </summary> /// <param name="state">The validation state</param> /// <param name="sourceinstance">The instance to wire up</param> private void WireUpParameters(ValidationState state, Instance.IParameterizedInstance sourceinstance) { if (sourceinstance.MappedParameters.Count < sourceinstance.SourceParameters.Length) { if (sourceinstance.MappedParameters.Count != 0) { throw new Exception("Unexpected half-filled parameter list"); } var position = 0; var anynamed = false; var map = new Instance.MappedParameter[sourceinstance.SourceParameters.Length]; var scope = state.LocalScopes[sourceinstance]; // Map for getting the parameter index of a name var namelist = sourceinstance .SourceParameters .Zip( Enumerable.Range(0, sourceinstance.SourceParameters.Length), (p, i) => new { i, p.Name.Name } ); var collisions = namelist .GroupBy(x => x.Name) .Where(x => x.Count() != 1) .FirstOrDefault(); if (collisions != null) { throw new ParserException($"Multiple arguments named {collisions.Key}, positions: {string.Join(", ", collisions.Select(x => x.i.ToString())) }", sourceinstance.SourceParameters[collisions.Last().i].Name); } var nameindexmap = namelist.ToDictionary(x => x.Name, x => x.i); foreach (var p in sourceinstance.ParameterMap) { var pos = position; if (p.Name == null) { if (anynamed) { throw new ParserException($"Cannot have positional arguments after named arguments", p); } } else { anynamed = true; if (!nameindexmap.TryGetValue(p.Name.Name, out pos)) { throw new ParserException($"No parameter named {p.Name.Name} in {sourceinstance.SourceName}", sourceinstance.SourceItem); } } if (map[pos] != null) { throw new ParserException($"Double argument for {sourceinstance.SourceParameters[pos].Name.Name} detected", sourceinstance.SourceItem); } // Extract the parameter definition var sourceparam = sourceinstance.SourceParameters[pos]; Instance.IInstance value; var tc = p.Expression as AST.TypeCast; if (tc != null) { value = state.ResolveSymbol(tc.Expression, scope); } else { value = state.ResolveSymbol(p.Expression, scope); } if (value == null) { throw new ParserException("Unable to resolve expression", p.Expression.SourceToken); } var itemtype = state.InstanceType(value); var parametertype = sourceparam.ExplictType == null ? itemtype : state.ResolveTypeName(sourceparam.ExplictType, scope); if (parametertype.IsValue && sourceparam.Direction == AST.ParameterDirection.Out) { throw new ParserException($"Cannot use a value-type parameter as output: {sourceparam.SourceToken}", sourceparam); } // We need to expand both types to intrinsics to remove any type aliases that need lookups var intrinsic_itemtype = state.ResolveToIntrinsics(itemtype, scope); var intrinsic_parametertype = state.ResolveToIntrinsics(parametertype, scope); // If the input is a typecast (and required) we wire it through a process if (tc != null && !state.CanUnifyTypes(intrinsic_itemtype, intrinsic_parametertype, scope)) { var typecast_target = state.ResolveToIntrinsics(state.ResolveTypeName(tc.TargetName, scope), scope); var typecast_source = sourceparam.Direction == ParameterDirection.In ? intrinsic_itemtype : intrinsic_parametertype; var sourceSignals = typecast_source .Shape .Signals .Select(x => x.Key) .ToHashSet(); var shared_shape = typecast_target .Shape .Signals .Where(x => sourceSignals.Contains(x.Key)) .Select(x => new AST.BusSignalDeclaration( p.SourceToken, new AST.Identifier( new ParseToken(0, 0, 0, x.Key) ), x.Value.Type, null, x.Value.Direction )) .ToArray(); if (sourceSignals.Count != shared_shape.Length) { throw new ParserException($"The typecast is invalid as the names do not match", p.SourceToken); } var proc = IdentityHelper.CreateTypeCastProcess( state, scope, p.SourceToken, tc.Expression, new AST.Name(p.SourceToken, new[] { new AST.Identifier(new ParseToken(0, 0, 0, sourceinstance.Name)), sourceparam.Name }, null).AsExpression(), shared_shape, shared_shape ); throw new ParserException($"Typecasts inside process instantiations are not currently supported", p.SourceToken); // using (state.StartScope(proc)) // CreateAndRegisterInstance(state, proc); // parentCollection.Add(proc); } // Check argument compatibility if (!state.CanUnifyTypes(intrinsic_itemtype, intrinsic_parametertype, scope)) { throw new ParserException($"Cannot use {p.Expression.SourceToken} of type {intrinsic_itemtype.ToString()} as the argument for {sourceparam.Name.SourceToken} of type {intrinsic_parametertype}", p.Expression); } // Check that the type we use as input is "larger" than the target var unified = state.UnifiedType(intrinsic_itemtype, parametertype, scope); if (!object.Equals(unified, intrinsic_itemtype)) { throw new ParserException($"Cannot use {p.Expression.SourceToken} of type {intrinsic_itemtype.ToString()} as the argument for {sourceparam.Name.SourceToken} of type {intrinsic_parametertype}", p.Expression); } map[pos] = new Instance.MappedParameter(p, sourceparam, value, parametertype); var localname = map[pos].LocalName; // Register the instance in the local symbol table to allow // refering to the instance with the parameter name scope.TryAddSymbol(localname, value, sourceparam.Name); position++; } if (map.Any(x => x == null)) { throw new ParserException("Argument missing", sourceinstance.SourceItem); } sourceinstance.MappedParameters.AddRange(map); } }
/// <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; } } } }
/// <summary> /// Validates the module /// </summary> /// <param name="state">The validation state</param> public void Validate(ValidationState state) { // Keep a list of unscheduled processes var remainingprocesses = state.AllInstances .OfType <Instance.Process>() .ToList(); // Keep track of wavefronts of processes var roots = new List <List <Instance.Process> >(); // Map all output signals to their writers var writers = remainingprocesses .SelectMany( x => OutputSignals(state, x) .Select(z => new { P = x, S = z }) ) .GroupBy(x => x.S) .ToDictionary(x => x.Key, y => y.Select(x => x.P).ToList()); // The code in SME handles double writes, but FPGA tools generally dislike double writers var doublewrite = writers .Where(x => x.Value.Count > 1) .Select(x => x.Key) .FirstOrDefault(); if (doublewrite != null) { var dblnames = string.Join(Environment.NewLine, writers[doublewrite].Select(x => x.Name)); throw new ParserException($"Multiple writers found for signal {doublewrite.Name}: {Environment.NewLine} {dblnames}", doublewrite.Source); } // Find all signals that are inputs var inputSignals = state .TopLevel .InputBusses .SelectMany( x => x.Instances .OfType <Instance.Signal>() .Where(y => y.Source.Direction == SignalDirection.Normal) ) .Concat( state .TopLevel .OutputBusses .SelectMany( x => x.Instances .OfType <Instance.Signal>() .Where(y => y.Source.Direction == SignalDirection.Inverse) ) ) ; // Register all inputs as having no writers foreach (var s in inputSignals) { writers.Add(s, new List <Instance.Process>()); } // Prepare a list of signals processed by all writers var ready = new HashSet <Instance.Signal>( writers.Where(x => x.Value.Count == 0).Select(x => x.Key) ); // Find signals with no writers var orphansSignal = remainingprocesses .SelectMany( x => InputSignals(state, x) .Where(z => !writers.ContainsKey(z)) ) .FirstOrDefault(); if (orphansSignal != null) { throw new ParserException($"No writers found for signal {orphansSignal.Name}: {orphansSignal.Source.SourceToken}", orphansSignal.Source); } // Find all processes that each process depends on var dependsOn = remainingprocesses .SelectMany( x => InputSignals(state, x) .Select(z => new { P = x, D = writers[z] } ) ) .GroupBy(x => x.P) .ToDictionary(x => x.Key, x => x.SelectMany(y => y.D).ToArray()); // Keep removing processes until all have been scheduled while (remainingprocesses.Count > 0) { var current = new List <Instance.Process>(); // Find all processes where all signals are ready // and remove them from the list of waiters for (var i = remainingprocesses.Count - 1; i >= 0; i--) { var rp = remainingprocesses[i]; var allInputsReady = InputSignals(state, rp) .All(x => ready.Contains(x)); if (allInputsReady) { current.Add(rp); remainingprocesses.RemoveAt(i); } } // If a round does not remove any items, we have a circular dependency if (current.Count == 0) { throw new Exception("Cicular bus dependency detected, remaining processes: " + string.Join(Environment.NewLine, remainingprocesses.Select(x => x.Name))); } // Register all signals that are now fully written var completedsignals = current .SelectMany( x => OutputSignals(state, x) ) .Where( x => !ready.Contains(x) && !writers[x] .Any( y => remainingprocesses.Contains(y) ) ) .Distinct(); foreach (var n in completedsignals) { ready.Add(n); } roots.Add(current); } state.DependencyGraph = dependsOn; state.SuggestedSchedule = roots; }