diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index dd62b7a..690e071 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -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 | diff --git a/YesNt.Interpreter.Tests/ListStatementsTests.cs b/YesNt.Interpreter.Tests/ListStatementsTests.cs new file mode 100644 index 0000000..b5a447d --- /dev/null +++ b/YesNt.Interpreter.Tests/ListStatementsTests.cs @@ -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 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 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 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 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 lines = + [ + "list items new", + "list items delete", + "list items length" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "List \"items\" not found"); + } + + [TestMethod] + public void ListMissingFailsTest() + { + List lines = + [ + "list missing get 0" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "List \"missing\" not found"); + } + + [TestMethod] + public void ListInvalidIndexFailsTest() + { + List 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 lines = + [ + "list items add" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void ListScopeInsideFunctionTest() + { + List 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 lines = + [ + "list items new", + "list items add hello~spcworld", + "list items get 0", + "var result = %out eval", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello world"); + } +} diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs index 98a093e..7bd07a3 100644 --- a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -132,7 +132,9 @@ public class ProcessingStatementsTests [ "global result = 0", "global result = 1 task", - "sleep 50", + "while ${result} == 0:", + "sleep 10", + "end_while", "${result}" ]; diff --git a/YesNt.Interpreter/Runtime/FunctionScope.cs b/YesNt.Interpreter/Runtime/FunctionScope.cs index cbae505..4bea9c6 100644 --- a/YesNt.Interpreter/Runtime/FunctionScope.cs +++ b/YesNt.Interpreter/Runtime/FunctionScope.cs @@ -6,7 +6,8 @@ internal class FunctionScope(int callerLine, Stack arguments) { public int CallerLine { get; } = callerLine; public Dictionary Variables { get; } = []; + public Dictionary> Lists { get; } = []; public Dictionary Labels { get; } = []; public Stack Arguments { get; } = arguments; public Stack Results { get; } = new(); -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 719e010..57d61d6 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -15,6 +15,7 @@ internal sealed class RuntimeInformation private static int internalTaskId = 0; private readonly Dictionary topVariables = []; + private readonly Dictionary> topLists = []; private readonly Dictionary topLabels = []; private RuntimeInformation parentRuntimeInformation; private int taskId = 0; @@ -43,6 +44,7 @@ internal sealed class RuntimeInformation } public Dictionary Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables; + public Dictionary> Lists => FunctionCallStack.Count == 0 ? topLists : FunctionCallStack.Peek().Lists; public Dictionary 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); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/ListStatements.cs b/YesNt.Interpreter/Statements/ListStatements.cs new file mode 100644 index 0000000..bb2e810 --- /dev/null +++ b/YesNt.Interpreter/Statements/ListStatements.cs @@ -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 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 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 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 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 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 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 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 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; + } +}