From 86819900a02ad7f8854e9e495e561a22cf794bc7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:21:49 +0100 Subject: [PATCH] Add string literal parsing with escapes and related tests --- SYNTAX_V2.md | 3 + .../StringLiteralStatementsTests.cs | 71 +++++++++++++++++ .../Statements/StringLiteralStatements.cs | 78 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs create mode 100644 YesNt.Interpreter/Statements/StringLiteralStatements.cs diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index 690e071..6669145 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -14,6 +14,8 @@ This document describes the current, word-based YesNt syntax. - Statements are line-based. - `# ...` remains a comment. - Variable interpolation inside text uses `${name}`. +- String literals use double quotes (`"..."`) with escapes like `\n`, `\t`, `\"`, `\\`. + Interpolation is not evaluated inside string literals. - Function and label declarations are block markers with a trailing `:`. - Conditions retain current evaluator expressions (for example: `a == b`, `x > 5`, `10 + 2 == 12`). @@ -22,6 +24,7 @@ This document describes the current, word-based YesNt syntax. ```ynt var name = world func greet: +print_line "Hello world" print_line Hello ${name} return call greet diff --git a/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs b/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs new file mode 100644 index 0000000..e6ffcda --- /dev/null +++ b/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs @@ -0,0 +1,71 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class StringLiteralStatementsTests +{ + [TestMethod] + public void StringLiteralWithSpacesWorksTest() + { + List lines = + [ + "var msg = \"hello world\"", + "${msg}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello world"); + } + + [TestMethod] + public void StringLiteralEscapesWorkTest() + { + List lines = + [ + "var msg = \"a\\n\\t\\\"b\"", + "${msg}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "a\n\t\"b"); + } + + [TestMethod] + public void StringLiteralPreventsVariableInterpolationTest() + { + List lines = + [ + "var x = hidden", + "print_line \"${x}\"" + ]; + + YesNtAssert.ContainsDebugOutput(lines, "${x}"); + } + + [TestMethod] + public void StringLiteralWorksWithListAddTest() + { + List 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 UnterminatedStringLiteralFailsTest() + { + List lines = + [ + "var msg = \"hello" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid string literal"); + } +} diff --git a/YesNt.Interpreter/Statements/StringLiteralStatements.cs b/YesNt.Interpreter/Statements/StringLiteralStatements.cs new file mode 100644 index 0000000..c2d0971 --- /dev/null +++ b/YesNt.Interpreter/Statements/StringLiteralStatements.cs @@ -0,0 +1,78 @@ +using System.Text; + +using YesNt.Interpreter.Attributes; +using YesNt.Interpreter.Enums; +using YesNt.Interpreter.Runtime; +using YesNt.Interpreter.Utilities; + +namespace YesNt.Interpreter.Statements; + +internal class StringLiteralStatements : StatementRuntimeInformation +{ + [Statement("\"", SearchMode.Contains, SpaceAround.None, System.ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)] + public void ParseStringLiterals(string args) + { + if (!args.Contains('"')) + { + return; + } + + StringBuilder output = new StringBuilder(args.Length); + + for (int i = 0; i < args.Length; i++) + { + char current = args[i]; + if (current != '"') + { + _ = output.Append(current); + continue; + } + + StringBuilder literal = new StringBuilder(); + bool closed = false; + i++; + + for (; i < args.Length; i++) + { + char ch = args[i]; + if (ch == '\\' && i + 1 < args.Length) + { + i++; + _ = literal.Append(ParseEscape(args[i])); + continue; + } + + if (ch == '"') + { + closed = true; + break; + } + + _ = literal.Append(ch); + } + + if (!closed) + { + RuntimeInfo.Exit("Invalid string literal", true); + return; + } + + _ = output.Append(literal.ToString().ToSafeString()); + } + + RuntimeInfo.CurrentLine = output.ToString(); + } + + private static char ParseEscape(char escapeChar) + { + return escapeChar switch + { + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + '"' => '"', + '\\' => '\\', + _ => escapeChar + }; + } +}