Files
nub-lang/compiler/ModuleGraph.cs
nub31 3ebaa67b6d ...
2026-02-24 21:51:36 +01:00

435 lines
16 KiB
C#

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Compiler;
public class ModuleGraph
{
public static Builder CreateBuilder() => new();
private ModuleGraph(Dictionary<string, Module> modules)
{
this.modules = modules;
}
private readonly Dictionary<string, Module> modules;
public List<Module> GetModules()
{
return modules.Values.ToList();
}
public bool TryResolveIdentifier(string moduleName, string identifierName, bool searchPrivate, [NotNullWhen(true)] out Module.IdentifierInfo? info)
{
if (!TryResolveModule(moduleName, out var module))
{
info = null;
return false;
}
if (!module.TryResolveIdentifier(identifierName, searchPrivate, out info))
return false;
return true;
}
public bool TryResolveType(string moduleName, string typeName, bool searchPrivate, [NotNullWhen(true)] out Module.TypeInfo? info)
{
if (!TryResolveModule(moduleName, out var module))
{
info = null;
return false;
}
if (!module.TryResolveType(typeName, searchPrivate, out info))
return false;
return true;
}
public bool TryResolveModule(string moduleName, [NotNullWhen(true)] out Module? module)
{
module = modules.GetValueOrDefault(moduleName);
return module != null;
}
public class Builder
{
private readonly List<Ast> asts = [];
private readonly List<Manifest> manifests = [];
public void AddAst(Ast ast)
{
asts.Add(ast);
}
public void AddManifest(Manifest manifest)
{
manifests.Add(manifest);
}
public ModuleGraph? Build(out List<Diagnostic> diagnostics)
{
diagnostics = [];
var modules = new Dictionary<string, Module>();
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(Module.DefinitionKind.External, 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;
}
case Manifest.Module.TypeInfoEnum s:
{
var info = new Module.TypeInfoEnum(Module.DefinitionKind.External);
var variants = s.Variants.Select(v => new Module.TypeInfoEnum.Variant(v.Name, v.Fields.Select(x => new Module.TypeInfoEnum.Variant.Field(x.Name, x.Type)).ToList())).ToList();
info.SetVariants(variants);
module.AddType(name, info);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(type));
}
}
foreach (var (name, identifier) in manifestModule.Identifiers)
{
module.AddIdentifier(name, new Module.IdentifierInfo(Module.DefinitionKind.External, identifier.Type, identifier.MangledName));
}
}
}
foreach (var ast in asts)
{
var module = GetOrCreateModule(ast.ModuleName.Ident);
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
{
var kind = structDef.Exported ? Module.DefinitionKind.Exported : Module.DefinitionKind.Internal;
module.AddType(structDef.Name.Ident, new Module.TypeInfoStruct(kind, structDef.Packed));
}
foreach (var enumDef in ast.Definitions.OfType<NodeDefinitionEnum>())
{
var kind = enumDef.Exported ? Module.DefinitionKind.Exported : Module.DefinitionKind.Internal;
module.AddType(enumDef.Name.Ident, new Module.TypeInfoEnum(kind));
}
}
foreach (var ast in asts)
{
var module = GetOrCreateModule(ast.ModuleName.Ident);
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
{
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 enumDef in ast.Definitions.OfType<NodeDefinitionEnum>())
{
if (!module.TryResolveType(enumDef.Name.Ident, true, out var typeInfo))
throw new UnreachableException($"{nameof(typeInfo)} should always be registered");
if (typeInfo is Module.TypeInfoEnum enumType)
{
var variants = enumDef.Variants.Select(v => new Module.TypeInfoEnum.Variant(v.Name.Ident, v.Fields.Select(f => new Module.TypeInfoEnum.Variant.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList())).ToList();
enumType.SetVariants(variants);
}
}
}
foreach (var ast in asts)
{
var module = GetOrCreateModule(ast.ModuleName.Ident);
foreach (var funcDef in ast.Definitions.OfType<NodeDefinitionFunc>())
{
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 kind = funcDef.Exported ? Module.DefinitionKind.Exported : Module.DefinitionKind.Internal;
var info = new Module.IdentifierInfo(kind, funcType, NameMangler.Mangle(module.Name, funcDef.Name.Ident, funcType));
module.AddIdentifier(funcDef.Name.Ident, info);
}
foreach (var globalVariable in ast.Definitions.OfType<NodeDefinitionGlobalVariable>())
{
var type = ResolveType(globalVariable.Type, module.Name);
var kind = globalVariable.Exported ? Module.DefinitionKind.Exported : Module.DefinitionKind.Internal;
var info = new Module.IdentifierInfo(kind, type, NameMangler.Mangle(module.Name, globalVariable.Name.Ident, 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,
NodeTypeNamed type => ResolveNamedType(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 ResolveNamedType(NodeTypeNamed type, string currentModule)
{
return type.Sections.Count switch
{
3 => ResolveThreePartType(type.Sections[0], type.Sections[1], type.Sections[2], currentModule),
2 => ResolveTwoPartType(type.Sections[0], type.Sections[1], currentModule),
1 => ResolveOnePartType(type.Sections[0], currentModule),
_ => throw BasicError("Invalid type name")
};
}
NubType ResolveThreePartType(TokenIdent first, TokenIdent second, TokenIdent third, string currentModule)
{
var module = ResolveModule(first);
if (!module.TryResolveType(second.Ident, currentModule == module.Name, out var typeInfo))
throw BasicError($"Named type '{module.Name}::{second.Ident}' not found");
if (typeInfo is not Module.TypeInfoEnum enumInfo)
throw BasicError($"'{module.Name}::{second.Ident}' is not an enum");
var variant = enumInfo.Variants.FirstOrDefault(v => v.Name == third.Ident);
if (variant == null)
throw BasicError($"Enum '{module.Name}::{second.Ident}' does not have a variant named '{third.Ident}'");
return NubTypeEnumVariant.Get(NubTypeEnum.Get(module.Name, second.Ident), third.Ident);
}
NubType ResolveTwoPartType(TokenIdent first, TokenIdent second, string currentModule)
{
if (TryResolveEnumVariant(currentModule, first.Ident, second.Ident, out var variant))
return variant;
var module = ResolveModule(first);
if (!module.TryResolveType(second.Ident, currentModule == module.Name, out var typeInfo))
throw BasicError($"Named type '{module.Name}::{second.Ident}' not found");
return typeInfo switch
{
Module.TypeInfoStruct => NubTypeStruct.Get(module.Name, second.Ident),
Module.TypeInfoEnum => NubTypeEnum.Get(module.Name, second.Ident),
_ => throw new ArgumentOutOfRangeException(nameof(typeInfo))
};
}
NubType ResolveOnePartType(TokenIdent name, string currentModule)
{
if (!modules.TryGetValue(currentModule, out var module))
throw BasicError($"Module '{currentModule}' not found");
if (!module.TryResolveType(name.Ident, true, out var typeInfo))
throw BasicError($"Named type '{module.Name}::{name.Ident}' not found");
return typeInfo switch
{
Module.TypeInfoStruct => NubTypeStruct.Get(module.Name, name.Ident),
Module.TypeInfoEnum => NubTypeEnum.Get(module.Name, name.Ident),
_ => throw new ArgumentOutOfRangeException(nameof(typeInfo))
};
}
Module ResolveModule(TokenIdent name)
{
if (!modules.TryGetValue(name.Ident, out var module))
throw BasicError($"Module '{name.Ident}' not found");
return module;
}
bool TryResolveEnumVariant(string moduleName, string enumName, string variantName, [NotNullWhen(true)] out NubType? result)
{
result = null;
if (!modules.TryGetValue(moduleName, out var module))
return false;
if (!module.TryResolveType(enumName, true, out var typeInfo))
return false;
if (typeInfo is not Module.TypeInfoEnum enumInfo)
return false;
var variant = enumInfo.Variants.FirstOrDefault(v => v.Name == variantName);
if (variant == null)
return false;
result = NubTypeEnumVariant.Get(
NubTypeEnum.Get(moduleName, enumName),
variantName);
return true;
}
Exception BasicError(string message)
{
return new CompileException(Diagnostic.Error(message).Build());
}
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<string, TypeInfo> types = new();
private Dictionary<string, IdentifierInfo> identifiers = new();
public IReadOnlyDictionary<string, TypeInfo> GetTypes() => types;
public IReadOnlyDictionary<string, IdentifierInfo> 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 (searchPrivate || info.Kind is DefinitionKind.External or DefinitionKind.Exported)
{
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 (searchPrivate || info.Kind is DefinitionKind.External or DefinitionKind.Exported)
{
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 enum DefinitionKind
{
Internal,
Exported,
External,
}
public class IdentifierInfo(DefinitionKind kind, NubType type, string mangledName)
{
public DefinitionKind Kind { get; } = kind;
public NubType Type { get; } = type;
public string MangledName { get; } = mangledName;
}
public abstract class TypeInfo(DefinitionKind kind)
{
public DefinitionKind Kind { get; } = kind;
}
public class TypeInfoStruct(DefinitionKind kind, bool packed) : TypeInfo(kind)
{
private IReadOnlyList<Field>? fields;
public bool Packed { get; } = packed;
public IReadOnlyList<Field> Fields => fields ?? throw new InvalidOperationException("Fields has not been set yet");
public void SetFields(IReadOnlyList<Field> fields)
{
this.fields = fields;
}
public class Field(string name, NubType type)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
}
}
public class TypeInfoEnum(DefinitionKind kind) : TypeInfo(kind)
{
private IReadOnlyList<Variant>? variants;
public IReadOnlyList<Variant> Variants => variants ?? throw new InvalidOperationException("Fields has not been set yet");
public void SetVariants(IReadOnlyList<Variant> variants)
{
this.variants = variants;
}
public class Variant(string name, List<Variant.Field> fields)
{
public string Name { get; } = name;
public List<Field> Fields { get; } = fields;
public class Field(string name, NubType type)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
}
}
}
}