Compare commits

..

10 Commits

Author SHA1 Message Date
4761cd1f83 ... 2026-02-08 17:55:15 +01:00
423ec4c798 ... 2026-02-08 16:18:41 +01:00
38f55d8e7c ... 2026-02-08 16:10:39 +01:00
1a5742fc4f ... 2026-02-08 15:53:00 +01:00
4c201c4085 ... 2026-02-08 15:45:53 +01:00
e77c7028b9 ... 2026-02-08 01:22:24 +01:00
26d365cf4f ... 2026-02-08 00:53:55 +01:00
f2ea00b34d ... 2026-02-08 00:21:38 +01:00
cb2411a7eb ... 2026-02-08 00:05:59 +01:00
3b75e62aa7 dev 2026-02-07 19:54:09 +01:00
21 changed files with 1400 additions and 1501 deletions

View File

@@ -4,7 +4,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.LSP", "NubLang.LSP\NubLang.LSP.csproj", "{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Compiler", "Compiler\Compiler.csproj", "{C7D2F2D1-2012-4624-8D62-2EAB34A9F714}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -20,9 +20,9 @@ Global
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Release|Any CPU.Build.0 = Release|Any CPU
{C7D2F2D1-2012-4624-8D62-2EAB34A9F714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7D2F2D1-2012-4624-8D62-2EAB34A9F714}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7D2F2D1-2012-4624-8D62-2EAB34A9F714}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7D2F2D1-2012-4624-8D62-2EAB34A9F714}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

1
compiler/Compiler/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
out.c

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,281 @@
using System.Text;
namespace Compiler;
public sealed class Generator(List<NodeDefinition> nodes)
{
public static string Emit(List<NodeDefinition> nodes)
{
return new Generator(nodes).Emit();
}
private IndentedTextWriter writer = new();
private string Emit()
{
writer.WriteLine("""
#include <float.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct string {
const char *data;
int length;
};
""");
foreach (var node in nodes.OfType<NodeDefinitionFunc>())
{
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();
foreach (var node in nodes.OfType<NodeDefinitionFunc>())
{
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("{");
using (writer.Indent())
{
EmitStatement(node.Body);
}
writer.WriteLine("}");
writer.WriteLine();
}
return writer.ToString();
}
private void EmitStatement(NodeStatement node)
{
switch (node)
{
case NodeStatementBlock statement:
EmitStatementBlock(statement);
break;
case NodeStatementFuncCall statement:
EmitStatementFuncCall(statement);
break;
case NodeStatementReturn statement:
EmitStatementReturn(statement);
break;
case NodeStatementVariableDeclaration statement:
EmitStatementVariableDeclaration(statement);
break;
case NodeStatementAssignment statement:
EmitStatementAssignment(statement);
break;
case NodeStatementIf statement:
EmitStatementIf(statement);
break;
case NodeStatementWhile statement:
EmitStatementWhile(statement);
break;
default:
throw new ArgumentOutOfRangeException(nameof(node), node, null);
}
}
private void EmitStatementBlock(NodeStatementBlock node)
{
writer.WriteLine("{");
using (writer.Indent())
{
foreach (var statement in node.Statements)
EmitStatement(statement);
}
writer.WriteLine("}");
}
private void EmitStatementFuncCall(NodeStatementFuncCall node)
{
var name = EmitExpression(node.Target);
var parameterValues = node.Parameters.Select(EmitExpression).ToList();
writer.WriteLine($"{name}({string.Join(", ", parameterValues)});");
}
private void EmitStatementReturn(NodeStatementReturn statement)
{
var value = EmitExpression(statement.Value);
writer.WriteLine($"return {value};");
}
private void EmitStatementVariableDeclaration(NodeStatementVariableDeclaration statement)
{
var value = EmitExpression(statement.Value);
writer.WriteLine($"{CType(statement.Type)} {statement.Name.Ident} = {value};");
}
private void EmitStatementAssignment(NodeStatementAssignment statement)
{
var target = EmitExpression(statement.Target);
var value = EmitExpression(statement.Value);
writer.WriteLine($"{target} = {value};");
}
private void EmitStatementIf(NodeStatementIf statement)
{
var condition = EmitExpression(statement.Condition);
writer.WriteLine($"if ({condition})");
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.ThenBlock);
}
writer.WriteLine("}");
if (statement.ElseBlock != null)
{
writer.Write("else");
if (statement.ElseBlock is NodeStatementIf)
writer.Write(" ");
else
writer.WriteLine();
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.ElseBlock);
}
writer.WriteLine("}");
}
}
private void EmitStatementWhile(NodeStatementWhile statement)
{
var condition = EmitExpression(statement.Condition);
writer.WriteLine($"while ({condition})");
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.Block);
}
writer.WriteLine("}");
}
private string EmitExpression(NodeExpression node)
{
return node switch
{
NodeExpressionBinary expression => EmitExpressionBinary(expression),
NodeExpressionBoolLiteral expression => expression.Value.Value ? "true" : "false",
NodeExpressionIntLiteral expression => expression.Value.Value.ToString(),
NodeExpressionStringLiteral expression => $"(struct string){{ \"{expression.Value.Value}\", {expression.Value.Value.Length} }}",
NodeExpressionIdent expression => expression.Value.Ident,
_ => throw new ArgumentOutOfRangeException(nameof(node), node, null)
};
}
private string EmitExpressionBinary(NodeExpressionBinary expression)
{
var left = EmitExpression(expression.Left);
var right = EmitExpression(expression.Right);
return expression.Operation switch
{
NodeExpressionBinary.Op.Add => $"({left} + {right})",
NodeExpressionBinary.Op.Subtract => $"({left} - {right})",
NodeExpressionBinary.Op.Multiply => $"({left} * {right})",
NodeExpressionBinary.Op.Divide => $"({left} / {right})",
NodeExpressionBinary.Op.Modulo => $"({left} % {right})",
NodeExpressionBinary.Op.Equal => $"({left} == {right})",
NodeExpressionBinary.Op.NotEqual => $"({left} != {right})",
NodeExpressionBinary.Op.LessThan => $"({left} < {right})",
NodeExpressionBinary.Op.LessThanOrEqual => $"({left} <= {right})",
NodeExpressionBinary.Op.GreaterThan => $"({left} > {right})",
NodeExpressionBinary.Op.GreaterThanOrEqual => $"({left} >= {right})",
NodeExpressionBinary.Op.LeftShift => $"({left} << {right})",
NodeExpressionBinary.Op.RightShift => $"({left} >> {right})",
NodeExpressionBinary.Op.LogicalAnd => $"({left} && {right})",
NodeExpressionBinary.Op.LogicalOr => $"({left} || {right})",
_ => throw new ArgumentOutOfRangeException()
};
}
private static string CType(NodeType node, string? varName = null)
{
return node switch
{
NodeTypeVoid => "void" + (varName != null ? $" {varName}" : ""),
NodeTypeBool => "bool" + (varName != null ? $" {varName}" : ""),
NodeTypeCustom type => $"struct {type}" + (varName != null ? $" {varName}" : ""),
NodeTypeSInt type => $"int{type.Width}_t" + (varName != null ? $" {varName}" : ""),
NodeTypeUInt type => $"uint{type.Width}_t" + (varName != null ? $" {varName}" : ""),
NodeTypePointer type => CType(type.To) + (varName != null ? $" *{varName}" : "*"),
NodeTypeString => "struct string" + (varName != null ? $" {varName}" : ""),
NodeTypeFunc type => $"{CType(type.ReturnType)} (*{varName})({string.Join(", ", type.Parameters.Select(p => CType(p)))})",
_ => throw new ArgumentOutOfRangeException(nameof(node), node, null)
};
}
}
internal class IndentedTextWriter
{
private readonly StringBuilder builder = new();
private int indentLevel;
public IDisposable Indent()
{
indentLevel++;
return new IndentScope(this);
}
public void WriteLine(string text)
{
WriteIndent();
builder.AppendLine(text);
}
public void Write(string text)
{
WriteIndent();
builder.Append(text);
}
public void WriteLine()
{
builder.AppendLine();
}
public override string ToString()
{
return builder.ToString();
}
private void WriteIndent()
{
if (builder.Length > 0)
{
var lastChar = builder[^1];
if (lastChar != '\n' && lastChar != '\r')
return;
}
for (var i = 0; i < indentLevel; i++)
{
builder.Append(" ");
}
}
private class IndentScope(IndentedTextWriter writer) : IDisposable
{
private bool disposed;
public void Dispose()
{
if (disposed) return;
writer.indentLevel--;
disposed = true;
}
}
}

597
compiler/Compiler/Parser.cs Normal file
View File

@@ -0,0 +1,597 @@
using System.Diagnostics.CodeAnalysis;
namespace Compiler;
public sealed class Parser(List<Token> tokens)
{
public static List<NodeDefinition> Parse(List<Token> tokens)
{
return new Parser(tokens).Parse();
}
private int index;
private List<NodeDefinition> Parse()
{
var nodes = new List<NodeDefinition>();
while (Peek() != null)
nodes.Add(ParseDefinition());
return nodes;
}
private NodeDefinition ParseDefinition()
{
var startIndex = index;
if (TryExpectKeyword(Keyword.Func))
{
var name = ExpectIdent();
var parameters = new List<NodeDefinitionFunc.Param>();
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
var paramStartIndex = index;
var parameterName = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var parameterType = ParseType();
parameters.Add(new NodeDefinitionFunc.Param(TokensFrom(paramStartIndex), parameterName, parameterType));
}
ExpectSymbol(Symbol.Colon);
var returnType = ParseType();
var body = ParseStatement();
return new NodeDefinitionFunc(TokensFrom(startIndex), name, parameters, body, returnType);
}
throw new Exception("Not a valid definition");
}
private NodeStatement ParseStatement()
{
var startIndex = index;
if (TryExpectSymbol(Symbol.OpenCurly))
{
var statements = new List<NodeStatement>();
while (!TryExpectSymbol(Symbol.CloseCurly))
statements.Add(ParseStatement());
return new NodeStatementBlock(TokensFrom(startIndex), statements);
}
if (TryExpectKeyword(Keyword.Return))
{
var value = ParseExpression();
return new NodeStatementReturn(TokensFrom(startIndex), value);
}
if (TryExpectKeyword(Keyword.Let))
{
var name = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
ExpectSymbol(Symbol.Equal);
var value = ParseExpression();
return new NodeStatementVariableDeclaration(TokensFrom(startIndex), name, type, value);
}
if (TryExpectKeyword(Keyword.If))
{
var condition = ParseExpression();
var thenBlock = ParseStatement();
NodeStatement? elseBlock = null;
if (TryExpectKeyword(Keyword.Else))
elseBlock = ParseStatement();
return new NodeStatementIf(TokensFrom(startIndex), condition, thenBlock, elseBlock);
}
if (TryExpectKeyword(Keyword.While))
{
var condition = ParseExpression();
var thenBlock = ParseStatement();
return new NodeStatementWhile(TokensFrom(startIndex), condition, thenBlock);
}
var target = ParseExpression();
if (TryExpectSymbol(Symbol.OpenParen))
{
var parameters = new List<NodeExpression>();
while (!TryExpectSymbol(Symbol.CloseParen))
parameters.Add(ParseExpression());
return new NodeStatementFuncCall(TokensFrom(startIndex), target, parameters);
}
if (TryExpectSymbol(Symbol.Equal))
{
var value = ParseExpression();
return new NodeStatementAssignment(TokensFrom(startIndex), target, value);
}
throw new Exception("Not a valid followup for expression statement");
}
private NodeExpression ParseExpression(int minPrecedence = -1)
{
var startIndex = index;
var left = ParseExpressionLeaf();
while (TryPeekBinaryOperator(out var op) && GetPrecedence(op) >= minPrecedence)
{
Consume();
var right = ParseExpression(GetPrecedence(op) + 1);
left = new NodeExpressionBinary(TokensFrom(startIndex), left, op, right);
}
return left;
}
private static int GetPrecedence(NodeExpressionBinary.Op operation)
{
return operation switch
{
NodeExpressionBinary.Op.Multiply => 10,
NodeExpressionBinary.Op.Divide => 10,
NodeExpressionBinary.Op.Modulo => 10,
NodeExpressionBinary.Op.Add => 9,
NodeExpressionBinary.Op.Subtract => 9,
NodeExpressionBinary.Op.LeftShift => 8,
NodeExpressionBinary.Op.RightShift => 8,
NodeExpressionBinary.Op.GreaterThan => 7,
NodeExpressionBinary.Op.GreaterThanOrEqual => 7,
NodeExpressionBinary.Op.LessThan => 7,
NodeExpressionBinary.Op.LessThanOrEqual => 7,
NodeExpressionBinary.Op.Equal => 7,
NodeExpressionBinary.Op.NotEqual => 7,
// NodeExpressionBinary.Op.BitwiseAnd => 6,
// NodeExpressionBinary.Op.BitwiseXor => 5,
// NodeExpressionBinary.Op.BitwiseOr => 4,
NodeExpressionBinary.Op.LogicalAnd => 3,
NodeExpressionBinary.Op.LogicalOr => 2,
_ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null)
};
}
private NodeExpression ParseExpressionLeaf()
{
var startIndex = index;
if (TryExpectSymbol(Symbol.OpenParen))
{
var value = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
return value;
}
if (TryExpectIntLiteral(out var intLiteral))
{
return new NodeExpressionIntLiteral(TokensFrom(startIndex), intLiteral);
}
if (TryExpectStringLiteral(out var stringLiteral))
{
return new NodeExpressionStringLiteral(TokensFrom(startIndex), stringLiteral);
}
if (TryExpectBoolLiteral(out var boolLiteral))
{
return new NodeExpressionBoolLiteral(TokensFrom(startIndex), boolLiteral);
}
if (TryExpectIdent(out var ident))
{
return new NodeExpressionIdent(TokensFrom(startIndex), ident);
}
throw new Exception("Not a valid expression leaf");
}
private NodeType ParseType()
{
var startIndex = index;
if (TryExpectSymbol(Symbol.Caret))
{
var to = ParseType();
return new NodeTypePointer(TokensFrom(startIndex), to);
}
if (TryExpectKeyword(Keyword.Func))
{
var parameters = new List<NodeType>();
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseType());
}
ExpectSymbol(Symbol.Colon);
var returnType = ParseType();
return new NodeTypeFunc(TokensFrom(startIndex), parameters, returnType);
}
if (TryExpectIdent(out var ident))
{
switch (ident.Ident)
{
case "void":
return new NodeTypeVoid(TokensFrom(startIndex));
case "string":
return new NodeTypeString(TokensFrom(startIndex));
case "bool":
return new NodeTypeBool(TokensFrom(startIndex));
case "i8":
return new NodeTypeSInt(TokensFrom(startIndex), 8);
case "i16":
return new NodeTypeSInt(TokensFrom(startIndex), 16);
case "i32":
return new NodeTypeSInt(TokensFrom(startIndex), 32);
case "i64":
return new NodeTypeSInt(TokensFrom(startIndex), 64);
case "u8":
return new NodeTypeUInt(TokensFrom(startIndex), 8);
case "u16":
return new NodeTypeUInt(TokensFrom(startIndex), 16);
case "u32":
return new NodeTypeUInt(TokensFrom(startIndex), 32);
case "u64":
return new NodeTypeUInt(TokensFrom(startIndex), 64);
default:
return new NodeTypeCustom(TokensFrom(startIndex), ident);
}
}
throw new Exception("Not a valid type");
}
private List<Token> TokensFrom(int startIndex)
{
return tokens.GetRange(startIndex, index - startIndex);
}
private bool TryExpectKeyword(Keyword keyword)
{
if (Peek() is TokenKeyword token && token.Keyword == keyword)
{
Consume();
return true;
}
return false;
}
private void ExpectSymbol(Symbol symbol)
{
if (Peek() is TokenSymbol token && token.Symbol == symbol)
{
Consume();
return;
}
throw new Exception($"Expected symbol '{symbol}'");
}
private bool TryExpectSymbol(Symbol symbol)
{
if (Peek() is TokenSymbol token && token.Symbol == symbol)
{
Consume();
return true;
}
return false;
}
private TokenIdent ExpectIdent()
{
if (Peek() is TokenIdent token)
{
Consume();
return token;
}
throw new Exception("Expected ident");
}
private bool TryExpectIdent([NotNullWhen(true)] out TokenIdent? ident)
{
if (Peek() is TokenIdent token)
{
Consume();
ident = token;
return true;
}
ident = null;
return false;
}
private bool TryExpectIntLiteral([NotNullWhen(true)] out TokenIntLiteral? intLiteral)
{
if (Peek() is TokenIntLiteral token)
{
Consume();
intLiteral = token;
return true;
}
intLiteral = null;
return false;
}
private bool TryExpectStringLiteral([NotNullWhen(true)] out TokenStringLiteral? stringLiteral)
{
if (Peek() is TokenStringLiteral token)
{
Consume();
stringLiteral = token;
return true;
}
stringLiteral = null;
return false;
}
private bool TryExpectBoolLiteral([NotNullWhen(true)] out TokenBoolLiteral? boolLiteral)
{
if (Peek() is TokenBoolLiteral token)
{
Consume();
boolLiteral = token;
return true;
}
boolLiteral = null;
return false;
}
private Token Consume()
{
if (index >= tokens.Count)
throw new Exception("End of tokens");
return tokens[index++];
}
private Token? Peek(int offset = 0)
{
if (index + offset >= tokens.Count)
return null;
return tokens[index + offset];
}
private bool TryPeekBinaryOperator(out NodeExpressionBinary.Op op)
{
if (Peek() is not TokenSymbol token)
{
op = default;
return false;
}
switch (token.Symbol)
{
case Symbol.Plus:
op = NodeExpressionBinary.Op.Add;
return true;
case Symbol.Minus:
op = NodeExpressionBinary.Op.Subtract;
return true;
case Symbol.Star:
op = NodeExpressionBinary.Op.Multiply;
return true;
case Symbol.ForwardSlash:
op = NodeExpressionBinary.Op.Divide;
return true;
case Symbol.Percent:
op = NodeExpressionBinary.Op.Modulo;
return true;
case Symbol.BangEqual:
op = NodeExpressionBinary.Op.NotEqual;
return true;
case Symbol.EqualEqual:
op = NodeExpressionBinary.Op.Equal;
return true;
case Symbol.LessThan:
op = NodeExpressionBinary.Op.LessThan;
return true;
case Symbol.LessThanEqual:
op = NodeExpressionBinary.Op.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
op = NodeExpressionBinary.Op.GreaterThan;
return true;
case Symbol.GreaterThanEqual:
op = NodeExpressionBinary.Op.GreaterThanOrEqual;
return true;
case Symbol.LessThanLessThan:
op = NodeExpressionBinary.Op.LeftShift;
return true;
case Symbol.GreaterThanGreaterThan:
op = NodeExpressionBinary.Op.RightShift;
return true;
case Symbol.AmpersandAmpersand:
op = NodeExpressionBinary.Op.LogicalAnd;
return true;
case Symbol.PipePipe:
op = NodeExpressionBinary.Op.LogicalOr;
return true;
default:
op = default;
return false;
}
}
}
public abstract class Node(List<Token> tokens)
{
public readonly List<Token> Tokens = tokens;
}
public abstract class NodeDefinition(List<Token> tokens) : Node(tokens);
public sealed class NodeDefinitionFunc(List<Token> tokens, TokenIdent name, List<NodeDefinitionFunc.Param> parameters, NodeStatement body, NodeType returnType) : NodeDefinition(tokens)
{
public readonly TokenIdent Name = name;
public readonly List<Param> Parameters = parameters;
public readonly NodeStatement Body = body;
public readonly NodeType ReturnType = returnType;
public sealed class Param(List<Token> tokens, TokenIdent name, NodeType type) : Node(tokens)
{
public readonly TokenIdent Name = name;
public readonly NodeType Type = type;
}
}
public abstract class NodeStatement(List<Token> tokens) : Node(tokens);
public sealed class NodeStatementBlock(List<Token> tokens, List<NodeStatement> statements) : NodeStatement(tokens)
{
public readonly List<NodeStatement> Statements = statements;
}
public sealed class NodeStatementFuncCall(List<Token> tokens, NodeExpression target, List<NodeExpression> parameters) : NodeStatement(tokens)
{
public readonly NodeExpression Target = target;
public readonly List<NodeExpression> Parameters = parameters;
}
public class NodeStatementReturn(List<Token> tokens, NodeExpression value) : NodeStatement(tokens)
{
public readonly NodeExpression Value = value;
}
public class NodeStatementVariableDeclaration(List<Token> tokens, TokenIdent name, NodeType type, NodeExpression value) : NodeStatement(tokens)
{
public readonly TokenIdent Name = name;
public readonly NodeType Type = type;
public readonly NodeExpression Value = value;
}
public class NodeStatementAssignment(List<Token> tokens, NodeExpression target, NodeExpression value) : NodeStatement(tokens)
{
public readonly NodeExpression Target = target;
public readonly NodeExpression Value = value;
}
public class NodeStatementIf(List<Token> tokens, NodeExpression condition, NodeStatement thenBlock, NodeStatement? elseBlock) : NodeStatement(tokens)
{
public readonly NodeExpression Condition = condition;
public readonly NodeStatement ThenBlock = thenBlock;
public readonly NodeStatement? ElseBlock = elseBlock;
}
public class NodeStatementWhile(List<Token> tokens, NodeExpression condition, NodeStatement block) : NodeStatement(tokens)
{
public readonly NodeExpression Condition = condition;
public readonly NodeStatement Block = block;
}
public abstract class NodeExpression(List<Token> tokens) : Node(tokens);
public sealed class NodeExpressionIntLiteral(List<Token> tokens, TokenIntLiteral value) : NodeExpression(tokens)
{
public readonly TokenIntLiteral Value = value;
}
public sealed class NodeExpressionStringLiteral(List<Token> tokens, TokenStringLiteral value) : NodeExpression(tokens)
{
public readonly TokenStringLiteral Value = value;
}
public sealed class NodeExpressionBoolLiteral(List<Token> tokens, TokenBoolLiteral value) : NodeExpression(tokens)
{
public readonly TokenBoolLiteral Value = value;
}
public sealed class NodeExpressionIdent(List<Token> tokens, TokenIdent value) : NodeExpression(tokens)
{
public readonly TokenIdent Value = value;
}
public class NodeExpressionBinary(List<Token> tokens, NodeExpression left, NodeExpressionBinary.Op operation, NodeExpression right) : NodeExpression(tokens)
{
public NodeExpression Left { get; } = left;
public readonly Op Operation = operation;
public NodeExpression Right { get; } = right;
public enum Op
{
Add,
Subtract,
Multiply,
Divide,
Modulo,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
LeftShift,
RightShift,
// BitwiseAnd,
// BitwiseXor,
// BitwiseOr,
LogicalAnd,
LogicalOr,
}
}
public abstract class NodeType(List<Token> tokens) : Node(tokens);
public sealed class NodeTypeVoid(List<Token> tokens) : NodeType(tokens);
public sealed class NodeTypeUInt(List<Token> tokens, int width) : NodeType(tokens)
{
public readonly int Width = width;
}
public sealed class NodeTypeSInt(List<Token> tokens, int width) : NodeType(tokens)
{
public readonly int Width = width;
}
public sealed class NodeTypeBool(List<Token> tokens) : NodeType(tokens);
public sealed class NodeTypeString(List<Token> tokens) : NodeType(tokens);
public sealed class NodeTypeCustom(List<Token> tokens, TokenIdent name) : NodeType(tokens)
{
public readonly TokenIdent Name = name;
}
public sealed class NodeTypePointer(List<Token> tokens, NodeType to) : NodeType(tokens)
{
public readonly NodeType To = to;
}
public sealed class NodeTypeFunc(List<Token> tokens, List<NodeType> parameters, NodeType returnType) : NodeType(tokens)
{
public readonly List<NodeType> Parameters = parameters;
public readonly NodeType ReturnType = returnType;
}

View File

@@ -0,0 +1,35 @@
using Compiler;
const string contents = """
func main(): i32 {
let x: i32 = 23
x = 24
if true {
x = 49
} else {
x = 3
}
let i: i32 = 0
x = 1 + 2 * 34
while i < 10 {
i = i + 1
x = i
}
do_something("test")
return x
}
func do_something(text: string): void {
}
""";
var tokens = Tokenizer.Tokenize(contents);
var nodes = Parser.Parse(tokens);
var output = Generator.Emit(nodes);
File.WriteAllText("C:/Users/oliste/repos/nub-lang/compiler/Compiler/out.c", output);

View File

@@ -0,0 +1,464 @@
using System.Numerics;
using System.Text;
namespace Compiler;
public sealed class Tokenizer(string contents)
{
public static List<Token> Tokenize(string contents)
{
return new Tokenizer(contents).Tokenize();
}
private int index;
private int line = 1;
private int column = 1;
private List<Token> Tokenize()
{
var tokens = new List<Token>();
while (true)
{
if (!TryPeek(out var c))
break;
if (char.IsWhiteSpace(c))
{
Consume();
continue;
}
tokens.Add(ParseToken());
}
return tokens;
}
private Token ParseToken()
{
var startColumn = column;
var c = Peek()!.Value;
if (char.IsDigit(c))
{
switch (c)
{
case '0' when Peek(1) is 'x':
{
Consume();
Consume();
var parsed = BigInteger.Zero;
while (TryPeek(out c))
{
if (c == '_')
{
Consume();
continue;
}
if (!char.IsAsciiHexDigit(c))
break;
parsed <<= 4;
Consume();
parsed += c switch
{
>= '0' and <= '9' => c - '0',
>= 'a' and <= 'f' => c - 'a' + 10,
>= 'A' and <= 'F' => c - 'A' + 10,
_ => 0
};
}
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
}
case '0' when Peek(1) is 'b':
{
Consume();
Consume();
var parsed = BigInteger.Zero;
while (TryPeek(out c))
{
if (c == '_')
{
Consume();
continue;
}
if (c is not '0' and not '1')
break;
parsed <<= 1;
if (Consume() == '1')
parsed += BigInteger.One;
}
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
}
default:
{
var parsed = BigInteger.Zero;
while (TryPeek(out c))
{
if (c == '_')
{
Consume();
continue;
}
if (!char.IsDigit(c))
break;
parsed *= 10;
parsed += Consume() - '0';
}
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
}
}
}
switch (c)
{
case '"':
{
Consume();
var buf = new StringBuilder();
while (TryPeek(out c) && c != '"')
buf.Append(Consume());
Consume();
return new TokenStringLiteral(line, startColumn, column - startColumn, buf.ToString());
}
case '{':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.OpenCurly);
}
case '}':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.CloseCurly);
}
case '(':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.OpenParen);
}
case ')':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.CloseParen);
}
case ',':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Comma);
}
case ':':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Colon);
}
case '^':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Caret);
}
case '!' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.BangEqual);
}
case '!':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Bang);
}
case '=' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.EqualEqual);
}
case '=':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Equal);
}
case '<' when Peek(1) is '<':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThanLessThan);
}
case '<' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThanEqual);
}
case '<':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThan);
}
case '>' when Peek(1) is '>':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThanGreaterThan);
}
case '>' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThanEqual);
}
case '>':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThan);
}
case '+' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PlusEqual);
}
case '+':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Plus);
}
case '-' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.MinusEqual);
}
case '-':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Minus);
}
case '*' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.StarEqual);
}
case '*':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Star);
}
case '/' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.ForwardSlashEqual);
}
case '/':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.ForwardSlash);
}
case '%' when Peek(1) is '=':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PercentEqual);
}
case '%':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Percent);
}
case '&' when Peek(1) is '&':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.AmpersandAmpersand);
}
case '&':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Ampersand);
}
case '|' when Peek(1) is '|':
{
Consume();
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PipePipe);
}
case '|':
{
Consume();
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Pipe);
}
default:
{
if (char.IsLetter(c) || c == '_')
{
var buf = new StringBuilder();
while (TryPeek(out c) && (char.IsLetterOrDigit(c) || c == '_'))
buf.Append(Consume());
var value = buf.ToString();
return value switch
{
"func" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Func),
"let" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Let),
"if" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.If),
"else" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Else),
"while" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.While),
"return" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Return),
"true" => new TokenBoolLiteral(line, startColumn, column - startColumn, true),
"false" => new TokenBoolLiteral(line, startColumn, column - startColumn, false),
_ => new TokenIdent(line, startColumn, column - startColumn, value)
};
}
throw new Exception($"Unexpected character '{c}'");
}
}
}
private char Consume()
{
if (index >= contents.Length)
throw new Exception("End of tokens");
var c = contents[index];
if (c == '\n')
{
line += 1;
column = 1;
}
else
{
column += 1;
}
index += 1;
return c;
}
private char? Peek(int offset = 0)
{
if (index + offset >= contents.Length)
return null;
return contents[index + offset];
}
private bool TryPeek(out char c)
{
if (index >= contents.Length)
{
c = '\0';
return false;
}
c = contents[index];
return true;
}
}
public abstract class Token(int line, int column, int length)
{
public int Line = line;
public int Column = column;
public int Length = length;
}
public sealed class TokenIdent(int line, int column, int length, string ident) : Token(line, column, length)
{
public readonly string Ident = ident;
}
public sealed class TokenIntLiteral(int line, int column, int length, BigInteger value) : Token(line, column, length)
{
public BigInteger Value = value;
}
public sealed class TokenStringLiteral(int line, int column, int length, string value) : Token(line, column, length)
{
public readonly string Value = value;
}
public sealed class TokenBoolLiteral(int line, int column, int length, bool value) : Token(line, column, length)
{
public readonly bool Value = value;
}
public enum Symbol
{
OpenCurly,
CloseCurly,
OpenParen,
CloseParen,
Comma,
Colon,
Caret,
Bang,
Equal,
EqualEqual,
BangEqual,
LessThan,
LessThanLessThan,
LessThanEqual,
GreaterThan,
GreaterThanGreaterThan,
GreaterThanEqual,
Plus,
PlusEqual,
Minus,
MinusEqual,
Star,
StarEqual,
ForwardSlash,
ForwardSlashEqual,
Percent,
PercentEqual,
Ampersand,
AmpersandAmpersand,
Pipe,
PipePipe,
}
public sealed class TokenSymbol(int line, int column, int length, Symbol symbol) : Token(line, column, length)
{
public readonly Symbol Symbol = symbol;
}
public enum Keyword
{
Func,
Let,
If,
Else,
While,
Return,
}
public sealed class TokenKeyword(int line, int column, int length, Keyword keyword) : Token(line, column, length)
{
public readonly Keyword Keyword = keyword;
}

View File

@@ -1,7 +1,5 @@
using System.Diagnostics;
using NubLang.Ast;
using NubLang.Ast;
using NubLang.Diagnostics;
using NubLang.Generation;
using NubLang.Syntax;
var diagnostics = new List<Diagnostic>();
@@ -42,50 +40,4 @@ if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Erro
return 1;
}
var cPaths = new List<string>();
Directory.CreateDirectory(".build");
for (var i = 0; i < args.Length; i++)
{
var file = args[i];
var compilationUnit = compilationUnits[i];
var generator = new Generator(compilationUnit);
var directory = Path.GetDirectoryName(file);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(Path.Combine(".build", directory));
}
var path = Path.Combine(".build", Path.ChangeExtension(file, "c"));
File.WriteAllText(path, generator.Emit());
cPaths.Add(path);
}
var objectPaths = new List<string>();
foreach (var cPath in cPaths)
{
var objectPath = Path.ChangeExtension(cPath, "o");
using var compileProcess = Process.Start("clang", [
"-ffreestanding", "-std=c23",
"-g", "-c",
"-o", objectPath,
cPath,
]);
compileProcess.WaitForExit();
if (compileProcess.ExitCode != 0)
{
Console.Error.WriteLine($"clang failed with exit code {compileProcess.ExitCode}");
return 1;
}
objectPaths.Add(objectPath);
}
Console.Out.WriteLine(string.Join(' ', objectPaths));
return 0;

View File

@@ -1,77 +0,0 @@
using NubLang.Ast;
using NubLang.Syntax;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
namespace NubLang.LSP;
public static class AstExtensions
{
public static Location ToLocation(this Node node)
{
if (node.Tokens.Count == 0)
{
return new Location();
}
return new Location
{
Uri = node.Tokens.First().Span.FilePath,
Range = new Range(node.Tokens.First().Span.Start.Line - 1, node.Tokens.First().Span.Start.Column - 1, node.Tokens.Last().Span.End.Line - 1, node.Tokens.Last().Span.End.Column - 1)
};
}
public static bool ContainsPosition(this Node node, int line, int character)
{
if (node.Tokens.Count == 0)
{
return false;
}
var start = node.Tokens.First().Span.Start;
var end = node.Tokens.Last().Span.End;
var startLine = start.Line - 1;
var startChar = start.Column - 1;
var endLine = end.Line - 1;
var endChar = end.Column - 1;
if (line < startLine || line > endLine) return false;
if (line > startLine && line < endLine) return true;
if (startLine == endLine)
{
return character >= startChar && character <= endChar;
}
if (line == startLine)
{
return character >= startChar;
}
if (line == endLine)
{
return character <= endChar;
}
return false;
}
public static FuncNode? FunctionAtPosition(this CompilationUnit compilationUnit, int line, int character)
{
return compilationUnit
.Functions
.FirstOrDefault(x => x.ContainsPosition(line, character));
}
public static Node? DeepestNodeAtPosition(this CompilationUnit compilationUnit, int line, int character)
{
return compilationUnit.Functions
.SelectMany(x => x.DescendantsAndSelf())
.Where(n => n.ContainsPosition(line, character))
.OrderBy(n => n.Tokens.First().Span.Start.Line)
.ThenBy(n => n.Tokens.First().Span.Start.Column)
.LastOrDefault();
}
}

View File

@@ -1,180 +0,0 @@
using NubLang.Ast;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
namespace NubLang.LSP;
internal class CompletionHandler(WorkspaceManager workspaceManager) : CompletionHandlerBase
{
private readonly CompletionItem[] _definitionSnippets =
[
new()
{
Kind = CompletionItemKind.Keyword,
Label = "func",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "func ${1:name}(${2:params})\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "struct",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "struct ${1:name}\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "module",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "module \"$0\"",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "import",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "import \"$0\"",
}
];
private readonly CompletionItem[] _statementSnippets =
[
new()
{
Kind = CompletionItemKind.Keyword,
Label = "let",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "let ${1:name} = $0",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "if",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "if ${1:condition}\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "else if",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "else if ${1:condition}\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "else",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "else\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "while",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "while ${1:condition}\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "for",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "for ${1:name}, ${2:index} in ${3:array}\n{\n $0\n}",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "return",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "return $0",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "defer",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "defer $0",
}
];
protected override CompletionRegistrationOptions CreateRegistrationOptions(CompletionCapability capability, ClientCapabilities clientCapabilities)
{
return new CompletionRegistrationOptions();
}
public override Task<CompletionList> Handle(CompletionParams request, CancellationToken cancellationToken)
{
return Task.FromResult(HandleSync(request, cancellationToken));
}
private CompletionList HandleSync(CompletionParams request, CancellationToken cancellationToken)
{
var completions = new List<CompletionItem>();
var position = request.Position;
var uri = request.TextDocument.Uri;
var compilationUnit = workspaceManager.GetCompilationUnit(uri);
if (compilationUnit != null)
{
var function = compilationUnit.Functions.FirstOrDefault(x => x.Body != null && x.Body.ContainsPosition(position.Line, position.Character));
if (function != null)
{
completions.AddRange(_statementSnippets);
foreach (var prototype in compilationUnit.ImportedFunctions)
{
var parameterStrings = new List<string>();
foreach (var (index, parameter) in prototype.Parameters.Index())
{
parameterStrings.AddRange($"${{{index + 1}:{parameter.Name}}}");
}
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Function,
Label = $"{prototype.Module}::{prototype.Name}",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = $"{prototype.Module}::{prototype.Name}({string.Join(", ", parameterStrings)})",
});
}
foreach (var parameter in function.Prototype.Parameters)
{
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Variable,
Label = parameter.Name,
InsertText = parameter.Name,
});
}
var variables = function.Body!
.Descendants()
.OfType<VariableDeclarationNode>();
foreach (var variable in variables)
{
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Variable,
Label = variable.Name,
InsertText = variable.Name,
});
}
}
else
{
completions.AddRange(_definitionSnippets);
}
}
return new CompletionList(completions, false);
}
public override Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
{
return Task.FromResult(new CompletionItem());
}
}

View File

@@ -1,74 +0,0 @@
using NubLang.Ast;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
namespace NubLang.LSP;
internal class DefinitionHandler(WorkspaceManager workspaceManager) : DefinitionHandlerBase
{
protected override DefinitionRegistrationOptions CreateRegistrationOptions(DefinitionCapability capability, ClientCapabilities clientCapabilities)
{
return new DefinitionRegistrationOptions();
}
public override Task<LocationOrLocationLinks?> Handle(DefinitionParams request, CancellationToken cancellationToken)
{
return Task.FromResult(HandleSync(request, cancellationToken));
}
private LocationOrLocationLinks? HandleSync(DefinitionParams request, CancellationToken cancellationToken)
{
var uri = request.TextDocument.Uri;
var compilationUnit = workspaceManager.GetCompilationUnit(uri);
if (compilationUnit == null)
{
return null;
}
var line = request.Position.Line;
var character = request.Position.Character;
var node = compilationUnit.DeepestNodeAtPosition(line, character);
switch (node)
{
case VariableIdentifierNode variableIdentifierNode:
{
var function = compilationUnit.FunctionAtPosition(line, character);
var parameter = function?.Prototype.Parameters.FirstOrDefault(x => x.Name == variableIdentifierNode.Name);
if (parameter != null)
{
return new LocationOrLocationLinks(parameter.ToLocation());
}
var variable = function?.Body?
.Descendants()
.OfType<VariableDeclarationNode>()
.FirstOrDefault(x => x.Name == variableIdentifierNode.Name);
if (variable != null)
{
return new LocationOrLocationLinks(variable.ToLocation());
}
return null;
}
case FuncIdentifierNode funcIdentifierNode:
{
var prototype = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name);
if (prototype != null)
{
return new LocationOrLocationLinks(prototype.ToLocation());
}
return null;
}
default:
{
return null;
}
}
}
}

View File

@@ -1,44 +0,0 @@
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
namespace NubLang.LSP;
public class DiagnosticsPublisher
{
private readonly ILanguageServerFacade _server;
public DiagnosticsPublisher(ILanguageServerFacade server)
{
_server = server;
}
public void Publish(DocumentUri uri, IEnumerable<Diagnostics.Diagnostic> diagnostics)
{
_server.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams
{
Uri = uri,
Diagnostics = new Container<Diagnostic>(diagnostics.Select(MapDiagnostic))
});
}
private static Diagnostic MapDiagnostic(Diagnostics.Diagnostic nubDiagnostic)
{
return new Diagnostic
{
Severity = nubDiagnostic.Severity switch
{
Diagnostics.DiagnosticSeverity.Info => DiagnosticSeverity.Information,
Diagnostics.DiagnosticSeverity.Warning => DiagnosticSeverity.Warning,
Diagnostics.DiagnosticSeverity.Error => DiagnosticSeverity.Error,
_ => null
},
Message = $"{nubDiagnostic.Message}\n{(nubDiagnostic.Help == null ? "" : $"help: {nubDiagnostic.Help}")}",
Range = nubDiagnostic.Span.HasValue
? new Range(nubDiagnostic.Span.Value.Start.Line - 1, nubDiagnostic.Span.Value.Start.Column - 1, nubDiagnostic.Span.Value.End.Line - 1, nubDiagnostic.Span.Value.End.Column - 1)
: new Range(),
};
}
}

View File

@@ -1,142 +0,0 @@
using System.Globalization;
using NubLang.Ast;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
namespace NubLang.LSP;
internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBase
{
protected override HoverRegistrationOptions CreateRegistrationOptions(HoverCapability capability, ClientCapabilities clientCapabilities)
{
return new HoverRegistrationOptions
{
DocumentSelector = TextDocumentSelector.ForLanguage("nub")
};
}
public override Task<Hover?> Handle(HoverParams request, CancellationToken cancellationToken)
{
return Task.FromResult(HandleSync(request, cancellationToken));
}
private Hover? HandleSync(HoverParams request, CancellationToken cancellationToken)
{
var compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri);
if (compilationUnit == null)
{
return null;
}
var line = request.Position.Line;
var character = request.Position.Character;
var hoveredNode = compilationUnit.DeepestNodeAtPosition(line, character);
if (hoveredNode == null)
{
return null;
}
var message = CreateMessage(hoveredNode, compilationUnit);
if (message == null)
{
return null;
}
return new Hover
{
Contents = new MarkedStringsOrMarkupContent(new MarkupContent
{
Value = message,
Kind = MarkupKind.Markdown,
})
};
}
private static string? CreateMessage(Node hoveredNode, CompilationUnit compilationUnit)
{
return hoveredNode switch
{
FuncNode funcNode => CreateFuncPrototypeMessage(funcNode.Prototype),
FuncPrototypeNode funcPrototypeNode => CreateFuncPrototypeMessage(funcPrototypeNode),
FuncIdentifierNode funcIdentifierNode => CreateFuncIdentifierMessage(funcIdentifierNode, compilationUnit),
FuncParameterNode funcParameterNode => CreateTypeNameMessage("Function parameter", funcParameterNode.Name, funcParameterNode.Type),
VariableIdentifierNode variableIdentifierNode => CreateTypeNameMessage("Variable", variableIdentifierNode.Name, variableIdentifierNode.Type),
VariableDeclarationNode variableDeclarationNode => CreateTypeNameMessage("Variable declaration", variableDeclarationNode.Name, variableDeclarationNode.Type),
StructFieldAccessNode structFieldAccessNode => CreateTypeNameMessage("Struct field", $"{structFieldAccessNode.Target.Type}.{structFieldAccessNode.Field}", structFieldAccessNode.Type),
CStringLiteralNode cStringLiteralNode => CreateLiteralMessage(cStringLiteralNode.Type, '"' + cStringLiteralNode.Value + '"'),
StringLiteralNode stringLiteralNode => CreateLiteralMessage(stringLiteralNode.Type, '"' + stringLiteralNode.Value + '"'),
BoolLiteralNode boolLiteralNode => CreateLiteralMessage(boolLiteralNode.Type, boolLiteralNode.Value.ToString()),
Float32LiteralNode float32LiteralNode => CreateLiteralMessage(float32LiteralNode.Type, float32LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
Float64LiteralNode float64LiteralNode => CreateLiteralMessage(float64LiteralNode.Type, float64LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
I8LiteralNode i8LiteralNode => CreateLiteralMessage(i8LiteralNode.Type, i8LiteralNode.Value.ToString()),
I16LiteralNode i16LiteralNode => CreateLiteralMessage(i16LiteralNode.Type, i16LiteralNode.Value.ToString()),
I32LiteralNode i32LiteralNode => CreateLiteralMessage(i32LiteralNode.Type, i32LiteralNode.Value.ToString()),
I64LiteralNode i64LiteralNode => CreateLiteralMessage(i64LiteralNode.Type, i64LiteralNode.Value.ToString()),
U8LiteralNode u8LiteralNode => CreateLiteralMessage(u8LiteralNode.Type, u8LiteralNode.Value.ToString()),
U16LiteralNode u16LiteralNode => CreateLiteralMessage(u16LiteralNode.Type, u16LiteralNode.Value.ToString()),
U32LiteralNode u32LiteralNode => CreateLiteralMessage(u32LiteralNode.Type, u32LiteralNode.Value.ToString()),
U64LiteralNode u64LiteralNode => CreateLiteralMessage(u64LiteralNode.Type, u64LiteralNode.Value.ToString()),
// Expressions can have a generic fallback showing the resulting type
ExpressionNode expressionNode => $"""
**Expression** `{expressionNode.GetType().Name}`
```nub
{expressionNode.Type}
```
""",
BlockNode => null,
_ => hoveredNode.GetType().Name
};
}
private static string CreateLiteralMessage(NubType type, string value)
{
return $"""
**Literal** `{type}`
```nub
{value}: {type}
```
""";
}
private static string CreateTypeNameMessage(string description, string name, NubType type)
{
return $"""
**{description}** `{name}`
```nub
{name}: {type}
```
""";
}
private static string CreateFuncIdentifierMessage(FuncIdentifierNode funcIdentifierNode, CompilationUnit compilationUnit)
{
var func = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name);
if (func == null)
{
return $"""
**Function** `{funcIdentifierNode.Module}::{funcIdentifierNode.Name}`
```nub
// Declaration not found
```
""";
}
return CreateFuncPrototypeMessage(func);
}
private static string CreateFuncPrototypeMessage(FuncPrototypeNode funcPrototypeNode)
{
var parameterText = string.Join(", ", funcPrototypeNode.Parameters.Select(x => $"{x.Name}: {x.Type}"));
var externText = funcPrototypeNode.ExternSymbol != null ? $"extern \"{funcPrototypeNode.ExternSymbol}\" " : "";
return $"""
**Function** `{funcPrototypeNode.Module}::{funcPrototypeNode.Name}`
```nub
{externText}func {funcPrototypeNode.Module}::{funcPrototypeNode.Name}({parameterText}): {funcPrototypeNode.ReturnType}
```
""";
}
}

View File

@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>nublsp</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="0.19.9" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NubLang\NubLang.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,34 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NubLang.LSP;
using OmniSharp.Extensions.LanguageServer.Server;
var server = await LanguageServer.From(options => options
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput())
.WithServices(services =>
{
services.AddSingleton<DiagnosticsPublisher>();
services.AddSingleton<WorkspaceManager>();
})
.ConfigureLogging(x => x
.AddLanguageProtocolLogging()
.SetMinimumLevel(LogLevel.Debug))
.WithHandler<TextDocumentSyncHandler>()
.WithHandler<HoverHandler>()
.WithHandler<CompletionHandler>()
.WithHandler<DefinitionHandler>()
.OnInitialize((server, request, ct) =>
{
var workspaceManager = server.GetRequiredService<WorkspaceManager>();
if (request.RootPath != null)
{
workspaceManager.Init(request.RootPath);
}
return Task.CompletedTask;
})
);
await server.WaitForExit;

View File

@@ -1,44 +0,0 @@
using MediatR;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
namespace NubLang.LSP;
internal class TextDocumentSyncHandler(WorkspaceManager workspaceManager) : TextDocumentSyncHandlerBase
{
public override TextDocumentAttributes GetTextDocumentAttributes(DocumentUri uri)
{
return new TextDocumentAttributes(uri, "nub");
}
public override Task<Unit> Handle(DidOpenTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidChangeTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidSaveTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidCloseTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
protected override TextDocumentSyncRegistrationOptions CreateRegistrationOptions(TextSynchronizationCapability capability, ClientCapabilities clientCapabilities)
{
return new TextDocumentSyncRegistrationOptions();
}
}

View File

@@ -1,74 +0,0 @@
using NubLang.Ast;
using NubLang.Syntax;
using OmniSharp.Extensions.LanguageServer.Protocol;
namespace NubLang.LSP;
public class WorkspaceManager(DiagnosticsPublisher diagnosticsPublisher)
{
private readonly Dictionary<string, SyntaxTree> _syntaxTrees = new();
private readonly Dictionary<string, CompilationUnit> _compilationUnits = new();
public void Init(string rootPath)
{
var files = Directory.GetFiles(rootPath, "*.nub", SearchOption.AllDirectories);
foreach (var path in files)
{
var text = File.ReadAllText(path);
var tokenizer = new Tokenizer(path, text);
tokenizer.Tokenize();
diagnosticsPublisher.Publish(path, tokenizer.Diagnostics);
var parser = new Parser();
var parseResult = parser.Parse(tokenizer.Tokens);
diagnosticsPublisher.Publish(path, parser.Diagnostics);
_syntaxTrees[path] = parseResult;
}
foreach (var (fsPath, syntaxTree) in _syntaxTrees)
{
var modules = Module.Collect(_syntaxTrees.Select(x => x.Value).ToList());
var typeChecker = new TypeChecker(syntaxTree, modules);
var result = typeChecker.Check();
diagnosticsPublisher.Publish(fsPath, typeChecker.Diagnostics);
_compilationUnits[fsPath] = result;
}
}
public void UpdateFile(DocumentUri path)
{
var fsPath = path.GetFileSystemPath();
var text = File.ReadAllText(fsPath);
var tokenizer = new Tokenizer(fsPath, text);
tokenizer.Tokenize();
diagnosticsPublisher.Publish(path, tokenizer.Diagnostics);
var parser = new Parser();
var syntaxTree = parser.Parse(tokenizer.Tokens);
diagnosticsPublisher.Publish(path, parser.Diagnostics);
_syntaxTrees[fsPath] = syntaxTree;
var modules = Module.Collect(_syntaxTrees.Select(x => x.Value).ToList());
var typeChecker = new TypeChecker(syntaxTree, modules);
var result = typeChecker.Check();
diagnosticsPublisher.Publish(fsPath, typeChecker.Diagnostics);
_compilationUnits[fsPath] = result;
}
public void RemoveFile(DocumentUri path)
{
var fsPath = path.GetFileSystemPath();
_syntaxTrees.Remove(fsPath);
_compilationUnits.Remove(fsPath);
}
public CompilationUnit? GetCompilationUnit(DocumentUri path)
{
return _compilationUnits.GetValueOrDefault(path.GetFileSystemPath());
}
}

View File

@@ -1,97 +0,0 @@
using NubLang.Ast;
namespace NubLang.Generation;
public static class CType
{
public static string Create(NubType type, string? variableName = null, bool constArraysAsPointers = true)
{
return type switch
{
NubVoidType => "void" + (variableName != null ? $" {variableName}" : ""),
NubBoolType => "bool" + (variableName != null ? $" {variableName}" : ""),
NubIntType intType => CreateIntType(intType, variableName),
NubFloatType floatType => CreateFloatType(floatType, variableName),
NubPointerType ptr => CreatePointerType(ptr, variableName),
NubSliceType => "struct nub_slice" + (variableName != null ? $" {variableName}" : ""),
NubStringType => "struct nub_string" + (variableName != null ? $" {variableName}" : ""),
NubConstArrayType arr => CreateConstArrayType(arr, variableName, constArraysAsPointers),
NubArrayType arr => CreateArrayType(arr, variableName),
NubFuncType fn => CreateFuncType(fn, variableName),
NubStructType st => $"struct {st.Module}_{st.Name}_{NameMangler.Mangle(st)}" + (variableName != null ? $" {variableName}" : ""),
_ => throw new NotSupportedException($"C type generation not supported for: {type}")
};
}
private static string CreateIntType(NubIntType intType, string? varName)
{
var cType = intType.Width switch
{
8 => intType.Signed ? "char" : "unsigned char",
16 => intType.Signed ? "short" : "unsigned short",
32 => intType.Signed ? "int" : "unsigned int",
64 => intType.Signed ? "long long" : "unsigned long long",
_ => throw new NotSupportedException($"Unsupported integer width: {intType.Width}")
};
return cType + (varName != null ? $" {varName}" : "");
}
private static string CreateFloatType(NubFloatType floatType, string? varName)
{
var cType = floatType.Width switch
{
32 => "float",
64 => "double",
_ => throw new NotSupportedException($"Unsupported float width: {floatType.Width}")
};
return cType + (varName != null ? $" {varName}" : "");
}
private static string CreatePointerType(NubPointerType ptr, string? varName)
{
var baseType = Create(ptr.BaseType);
return baseType + "*" + (varName != null ? $" {varName}" : "");
}
private static string CreateConstArrayType(NubConstArrayType arr, string? varName, bool inStructDef)
{
var elementType = Create(arr.ElementType);
// Treat const arrays as pointers unless in a struct definition
if (!inStructDef)
{
return elementType + "*" + (varName != null ? $" {varName}" : "");
}
if (varName != null)
{
return $"{elementType} {varName}[{arr.Size}]";
}
return $"{elementType}[{arr.Size}]";
}
private static string CreateArrayType(NubArrayType arr, string? varName)
{
var elementType = Create(arr.ElementType);
return elementType + "*" + (varName != null ? $" {varName}" : "");
}
private static string CreateFuncType(NubFuncType fn, string? varName)
{
var returnType = Create(fn.ReturnType);
var parameters = string.Join(", ", fn.Parameters.Select(p => Create(p)));
if (string.IsNullOrEmpty(parameters))
{
parameters = "void";
}
if (varName != null)
{
return $"{returnType} (*{varName})({parameters})";
}
return $"{returnType} (*)({parameters})";
}
}

View File

@@ -1,590 +0,0 @@
using System.Diagnostics;
using System.Text;
using NubLang.Ast;
using NubLang.Syntax;
namespace NubLang.Generation;
public class Generator
{
private readonly CompilationUnit _compilationUnit;
private readonly IndentedTextWriter _writer;
private readonly Stack<List<DeferNode>> _deferStack = [];
private int _tmpIndex;
public Generator(CompilationUnit compilationUnit)
{
_compilationUnit = compilationUnit;
_writer = new IndentedTextWriter();
}
// todo(nub31): Handle name collisions
private string NewTmp()
{
return $"_t{++_tmpIndex}";
}
private static string FuncName(string module, string name, string? externSymbol)
{
return externSymbol ?? $"{module}_{name}";
}
public string Emit()
{
_writer.WriteLine("""
struct nub_string
{
unsigned long long length;
char *data;
};
struct nub_slice
{
unsigned long long length;
void *data;
};
""");
foreach (var structType in _compilationUnit.ImportedStructTypes)
{
_writer.WriteLine(CType.Create(structType));
_writer.WriteLine("{");
using (_writer.Indent())
{
foreach (var field in structType.Fields)
{
_writer.WriteLine($"{CType.Create(field.Type, field.Name, constArraysAsPointers: false)};");
}
}
_writer.WriteLine("};");
_writer.WriteLine();
}
// note(nub31): Forward declarations
foreach (var prototype in _compilationUnit.ImportedFunctions)
{
EmitLine(prototype.Tokens.FirstOrDefault());
var parameters = prototype.Parameters.Count != 0
? string.Join(", ", prototype.Parameters.Select(x => CType.Create(x.Type, x.Name)))
: "void";
var name = FuncName(prototype.Module, prototype.Name, prototype.ExternSymbol);
_writer.WriteLine($"{CType.Create(prototype.ReturnType, name)}({parameters});");
_writer.WriteLine();
}
// note(nub31): Normal functions
foreach (var funcNode in _compilationUnit.Functions)
{
if (funcNode.Body == null) continue;
EmitLine(funcNode.Tokens.FirstOrDefault());
var parameters = funcNode.Prototype.Parameters.Count != 0
? string.Join(", ", funcNode.Prototype.Parameters.Select(x => CType.Create(x.Type, x.Name)))
: "void";
var name = FuncName(funcNode.Module, funcNode.Name, funcNode.Prototype.ExternSymbol);
_writer.WriteLine($"{CType.Create(funcNode.Prototype.ReturnType, name)}({parameters})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(funcNode.Body);
}
_writer.WriteLine("}");
_writer.WriteLine();
}
return _writer.ToString();
}
private void EmitStatement(StatementNode statementNode)
{
EmitLine(statementNode.Tokens.FirstOrDefault());
switch (statementNode)
{
case AssignmentNode assignmentNode:
EmitAssignment(assignmentNode);
break;
case BlockNode blockNode:
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(blockNode);
}
_writer.WriteLine("}");
break;
case BreakNode breakNode:
EmitBreak(breakNode);
break;
case ContinueNode continueNode:
EmitContinue(continueNode);
break;
case DeferNode deferNode:
EmitDefer(deferNode);
break;
case ForConstArrayNode forConstArrayNode:
EmitForConstArray(forConstArrayNode);
break;
case ForSliceNode forSliceNode:
EmitForSlice(forSliceNode);
break;
case IfNode ifNode:
EmitIf(ifNode);
break;
case ReturnNode returnNode:
EmitReturn(returnNode);
break;
case StatementFuncCallNode statementFuncCallNode:
EmitStatementFuncCall(statementFuncCallNode);
break;
case VariableDeclarationNode variableDeclarationNode:
EmitVariableDeclaration(variableDeclarationNode);
break;
case WhileNode whileNode:
EmitWhile(whileNode);
break;
default:
throw new ArgumentOutOfRangeException(nameof(statementNode));
}
}
private void EmitLine(Token? token)
{
if (token == null) return;
var file = token.Span.FilePath;
var line = token.Span.Start.Line;
_writer.WriteLine($"#line {line} \"{file}\"");
}
private void EmitAssignment(AssignmentNode assignmentNode)
{
var target = EmitExpression(assignmentNode.Target);
var value = EmitExpression(assignmentNode.Value);
_writer.WriteLine($"{target} = {value};");
}
private void EmitBreak(BreakNode _)
{
// todo(nub31): Emit deferred statements
_writer.WriteLine("break;");
}
private void EmitContinue(ContinueNode _)
{
// todo(nub31): Emit deferred statements
_writer.WriteLine("continue;");
}
private void EmitDefer(DeferNode deferNode)
{
_deferStack.Peek().Add(deferNode);
}
private void EmitForSlice(ForSliceNode forSliceNode)
{
var targetType = (NubSliceType)forSliceNode.Target.Type;
var target = EmitExpression(forSliceNode.Target);
var indexName = forSliceNode.IndexName ?? NewTmp();
_writer.WriteLine($"for (unsigned long long {indexName} = 0; {indexName} < {target}.length; ++{indexName})");
_writer.WriteLine("{");
using (_writer.Indent())
{
_writer.WriteLine($"{CType.Create(targetType.ElementType, forSliceNode.ElementName)} = (({CType.Create(targetType.ElementType)}*){target}.data)[{indexName}];");
EmitBlock(forSliceNode.Body);
}
_writer.WriteLine("}");
}
private void EmitForConstArray(ForConstArrayNode forConstArrayNode)
{
var targetType = (NubConstArrayType)forConstArrayNode.Target.Type;
var target = EmitExpression(forConstArrayNode.Target);
var indexName = forConstArrayNode.IndexName ?? NewTmp();
_writer.WriteLine($"for (unsigned long long {indexName} = 0; {indexName} < {targetType.Size}; ++{indexName})");
_writer.WriteLine("{");
using (_writer.Indent())
{
_writer.WriteLine($"{CType.Create(targetType.ElementType, forConstArrayNode.ElementName)} = {target}[{indexName}];");
EmitBlock(forConstArrayNode.Body);
}
_writer.WriteLine("}");
}
private void EmitIf(IfNode ifNode, bool elseIf = false)
{
var condition = EmitExpression(ifNode.Condition);
_writer.WriteLine($"{(elseIf ? "else " : "")}if ({condition})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(ifNode.Body);
}
_writer.WriteLine("}");
ifNode.Else?.Match
(
elseIfNode => EmitIf(elseIfNode, true),
elseNode =>
{
_writer.WriteLine("else");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(elseNode);
}
_writer.WriteLine("}");
}
);
}
private void EmitReturn(ReturnNode returnNode)
{
if (returnNode.Value == null)
{
var blockDefers = _deferStack.Peek();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
_writer.WriteLine("return;");
}
else
{
var returnValue = EmitExpression(returnNode.Value);
if (_deferStack.Peek().Count != 0)
{
var tmp = NewTmp();
_writer.WriteLine($"{CType.Create(returnNode.Value.Type, tmp)} = {returnValue};");
var blockDefers = _deferStack.Peek();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
EmitLine(returnNode.Tokens.FirstOrDefault());
_writer.WriteLine($"return {tmp};");
}
else
{
EmitLine(returnNode.Tokens.FirstOrDefault());
_writer.WriteLine($"return {returnValue};");
}
}
}
private void EmitStatementFuncCall(StatementFuncCallNode statementFuncCallNode)
{
var funcCall = EmitFuncCall(statementFuncCallNode.FuncCall);
_writer.WriteLine($"{funcCall};");
}
private void EmitVariableDeclaration(VariableDeclarationNode variableDeclarationNode)
{
if (variableDeclarationNode.Assignment != null)
{
var value = EmitExpression(variableDeclarationNode.Assignment);
_writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)} = {value};");
}
else
{
_writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)};");
}
}
private void EmitWhile(WhileNode whileNode)
{
var condition = EmitExpression(whileNode.Condition);
_writer.WriteLine($"while ({condition})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(whileNode.Body);
}
_writer.WriteLine("}");
}
private string EmitExpression(ExpressionNode expressionNode)
{
if (expressionNode is IntermediateExpression)
{
throw new UnreachableException("Type checker fucked up");
}
var expr = expressionNode switch
{
ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(arrayIndexAccessNode),
ArrayInitializerNode arrayInitializerNode => EmitArrayInitializer(arrayInitializerNode),
BinaryExpressionNode binaryExpressionNode => EmitBinaryExpression(binaryExpressionNode),
BoolLiteralNode boolLiteralNode => boolLiteralNode.Value ? "true" : "false",
ConstArrayIndexAccessNode constArrayIndexAccessNode => EmitConstArrayIndexAccess(constArrayIndexAccessNode),
ConstArrayInitializerNode constArrayInitializerNode => EmitConstArrayInitializer(constArrayInitializerNode),
CStringLiteralNode cStringLiteralNode => $"\"{cStringLiteralNode.Value}\"",
DereferenceNode dereferenceNode => EmitDereference(dereferenceNode),
Float32LiteralNode float32LiteralNode => EmitFloat32Literal(float32LiteralNode),
Float64LiteralNode float64LiteralNode => EmitFloat64Literal(float64LiteralNode),
CastNode castNode => EmitCast(castNode),
FuncCallNode funcCallNode => EmitFuncCall(funcCallNode),
FuncIdentifierNode funcIdentifierNode => FuncName(funcIdentifierNode.Module, funcIdentifierNode.Name, funcIdentifierNode.ExternSymbol),
AddressOfNode addressOfNode => EmitAddressOf(addressOfNode),
SizeNode sizeBuiltinNode => $"sizeof({CType.Create(sizeBuiltinNode.TargetType)})",
SliceIndexAccessNode sliceIndexAccessNode => EmitSliceArrayIndexAccess(sliceIndexAccessNode),
StringLiteralNode stringLiteralNode => EmitStringLiteral(stringLiteralNode),
StructFieldAccessNode structFieldAccessNode => EmitStructFieldAccess(structFieldAccessNode),
StructInitializerNode structInitializerNode => EmitStructInitializer(structInitializerNode),
I8LiteralNode i8LiteralNode => EmitI8Literal(i8LiteralNode),
I16LiteralNode i16LiteralNode => EmitI16Literal(i16LiteralNode),
I32LiteralNode i32LiteralNode => EmitI32Literal(i32LiteralNode),
I64LiteralNode i64LiteralNode => EmitI64Literal(i64LiteralNode),
U8LiteralNode u8LiteralNode => EmitU8Literal(u8LiteralNode),
U16LiteralNode u16LiteralNode => EmitU16Literal(u16LiteralNode),
U32LiteralNode u32LiteralNode => EmitU32Literal(u32LiteralNode),
U64LiteralNode u64LiteralNode => EmitU64Literal(u64LiteralNode),
UnaryExpressionNode unaryExpressionNode => EmitUnaryExpression(unaryExpressionNode),
VariableIdentifierNode variableIdentifierNode => variableIdentifierNode.Name,
_ => throw new ArgumentOutOfRangeException(nameof(expressionNode))
};
return $"({expr})";
}
private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccessNode)
{
var target = EmitExpression(arrayIndexAccessNode.Target);
var index = EmitExpression(arrayIndexAccessNode.Index);
return $"{target}[{index}]";
}
private string EmitArrayInitializer(ArrayInitializerNode arrayInitializerNode)
{
var values = new List<string>();
foreach (var value in arrayInitializerNode.Values)
{
values.Add(EmitExpression(value));
}
var arrayType = (NubArrayType)arrayInitializerNode.Type;
return $"({CType.Create(arrayType.ElementType)}[]){{{string.Join(", ", values)}}}";
}
private string EmitBinaryExpression(BinaryExpressionNode binaryExpressionNode)
{
var left = EmitExpression(binaryExpressionNode.Left);
var right = EmitExpression(binaryExpressionNode.Right);
var op = binaryExpressionNode.Operator switch
{
BinaryOperator.Plus => "+",
BinaryOperator.Minus => "-",
BinaryOperator.Multiply => "*",
BinaryOperator.Divide => "/",
BinaryOperator.Modulo => "%",
BinaryOperator.Equal => "==",
BinaryOperator.NotEqual => "!=",
BinaryOperator.LessThan => "<",
BinaryOperator.LessThanOrEqual => "<=",
BinaryOperator.GreaterThan => ">",
BinaryOperator.GreaterThanOrEqual => ">=",
BinaryOperator.LogicalAnd => "&&",
BinaryOperator.LogicalOr => "||",
BinaryOperator.BitwiseAnd => "&",
BinaryOperator.BitwiseOr => "|",
BinaryOperator.BitwiseXor => "^",
BinaryOperator.LeftShift => "<<",
BinaryOperator.RightShift => ">>",
_ => throw new ArgumentOutOfRangeException()
};
return $"{left} {op} {right}";
}
private string EmitConstArrayIndexAccess(ConstArrayIndexAccessNode constArrayIndexAccessNode)
{
var target = EmitExpression(constArrayIndexAccessNode.Target);
var index = EmitExpression(constArrayIndexAccessNode.Index);
// todo(nub31): We can emit bounds checking here
return $"{target}[{index}]";
}
private string EmitConstArrayInitializer(ConstArrayInitializerNode arrayInitializerNode)
{
var values = new List<string>();
foreach (var value in arrayInitializerNode.Values)
{
values.Add(EmitExpression(value));
}
var arrayType = (NubConstArrayType)arrayInitializerNode.Type;
return $"({CType.Create(arrayType.ElementType)}[{arrayType.Size}]){{{string.Join(", ", values)}}}";
}
private string EmitDereference(DereferenceNode dereferenceNode)
{
var pointer = EmitExpression(dereferenceNode.Target);
return $"*{pointer}";
}
private string EmitFloat32Literal(Float32LiteralNode float32LiteralNode)
{
var str = float32LiteralNode.Value.ToString("G9", System.Globalization.CultureInfo.InvariantCulture);
if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E'))
{
str += ".0";
}
return str + "f";
}
private string EmitFloat64Literal(Float64LiteralNode float64LiteralNode)
{
var str = float64LiteralNode.Value.ToString("G17", System.Globalization.CultureInfo.InvariantCulture);
if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E'))
{
str += ".0";
}
return str;
}
private string EmitCast(CastNode castNode)
{
var value = EmitExpression(castNode.Value);
if (castNode is { Type: NubSliceType sliceType, Value.Type: NubConstArrayType arrayType })
{
return $"({CType.Create(sliceType)}){{.length = {arrayType.Size}, .data = (void*){value}}}";
}
return $"({CType.Create(castNode.Type)}){value}";
}
private string EmitFuncCall(FuncCallNode funcCallNode)
{
var name = EmitExpression(funcCallNode.Expression);
var parameterNames = funcCallNode.Parameters.Select(EmitExpression).ToList();
return $"{name}({string.Join(", ", parameterNames)})";
}
private string EmitAddressOf(AddressOfNode addressOfNode)
{
var value = EmitExpression(addressOfNode.LValue);
return $"&{value}";
}
private string EmitSliceArrayIndexAccess(SliceIndexAccessNode sliceIndexAccessNode)
{
var targetType = (NubSliceType)sliceIndexAccessNode.Target.Type;
var target = EmitExpression(sliceIndexAccessNode.Target);
var index = EmitExpression(sliceIndexAccessNode.Index);
// todo(nub31): We can emit bounds checking here
return $"(({CType.Create(targetType.ElementType)}*){target}.data)[{index}]";
}
private string EmitStringLiteral(StringLiteralNode stringLiteralNode)
{
var length = Encoding.UTF8.GetByteCount(stringLiteralNode.Value);
return $"(nub_string){{.length = {length}, .data = \"{stringLiteralNode.Value}\"}}";
}
private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccessNode)
{
var structExpr = EmitExpression(structFieldAccessNode.Target);
return $"{structExpr}.{structFieldAccessNode.Field}";
}
private string EmitStructInitializer(StructInitializerNode structInitializerNode)
{
var initValues = new List<string>();
foreach (var initializer in structInitializerNode.Initializers)
{
var value = EmitExpression(initializer.Value);
initValues.Add($".{initializer.Key} = {value}");
}
var initString = initValues.Count == 0
? "0"
: string.Join(", ", initValues);
return $"({CType.Create(structInitializerNode.Type)}){{{initString}}}";
}
private string EmitI8Literal(I8LiteralNode i8LiteralNode)
{
return i8LiteralNode.Value.ToString();
}
private string EmitI16Literal(I16LiteralNode i16LiteralNode)
{
return i16LiteralNode.Value.ToString();
}
private string EmitI32Literal(I32LiteralNode i32LiteralNode)
{
return i32LiteralNode.Value.ToString();
}
private string EmitI64Literal(I64LiteralNode i64LiteralNode)
{
return i64LiteralNode.Value + "LL";
}
private string EmitU8Literal(U8LiteralNode u8LiteralNode)
{
return u8LiteralNode.Value.ToString();
}
private string EmitU16Literal(U16LiteralNode u16LiteralNode)
{
return u16LiteralNode.Value.ToString();
}
private string EmitU32Literal(U32LiteralNode u32LiteralNode)
{
return u32LiteralNode.Value.ToString();
}
private string EmitU64Literal(U64LiteralNode u64LiteralNode)
{
return u64LiteralNode.Value + "ULL";
}
private string EmitUnaryExpression(UnaryExpressionNode unaryExpressionNode)
{
var value = EmitExpression(unaryExpressionNode.Operand);
return unaryExpressionNode.Operator switch
{
UnaryOperator.Negate => $"-{value}",
UnaryOperator.Invert => $"!{value}",
_ => throw new ArgumentOutOfRangeException()
};
}
private void EmitBlock(BlockNode blockNode)
{
_deferStack.Push([]);
foreach (var statementNode in blockNode.Statements)
{
EmitStatement(statementNode);
}
var blockDefers = _deferStack.Pop();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
}
}

View File

@@ -1,70 +0,0 @@
using System.Text;
namespace NubLang.Generation;
internal class IndentedTextWriter
{
private readonly StringBuilder _builder = new();
private int _indentLevel;
public IDisposable Indent()
{
_indentLevel++;
return new IndentScope(this);
}
public void WriteLine(string text)
{
WriteIndent();
_builder.AppendLine(text);
}
public void Write(string text)
{
WriteIndent();
_builder.Append(text);
}
public void WriteLine()
{
_builder.AppendLine();
}
public override string ToString()
{
return _builder.ToString();
}
private void WriteIndent()
{
if (_builder.Length > 0)
{
var lastChar = _builder[^1];
if (lastChar != '\n' && lastChar != '\r')
return;
}
for (var i = 0; i < _indentLevel; i++)
{
_builder.Append(" ");
}
}
private class IndentScope : IDisposable
{
private readonly IndentedTextWriter _writer;
private bool _disposed;
public IndentScope(IndentedTextWriter writer)
{
_writer = writer;
}
public void Dispose()
{
if (_disposed) return;
_writer._indentLevel--;
_disposed = true;
}
}
}

View File

@@ -0,0 +1,6 @@
namespace NubLang.Sugar;
public class DeSugar
{
}