diff --git a/compiler/Generator.cs b/compiler/Generator.cs index 82a482d..f565224 100644 --- a/compiler/Generator.cs +++ b/compiler/Generator.cs @@ -14,6 +14,9 @@ public sealed class Generator(List functions, ModuleGra private string Emit() { + foreach (var (i, structType) in moduleGraph.GetModules().SelectMany(x => x.GetCustomTypes().OfType().Index())) + structTypeNames[structType] = $"s{i}"; + writer.WriteLine(""" #include #include @@ -28,9 +31,6 @@ public sealed class Generator(List functions, ModuleGra """); - foreach (var (i, structType) in moduleGraph.GetModules().SelectMany(x => x.GetCustomTypes().OfType().Index())) - structTypeNames[structType] = $"s{i}"; - foreach (var typeName in structTypeNames) writer.WriteLine($"struct {typeName.Value};"); @@ -45,30 +45,55 @@ public sealed class Generator(List functions, ModuleGra foreach (var field in typeName.Key.Fields) writer.WriteLine($"{CType(field.Type, field.Name)};"); } - writer.WriteLine("};"); } writer.WriteLine(); - foreach (var node in functions) + foreach (var module in moduleGraph.GetModules()) { - var parameters = node.Parameters.Select(x => CType(x.Type, x.Name.Ident)); - writer.WriteLine($"{CType(node.ReturnType, node.Name.Ident)}({string.Join(", ", parameters)});"); + foreach (var (name, type) in module.GetIdentifierTypes()) + { + if (type is NubTypeFunc fn) + { + if (!functions.Any(x => x.GetMangledName() == SymbolNameGen.Exported(module.Name, name, type))) + writer.Write("extern "); + + writer.WriteLine($"{CType(fn.ReturnType)} {SymbolNameGen.Exported(module.Name, name, type)}({string.Join(", ", fn.Parameters.Select(p => CType(p)))});"); + } + else + { + writer.WriteLine($"{CType(type)} {SymbolNameGen.Exported(module.Name, name, type)};"); + } + } } writer.WriteLine(); - foreach (var node in functions) + var main = functions.FirstOrDefault(x => x.Module == "main" && x.Name.Ident == "main"); + + if (main != null) { - var parameters = node.Parameters.Select(x => CType(x.Type, x.Name.Ident)); - writer.WriteLine($"{CType(node.ReturnType, node.Name.Ident)}({string.Join(", ", parameters)})"); + writer.WriteLine("int main(int argc, char *argv[])"); writer.WriteLine("{"); using (writer.Indent()) { - EmitStatement(node.Body); + writer.WriteLine($"return {main.GetMangledName()}();"); } + writer.WriteLine("}"); + } + writer.WriteLine(); + + foreach (var function in functions) + { + var parameters = function.Parameters.Select(x => CType(x.Type, x.Name.Ident)); + writer.WriteLine($"{CType(function.ReturnType, function.GetMangledName())}({string.Join(", ", parameters)})"); + writer.WriteLine("{"); + using (writer.Indent()) + { + EmitStatement(function.Body); + } writer.WriteLine("}"); writer.WriteLine(); } @@ -114,7 +139,6 @@ public sealed class Generator(List functions, ModuleGra foreach (var statement in node.Statements) EmitStatement(statement); } - writer.WriteLine("}"); } @@ -153,7 +177,6 @@ public sealed class Generator(List functions, ModuleGra { EmitStatement(statement.ThenBlock); } - writer.WriteLine("}"); if (statement.ElseBlock != null) @@ -169,7 +192,6 @@ public sealed class Generator(List functions, ModuleGra { EmitStatement(statement.ElseBlock); } - writer.WriteLine("}"); } } @@ -183,7 +205,6 @@ public sealed class Generator(List functions, ModuleGra { EmitStatement(statement.Block); } - writer.WriteLine("}"); } @@ -199,7 +220,7 @@ public sealed class Generator(List functions, ModuleGra TypedNodeExpressionStructLiteral expression => EmitExpressionStructLiteral(expression), TypedNodeExpressionMemberAccess expression => EmitExpressionMemberAccess(expression), TypedNodeExpressionLocalIdent expression => expression.Value.Ident, - TypedNodeExpressionModuleIdent expression => expression.Value.Ident, + TypedNodeExpressionModuleIdent expression => SymbolNameGen.Exported(expression.Module.Ident, expression.Value.Ident, expression.Type), TypedNodeExpressionFuncCall expression => EmitExpressionFuncCall(expression), _ => throw new ArgumentOutOfRangeException(nameof(node), node, null) }; diff --git a/compiler/ModuleGraph.cs b/compiler/ModuleGraph.cs index 1704940..70cad75 100644 --- a/compiler/ModuleGraph.cs +++ b/compiler/ModuleGraph.cs @@ -24,11 +24,16 @@ public class ModuleGraph(Dictionary modules) private readonly Dictionary customTypes = new(); private readonly Dictionary identifierTypes = new(); - public List GetCustomTypes() + public IReadOnlyList GetCustomTypes() { return customTypes.Values.ToList(); } + public IReadOnlyDictionary GetIdentifierTypes() + { + return identifierTypes; + } + public bool TryResolveCustomType(string name, [NotNullWhen(true)] out NubType? customType) { customType = customTypes.GetValueOrDefault(name); @@ -61,7 +66,7 @@ public class ModuleGraph(Dictionary modules) asts.Add(ast); } - public ModuleGraph Build(out List diagnostics) + public ModuleGraph? Build(out List diagnostics) { diagnostics = []; @@ -71,24 +76,11 @@ public class ModuleGraph(Dictionary modules) // First pass: Register modules foreach (var ast in asts) { - var moduleDefinitions = ast.Definitions.OfType().ToList(); - - if (moduleDefinitions.Count == 0) - diagnostics.Add(Diagnostic.Error("Missing module declaration").At(ast.FileName, 1, 1, 1).Build()); - - foreach (var extraModuleDefinition in moduleDefinitions.Skip(1)) - diagnostics.Add(Diagnostic.Warning("Duplicate module declaration will be ignored").At(ast.FileName, extraModuleDefinition).Build()); - - if (moduleDefinitions.Count >= 1) + if (!modules.ContainsKey(ast.ModuleName.Ident)) { - var currentModule = moduleDefinitions[0].Name.Ident; - - if (!modules.ContainsKey(currentModule)) - { - var module = new Module(currentModule); - modules.Add(currentModule, module); - astModuleCache[ast] = module; - } + var module = new Module(ast.ModuleName.Ident); + modules.Add(ast.ModuleName.Ident, module); + astModuleCache[ast] = module; } } @@ -133,6 +125,9 @@ public class ModuleGraph(Dictionary modules) } } + if (diagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + return null; + return new ModuleGraph(modules); NubType ResolveType(NodeType node) diff --git a/compiler/NubType.cs b/compiler/NubType.cs index 6a77525..3223327 100644 --- a/compiler/NubType.cs +++ b/compiler/NubType.cs @@ -1,3 +1,5 @@ +using System.Text; + namespace Compiler; public abstract class NubType @@ -153,6 +155,8 @@ public sealed class NubTypeFunc : NubType public IReadOnlyList Parameters { get; } public NubType ReturnType { get; } + public string MangledName(string name, string module) => SymbolNameGen.Exported(name, module, this); + private NubTypeFunc(List parameters, NubType returnType) { Parameters = parameters; @@ -162,4 +166,110 @@ public sealed class NubTypeFunc : NubType public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}"; private readonly record struct Signature(IReadOnlyList Parameters, NubType ReturnType); -} \ No newline at end of file +} + +static class TypeMangler +{ + public static string Encode(NubType type) + { + var sb = new StringBuilder(); + Encode(type, sb); + return sb.ToString(); + } + + private static void Encode(NubType type, StringBuilder sb) + { + switch (type) + { + case NubTypeVoid: + sb.Append('V'); + break; + + case NubTypeBool: + sb.Append('B'); + break; + + case NubTypeUInt u: + sb.Append("U("); + sb.Append(u.Width); + sb.Append(')'); + break; + + case NubTypeSInt s: + sb.Append("U("); + sb.Append(s.Width); + sb.Append(')'); + break; + + case NubTypeString: + sb.Append('S'); + break; + + case NubTypePointer p: + sb.Append("P("); + Encode(p.To, sb); + sb.Append(')'); + break; + + case NubTypeStruct st: + sb.Append($"T({st.Module}::{st.Name})").Append(st.Module).Append("::").Append(st.Name).Append(')'); + break; + + case NubTypeFunc fn: + sb.Append("F("); + for (int i = 0; i < fn.Parameters.Count; i++) + { + Encode(fn.Parameters[i], sb); + sb.Append(','); + } + Encode(fn.ReturnType, sb); + sb.Append(')'); + break; + + default: + throw new NotSupportedException(type.GetType().Name); + } + } +} + +static class Hashing +{ + public static ulong Fnv1a64(string text) + { + const ulong offset = 14695981039346656037UL; + const ulong prime = 1099511628211UL; + + ulong hash = offset; + foreach (var c in Encoding.UTF8.GetBytes(text)) + { + hash ^= c; + hash *= prime; + } + + return hash; + } +} + +static class SymbolNameGen +{ + public static string Exported(string module, string function, NubType type) + { + var canonical = TypeMangler.Encode(type); + var hash = Hashing.Fnv1a64(canonical); + + return $"nub_{Sanitize(module)}_{Sanitize(function)}_{hash:x16}"; + } + + private static string Sanitize(string s) + { + var sb = new StringBuilder(s.Length); + foreach (var c in s) + { + if (char.IsLetterOrDigit(c)) + sb.Append(c); + else + sb.Append('_'); + } + return sb.ToString(); + } +} diff --git a/compiler/Parser.cs b/compiler/Parser.cs index 1d9fe6d..02e586d 100644 --- a/compiler/Parser.cs +++ b/compiler/Parser.cs @@ -4,20 +4,25 @@ namespace Compiler; public sealed class Parser(string fileName, List tokens) { - public static Ast Parse(string fileName, List tokens, out List diagnostics) + public static Ast? Parse(string fileName, List tokens, out List diagnostics) { return new Parser(fileName, tokens).Parse(out diagnostics); } private int index; - private Ast Parse(out List diagnostics) + private Ast? Parse(out List diagnostics) { var definitions = new List(); diagnostics = []; + TokenIdent? moduleName = null; + try { + ExpectKeyword(Keyword.Module); + moduleName = ExpectIdent(); + while (Peek() != null) { definitions.Add(ParseDefinition()); @@ -28,7 +33,10 @@ public sealed class Parser(string fileName, List tokens) diagnostics.Add(e.Diagnostic); } - return new Ast(definitions, fileName); + if (moduleName == null || diagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + return null; + + return new Ast(fileName, moduleName, definitions); } private NodeDefinition ParseDefinition() @@ -76,12 +84,6 @@ public sealed class Parser(string fileName, List tokens) return new NodeDefinitionStruct(TokensFrom(startIndex), name, fields); } - if (TryExpectKeyword(Keyword.Module)) - { - var name = ExpectIdent(); - return new NodeDefinitionModule(TokensFrom(startIndex), name); - } - throw new CompileException(Diagnostic.Error("Not a valid definition").At(fileName, Peek()).Build()); } @@ -355,6 +357,17 @@ public sealed class Parser(string fileName, List tokens) return tokens.GetRange(startIndex, index - startIndex); } + private void ExpectKeyword(Keyword keyword) + { + if (Peek() is TokenKeyword token && token.Keyword == keyword) + { + Next(); + return; + } + + throw new CompileException(Diagnostic.Error($"Expected '{keyword.AsString()}'").At(fileName, Peek()).Build()); + } + private bool TryExpectKeyword(Keyword keyword) { if (Peek() is TokenKeyword token && token.Keyword == keyword) @@ -529,9 +542,10 @@ public sealed class Parser(string fileName, List tokens) } } -public sealed class Ast(List definitions, string fileName) +public sealed class Ast(string fileName, TokenIdent moduleName, List definitions) { public string FileName { get; } = fileName; + public TokenIdent ModuleName { get; } = moduleName; public List Definitions { get; } = definitions; } @@ -542,11 +556,6 @@ public abstract class Node(List tokens) public abstract class NodeDefinition(List tokens) : Node(tokens); -public sealed class NodeDefinitionModule(List tokens, TokenIdent name) : NodeDefinition(tokens) -{ - public TokenIdent Name { get; } = name; -} - public sealed class NodeDefinitionFunc(List tokens, TokenIdent name, List parameters, NodeStatement body, NodeType returnType) : NodeDefinition(tokens) { public TokenIdent Name { get; } = name; diff --git a/compiler/Program.cs b/compiler/Program.cs index a7c3008..bb74939 100644 --- a/compiler/Program.cs +++ b/compiler/Program.cs @@ -13,7 +13,7 @@ foreach (var fileName in args) foreach (var diagnostic in tokenizerDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); - if (tokenizerDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + if (tokens == null) return 1; var ast = Parser.Parse(fileName, tokens, out var parserDiagnostics); @@ -21,7 +21,7 @@ foreach (var fileName in args) foreach (var diagnostic in parserDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); - if (parserDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + if (ast == null) return 1; moduleGraphBuilder.AddAst(ast); @@ -33,7 +33,7 @@ var moduleGraph = moduleGraphBuilder.Build(out var moduleGraphDiagnostics); foreach (var diagnostic in moduleGraphDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); -if (moduleGraphDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) +if (moduleGraph == null) return 1; var functions = new List(); @@ -42,7 +42,7 @@ foreach (var ast in asts) { foreach (var func in ast.Definitions.OfType()) { - var typedFunction = TypeChecker.CheckFunction(ast.FileName, func, moduleGraph, out var typeCheckerDiagnostics); + var typedFunction = TypeChecker.CheckFunction(ast.FileName, ast.ModuleName.Ident, func, moduleGraph, out var typeCheckerDiagnostics); foreach (var diagnostic in typeCheckerDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); @@ -61,6 +61,6 @@ Directory.CreateDirectory(".build"); File.WriteAllText(".build/out.c", output); -Process.Start("gcc", ["-Og", "-g", "-o", ".build/out", ".build/out.c"]); +Process.Start("gcc", ["-Og", "-g", "-o", ".build/out", ".build/out.c", "-fvisibility=hidden","-fno-builtin"]); return 0; \ No newline at end of file diff --git a/compiler/Tokenizer.cs b/compiler/Tokenizer.cs index d057834..f4b2014 100644 --- a/compiler/Tokenizer.cs +++ b/compiler/Tokenizer.cs @@ -5,7 +5,7 @@ namespace Compiler; public sealed class Tokenizer(string fileName, string contents) { - public static List Tokenize(string fileName, string contents, out List diagnostics) + public static List? Tokenize(string fileName, string contents, out List diagnostics) { return new Tokenizer(fileName, contents).Tokenize(out diagnostics); } @@ -14,7 +14,7 @@ public sealed class Tokenizer(string fileName, string contents) private int line = 1; private int column = 1; - private List Tokenize(out List diagnostics) + private List? Tokenize(out List diagnostics) { var tokens = new List(); diagnostics = []; @@ -53,6 +53,9 @@ public sealed class Tokenizer(string fileName, string contents) } } + if (diagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + return null; + return tokens; } diff --git a/compiler/TypeChecker.cs b/compiler/TypeChecker.cs index 27f835e..8a2838d 100644 --- a/compiler/TypeChecker.cs +++ b/compiler/TypeChecker.cs @@ -1,10 +1,10 @@ namespace Compiler; -public sealed class TypeChecker(string fileName, NodeDefinitionFunc function, ModuleGraph moduleGraph) +public sealed class TypeChecker(string fileName, string moduleName, NodeDefinitionFunc function, ModuleGraph moduleGraph) { - public static TypedNodeDefinitionFunc? CheckFunction(string fileName, NodeDefinitionFunc function, ModuleGraph moduleGraph, out List diagnostics) + public static TypedNodeDefinitionFunc? CheckFunction(string fileName, string moduleName, NodeDefinitionFunc function, ModuleGraph moduleGraph, out List diagnostics) { - return new TypeChecker(fileName, function, moduleGraph).CheckFunction(out diagnostics); + return new TypeChecker(fileName, moduleName, function, moduleGraph).CheckFunction(out diagnostics); } private readonly Scope scope = new(null); @@ -52,7 +52,7 @@ public sealed class TypeChecker(string fileName, NodeDefinitionFunc function, Mo if (body == null || returnType == null || invalidParameter) return null; - return new TypedNodeDefinitionFunc(function.Tokens, function.Name, parameters, body, returnType); + return new TypedNodeDefinitionFunc(function.Tokens, moduleName, function.Name, parameters, body, returnType); } private TypedNodeDefinitionFunc.Param CheckDefinitionFuncParameter(NodeDefinitionFunc.Param node) @@ -409,15 +409,28 @@ public abstract class TypedNode(List tokens) public List Tokens { get; } = tokens; } -public abstract class TypedNodeDefinition(List tokens) : TypedNode(tokens); +public abstract class TypedNodeDefinition(List tokens, string module) : TypedNode(tokens) +{ + public string Module { get; } = module; +} -public sealed class TypedNodeDefinitionFunc(List tokens, TokenIdent name, List parameters, TypedNodeStatement body, NubType returnType) : TypedNodeDefinition(tokens) +public sealed class TypedNodeDefinitionFunc(List tokens, string module, TokenIdent name, List parameters, TypedNodeStatement body, NubType returnType) : TypedNodeDefinition(tokens, module) { public TokenIdent Name { get; } = name; public List Parameters { get; } = parameters; public TypedNodeStatement Body { get; } = body; public NubType ReturnType { get; } = returnType; + public NubTypeFunc GetNubType() + { + return NubTypeFunc.Get(Parameters.Select(x => x.Type).ToList(), ReturnType); + } + + public string GetMangledName() + { + return SymbolNameGen.Exported(Module, Name.Ident, GetNubType()); + } + public sealed class Param(List tokens, TokenIdent name, NubType type) : TypedNode(tokens) { public TokenIdent Name { get; } = name;