diff --git a/.gitignore b/.gitignore index 9998c3d..fe932d1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ _UpgradeReport_Files/ Thumbs.db Desktop.ini .DS_Store + +Nub.Lang/Nub.Lang/Output/out* \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Generation/Generator.cs b/Nub.Lang/Nub.Lang/Generation/Generator.cs new file mode 100644 index 0000000..4fc2fab --- /dev/null +++ b/Nub.Lang/Nub.Lang/Generation/Generator.cs @@ -0,0 +1,272 @@ +using System.Text; +using Nub.Lang.Parsing; + +namespace Nub.Lang.Generation; + +public class Generator +{ + private const string Entrypoint = "main"; + + private readonly IReadOnlyCollection _definitions; + private readonly SymbolTable _symbolTable; + private readonly StringBuilder _builder; + private Dictionary _strings; + private int _stringIndex; + + public Generator(IReadOnlyCollection definitions) + { + _strings = []; + _definitions = definitions; + _builder = new StringBuilder(); + _symbolTable = new SymbolTable(definitions.OfType().ToList()); + foreach (var funcDefinitionNode in definitions.OfType()) + { + _symbolTable.DefineFunc(funcDefinitionNode); + } + } + + public string Generate() + { + _builder.AppendLine("global _start"); + + _builder.AppendLine(); + _builder.AppendLine("section .bss"); + foreach (var globalVariable in _definitions.OfType()) + { + var symbol = _symbolTable.ResolveGlobalVariable(globalVariable.Name); + _builder.AppendLine($" {symbol.Identifier}: resq 1"); + } + + _builder.AppendLine(); + _builder.AppendLine("section .text"); + _builder.AppendLine("_start:"); + + var main = _symbolTable.ResolveFunc(Entrypoint, []); + + foreach (var globalVariable in _definitions.OfType()) + { + var symbol = _symbolTable.ResolveGlobalVariable(globalVariable.Name); + _builder.AppendLine($" ; Initialize global variable {symbol.Name}"); + GenerateExpression(globalVariable.Value, main); + _builder.AppendLine($" mov [{symbol.Identifier}], rax"); + } + + _builder.AppendLine(); + _builder.AppendLine($" ; Call entrypoint {Entrypoint}"); + _builder.AppendLine($" call {main.Label}"); + + _builder.AppendLine(); + _builder.AppendLine(" ; Exit with status code 0"); + _builder.AppendLine(" mov rax, 60"); + _builder.AppendLine(" mov rdi, 0"); + _builder.AppendLine(" syscall"); + + foreach (var funcDefinition in _definitions.OfType()) + { + _builder.AppendLine(); + GenerateFuncDefinition(funcDefinition); + } + _builder.AppendLine(); + _builder.AppendLine("section .data"); + foreach (var str in _strings) + { + _builder.AppendLine($"{str.Key}: db `{str.Value}`, 0"); + } + + return _builder.ToString(); + } + + private void GenerateFuncDefinition(FuncDefinitionNode funcDefinition) + { + var symbol = _symbolTable.ResolveFunc(funcDefinition.Name, funcDefinition.Parameters.Select(p => p.Type).ToList()); + _builder.AppendLine($"; {funcDefinition.ToString()}"); + _builder.AppendLine($"{symbol.Label}:"); + _builder.AppendLine(" push rbp"); + _builder.AppendLine(" mov rbp, rsp"); + _builder.AppendLine($" sub rsp, {symbol.StackAllocation}"); + + string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; + + for (var i = 0; i < symbol.Parameters.Count; i++) + { + var parameter = symbol.Parameters.ElementAt(i); + + if (i < registers.Length) + { + var variable = symbol.ResolveLocalVariable(parameter.Name); + _builder.AppendLine($" mov [rbp - {variable.Offset}], {registers[i]}"); + } + else + { + // TODO: Implement parameters passed on the stack + throw new NotImplementedException(); + } + } + + GenerateBlock(funcDefinition.Body, symbol); + _builder.AppendLine(" mov rsp, rbp"); + _builder.AppendLine(" pop rbp"); + _builder.AppendLine(" ret"); + } + + private void GenerateBlock(BlockNode block, Func func) + { + foreach (var statement in block.Statements) + { + GenerateStatement(statement, func); + } + } + + private void GenerateStatement(StatementNode statement, Func func) + { + switch (statement) + { + case FuncCallStatementNode funcCallStatement: + GenerateFuncCall(funcCallStatement.FuncCall, func); + break; + case SyscallStatementNode syscallStatement: + GenerateSyscall(syscallStatement.Syscall, func); + break; + case VariableAssignmentNode variableAssignment: + throw new NotImplementedException(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(statement)); + } + } + + private void GenerateExpression(ExpressionNode expression, Func func) + { + switch (expression) + { + case FuncCallExpressionNode funcCallExpression: + throw new NotImplementedException(); + break; + case IdentifierNode identifier: + GenerateIdentifier(identifier, func); + break; + case LiteralNode literal: + GenerateLiteral(literal, func); + break; + case SyscallExpressionNode syscallExpression: + throw new NotImplementedException(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(expression)); + } + } + + private void GenerateIdentifier(IdentifierNode identifier, Func func) + { + var variable = func.ResolveVariable(identifier.Identifier); + + switch (variable) + { + case GlobalVariable globalVariable: + _builder.AppendLine($" mov rax, [{globalVariable.Identifier}]"); + break; + case LocalVariable localVariable: + { + _builder.AppendLine($" mov rax, [rbp - {localVariable.Offset}]"); + break; + } + default: + { + throw new ArgumentOutOfRangeException(nameof(variable)); + } + } + } + + private void GenerateLiteral(LiteralNode literal, Func func) + { + switch (literal.Type) + { + case DelegateType: + throw new NotImplementedException(); + break; + case StringType: + var ident = $"string{++_stringIndex}"; + _strings.Add(ident, literal.Literal); + _builder.AppendLine($" mov rax, {ident}"); + break; + case PrimitiveType primitive: + switch (primitive.Kind) + { + case PrimitiveTypeKind.Bool: + { + var value = literal.Literal == "true" ? 1 : 0; + _builder.AppendLine($" mov al, {value}"); + break; + } + case PrimitiveTypeKind.Char: + throw new NotImplementedException(); + break; + case PrimitiveTypeKind.Int8: + case PrimitiveTypeKind.UInt8: + _builder.AppendLine($" mov al, {literal.Literal}"); + break; + case PrimitiveTypeKind.Int16: + case PrimitiveTypeKind.UInt16: + _builder.AppendLine($" mov ax, {literal.Literal}"); + break; + case PrimitiveTypeKind.Int32: + case PrimitiveTypeKind.UInt32: + _builder.AppendLine($" mov eax, {literal.Literal}"); + break; + case PrimitiveTypeKind.Int64: + case PrimitiveTypeKind.UInt64: + _builder.AppendLine($" mov rax, {literal.Literal}"); + break; + case PrimitiveTypeKind.Float: + throw new NotImplementedException(); + break; + case PrimitiveTypeKind.Double: + throw new NotImplementedException(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // TODO: Use stack for more than 6 parameters + private void GenerateFuncCall(FuncCall funcCall, Func func) + { + var symbol = _symbolTable.ResolveFunc(funcCall.Name, funcCall.Parameters.Select(p => p.Type).ToList()); + string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; + + foreach (var parameter in funcCall.Parameters) + { + GenerateExpression(parameter, func); + _builder.AppendLine(" push rax"); + } + + for (var i = funcCall.Parameters.Count - 1; i >= 0; i--) + { + _builder.AppendLine($" pop {registers[i]}"); + } + + _builder.AppendLine($" call {symbol.Label}"); + } + + private void GenerateSyscall(Syscall syscall, Func func) + { + string[] registers = ["rax", "rdi", "rsi", "rdx", "r10", "r8", "r9"]; + + foreach (var parameter in syscall.Parameters) + { + GenerateExpression(parameter, func); + _builder.AppendLine(" push rax"); + } + + for (var i = syscall.Parameters.Count - 1; i >= 0; i--) + { + _builder.AppendLine($" pop {registers[i]}"); + } + + _builder.AppendLine(" syscall"); + } +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Generation/SymbolTable.cs b/Nub.Lang/Nub.Lang/Generation/SymbolTable.cs new file mode 100644 index 0000000..0af7b19 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Generation/SymbolTable.cs @@ -0,0 +1,128 @@ +using Nub.Lang.Parsing; +using Nub.Lib; + +namespace Nub.Lang.Generation; + +public class SymbolTable +{ + private readonly List _functions = []; + private readonly List _globalVariables = []; + private int _labelIndex; + + public SymbolTable(IReadOnlyCollection globalVariableDefinitions) + { + var globalVariableIndex = 0; + foreach (var globalVariable in globalVariableDefinitions) + { + var identifier = $"variable{++globalVariableIndex}"; + _globalVariables.Add(new GlobalVariable(globalVariable.Name, globalVariable.Value.Type, identifier)); + } + } + + public void DefineFunc(FuncDefinitionNode funcDefinition) + { + var label = $"func{++_labelIndex}"; + var localVariables = ResolveFunctionVariables(funcDefinition); + _functions.Add(new Func(label, funcDefinition.Name, funcDefinition.Parameters, funcDefinition.ReturnType, _globalVariables.Concat(localVariables.Variables).ToList(), localVariables.StackSize)); + } + + private (int StackSize, List Variables) ResolveFunctionVariables(FuncDefinitionNode funcDefinition) + { + var offset = 0; + List variables = []; + + foreach (var parameter in funcDefinition.Parameters) + { + offset += 8; + variables.Add(new LocalVariable(parameter.Name, parameter.Type, offset)); + } + + foreach (var statement in funcDefinition.Body.Statements) + { + if (statement is VariableAssignmentNode variableAssignment) + { + offset += 8; + variables.Add(new LocalVariable(variableAssignment.Name, variableAssignment.Value.Type, offset)); + } + } + + return (offset, variables); + } + + public Func ResolveFunc(string name, IReadOnlyCollection parameterTypes) + { + var func = _functions.FirstOrDefault(f => f.Name == name && f.Parameters.Count == parameterTypes.Count && f.Parameters.Where((p, i) => p.Type == parameterTypes.ElementAt(i)).Count() == parameterTypes.Count); + if (func == null) + { + throw new Exception($"Func {name}({string.Join(", ", parameterTypes)}) is not defined"); + } + + return func; + } + + public GlobalVariable ResolveGlobalVariable(string name) + { + var variable = _globalVariables.FirstOrDefault(v => v.Name == name); + if (variable == null) + { + throw new Exception($"Global variable {name} is not defined"); + } + + return variable; + } +} + +public abstract class Variable(string name, Type type) +{ + public string Name { get; } = name; + public Type Type { get; } = type; +} + +public class LocalVariable(string name, Type type, int offset) : Variable(name, type) +{ + public int Offset { get; } = offset; +} + +public class GlobalVariable(string name, Type type, string identifier) : Variable(name, type) +{ + public string Identifier { get; } = identifier; +} + +public class Func(string label, string name, IReadOnlyCollection parameters, Optional returnType, IReadOnlyCollection variables, int stackAllocation) +{ + public string Label { get; } = label; + public string Name { get; } = name; + public IReadOnlyCollection Parameters { get; } = parameters; + public Optional ReturnType { get; } = returnType; + public IReadOnlyCollection Variables { get; } = variables; + public int StackAllocation { get; } = stackAllocation; + + public Variable ResolveVariable(string name) + { + var variable = Variables.FirstOrDefault(v => v.Name == name); + if (variable == null) + { + throw new Exception($"Variable {name} is not defined"); + } + + return variable; + } + + public LocalVariable ResolveLocalVariable(string name) + { + var variable = Variables.FirstOrDefault(v => v.Name == name); + if (variable == null) + { + throw new Exception($"Variable {name} is not defined"); + } + + if (variable is not LocalVariable localVariable) + { + throw new Exception($"Variable {name} is not a local variable"); + } + + return localVariable; + } + + public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Input/program.nub b/Nub.Lang/Nub.Lang/Input/program.nub index 7eb5570..ab313f4 100644 --- a/Nub.Lang/Nub.Lang/Input/program.nub +++ b/Nub.Lang/Nub.Lang/Input/program.nub @@ -4,9 +4,9 @@ let STD_OUT = 1; let STD_ERR = 2; func main() { - write("test"); + write("test\n"); } -func write(msg: Func) { - syscall(SYS_WRITE, STD_OUT, msg); +func write(msg: pointer) { + syscall(SYS_WRITE, STD_OUT, msg, 5); } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/Lexer.cs b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs index 1dfd7de..cf969db 100644 --- a/Nub.Lang/Nub.Lang/Lexing/Lexer.cs +++ b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs @@ -104,7 +104,7 @@ public class Lexer buffer += current.Value; } - return new LiteralToken(new PointerType(), buffer); + return new LiteralToken(new StringType(), buffer); } if (char.IsWhiteSpace(current.Value)) diff --git a/Nub.Lang/Nub.Lang/Output/run.sh b/Nub.Lang/Nub.Lang/Output/run.sh new file mode 100644 index 0000000..65292c1 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Output/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +nasm -g -felf64 out.asm -o out.o +ld out.o -o out +./out + +rm out.o + +echo "Process exited with status code $?" \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs b/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs index 8b08ceb..496afec 100644 --- a/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs @@ -1,6 +1,6 @@ namespace Nub.Lang.Parsing; -public class BlockNode(IEnumerable statements) : Node +public class BlockNode(IReadOnlyCollection statements) : Node { - public IEnumerable Statements { get; } = statements; + public IReadOnlyCollection Statements { get; } = statements; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs b/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs index f9774b9..0289a8a 100644 --- a/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs +++ b/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs @@ -1,9 +1,9 @@ namespace Nub.Lang.Parsing; -public class FuncCall(string name, IEnumerable parameters) +public class FuncCall(string name, IReadOnlyCollection parameters) { public string Name { get; } = name; - public IEnumerable Parameters { get; } = parameters; + public IReadOnlyCollection Parameters { get; } = parameters; public override string ToString() => $"{Name}()"; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs index 03329cc..72bb5f4 100644 --- a/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs @@ -2,12 +2,12 @@ namespace Nub.Lang.Parsing; -public class FuncDefinitionNode(string name, IEnumerable parameters, BlockNode body, Optional returnType) : DefinitionNode +public class FuncDefinitionNode(string name, IReadOnlyCollection parameters, BlockNode body, Optional returnType) : DefinitionNode { public string Name { get; } = name; - public IEnumerable Parameters { get; } = parameters; + public IReadOnlyCollection Parameters { get; } = parameters; public BlockNode Body { get; } = body; public Optional ReturnType { get; } = returnType; - public override string ToString() => $"{Name}({Parameters.Select(p => p.ToString())}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; + public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/Parser.cs b/Nub.Lang/Nub.Lang/Parsing/Parser.cs index 7e1a8d9..88e1197 100644 --- a/Nub.Lang/Nub.Lang/Parsing/Parser.cs +++ b/Nub.Lang/Nub.Lang/Parsing/Parser.cs @@ -8,7 +8,7 @@ public class Parser private readonly Token[] _tokens; private int _index; - public Parser(IEnumerable tokens) + public Parser(IReadOnlyCollection tokens) { _tokens = tokens.ToArray(); } @@ -181,7 +181,12 @@ public class Parser var returnType = Optional.OfNullable(typeArguments.LastOrDefault()); - return new DelegateType(typeArguments.Take(typeArguments.Count - 1), returnType); + return new DelegateType(typeArguments.Take(typeArguments.Count - 1).ToList(), returnType); + } + + if (name == "pointer") + { + return new StringType(); } return PrimitiveType.Parse(name); diff --git a/Nub.Lang/Nub.Lang/Parsing/Syscall.cs b/Nub.Lang/Nub.Lang/Parsing/Syscall.cs index bf17ff1..93c2a61 100644 --- a/Nub.Lang/Nub.Lang/Parsing/Syscall.cs +++ b/Nub.Lang/Nub.Lang/Parsing/Syscall.cs @@ -1,6 +1,6 @@ namespace Nub.Lang.Parsing; -public class Syscall(IEnumerable parameters) +public class Syscall(IReadOnlyCollection parameters) { - public IEnumerable Parameters { get; } = parameters; + public IReadOnlyCollection Parameters { get; } = parameters; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Program.cs b/Nub.Lang/Nub.Lang/Program.cs index f77b6fe..5492bed 100644 --- a/Nub.Lang/Nub.Lang/Program.cs +++ b/Nub.Lang/Nub.Lang/Program.cs @@ -1,4 +1,5 @@ -using Nub.Lang.Lexing; +using Nub.Lang.Generation; +using Nub.Lang.Lexing; using Nub.Lang.Parsing; using Nub.Lang.Typing; @@ -11,4 +12,11 @@ var parser = new Parser(tokens); var definitions = parser.Parse(); var typer = new ExpressionTyper(definitions); -typer.Populate(); \ No newline at end of file +typer.Populate(); + +var generator = new Generator(definitions); +var asm = generator.Generate(); + +Console.WriteLine(asm); + +File.WriteAllText(args[1], asm); \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Type.cs b/Nub.Lang/Nub.Lang/Type.cs index f154280..ef42cdb 100644 --- a/Nub.Lang/Nub.Lang/Type.cs +++ b/Nub.Lang/Nub.Lang/Type.cs @@ -2,10 +2,15 @@ namespace Nub.Lang; -public abstract class Type; +public abstract record Type; -public class PrimitiveType(PrimitiveTypeKind kind) : Type +public record PrimitiveType : Type { + public PrimitiveType(PrimitiveTypeKind kind) + { + Kind = kind; + } + public static PrimitiveType Parse(string value) { var kind = value switch @@ -28,7 +33,9 @@ public class PrimitiveType(PrimitiveTypeKind kind) : Type return new PrimitiveType(kind); } - public PrimitiveTypeKind Kind { get; } = kind; + public PrimitiveTypeKind Kind { get; } + + public override string ToString() => Kind.ToString(); } public enum PrimitiveTypeKind @@ -47,10 +54,21 @@ public enum PrimitiveTypeKind Double, } -public class PointerType : Type; - -public class DelegateType(IEnumerable parameters, Optional returnType) : Type +public record StringType : Type { - public IEnumerable Parameters { get; } = parameters; - public Optional ReturnType { get; } = returnType; + public override string ToString() => "string"; +} + +public record DelegateType : Type +{ + public DelegateType(IReadOnlyCollection parameters, Optional returnType) + { + Parameters = parameters; + ReturnType = returnType; + } + + public IReadOnlyCollection Parameters { get; } + public Optional ReturnType { get; } + + public override string ToString() => $"({string.Join(", ", Parameters)}): {(ReturnType.HasValue ? ReturnType.Value.ToString() : "")}"; } \ No newline at end of file