22
build.sh
22
build.sh
@@ -1,15 +1,21 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p out
|
||||
|
||||
dotnet run --project lang/Nub.Lang example out/out.asm
|
||||
echo "setup..."
|
||||
|
||||
gcc -c -g -fno-stack-protector -fno-builtin std/baseline/gc.c -o out/gc.o
|
||||
nasm -g -felf64 std/baseline/str_cmp.asm -o out/str_cmp.o
|
||||
dotnet publish -c Release src/compiler/Nub.Lang
|
||||
|
||||
nasm -g -felf64 std/core/str_len.asm -o out/str_len.o
|
||||
nasm -g -felf64 std/core/arr_size.asm -o out/arr_size.o
|
||||
nasm -g -felf64 std/core/itoa.asm -o out/itoa.o
|
||||
echo "compiling..."
|
||||
|
||||
nasm -g -felf64 out/out.asm -o out/out.o
|
||||
nub example out/out.qbe
|
||||
|
||||
gcc -no-pie -nostartfiles -o out/program out/gc.o out/str_cmp.o out/str_len.o out/arr_size.o out/itoa.o out/out.o
|
||||
nasm -g -felf64 src/runtime/runtime.asm -o out/runtime.o
|
||||
|
||||
qbe out/out.qbe > out/out.s
|
||||
gcc -c -g out/out.s -o out/out.o
|
||||
|
||||
gcc -nostartfiles -o out/program out/runtime.o out/out.o
|
||||
|
||||
echo "done..."
|
||||
|
||||
2
example/c/bindings.nub
Normal file
2
example/c/bindings.nub
Normal file
@@ -0,0 +1,2 @@
|
||||
extern func puts(str: string)
|
||||
extern func printf(fmt: string, ...args: any)
|
||||
@@ -1 +0,0 @@
|
||||
extern func arr_size(array: Array<Any>): int64;
|
||||
@@ -1,38 +0,0 @@
|
||||
let SYS_WRITE = 1;
|
||||
let STD_OUT = 1;
|
||||
let STD_ERR = 2;
|
||||
|
||||
func print(msg: String) {
|
||||
syscall(SYS_WRITE, STD_OUT, msg, str_len(msg));
|
||||
}
|
||||
|
||||
func print(value1: int64) {
|
||||
print(itoa(value1));
|
||||
}
|
||||
|
||||
func print(value2: bool) {
|
||||
if value2 {
|
||||
print("true");
|
||||
} else {
|
||||
print("false");
|
||||
}
|
||||
}
|
||||
|
||||
func println() {
|
||||
print("\n");
|
||||
}
|
||||
|
||||
func println(msg: String) {
|
||||
print(msg);
|
||||
println();
|
||||
}
|
||||
|
||||
func println(value3: bool) {
|
||||
print(value3);
|
||||
println();
|
||||
}
|
||||
|
||||
func println(value4: int64) {
|
||||
print(value4);
|
||||
println();
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
extern func str_len(msg: String): int64;
|
||||
extern func itoa(value: int64): String;
|
||||
@@ -1,16 +1,8 @@
|
||||
import "core";
|
||||
import c
|
||||
|
||||
struct Human {
|
||||
let name: String;
|
||||
let age: int64;
|
||||
}
|
||||
global func main(argc: i64, argv: i64) {
|
||||
printf("args: %d, starts at %p\n", argc, argv)
|
||||
|
||||
func main() {
|
||||
while true {
|
||||
let x = new Human
|
||||
{
|
||||
name = "test",
|
||||
age = 34958743
|
||||
};
|
||||
}
|
||||
x: i8 = 320000
|
||||
printf("%d\n", x)
|
||||
}
|
||||
@@ -1,736 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Backend.Custom;
|
||||
|
||||
public class LabelFactory
|
||||
{
|
||||
private int _index;
|
||||
public string Create() => $"label{++_index}";
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class LiteralToken(Type type, string value) : Token
|
||||
{
|
||||
public Type Type { get; } = type;
|
||||
public string Value { get; } = value;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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}]";
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ArrayInitializerNode(long length, Type innerType) : ExpressionNode
|
||||
{
|
||||
public long Length { get; } = length;
|
||||
public Type InnerType { get; } = innerType;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class GlobalVariableDefinitionNode(string name, ExpressionNode value) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class LiteralNode(string literal, Type type) : ExpressionNode
|
||||
{
|
||||
public string Literal { get; } = literal;
|
||||
public Type LiteralType { get; } = type;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructMemberAccessorNode(List<string> members) : ExpressionNode
|
||||
{
|
||||
public List<string> Members { get; } = members;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class Syscall(List<ExpressionNode> parameters)
|
||||
{
|
||||
public List<ExpressionNode> Parameters { get; } = parameters;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class SyscallExpressionNode(Syscall syscall) : ExpressionNode
|
||||
{
|
||||
public Syscall Syscall { get; } = syscall;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class SyscallStatementNode(Syscall syscall) : StatementNode
|
||||
{
|
||||
public Syscall Syscall { get; } = syscall;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class VariableAssignmentNode(string name, ExpressionNode value) : StatementNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class VariableReassignmentNode(string name, ExpressionNode value) : StatementNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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}";
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders>
|
||||
<Path>../std</Path>
|
||||
<Path>../../example</Path>
|
||||
<Path>../core</Path>
|
||||
</attachedFolders>
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1189
src/compiler/Nub.Lang/Backend/Generator.cs
Normal file
1189
src/compiler/Nub.Lang/Backend/Generator.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,7 @@ 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,
|
||||
@@ -17,6 +15,12 @@ public class Lexer
|
||||
["new"] = Symbol.New,
|
||||
["struct"] = Symbol.Struct,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, Modifier> Modifers = new()
|
||||
{
|
||||
["global"] = Modifier.Global,
|
||||
["extern"] = Modifier.Extern,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<char[], Symbol> Chians = new()
|
||||
{
|
||||
@@ -84,9 +88,14 @@ public class Lexer
|
||||
return new SymbolToken(keywordSymbol);
|
||||
}
|
||||
|
||||
if (Modifers.TryGetValue(buffer, out var modifer))
|
||||
{
|
||||
return new ModifierToken(modifer);
|
||||
}
|
||||
|
||||
if (buffer is "true" or "false")
|
||||
{
|
||||
return new LiteralToken(new PrimitiveType(PrimitiveTypeKind.Bool), buffer);
|
||||
return new LiteralToken(NubPrimitiveType.Bool, buffer);
|
||||
}
|
||||
|
||||
return new IdentifierToken(buffer);
|
||||
@@ -94,16 +103,41 @@ public class Lexer
|
||||
|
||||
if (char.IsDigit(current.Value))
|
||||
{
|
||||
var isFloat = false;
|
||||
var buffer = string.Empty;
|
||||
|
||||
while (current.HasValue && char.IsDigit(current.Value))
|
||||
while (current.HasValue)
|
||||
{
|
||||
buffer += current.Value;
|
||||
Next();
|
||||
if (current.Value == '.')
|
||||
{
|
||||
if (isFloat)
|
||||
{
|
||||
throw new Exception("More than one period found in float literal");
|
||||
}
|
||||
isFloat = true;
|
||||
buffer += current.Value;
|
||||
Next();
|
||||
current = Peek();
|
||||
}
|
||||
else if (char.IsDigit(current.Value))
|
||||
{
|
||||
buffer += current.Value;
|
||||
Next();
|
||||
current = Peek();
|
||||
}
|
||||
else if (current.Value == 'f')
|
||||
{
|
||||
isFloat = true;
|
||||
Next();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new LiteralToken(new PrimitiveType(PrimitiveTypeKind.Int64), buffer);
|
||||
|
||||
return new LiteralToken(isFloat ? NubPrimitiveType.F64 : NubPrimitiveType.I64, buffer);
|
||||
}
|
||||
|
||||
// TODO: Revisit this
|
||||
@@ -148,7 +182,7 @@ public class Lexer
|
||||
buffer += current.Value;
|
||||
}
|
||||
|
||||
return new LiteralToken(new StringType(), buffer);
|
||||
return new LiteralToken(NubPrimitiveType.String, buffer);
|
||||
}
|
||||
|
||||
if (char.IsWhiteSpace(current.Value))
|
||||
7
src/compiler/Nub.Lang/Frontend/Lexing/LiteralToken.cs
Normal file
7
src/compiler/Nub.Lang/Frontend/Lexing/LiteralToken.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class LiteralToken(NubType type, string value) : Token
|
||||
{
|
||||
public NubType Type { get; } = type;
|
||||
public string Value { get; } = value;
|
||||
}
|
||||
12
src/compiler/Nub.Lang/Frontend/Lexing/ModifierToken.cs
Normal file
12
src/compiler/Nub.Lang/Frontend/Lexing/ModifierToken.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Nub.Lang.Frontend.Lexing;
|
||||
|
||||
public class ModifierToken(Modifier symbol) : Token
|
||||
{
|
||||
public Modifier Modifier { get; } = symbol;
|
||||
}
|
||||
|
||||
public enum Modifier
|
||||
{
|
||||
Extern,
|
||||
Global
|
||||
}
|
||||
@@ -9,10 +9,8 @@ public enum Symbol
|
||||
{
|
||||
Whitespace,
|
||||
Import,
|
||||
Extern,
|
||||
Func,
|
||||
Return,
|
||||
Let,
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
public abstract class ExpressionNode : Node
|
||||
{
|
||||
private Type? _type;
|
||||
public Type Type
|
||||
private NubType? _type;
|
||||
public NubType Type
|
||||
{
|
||||
get => _type ?? throw new Exception("Tried to access expression type before type was populated");
|
||||
set => _type = value;
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class ExternFuncDefinitionNode(string name, List<FuncParameter> parameters, Optional<Type> returnType) : DefinitionNode
|
||||
public class ExternFuncDefinitionNode(string name, List<FuncParameter> parameters, Optional<NubType> returnType) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
public Optional<NubType> ReturnType { get; } = returnType;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
|
||||
}
|
||||
7
src/compiler/Nub.Lang/Frontend/Parsing/LiteralNode.cs
Normal file
7
src/compiler/Nub.Lang/Frontend/Parsing/LiteralNode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class LiteralNode(string literal, NubType type) : ExpressionNode
|
||||
{
|
||||
public string Literal { get; } = literal;
|
||||
public NubType LiteralType { get; } = type;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class LocalFuncDefinitionNode(string name, List<FuncParameter> parameters, BlockNode body, Optional<Type> returnType) : DefinitionNode
|
||||
public class LocalFuncDefinitionNode(string name, List<FuncParameter> parameters, BlockNode body, Optional<NubType> returnType, bool global) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public BlockNode Body { get; } = body;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
public Optional<NubType> ReturnType { get; } = returnType;
|
||||
public bool Global { get; } = global;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
|
||||
}
|
||||
@@ -7,26 +7,20 @@ 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);
|
||||
var name = ExpectIdentifier();
|
||||
imports.Add(name.Value);
|
||||
}
|
||||
else
|
||||
@@ -40,28 +34,23 @@ public class Parser
|
||||
|
||||
private DefinitionNode ParseDefinition()
|
||||
{
|
||||
List<Modifier> modifiers = [];
|
||||
|
||||
while (TryExpectModifier(out var modifier))
|
||||
{
|
||||
modifiers.Add(modifier);
|
||||
}
|
||||
|
||||
var keyword = ExpectSymbol();
|
||||
return keyword.Symbol switch
|
||||
{
|
||||
Symbol.Let => ParseGlobalVariableDefinition(),
|
||||
Symbol.Func => ParseFuncDefinition(),
|
||||
Symbol.Extern => ParseExternFuncDefinition(),
|
||||
Symbol.Struct => ParseStruct(),
|
||||
Symbol.Func => ParseFuncDefinition(modifiers),
|
||||
Symbol.Struct => ParseStruct(modifiers),
|
||||
_ => 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()
|
||||
private DefinitionNode ParseFuncDefinition(List<Modifier> modifiers)
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
List<FuncParameter> parameters = [];
|
||||
@@ -75,80 +64,73 @@ public class Parser
|
||||
}
|
||||
}
|
||||
|
||||
var returnType = Optional<Type>.Empty();
|
||||
var returnType = Optional<NubType>.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))
|
||||
if (modifiers.Remove(Modifier.Extern))
|
||||
{
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
if (modifiers.Count != 0)
|
||||
{
|
||||
parameters.Add(ParseFuncParameter());
|
||||
TryExpectSymbol(Symbol.Comma);
|
||||
throw new Exception($"Modifiers: {string.Join(", ", modifiers)} is not valid for an extern function");
|
||||
}
|
||||
return new ExternFuncDefinitionNode(name.Value, parameters, returnType);
|
||||
}
|
||||
|
||||
var returnType = Optional<Type>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Colon))
|
||||
{
|
||||
returnType = ParseType();
|
||||
}
|
||||
|
||||
ExpectSymbol(Symbol.Semicolon);
|
||||
|
||||
return new ExternFuncDefinitionNode(name.Value, parameters, returnType);
|
||||
var body = ParseBlock();
|
||||
var global = modifiers.Remove(Modifier.Global);
|
||||
|
||||
if (modifiers.Count != 0)
|
||||
{
|
||||
throw new Exception($"Modifiers: {string.Join(", ", modifiers)} is not valid for a local function");
|
||||
}
|
||||
return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType, global);
|
||||
}
|
||||
|
||||
private StructDefinitionNode ParseStruct()
|
||||
private StructDefinitionNode ParseStruct(List<Modifier> modifiers)
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
|
||||
List<StructMember> variables = [];
|
||||
|
||||
|
||||
List<StructField> 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));
|
||||
|
||||
variables.Add(new StructField(variableName, variableType, variableValue));
|
||||
}
|
||||
|
||||
|
||||
return new StructDefinitionNode(name, variables);
|
||||
}
|
||||
|
||||
private FuncParameter ParseFuncParameter()
|
||||
{
|
||||
var variadic = false;
|
||||
if (TryExpectSymbol(Symbol.Period))
|
||||
{
|
||||
ExpectSymbol(Symbol.Period);
|
||||
ExpectSymbol(Symbol.Period);
|
||||
variadic = true;
|
||||
}
|
||||
|
||||
var name = ExpectIdentifier();
|
||||
ExpectSymbol(Symbol.Colon);
|
||||
var type = ParseType();
|
||||
|
||||
return new FuncParameter(name.Value, type);
|
||||
return new FuncParameter(name.Value, type, variadic);
|
||||
}
|
||||
|
||||
private StatementNode ParseStatement()
|
||||
@@ -170,29 +152,19 @@ public class Parser
|
||||
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);
|
||||
return new VariableAssignmentNode(identifier.Value, Optional<NubType>.Empty(), value);
|
||||
}
|
||||
case Symbol.Colon:
|
||||
{
|
||||
var type = ParseType();
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
return new VariableAssignmentNode(identifier.Value,type, value);
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -205,11 +177,10 @@ public class Parser
|
||||
return symbol.Symbol switch
|
||||
{
|
||||
Symbol.Return => ParseReturn(),
|
||||
Symbol.Let => ParseVariableAssignment(),
|
||||
Symbol.If => ParseIf(),
|
||||
Symbol.While => ParseWhile(),
|
||||
Symbol.Break => ParseBreak(),
|
||||
Symbol.Continue => ParseContinue(),
|
||||
Symbol.Break => new BreakNode(),
|
||||
Symbol.Continue => new ContinueNode(),
|
||||
_ => throw new Exception($"Unexpected symbol {symbol.Symbol}")
|
||||
};
|
||||
}
|
||||
@@ -226,22 +197,11 @@ public class Parser
|
||||
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();
|
||||
@@ -254,7 +214,7 @@ public class Parser
|
||||
? (Variant<IfNode, BlockNode>)ParseIf()
|
||||
: (Variant<IfNode, BlockNode>)ParseBlock();
|
||||
}
|
||||
|
||||
|
||||
return new IfNode(condition, body, elseStatement);
|
||||
}
|
||||
|
||||
@@ -265,18 +225,6 @@ public class Parser
|
||||
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();
|
||||
@@ -284,15 +232,16 @@ public class Parser
|
||||
while (true)
|
||||
{
|
||||
var token = Peek();
|
||||
if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) || GetBinaryOperatorPrecedence(op.Value) < precedence)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -360,9 +309,13 @@ public class Parser
|
||||
switch (token)
|
||||
{
|
||||
case LiteralToken literal:
|
||||
{
|
||||
return new LiteralNode(literal.Value, literal.Type);
|
||||
}
|
||||
case IdentifierToken identifier:
|
||||
{
|
||||
return ParseExpressionIdentifier(identifier);
|
||||
}
|
||||
case SymbolToken symbolToken:
|
||||
{
|
||||
switch (symbolToken.Symbol)
|
||||
@@ -376,44 +329,25 @@ public class Parser
|
||||
case Symbol.New:
|
||||
{
|
||||
var type = ParseType();
|
||||
switch (type)
|
||||
Dictionary<string, ExpressionNode> initializers = [];
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
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");
|
||||
var name = ExpectIdentifier().Value;
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
initializers.Add(name, value);
|
||||
}
|
||||
|
||||
return new StructInitializerNode(type, initializers);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception($"Unknown symbol: {symbolToken.Symbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
default:
|
||||
throw new Exception($"Unexpected token type {token.GetType().Name}");
|
||||
}
|
||||
}
|
||||
@@ -435,25 +369,15 @@ public class Parser
|
||||
case Symbol.Period:
|
||||
{
|
||||
Next();
|
||||
List<string> members =
|
||||
[
|
||||
identifier.Value,
|
||||
ExpectIdentifier().Value
|
||||
];
|
||||
ExpressionNode result = new IdentifierNode(identifier.Value);
|
||||
|
||||
while (TryExpectSymbol(Symbol.Period))
|
||||
do
|
||||
{
|
||||
members.Add(ExpectIdentifier().Value);
|
||||
}
|
||||
var field = ExpectIdentifier();
|
||||
result = new StructFieldAccessorNode(result, field.Value);
|
||||
} while (TryExpectSymbol(Symbol.Period));
|
||||
|
||||
return new StructMemberAccessorNode(members);
|
||||
}
|
||||
case Symbol.OpenBracket:
|
||||
{
|
||||
Next();
|
||||
var index = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
return new ArrayIndexAccessNode(new IdentifierNode(identifier.Value), index);
|
||||
return result;
|
||||
}
|
||||
case Symbol.OpenParen:
|
||||
{
|
||||
@@ -465,18 +389,14 @@ public class Parser
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -492,36 +412,10 @@ public class Parser
|
||||
return new BlockNode(statements);
|
||||
}
|
||||
|
||||
private Type ParseType()
|
||||
private NubType 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);
|
||||
}
|
||||
}
|
||||
return NubType.Parse(name);
|
||||
}
|
||||
|
||||
private Token ExpectToken()
|
||||
@@ -563,6 +457,19 @@ public class Parser
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TryExpectModifier(out Modifier modifier)
|
||||
{
|
||||
if (Peek() is { HasValue: true, Value: ModifierToken modifierToken })
|
||||
{
|
||||
modifier = modifierToken.Modifier;
|
||||
Next();
|
||||
return true;
|
||||
}
|
||||
|
||||
modifier = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private IdentifierToken ExpectIdentifier()
|
||||
{
|
||||
var token = ExpectToken();
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructDefinitionNode(string name, List<StructField> fields) : DefinitionNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<StructField> Fields { get; } = fields;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructFieldAccessorNode(ExpressionNode @struct, string field) : ExpressionNode
|
||||
{
|
||||
public ExpressionNode Struct { get; } = @struct;
|
||||
public string Field { get; } = field;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class StructInitializerNode(NubType structType, Dictionary<string, ExpressionNode> initializers) : ExpressionNode
|
||||
{
|
||||
public NubType StructType { get; } = structType;
|
||||
public Dictionary<string, ExpressionNode> Initializers { get; } = initializers;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Nub.Lang.Frontend.Parsing;
|
||||
|
||||
public class VariableAssignmentNode(string name, Optional<NubType> explicitType, ExpressionNode value) : StatementNode
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Optional<NubType> ExplicitType { get; } = explicitType;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
@@ -2,18 +2,17 @@
|
||||
|
||||
namespace Nub.Lang.Frontend.Typing;
|
||||
|
||||
public class Func(string name, List<FuncParameter> parameters, Optional<BlockNode> body, Optional<Type> returnType)
|
||||
public class Func(string name, List<FuncParameter> parameters, Optional<BlockNode> body, Optional<NubType> returnType)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public Optional<BlockNode> Body { get; } = body;
|
||||
public Optional<Type> ReturnType { get; } = returnType;
|
||||
public Optional<NubType> 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;
|
||||
|
||||
@@ -21,7 +20,6 @@ public class ExpressionTyper
|
||||
{
|
||||
_variables = new Stack<Variable>();
|
||||
_functions = [];
|
||||
_variableDefinitions = [];
|
||||
|
||||
_structDefinitions = definitions.OfType<StructDefinitionNode>().ToList();
|
||||
|
||||
@@ -37,7 +35,6 @@ public class ExpressionTyper
|
||||
|
||||
_functions.AddRange(functions);
|
||||
_functions.AddRange(externFunctions);
|
||||
_variableDefinitions.AddRange(definitions.OfType<GlobalVariableDefinitionNode>());
|
||||
}
|
||||
|
||||
public void Populate()
|
||||
@@ -46,7 +43,7 @@ public class ExpressionTyper
|
||||
|
||||
foreach (var @class in _structDefinitions)
|
||||
{
|
||||
foreach (var variable in @class.Members)
|
||||
foreach (var variable in @class.Fields)
|
||||
{
|
||||
if (variable.Value.HasValue)
|
||||
{
|
||||
@@ -55,12 +52,6 @@ public class ExpressionTyper
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -96,9 +87,6 @@ public class ExpressionTyper
|
||||
{
|
||||
switch (statement)
|
||||
{
|
||||
case ArrayIndexAssignmentNode arrayIndexAssignment:
|
||||
PopulateArrayIndexAssignment(arrayIndexAssignment);
|
||||
break;
|
||||
case BreakNode:
|
||||
case ContinueNode:
|
||||
break;
|
||||
@@ -111,15 +99,9 @@ public class ExpressionTyper
|
||||
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;
|
||||
@@ -128,13 +110,6 @@ public class ExpressionTyper
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -157,14 +132,6 @@ public class ExpressionTyper
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateSyscallStatement(SyscallStatementNode syscall)
|
||||
{
|
||||
foreach (var parameter in syscall.Syscall.Parameters)
|
||||
{
|
||||
PopulateExpression(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateReturn(ReturnNode returnNode)
|
||||
{
|
||||
if (returnNode.Value.HasValue)
|
||||
@@ -176,12 +143,12 @@ public class ExpressionTyper
|
||||
private void PopulateVariableAssignment(VariableAssignmentNode variableAssignment)
|
||||
{
|
||||
PopulateExpression(variableAssignment.Value);
|
||||
_variables.Push(new Variable(variableAssignment.Name, variableAssignment.Value.Type));
|
||||
_variables.Push(new Variable(variableAssignment.Name, variableAssignment.ExplicitType.HasValue ? variableAssignment.ExplicitType.Value : variableAssignment.Value.Type));
|
||||
}
|
||||
|
||||
private void PopulateVariableReassignment(VariableReassignmentNode variableReassignment)
|
||||
private void PopulateVariableReassignment(VariableAssignmentNode variableAssignment)
|
||||
{
|
||||
PopulateExpression(variableReassignment.Value);
|
||||
PopulateExpression(variableAssignment.Value);
|
||||
}
|
||||
|
||||
private void PopulateWhileStatement(WhileNode whileStatement)
|
||||
@@ -194,12 +161,6 @@ public class ExpressionTyper
|
||||
{
|
||||
switch (expression)
|
||||
{
|
||||
case ArrayIndexAccessNode arrayIndexAccess:
|
||||
PopulateArrayIndexAccess(arrayIndexAccess);
|
||||
break;
|
||||
case ArrayInitializerNode arrayInitializer:
|
||||
PopulateArrayInitializer(arrayInitializer);
|
||||
break;
|
||||
case BinaryExpressionNode binaryExpression:
|
||||
PopulateBinaryExpression(binaryExpression);
|
||||
break;
|
||||
@@ -215,41 +176,14 @@ public class ExpressionTyper
|
||||
case StructInitializerNode structInitializer:
|
||||
PopulateStructInitializer(structInitializer);
|
||||
break;
|
||||
case StructMemberAccessorNode structMemberAccessor:
|
||||
GenerateStructMemberAccessorNode(structMemberAccessor);
|
||||
break;
|
||||
case SyscallExpressionNode syscall:
|
||||
PopulateSyscallExpression(syscall);
|
||||
case StructFieldAccessorNode structMemberAccessor:
|
||||
PopulateStructMemberAccessorNode(structMemberAccessor);
|
||||
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);
|
||||
@@ -263,7 +197,7 @@ public class ExpressionTyper
|
||||
case BinaryExpressionOperator.LessThan:
|
||||
case BinaryExpressionOperator.LessThanOrEqual:
|
||||
{
|
||||
binaryExpression.Type = new PrimitiveType(PrimitiveTypeKind.Bool);
|
||||
binaryExpression.Type = NubPrimitiveType.Bool;
|
||||
break;
|
||||
}
|
||||
case BinaryExpressionOperator.Plus:
|
||||
@@ -325,68 +259,34 @@ public class ExpressionTyper
|
||||
structInitializer.Type = structInitializer.StructType;
|
||||
}
|
||||
|
||||
// TODO: Fix this ugly ass code
|
||||
private void GenerateStructMemberAccessorNode(StructMemberAccessorNode structMemberAccessor)
|
||||
private void PopulateStructMemberAccessorNode(StructFieldAccessorNode structFieldAccessor)
|
||||
{
|
||||
var variable = _variables.FirstOrDefault(v => v.Name == structMemberAccessor.Members[0]);
|
||||
if (variable == null)
|
||||
PopulateExpression(structFieldAccessor.Struct);
|
||||
|
||||
var structType = structFieldAccessor.Struct.Type;
|
||||
if (structType == null)
|
||||
{
|
||||
throw new Exception($"Variable {structMemberAccessor.Members[0]} is not defined");
|
||||
throw new Exception($"Cannot access field on non-struct type: {structFieldAccessor.Struct}");
|
||||
}
|
||||
|
||||
if (variable.Type is not StructType variableType)
|
||||
|
||||
var structDefinition = _structDefinitions.FirstOrDefault(s => s.Name == structType.Name);
|
||||
if (structDefinition == null)
|
||||
{
|
||||
throw new Exception("Variable " + structMemberAccessor.Members[0] + " is not a struct");
|
||||
throw new Exception($"Struct {structType.Name} is not defined");
|
||||
}
|
||||
|
||||
var definition = _structDefinitions.FirstOrDefault(sd => sd.Name == variableType.Name);
|
||||
if (definition == null)
|
||||
|
||||
var field = structDefinition.Fields.FirstOrDefault(f => f.Name == structFieldAccessor.Field);
|
||||
if (field == null)
|
||||
{
|
||||
throw new Exception($"Struct {structMemberAccessor.Members[0]} is not defined");
|
||||
throw new Exception($"Field {structFieldAccessor.Field} is not defined in struct {structType.Name}");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
structFieldAccessor.Type = field.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)
|
||||
private class Variable(string name, NubType type)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
public NubType Type { get; } = type;
|
||||
}
|
||||
}
|
||||
10
src/compiler/Nub.Lang/FuncParameter.cs
Normal file
10
src/compiler/Nub.Lang/FuncParameter.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Nub.Lang;
|
||||
|
||||
public class FuncParameter(string name, NubType type, bool variadic)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public NubType Type { get; } = type;
|
||||
public bool Variadic { get; } = variadic;
|
||||
|
||||
public override string ToString() => $"{Name}: {Type}";
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>nub</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
120
src/compiler/Nub.Lang/NubType.cs
Normal file
120
src/compiler/Nub.Lang/NubType.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Nub.Lang;
|
||||
|
||||
public abstract class NubType
|
||||
{
|
||||
protected NubType(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public static NubType Parse(string s)
|
||||
{
|
||||
if (NubPrimitiveType.TryParse(s, out var kind))
|
||||
{
|
||||
return new NubPrimitiveType(kind.Value);
|
||||
}
|
||||
|
||||
return new NubCustomType(s);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is NubType item && Name.Equals(item.Name);
|
||||
public override int GetHashCode() => HashCode.Combine(Name);
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
public class NubCustomType(string name) : NubType(name);
|
||||
|
||||
public class NubPrimitiveType : NubType
|
||||
{
|
||||
public NubPrimitiveType(PrimitiveTypeKind kind) : base(KindToString(kind))
|
||||
{
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public PrimitiveTypeKind Kind { get; }
|
||||
|
||||
public static NubPrimitiveType I64 => new(PrimitiveTypeKind.I64);
|
||||
public static NubPrimitiveType I32 => new(PrimitiveTypeKind.I32);
|
||||
public static NubPrimitiveType I16 => new(PrimitiveTypeKind.I16);
|
||||
public static NubPrimitiveType I8 => new(PrimitiveTypeKind.I8);
|
||||
|
||||
public static NubPrimitiveType U64 => new(PrimitiveTypeKind.U64);
|
||||
public static NubPrimitiveType U32 => new(PrimitiveTypeKind.U32);
|
||||
public static NubPrimitiveType U16 => new(PrimitiveTypeKind.U16);
|
||||
public static NubPrimitiveType U8 => new(PrimitiveTypeKind.U8);
|
||||
|
||||
public static NubPrimitiveType F64 => new(PrimitiveTypeKind.F64);
|
||||
public static NubPrimitiveType F32 => new(PrimitiveTypeKind.F32);
|
||||
|
||||
public static NubPrimitiveType Bool => new(PrimitiveTypeKind.Bool);
|
||||
public static NubPrimitiveType String => new(PrimitiveTypeKind.String);
|
||||
public static NubPrimitiveType Any => new(PrimitiveTypeKind.Any);
|
||||
|
||||
public static bool TryParse(string s, [NotNullWhen(true)] out PrimitiveTypeKind? kind)
|
||||
{
|
||||
kind = s switch
|
||||
{
|
||||
"i64" => PrimitiveTypeKind.I64,
|
||||
"i32" => PrimitiveTypeKind.I32,
|
||||
"i16" => PrimitiveTypeKind.I16,
|
||||
"i8" => PrimitiveTypeKind.I8,
|
||||
"u64" => PrimitiveTypeKind.U64,
|
||||
"u32" => PrimitiveTypeKind.U32,
|
||||
"u16" => PrimitiveTypeKind.U16,
|
||||
"u8" => PrimitiveTypeKind.U8,
|
||||
"f64" => PrimitiveTypeKind.F64,
|
||||
"f32" => PrimitiveTypeKind.F32,
|
||||
"bool" => PrimitiveTypeKind.Bool,
|
||||
"string" => PrimitiveTypeKind.String,
|
||||
"any" => PrimitiveTypeKind.Any,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return kind != null;
|
||||
}
|
||||
|
||||
public static string KindToString(PrimitiveTypeKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
PrimitiveTypeKind.I8 => "i8",
|
||||
PrimitiveTypeKind.I16 => "i16",
|
||||
PrimitiveTypeKind.I32 => "i32",
|
||||
PrimitiveTypeKind.I64 => "i64",
|
||||
|
||||
PrimitiveTypeKind.U8 => "u8",
|
||||
PrimitiveTypeKind.U16 => "u16",
|
||||
PrimitiveTypeKind.U32 => "u32",
|
||||
PrimitiveTypeKind.U64 => "u64",
|
||||
|
||||
PrimitiveTypeKind.F32 => "f32",
|
||||
PrimitiveTypeKind.F64 => "f64",
|
||||
|
||||
PrimitiveTypeKind.Bool => "bool",
|
||||
PrimitiveTypeKind.String => "string",
|
||||
PrimitiveTypeKind.Any => "any",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum PrimitiveTypeKind
|
||||
{
|
||||
I64,
|
||||
I32,
|
||||
I16,
|
||||
I8,
|
||||
U64,
|
||||
U32,
|
||||
U16,
|
||||
U8,
|
||||
F64,
|
||||
F32,
|
||||
Bool,
|
||||
String,
|
||||
Any
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Nub.Lang.Backend.Custom;
|
||||
using Nub.Lang.Backend;
|
||||
using Nub.Lang.Frontend.Lexing;
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
using Nub.Lang.Frontend.Typing;
|
||||
@@ -48,9 +48,9 @@ internal static class Program
|
||||
typer.Populate();
|
||||
|
||||
var generator = new Generator(definitions);
|
||||
var asm = generator.Generate();
|
||||
var result = generator.Generate();
|
||||
|
||||
File.WriteAllText(output, asm);
|
||||
File.WriteAllText(output, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Nub.Lang;
|
||||
|
||||
public class StructMember(string name, Type type, Optional<ExpressionNode> value)
|
||||
public class StructField(string name, NubType type, Optional<ExpressionNode> value)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Type Type { get; } = type;
|
||||
public NubType Type { get; } = type;
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
36
src/runtime/runtime.asm
Normal file
36
src/runtime/runtime.asm
Normal file
@@ -0,0 +1,36 @@
|
||||
global _start
|
||||
extern main
|
||||
|
||||
section .text
|
||||
_start:
|
||||
; Extract argc and argv from the stack
|
||||
mov rdi, [rsp] ; rdi = argc
|
||||
lea rsi, [rsp + 8] ; rsi = argv (pointer to array of strings)
|
||||
|
||||
; Call main(argc, argv)
|
||||
call main ; main returns int in rax
|
||||
|
||||
; Exit with main's return value
|
||||
mov rdi, rax ; exit code
|
||||
mov rax, 60 ; syscall: exit
|
||||
syscall
|
||||
|
||||
global nub_strcmp
|
||||
|
||||
nub_strcmp:
|
||||
xor rdx, rdx
|
||||
.loop:
|
||||
mov al, [rsi + rdx]
|
||||
mov bl, [rdi + rdx]
|
||||
inc rdx
|
||||
cmp al, bl
|
||||
jne .not_equal
|
||||
cmp al, 0
|
||||
je .equal
|
||||
jmp .loop
|
||||
.not_equal:
|
||||
mov rax, 0
|
||||
ret
|
||||
.equal:
|
||||
mov rax, 1
|
||||
ret
|
||||
@@ -1,264 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define MINIMUM_THRESHOLD (1024 * 1024 * 8)
|
||||
#define MINIMUM_BLOCK_SIZE 4096
|
||||
|
||||
typedef struct alloc_block {
|
||||
uint64_t mark;
|
||||
uint64_t size;
|
||||
struct alloc_block* next;
|
||||
} alloc_block_t;
|
||||
|
||||
typedef struct free_block {
|
||||
uint64_t size;
|
||||
struct free_block* next;
|
||||
} free_block_t;
|
||||
|
||||
static alloc_block_t* alloc_list_head = NULL;
|
||||
static free_block_t* free_list_head = NULL;
|
||||
static void* stack_start = NULL;
|
||||
static int64_t free_list_size = 0;
|
||||
static int64_t mark_count = 0;
|
||||
|
||||
/* Bytes allocated since last collect */
|
||||
static int64_t bytes_allocated = 0;
|
||||
/* Threshold for next collect */
|
||||
static int64_t trigger_threshold = MINIMUM_THRESHOLD;
|
||||
|
||||
static void* sys_mmap(size_t size);
|
||||
static void* get_sp(void);
|
||||
static void gc_collect(void);
|
||||
static void gc_mark(void* ptr);
|
||||
static void gc_mark_stack(void);
|
||||
static void gc_sweep(void);
|
||||
static int64_t max(int64_t a, int64_t b);
|
||||
static void insert_into_free(free_block_t* block);
|
||||
static void merge(free_block_t* block);
|
||||
|
||||
void gc_init(void) {
|
||||
stack_start = get_sp();
|
||||
}
|
||||
|
||||
/* Allocate memory with garbage collection */
|
||||
void* gc_alloc(int64_t size) {
|
||||
size += sizeof(alloc_block_t); // Adjust for metadata size
|
||||
|
||||
if (bytes_allocated > trigger_threshold) {
|
||||
gc_collect();
|
||||
}
|
||||
|
||||
bytes_allocated += size;
|
||||
|
||||
// Search free list for a suitable block
|
||||
free_block_t* current = free_list_head;
|
||||
free_block_t* prev = NULL;
|
||||
|
||||
while (current != NULL) {
|
||||
if (current->size >= size) {
|
||||
// Found a suitable block
|
||||
break;
|
||||
}
|
||||
prev = current;
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
if (current == NULL) {
|
||||
// No suitable block found, allocate a new one
|
||||
int64_t alloc_size = max(size, MINIMUM_BLOCK_SIZE);
|
||||
void* memory = sys_mmap(alloc_size);
|
||||
|
||||
free_block_t* new_block = (free_block_t*)memory;
|
||||
new_block->size = alloc_size - sizeof(free_block_t);
|
||||
new_block->next = NULL;
|
||||
|
||||
insert_into_free(new_block);
|
||||
current = new_block;
|
||||
|
||||
// Recalculate prev
|
||||
if (current == free_list_head) {
|
||||
prev = NULL;
|
||||
} else {
|
||||
prev = free_list_head;
|
||||
while (prev->next != current) {
|
||||
prev = prev->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the block
|
||||
alloc_block_t* result;
|
||||
|
||||
if (current->size > size) {
|
||||
// Block is larger than needed, split it
|
||||
result = (alloc_block_t*)((char*)current + current->size + sizeof(free_block_t) - size);
|
||||
current->size -= size;
|
||||
} else {
|
||||
// Use the entire block
|
||||
result = (alloc_block_t*)current;
|
||||
|
||||
// Remove block from free list
|
||||
if (prev == NULL) {
|
||||
free_list_head = current->next;
|
||||
} else {
|
||||
prev->next = current->next;
|
||||
}
|
||||
|
||||
free_list_size--;
|
||||
}
|
||||
|
||||
// Initialize metadata
|
||||
result->mark = 0;
|
||||
result->size = size - sizeof(alloc_block_t);
|
||||
result->next = alloc_list_head;
|
||||
alloc_list_head = result;
|
||||
|
||||
// Return pointer to usable memory
|
||||
return (void*)(result + 1);
|
||||
}
|
||||
|
||||
/* Run garbage collection */
|
||||
static void gc_collect(void) {
|
||||
gc_mark_stack();
|
||||
gc_sweep();
|
||||
trigger_threshold = max(bytes_allocated * 2, MINIMUM_THRESHOLD);
|
||||
bytes_allocated = 0;
|
||||
}
|
||||
|
||||
static void gc_mark_stack(void) {
|
||||
mark_count = 0;
|
||||
|
||||
void** current = get_sp();
|
||||
void** end = (void**)stack_start;
|
||||
|
||||
while (current < end) {
|
||||
gc_mark(*current);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark a single object and recursively mark its contents */
|
||||
static void gc_mark(void* ptr) {
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
alloc_block_t* block = alloc_list_head;
|
||||
while (block != NULL) {
|
||||
void* block_data = (void*)(block + 1);
|
||||
if (block_data == ptr) {
|
||||
if (block->mark == 0) {
|
||||
mark_count++;
|
||||
block->mark = 1;
|
||||
|
||||
void** p = (void**)block_data;
|
||||
void** end = (void**)((char*)block_data + block->size);
|
||||
while (p < end) {
|
||||
gc_mark(*p);
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
block = block->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void gc_sweep(void) {
|
||||
alloc_block_t* current = alloc_list_head;
|
||||
alloc_block_t* prev = NULL;
|
||||
|
||||
while (current != NULL) {
|
||||
if (current->mark == 0) {
|
||||
alloc_block_t* next = current->next;
|
||||
|
||||
if (prev == NULL) {
|
||||
alloc_list_head = next;
|
||||
} else {
|
||||
prev->next = next;
|
||||
}
|
||||
|
||||
bytes_allocated -= (current->size + sizeof(alloc_block_t));
|
||||
|
||||
free_block_t* free_block = (free_block_t*)current;
|
||||
free_block->size = current->size + sizeof(alloc_block_t) - sizeof(free_block_t);
|
||||
free_block->next = NULL;
|
||||
|
||||
insert_into_free(free_block);
|
||||
|
||||
current = next;
|
||||
} else {
|
||||
current->mark = 0;
|
||||
prev = current;
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert a block into the free list, maintaining address order */
|
||||
static void insert_into_free(free_block_t* block) {
|
||||
if (free_list_head == NULL || block < free_list_head) {
|
||||
// Insert at head
|
||||
block->next = free_list_head;
|
||||
free_list_head = block;
|
||||
free_list_size++;
|
||||
merge(block);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find insertion point
|
||||
free_block_t* current = free_list_head;
|
||||
while (current->next != NULL && current->next < block) {
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
// Insert after current
|
||||
block->next = current->next;
|
||||
current->next = block;
|
||||
free_list_size++;
|
||||
|
||||
// Try to merge adjacent blocks
|
||||
merge(current);
|
||||
}
|
||||
|
||||
static void merge(free_block_t* block) {
|
||||
while (block->next != NULL) {
|
||||
char* block_end = (char*)block + block->size + sizeof(free_block_t);
|
||||
if (block_end == (char*)block->next) {
|
||||
free_list_size--;
|
||||
block->size += block->next->size + sizeof(free_block_t);
|
||||
block->next = block->next->next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void* sys_mmap(size_t size) {
|
||||
void* result = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
if (result == MAP_FAILED) {
|
||||
perror("[sys_mmap] mmap failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int64_t max(int64_t a, int64_t b) {
|
||||
if (a > b) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
void* get_sp(void) {
|
||||
volatile unsigned long var = 0;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wreturn-local-addr"
|
||||
return (void*)((unsigned long)&var + 4);
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
global str_cmp
|
||||
|
||||
section .text
|
||||
str_cmp:
|
||||
xor rdx, rdx
|
||||
.loop:
|
||||
mov al, [rsi + rdx]
|
||||
mov bl, [rdi + rdx]
|
||||
inc rdx
|
||||
cmp al, bl
|
||||
jne .not_equal
|
||||
cmp al, 0
|
||||
je .equal
|
||||
jmp .loop
|
||||
.not_equal:
|
||||
mov rax, 0
|
||||
ret
|
||||
.equal:
|
||||
mov rax, 1
|
||||
ret
|
||||
@@ -1,6 +0,0 @@
|
||||
global arr_size
|
||||
|
||||
section .text
|
||||
arr_size:
|
||||
mov rax, [rdi]
|
||||
ret
|
||||
@@ -1,23 +0,0 @@
|
||||
section .bss
|
||||
buffer resb 20
|
||||
|
||||
section .text
|
||||
global itoa
|
||||
|
||||
itoa:
|
||||
mov rax, rdi
|
||||
mov rsi, buffer + 19
|
||||
mov byte [rsi], 0
|
||||
dec rsi
|
||||
.loop:
|
||||
xor rdx, rdx
|
||||
mov rcx, 10
|
||||
div rcx
|
||||
add dl, '0'
|
||||
mov [rsi], dl
|
||||
dec rsi
|
||||
test rax, rax
|
||||
jnz .loop
|
||||
inc rsi
|
||||
mov rax, rsi
|
||||
ret
|
||||
@@ -1,13 +0,0 @@
|
||||
global str_len
|
||||
|
||||
section .text
|
||||
str_len:
|
||||
xor rax, rax
|
||||
.loop:
|
||||
cmp byte [rdi], 0
|
||||
jz .done
|
||||
inc rax
|
||||
inc rdi
|
||||
jmp .loop
|
||||
.done:
|
||||
ret
|
||||
Reference in New Issue
Block a user