Skip to content

Commit

Permalink
Implement support for mutable variables
Browse files Browse the repository at this point in the history
  • Loading branch information
coetaur0 committed Jul 15, 2024
1 parent b66208a commit 043b5ef
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 14 deletions.
6 changes: 6 additions & 0 deletions Kaleidoscope.Tests/InterpreterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ public void InterpretCode()
{
var interpreter = new Interpreter();
interpreter.Run("def fact(n) if n < 2 then n else n * fact(n - 1)");
interpreter.Run("def binary : 1 (x, y) y");
interpreter.Run(
"def fib(x) var a = 1 in var b = 1 in var c = 0 in (for i = 3, i < x in c = a + b : a = b : b = c) : b");

var (_, result) = interpreter.Run("fact(5)");
Assert.Equal(120.0, result);

(_, result) = interpreter.Run("fib(8)");
Assert.Equal(21.0, result);

interpreter.Run("def binary$ 30 (x, y) x * y");
(_, result) = interpreter.Run("3 + 2 $ 4");
Assert.Equal(11.0, result);
Expand Down
7 changes: 7 additions & 0 deletions Kaleidoscope.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ public void ParseForExpr()
Assert.Equal("Syntax error at 1:21..1:26: expect the 'in' keyword.", exception.Message);
}

[Fact]
public void ParseVarInExpr()
{
var expr = _parser.ParseItem("var x = 0 in x = 3");
Assert.Equal("def __anon_expr() var x = 0 in (x = 3)", expr.ToString());
}

[Fact]
public void ParseBinaryExpr()
{
Expand Down
63 changes: 51 additions & 12 deletions Kaleidoscope/Codegen/LLVMCodegen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ public LLVMValueRef Visit(Function item)
for (var i = 0; i < item.Prototype.Params.Count; i++)
{
var param = function.GetParam((uint)i);
_variables[item.Prototype.Params[i]] = param;
var paramName = item.Prototype.Params[i];
param.Name = paramName;
var alloca = _builder.BuildAlloca(LLVMTypeRef.Double, paramName);
_builder.BuildStore(param, alloca);
_variables[paramName] = alloca;
}

var returnValue = item.Body.Accept(this);
Expand Down Expand Up @@ -122,31 +126,29 @@ public LLVMValueRef Visit(IfExpr expr)

public LLVMValueRef Visit(ForExpr expr)
{
var function = _builder.InsertBlock.Parent;
var alloca = _builder.BuildAlloca(LLVMTypeRef.Double, expr.VarName);
var start = expr.Start.Accept(this);
var preHeader = _builder.InsertBlock;
var function = preHeader.Parent;
_builder.BuildStore(start, alloca);

var loopBlock = function.AppendBasicBlock("loop");
_builder.BuildBr(loopBlock);

_builder.PositionAtEnd(loopBlock);
var variable = _builder.BuildPhi(LLVMTypeRef.Double, expr.VarName);
variable.AddIncoming([start], [preHeader], 1u);
_variables.TryGetValue(expr.VarName, out var oldValue);
_variables[expr.VarName] = variable;
_variables[expr.VarName] = alloca;
expr.Body.Accept(this);

var step = expr.Step.Accept(this);
var nextVar = _builder.BuildFAdd(variable, step, "nextvar");
var endCond = expr.End.Accept(this);
var currentVar = _builder.BuildLoad2(LLVMTypeRef.Double, alloca, expr.VarName);
var nextVar = _builder.BuildFAdd(currentVar, step, "nextvar");
_builder.BuildStore(nextVar, alloca);
var zero = LLVMValueRef.CreateConstReal(LLVMTypeRef.Double, 0);
endCond = _builder.BuildFCmp(LLVMRealPredicate.LLVMRealONE, endCond, zero, "loopcond");

var loopEnd = _builder.InsertBlock;
var afterBlock = function.AppendBasicBlock("afterloop");
_builder.BuildCondBr(endCond, loopBlock, afterBlock);
_builder.PositionAtEnd(afterBlock);
variable.AddIncoming([nextVar], [loopEnd], 1u);

if (oldValue.Handle != IntPtr.Zero)
{
_variables[expr.VarName] = oldValue;
Expand All @@ -159,8 +161,45 @@ public LLVMValueRef Visit(ForExpr expr)
return zero;
}

public LLVMValueRef Visit(VarInExpr expr)
{
var initVal = expr.Value.Accept(this);
var alloca = _builder.BuildAlloca(LLVMTypeRef.Double, expr.Name);
_builder.BuildStore(initVal, alloca);
_variables.TryGetValue(expr.Name, out var oldValue);
_variables[expr.Name] = alloca;
var body = expr.Body.Accept(this);
if (oldValue.Handle != IntPtr.Zero)
{
_variables[expr.Name] = oldValue;
}
else
{
_variables.Remove(expr.Name);
}

return body;
}

public LLVMValueRef Visit(BinaryExpr expr)
{
if (expr.Op == "=")
{
if (expr.Lhs is not VariableExpr target)
{
throw Exception("destination of '=' must be a variable", expr.Lhs.Range);
}

var value = expr.Rhs.Accept(this);
if (!_variables.TryGetValue(target.Name, out var variable))
{
throw Exception($"unknown variable {target.Name}", expr.Range);
}

_builder.BuildStore(value, variable);
return value;
}

var lhsValue = expr.Lhs.Accept(this);
var rhsValue = expr.Rhs.Accept(this);

Expand Down Expand Up @@ -234,7 +273,7 @@ public LLVMValueRef Visit(VariableExpr expr)
throw Exception($"unknown variable {expr.Name}", expr.Range);
}

return value;
return _builder.BuildLoad2(LLVMTypeRef.Double, value, expr.Name);
}

private static CodegenException Exception(string message, Syntax.Range range)
Expand Down
1 change: 1 addition & 0 deletions Kaleidoscope/Parser/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public void Reset()
"else" => TokenKind.Else,
"for" => TokenKind.For,
"in" => TokenKind.In,
"var" => TokenKind.Var,
"binary" => TokenKind.Binary,
"unary" => TokenKind.Unary,
_ => TokenKind.Identifier
Expand Down
19 changes: 17 additions & 2 deletions Kaleidoscope/Parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public sealed class Parser
{
private readonly Dictionary<string, int> _precedence = new()
{
["="] = 2,
["<"] = 10,
["+"] = 20,
["-"] = 20,
Expand Down Expand Up @@ -215,7 +216,7 @@ private IExpr ParsePrimary()

case TokenKind.For:
var forStart = Advance().Range.Start;
var varName = _source[Consume(TokenKind.Identifier, "expect a variable name").Range];
var forVarName = _source[Consume(TokenKind.Identifier, "expect a variable name").Range];
if (_nextToken.Kind != TokenKind.Op && _source[_nextToken.Range] != "=")
{
throw Exception("expect a '='", _nextToken.Range);
Expand All @@ -233,9 +234,23 @@ private IExpr ParsePrimary()
step = ParseExpr();
}

Consume(TokenKind.In, "expect the 'in' keyword");
var forBody = ParseExpr();
return new ForExpr(forVarName, start, end, step, forBody, forBody.Range with { Start = forStart });

case TokenKind.Var:
var varStart = Advance().Range.Start;
var varName = _source[Consume(TokenKind.Identifier, "expect a variable name").Range];
if (_nextToken.Kind != TokenKind.Op && _source[_nextToken.Range] != "=")
{
throw Exception("expect a '='", _nextToken.Range);
}

Advance();
var value = ParseExpr();
Consume(TokenKind.In, "expect the 'in' keyword");
var body = ParseExpr();
return new ForExpr(varName, start, end, step, body, body.Range with { Start = forStart });
return new VarInExpr(varName, value, body, body.Range with { Start = varStart });

case TokenKind.Identifier:
return ParseIdentifier();
Expand Down
1 change: 1 addition & 0 deletions Kaleidoscope/Parser/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal enum TokenKind
Else,
For,
In,
Var,
Binary,
Unary,
Identifier,
Expand Down
5 changes: 5 additions & 0 deletions Kaleidoscope/Syntax/IExprVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public interface IExprVisitor<out T>
/// </summary>
T Visit(ForExpr expr);

/// <summary>
/// Visits a variable definition expression node.
/// </summary>
T Visit(VarInExpr expr);

/// <summary>
/// Visits a binary expression node.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions Kaleidoscope/Syntax/VarInExpr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Kaleidoscope.Syntax;

/// <summary>
/// A variable definition expression.
/// </summary>
/// <param name="Name">The name of the variable being defined.</param>
/// <param name="Value">The initial value of the variable.</param>
/// <param name="Body">The expression in which the variable is defined.</param>
/// <param name="Range">The expression's source range.</param>
public sealed record VarInExpr(string Name, IExpr Value, IExpr Body, Range Range) : IExpr
{
public T Accept<T>(IExprVisitor<T> visitor)
{
return visitor.Visit(this);
}

public override string ToString() => $"var {Name} = {Value} in {Body}";
}

0 comments on commit 043b5ef

Please sign in to comment.