Skip to content

Commit

Permalink
Merge pull request #3 from R-unic/feat/functions
Browse files Browse the repository at this point in the history
Functions
  • Loading branch information
R-unic authored Jan 13, 2025
2 parents 82c3d52 + 38db8b4 commit 1520bee
Show file tree
Hide file tree
Showing 45 changed files with 1,273 additions and 290 deletions.
124 changes: 123 additions & 1 deletion Heir.Tests/BinderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,128 @@ public void ThrowsWith(string input, DiagnosticCode expectedErrorCode)
Assert.Contains(boundTree.Diagnostics, diagnostic => diagnostic.Code == expectedErrorCode);
}

[Theory]
[InlineData("fn add(x: int, y = 1): int { return x + y; }")]
[InlineData("fn add(x: int, y = 1): int -> x + y;")]
[InlineData("fn add(x: int, y = 1) { return x + y; }")]
[InlineData("fn add(x: int, y = 1) -> x + y;")]
[InlineData("fn add(x: int, y: int = 1) { return x + y; }")]
public void Binds_FunctionDeclarations_WithParameters(string input)
{
var tree = Bind(input);
var statement = tree.Statements.First();
Assert.IsType<BoundFunctionDeclaration>(statement);

var functionDeclaration = (BoundFunctionDeclaration)statement;
Assert.Equal(SyntaxKind.FnKeyword, functionDeclaration.Keyword.Kind);
Assert.Equal("add", functionDeclaration.Symbol.Name.Text);
Assert.Equal(2, functionDeclaration.Parameters.Count);

var xParameter = functionDeclaration.Parameters.First();
var yParameter = functionDeclaration.Parameters.Last();
Assert.Equal("x", xParameter.Symbol.Name.Text);
Assert.Equal("y", yParameter.Symbol.Name.Text);
Assert.IsType<PrimitiveType>(xParameter.Type);
Assert.IsType<PrimitiveType>(yParameter.Type);
Assert.Null(xParameter.Initializer);
Assert.IsType<BoundLiteral>(yParameter.Initializer);

var xType = (PrimitiveType)xParameter.Type;
var yType = (PrimitiveType)xParameter.Type;
Assert.Equal(PrimitiveTypeKind.Int, xType.PrimitiveKind);
Assert.Equal(PrimitiveTypeKind.Int, yType.PrimitiveKind);

Assert.Single(functionDeclaration.Body.Statements);
Assert.IsType<BoundReturn>(functionDeclaration.Body.Statements.First());

var returnStatement = (BoundReturn)functionDeclaration.Body.Statements.First();
Assert.IsType<BoundBinaryOp>(returnStatement.Expression);

var binaryOp = (BoundBinaryOp)returnStatement.Expression;
Assert.IsType<UnionType>(binaryOp.Type);

var binaryOpType = (UnionType)binaryOp.Type;
Assert.Equal(2, binaryOpType.Types.Count);
Assert.IsType<PrimitiveType>(binaryOpType.Types.First());
Assert.IsType<PrimitiveType>(binaryOpType.Types.Last());

var binaryOpType1 = (PrimitiveType)binaryOpType.Types.First();
var binaryOpType2 = (PrimitiveType)binaryOpType.Types.Last();
Assert.Equal(PrimitiveTypeKind.Int, binaryOpType1.PrimitiveKind);
Assert.Equal(PrimitiveTypeKind.Float, binaryOpType2.PrimitiveKind);
Assert.Equal(BoundBinaryOperatorType.Addition, binaryOp.Operator.Type);
Assert.IsType<BoundIdentifierName>(binaryOp.Left);
Assert.IsType<BoundIdentifierName>(binaryOp.Right);

var left = (BoundIdentifierName)binaryOp.Left;
var right = (BoundIdentifierName)binaryOp.Right;
Assert.Equal("x", left.Symbol.Name.Text);
Assert.Equal("y", right.Symbol.Name.Text);
Assert.IsType<PrimitiveType>(left.Type);
Assert.IsType<PrimitiveType>(right.Type);
}

[Theory]
[InlineData("fn abc: int { return 123; }")]
[InlineData("fn abc: int -> 123;")]
[InlineData("fn abc { return 123; }")]
[InlineData("fn abc -> 123;")]
public void Binds_FunctionDeclarations(string input)
{
var boundTree = Bind(input);
var statement = boundTree.Statements.First();
Assert.IsType<BoundFunctionDeclaration>(statement);

var functionDeclaration = (BoundFunctionDeclaration)statement;
Assert.Empty(functionDeclaration.Parameters);
Assert.Empty(functionDeclaration.Type.ParameterTypes);
Assert.Equal("abc", functionDeclaration.Symbol.Name.Text);
Assert.IsType<PrimitiveType>(functionDeclaration.Type.ReturnType);

var returnType = (PrimitiveType)functionDeclaration.Type.ReturnType;
Assert.Equal(PrimitiveTypeKind.Int, returnType.PrimitiveKind);
Assert.Single(functionDeclaration.Body.Statements);
Assert.IsType<BoundReturn>(functionDeclaration.Body.Statements.First());

var returnStatement = (BoundReturn)functionDeclaration.Body.Statements.First();
Assert.IsType<BoundLiteral>(returnStatement.Expression);

var literal = (BoundLiteral)returnStatement.Expression;
Assert.Equal(123L, literal.Token.Value);
}

[Theory]
[InlineData("fn abc(x = 0, y = 0) {} abc();", 0)]
[InlineData("fn abc(x = 0, y = 0) {} abc(69);", 1)]
[InlineData("fn abc(x = 0, y = 0) {} abc(69, 420);", 2)]
public void Binds_Invocation(string input, int expectedArgumentCount)
{
var tree = Bind(input);
var statement = tree.Statements.Last();
Assert.IsType<BoundExpressionStatement>(statement);

var expressionStatement = (BoundExpressionStatement)statement;
Assert.IsType<BoundInvocation>(expressionStatement.Expression);

var invocation = (BoundInvocation)expressionStatement.Expression;
Assert.IsType<BoundIdentifierName>(invocation.Callee);

var calleeName = (BoundIdentifierName)invocation.Callee;
Assert.Equal("abc", calleeName.Symbol.Name.Text);
Assert.IsType<FunctionType>(calleeName.Type);

var functionType = (FunctionType)calleeName.Type;
Assert.Equal(2, functionType.ParameterTypes.Count);
Assert.IsType<PrimitiveType>(functionType.ReturnType);

var returnType = (PrimitiveType)functionType.ReturnType;
Assert.Equal(PrimitiveTypeKind.None, returnType.PrimitiveKind);

Assert.Equal(expectedArgumentCount, invocation.Arguments.Count);
foreach (var argument in invocation.Arguments)
Assert.IsType<BoundLiteral>(argument);
}

[Fact]
public void Binds_ReturnStatements()
{
Expand All @@ -44,7 +166,7 @@ public void Binds_Identifiers()
Assert.IsType<BoundIdentifierName>(node);

var identifier = (BoundIdentifierName)node;
Assert.Equal("x", identifier.Token.Text);
Assert.Equal("x", identifier.Symbol.Name.Text);
Assert.IsType<PrimitiveType>(identifier.Type);

var type = (PrimitiveType)identifier.Type;
Expand Down
104 changes: 72 additions & 32 deletions Heir.Tests/BytecodeGeneratorTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Heir.CodeGeneration;
using Heir.Runtime.Values;
using static Heir.Tests.Common;

namespace Heir.Tests;
Expand Down Expand Up @@ -78,9 +79,9 @@ public void GeneratesPush_ForLiterals()
public void Generates_BinaryOperations(string input, object? leftValue, object? rightValue, OpCode opCode)
{
var bytecode = GenerateBytecode(input);
var pushLeft = bytecode.Instructions[0];
var pushRight = bytecode.Instructions[1];
var operation = bytecode.Instructions[2];
var pushLeft = bytecode[0];
var pushRight = bytecode[1];
var operation = bytecode[2];
Assert.Equal(OpCode.PUSH, pushLeft.OpCode);
Assert.Equal(leftValue, pushLeft.Operand);
Assert.Equal(OpCode.PUSH, pushRight.OpCode);
Expand All @@ -96,10 +97,10 @@ public void Generates_BinaryOperations(string input, object? leftValue, object?
public void Generates_InvertedBinaryOperations(string input, object? leftValue, object? rightValue, OpCode opCode)
{
var bytecode = GenerateBytecode(input);
var pushLeft = bytecode.Instructions[0];
var pushRight = bytecode.Instructions[1];
var operation = bytecode.Instructions[2];
var inversion = bytecode.Instructions[3];
var pushLeft = bytecode[0];
var pushRight = bytecode[1];
var operation = bytecode[2];
var inversion = bytecode[3];
Assert.Equal(OpCode.PUSH, pushLeft.OpCode);
Assert.Equal(leftValue, pushLeft.Operand);
Assert.Equal(OpCode.PUSH, pushRight.OpCode);
Expand All @@ -114,15 +115,15 @@ public void Generates_InvertedBinaryOperations(string input, object? leftValue,
public void Generates_Assignment()
{
var bytecode = GenerateBytecode("let mut a = 1; a = 2;").Skip(3);
var pushIdentifier = bytecode.Instructions[0];
var pushRight = bytecode.Instructions[1];
var store = bytecode.Instructions[2];
var pushIdentifier = bytecode[0];
var pushRight = bytecode[1];
var store = bytecode[2];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal("a", pushIdentifier.Operand);
Assert.Equal(OpCode.PUSH, pushRight.OpCode);
Assert.Equal(2L, pushRight.Operand);
Assert.Equal(OpCode.STORE, store.OpCode);
Assert.Null(store.Operand);
Assert.True(store.Operand as bool?);
}

[Theory]
Expand All @@ -131,12 +132,12 @@ public void Generates_Assignment()
public void Generates_BinaryCompoundAssignment(string input, object? right, OpCode opCode)
{
var bytecode = GenerateBytecode(input).Skip(3);
var pushIdentifier = bytecode.Instructions[0];
var pushIdentifierAgain = bytecode.Instructions[1];
var load = bytecode.Instructions[2];
var pushRight = bytecode.Instructions[3];
var operation = bytecode.Instructions[4];
var store = bytecode.Instructions[5];
var pushIdentifier = bytecode[0];
var pushIdentifierAgain = bytecode[1];
var load = bytecode[2];
var pushRight = bytecode[3];
var operation = bytecode[4];
var store = bytecode[5];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal("a", pushIdentifier.Operand);
Assert.Equal(OpCode.PUSH, pushIdentifierAgain.OpCode);
Expand All @@ -148,7 +149,7 @@ public void Generates_BinaryCompoundAssignment(string input, object? right, OpCo
Assert.Equal(opCode, operation.OpCode);
Assert.Null(operation.Operand);
Assert.Equal(OpCode.STORE, store.OpCode);
Assert.Null(store.Operand);
Assert.True(store.Operand as bool?);
}

[Theory]
Expand All @@ -158,8 +159,8 @@ public void Generates_BinaryCompoundAssignment(string input, object? right, OpCo
public void Generates_UnaryOperations(string input, object? operandValue, OpCode opCode)
{
var bytecode = GenerateBytecode(input);
var push = bytecode.Instructions[0];
var operation = bytecode.Instructions[1];
var push = bytecode[0];
var operation = bytecode[1];
Assert.Equal(OpCode.PUSH, push.OpCode);
Assert.Equal(operandValue, push.Operand);
Assert.Equal(opCode, operation.OpCode);
Expand All @@ -172,12 +173,12 @@ public void Generates_UnaryOperations(string input, object? operandValue, OpCode
public void Generates_UnaryCompoundAssignment(string input, OpCode opCode)
{
var bytecode = GenerateBytecode(input).Skip(3);
var pushIdentifier = bytecode.Instructions[0];
var pushIdentifierAgain = bytecode.Instructions[1];
var load = bytecode.Instructions[2];
var pushOne = bytecode.Instructions[3];
var operation = bytecode.Instructions[4];
var store = bytecode.Instructions[5];
var pushIdentifier = bytecode[0];
var pushIdentifierAgain = bytecode[1];
var load = bytecode[2];
var pushOne = bytecode[3];
var operation = bytecode[4];
var store = bytecode[5];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal("a", pushIdentifier.Operand);
Assert.Equal(OpCode.PUSH, pushIdentifierAgain.OpCode);
Expand All @@ -189,7 +190,7 @@ public void Generates_UnaryCompoundAssignment(string input, OpCode opCode)
Assert.Equal(opCode, operation.OpCode);
Assert.Null(operation.Operand);
Assert.Equal(OpCode.STORE, store.OpCode);
Assert.Null(store.Operand);
Assert.True(store.Operand as bool?);
}

[Theory]
Expand All @@ -198,15 +199,54 @@ public void Generates_UnaryCompoundAssignment(string input, OpCode opCode)
public void Generates_VariableDeclarations(string input, string name, object? value, OpCode opCode)
{
var bytecode = GenerateBytecode(input);
var pushIdentifier = bytecode.Instructions[0];
var pushValue = bytecode.Instructions[1];
var operation = bytecode.Instructions[2];
var pushIdentifier = bytecode[0];
var pushValue = bytecode[1];
var operation = bytecode[2];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal(name, pushIdentifier.Operand);
Assert.Equal(OpCode.PUSH, pushValue.OpCode);
Assert.Equal(value, pushValue.Operand);
Assert.Equal(opCode, operation.OpCode);
Assert.IsType<bool>(operation.Operand);
Assert.False((bool)operation.Operand);
Assert.False(operation.Operand as bool?);
}

[Theory]
[InlineData("fn abc -> 420;")]
[InlineData("fn abc(x: int): int -> 123 + x;")]
public void Generates_FunctionDeclarations(string input)
{
var bytecode = GenerateBytecode(input);
var pushIdentifier = bytecode[0];
var pushValue = bytecode[1];
var operation = bytecode[2];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal("abc", pushIdentifier.Operand);
Assert.Equal(OpCode.PUSH, pushValue.OpCode);
Assert.IsType<Function>(pushValue.Operand);
Assert.Equal(OpCode.STORE, operation.OpCode);
Assert.False(operation.Operand as bool?);
}

[Fact]
public void Generates_Invocation()
{
var bytecode = GenerateBytecode("fn abc(x: int): int -> 123 + x; abc(69);").Skip(3);
var pushIdentifier = bytecode[0];
var load = bytecode[1];
var call = bytecode[2];
Assert.Equal(OpCode.PUSH, pushIdentifier.OpCode);
Assert.Equal("abc", pushIdentifier.Operand);
Assert.Equal(OpCode.LOAD, load.OpCode);
Assert.Null(load.Operand);
Assert.Equal(OpCode.CALL, call.OpCode);
Assert.IsType<List<List<Instruction>>>(call.Operand);

var argumentsBytecode = (List<List<Instruction>>)call.Operand;
Assert.Single(argumentsBytecode);

var argumentBytecode = argumentsBytecode.First();
var pushValue = argumentBytecode[0];
Assert.Equal(OpCode.PUSH, pushValue.OpCode);
Assert.Equal(69L, pushValue.Operand);
}
}
Loading

0 comments on commit 1520bee

Please sign in to comment.