Add list statement support with tests for creation, access, and errors

This commit is contained in:
Stone_Red
2026-03-04 15:47:54 +01:00
parent 344e9e644e
commit e99df1e3f2
6 changed files with 455 additions and 3 deletions
+11
View File
@@ -47,6 +47,17 @@ var i = ${i} - 1 calc
end_while
```
## Lists
```ynt
list items new
list items add apple
list items add banana
list items get 1
var value = %out
print_line ${value}
```
## Full v1 -> v2 Mapping
| Area | v1 Syntax | v2 Syntax | Notes |
@@ -0,0 +1,161 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class ListStatementsTests
{
[TestMethod]
public void ListCreateAddGetTest()
{
List<string> lines =
[
"list items new",
"list items add a",
"list items add b",
"list items get 1",
"var result = %out",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "b");
}
[TestMethod]
public void ListSetAndInsertTest()
{
List<string> lines =
[
"list items new",
"list items add a",
"list items add c",
"list items insert 1 b",
"list items set 2 d",
"list items get 2",
"var result = %out",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "d");
}
[TestMethod]
public void ListRemoveAndLengthTest()
{
List<string> lines =
[
"list items new",
"list items add a",
"list items add b",
"list items add c",
"list items remove 1",
"list items length",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "2");
}
[TestMethod]
public void ListClearTest()
{
List<string> lines =
[
"list items new",
"list items add a",
"list items clear",
"list items length",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void ListDeleteTest()
{
List<string> lines =
[
"list items new",
"list items delete",
"list items length"
];
YesNtAssert.ContainsTerminationMessage(lines, "List \"items\" not found");
}
[TestMethod]
public void ListMissingFailsTest()
{
List<string> lines =
[
"list missing get 0"
];
YesNtAssert.ContainsTerminationMessage(lines, "List \"missing\" not found");
}
[TestMethod]
public void ListInvalidIndexFailsTest()
{
List<string> lines =
[
"list items new",
"list items add a",
"list items get 3"
];
YesNtAssert.ContainsTerminationMessage(lines, "Index 3 out of range");
}
[TestMethod]
public void ListInvalidSyntaxFailsTest()
{
List<string> lines =
[
"list items add"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void ListScopeInsideFunctionTest()
{
List<string> lines =
[
"goto main",
"func make:",
"list items new",
"list items add x",
"list items get 0",
"push_out %out",
"return",
"label main:",
"call make",
"var result = %out",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "x");
}
[TestMethod]
public void ListAddWithSpacesTest()
{
List<string> lines =
[
"list items new",
"list items add hello~spcworld",
"list items get 0",
"var result = %out eval",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "hello world");
}
}
@@ -132,7 +132,9 @@ public class ProcessingStatementsTests
[
"global result = 0",
"global result = 1 task",
"sleep 50",
"while ${result} == 0:",
"sleep 10",
"end_while",
"${result}"
];
+2 -1
View File
@@ -6,7 +6,8 @@ internal class FunctionScope(int callerLine, Stack<string> arguments)
{
public int CallerLine { get; } = callerLine;
public Dictionary<string, string> Variables { get; } = [];
public Dictionary<string, List<string>> Lists { get; } = [];
public Dictionary<string, int> Labels { get; } = [];
public Stack<string> Arguments { get; } = arguments;
public Stack<string> Results { get; } = new();
}
}
@@ -15,6 +15,7 @@ internal sealed class RuntimeInformation
private static int internalTaskId = 0;
private readonly Dictionary<string, string> topVariables = [];
private readonly Dictionary<string, List<string>> topLists = [];
private readonly Dictionary<string, int> topLabels = [];
private RuntimeInformation parentRuntimeInformation;
private int taskId = 0;
@@ -43,6 +44,7 @@ internal sealed class RuntimeInformation
}
public Dictionary<string, string> Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables;
public Dictionary<string, List<string>> Lists => FunctionCallStack.Count == 0 ? topLists : FunctionCallStack.Peek().Lists;
public Dictionary<string, int> Labels => FunctionCallStack.Count == 0 ? topLabels : FunctionCallStack.Peek().Labels;
@@ -148,6 +150,7 @@ internal sealed class RuntimeInformation
public void Reset()
{
topVariables.Clear();
topLists.Clear();
Lines.Clear();
GlobalVariables.Clear();
Labels.Clear();
@@ -176,4 +179,4 @@ internal sealed class RuntimeInformation
{
Exit($"Terminated by parent task", stopAllTasks);
}
}
}
@@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using YesNt.Interpreter.Attributes;
using YesNt.Interpreter.Enums;
using YesNt.Interpreter.Runtime;
namespace YesNt.Interpreter.Statements;
internal class ListStatements : StatementRuntimeInformation
{
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " new")]
public void Create(string args)
{
string[] parts = SplitTwo(args, " new");
if (parts is null)
{
return;
}
string name = parts[0];
if (!RuntimeInfo.Lists.ContainsKey(name))
{
RuntimeInfo.Lists.Add(name, []);
}
else
{
RuntimeInfo.Lists[name].Clear();
}
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " delete")]
public void Delete(string args)
{
string[] parts = SplitTwo(args, " delete");
if (parts is null)
{
return;
}
string name = parts[0];
if (!RuntimeInfo.Lists.ContainsKey(name))
{
RuntimeInfo.Exit($"List \"{name}\" not found", true);
return;
}
_ = RuntimeInfo.Lists.Remove(name);
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " clear")]
public void Clear(string args)
{
string[] parts = SplitTwo(args, " clear");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
list.Clear();
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " length")]
public void Length(string args)
{
string[] parts = SplitTwo(args, " length");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
RuntimeInfo.OutParametersStack.Clear();
RuntimeInfo.OutParametersStack.Push(list.Count.ToString());
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " add ")]
public void Add(string args)
{
string[] parts = SplitTwo(args, " add ");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
if (string.IsNullOrWhiteSpace(parts[1]))
{
RuntimeInfo.Exit("Invalid syntax", true);
return;
}
list.Add(parts[1].Trim());
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " get ")]
public void Get(string args)
{
string[] parts = SplitTwo(args, " get ");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
if (!TryParseIndex(parts[1], out int index, list.Count))
{
return;
}
RuntimeInfo.OutParametersStack.Clear();
RuntimeInfo.OutParametersStack.Push(list[index]);
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " remove ")]
public void Remove(string args)
{
string[] parts = SplitTwo(args, " remove ");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
if (!TryParseIndex(parts[1], out int index, list.Count))
{
return;
}
list.RemoveAt(index);
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " set ")]
public void Set(string args)
{
string[] parts = SplitTwo(args, " set ");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
string[] indexAndValue = SplitIndexAndValue(parts[1]);
if (indexAndValue is null)
{
return;
}
if (!TryParseIndex(indexAndValue[0], out int index, list.Count))
{
return;
}
list[index] = indexAndValue[1];
}
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " insert ")]
public void Insert(string args)
{
string[] parts = SplitTwo(args, " insert ");
if (parts is null)
{
return;
}
if (!TryGetList(parts[0], out List<string> list))
{
return;
}
string[] indexAndValue = SplitIndexAndValue(parts[1]);
if (indexAndValue is null)
{
return;
}
if (!TryParseIndex(indexAndValue[0], out int index, list.Count + 1))
{
return;
}
list.Insert(index, indexAndValue[1]);
}
private string[] SplitTwo(string input, string separator)
{
string[] parts = input.Split(separator, 2, StringSplitOptions.None);
if (parts.Length != 2)
{
RuntimeInfo.Exit("Invalid syntax", true);
return null;
}
parts[0] = parts[0].Trim();
parts[1] = parts[1].Trim();
if (string.IsNullOrWhiteSpace(parts[0]))
{
RuntimeInfo.Exit("Invalid syntax", true);
return null;
}
return parts;
}
private bool TryGetList(string name, out List<string> list)
{
if (!RuntimeInfo.Lists.TryGetValue(name, out list))
{
RuntimeInfo.Exit($"List \"{name}\" not found", true);
return false;
}
return true;
}
private string[] SplitIndexAndValue(string input)
{
string[] parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
{
RuntimeInfo.Exit("Invalid syntax", true);
return null;
}
parts[0] = parts[0].Trim();
parts[1] = parts[1].Trim();
return parts;
}
private bool TryParseIndex(string rawIndex, out int index, int maxExclusive)
{
bool success = int.TryParse(rawIndex.Trim(), out index);
if (!success)
{
RuntimeInfo.Exit($"\"{rawIndex}\" is not a valid index", true);
return false;
}
if (index < 0 || index >= maxExclusive)
{
RuntimeInfo.Exit($"Index {index} out of range", true);
return false;
}
return true;
}
}