Add snapcraft & chocolatey manifests and move code to src

This commit is contained in:
Stone_Red
2026-03-06 00:51:31 +01:00
parent 5efd698772
commit 2a03352169
64 changed files with 302 additions and 5 deletions
@@ -0,0 +1,377 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using YesNt.Interpreter.Attributes;
using YesNt.Interpreter.Enums;
using YesNt.Interpreter.Runtime;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class AddStatementTests
{
[TestMethod]
public void AddStatementStartOfLineExecutesHandlerTest()
{
List<string> lines =
[
"my_command hello"
];
string? capturedArgs = null;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement("my_command", SearchMode.StartOfLine, SpaceAround.End, args =>
{
capturedArgs = args;
});
});
Assert.AreEqual("hello", capturedArgs);
}
[TestMethod]
public void AddStatementConvenienceOverloadHandlerIsCalledTest()
{
List<string> lines =
[
"custom_cmd world"
];
bool handlerCalled = false;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement("custom_cmd", SearchMode.StartOfLine, SpaceAround.End, _ =>
{
handlerCalled = true;
});
});
Assert.IsTrue(handlerCalled);
}
[TestMethod]
public void AddStatementAttributeOverloadHandlerIsCalledTest()
{
List<string> lines =
[
"attr_cmd test"
];
bool handlerCalled = false;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
StatementAttribute attr = new StatementAttribute("attr_cmd", SearchMode.StartOfLine, SpaceAround.End);
interpreter.AddStatement(attr, _ =>
{
handlerCalled = true;
});
});
Assert.IsTrue(handlerCalled);
}
[TestMethod]
public void AddStatementExactSearchModeTest()
{
List<string> lines =
[
"exact_cmd"
];
bool handlerCalled = false;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement("exact_cmd", SearchMode.Exact, SpaceAround.None, _ =>
{
handlerCalled = true;
});
});
Assert.IsTrue(handlerCalled);
}
[TestMethod]
public void AddStatementContainsSearchModeTest()
{
List<string> lines =
[
"prefix ~mark~ suffix"
];
bool handlerCalled = false;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement(" ~mark~ ", SearchMode.Contains, SpaceAround.None, _ =>
{
handlerCalled = true;
});
});
Assert.IsTrue(handlerCalled);
}
[TestMethod]
public void AddStatementEndOfLineSearchModeTest()
{
List<string> lines =
[
"some text !end"
];
bool handlerCalled = false;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement(" !end", SearchMode.EndOfLine, SpaceAround.None, _ =>
{
handlerCalled = true;
});
});
Assert.IsTrue(handlerCalled);
}
[TestMethod]
public void AddStatementReceivesCorrectArgsTest()
{
List<string> lines =
[
"capture_cmd the quick brown fox"
];
string? capturedArgs = null;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement("capture_cmd", SearchMode.StartOfLine, SpaceAround.End, args =>
{
capturedArgs = args;
});
});
Assert.AreEqual("the quick brown fox", capturedArgs);
}
[TestMethod]
public void AddStatementWorksAlongsideBuiltinStatementsTest()
{
List<string> lines =
[
"custom_log first",
"print_line second"
];
bool customCalled = false;
YesNtAssert.ContainsDebugOutputWithSetup(lines, "second", interpreter =>
{
interpreter.AddStatement("custom_log", SearchMode.StartOfLine, SpaceAround.End, _ =>
{
customCalled = true;
});
});
Assert.IsTrue(customCalled);
}
[TestMethod]
public void AddStatementUnknownStatementFailsTest()
{
List<string> lines =
[
"unknown_command foo"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void RemoveStatementCausesInvalidStatementTest()
{
List<string> lines =
[
"sleep 100"
];
YesNtAssert.ContainsTerminationMessageWithSetup(lines, "Invalid statement", interpreter =>
{
interpreter.RemoveStatement("sleep");
});
}
[TestMethod]
public void DisabledStatementIsIgnoredTest()
{
List<string> lines =
[
"var x = hello",
"${x}"
];
YesNtAssert.ContainsTerminationMessageWithSetup(lines, "Variable \"x\" not found", interpreter =>
{
interpreter.DisableStatement("var");
});
}
[TestMethod]
public void DisabledStatementCanBeReenabledTest()
{
List<string> lines =
[
"var result = overwritten",
"${result}"
];
YesNtAssert.IsLastLineEqualWithSetup(lines, "overwritten", setup: interpreter =>
{
interpreter.DisableStatement("var");
interpreter.EnableStatement("var");
});
}
[TestMethod]
public void DisableNonExistentStatementIsNoOpTest()
{
List<string> lines =
[
"var x = ok",
"${x}"
];
YesNtAssert.IsLastLineEqualWithSetup(lines, "ok", setup: interpreter =>
{
interpreter.DisableStatement("nonexistent_keyword");
});
}
[TestMethod]
public void EnableNonDisabledStatementIsNoOpTest()
{
List<string> lines =
[
"var x = ok",
"${x}"
];
YesNtAssert.IsLastLineEqualWithSetup(lines, "ok", setup: interpreter =>
{
interpreter.EnableStatement("var");
});
}
[TestMethod]
public void AddStatementWithHighPriorityRunsBeforeNormalTest()
{
List<string> lines =
[
"priority_cmd arg"
];
int callOrder = 0;
int highPriorityOrder = -1;
int normalPriorityOrder = -1;
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement(
new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.High },
_ => highPriorityOrder = callOrder++);
interpreter.AddStatement(
new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal },
_ => normalPriorityOrder = callOrder++);
});
Assert.IsTrue(highPriorityOrder < normalPriorityOrder, "High priority statement should execute before Normal priority");
}
[TestMethod]
public void AddStatementAloneDoesNotReplaceBuiltinTest()
{
// Adding a custom 'var' handler without RemoveStatement first means BOTH handlers fire.
// The built-in still sets the variable, so ${x} resolves normally.
List<string> lines =
[
"var x = original",
"${x}"
];
bool customHandlerCalled = false;
YesNtAssert.IsLastLineEqualWithSetup(lines, "original", setup: interpreter =>
{
interpreter.AddStatement("var", SearchMode.StartOfLine, SpaceAround.End, _ =>
{
customHandlerCalled = true;
});
});
Assert.IsTrue(customHandlerCalled, "Custom handler should have fired alongside the built-in");
}
[TestMethod]
public void RemoveThenAddStatementReplacesBuiltinTest()
{
// RemoveStatement + AddStatement correctly replaces the built-in.
// The built-in 'var' is gone, so only the custom handler fires.
List<string> lines =
[
"var x = original",
"${x}"
];
YesNtAssert.ContainsTerminationMessageWithSetup(lines, "Variable \"x\" not found", interpreter =>
{
interpreter.RemoveStatement("var");
interpreter.AddStatement("var", SearchMode.StartOfLine, SpaceAround.End, _ => { });
});
}
[TestMethod]
public void AddStatementWithRuntimeInfoCanSetVariableTest()
{
List<string> lines =
[
"set_var foo",
"${foo}"
];
YesNtAssert.IsLastLineEqualWithSetup(lines, "hello", interpreter =>
{
interpreter.AddStatement("set_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
{
rt.Variables[args] = "hello";
});
});
}
[TestMethod]
public void AddStatementWithRuntimeInfoCanReadVariableTest()
{
List<string> lines =
[
"var greeting = world",
"echo_var greeting"
];
string? captured = null;
YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
{
interpreter.AddStatement("echo_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
{
_ = rt.Variables.TryGetValue(args, out captured);
});
});
Assert.AreEqual("world", captured);
}
}
@@ -0,0 +1,3 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)]
@@ -0,0 +1,80 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class CodeFlowStatementsTests
{
[TestMethod]
public void ExitStopsExecutionTest()
{
List<string> lines =
[
"var result = before",
"exit",
"var result = after",
"${result}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Planned termination by code");
}
[TestMethod]
public void AbortAllStopsExecutionTest()
{
List<string> lines =
[
"abort_all",
"var result = after",
"${result}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Canceling all tasks");
}
[TestMethod]
public void ThrowTerminatesWithErrorFlagTest()
{
List<string> lines =
[
"throw bad"
];
YesNtAssert.ContainsTerminationMessage(lines, "with the message: bad");
}
[TestMethod]
public void ErrorTerminatesWithMessageTest()
{
List<string> lines =
[
"error soft"
];
YesNtAssert.ContainsTerminationMessage(lines, "with the message: soft");
}
[TestMethod]
public void MissingLabelFailsTest()
{
List<string> lines =
[
"goto nowhere"
];
YesNtAssert.ContainsTerminationMessage(lines, "Label \"nowhere\" not found");
}
[TestMethod]
public void MissingFunctionFailsTest()
{
List<string> lines =
[
"call nowhere"
];
YesNtAssert.ContainsTerminationMessage(lines, "Function \"nowhere\" not found");
}
}
@@ -0,0 +1,288 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class CodeFlowTests
{
[TestMethod]
public void FunctionTest()
{
List<string> lines =
[
"call yes",
"func yes:",
"global result = 1",
"return",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void LabelsTest()
{
List<string> lines =
[
"var result = 1",
"goto yes",
"var result = 0",
"label yes:",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void IfBlockTrueTest()
{
List<string> lines =
[
"var result = low",
"if 6 > 5:",
"var result = high",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "high");
}
[TestMethod]
public void IfElseFalseBranchTest()
{
List<string> lines =
[
"var result = low",
"if 6 < 5:",
"var result = high",
"else:",
"var result = medium",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "medium");
}
[TestMethod]
public void NestedIfElseTest()
{
List<string> lines =
[
"var result = 0",
"if 1 == 1:",
"if 2 == 3:",
"var result = 1",
"else:",
"var result = 2",
"end_if",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "2");
}
[TestMethod]
public void WhileLoopTest()
{
List<string> lines =
[
"var i = 3",
"while ${i} > 0:",
"var i = ${i} - 1 calc",
"end_while",
"${i}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void WhileSkipBodyWhenFalseTest()
{
List<string> lines =
[
"var i = 0",
"while ${i} > 0:",
"var i = 99",
"end_while",
"${i}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void NestedWhileLoopTest()
{
List<string> lines =
[
"var outer = 2",
"var count = 0",
"while ${outer} > 0:",
"var inner = 2",
"while ${inner} > 0:",
"var count = ${count} + 1 calc",
"var inner = ${inner} - 1 calc",
"end_while",
"var outer = ${outer} - 1 calc",
"end_while",
"${count}"
];
YesNtAssert.IsLastLineEqual(lines, "4");
}
[TestMethod]
public void IfElseTrueSkipsElseBranchTest()
{
List<string> lines =
[
"var result = 0",
"if 2 > 1:",
"var result = 1",
"else:",
"var result = 2",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void IfWithoutElseFalseSkipsBodyTest()
{
List<string> lines =
[
"var result = 5",
"if 1 == 2:",
"var result = 1",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "5");
}
[TestMethod]
public void IfGotoTrueTest()
{
List<string> lines =
[
"var result = 0",
"if 1 == 1 goto done",
"var result = 2",
"label done:",
"var result = ${result} + 1 calc",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void IfCallTrueTest()
{
List<string> lines =
[
"func set_result:",
"global result = ok",
"return",
"if 1 == 1 call set_result",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
[TestMethod]
public void GotoInsideLoopExitsLoopTest()
{
List<string> lines =
[
"var hit = no",
"while 1 == 1:",
"var hit = yes",
"goto done",
"end_while",
"label done:",
"${hit}"
];
YesNtAssert.IsLastLineEqual(lines, "yes");
}
[TestMethod]
public void LabelWithoutColonFailsTest()
{
List<string> lines =
[
"label loop"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void FunctionWithoutColonFailsTest()
{
List<string> lines =
[
"func missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void MissingEndIfFailsTest()
{
List<string> lines =
[
"if 1 == 2:",
"var result = 1"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found");
}
[TestMethod]
public void ElseWithoutIfFailsTest()
{
List<string> lines =
[
"else:"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found");
}
[TestMethod]
public void MissingEndWhileFailsTest()
{
List<string> lines =
[
"var i = 0",
"while ${i} > 1:",
"var i = ${i} + 1 calc"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_while found");
}
[TestMethod]
public void EndWhileWithoutWhileFailsTest()
{
List<string> lines =
[
"end_while"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching while found");
}
}
@@ -0,0 +1,110 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using YesNt.Interpreter.Runtime;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class ConsoleStatementsTests
{
private static readonly object ConsoleLock = new object();
[TestMethod]
public void PrintLineWritesOutputTest()
{
List<string> lines =
[
"print_line hello"
];
YesNtAssert.ContainsDebugOutput(lines, "hello");
}
[TestMethod]
public void PrintWritesOutputTest()
{
List<string> lines =
[
"print hello"
];
YesNtAssert.ContainsDebugOutput(lines, "hello");
}
[TestMethod]
public void ClearThrowsInNonInteractiveConsoleTest()
{
List<string> lines =
[
"clear"
];
_ = Assert.Throws<IOException>(() => YesNtAssert.GetLastLine(lines));
}
[TestMethod]
public void ReadLineReplacesTokenTest()
{
lock (ConsoleLock)
{
TextReader originalIn = Console.In;
try
{
Console.SetIn(new StringReader("typed value" + Environment.NewLine));
List<string> lines =
[
"var value = %read_line",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "typed value");
}
finally
{
Console.SetIn(originalIn);
}
}
}
[TestMethod]
[DoNotParallelize] // timing-sensitive: relies on Thread.Sleep to let the interpreter reach %read_key
public void ReadKeyCanBeInterruptedByStopTest()
{
YesNtInterpreter interpreter = new YesNtInterpreter();
AutoResetEvent onDone = new AutoResetEvent(false);
StringBuilder output = new StringBuilder();
interpreter.OnDebugOutput += (s) => _ = output.Append(s);
interpreter.OnLineExecuted += (e) =>
{
if (e is null)
{
_ = onDone.Set();
}
};
List<string> lines =
[
"var value = %read_key"
];
_ = Task.Run(() => interpreter.Execute(lines, true));
Thread.Sleep(100);
interpreter.Stop();
_ = onDone.WaitOne(TimeSpan.FromSeconds(3));
StringAssert.Contains(output.ToString(), "Terminated by external process");
}
}
@@ -0,0 +1,226 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class FunctionStatementsTests
{
[TestMethod]
public void FunctionCallWithInParameterTest()
{
List<string> lines =
[
"goto main",
"func echo:",
"global result = %in",
"return",
"label main:",
"call echo with hello",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "hello");
}
[TestMethod]
public void HasInAndHasOutTokensTest()
{
List<string> lines =
[
"goto main",
"func probe:",
"global hasInBefore = %has_in",
"var consume = %in",
"global hasInAfter = %has_in",
"push_out ${hasInBefore}",
"push_out ${hasInAfter}",
"return",
"label main:",
"call probe with x",
"var hasOut = %has_out",
"${hasOut}"
];
YesNtAssert.IsLastLineEqual(lines, "True");
}
[TestMethod]
public void OutParameterReadTest()
{
List<string> lines =
[
"goto main",
"func make:",
"push_out out_value",
"return",
"label main:",
"call make with anything",
"var value = %out",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "out_value");
}
[TestMethod]
public void OutParameterWithoutValueFailsTest()
{
List<string> lines =
[
"var x = %out"
];
YesNtAssert.ContainsTerminationMessage(lines, "No out argument in stack");
}
[TestMethod]
public void InParameterOutsideFunctionFailsTest()
{
List<string> lines =
[
"var x = %in"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void ReturnOutsideFunctionFailsTest()
{
List<string> lines =
[
"return"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void PushOutOutsideFunctionFailsTest()
{
List<string> lines =
[
"push_out value"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void FunctionWithoutColonFailsTest()
{
List<string> lines =
[
"func missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void NestedFunctionDefinitionFailsTest()
{
List<string> lines =
[
"func outer:",
"func inner:",
"return"
];
YesNtAssert.ContainsTerminationMessage(lines, "Nested functions are not allowed");
}
[TestMethod]
public void LocalVariableDoesNotLeakToCallerTest()
{
List<string> lines =
[
"goto main",
"func modify:",
"var x = inner",
"return",
"label main:",
"var x = outer",
"call modify",
"${x}"
];
YesNtAssert.IsLastLineEqual(lines, "outer");
}
[TestMethod]
public void ClearCallStackRunsTest()
{
List<string> lines =
[
"clear_call_stack",
"var result = ok",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
// --- Error path tests ---
[TestMethod]
public void AccessInWithoutArgFailsTest()
{
List<string> lines =
[
"goto main",
"func noin:",
"var x = %in",
"return",
"label main:",
"call noin"
];
YesNtAssert.ContainsTerminationMessage(lines, "No in argument in stack");
}
// --- Nested scope tests ---
[TestMethod]
public void GlobalModifiedInsideFunctionIsVisibleAfterReturnTest()
{
List<string> lines =
[
"goto main",
"func setglobal:",
"global shared = modified",
"return",
"label main:",
"global shared = original",
"call setglobal",
"${shared}"
];
YesNtAssert.IsLastLineEqual(lines, "modified");
}
[TestMethod]
public void NestedFunctionCallsHaveIndependentLocalScopesTest()
{
List<string> lines =
[
"goto main",
"func outer:",
"var x = outer_val",
"call inner",
"push_out ${x}",
"return",
"func inner:",
"var x = inner_val",
"return",
"label main:",
"call outer",
"var result = %out",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "outer_val");
}
}
@@ -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 world\"",
"list items get 0",
"var result = %out eval",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "hello world");
}
}
@@ -0,0 +1,107 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class PredefinedVariableStatementsTests
{
[TestMethod]
public void TimeTokenProducesUnixTimestampTest()
{
List<string> lines =
[
"%time"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(long.TryParse(value, out long parsed));
long now = DateTimeOffset.Now.ToUnixTimeSeconds();
Assert.IsTrue(Math.Abs(now - parsed) < 10);
}
[TestMethod]
public void OsTokenProducesValueTest()
{
List<string> lines =
[
"%os"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsFalse(string.IsNullOrWhiteSpace(value));
}
[TestMethod]
public void CpuTokenProducesValueTest()
{
List<string> lines =
[
"%cpu"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsFalse(string.IsNullOrWhiteSpace(value));
}
[TestMethod]
public void Is64TokenProducesBooleanTest()
{
List<string> lines =
[
"%is64"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.AreEqual(Environment.Is64BitOperatingSystem.ToString(), value);
}
[TestMethod]
public void PiTokenProducesPiTest()
{
List<string> lines =
[
"%pi"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(double.TryParse(value, out double parsed));
Assert.IsTrue(Math.Abs(parsed - Math.PI) < 0.001d);
}
[TestMethod]
public void RandTokenProducesIntegerTest()
{
List<string> lines =
[
"%rand"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(int.TryParse(value, out int parsed));
Assert.IsTrue(parsed >= 32767);
}
[TestMethod]
public void MultipleRandTokensAreReplacedTest()
{
List<string> lines =
[
"%rand %rand"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
string[] parts = value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
Assert.AreEqual(2, parts.Length);
Assert.IsTrue(int.TryParse(parts[0], out _));
Assert.IsTrue(int.TryParse(parts[1], out _));
}
}
@@ -0,0 +1,201 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class ProcessingStatementsTests
{
[TestMethod]
public void MultiplicationTest()
{
YesNtAssert.IsLineEqual("10 * 10 calc", "100");
}
[TestMethod]
public void DivisionTest()
{
YesNtAssert.IsLineEqual("90 / 4 calc", "22.5");
}
[TestMethod]
public void AdditionTest()
{
YesNtAssert.IsLineEqual("10 + 10 calc", "20");
}
[TestMethod]
public void SubtractionTest()
{
YesNtAssert.IsLineEqual("10 - 10 calc", "0");
}
[TestMethod]
public void ModulusTest()
{
YesNtAssert.IsLineEqual("10 % 3 calc", "1");
}
[TestMethod]
public void ExponentiationTest()
{
YesNtAssert.IsLineEqual("2 ^ 3 calc", "8");
}
[TestMethod]
public void EvalRawTildeSequenceIsLiteralTest()
{
YesNtAssert.IsLineEqual("hello~nliworld eval", "hello~nliworld");
}
[TestMethod]
public void EvalDecodesStringLiteralEscapesTest()
{
List<string> lines =
[
"var x = \"hello\\nworld\"",
"${x} eval"
];
YesNtAssert.IsLastLineEqual(lines, "hello\nworld");
}
[TestMethod]
public void CalcRespectsPrecedenceTest()
{
YesNtAssert.IsLineEqual("2 + 3 * 4 calc", "14");
}
[TestMethod]
public void CalcParenthesesOverridePrecedenceTest()
{
YesNtAssert.IsLineEqual("(2 + 3) * 4 calc", "20");
}
[TestMethod]
public void SleepZeroIsValidTest()
{
List<string> lines =
[
"sleep 0",
"var result = ok",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
[TestMethod]
public void SleepInvalidValueFailsTest()
{
List<string> lines =
[
"sleep nope"
];
YesNtAssert.ContainsTerminationMessage(lines, "\"nope\" is not a valid time-out value");
}
[TestMethod]
public void SleepRunsAndContinuesTest()
{
List<string> lines =
[
"sleep 5",
"var result = ok",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
[TestMethod]
public void LengthPushesOutParameterTest()
{
List<string> lines =
[
"length hello",
"var value = %out",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "5");
}
[TestMethod]
public void ImportLoadsScriptTest()
{
string tempFile = Path.Combine(Path.GetTempPath(), $"yesnt-import-{Guid.NewGuid():N}.ynt");
try
{
File.WriteAllText(tempFile, "var imported = yes");
List<string> lines =
[
$"import {tempFile}",
"${imported}"
];
YesNtAssert.IsLastLineEqual(lines, "yes");
}
finally
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
}
[TestMethod]
public void ImportMissingFileFailsTest()
{
List<string> lines =
[
$"import {Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))}.ynt"
];
YesNtAssert.ContainsTerminationMessage(lines, "Could not find file");
}
[TestMethod]
public void TaskCanUpdateGlobalVariableTest()
{
List<string> lines =
[
"global result = 0",
"global result = 1 task",
"while ${result} == 0:",
"sleep 10",
"end_while",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1", timeout: 3000);
}
[TestMethod]
public void MultipleTasksRunConcurrentlyTest()
{
List<string> lines =
[
"global a = 0",
"global b = 0",
"global a = 1 task",
"global b = 2 task",
"while ${a} == 0:",
"sleep 10",
"end_while",
"while ${b} == 0:",
"sleep 10",
"end_while",
"${b}"
];
YesNtAssert.IsLastLineEqual(lines, "2", timeout: 3000);
}
}
@@ -0,0 +1,113 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class StringLiteralStatementsTests
{
[TestMethod]
public void StringLiteralWithSpacesWorksTest()
{
List<string> lines =
[
"var msg = \"hello world\"",
"${msg}"
];
YesNtAssert.IsLastLineEqual(lines, "hello world");
}
[TestMethod]
public void StringLiteralEscapesWorkTest()
{
List<string> lines =
[
"var msg = \"a\\n\\t\\\"b\"",
"${msg}"
];
YesNtAssert.IsLastLineEqual(lines, "a\n\t\"b");
}
[TestMethod]
public void StringLiteralPreventsVariableInterpolationTest()
{
List<string> lines =
[
"var x = hidden",
"print_line \"${x}\""
];
YesNtAssert.ContainsDebugOutput(lines, "${x}");
}
[TestMethod]
public void StringLiteralWorksWithListAddTest()
{
List<string> lines =
[
"list items new",
"list items add \"hello world\"",
"list items get 0",
"var result = %out",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "hello world");
}
[TestMethod]
public void EmptyStringLiteralHasLengthZeroTest()
{
List<string> lines =
[
"var x = \"\"",
"length ${x}",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void EscapedBackslashProducesLiteralBackslashTest()
{
List<string> lines =
[
"var msg = \"\\\\n\"", // YesNt source: "\\n" → \n (backslash + n, 2 chars)
"length ${msg}",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "2");
}
[TestMethod]
public void UnknownEscapeSequenceBecomesCharTest()
{
List<string> lines =
[
"var msg = \"\\q\"", // YesNt source: "\q" → q (1 char)
"length ${msg}",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void UnterminatedStringLiteralFailsTest()
{
List<string> lines =
[
"var msg = \"hello"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid string literal");
}
}
@@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class SystemStatementsTests
{
[TestMethod]
public void ExecWithArgsRunsProcessTest()
{
List<string> lines =
[
"exec cmd with /c,echo yesnt",
"var exitCode = %out",
"${exitCode}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void ExecWithInStackArgsRunsProcessTest()
{
List<string> lines =
[
"push_in /c",
"push_in echo yesnt",
"exec cmd",
"var exitCode = %out",
"${exitCode}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void ExecInvalidProgramFailsTest()
{
List<string> lines =
[
"exec does_not_exist_abc_xyz"
];
YesNtAssert.ContainsTerminationMessage(lines, "Failed to start \"does_not_exist_abc_xyz\"");
}
}
@@ -0,0 +1,131 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class VariableStatementsTests
{
[TestMethod]
public void LetAndReadVariableTest()
{
List<string> lines =
[
"var value = hi",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "hi");
}
[TestMethod]
public void GlobalVariableReadTest()
{
List<string> lines =
[
"global value = hi",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "hi");
}
[TestMethod]
public void LocalVariableOverridesGlobalTest()
{
List<string> lines =
[
"global value = global",
"var value = local",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "local");
}
[TestMethod]
public void DeleteLocalVariableTest()
{
List<string> lines =
[
"var value = a",
"delete value",
"global value = b",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "b");
}
[TestMethod]
public void DeleteMissingVariableFailsTest()
{
List<string> lines =
[
"delete missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found");
}
[TestMethod]
public void LetInvalidSyntaxFailsTest()
{
List<string> lines =
[
"var a"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void LetInvalidNameFailsTest()
{
List<string> lines =
[
"var a b = 1"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid syntax");
}
[TestMethod]
public void OverwriteVariableTest()
{
List<string> lines =
[
"var x = first",
"var x = second",
"${x}"
];
YesNtAssert.IsLastLineEqual(lines, "second");
}
[TestMethod]
public void EmptyStringVariableHasLengthZeroTest()
{
List<string> lines =
[
"var x = \"\"",
"length ${x}",
"var len = %out",
"${len}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void MissingVariableReferenceFailsTest()
{
List<string> lines =
[
"${missing}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found");
}
}
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
<PackageReference Include="coverlet.collector" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\YesNt.Interpreter\YesNt.Interpreter.csproj" />
</ItemGroup>
</Project>
+126
View File
@@ -0,0 +1,126 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using YesNt.Interpreter.Runtime;
namespace YesNt.Interpreter.Tests;
internal static class YesNtAssert
{
public static void IsLastLineEqual(List<string> lines, string expected, int timeout = 1000)
{
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreEqual(expected, debugEventArgs?.CurrentLine);
}
public static void IsLineEqual(string line, string expected, int timeout = 1000)
{
List<string> lines =
[
line
];
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreEqual(expected, debugEventArgs?.CurrentLine);
}
public static void IsLineNotEqual(string line, string expected, int timeout = 1000)
{
List<string> lines =
[
line
];
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreNotEqual(expected, debugEventArgs?.CurrentLine);
}
public static void ContainsTerminationMessage(List<string> lines, string expectedMessageFragment, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout);
StringAssert.Contains(debugOutput, expectedMessageFragment);
}
public static void ContainsDebugOutput(List<string> lines, string expectedFragment, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout);
StringAssert.Contains(debugOutput, expectedFragment);
}
public static string? GetLastLine(List<string> lines, int timeout = 1000)
{
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
return debugEventArgs?.CurrentLine;
}
public static void LastLineMatches(List<string> lines, string pattern, int timeout = 1000)
{
string? value = GetLastLine(lines, timeout);
Assert.IsNotNull(value);
StringAssert.Matches(value, new Regex(pattern));
}
public static void IsLastLineEqualWithSetup(List<string> lines, string expected, Action<YesNtInterpreter> setup, int timeout = 1000)
{
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout, setup);
Assert.AreEqual(expected, debugEventArgs?.CurrentLine);
}
public static void ContainsDebugOutputWithSetup(List<string> lines, string expectedFragment, Action<YesNtInterpreter> setup, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout, setup);
StringAssert.Contains(debugOutput, expectedFragment);
}
public static void ContainsTerminationMessageWithSetup(List<string> lines, string expectedMessageFragment, Action<YesNtInterpreter> setup, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout, setup);
StringAssert.Contains(debugOutput, expectedMessageFragment);
}
public static string? GetLastLineWithSetup(List<string> lines, Action<YesNtInterpreter> setup, int timeout = 1000)
{
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout, setup);
return debugEventArgs?.CurrentLine;
}
private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List<string> lines, int timeout, Action<YesNtInterpreter>? setup = null)
{
YesNtInterpreter yesNtInterpreter = new YesNtInterpreter();
setup?.Invoke(yesNtInterpreter);
AutoResetEvent onDone = new AutoResetEvent(false);
DebugEventArgs? debugEventArgs = null;
StringBuilder outputBuilder = new StringBuilder();
yesNtInterpreter.OnLineExecuted += (er) =>
{
if (er is not null)
{
debugEventArgs = er;
}
else
{
_ = onDone.Set();
}
};
yesNtInterpreter.OnDebugOutput += (s) =>
{
_ = outputBuilder.Append(s);
};
yesNtInterpreter.Execute(lines, true);
_ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout));
return (debugEventArgs, outputBuilder.ToString());
}
}