Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed Examples/bug.lumi
Empty file.
30 changes: 30 additions & 0 deletions Examples/debug.lumi
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
struct Point {
x: int;
y: int;

fn move(dx, dy) {
this.x = this.x + dx;
this.y = this.y + dy;
}
}

let points: list -> [];

for i in 0 to 499 step 1 {
points.add(new Point(x: i, y: i));
}

for pass in 0 to 9 step 1 {
for i in 0 to points.length() - 1 step 1 {
let p: Point -> points[i];
p.move(1, 1);
}
}

let checksum -> 0;
for i in 0 to points.length() - 1 step 1 {
let p: Point -> points[i];
checksum = checksum + p.x + p.y;
}

print checksum;
6 changes: 6 additions & 0 deletions Examples/heap/heap1.lumi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
let a: list -> [1,2,3];
let b: list;
b = a;
b.add(4); // pointer to a so a is also modified

print a;
12 changes: 12 additions & 0 deletions Examples/heap/heap2.lumi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
let a -> [1, 2, 3];

struct Container {
items: list;

fn printItems() {
print this.items;
}
}

let c -> new Container(items: a);
c.printItems();
30 changes: 30 additions & 0 deletions Examples/heap/heap3.lumi
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
struct Point {
x: int;
y: int;

fn move(dx, dy) {
this.x = this.x + dx;
this.y = this.y + dy;
}
}

let points: list -> [];

for i in 0 to 499 step 1 {
points.add(new Point(x: i, y: i));
}

for pass in 0 to 9 step 1 {
for i in 0 to points.length() - 1 step 1 {
let p: Point -> points[i];
p.move(1, 1);
}
}

let checksum -> 0;
for i in 0 to points.length() - 1 step 1 {
let p: Point -> points[i];
checksum = checksum + p.x + p.y;
}

print checksum;
41 changes: 41 additions & 0 deletions Examples/heap/heap4.lumi
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
struct Bucket {
items: list;
sum: int;

fn addValue(value) {
this.items.add(value);
this.sum = this.sum + value;
}
}

let buckets: list -> [];

for i in 0 to 199 step 1 {
let items: list -> [];
let bucket: Bucket -> new Bucket(items: items, sum: 0);
buckets.add(bucket);
}

for i in 0 to buckets.length() - 1 step 1 {
let bucket: Bucket -> buckets[i];
let alias: list -> bucket.items;

for j in 0 to 49 step 1 {
bucket.addValue(i + j);
}

for j in 0 to 24 step 1 {
alias.add(j);
}
}

let total -> 0;
for i in 0 to buckets.length() - 1 step 1 {
let bucket: Bucket -> buckets[i];
total = total + bucket.sum;
total = total + bucket.items.length();
total = total + bucket.items[0];
total = total + bucket.items[bucket.items.length() - 1];
}

print total;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let text: str = "Hello world!";
let text: str -> "Hello world!";

File.writeText("aoc-sample.txt", text);

Expand Down
File renamed without changes.
17 changes: 11 additions & 6 deletions Lumi.Bytecode.Tests/BytecodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ public void Test_Binary_Expression()
var gen = new BytecodeGenerator();
var result = gen.Generate(program);

// Expect: PushConst 1, PushConst 2, Add
Assert.HasCount(3, result.Instructions, "Instruction count mismatch");
// Expect: PushConst 1, PushConst 2, Add, Pop
Assert.HasCount(4, result.Instructions, "Instruction count mismatch");
Assert.HasCount(2, result.Constants, "Constants count mismatch");

Assert.AreEqual(InstructionKind.PushConst, result.Instructions[0].Kind);
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[1].Kind);
Assert.AreEqual(InstructionKind.Add, result.Instructions[2].Kind);
Assert.AreEqual(InstructionKind.Pop, result.Instructions[3].Kind);

Assert.AreEqual(ConstantKind.Number, result.Constants[0].Kind);
Assert.AreEqual(1.0, result.Constants[0].Number);
Expand Down Expand Up @@ -478,9 +479,10 @@ public void Test_String_Constant()
var gen = new BytecodeGenerator();
var result = gen.Generate(program);

Assert.HasCount(1, result.Instructions);
Assert.HasCount(2, result.Instructions);
Assert.HasCount(1, result.Constants);
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[0].Kind);
Assert.AreEqual(InstructionKind.Pop, result.Instructions[1].Kind);
Assert.AreEqual(ConstantKind.String, result.Constants[0].Kind);
Assert.AreEqual("hello", result.Constants[0].String);
}
Expand Down Expand Up @@ -522,13 +524,14 @@ public void Test_Mixed_Local_Kinds_And_Shadowing()
var gen = new BytecodeGenerator();
var result = gen.Generate(program);

// Expect sequence: PushConst(1), StoreVar(outer x), PushConst(2), StoreVar(inner x), LoadVar(inner x)
Assert.IsGreaterThanOrEqualTo(5, result.Instructions.Count);
// Expect sequence: PushConst(1), StoreVar(outer x), PushConst(2), StoreVar(inner x), LoadVar(inner x), Pop
Assert.IsGreaterThanOrEqualTo(6, result.Instructions.Count);
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[0].Kind);
Assert.AreEqual(InstructionKind.StoreVar, result.Instructions[1].Kind);
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[2].Kind);
Assert.AreEqual(InstructionKind.StoreVar, result.Instructions[3].Kind);
Assert.AreEqual(InstructionKind.LoadVar, result.Instructions[4].Kind);
Assert.AreEqual(InstructionKind.Pop, result.Instructions[5].Kind);

// The LoadVar should point to the inner variable (label id 1)
var loadOp = result.Instructions[4].GetSafeIntOperand();
Expand Down Expand Up @@ -1002,6 +1005,7 @@ public void Test_ArrayIndex_EmitsCorrectInstructions()
Assert.AreEqual(InstructionKind.LoadVar, result.Instructions[4].Kind);
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[5].Kind);
Assert.AreEqual(InstructionKind.IndexArray, result.Instructions[6].Kind);
Assert.AreEqual(InstructionKind.Pop, result.Instructions[7].Kind);
}

[TestMethod]
Expand Down Expand Up @@ -1034,11 +1038,12 @@ public void Test_ArrayIndex_InlineArrayLiteral_EmitsCorrectInstructions()
Body = [new ExpressionStatement { Expression = indexExpr }]
});

Assert.HasCount(6, result.Instructions);
Assert.HasCount(7, result.Instructions);
Assert.AreEqual(InstructionKind.MakeArray, result.Instructions[3].Kind);
Assert.AreEqual(3, result.Instructions[3].GetSafeIntOperand());
Assert.AreEqual(InstructionKind.PushConst, result.Instructions[4].Kind);
Assert.AreEqual(InstructionKind.IndexArray, result.Instructions[5].Kind);
Assert.AreEqual(InstructionKind.Pop, result.Instructions[6].Kind);
}

[TestMethod]
Expand Down
8 changes: 8 additions & 0 deletions Lumi.Bytecode/BytecodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ private void Visit(Node node)

case ExpressionStatement expressionStatement:
Visit(expressionStatement.Expression);

if (ExpressionLeavesValue(expressionStatement.Expression))
{
Emit(Instruction.Pop());
}

break;

case UnaryExpression unaryExpression:
Expand Down Expand Up @@ -513,6 +519,8 @@ private void VisitCallExpression(CallExpression callExpression)
Emit(Instruction.CallMemberMethod(methodIdentifier.Name, callExpression.Arguments.Count));
}

private static bool ExpressionLeavesValue(Node expression) => expression is not AssignmentExpression;

private void VisitStructDeclaration(StructDeclaration structDeclaration)
{
var structName = structDeclaration.Identifier.Name;
Expand Down
1 change: 1 addition & 0 deletions Lumi.Bytecode/Instructions/Instruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public override string ToString()
public static Instruction StoreVar(Label label) => new(InstructionKind.StoreVar, label.Id);
public static Instruction LoadVar(Label label) => new(InstructionKind.LoadVar, label.Id);
public static Instruction PushConst(int constIndex) => new(InstructionKind.PushConst, constIndex);
public static Instruction Pop() => new(InstructionKind.Pop);
public static Instruction Add() => new(InstructionKind.Add);
public static Instruction JumpIfFalse(int operand) => new(InstructionKind.JumpIfFalse, operand);
public static Instruction Jump(int operand) => new(InstructionKind.Jump, operand);
Expand Down
16 changes: 16 additions & 0 deletions Lumi.Engine.Tests/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ public void Test_List_Contains_Method_Returns_False_When_Absent()
Assert.AreEqual("False", output.Trim());
}

[TestMethod]
public void Test_List_Add_Method_In_Loop_Does_Not_Overflow_Stack()
{
var source = """
let items: list -> [];
for i in 0 to 1499 step 1 {
items.add(i);
}
print items.length();
""";

var output = ExecuteAndCapture(source);

Assert.AreEqual("1500", output.Trim());
}

private static string ExecuteAndCapture(string source)
{
var originalOut = Console.Out;
Expand Down
44 changes: 44 additions & 0 deletions Lumi.Engine.Tests/BenchmarkSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,48 @@ fn move(dx, dy) {
let content -> File.readLines("point.txt");
// print content;
""";

public const string HeapReferenceWorkloadSource = """
struct Bucket {
items: list;
sum: int;

fn addValue(value) {
this.items.add(value);
this.sum = this.sum + value;
}
}

let buckets: list -> [];

for i in 0 to 199 step 1 {
let items: list -> [];
let bucket: Bucket -> new Bucket(items: items, sum: 0);
buckets.add(bucket);
}

for i in 0 to buckets.length() - 1 step 1 {
let bucket: Bucket -> buckets[i];
let alias: list -> bucket.items;

for j in 0 to 49 step 1 {
bucket.addValue(i + j);
}

for j in 0 to 24 step 1 {
alias.add(j);
}
}

let total -> 0;
for i in 0 to buckets.length() - 1 step 1 {
let bucket: Bucket -> buckets[i];
total = total + bucket.sum;
total = total + bucket.items.length();
total = total + bucket.items[0];
total = total + bucket.items[bucket.items.length() - 1];
}

print total;
""";
}
12 changes: 12 additions & 0 deletions Lumi.Engine.Tests/BenchmarkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ public void Test_Mixed_Workload()
CollectBenchmarkData(iterations, source);
}

[TestMethod]
public void Test_Heap_Reference_Workload()
{
// Arrange
var source = SourceStrings.HeapReferenceWorkloadSource;

// Act + Benchmark
const int iterations = 1000;
Warmup(source);
CollectBenchmarkData(iterations, source);
}

[TestMethod]
public void Test_Simple_Binary_Operation()
{
Expand Down
67 changes: 67 additions & 0 deletions Lumi.Engine.Tests/ExecutionPipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,73 @@ public void TryExecute_FileReadFailure_UsesVirtualMachineError()
Assert.IsFalse(output.Contains("System.IO.FileNotFoundException", StringComparison.Ordinal));
}

[TestMethod]
public void TryExecute_FileReadLines_Returns_HeapBacked_List()
{
var tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.txt");
var escapedPath = EscapeForLumi(tempPath);

File.WriteAllLines(tempPath, ["alpha", "beta"]);

var source = $$"""
let lines: list -> File.readLines("{{escapedPath}}");
print lines.length();
print lines[0];
print lines[1];
""";

using var writer = new StringWriter();

try
{
var pipeline = CreatePipeline();
var succeeded = pipeline.TryExecute(source, new VirtualMachine(), new BytecodeGenerator(), new SemanticAnalyzerType(), writer);
var output = writer.ToString();
var lines = SplitLines(output);

Assert.IsTrue(succeeded);
Assert.HasCount(3, lines);
Assert.AreEqual("2", lines[0]);
Assert.AreEqual("\"alpha\"", lines[1]);
Assert.AreEqual("\"beta\"", lines[2]);
}
finally
{
if (File.Exists(tempPath))
File.Delete(tempPath);
}
}

[TestMethod]
public void TryExecute_FileWriteLines_Accepts_HeapBacked_List()
{
var tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.txt");
var escapedPath = EscapeForLumi(tempPath);

var source = $$"""
let lines: list -> ["alpha", "beta"];
File.writeLines("{{escapedPath}}", lines);
""";

using var writer = new StringWriter();

try
{
var pipeline = CreatePipeline();
var succeeded = pipeline.TryExecute(source, new VirtualMachine(), new BytecodeGenerator(), new SemanticAnalyzerType(), writer);
var output = writer.ToString();

Assert.IsTrue(succeeded);
Assert.AreEqual(string.Empty, output);
CollectionAssert.AreEqual(new[] { "alpha", "beta" }, File.ReadAllLines(tempPath));
}
finally
{
if (File.Exists(tempPath))
File.Delete(tempPath);
}
}

private static ExecutionPipeline CreatePipeline()
{
var services = new ServiceCollection();
Expand Down
Loading
Loading