mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 00:56:31 +02:00
Add support for maps
This commit is contained in:
+106
-4
@@ -17,10 +17,11 @@ Execution proceeds top-to-bottom unless a control-flow statement changes the lin
|
||||
8. [Control flow](#control-flow)
|
||||
9. [Functions](#functions)
|
||||
10. [Lists](#lists)
|
||||
11. [Processing](#processing)
|
||||
12. [System](#system)
|
||||
13. [Predefined tokens](#predefined-tokens)
|
||||
14. [Termination](#termination)
|
||||
11. [Maps](#maps)
|
||||
12. [Processing](#processing)
|
||||
13. [System](#system)
|
||||
14. [Predefined tokens](#predefined-tokens)
|
||||
15. [Termination](#termination)
|
||||
|
||||
---
|
||||
|
||||
@@ -609,6 +610,99 @@ Removes the list entirely.
|
||||
|
||||
---
|
||||
|
||||
## Maps
|
||||
|
||||
Maps are key–value stores. Each key is unique; putting a value under an existing key overwrites it. All map operations start with the keyword `map`.
|
||||
|
||||
### Create or reset - `map … new`
|
||||
|
||||
```
|
||||
map <name> new
|
||||
```
|
||||
|
||||
Creates an empty map. If the map already exists it is cleared.
|
||||
|
||||
### Add or update an entry - `map … put`
|
||||
|
||||
```
|
||||
map <name> put <key>, <value>
|
||||
```
|
||||
|
||||
Sets `<key>` to `<value>`. If the key already exists its value is replaced. The statement takes exactly two comma-separated parts. Keys may contain spaces unquoted. A key or value that itself contains a comma must be wrapped in quotes.
|
||||
|
||||
```ynt
|
||||
map settings new
|
||||
map settings put name, Alice
|
||||
map settings put city, Berlin
|
||||
map settings get name
|
||||
var who = %out
|
||||
print_line ${who}
|
||||
```
|
||||
|
||||
### Get a value - `map … get`
|
||||
|
||||
```
|
||||
map <name> get <key>
|
||||
```
|
||||
|
||||
Pushes the value stored under `<key>` onto the output stack. Access it with `%out`. Terminates with an error if the key does not exist.
|
||||
|
||||
### Check a key - `map … has`
|
||||
|
||||
```
|
||||
map <name> has <key>
|
||||
```
|
||||
|
||||
Pushes `True` if `<key>` exists, `False` otherwise, onto the output stack.
|
||||
|
||||
```ynt
|
||||
map settings new
|
||||
map settings put name Alice
|
||||
map settings has name
|
||||
var present = %out
|
||||
print_line ${present}
|
||||
```
|
||||
|
||||
### Remove an entry - `map … remove`
|
||||
|
||||
```
|
||||
map <name> remove <key>
|
||||
```
|
||||
|
||||
Removes `<key>` and its value. Removing a key that does not exist is silently ignored.
|
||||
|
||||
### Get the size - `map … size`
|
||||
|
||||
```
|
||||
map <name> size
|
||||
```
|
||||
|
||||
Pushes the number of entries onto the output stack.
|
||||
|
||||
```ynt
|
||||
map settings size
|
||||
var n = %out
|
||||
print_line ${n} entries
|
||||
```
|
||||
|
||||
### Clear all entries - `map … clear`
|
||||
|
||||
```
|
||||
map <name> clear
|
||||
```
|
||||
|
||||
Removes all entries but keeps the map alive.
|
||||
|
||||
### Delete a map - `map … delete`
|
||||
|
||||
```
|
||||
map <name> delete
|
||||
```
|
||||
|
||||
Removes the map entirely.
|
||||
|
||||
---
|
||||
|
||||
## Processing
|
||||
|
||||
### Arithmetic - `calc`
|
||||
@@ -842,6 +936,14 @@ Like `throw` but does **not** cancel background tasks.
|
||||
| `list … length` | `list name length` | Get count → `%out` |
|
||||
| `list … clear` | `list name clear` | Clear all items |
|
||||
| `list … delete` | `list name delete` | Delete list |
|
||||
| `map … new` | `map name new` | Create/reset map |
|
||||
| `map … put` | `map name put key, value` | Add or update entry |
|
||||
| `map … get` | `map name get key` | Get value → `%out` |
|
||||
| `map … has` | `map name has key` | `True`/`False` if key exists → `%out` |
|
||||
| `map … remove` | `map name remove key` | Remove entry |
|
||||
| `map … size` | `map name size` | Get entry count → `%out` |
|
||||
| `map … clear` | `map name clear` | Clear all entries |
|
||||
| `map … delete` | `map name delete` | Delete map |
|
||||
| `calc` | `expr calc` | Evaluate arithmetic |
|
||||
| `eval` | `expr eval` | Decode string encoding |
|
||||
| `task` | `line task` | Run current line (without `task`) and continue in background |
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YesNt.Interpreter.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class MapStatementsTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void MapCreatePutGetTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings put b, 2",
|
||||
"map settings get b",
|
||||
"var result = %out",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapPutOverwritesTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings put a, 2",
|
||||
"map settings get a",
|
||||
"var result = %out",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapHasTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings has a",
|
||||
"var hasA = %out",
|
||||
"map settings has b",
|
||||
"var hasB = %out",
|
||||
"${hasA} ${hasB}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "True False");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapRemoveTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings put b, 2",
|
||||
"map settings remove a",
|
||||
"map settings size",
|
||||
"var n = %out",
|
||||
"map settings has a",
|
||||
"var hasA = %out",
|
||||
"${n} ${hasA}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "1 False");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapRemoveMissingKeyNoOpTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings remove b",
|
||||
"map settings size",
|
||||
"var n = %out",
|
||||
"${n}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapSizeTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings put b, 2",
|
||||
"map settings size",
|
||||
"var n = %out",
|
||||
"${n}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapClearTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings clear",
|
||||
"map settings size",
|
||||
"var n = %out",
|
||||
"${n}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapDeleteTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put a, 1",
|
||||
"map settings delete",
|
||||
"map settings size"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Map \"settings\" not found");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapMissingFailsTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map missing size"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Map \"missing\" not found");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapGetMissingKeyFailsTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings get a"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Key \"a\" not found");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapInvalidSyntaxFailsTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings put"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapPutExtraCommaFailsTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put test, abc, htuerthieu"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Invalid syntax");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapScopeInsideFunctionTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func make:",
|
||||
"map data new",
|
||||
"map data put k, v",
|
||||
"map data get k",
|
||||
"push_out %out",
|
||||
"return",
|
||||
"label main:",
|
||||
"call make",
|
||||
"var result = %out",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "v");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapDoesNotLeakToCallerTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func make:",
|
||||
"map data new",
|
||||
"map data put k, v",
|
||||
"return",
|
||||
"label main:",
|
||||
"call make",
|
||||
"map data size"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Map \"data\" not found");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapPutValueWithSpacesTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put \"display name\", \"John Doe\"",
|
||||
"map settings get \"display name\"",
|
||||
"var result = %out eval",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "John Doe");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapKeyWithSpacesTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put first name, John",
|
||||
"map settings get first name",
|
||||
"var result = %out",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "John");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapValueWithCommaTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"map settings new",
|
||||
"map settings put name, \"Doe, John\"",
|
||||
"map settings get name",
|
||||
"var result = %out eval",
|
||||
"${result}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "Doe, John");
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,16 @@ internal static class ExitMessages
|
||||
return $"List \"{list}\" not found";
|
||||
}
|
||||
|
||||
internal static string MapNotFound(string map)
|
||||
{
|
||||
return $"Map \"{map}\" not found";
|
||||
}
|
||||
|
||||
internal static string KeyNotFound(string key)
|
||||
{
|
||||
return $"Key \"{key}\" not found";
|
||||
}
|
||||
|
||||
internal static string InvalidIndex(string rawIndex)
|
||||
{
|
||||
return $"\"{rawIndex}\" is not a valid index";
|
||||
|
||||
@@ -17,6 +17,9 @@ internal class FunctionScope(int callerLine, Stack<string> arguments)
|
||||
/// <summary>Gets the local list table for this function invocation.</summary>
|
||||
public Dictionary<string, List<string>> Lists { get; } = [];
|
||||
|
||||
/// <summary>Gets the local map table for this function invocation.</summary>
|
||||
public Dictionary<string, Dictionary<string, string>> Maps { get; } = [];
|
||||
|
||||
/// <summary>Gets the local label table for this function invocation.</summary>
|
||||
public Dictionary<string, int> Labels { get; } = [];
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
||||
private static int internalTaskId = 0;
|
||||
private readonly Dictionary<string, string> topVariables = [];
|
||||
private readonly Dictionary<string, List<string>> topLists = [];
|
||||
private readonly Dictionary<string, Dictionary<string, string>> topMaps = [];
|
||||
|
||||
public Dictionary<string, string> GlobalVariables { get; set; } = [];
|
||||
public Dictionary<string, int> Functions { get; } = [];
|
||||
@@ -54,6 +55,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
||||
|
||||
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, Dictionary<string, string>> Maps => FunctionCallStack.Count == 0 ? topMaps : FunctionCallStack.Peek().Maps;
|
||||
|
||||
public Dictionary<string, int> Labels { get => FunctionCallStack.Count == 0 ? field : FunctionCallStack.Peek().Labels; } = [];
|
||||
|
||||
@@ -173,6 +175,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
||||
{
|
||||
topVariables.Clear();
|
||||
topLists.Clear();
|
||||
topMaps.Clear();
|
||||
Lines.Clear();
|
||||
GlobalVariables.Clear();
|
||||
Labels.Clear();
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class MapStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " new")]
|
||||
public void Create(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " new");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = parts[0];
|
||||
if (!RuntimeInfo.Maps.TryGetValue(name, out Dictionary<string, string> value))
|
||||
{
|
||||
RuntimeInfo.Maps.Add(name, []);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " delete")]
|
||||
public void Delete(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " delete");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = parts[0];
|
||||
|
||||
if (!RuntimeInfo.Maps.ContainsKey(name))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.MapNotFound(name), true);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = RuntimeInfo.Maps.Remove(name);
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " clear")]
|
||||
public void Clear(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " clear");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map.Clear();
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " size")]
|
||||
public void Size(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " size");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Push(map.Count.ToString());
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " put ")]
|
||||
public void Put(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " put ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] keyAndValue = SplitKeyAndValue(parts[1]);
|
||||
if (keyAndValue.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map[keyAndValue[0]] = keyAndValue[1];
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " get ")]
|
||||
public void Get(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " get ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string key = parts[1];
|
||||
if (!map.TryGetValue(key, out string value))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.KeyNotFound(key), true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Push(value);
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " has ")]
|
||||
public void Has(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " has ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Push(map.ContainsKey(parts[1]).ToString());
|
||||
}
|
||||
|
||||
[Statement("map", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " remove ")]
|
||||
public void Remove(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " remove ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetMap(parts[0], out Dictionary<string, string> map))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = map.Remove(parts[1]);
|
||||
}
|
||||
|
||||
private string[] SplitTwo(string input, string separator)
|
||||
{
|
||||
string[] parts = input.Split(separator, 2, StringSplitOptions.None);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
parts[0] = parts[0].Trim();
|
||||
parts[1] = parts[1].Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parts[0]))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private bool TryGetMap(string name, out Dictionary<string, string> map)
|
||||
{
|
||||
if (!RuntimeInfo.Maps.TryGetValue(name, out map))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.MapNotFound(name), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string[] SplitKeyAndValue(string input)
|
||||
{
|
||||
string[] parts = input.Split(',');
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
parts[0] = parts[0].Trim();
|
||||
parts[1] = parts[1].Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parts[0]) || string.IsNullOrWhiteSpace(parts[1]))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user