restructure fs
This commit is contained in:
736
lang/Nub.Lang/Backend/Custom/Generator.cs
Normal file
736
lang/Nub.Lang/Backend/Custom/Generator.cs
Normal file
@@ -0,0 +1,736 @@
|
||||
using System.Text;
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
|
||||
namespace Nub.Lang.Backend.Custom;
|
||||
|
||||
public class Generator
|
||||
{
|
||||
private const string Entrypoint = "main";
|
||||
private const bool ZeroBasedIndexing = false;
|
||||
|
||||
private readonly List<DefinitionNode> _definitions;
|
||||
private readonly SymbolTable _symbolTable;
|
||||
private readonly StringBuilder _builder;
|
||||
private readonly LabelFactory _labelFactory;
|
||||
private readonly Stack<(string StartLabel, string EndLabel)> _loops;
|
||||
|
||||
public Generator(List<DefinitionNode> definitions)
|
||||
{
|
||||
_definitions = definitions;
|
||||
_builder = new StringBuilder();
|
||||
_labelFactory = new LabelFactory();
|
||||
_symbolTable = new SymbolTable(_labelFactory);
|
||||
_loops = [];
|
||||
|
||||
foreach (var globalVariableDefinition in definitions.OfType<GlobalVariableDefinitionNode>())
|
||||
{
|
||||
_symbolTable.DefineGlobalVariable(globalVariableDefinition);
|
||||
}
|
||||
|
||||
foreach (var funcDefinitionNode in definitions.OfType<ExternFuncDefinitionNode>())
|
||||
{
|
||||
_symbolTable.DefineFunc(funcDefinitionNode);
|
||||
}
|
||||
|
||||
foreach (var funcDefinitionNode in definitions.OfType<LocalFuncDefinitionNode>())
|
||||
{
|
||||
_symbolTable.DefineFunc(funcDefinitionNode);
|
||||
}
|
||||
}
|
||||
|
||||
public string Generate()
|
||||
{
|
||||
_builder.AppendLine("global _start");
|
||||
_builder.AppendLine("extern gc_init");
|
||||
_builder.AppendLine("extern gc_alloc");
|
||||
_builder.AppendLine("extern str_cmp");
|
||||
|
||||
foreach (var externFuncDefinition in _definitions.OfType<ExternFuncDefinitionNode>())
|
||||
{
|
||||
_builder.AppendLine($"extern {externFuncDefinition.Name}");
|
||||
}
|
||||
|
||||
_builder.AppendLine();
|
||||
_builder.AppendLine("section .text");
|
||||
|
||||
// TODO: Only add start label if entrypoint is present, otherwise assume library
|
||||
var main = _symbolTable.ResolveLocalFunc(Entrypoint, []);
|
||||
|
||||
_builder.AppendLine("_start:");
|
||||
_builder.AppendLine(" call gc_init");
|
||||
_builder.AppendLine($" call {main.StartLabel}");
|
||||
|
||||
_builder.AppendLine(main.ReturnType.HasValue
|
||||
? " mov rdi, rax"
|
||||
: " mov rdi, 0");
|
||||
_builder.AppendLine(" mov rax, 60");
|
||||
_builder.AppendLine(" syscall");
|
||||
|
||||
foreach (var funcDefinition in _definitions.OfType<LocalFuncDefinitionNode>())
|
||||
{
|
||||
_builder.AppendLine();
|
||||
GenerateFuncDefinition(funcDefinition);
|
||||
}
|
||||
|
||||
_builder.AppendLine("""
|
||||
|
||||
eb6e_oob_error:
|
||||
mov rax, 60
|
||||
mov rdi, 139
|
||||
syscall
|
||||
""");
|
||||
|
||||
_builder.AppendLine();
|
||||
_builder.AppendLine("section .data");
|
||||
|
||||
foreach (var str in _symbolTable.Strings)
|
||||
{
|
||||
_builder.AppendLine($" {str.Key}: db `{str.Value}`, 0");
|
||||
}
|
||||
|
||||
Dictionary<string, string> completed = [];
|
||||
foreach (var globalVariableDefinition in _definitions.OfType<GlobalVariableDefinitionNode>())
|
||||
{
|
||||
var variable = _symbolTable.ResolveGlobalVariable(globalVariableDefinition.Name);
|
||||
var evaluated = EvaluateExpression(globalVariableDefinition.Value, completed);
|
||||
_builder.AppendLine($" {variable.Identifier}: dq {evaluated}");
|
||||
completed[variable.Name] = evaluated;
|
||||
}
|
||||
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private string EvaluateExpression(ExpressionNode expression, Dictionary<string, string> completed)
|
||||
{
|
||||
switch (expression)
|
||||
{
|
||||
case BinaryExpressionNode binaryExpression:
|
||||
{
|
||||
var left = EvaluateExpression(binaryExpression.Left, completed);
|
||||
var right = EvaluateExpression(binaryExpression.Right, completed);
|
||||
return binaryExpression.Operator switch
|
||||
{
|
||||
BinaryExpressionOperator.Equal => bool.Parse(left) == bool.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.NotEqual => bool.Parse(left) != bool.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.GreaterThan => long.Parse(left) > long.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.GreaterThanOrEqual => long.Parse(left) >= long.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.LessThan => long.Parse(left) < long.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.LessThanOrEqual => long.Parse(left) <= long.Parse(right) ? "1" : "0",
|
||||
BinaryExpressionOperator.Plus => (long.Parse(left) + long.Parse(right)).ToString(),
|
||||
BinaryExpressionOperator.Minus => (long.Parse(left) - long.Parse(right)).ToString(),
|
||||
BinaryExpressionOperator.Multiply => (long.Parse(left) * long.Parse(right)).ToString(),
|
||||
BinaryExpressionOperator.Divide => (long.Parse(left) / long.Parse(right)).ToString(),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
case IdentifierNode identifier:
|
||||
{
|
||||
return completed[identifier.Identifier];
|
||||
}
|
||||
case LiteralNode literal:
|
||||
{
|
||||
if (literal.Type is not PrimitiveType primitiveType)
|
||||
{
|
||||
throw new NotSupportedException("Global variable literals must be of a primitive type");
|
||||
}
|
||||
|
||||
return primitiveType.Kind switch
|
||||
{
|
||||
PrimitiveTypeKind.Bool => bool.Parse(literal.Literal) ? "1" : "0",
|
||||
PrimitiveTypeKind.Int64 or PrimitiveTypeKind.Int32 => $"{literal.Literal}",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException("Global variables must be compile time consistant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateFuncDefinition(LocalFuncDefinitionNode node)
|
||||
{
|
||||
var func = _symbolTable.ResolveLocalFunc(node.Name, node.Parameters.Select(p => p.Type).ToList());
|
||||
|
||||
_builder.AppendLine($"{func.StartLabel}:");
|
||||
_builder.AppendLine(" push rbp");
|
||||
_builder.AppendLine(" mov rbp, rsp");
|
||||
_builder.AppendLine($" sub rsp, {func.StackAllocation}");
|
||||
|
||||
string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
|
||||
|
||||
for (var i = 0; i < func.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = func.ResolveLocalVariable(func.Parameters.ElementAt(i).Name);
|
||||
if (i < registers.Length)
|
||||
{
|
||||
_builder.AppendLine($" mov [rbp - {parameter.Offset}], {registers[i]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var stackOffset = 16 + (i - registers.Length) * 8;
|
||||
_builder.AppendLine($" mov rax, [rbp + {stackOffset}]");
|
||||
_builder.AppendLine($" mov [rbp - {parameter.Offset}], rax");
|
||||
}
|
||||
}
|
||||
|
||||
GenerateBlock(node.Body, func);
|
||||
|
||||
_builder.AppendLine($"{func.EndLabel}:");
|
||||
_builder.AppendLine(" mov rsp, rbp");
|
||||
_builder.AppendLine(" pop rbp");
|
||||
_builder.AppendLine(" ret");
|
||||
}
|
||||
|
||||
private void GenerateBlock(BlockNode block, LocalFunc func)
|
||||
{
|
||||
foreach (var statement in block.Statements)
|
||||
{
|
||||
GenerateStatement(statement, func);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStatement(StatementNode statement, LocalFunc func)
|
||||
{
|
||||
switch (statement)
|
||||
{
|
||||
case ArrayIndexAssignmentNode arrayIndexAssignment:
|
||||
GenerateArrayIndexAssignment(arrayIndexAssignment, func);
|
||||
break;
|
||||
case BreakNode:
|
||||
GenerateBreak();
|
||||
break;
|
||||
case ContinueNode:
|
||||
GenerateContinue();
|
||||
break;
|
||||
case FuncCallStatementNode funcCallStatement:
|
||||
GenerateFuncCall(funcCallStatement.FuncCall, func);
|
||||
break;
|
||||
case IfNode ifStatement:
|
||||
GenerateIf(ifStatement, func);
|
||||
break;
|
||||
case ReturnNode @return:
|
||||
GenerateReturn(@return, func);
|
||||
break;
|
||||
case SyscallStatementNode syscallStatement:
|
||||
GenerateSyscall(syscallStatement.Syscall, func);
|
||||
break;
|
||||
case VariableAssignmentNode variableAssignment:
|
||||
GenerateVariableAssignment(variableAssignment, func);
|
||||
break;
|
||||
case VariableReassignmentNode variableReassignment:
|
||||
GenerateVariableReassignment(variableReassignment, func);
|
||||
break;
|
||||
case WhileNode whileStatement:
|
||||
GenerateWhile(whileStatement, func);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(statement));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBreak()
|
||||
{
|
||||
_builder.AppendLine($" jmp {_loops.Peek().EndLabel}");
|
||||
}
|
||||
|
||||
private void GenerateContinue()
|
||||
{
|
||||
_builder.AppendLine($" jmp {_loops.Peek().StartLabel}");
|
||||
}
|
||||
|
||||
private void GenerateArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment, LocalFunc func)
|
||||
{
|
||||
GenerateExpression(arrayIndexAssignment.Value, func);
|
||||
_builder.AppendLine(" push rax");
|
||||
GenerateArrayIndexPointerAccess(arrayIndexAssignment.Identifier, arrayIndexAssignment.Index, func);
|
||||
_builder.AppendLine(" pop rdx");
|
||||
_builder.AppendLine(" mov [rax], rdx");
|
||||
}
|
||||
|
||||
private void GenerateIf(IfNode ifStatement, LocalFunc func)
|
||||
{
|
||||
var endLabel = _labelFactory.Create();
|
||||
GenerateIf(ifStatement, endLabel, func);
|
||||
_builder.AppendLine($"{endLabel}:");
|
||||
}
|
||||
|
||||
private void GenerateIf(IfNode ifStatement, string endLabel, LocalFunc func)
|
||||
{
|
||||
var nextLabel = _labelFactory.Create();
|
||||
GenerateExpression(ifStatement.Condition, func);
|
||||
_builder.AppendLine(" cmp rax, 0");
|
||||
_builder.AppendLine($" je {nextLabel}");
|
||||
GenerateBlock(ifStatement.Body, func);
|
||||
_builder.AppendLine($" jmp {endLabel}");
|
||||
_builder.AppendLine($"{nextLabel}:");
|
||||
|
||||
if (ifStatement.Else.HasValue)
|
||||
{
|
||||
ifStatement.Else.Value.Match
|
||||
(
|
||||
elseIfStatement => GenerateIf(elseIfStatement, endLabel, func),
|
||||
elseStatement => GenerateBlock(elseStatement, func)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateReturn(ReturnNode @return, LocalFunc func)
|
||||
{
|
||||
if (@return.Value.HasValue)
|
||||
{
|
||||
GenerateExpression(@return.Value.Value, func);
|
||||
}
|
||||
|
||||
_builder.AppendLine($" jmp {func.EndLabel}");
|
||||
}
|
||||
|
||||
private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment, LocalFunc func)
|
||||
{
|
||||
var variable = func.ResolveLocalVariable(variableAssignment.Name);
|
||||
GenerateExpression(variableAssignment.Value, func);
|
||||
_builder.AppendLine($" mov [rbp - {variable.Offset}], rax");
|
||||
}
|
||||
|
||||
private void GenerateVariableReassignment(VariableReassignmentNode variableReassignment, LocalFunc func)
|
||||
{
|
||||
var variable = func.ResolveLocalVariable(variableReassignment.Name);
|
||||
GenerateExpression(variableReassignment.Value, func);
|
||||
_builder.AppendLine($" mov [rbp - {variable.Offset}], rax");
|
||||
}
|
||||
|
||||
private void GenerateWhile(WhileNode whileStatement, LocalFunc func)
|
||||
{
|
||||
var startLabel = _labelFactory.Create();
|
||||
var endLabel = _labelFactory.Create();
|
||||
|
||||
_builder.AppendLine($"{startLabel}:");
|
||||
GenerateExpression(whileStatement.Condition, func);
|
||||
_builder.AppendLine(" cmp rax, 0");
|
||||
_builder.AppendLine($" je {endLabel}");
|
||||
_loops.Push((startLabel, endLabel));
|
||||
GenerateBlock(whileStatement.Body, func);
|
||||
_loops.Pop();
|
||||
_builder.AppendLine($" jmp {startLabel}");
|
||||
_builder.AppendLine($"{endLabel}:");
|
||||
}
|
||||
|
||||
private void GenerateExpression(ExpressionNode expression, LocalFunc func)
|
||||
{
|
||||
switch (expression)
|
||||
{
|
||||
case ArrayIndexAccessNode arrayIndexAccess:
|
||||
GenerateArrayIndexAccess(arrayIndexAccess, func);
|
||||
break;
|
||||
case ArrayInitializerNode arrayInitializer:
|
||||
GenerateArrayInitializer(arrayInitializer);
|
||||
break;
|
||||
case BinaryExpressionNode binaryExpression:
|
||||
GenerateBinaryExpression(binaryExpression, func);
|
||||
break;
|
||||
case FuncCallExpressionNode funcCallExpression:
|
||||
GenerateFuncCall(funcCallExpression.FuncCall, func);
|
||||
break;
|
||||
case IdentifierNode identifier:
|
||||
GenerateIdentifier(identifier, func);
|
||||
break;
|
||||
case LiteralNode literal:
|
||||
GenerateLiteral(literal);
|
||||
break;
|
||||
case StructInitializerNode structInitializer:
|
||||
GenerateStructInitializer(structInitializer, func);
|
||||
break;
|
||||
case StructMemberAccessorNode structMemberAccessor:
|
||||
GenerateStructMemberAccessor(structMemberAccessor, func);
|
||||
break;
|
||||
case SyscallExpressionNode syscallExpression:
|
||||
GenerateSyscall(syscallExpression.Syscall, func);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(expression));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStructMemberAccessor(StructMemberAccessorNode structMemberAccessor, LocalFunc func)
|
||||
{
|
||||
var variable = func.ResolveLocalVariable(structMemberAccessor.Members[0]);
|
||||
|
||||
if (variable.Type is not StructType structType)
|
||||
{
|
||||
throw new Exception($"Cannot access struct member on {variable} since it is not a struct type");
|
||||
}
|
||||
|
||||
_builder.AppendLine($" mov rax, [rbp - {variable.Offset}]");
|
||||
|
||||
Type prevMemberType = structType;
|
||||
for (var i = 1; i < structMemberAccessor.Members.Count; i++)
|
||||
{
|
||||
if (prevMemberType is not StructType prevMemberStructType)
|
||||
{
|
||||
throw new Exception($"Cannot access {structMemberAccessor.Members[i]} on type {prevMemberType} because it is not a struct type");
|
||||
}
|
||||
|
||||
var structDefinition = _definitions.OfType<StructDefinitionNode>().FirstOrDefault(sd => sd.Name == prevMemberStructType.Name);
|
||||
if (structDefinition == null)
|
||||
{
|
||||
throw new Exception($"Struct {prevMemberStructType} is not defined");
|
||||
}
|
||||
|
||||
var member = structDefinition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members[i]);
|
||||
if (member == null)
|
||||
{
|
||||
throw new Exception($"Struct {prevMemberStructType} has no member with name {structMemberAccessor.Members[i]}");
|
||||
}
|
||||
|
||||
var offset = structDefinition.Members.IndexOf(member);
|
||||
_builder.AppendLine($" mov rax, [rax + {offset * 8}]");
|
||||
|
||||
prevMemberType = member.Type;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess, LocalFunc func)
|
||||
{
|
||||
GenerateArrayIndexPointerAccess(arrayIndexAccess.Identifier, arrayIndexAccess.Index, func);
|
||||
_builder.AppendLine(" mov rax, [rax]");
|
||||
}
|
||||
|
||||
private void GenerateArrayInitializer(ArrayInitializerNode arrayInitializer)
|
||||
{
|
||||
_builder.AppendLine($" mov rdi, {8 + arrayInitializer.Length * 8}");
|
||||
_builder.AppendLine(" call gc_alloc");
|
||||
_builder.AppendLine($" mov qword [rax], {arrayInitializer.Length}");
|
||||
}
|
||||
|
||||
private void GenerateBinaryExpression(BinaryExpressionNode binaryExpression, LocalFunc func)
|
||||
{
|
||||
GenerateExpression(binaryExpression.Left, func);
|
||||
_builder.AppendLine(" push rax");
|
||||
GenerateExpression(binaryExpression.Right, func);
|
||||
_builder.AppendLine(" mov rcx, rax");
|
||||
_builder.AppendLine(" pop rax");
|
||||
|
||||
switch (binaryExpression.Operator)
|
||||
{
|
||||
case BinaryExpressionOperator.Equal:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" sete al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.NotEqual:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" setne al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.GreaterThan:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" setg al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.GreaterThanOrEqual:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" setge al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.LessThan:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" setl al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.LessThanOrEqual:
|
||||
GenerateComparison(binaryExpression.Left.Type);
|
||||
_builder.AppendLine(" setle al");
|
||||
_builder.AppendLine(" movzx rax, al");
|
||||
break;
|
||||
case BinaryExpressionOperator.Plus:
|
||||
GenerateBinaryAddition(binaryExpression.Left.Type);
|
||||
break;
|
||||
case BinaryExpressionOperator.Minus:
|
||||
GenerateBinarySubtraction(binaryExpression.Left.Type);
|
||||
break;
|
||||
case BinaryExpressionOperator.Multiply:
|
||||
GenerateBinaryMultiplication(binaryExpression.Left.Type);
|
||||
break;
|
||||
case BinaryExpressionOperator.Divide:
|
||||
GenerateBinaryDivision(binaryExpression.Left.Type);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(binaryExpression.Operator));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateComparison(Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AnyType:
|
||||
throw new InvalidOperationException($"Cannot compare type {type}");
|
||||
case ArrayType:
|
||||
// compare pointers
|
||||
_builder.AppendLine(" cmp rax, rcx");
|
||||
break;
|
||||
case PrimitiveType:
|
||||
_builder.AppendLine(" cmp rax, rcx");
|
||||
break;
|
||||
case StringType:
|
||||
_builder.AppendLine(" mov rdi, rax");
|
||||
_builder.AppendLine(" mov rsi, rcx");
|
||||
_builder.AppendLine(" call str_cmp");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBinaryAddition(Type type)
|
||||
{
|
||||
if (type is not PrimitiveType primitiveType)
|
||||
{
|
||||
throw new InvalidOperationException("Addition can only be done on primitive types");
|
||||
}
|
||||
|
||||
switch (primitiveType.Kind)
|
||||
{
|
||||
case PrimitiveTypeKind.Int64:
|
||||
_builder.AppendLine(" add rax, rcx");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int32:
|
||||
_builder.AppendLine(" add eax, ecx");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid type {primitiveType.Kind}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBinarySubtraction(Type type)
|
||||
{
|
||||
if (type is not PrimitiveType primitiveType)
|
||||
{
|
||||
throw new InvalidOperationException("Subtraction can only be done on primitive types");
|
||||
}
|
||||
|
||||
switch (primitiveType.Kind)
|
||||
{
|
||||
case PrimitiveTypeKind.Int64:
|
||||
_builder.AppendLine(" sub rax, rcx");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int32:
|
||||
_builder.AppendLine(" sub eax, ecx");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid type {primitiveType.Kind}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBinaryMultiplication(Type type)
|
||||
{
|
||||
if (type is not PrimitiveType primitiveType)
|
||||
{
|
||||
throw new InvalidOperationException("Multiplication can only be done on primitive types");
|
||||
}
|
||||
|
||||
switch (primitiveType.Kind)
|
||||
{
|
||||
case PrimitiveTypeKind.Int64:
|
||||
_builder.AppendLine(" imul rcx");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int32:
|
||||
_builder.AppendLine(" imul ecx");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid type {primitiveType.Kind}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBinaryDivision(Type type)
|
||||
{
|
||||
if (type is not PrimitiveType primitiveType)
|
||||
{
|
||||
throw new InvalidOperationException("Division can only be done on primitive types");
|
||||
}
|
||||
|
||||
switch (primitiveType.Kind)
|
||||
{
|
||||
case PrimitiveTypeKind.Int64:
|
||||
_builder.AppendLine(" cqo");
|
||||
_builder.AppendLine(" idiv rcx");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int32:
|
||||
_builder.AppendLine(" cdq");
|
||||
_builder.AppendLine(" idiv ecx");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid type {primitiveType.Kind}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateIdentifier(IdentifierNode identifier, LocalFunc func)
|
||||
{
|
||||
var variable = func.ResolveVariable(identifier.Identifier);
|
||||
switch (variable)
|
||||
{
|
||||
case GlobalVariable globalVariable:
|
||||
_builder.AppendLine($" mov rax, [{globalVariable.Identifier}]");
|
||||
break;
|
||||
case LocalVariable localVariable:
|
||||
{
|
||||
_builder.AppendLine($" mov rax, [rbp - {localVariable.Offset}]");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(variable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLiteral(LiteralNode literal)
|
||||
{
|
||||
switch (literal.Type)
|
||||
{
|
||||
case StringType:
|
||||
{
|
||||
var label = _symbolTable.DefineString(literal.Literal);
|
||||
_builder.AppendLine($" mov rax, {label}");
|
||||
break;
|
||||
}
|
||||
case PrimitiveType primitive:
|
||||
{
|
||||
switch (primitive.Kind)
|
||||
{
|
||||
case PrimitiveTypeKind.Bool:
|
||||
_builder.AppendLine($" mov rax, {(bool.Parse(literal.Literal) ? "1" : "0")}");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int64:
|
||||
_builder.AppendLine($" mov rax, {literal.Literal}");
|
||||
break;
|
||||
case PrimitiveTypeKind.Int32:
|
||||
_builder.AppendLine($" mov rax, {literal.Literal}");
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Cannot convert literal to string");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStructInitializer(StructInitializerNode structInitializer, LocalFunc func)
|
||||
{
|
||||
var structDefinition = _definitions
|
||||
.OfType<StructDefinitionNode>()
|
||||
.FirstOrDefault(sd => sd.Name == structInitializer.StructType.Name);
|
||||
|
||||
if (structDefinition == null)
|
||||
{
|
||||
throw new Exception($"Struct {structInitializer.StructType} is not defined");
|
||||
}
|
||||
|
||||
_builder.AppendLine($" mov rdi, {structDefinition.Members.Count * 8}");
|
||||
_builder.AppendLine(" call gc_alloc");
|
||||
_builder.AppendLine(" mov rcx, rax");
|
||||
|
||||
foreach (var initializer in structInitializer.Initializers)
|
||||
{
|
||||
_builder.AppendLine(" push rcx");
|
||||
GenerateExpression(initializer.Value, func);
|
||||
var index = structDefinition.Members.FindIndex(sd => sd.Name == initializer.Key);
|
||||
if (index == -1)
|
||||
{
|
||||
throw new Exception($"Member {initializer.Key} is not defined on struct {structInitializer.StructType}");
|
||||
}
|
||||
|
||||
_builder.AppendLine(" pop rcx");
|
||||
_builder.AppendLine($" mov [rcx + {index * 8}], rax");
|
||||
}
|
||||
|
||||
foreach (var uninitializedMember in structDefinition.Members.Where(m => !structInitializer.Initializers.ContainsKey(m.Name)))
|
||||
{
|
||||
if (!uninitializedMember.Value.HasValue)
|
||||
{
|
||||
throw new Exception($"Struct {structInitializer.StructType} must be initializer with member {uninitializedMember.Name}");
|
||||
}
|
||||
|
||||
_builder.AppendLine(" push rcx");
|
||||
GenerateExpression(uninitializedMember.Value.Value, func);
|
||||
var index = structDefinition.Members.IndexOf(uninitializedMember);
|
||||
_builder.AppendLine(" pop rcx");
|
||||
_builder.AppendLine($" mov [rcx + {index * 8}], rax");
|
||||
}
|
||||
|
||||
_builder.AppendLine(" mov rax, rcx");
|
||||
}
|
||||
|
||||
private void GenerateFuncCall(FuncCall funcCall, LocalFunc func)
|
||||
{
|
||||
var symbol = _symbolTable.ResolveFunc(funcCall.Name, funcCall.Parameters.Select(p => p.Type).ToList());
|
||||
string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
|
||||
|
||||
for (var i = funcCall.Parameters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
GenerateExpression(funcCall.Parameters.ElementAt(i), func);
|
||||
_builder.AppendLine(" push rax");
|
||||
}
|
||||
|
||||
var registerParameters = Math.Min(registers.Length, funcCall.Parameters.Count);
|
||||
var stackParameters = funcCall.Parameters.Count - registerParameters;
|
||||
|
||||
for (var i = 0; i < registerParameters; i++)
|
||||
{
|
||||
_builder.AppendLine($" pop {registers[i]}");
|
||||
}
|
||||
|
||||
_builder.AppendLine($" call {symbol.StartLabel}");
|
||||
if (stackParameters != 0)
|
||||
{
|
||||
_builder.AppendLine($" add rsp, {stackParameters}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateSyscall(Syscall syscall, LocalFunc func)
|
||||
{
|
||||
string[] registers = ["rax", "rdi", "rsi", "rdx", "r10", "r8", "r9"];
|
||||
|
||||
foreach (var parameter in syscall.Parameters)
|
||||
{
|
||||
GenerateExpression(parameter, func);
|
||||
_builder.AppendLine(" push rax");
|
||||
}
|
||||
|
||||
for (var i = syscall.Parameters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_builder.AppendLine($" pop {registers[i]}");
|
||||
}
|
||||
|
||||
_builder.AppendLine(" syscall");
|
||||
}
|
||||
|
||||
private void GenerateArrayIndexPointerAccess(IdentifierNode identifier, ExpressionNode index, LocalFunc func)
|
||||
{
|
||||
GenerateExpression(index, func);
|
||||
_builder.AppendLine(" push rax");
|
||||
GenerateIdentifier(identifier, func);
|
||||
_builder.AppendLine(" pop rdx");
|
||||
|
||||
// rcx now holds the length of the array which we can use to check bounds
|
||||
_builder.AppendLine(" mov rcx, [rax]");
|
||||
_builder.AppendLine(" cmp rdx, rcx");
|
||||
if (ZeroBasedIndexing)
|
||||
{
|
||||
_builder.AppendLine(" jge eb6e_oob_error");
|
||||
_builder.AppendLine(" cmp rdx, 0");
|
||||
}
|
||||
else
|
||||
{
|
||||
_builder.AppendLine(" jg eb6e_oob_error");
|
||||
_builder.AppendLine(" cmp rdx, 1");
|
||||
}
|
||||
_builder.AppendLine(" jl eb6e_oob_error");
|
||||
|
||||
_builder.AppendLine(" inc rdx");
|
||||
_builder.AppendLine(" shl rdx, 3");
|
||||
_builder.AppendLine(" add rax, rdx");
|
||||
}
|
||||
}
|
||||
7
lang/Nub.Lang/Backend/Custom/LabelFactory.cs
Normal file
7
lang/Nub.Lang/Backend/Custom/LabelFactory.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Backend.Custom;
|
||||
|
||||
public class LabelFactory
|
||||
{
|
||||
private int _index;
|
||||
public string Create() => $"label{++_index}";
|
||||
}
|
||||
258
lang/Nub.Lang/Backend/Custom/SymbolTable.cs
Normal file
258
lang/Nub.Lang/Backend/Custom/SymbolTable.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
|
||||
namespace Nub.Lang.Backend.Custom;
|
||||
|
||||
public class SymbolTable
|
||||
{
|
||||
private readonly List<Func> _funcDefinitions = [];
|
||||
private readonly List<GlobalVariable> _globalVariables = [];
|
||||
private readonly LabelFactory _labelFactory;
|
||||
|
||||
public readonly Dictionary<string, string> Strings = [];
|
||||
|
||||
public SymbolTable(LabelFactory labelFactory)
|
||||
{
|
||||
_labelFactory = labelFactory;
|
||||
}
|
||||
|
||||
public string DefineString(string value)
|
||||
{
|
||||
var label = _labelFactory.Create();
|
||||
Strings.Add(label, value);
|
||||
return label;
|
||||
}
|
||||
|
||||
public void DefineGlobalVariable(GlobalVariableDefinitionNode globalVariableDefinition)
|
||||
{
|
||||
var identifier = _labelFactory.Create();
|
||||
_globalVariables.Add(new GlobalVariable(globalVariableDefinition.Name, globalVariableDefinition.Value.Type, identifier));
|
||||
}
|
||||
|
||||
public void DefineFunc(ExternFuncDefinitionNode externFuncDefinition)
|
||||
{
|
||||
var existing = _funcDefinitions
|
||||
.FirstOrDefault(f => f
|
||||
.SignatureMatches
|
||||
(
|
||||
externFuncDefinition.Name,
|
||||
externFuncDefinition.Parameters.Select(p => p.Type).ToList()
|
||||
));
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
throw new Exception($"Func {existing} is already defined");
|
||||
}
|
||||
|
||||
_funcDefinitions.Add(new ExternFunc(externFuncDefinition.Name, externFuncDefinition.Name, externFuncDefinition.Parameters, externFuncDefinition.ReturnType));
|
||||
}
|
||||
|
||||
public void DefineFunc(LocalFuncDefinitionNode localFuncDefinition)
|
||||
{
|
||||
var existing = _funcDefinitions
|
||||
.FirstOrDefault(f => f
|
||||
.SignatureMatches
|
||||
(
|
||||
localFuncDefinition.Name,
|
||||
localFuncDefinition.Parameters.Select(p => p.Type).ToList()
|
||||
));
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
throw new Exception($"Func {existing} is already defined");
|
||||
}
|
||||
|
||||
var startLabel = _labelFactory.Create();
|
||||
var endLabel = _labelFactory.Create();
|
||||
_funcDefinitions.Add(new LocalFunc(localFuncDefinition.Name, startLabel, endLabel, localFuncDefinition.Parameters, localFuncDefinition.ReturnType, _globalVariables.Concat<Variable>(ResolveFuncVariables(localFuncDefinition)).ToList()));
|
||||
}
|
||||
|
||||
private static List<LocalVariable> ResolveFuncVariables(LocalFuncDefinitionNode localFuncDefinition)
|
||||
{
|
||||
var offset = 0;
|
||||
List<LocalVariable> variables = [];
|
||||
|
||||
foreach (var parameter in localFuncDefinition.Parameters)
|
||||
{
|
||||
offset += 8;
|
||||
variables.Add(new LocalVariable(parameter.Name, parameter.Type, offset));
|
||||
}
|
||||
|
||||
ResolveBlockVariables(localFuncDefinition.Body, variables, offset);
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static int ResolveBlockVariables(BlockNode block, List<LocalVariable> variables, int offset)
|
||||
{
|
||||
foreach (var statement in block.Statements)
|
||||
{
|
||||
switch (statement)
|
||||
{
|
||||
case IfNode ifStatement:
|
||||
{
|
||||
offset = ResolveBlockVariables(ifStatement.Body, variables, offset);
|
||||
if (ifStatement.Else.HasValue)
|
||||
{
|
||||
ifStatement.Else.Value.Match
|
||||
(
|
||||
elseIfStatement => offset = ResolveBlockVariables(elseIfStatement.Body, variables, offset),
|
||||
elseStatement => offset = ResolveBlockVariables(elseStatement, variables, offset)
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WhileNode whileStatement:
|
||||
{
|
||||
offset = ResolveBlockVariables(whileStatement.Body, variables, offset);
|
||||
break;
|
||||
}
|
||||
case VariableAssignmentNode variableAssignment:
|
||||
{
|
||||
offset += 8;
|
||||
variables.Add(new LocalVariable(variableAssignment.Name, variableAssignment.Value.Type, offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public Func ResolveFunc(string name, List<Type> parameterTypes)
|
||||
{
|
||||
var func = _funcDefinitions.FirstOrDefault(f => f.SignatureMatches(name, parameterTypes));
|
||||
if (func == null)
|
||||
{
|
||||
throw new Exception($"Func {name}({string.Join(", ", parameterTypes)}) is not defined");
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
public LocalFunc ResolveLocalFunc(string name, List<Type> parameterTypes)
|
||||
{
|
||||
var func = ResolveFunc(name, parameterTypes);
|
||||
if (func is not LocalFunc localFunc)
|
||||
{
|
||||
throw new Exception($"Func {func} is not a local func");
|
||||
}
|
||||
return localFunc;
|
||||
}
|
||||
|
||||
public ExternFunc ResolveExternFunc(string name, List<Type> parameterTypes)
|
||||
{
|
||||
var func = ResolveFunc(name, parameterTypes);
|
||||
if (func is not ExternFunc externFunc)
|
||||
{
|
||||
throw new Exception($"Func {func} is not an extern func");
|
||||
}
|
||||
return externFunc;
|
||||
}
|
||||
|
||||
public GlobalVariable ResolveGlobalVariable(string name)
|
||||
{
|
||||
var variable = _globalVariables.FirstOrDefault(v => v.Name == name);
|
||||
if (variable == null)
|
||||
{
|
||||
throw new Exception($"Global variable {name} is not defined");
|
||||
}
|
||||
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Variable(string name, Type type)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
|
||||
public override string ToString() => $"{Name}: {Type}";
|
||||
}
|
||||
|
||||
public class LocalVariable(string name, Type type, int offset) : Variable(name, type)
|
||||
{
|
||||
public int Offset { get; } = offset;
|
||||
}
|
||||
|
||||
public class GlobalVariable(string name, Type type, string identifier) : Variable(name, type)
|
||||
{
|
||||
public string Identifier { get; } = identifier;
|
||||
}
|
||||
|
||||
public abstract class Func
|
||||
{
|
||||
protected Func(string name, string startLabel, List<FuncParameter> parameters, Optional<Type> returnType)
|
||||
{
|
||||
Name = name;
|
||||
Parameters = parameters;
|
||||
ReturnType = returnType;
|
||||
StartLabel = startLabel;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string StartLabel { get; }
|
||||
public List<FuncParameter> Parameters { get; }
|
||||
public Optional<Type> ReturnType { get; }
|
||||
|
||||
public bool SignatureMatches(string name, List<Type> parameterTypes)
|
||||
{
|
||||
if (Name != name) return false;
|
||||
if (Parameters.Count != parameterTypes.Count) return false;
|
||||
|
||||
for (var i = 0; i < parameterTypes.Count; i++)
|
||||
{
|
||||
if (!Parameters[i].Type.IsAssignableTo(parameterTypes[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
|
||||
}
|
||||
|
||||
public class ExternFunc : Func
|
||||
{
|
||||
public ExternFunc(string name, string startLabel, List<FuncParameter> parameters, Optional<Type> returnType) : base(name, startLabel, parameters, returnType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalFunc : Func
|
||||
{
|
||||
public LocalFunc(string name, string startLabel, string endLabel, List<FuncParameter> parameters, Optional<Type> returnType, List<Variable> variables) : base(name, startLabel, parameters, returnType)
|
||||
{
|
||||
EndLabel = endLabel;
|
||||
Variables = variables;
|
||||
}
|
||||
|
||||
public string EndLabel { get; }
|
||||
public List<Variable> Variables { get; }
|
||||
public int StackAllocation => Variables.OfType<LocalVariable>().Sum(variable => variable.Offset);
|
||||
|
||||
public Variable ResolveVariable(string name)
|
||||
{
|
||||
var variable = Variables.FirstOrDefault(v => v.Name == name);
|
||||
if (variable == null)
|
||||
{
|
||||
throw new Exception($"Variable {name} is not defined");
|
||||
}
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
public LocalVariable ResolveLocalVariable(string name)
|
||||
{
|
||||
var variable = Variables.FirstOrDefault(v => v.Name == name);
|
||||
if (variable == null)
|
||||
{
|
||||
throw new Exception($"Variable {name} is not defined");
|
||||
}
|
||||
|
||||
if (variable is not LocalVariable localVariable)
|
||||
{
|
||||
throw new Exception($"Variable {name} is not a local variable");
|
||||
}
|
||||
|
||||
return localVariable;
|
||||
}
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs
Normal file
6
lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class IdentifierToken(string value) : Token
|
||||
{
|
||||
public string Value { get; } = value;
|
||||
}
|
||||
177
lang/Nub.Lang/Frontend/Lexing/Lexer.cs
Normal file
177
lang/Nub.Lang/Frontend/Lexing/Lexer.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class Lexer
|
||||
{
|
||||
private static readonly Dictionary<string, Symbol> Keywords = new()
|
||||
{
|
||||
["func"] = Symbol.Func,
|
||||
["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(new PrimitiveType(PrimitiveTypeKind.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(new PrimitiveType(PrimitiveTypeKind.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(new StringType(), 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++;
|
||||
}
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs
Normal file
7
lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class LiteralToken(Type type, string value) : Token
|
||||
{
|
||||
public Type Type { get; } = type;
|
||||
public string Value { get; } = value;
|
||||
}
|
||||
45
lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs
Normal file
45
lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class SymbolToken(Symbol symbol) : Token
|
||||
{
|
||||
public Symbol Symbol { get; } = symbol;
|
||||
}
|
||||
|
||||
public enum Symbol
|
||||
{
|
||||
Whitespace,
|
||||
Import,
|
||||
Extern,
|
||||
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
|
||||
}
|
||||
3
lang/Nub.Lang/Frontend/Lexing/Token.cs
Normal file
3
lang/Nub.Lang/Frontend/Lexing/Token.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public abstract class Token;
|
||||
9
lang/Nub.Lang/Frontend/Parsing/ArrayIndexAccessNode.cs
Normal file
9
lang/Nub.Lang/Frontend/Parsing/ArrayIndexAccessNode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ArrayIndexAccessNode(IdentifierNode identifier, ExpressionNode index) : ExpressionNode
|
||||
{
|
||||
public IdentifierNode Identifier { get; } = identifier;
|
||||
public ExpressionNode Index { get; } = index;
|
||||
|
||||
public override string ToString() => $"{Identifier}[{Index}]";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ArrayIndexAssignmentNode(IdentifierNode identifier, ExpressionNode index, ExpressionNode value) : StatementNode
|
||||
{
|
||||
public IdentifierNode Identifier { get; } = identifier;
|
||||
public ExpressionNode Index { get; } = index;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Parsing/ArrayInitializerNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/ArrayInitializerNode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ArrayInitializerNode(long length, Type innerType) : ExpressionNode
|
||||
{
|
||||
public long Length { get; } = length;
|
||||
public Type InnerType { get; } = innerType;
|
||||
}
|
||||
22
lang/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs
Normal file
22
lang/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs
Normal 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
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Parsing/BlockNode.cs
Normal file
6
lang/Nub.Lang/Frontend/Parsing/BlockNode.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class BlockNode(List<StatementNode> statements) : Node
|
||||
{
|
||||
public List<StatementNode> Statements { get; } = statements;
|
||||
}
|
||||
3
lang/Nub.Lang/Frontend/Parsing/BreakNode.cs
Normal file
3
lang/Nub.Lang/Frontend/Parsing/BreakNode.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class BreakNode : StatementNode;
|
||||
3
lang/Nub.Lang/Frontend/Parsing/ContinueNode.cs
Normal file
3
lang/Nub.Lang/Frontend/Parsing/ContinueNode.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ContinueNode : StatementNode;
|
||||
3
lang/Nub.Lang/Frontend/Parsing/DefinitionNode.cs
Normal file
3
lang/Nub.Lang/Frontend/Parsing/DefinitionNode.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public abstract class DefinitionNode : Node;
|
||||
11
lang/Nub.Lang/Frontend/Parsing/ExpressionNode.cs
Normal file
11
lang/Nub.Lang/Frontend/Parsing/ExpressionNode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public abstract class ExpressionNode : Node
|
||||
{
|
||||
private Type? _type;
|
||||
public Type Type
|
||||
{
|
||||
get => _type ?? throw new Exception("Tried to access expression type before type was populated");
|
||||
set => _type = value;
|
||||
}
|
||||
}
|
||||
10
lang/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs
Normal file
10
lang/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ExternFuncDefinitionNode(string name, List<FuncParameter> parameters, Optional<Type> returnType) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
|
||||
}
|
||||
9
lang/Nub.Lang/Frontend/Parsing/FuncCall.cs
Normal file
9
lang/Nub.Lang/Frontend/Parsing/FuncCall.cs
Normal 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}()";
|
||||
}
|
||||
8
lang/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs
Normal file
8
lang/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs
Normal 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();
|
||||
}
|
||||
8
lang/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs
Normal file
8
lang/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs
Normal 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();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class GlobalVariableDefinitionNode(string name, ExpressionNode value) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
8
lang/Nub.Lang/Frontend/Parsing/IdentifierNode.cs
Normal file
8
lang/Nub.Lang/Frontend/Parsing/IdentifierNode.cs
Normal 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;
|
||||
}
|
||||
8
lang/Nub.Lang/Frontend/Parsing/IfNode.cs
Normal file
8
lang/Nub.Lang/Frontend/Parsing/IfNode.cs
Normal 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;
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Parsing/LiteralNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/LiteralNode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class LiteralNode(string literal, Type type) : ExpressionNode
|
||||
{
|
||||
public string Literal { get; } = literal;
|
||||
public Type LiteralType { get; } = type;
|
||||
}
|
||||
11
lang/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs
Normal file
11
lang/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class LocalFuncDefinitionNode(string name, List<FuncParameter> parameters, BlockNode body, Optional<Type> returnType) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public BlockNode Body { get; } = body;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
|
||||
}
|
||||
8
lang/Nub.Lang/Frontend/Parsing/ModuleNode.cs
Normal file
8
lang/Nub.Lang/Frontend/Parsing/ModuleNode.cs
Normal 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;
|
||||
}
|
||||
3
lang/Nub.Lang/Frontend/Parsing/Node.cs
Normal file
3
lang/Nub.Lang/Frontend/Parsing/Node.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public abstract class Node;
|
||||
607
lang/Nub.Lang/Frontend/Parsing/Parser.cs
Normal file
607
lang/Nub.Lang/Frontend/Parsing/Parser.cs
Normal file
@@ -0,0 +1,607 @@
|
||||
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 is not StringType)
|
||||
{
|
||||
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.Let => ParseGlobalVariableDefinition(),
|
||||
Symbol.Func => ParseFuncDefinition(),
|
||||
Symbol.Extern => ParseExternFuncDefinition(),
|
||||
Symbol.Struct => ParseStruct(),
|
||||
_ => throw new Exception("Unexpected symbol: " + keyword.Symbol)
|
||||
};
|
||||
}
|
||||
|
||||
private GlobalVariableDefinitionNode ParseGlobalVariableDefinition()
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
ExpectSymbol(Symbol.Semicolon);
|
||||
|
||||
return new GlobalVariableDefinitionNode(name.Value, value);
|
||||
}
|
||||
|
||||
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<Type>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Colon))
|
||||
{
|
||||
returnType = ParseType();
|
||||
}
|
||||
|
||||
var body = ParseBlock();
|
||||
|
||||
return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType);
|
||||
}
|
||||
|
||||
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<Type>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Colon))
|
||||
{
|
||||
returnType = ParseType();
|
||||
}
|
||||
|
||||
ExpectSymbol(Symbol.Semicolon);
|
||||
|
||||
return new ExternFuncDefinitionNode(name.Value, parameters, returnType);
|
||||
}
|
||||
|
||||
private StructDefinitionNode ParseStruct()
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
|
||||
List<StructMember> variables = [];
|
||||
|
||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
ExpectSymbol(Symbol.Let);
|
||||
var variableName = ExpectIdentifier().Value;
|
||||
ExpectSymbol(Symbol.Colon);
|
||||
var variableType = ParseType();
|
||||
|
||||
var variableValue = Optional<ExpressionNode>.Empty();
|
||||
|
||||
if (TryExpectSymbol(Symbol.Assign))
|
||||
{
|
||||
variableValue = ParseExpression();
|
||||
}
|
||||
|
||||
ExpectSymbol(Symbol.Semicolon);
|
||||
|
||||
variables.Add(new StructMember(variableName, variableType, variableValue));
|
||||
}
|
||||
|
||||
return new StructDefinitionNode(name, variables);
|
||||
}
|
||||
|
||||
private FuncParameter ParseFuncParameter()
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
ExpectSymbol(Symbol.Colon);
|
||||
var type = ParseType();
|
||||
|
||||
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);
|
||||
|
||||
if (identifier.Value == "syscall")
|
||||
{
|
||||
return new SyscallStatementNode(new Syscall(parameters));
|
||||
}
|
||||
|
||||
return new FuncCallStatementNode(new FuncCall(identifier.Value, parameters));
|
||||
}
|
||||
case Symbol.OpenBracket:
|
||||
{
|
||||
var index = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
ExpectSymbol(Symbol.Semicolon);
|
||||
return new ArrayIndexAssignmentNode(new IdentifierNode(identifier.Value), index, value);
|
||||
}
|
||||
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 = ParseType();
|
||||
switch (type)
|
||||
{
|
||||
case ArrayType:
|
||||
{
|
||||
ExpectSymbol(Symbol.OpenParen);
|
||||
var size = ExpectLiteral();
|
||||
if (size.Type is not PrimitiveType { Kind: PrimitiveTypeKind.Int64 })
|
||||
{
|
||||
throw new Exception($"Array initializer size must be an {PrimitiveTypeKind.Int64}");
|
||||
}
|
||||
ExpectSymbol(Symbol.CloseParen);
|
||||
|
||||
return new ArrayInitializerNode(long.Parse(size.Value), type);
|
||||
}
|
||||
case StructType structType:
|
||||
{
|
||||
Dictionary<string, ExpressionNode> initializers = [];
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
TryExpectSymbol(Symbol.Comma);
|
||||
initializers.Add(name, value);
|
||||
}
|
||||
|
||||
return new StructInitializerNode(structType, initializers);
|
||||
}
|
||||
default:
|
||||
throw new Exception($"Type {type} cannot be initialized with the new keyword");
|
||||
}
|
||||
}
|
||||
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.OpenBracket:
|
||||
{
|
||||
Next();
|
||||
var index = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
return new ArrayIndexAccessNode(new IdentifierNode(identifier.Value), index);
|
||||
}
|
||||
case Symbol.OpenParen:
|
||||
{
|
||||
Next();
|
||||
List<ExpressionNode> parameters = [];
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
{
|
||||
parameters.Add(ParseExpression());
|
||||
TryExpectSymbol(Symbol.Comma);
|
||||
}
|
||||
|
||||
if (identifier.Value == "syscall")
|
||||
{
|
||||
return new SyscallExpressionNode(new Syscall(parameters));
|
||||
}
|
||||
|
||||
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 Type ParseType()
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
switch (name)
|
||||
{
|
||||
case "String":
|
||||
{
|
||||
return new StringType();
|
||||
}
|
||||
case "Array":
|
||||
{
|
||||
ExpectSymbol(Symbol.LessThan);
|
||||
var innerType = ParseType();
|
||||
ExpectSymbol(Symbol.GreaterThan);
|
||||
return new ArrayType(innerType);
|
||||
}
|
||||
case "Any":
|
||||
{
|
||||
return new AnyType();
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (PrimitiveType.TryParse(name, out var primitiveType))
|
||||
{
|
||||
return primitiveType;
|
||||
}
|
||||
|
||||
return new StructType(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Parsing/ReturnNode.cs
Normal file
6
lang/Nub.Lang/Frontend/Parsing/ReturnNode.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ReturnNode(Optional<ExpressionNode> value) : StatementNode
|
||||
{
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
3
lang/Nub.Lang/Frontend/Parsing/StatementNode.cs
Normal file
3
lang/Nub.Lang/Frontend/Parsing/StatementNode.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public abstract class StatementNode : Node;
|
||||
7
lang/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructDefinitionNode(string name, List<StructMember> members) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<StructMember> Members { get; } = members;
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructInitializerNode(StructType structType, Dictionary<string, ExpressionNode> initializers) : ExpressionNode
|
||||
{
|
||||
public StructType StructType { get; } = structType;
|
||||
public Dictionary<string, ExpressionNode> Initializers { get; } = initializers;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructMemberAccessorNode(List<string> members) : ExpressionNode
|
||||
{
|
||||
public List<string> Members { get; } = members;
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Parsing/Syscall.cs
Normal file
6
lang/Nub.Lang/Frontend/Parsing/Syscall.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class Syscall(List<ExpressionNode> parameters)
|
||||
{
|
||||
public List<ExpressionNode> Parameters { get; } = parameters;
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Parsing/SyscallExpressionNode.cs
Normal file
6
lang/Nub.Lang/Frontend/Parsing/SyscallExpressionNode.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class SyscallExpressionNode(Syscall syscall) : ExpressionNode
|
||||
{
|
||||
public Syscall Syscall { get; } = syscall;
|
||||
}
|
||||
6
lang/Nub.Lang/Frontend/Parsing/SyscallStatementNode.cs
Normal file
6
lang/Nub.Lang/Frontend/Parsing/SyscallStatementNode.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class SyscallStatementNode(Syscall syscall) : StatementNode
|
||||
{
|
||||
public Syscall Syscall { get; } = syscall;
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
7
lang/Nub.Lang/Frontend/Parsing/WhileNode.cs
Normal file
7
lang/Nub.Lang/Frontend/Parsing/WhileNode.cs
Normal 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;
|
||||
}
|
||||
392
lang/Nub.Lang/Frontend/Typing/ExpressionTyper.cs
Normal file
392
lang/Nub.Lang/Frontend/Typing/ExpressionTyper.cs
Normal file
@@ -0,0 +1,392 @@
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
|
||||
namespace Nub.Lang.Frontend.Typing;
|
||||
|
||||
public class Func(string name, List<FuncParameter> parameters, Optional<BlockNode> body, Optional<Type> returnType)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public Optional<BlockNode> Body { get; } = body;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
}
|
||||
|
||||
public class ExpressionTyper
|
||||
{
|
||||
private readonly List<Func> _functions;
|
||||
private readonly List<GlobalVariableDefinitionNode> _variableDefinitions;
|
||||
private readonly List<StructDefinitionNode> _structDefinitions;
|
||||
private readonly Stack<Variable> _variables;
|
||||
|
||||
public ExpressionTyper(List<DefinitionNode> definitions)
|
||||
{
|
||||
_variables = new Stack<Variable>();
|
||||
_functions = [];
|
||||
_variableDefinitions = [];
|
||||
|
||||
_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);
|
||||
_variableDefinitions.AddRange(definitions.OfType<GlobalVariableDefinitionNode>());
|
||||
}
|
||||
|
||||
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 variable in _variableDefinitions)
|
||||
{
|
||||
PopulateExpression(variable.Value);
|
||||
_variables.Push(new Variable(variable.Name, variable.Value.Type));
|
||||
}
|
||||
|
||||
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 ArrayIndexAssignmentNode arrayIndexAssignment:
|
||||
PopulateArrayIndexAssignment(arrayIndexAssignment);
|
||||
break;
|
||||
case BreakNode:
|
||||
case ContinueNode:
|
||||
break;
|
||||
case FuncCallStatementNode funcCall:
|
||||
PopulateFuncCallStatement(funcCall);
|
||||
break;
|
||||
case IfNode ifStatement:
|
||||
PopulateIf(ifStatement);
|
||||
break;
|
||||
case ReturnNode returnNode:
|
||||
PopulateReturn(returnNode);
|
||||
break;
|
||||
case SyscallStatementNode syscall:
|
||||
PopulateSyscallStatement(syscall);
|
||||
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 PopulateArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment)
|
||||
{
|
||||
PopulateIdentifier(arrayIndexAssignment.Identifier);
|
||||
PopulateExpression(arrayIndexAssignment.Index);
|
||||
PopulateExpression(arrayIndexAssignment.Value);
|
||||
}
|
||||
|
||||
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 PopulateSyscallStatement(SyscallStatementNode syscall)
|
||||
{
|
||||
foreach (var parameter in syscall.Syscall.Parameters)
|
||||
{
|
||||
PopulateExpression(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
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 ArrayIndexAccessNode arrayIndexAccess:
|
||||
PopulateArrayIndexAccess(arrayIndexAccess);
|
||||
break;
|
||||
case ArrayInitializerNode arrayInitializer:
|
||||
PopulateArrayInitializer(arrayInitializer);
|
||||
break;
|
||||
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;
|
||||
case SyscallExpressionNode syscall:
|
||||
PopulateSyscallExpression(syscall);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(expression));
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess)
|
||||
{
|
||||
PopulateExpression(arrayIndexAccess.Index);
|
||||
PopulateIdentifier(arrayIndexAccess.Identifier);
|
||||
|
||||
var variable = _variables.FirstOrDefault(v => v.Name == arrayIndexAccess.Identifier.Identifier);
|
||||
if (variable == null)
|
||||
{
|
||||
throw new Exception($"Variable {arrayIndexAccess.Identifier} is not defined");
|
||||
}
|
||||
|
||||
if (variable.Type is not ArrayType arrayType)
|
||||
{
|
||||
throw new Exception($"Variable {arrayIndexAccess.Identifier} is not an array type");
|
||||
}
|
||||
|
||||
arrayIndexAccess.Type = arrayType.InnerType;
|
||||
}
|
||||
|
||||
private void PopulateArrayInitializer(ArrayInitializerNode arrayInitializer)
|
||||
{
|
||||
arrayInitializer.Type = arrayInitializer.InnerType;
|
||||
}
|
||||
|
||||
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 PrimitiveType(PrimitiveTypeKind.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");
|
||||
}
|
||||
|
||||
if (variable.Type is not StructType variableType)
|
||||
{
|
||||
throw new Exception("Variable " + structMemberAccessor.Members[0] + " is not a struct");
|
||||
}
|
||||
|
||||
var definition = _structDefinitions.FirstOrDefault(sd => sd.Name == variableType.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}");
|
||||
}
|
||||
|
||||
if (member.Type is not StructType memberType)
|
||||
{
|
||||
throw new Exception($"Member {structMemberAccessor.Members[i]} on struct {definition.Name} is not a struct");
|
||||
}
|
||||
|
||||
definition = _structDefinitions.FirstOrDefault(sd => sd.Name == memberType.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 void PopulateSyscallExpression(SyscallExpressionNode syscall)
|
||||
{
|
||||
foreach (var parameter in syscall.Syscall.Parameters)
|
||||
{
|
||||
PopulateExpression(parameter);
|
||||
}
|
||||
|
||||
syscall.Type = new PrimitiveType(PrimitiveTypeKind.Int64);
|
||||
}
|
||||
|
||||
private class Variable(string name, Type type)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
}
|
||||
}
|
||||
9
lang/Nub.Lang/FuncParameter.cs
Normal file
9
lang/Nub.Lang/FuncParameter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Nub.Lang;
|
||||
|
||||
public class FuncParameter(string name, Type type)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
|
||||
public override string ToString() => $"{Name}: {Type}";
|
||||
}
|
||||
10
lang/Nub.Lang/Nub.Lang.csproj
Normal file
10
lang/Nub.Lang/Nub.Lang.csproj
Normal 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>
|
||||
48
lang/Nub.Lang/Optional.cs
Normal file
48
lang/Nub.Lang/Optional.cs
Normal 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<TValue> 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);
|
||||
}
|
||||
87
lang/Nub.Lang/Program.cs
Normal file
87
lang/Nub.Lang/Program.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Nub.Lang.Backend.Custom;
|
||||
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 asm = generator.Generate();
|
||||
|
||||
File.WriteAllText(output, asm);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
lang/Nub.Lang/StructMember.cs
Normal file
10
lang/Nub.Lang/StructMember.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
|
||||
namespace Nub.Lang;
|
||||
|
||||
public class StructMember(string name, Type type, Optional<ExpressionNode> value)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
114
lang/Nub.Lang/Type.cs
Normal file
114
lang/Nub.Lang/Type.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Nub.Lang;
|
||||
|
||||
public abstract class Type
|
||||
{
|
||||
public virtual bool IsAssignableTo(Type otherType)
|
||||
{
|
||||
return this == otherType || otherType is AnyType;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj is not Type otherType) return false;
|
||||
return Equals(otherType);
|
||||
}
|
||||
|
||||
protected abstract bool Equals(Type other);
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public static bool operator == (Type? left, Type? right)
|
||||
{
|
||||
if (left is null && right is null) return true;
|
||||
if (left is null || right is null) return false;
|
||||
return ReferenceEquals(left, right) || left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Type? left, Type? right) => !(left == right);
|
||||
}
|
||||
|
||||
public class AnyType : Type
|
||||
{
|
||||
protected override bool Equals(Type other) => other is AnyType;
|
||||
public override int GetHashCode() => nameof(AnyType).GetHashCode();
|
||||
public override string ToString() => "Any";
|
||||
}
|
||||
|
||||
public class PrimitiveType(PrimitiveTypeKind kind) : Type
|
||||
{
|
||||
// TODO: This should be looked at more in the future
|
||||
public override bool IsAssignableTo(Type otherType)
|
||||
{
|
||||
if (base.IsAssignableTo(otherType)) return true;
|
||||
|
||||
if (otherType is PrimitiveType otherPrimitive)
|
||||
{
|
||||
return (Kind, otherPrimitive.Kind) switch
|
||||
{
|
||||
(PrimitiveTypeKind.Int32, PrimitiveTypeKind.Int64) => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParse(string value, [NotNullWhen(true)] out PrimitiveType? result)
|
||||
{
|
||||
result = value switch
|
||||
{
|
||||
"bool" => new PrimitiveType(PrimitiveTypeKind.Bool),
|
||||
"int64" => new PrimitiveType(PrimitiveTypeKind.Int64),
|
||||
"int32" => new PrimitiveType(PrimitiveTypeKind.Int32),
|
||||
_ => null
|
||||
};
|
||||
|
||||
return result != null;
|
||||
}
|
||||
|
||||
public PrimitiveTypeKind Kind { get; } = kind;
|
||||
|
||||
protected override bool Equals(Type other) => other is PrimitiveType primitiveType && Kind == primitiveType.Kind;
|
||||
public override int GetHashCode() => Kind.GetHashCode();
|
||||
public override string ToString() => Kind.ToString();
|
||||
}
|
||||
|
||||
public enum PrimitiveTypeKind
|
||||
{
|
||||
Bool,
|
||||
Int64,
|
||||
Int32,
|
||||
}
|
||||
|
||||
public class StringType : Type
|
||||
{
|
||||
protected override bool Equals(Type other) => other is StringType;
|
||||
public override int GetHashCode() => nameof(StringType).GetHashCode();
|
||||
public override string ToString() => "String";
|
||||
}
|
||||
|
||||
public class ArrayType(Type innerType) : Type
|
||||
{
|
||||
public Type InnerType { get; } = innerType;
|
||||
|
||||
public override bool IsAssignableTo(Type otherType)
|
||||
{
|
||||
if (otherType is ArrayType arrayType && arrayType.InnerType.IsAssignableTo(InnerType)) return true;
|
||||
return base.IsAssignableTo(otherType);
|
||||
}
|
||||
|
||||
protected override bool Equals(Type other) => other is ArrayType at && InnerType.Equals(at.InnerType);
|
||||
public override int GetHashCode() => HashCode.Combine(InnerType);
|
||||
public override string ToString() => $"Array<{InnerType}>";
|
||||
}
|
||||
|
||||
public class StructType(string name) : Type
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
|
||||
protected override bool Equals(Type other) => other is StructType classType && Name == classType.Name;
|
||||
public override int GetHashCode() => Name.GetHashCode();
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
49
lang/Nub.Lang/Variant.cs
Normal file
49
lang/Nub.Lang/Variant.cs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user