using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Compiler; public class ModuleGraph { public static Builder CreateBuilder() => new(); private ModuleGraph(Dictionary modules) { this.modules = modules; } private readonly Dictionary modules; public List GetModules() { return modules.Values.ToList(); } public bool TryResolveModule(string moduleName, [NotNullWhen(true)] out Module? module) { module = modules.GetValueOrDefault(moduleName); return module != null; } public class Builder { private readonly List asts = []; private readonly List manifests = []; public void AddAst(Ast ast) { asts.Add(ast); } public void AddManifest(Manifest manifest) { manifests.Add(manifest); } public ModuleGraph? Build(out List diagnostics) { diagnostics = []; var modules = new Dictionary(); foreach (var manifest in manifests) { foreach (var (moduleName, manifestModule) in manifest.Modules) { var module = GetOrCreateModule(moduleName); foreach (var (name, type) in manifestModule.Types) { switch (type) { case Manifest.Module.TypeInfoStruct s: var info = new Module.TypeInfoStruct(s.Exported, s.Packed); var fields = s.Fields.Select(x => new Module.TypeInfoStruct.Field(x.Name, x.Type)).ToList(); info.SetFields(fields); module.AddType(name, info); break; default: throw new ArgumentOutOfRangeException(nameof(type)); } } foreach (var (name, identifier) in manifestModule.Identifiers) { module.AddIdentifier(name, new Module.IdentifierInfo(identifier.Exported, identifier.Type)); } } } foreach (var ast in asts) { var module = GetOrCreateModule(ast.ModuleName.Ident); foreach (var structDef in ast.Definitions.OfType()) { module.AddType(structDef.Name.Ident, new Module.TypeInfoStruct(structDef.Exported, structDef.Packed)); } } foreach (var ast in asts) { var module = GetOrCreateModule(ast.ModuleName.Ident); foreach (var structDef in ast.Definitions.OfType()) { if (!module.TryResolveType(structDef.Name.Ident, true, out var typeInfo)) throw new UnreachableException($"{nameof(typeInfo)} should always be registered"); if (typeInfo is Module.TypeInfoStruct structType) { var fields = structDef.Fields.Select(f => new Module.TypeInfoStruct.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList(); structType.SetFields(fields); } } } foreach (var ast in asts) { var module = GetOrCreateModule(ast.ModuleName.Ident); foreach (var funcDef in ast.Definitions.OfType()) { var parameters = funcDef.Parameters.Select(x => ResolveType(x.Type, module.Name)).ToList(); var returnType = ResolveType(funcDef.ReturnType, module.Name); var funcType = NubTypeFunc.Get(parameters, returnType); var info = new Module.IdentifierInfo(funcDef.Exported, funcType); module.AddIdentifier(funcDef.Name.Ident, info); } foreach (var globalVariable in ast.Definitions.OfType()) { var type = ResolveType(globalVariable.Type, module.Name); var info = new Module.IdentifierInfo(globalVariable.Exported, type); module.AddIdentifier(globalVariable.Name.Ident, info); } } if (diagnostics.Any(x => x.Severity == Diagnostic.DiagnosticSeverity.Error)) return null; return new ModuleGraph(modules); NubType ResolveType(NodeType node, string currentModule) { return node switch { NodeTypeBool => NubTypeBool.Instance, NodeTypeCustom type => ResolveCustomType(type, currentModule), NodeTypeFunc type => NubTypeFunc.Get(type.Parameters.Select(x => ResolveType(x, currentModule)).ToList(), ResolveType(type.ReturnType, currentModule)), NodeTypePointer type => NubTypePointer.Get(ResolveType(type.To, currentModule)), NodeTypeSInt type => NubTypeSInt.Get(type.Width), NodeTypeUInt type => NubTypeUInt.Get(type.Width), NodeTypeString => NubTypeString.Instance, NodeTypeVoid => NubTypeVoid.Instance, _ => throw new ArgumentOutOfRangeException(nameof(node)) }; } NubType ResolveCustomType(NodeTypeCustom type, string currentModule) { var module = modules.GetValueOrDefault(type.Module.Ident); if (module == null) throw new CompileException(Diagnostic.Error($"Unknown module: {type.Module.Ident}").Build()); var includePrivate = currentModule == type.Module.Ident; if (!module.TryResolveType(type.Name.Ident, includePrivate, out var customType)) throw new CompileException(Diagnostic.Error($"Unknown custom type: {type.Module.Ident}::{type.Name.Ident}").Build()); return customType switch { Module.TypeInfoStruct => NubTypeStruct.Get(type.Module.Ident, type.Name.Ident), _ => throw new ArgumentOutOfRangeException(nameof(customType)) }; } Module GetOrCreateModule(string name) { if (!modules.TryGetValue(name, out var module)) { module = new Module(name); modules.Add(name, module); } return module; } } } } public class Module(string name) { public string Name { get; } = name; private Dictionary types = new(); private Dictionary identifiers = new(); public IReadOnlyDictionary GetTypes() => types; public IReadOnlyDictionary GetIdentifiers() => identifiers; public bool TryResolveType(string name, bool searchPrivate, [NotNullWhen(true)] out TypeInfo? customType) { var info = types.GetValueOrDefault(name); if (info == null) { customType = null; return false; } if (info.Exported || searchPrivate) { customType = info; return true; } customType = null; return false; } public bool TryResolveIdentifier(string name, bool searchPrivate, [NotNullWhen(true)] out IdentifierInfo? identifierType) { var info = identifiers.GetValueOrDefault(name); if (info == null) { identifierType = null; return false; } if (info.Exported || searchPrivate) { identifierType = info; return true; } identifierType = null; return false; } public void AddType(string name, TypeInfo info) { types.Add(name, info); } public void AddIdentifier(string name, IdentifierInfo info) { identifiers.Add(name, info); } public class IdentifierInfo(bool exported, NubType type) { public bool Exported { get; } = exported; public NubType Type { get; } = type; } public abstract class TypeInfo(bool exported) { public bool Exported { get; } = exported; } public class TypeInfoStruct(bool exported, bool packed) : TypeInfo(exported) { private IReadOnlyList? fields; public bool Packed { get; } = packed; public IReadOnlyList Fields => fields ?? throw new InvalidOperationException("Fields has not been set yet"); public void SetFields(IReadOnlyList fields) { this.fields = fields; } public class Field(string name, NubType type) { public string Name { get; } = name; public NubType Type { get; } = type; } } }