more renaming

This commit is contained in:
nub31
2025-05-05 19:26:23 +02:00
parent e5b7982d37
commit bfaad0950b
50 changed files with 25 additions and 7 deletions

View File

@@ -0,0 +1,246 @@
using System.Text;
using Nub.Lang.Frontend.Parsing;
namespace Nub.Lang.Backend;
public class Generator
{
private readonly List<DefinitionNode> _definitions;
private readonly StringBuilder _builder = new();
private readonly Dictionary<string, string> _variables = new();
private readonly List<string> _strings = [];
public Generator(List<DefinitionNode> definitions)
{
_definitions = definitions;
}
public string Generate()
{
foreach (var funcDefinition in _definitions.OfType<LocalFuncDefinitionNode>())
{
GenerateFuncDefinition(funcDefinition);
}
for (var i = 0; i < _strings.Count; i++)
{
var str = _strings[i];
_builder.AppendLine($"data $str{i + 1} = {{ b \"{str}\", b 0 }}");
}
return _builder.ToString();
}
private string QbeTypeName(NubType type)
{
if (type.Equals(NubType.Int64) || type.Equals(NubType.String))
{
return "l";
}
if (type.Equals(NubType.Int32) || type.Equals(NubType.Bool))
{
return "w";
}
throw new Exception($"Invalid qbe type {type}");
}
private void GenerateFuncDefinition(LocalFuncDefinitionNode node)
{
_variables.Clear();
var parameters = node.Parameters.Select(p => $"{QbeTypeName(p.Type)} %{p.Name}");
if (node.Global)
{
_builder.Append("export ");
}
_builder.Append("function ");
if (node.ReturnType.HasValue)
{
_builder.Append($"{QbeTypeName(node.ReturnType.Value)} ");
}
_builder.Append('$');
_builder.Append(node.Name);
_builder.AppendLine($"({string.Join(", ", parameters)}) {{");
_builder.AppendLine("@start");
GenerateBlock(node.Body);
if (!node.ReturnType.HasValue)
{
_builder.AppendLine(" ret");
}
_builder.AppendLine("}");
}
private void GenerateBlock(BlockNode block)
{
foreach (var statement in block.Statements)
{
GenerateStatement(statement);
}
}
private void GenerateStatement(StatementNode statement)
{
switch (statement)
{
case BreakNode:
GenerateBreak();
break;
case ContinueNode:
GenerateContinue();
break;
case FuncCallStatementNode funcCallStatement:
GenerateStatementFuncCall(funcCallStatement);
break;
case IfNode ifStatement:
GenerateIf(ifStatement);
break;
case ReturnNode @return:
GenerateReturn(@return);
break;
case VariableAssignmentNode variableAssignment:
GenerateVariableAssignment(variableAssignment);
break;
case VariableReassignmentNode variableReassignment:
GenerateVariableReassignment(variableReassignment);
break;
case WhileNode whileStatement:
GenerateWhile(whileStatement);
break;
default:
throw new ArgumentOutOfRangeException(nameof(statement));
}
}
private void GenerateBreak()
{
throw new NotImplementedException();
}
private void GenerateContinue()
{
throw new NotImplementedException();
}
private void GenerateStatementFuncCall(FuncCallStatementNode funcCall)
{
var results = new List<(string, NubType)>();
foreach (var parameter in funcCall.FuncCall.Parameters)
{
results.Add((GenerateExpression(parameter), parameter.Type));
}
var parameters = results.Select(p => $"{QbeTypeName(p.Item2)} {p.Item1}");
_builder.AppendLine($" call ${funcCall.FuncCall.Name}({string.Join(", ", parameters)})");
}
private void GenerateIf(IfNode ifStatement)
{
throw new NotImplementedException();
}
private void GenerateReturn(ReturnNode @return)
{
if (@return.Value.HasValue)
{
var result = GenerateExpression(@return.Value.Value);
_builder.AppendLine($" ret {result}");
}
else
{
_builder.AppendLine(" ret");
}
}
private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment)
{
_variables[variableAssignment.Name] = GenerateExpression(variableAssignment.Value);
}
private void GenerateVariableReassignment(VariableReassignmentNode variableReassignment)
{
_variables[variableReassignment.Name] = GenerateExpression(variableReassignment.Value);
}
private void GenerateWhile(WhileNode whileStatement)
{
throw new NotImplementedException();
}
private string GenerateExpression(ExpressionNode expression)
{
switch (expression)
{
case BinaryExpressionNode binaryExpression:
return GenerateBinaryExpression(binaryExpression);
case FuncCallExpressionNode funcCallExpression:
return GenerateExpressionFuncCall(funcCallExpression);
case IdentifierNode identifier:
return GenerateIdentifier(identifier);
case LiteralNode literal:
return GenerateLiteral(literal);
case StructInitializerNode structInitializer:
return GenerateStructInitializer(structInitializer);
case StructMemberAccessorNode structMemberAccessor:
return GenerateStructMemberAccessor(structMemberAccessor);
default:
throw new ArgumentOutOfRangeException(nameof(expression));
}
}
private string GenerateStructMemberAccessor(StructMemberAccessorNode structMemberAccessor)
{
throw new NotImplementedException();
}
private string GenerateBinaryExpression(BinaryExpressionNode binaryExpression)
{
throw new NotImplementedException();
}
private string GenerateIdentifier(IdentifierNode identifier)
{
return _variables[identifier.Identifier];
}
private string GenerateLiteral(LiteralNode literal)
{
if (literal.LiteralType.Equals(NubType.String))
{
_strings.Add(literal.Literal);
return $"$str{_strings.Count}";
}
else
{
throw new NotImplementedException();
}
}
private string GenerateStructInitializer(StructInitializerNode structInitializer)
{
throw new NotImplementedException();
}
private string GenerateExpressionFuncCall(FuncCallExpressionNode funcCall)
{
var results = new List<(string, NubType)>();
foreach (var parameter in funcCall.FuncCall.Parameters)
{
results.Add((GenerateExpression(parameter), parameter.Type));
}
var parameters = results.Select(p => $"{QbeTypeName(p.Item2)} {p.Item1}");
var output = GenName();
_builder.AppendLine($" %{output} ={QbeTypeName(funcCall.Type)} call ${funcCall.FuncCall.Name}({string.Join(", ", parameters)})");
return $"%{output}";
}
private int _nameIndex;
private string GenName() => "v" + ++_nameIndex;
}

View File

@@ -0,0 +1,6 @@
namespace Nub.Lang.Frontend.Lexing;
public class IdentifierToken(string value) : Token
{
public string Value { get; } = value;
}

View File

@@ -0,0 +1,178 @@
namespace Nub.Lang.Frontend.Lexing;
public class Lexer
{
private static readonly Dictionary<string, Symbol> Keywords = new()
{
["func"] = Symbol.Func,
["global"] = Symbol.Global,
["extern"] = Symbol.Extern,
["import"] = Symbol.Import,
["let"] = Symbol.Let,
["if"] = Symbol.If,
["else"] = Symbol.Else,
["while"] = Symbol.While,
["break"] = Symbol.Break,
["continue"] = Symbol.Continue,
["return"] = Symbol.Return,
["new"] = Symbol.New,
["struct"] = Symbol.Struct,
};
private static readonly Dictionary<char[], Symbol> Chians = new()
{
[['=', '=']] = Symbol.Equal,
[['!', '=']] = Symbol.NotEqual,
[['<', '=']] = Symbol.LessThanOrEqual,
[['>', '=']] = Symbol.GreaterThanOrEqual,
};
private static readonly Dictionary<char, Symbol> Chars = new()
{
[';'] = Symbol.Semicolon,
[':'] = Symbol.Colon,
['('] = Symbol.OpenParen,
[')'] = Symbol.CloseParen,
['{'] = Symbol.OpenBrace,
['}'] = Symbol.CloseBrace,
['['] = Symbol.OpenBracket,
[']'] = Symbol.CloseBracket,
[','] = Symbol.Comma,
['.'] = Symbol.Period,
['='] = Symbol.Assign,
['<'] = Symbol.LessThan,
['>'] = Symbol.GreaterThan,
['+'] = Symbol.Plus,
['-'] = Symbol.Minus,
['*'] = Symbol.Star,
['/'] = Symbol.ForwardSlash,
['!'] = Symbol.Bang,
};
private string _src = string.Empty;
private int _index;
public List<Token> Lex(string src)
{
_src = src;
_index = 0;
List<Token> tokens = [];
while (Peek().HasValue)
{
tokens.Add(ParseToken());
}
return tokens;
}
private Token ParseToken()
{
var current = Peek();
if (char.IsLetter(current.Value) || current.Value == '_')
{
var buffer = string.Empty;
while (current.HasValue && (char.IsLetterOrDigit(current.Value) || current.Value == '_'))
{
buffer += current.Value;
Next();
current = Peek();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
return new SymbolToken(keywordSymbol);
}
if (buffer is "true" or "false")
{
return new LiteralToken(NubType.Bool, buffer);
}
return new IdentifierToken(buffer);
}
if (char.IsDigit(current.Value))
{
var buffer = string.Empty;
while (current.HasValue && char.IsDigit(current.Value))
{
buffer += current.Value;
Next();
current = Peek();
}
return new LiteralToken(NubType.Int64, buffer);
}
// TODO: Revisit this
foreach (var chain in Chians)
{
if (current.Value != chain.Key[0]) continue;
for (var i = 1; i < chain.Key.Length; i++)
{
var c = Peek(i);
if (!c.HasValue || c.Value != chain.Key[i]) break;
if (i == chain.Key.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
return new SymbolToken(chain.Value);
}
}
}
if (Chars.TryGetValue(current.Value, out var charSymbol))
{
Next();
return new SymbolToken(charSymbol);
}
if (current.Value == '"')
{
Next();
var buffer = string.Empty;
while (true)
{
current = Peek();
Next();
if (!current.HasValue) throw new Exception("Unclosed string literal");
if (current.Value == '"') break;
buffer += current.Value;
}
return new LiteralToken(NubType.String, buffer);
}
if (char.IsWhiteSpace(current.Value))
{
Next();
return new SymbolToken(Symbol.Whitespace);
}
throw new Exception($"Unknown character {current.Value}");
}
private Optional<char> Peek(int offset = 0)
{
if (_index + offset < _src.Length)
{
return _src[_index + offset];
}
return Optional<char>.Empty();
}
private void Next()
{
_index++;
}
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Lexing;
public class LiteralToken(NubType type, string value) : Token
{
public NubType Type { get; } = type;
public string Value { get; } = value;
}

View File

@@ -0,0 +1,46 @@
namespace Nub.Lang.Frontend.Lexing;
public class SymbolToken(Symbol symbol) : Token
{
public Symbol Symbol { get; } = symbol;
}
public enum Symbol
{
Whitespace,
Import,
Extern,
Global,
Func,
Return,
Let,
If,
Else,
While,
Break,
Continue,
Semicolon,
Colon,
OpenParen,
CloseParen,
OpenBrace,
CloseBrace,
OpenBracket,
CloseBracket,
Comma,
Period,
Assign,
Bang,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Plus,
Minus,
Star,
ForwardSlash,
New,
Struct
}

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Lexing;
public abstract class Token;

View File

@@ -0,0 +1,22 @@
namespace Nub.Lang.Frontend.Parsing;
public class BinaryExpressionNode(ExpressionNode left, BinaryExpressionOperator @operator, ExpressionNode right) : ExpressionNode
{
public ExpressionNode Left { get; } = left;
public BinaryExpressionOperator Operator { get; } = @operator;
public ExpressionNode Right { get; } = right;
}
public enum BinaryExpressionOperator
{
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Plus,
Minus,
Multiply,
Divide
}

View File

@@ -0,0 +1,6 @@
namespace Nub.Lang.Frontend.Parsing;
public class BlockNode(List<StatementNode> statements) : Node
{
public List<StatementNode> Statements { get; } = statements;
}

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Parsing;
public class BreakNode : StatementNode;

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Parsing;
public class ContinueNode : StatementNode;

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Parsing;
public abstract class DefinitionNode : Node;

View File

@@ -0,0 +1,11 @@
namespace Nub.Lang.Frontend.Parsing;
public abstract class ExpressionNode : Node
{
private NubType? _type;
public NubType Type
{
get => _type ?? throw new Exception("Tried to access expression type before type was populated");
set => _type = value;
}
}

View File

@@ -0,0 +1,10 @@
namespace Nub.Lang.Frontend.Parsing;
public class ExternFuncDefinitionNode(string name, List<FuncParameter> parameters, Optional<NubType> returnType) : DefinitionNode
{
public string Name { get; } = name;
public List<FuncParameter> Parameters { get; } = parameters;
public Optional<NubType> ReturnType { get; } = returnType;
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
}

View File

@@ -0,0 +1,9 @@
namespace Nub.Lang.Frontend.Parsing;
public class FuncCall(string name, List<ExpressionNode> parameters)
{
public string Name { get; } = name;
public List<ExpressionNode> Parameters { get; } = parameters;
public override string ToString() => $"{Name}()";
}

View File

@@ -0,0 +1,8 @@
namespace Nub.Lang.Frontend.Parsing;
public class FuncCallExpressionNode(FuncCall funcCall) : ExpressionNode
{
public FuncCall FuncCall { get; } = funcCall;
public override string ToString() => FuncCall.ToString();
}

View File

@@ -0,0 +1,8 @@
namespace Nub.Lang.Frontend.Parsing;
public class FuncCallStatementNode(FuncCall funcCall) : StatementNode
{
public FuncCall FuncCall { get; } = funcCall;
public override string ToString() => FuncCall.ToString();
}

View File

@@ -0,0 +1,8 @@
namespace Nub.Lang.Frontend.Parsing;
public class IdentifierNode(string identifier) : ExpressionNode
{
public string Identifier { get; } = identifier;
public override string ToString() => Identifier;
}

View File

@@ -0,0 +1,8 @@
namespace Nub.Lang.Frontend.Parsing;
public class IfNode(ExpressionNode condition, BlockNode body, Optional<Variant<IfNode, BlockNode>> @else) : StatementNode
{
public ExpressionNode Condition { get; } = condition;
public BlockNode Body { get; } = body;
public Optional<Variant<IfNode, BlockNode>> Else { get; } = @else;
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class LiteralNode(string literal, NubType type) : ExpressionNode
{
public string Literal { get; } = literal;
public NubType LiteralType { get; } = type;
}

View File

@@ -0,0 +1,12 @@
namespace Nub.Lang.Frontend.Parsing;
public class LocalFuncDefinitionNode(string name, List<FuncParameter> parameters, BlockNode body, Optional<NubType> returnType, bool global) : DefinitionNode
{
public string Name { get; } = name;
public List<FuncParameter> Parameters { get; } = parameters;
public BlockNode Body { get; } = body;
public Optional<NubType> ReturnType { get; } = returnType;
public bool Global { get; } = global;
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
}

View File

@@ -0,0 +1,8 @@
namespace Nub.Lang.Frontend.Parsing;
public class ModuleNode(string path, List<string> imports, List<DefinitionNode> definitions) : Node
{
public string Path { get; } = path;
public List<string> Imports { get; } = imports;
public List<DefinitionNode> Definitions { get; } = definitions;
}

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Parsing;
public abstract class Node;

View File

@@ -0,0 +1,568 @@
using System.Diagnostics.CodeAnalysis;
using Nub.Lang.Frontend.Lexing;
namespace Nub.Lang.Frontend.Parsing;
public class Parser
{
private List<Token> _tokens = [];
private int _index;
public ModuleNode ParseModule(List<Token> tokens, string path)
{
_index = 0;
_tokens = tokens;
List<DefinitionNode> definitions = [];
List<string> imports = [];
while (Peek().HasValue)
{
if (TryExpectSymbol(Symbol.Import))
{
var name = ExpectLiteral();
if (!name.Type.Equals(NubType.String))
{
throw new Exception("Import statements must have a string literal value");
}
TryExpectSymbol(Symbol.Semicolon);
imports.Add(name.Value);
}
else
{
definitions.Add(ParseDefinition());
}
}
return new ModuleNode(path, imports, definitions);
}
private DefinitionNode ParseDefinition()
{
var keyword = ExpectSymbol();
return keyword.Symbol switch
{
Symbol.Func => ParseFuncDefinition(),
Symbol.Global => ParseGlobalFuncDefinition(),
Symbol.Extern => ParseExternFuncDefinition(),
Symbol.Struct => ParseStruct(),
_ => throw new Exception("Unexpected symbol: " + keyword.Symbol)
};
}
private LocalFuncDefinitionNode ParseFuncDefinition()
{
var name = ExpectIdentifier();
List<FuncParameter> parameters = [];
ExpectSymbol(Symbol.OpenParen);
if (!TryExpectSymbol(Symbol.CloseParen))
{
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
TryExpectSymbol(Symbol.Comma);
}
}
var returnType = Optional<NubType>.Empty();
if (TryExpectSymbol(Symbol.Colon))
{
returnType = ParseTypeInstance();
}
var body = ParseBlock();
return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType, false);
}
private LocalFuncDefinitionNode ParseGlobalFuncDefinition()
{
ExpectSymbol(Symbol.Func);
var name = ExpectIdentifier();
List<FuncParameter> parameters = [];
ExpectSymbol(Symbol.OpenParen);
if (!TryExpectSymbol(Symbol.CloseParen))
{
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
TryExpectSymbol(Symbol.Comma);
}
}
var returnType = Optional<NubType>.Empty();
if (TryExpectSymbol(Symbol.Colon))
{
returnType = ParseTypeInstance();
}
var body = ParseBlock();
return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType, true);
}
private ExternFuncDefinitionNode ParseExternFuncDefinition()
{
ExpectSymbol(Symbol.Func);
var name = ExpectIdentifier();
List<FuncParameter> parameters = [];
ExpectSymbol(Symbol.OpenParen);
if (!TryExpectSymbol(Symbol.CloseParen))
{
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
TryExpectSymbol(Symbol.Comma);
}
}
var returnType = Optional<NubType>.Empty();
if (TryExpectSymbol(Symbol.Colon))
{
returnType = ParseTypeInstance();
}
ExpectSymbol(Symbol.Semicolon);
return new ExternFuncDefinitionNode(name.Value, parameters, returnType);
}
private StructDefinitionNode ParseStruct()
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.OpenBrace);
List<StructField> variables = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var variableName = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Colon);
var variableType = ParseTypeInstance();
var variableValue = Optional<ExpressionNode>.Empty();
if (TryExpectSymbol(Symbol.Assign))
{
variableValue = ParseExpression();
}
ExpectSymbol(Symbol.Semicolon);
variables.Add(new StructField(variableName, variableType, variableValue));
}
return new StructDefinitionNode(name, variables);
}
private FuncParameter ParseFuncParameter()
{
var name = ExpectIdentifier();
ExpectSymbol(Symbol.Colon);
var type = ParseTypeInstance();
return new FuncParameter(name.Value, type);
}
private StatementNode ParseStatement()
{
var token = ExpectToken();
switch (token)
{
case IdentifierToken identifier:
{
var symbol = ExpectSymbol();
switch (symbol.Symbol)
{
case Symbol.OpenParen:
{
var parameters = new List<ExpressionNode>();
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseExpression());
TryExpectSymbol(Symbol.Comma);
}
ExpectSymbol(Symbol.Semicolon);
return new FuncCallStatementNode(new FuncCall(identifier.Value, parameters));
}
case Symbol.Assign:
{
var value = ParseExpression();
ExpectSymbol(Symbol.Semicolon);
return new VariableReassignmentNode(identifier.Value, value);
}
default:
{
throw new Exception($"Unexpected symbol {symbol.Symbol}");
}
}
}
case SymbolToken symbol:
{
return symbol.Symbol switch
{
Symbol.Return => ParseReturn(),
Symbol.Let => ParseVariableAssignment(),
Symbol.If => ParseIf(),
Symbol.While => ParseWhile(),
Symbol.Break => ParseBreak(),
Symbol.Continue => ParseContinue(),
_ => throw new Exception($"Unexpected symbol {symbol.Symbol}")
};
}
default:
{
throw new Exception($"Unexpected token type {token.GetType().Name}");
}
}
}
private ReturnNode ParseReturn()
{
var value = Optional<ExpressionNode>.Empty();
if (!TryExpectSymbol(Symbol.Semicolon))
{
value = ParseExpression();
ExpectSymbol(Symbol.Semicolon);
}
return new ReturnNode(value);
}
private VariableAssignmentNode ParseVariableAssignment()
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Assign);
var value = ParseExpression();
ExpectSymbol(Symbol.Semicolon);
return new VariableAssignmentNode(name, value);
}
private IfNode ParseIf()
{
var condition = ParseExpression();
var body = ParseBlock();
var elseStatement = Optional<Variant<IfNode, BlockNode>>.Empty();
if (TryExpectSymbol(Symbol.Else))
{
elseStatement = TryExpectSymbol(Symbol.If)
? (Variant<IfNode, BlockNode>)ParseIf()
: (Variant<IfNode, BlockNode>)ParseBlock();
}
return new IfNode(condition, body, elseStatement);
}
private WhileNode ParseWhile()
{
var condition = ParseExpression();
var body = ParseBlock();
return new WhileNode(condition, body);
}
private BreakNode ParseBreak()
{
ExpectSymbol(Symbol.Semicolon);
return new BreakNode();
}
private ContinueNode ParseContinue()
{
ExpectSymbol(Symbol.Semicolon);
return new ContinueNode();
}
private ExpressionNode ParseExpression(int precedence = 0)
{
var left = ParsePrimaryExpression();
while (true)
{
var token = Peek();
if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) ||
GetBinaryOperatorPrecedence(op.Value) < precedence)
break;
Next();
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
left = new BinaryExpressionNode(left, op.Value, right);
}
return left;
}
private static int GetBinaryOperatorPrecedence(BinaryExpressionOperator binaryExpressionOperator)
{
return binaryExpressionOperator switch
{
BinaryExpressionOperator.Multiply => 3,
BinaryExpressionOperator.Divide => 3,
BinaryExpressionOperator.Plus => 2,
BinaryExpressionOperator.Minus => 2,
BinaryExpressionOperator.GreaterThan => 1,
BinaryExpressionOperator.GreaterThanOrEqual => 1,
BinaryExpressionOperator.LessThan => 1,
BinaryExpressionOperator.LessThanOrEqual => 1,
BinaryExpressionOperator.Equal => 0,
BinaryExpressionOperator.NotEqual => 0,
_ => throw new ArgumentOutOfRangeException(nameof(binaryExpressionOperator), binaryExpressionOperator, null)
};
}
private static bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryExpressionOperator? binaryExpressionOperator)
{
switch (symbol)
{
case Symbol.Equal:
binaryExpressionOperator = BinaryExpressionOperator.Equal;
return true;
case Symbol.NotEqual:
binaryExpressionOperator = BinaryExpressionOperator.NotEqual;
return true;
case Symbol.LessThan:
binaryExpressionOperator = BinaryExpressionOperator.LessThan;
return true;
case Symbol.LessThanOrEqual:
binaryExpressionOperator = BinaryExpressionOperator.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
binaryExpressionOperator = BinaryExpressionOperator.GreaterThan;
return true;
case Symbol.GreaterThanOrEqual:
binaryExpressionOperator = BinaryExpressionOperator.GreaterThanOrEqual;
return true;
case Symbol.Plus:
binaryExpressionOperator = BinaryExpressionOperator.Plus;
return true;
case Symbol.Minus:
binaryExpressionOperator = BinaryExpressionOperator.Minus;
return true;
case Symbol.Star:
binaryExpressionOperator = BinaryExpressionOperator.Multiply;
return true;
case Symbol.ForwardSlash:
binaryExpressionOperator = BinaryExpressionOperator.Divide;
return true;
default:
binaryExpressionOperator = null;
return false;
}
}
private ExpressionNode ParsePrimaryExpression()
{
var token = ExpectToken();
switch (token)
{
case LiteralToken literal:
{
return new LiteralNode(literal.Value, literal.Type);
}
case IdentifierToken identifier:
{
return ParseExpressionIdentifier(identifier);
}
case SymbolToken symbolToken:
{
switch (symbolToken.Symbol)
{
case Symbol.OpenParen:
{
var expression = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
return expression;
}
case Symbol.New:
{
var type = ParseTypeInstance();
Dictionary<string, ExpressionNode> initializers = [];
ExpectSymbol(Symbol.OpenBrace);
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Assign);
var value = ParseExpression();
TryExpectSymbol(Symbol.Semicolon);
initializers.Add(name, value);
}
return new StructInitializerNode(type, initializers);
}
default:
throw new Exception($"Unknown symbol: {symbolToken.Symbol}");
}
}
default:
throw new Exception($"Unexpected token type {token.GetType().Name}");
}
}
private ExpressionNode ParseExpressionIdentifier(IdentifierToken identifier)
{
var token = Peek();
if (!token.HasValue)
{
return new IdentifierNode(identifier.Value);
}
switch (token.Value)
{
case SymbolToken symbolToken:
{
switch (symbolToken.Symbol)
{
case Symbol.Period:
{
Next();
List<string> members =
[
identifier.Value,
ExpectIdentifier().Value
];
while (TryExpectSymbol(Symbol.Period))
{
members.Add(ExpectIdentifier().Value);
}
return new StructMemberAccessorNode(members);
}
case Symbol.OpenParen:
{
Next();
List<ExpressionNode> parameters = [];
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseExpression());
TryExpectSymbol(Symbol.Comma);
}
return new FuncCallExpressionNode(new FuncCall(identifier.Value, parameters));
}
}
break;
}
}
return new IdentifierNode(identifier.Value);
}
private BlockNode ParseBlock()
{
ExpectSymbol(Symbol.OpenBrace);
List<StatementNode> statements = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
statements.Add(ParseStatement());
}
return new BlockNode(statements);
}
private NubType ParseTypeInstance()
{
var parameters = new List<NubType>();
var name = ExpectIdentifier().Value;
if (TryExpectSymbol(Symbol.LessThan))
{
do
{
parameters.Add(ParseTypeInstance());
} while (TryExpectSymbol(Symbol.Comma));
ExpectSymbol(Symbol.GreaterThan);
}
return new NubType(name, parameters.ToArray());
}
private Token ExpectToken()
{
var token = Peek();
if (!token.HasValue)
{
throw new Exception("Reached end of tokens");
}
Next();
return token.Value;
}
private SymbolToken ExpectSymbol()
{
var token = ExpectToken();
if (token is not SymbolToken symbol)
{
throw new Exception($"Expected {nameof(SymbolToken)} but got {token.GetType().Name}");
}
return symbol;
}
private void ExpectSymbol(Symbol symbol)
{
var token = ExpectSymbol();
if (token.Symbol != symbol)
{
throw new Exception($"Expected symbol {symbol} but got {token.Symbol}");
}
}
private bool TryExpectSymbol(Symbol symbol)
{
var result = Peek() is { HasValue: true, Value: SymbolToken symbolToken } && symbolToken.Symbol == symbol;
if (result) Next();
return result;
}
private IdentifierToken ExpectIdentifier()
{
var token = ExpectToken();
if (token is not IdentifierToken identifier)
{
throw new Exception($"Expected {nameof(IdentifierToken)} but got {token.GetType().Name}");
}
return identifier;
}
private LiteralToken ExpectLiteral()
{
var token = ExpectToken();
if (token is not LiteralToken literal)
{
throw new Exception($"Expected {nameof(LiteralToken)} but got {token.GetType().Name}");
}
return literal;
}
private Optional<Token> Peek()
{
while (_index < _tokens.Count && _tokens.ElementAt(_index) is SymbolToken { Symbol: Symbol.Whitespace })
{
Next();
}
if (_index < _tokens.Count)
{
return _tokens.ElementAt(_index);
}
return Optional<Token>.Empty();
}
private void Next()
{
_index++;
}
}

View File

@@ -0,0 +1,6 @@
namespace Nub.Lang.Frontend.Parsing;
public class ReturnNode(Optional<ExpressionNode> value) : StatementNode
{
public Optional<ExpressionNode> Value { get; } = value;
}

View File

@@ -0,0 +1,3 @@
namespace Nub.Lang.Frontend.Parsing;
public abstract class StatementNode : Node;

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class StructDefinitionNode(string name, List<StructField> members) : DefinitionNode
{
public string Name { get; } = name;
public List<StructField> Members { get; } = members;
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class StructInitializerNode(NubType structType, Dictionary<string, ExpressionNode> initializers) : ExpressionNode
{
public NubType StructType { get; } = structType;
public Dictionary<string, ExpressionNode> Initializers { get; } = initializers;
}

View File

@@ -0,0 +1,6 @@
namespace Nub.Lang.Frontend.Parsing;
public class StructMemberAccessorNode(List<string> members) : ExpressionNode
{
public List<string> Members { get; } = members;
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class VariableAssignmentNode(string name, ExpressionNode value) : StatementNode
{
public string Name { get; } = name;
public ExpressionNode Value { get; } = value;
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class VariableReassignmentNode(string name, ExpressionNode value) : StatementNode
{
public string Name { get; } = name;
public ExpressionNode Value { get; } = value;
}

View File

@@ -0,0 +1,7 @@
namespace Nub.Lang.Frontend.Parsing;
public class WhileNode(ExpressionNode condition, BlockNode body) : StatementNode
{
public ExpressionNode Condition { get; } = condition;
public BlockNode Body { get; } = body;
}

View File

@@ -0,0 +1,309 @@
using Nub.Lang.Frontend.Parsing;
namespace Nub.Lang.Frontend.Typing;
public class Func(string name, List<FuncParameter> parameters, Optional<BlockNode> body, Optional<NubType> returnType)
{
public string Name { get; } = name;
public List<FuncParameter> Parameters { get; } = parameters;
public Optional<BlockNode> Body { get; } = body;
public Optional<NubType> ReturnType { get; } = returnType;
}
public class ExpressionTyper
{
private readonly List<Func> _functions;
private readonly List<StructDefinitionNode> _structDefinitions;
private readonly Stack<Variable> _variables;
public ExpressionTyper(List<DefinitionNode> definitions)
{
_variables = new Stack<Variable>();
_functions = [];
_structDefinitions = definitions.OfType<StructDefinitionNode>().ToList();
var functions = definitions
.OfType<LocalFuncDefinitionNode>()
.Select(f => new Func(f.Name, f.Parameters, f.Body, f.ReturnType))
.ToList();
var externFunctions = definitions
.OfType<ExternFuncDefinitionNode>()
.Select(f => new Func(f.Name, f.Parameters, Optional<BlockNode>.Empty(), f.ReturnType))
.ToList();
_functions.AddRange(functions);
_functions.AddRange(externFunctions);
}
public void Populate()
{
_variables.Clear();
foreach (var @class in _structDefinitions)
{
foreach (var variable in @class.Members)
{
if (variable.Value.HasValue)
{
PopulateExpression(variable.Value.Value);
}
}
}
foreach (var function in _functions)
{
foreach (var parameter in function.Parameters)
{
_variables.Push(new Variable(parameter.Name, parameter.Type));
}
if (function.Body.HasValue)
{
PopulateBlock(function.Body.Value);
}
for (var i = 0; i < function.Parameters.Count; i++)
{
_variables.Pop();
}
}
}
private void PopulateBlock(BlockNode block)
{
var variableCount = _variables.Count;
foreach (var statement in block.Statements)
{
PopulateStatement(statement);
}
while (_variables.Count > variableCount)
{
_variables.Pop();
}
}
private void PopulateStatement(StatementNode statement)
{
switch (statement)
{
case BreakNode:
case ContinueNode:
break;
case FuncCallStatementNode funcCall:
PopulateFuncCallStatement(funcCall);
break;
case IfNode ifStatement:
PopulateIf(ifStatement);
break;
case ReturnNode returnNode:
PopulateReturn(returnNode);
break;
case VariableAssignmentNode variableAssignment:
PopulateVariableAssignment(variableAssignment);
break;
case VariableReassignmentNode variableReassignment:
PopulateVariableReassignment(variableReassignment);
break;
case WhileNode whileStatement:
PopulateWhileStatement(whileStatement);
break;
default:
throw new ArgumentOutOfRangeException(nameof(statement));
}
}
private void PopulateFuncCallStatement(FuncCallStatementNode funcCall)
{
foreach (var parameter in funcCall.FuncCall.Parameters)
{
PopulateExpression(parameter);
}
}
private void PopulateIf(IfNode ifStatement)
{
PopulateExpression(ifStatement.Condition);
PopulateBlock(ifStatement.Body);
if (ifStatement.Else.HasValue)
{
ifStatement.Else.Value.Match
(
PopulateIf,
PopulateBlock
);
}
}
private void PopulateReturn(ReturnNode returnNode)
{
if (returnNode.Value.HasValue)
{
PopulateExpression(returnNode.Value.Value);
}
}
private void PopulateVariableAssignment(VariableAssignmentNode variableAssignment)
{
PopulateExpression(variableAssignment.Value);
_variables.Push(new Variable(variableAssignment.Name, variableAssignment.Value.Type));
}
private void PopulateVariableReassignment(VariableReassignmentNode variableReassignment)
{
PopulateExpression(variableReassignment.Value);
}
private void PopulateWhileStatement(WhileNode whileStatement)
{
PopulateExpression(whileStatement.Condition);
PopulateBlock(whileStatement.Body);
}
private void PopulateExpression(ExpressionNode expression)
{
switch (expression)
{
case BinaryExpressionNode binaryExpression:
PopulateBinaryExpression(binaryExpression);
break;
case FuncCallExpressionNode funcCall:
PopulateFuncCallExpression(funcCall);
break;
case IdentifierNode identifier:
PopulateIdentifier(identifier);
break;
case LiteralNode literal:
PopulateLiteral(literal);
break;
case StructInitializerNode structInitializer:
PopulateStructInitializer(structInitializer);
break;
case StructMemberAccessorNode structMemberAccessor:
GenerateStructMemberAccessorNode(structMemberAccessor);
break;
default:
throw new ArgumentOutOfRangeException(nameof(expression));
}
}
private void PopulateBinaryExpression(BinaryExpressionNode binaryExpression)
{
PopulateExpression(binaryExpression.Left);
PopulateExpression(binaryExpression.Right);
switch (binaryExpression.Operator)
{
case BinaryExpressionOperator.Equal:
case BinaryExpressionOperator.NotEqual:
case BinaryExpressionOperator.GreaterThan:
case BinaryExpressionOperator.GreaterThanOrEqual:
case BinaryExpressionOperator.LessThan:
case BinaryExpressionOperator.LessThanOrEqual:
{
binaryExpression.Type = new NubType("bool", []);
break;
}
case BinaryExpressionOperator.Plus:
case BinaryExpressionOperator.Minus:
case BinaryExpressionOperator.Multiply:
case BinaryExpressionOperator.Divide:
{
binaryExpression.Type = binaryExpression.Left.Type;
break;
}
default:
{
throw new ArgumentOutOfRangeException(nameof(binaryExpression.Operator));
}
}
}
private void PopulateFuncCallExpression(FuncCallExpressionNode funcCall)
{
foreach (var parameter in funcCall.FuncCall.Parameters)
{
PopulateExpression(parameter);
}
var function = _functions.FirstOrDefault(f => f.Name == funcCall.FuncCall.Name);
if (function == null)
{
throw new Exception($"Func {funcCall} is not defined");
}
if (!function.ReturnType.HasValue)
{
throw new Exception($"Func {funcCall} must have a return type when used in an expression");
}
funcCall.Type = function.ReturnType.Value;
}
private void PopulateIdentifier(IdentifierNode identifier)
{
var type = _variables.FirstOrDefault(v => v.Name == identifier.Identifier)?.Type;
if (type == null)
{
throw new Exception($"Variable {identifier} is not defined");
}
identifier.Type = type;
}
private static void PopulateLiteral(LiteralNode literal)
{
literal.Type = literal.LiteralType;
}
private void PopulateStructInitializer(StructInitializerNode structInitializer)
{
foreach (var initializer in structInitializer.Initializers)
{
PopulateExpression(initializer.Value);
}
structInitializer.Type = structInitializer.StructType;
}
// TODO: Fix this ugly ass code
private void GenerateStructMemberAccessorNode(StructMemberAccessorNode structMemberAccessor)
{
var variable = _variables.FirstOrDefault(v => v.Name == structMemberAccessor.Members[0]);
if (variable == null)
{
throw new Exception($"Variable {structMemberAccessor.Members[0]} is not defined");
}
var definition = _structDefinitions.FirstOrDefault(sd => sd.Name == variable.Type.Name);
if (definition == null)
{
throw new Exception($"Struct {structMemberAccessor.Members[0]} is not defined");
}
for (var i = 1; i < structMemberAccessor.Members.Count - 1; i++)
{
var member = definition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members[i]);
if (member == null)
{
throw new Exception($"Member {structMemberAccessor.Members[i]} does not exist on struct {definition.Name}");
}
definition = _structDefinitions.FirstOrDefault(sd => sd.Name == member.Type.Name);
if (definition == null)
{
throw new Exception($"Struct {structMemberAccessor.Members[i]} is not defined");
}
}
var tmp = definition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members.Last());
if (tmp == null)
{
throw new Exception($"Member {structMemberAccessor.Members.Last()} does not exist on struct {definition.Name}");
}
structMemberAccessor.Type = tmp.Type;
}
private class Variable(string name, NubType type)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
}
}

View File

@@ -0,0 +1,9 @@
namespace Nub.Lang;
public class FuncParameter(string name, NubType type)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
public override string ToString() => $"{Name}: {Type}";
}

View File

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

View File

@@ -0,0 +1,38 @@
namespace Nub.Lang;
public sealed class NubType
{
public NubType(string name, NubType[] generics)
{
Name = name;
Generics = generics;
}
public string Name { get; }
public NubType[] Generics { get; }
public static NubType Int64 => new("int64", []);
public static NubType Int32 => new("int32", []);
public static NubType Bool => new("bool", []);
public static NubType String => new("string", []);
public override bool Equals(object? obj)
{
if (obj is not NubType item)
{
return false;
}
return Name.Equals(item.Name) && Generics.SequenceEqual(item.Generics);
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Generics);
}
public override string ToString()
{
return $"{Name}<{string.Join(", ", Generics.Select(x => x.ToString()))}>";
}
}

View File

@@ -0,0 +1,48 @@
using System.Diagnostics.CodeAnalysis;
namespace Nub.Lang;
public readonly struct Optional
{
public static Optional<TValue> Empty<TValue>() => new();
/// <summary>
/// Alias for creating an Optional&lt;TValue&gt; which allows for implicit types
/// </summary>
/// <param name="value"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
public static Optional<TValue> OfNullable<TValue>(TValue? value)
{
return value ?? Optional<TValue>.Empty();
}
}
public readonly struct Optional<TValue>
{
public static Optional<TValue> Empty() => new();
public static Optional<TValue> OfNullable(TValue? value)
{
return value ?? Empty();
}
public Optional()
{
Value = default;
HasValue = false;
}
public Optional(TValue value)
{
Value = value;
HasValue = true;
}
public TValue? Value { get; }
[MemberNotNullWhen(true, nameof(Value))]
public bool HasValue { get; }
public static implicit operator Optional<TValue>(TValue value) => new(value);
}

View File

@@ -0,0 +1,87 @@
using Nub.Lang.Backend;
using Nub.Lang.Frontend.Lexing;
using Nub.Lang.Frontend.Parsing;
using Nub.Lang.Frontend.Typing;
namespace Nub.Lang;
internal static class Program
{
private static readonly Lexer Lexer = new();
private static readonly Parser Parser = new();
public static int Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: nub <input-dir> <output-file>");
Console.WriteLine("Example: nub src out.asm");
return 1;
}
var input = Path.GetFullPath(args[0]);
var output = Path.GetFullPath(args[1]);
if (!Directory.Exists(input))
{
Console.WriteLine($"Error: Input directory '{input}' does not exist.");
return 1;
}
var outputDir = Path.GetDirectoryName(output);
if (outputDir == null || !Directory.Exists(outputDir))
{
Console.WriteLine($"Error: Output directory '{outputDir}' does not exist.");
return 1;
}
if (string.IsNullOrWhiteSpace(Path.GetFileName(output)))
{
Console.WriteLine("Error: Output path must specify a file, not a directory.");
return 1;
}
var modules = RunFrontend(input);
var definitions = modules.SelectMany(f => f.Definitions).ToList();
var typer = new ExpressionTyper(definitions);
typer.Populate();
var generator = new Generator(definitions);
var result = generator.Generate();
File.WriteAllText(output, result);
return 0;
}
private static List<ModuleNode> RunFrontend(string path)
{
List<ModuleNode> modules = [];
RunFrontend(path, modules);
return modules;
}
private static void RunFrontend(string path, List<ModuleNode> modules)
{
var files = Directory.EnumerateFiles(path, "*.nub", SearchOption.TopDirectoryOnly);
List<Token> tokens = [];
foreach (var file in files)
{
var src = File.ReadAllText(file);
tokens.AddRange(Lexer.Lex(src));
}
var module = Parser.ParseModule(tokens, path);
modules.Add(module);
foreach (var import in module.Imports)
{
var importPath = Path.GetFullPath(import, module.Path);
if (modules.All(m => m.Path != importPath))
{
RunFrontend(importPath, modules);
}
}
}
}

View File

@@ -0,0 +1,10 @@
using Nub.Lang.Frontend.Parsing;
namespace Nub.Lang;
public class StructField(string name, NubType type, Optional<ExpressionNode> value)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
public Optional<ExpressionNode> Value { get; } = value;
}

View File

@@ -0,0 +1,49 @@
namespace Nub.Lang;
public readonly struct Variant<T1, T2> where T1 : notnull where T2 : notnull
{
public Variant()
{
throw new InvalidOperationException("Variant must be initialized with a value");
}
public Variant(T1 value)
{
_value = value;
}
public Variant(T2 value)
{
_value = value;
}
private readonly object _value;
public void Match(Action<T1> on1, Action<T2> on2)
{
switch (_value)
{
case T1 v1:
on1(v1);
break;
case T2 v2:
on2(v2);
break;
default:
throw new InvalidCastException();
}
}
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
{
return _value switch
{
T1 v1 => on1(v1),
T2 v2 => on2(v2),
_ => throw new InvalidCastException()
};
}
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
}