diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index ff18b42..ab96d1d 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -19,18 +19,6 @@ This document describes the current, word-based YesNt syntax. ## Quick Example -v1: - -```ynt -name -ret -cal greet -``` - -v2: - ```ynt let name = world func greet: @@ -39,6 +27,26 @@ return call greet ``` +## Block Conditionals + +```ynt +if 10 > 5: +print_line yes +else: +print_line no +end_if +``` + +## While Loops + +```ynt +let i = 3 +while ${i} > 0: +print_line ${i} +let i = ${i} - 1 calc +end_while +``` + ## Full v1 -> v2 Mapping | Area | v1 Syntax | v2 Syntax | Notes | diff --git a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index 0bddd5b..6bd75b9 100644 --- a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -34,4 +34,106 @@ public class CodeFlowTests ]; YesNtAssert.IsLastLineEqual(lines, "1"); } + + [TestMethod] + public void IfBlockTrueTest() + { + List lines = + [ + "let result = low", + "if 6 > 5:", + "let result = high", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "high"); + } + + [TestMethod] + public void IfElseFalseBranchTest() + { + List lines = + [ + "let result = low", + "if 6 < 5:", + "let result = high", + "else:", + "let result = medium", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "medium"); + } + + [TestMethod] + public void NestedIfElseTest() + { + List lines = + [ + "let result = 0", + "if 1 == 1:", + "if 2 == 3:", + "let result = 1", + "else:", + "let result = 2", + "end_if", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "2"); + } + + [TestMethod] + public void WhileLoopTest() + { + List lines = + [ + "let i = 3", + "while ${i} > 0:", + "let i = ${i} - 1 calc", + "end_while", + "${i}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void WhileSkipBodyWhenFalseTest() + { + List lines = + [ + "let i = 0", + "while ${i} > 0:", + "let i = 99", + "end_while", + "${i}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void NestedWhileLoopTest() + { + List lines = + [ + "let outer = 2", + "let count = 0", + "while ${outer} > 0:", + "let inner = 2", + "while ${inner} > 0:", + "let count = ${count} + 1 calc", + "let inner = ${inner} - 1 calc", + "end_while", + "let outer = ${outer} - 1 calc", + "end_while", + "${count}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "4"); + } } diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 91fc087..38aee20 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -140,6 +140,105 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } + [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")] + public void IfBlock(string args) + { + args = args.Trim(); + if (!args.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string condition = args[..^1].Trim(); + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", true); + return; + } + + if (result == true) + { + return; + } + + (int targetLine, _) = FindElseOrEndIf(RuntimeInfo.LineNumber); + if (targetLine < 0) + { + RuntimeInfo.Exit("No matching end_if found", true); + return; + } + + RuntimeInfo.LineNumber = targetLine; + } + + [Statement("else:", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void Else(string _) + { + int targetLine = FindEndIf(RuntimeInfo.LineNumber); + if (targetLine < 0) + { + RuntimeInfo.Exit("No matching end_if found", true); + return; + } + + RuntimeInfo.LineNumber = targetLine; + } + + [Statement("end_if", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void EndIf(string _) + { + } + + [Statement("while", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")] + public void While(string args) + { + args = args.Trim(); + if (!args.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string condition = args[..^1].Trim(); + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", true); + return; + } + + if (result == true) + { + return; + } + + int endWhileLine = FindEndWhile(RuntimeInfo.LineNumber); + if (endWhileLine < 0) + { + RuntimeInfo.Exit("No matching end_while found", true); + return; + } + + RuntimeInfo.LineNumber = endWhileLine; + } + + [Statement("end_while", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void EndWhile(string _) + { + int whileLine = FindWhile(RuntimeInfo.LineNumber); + if (whileLine < 0) + { + RuntimeInfo.Exit("No matching while found", true); + return; + } + + RuntimeInfo.LineNumber = whileLine - 1; + } + [Statement("exit", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] public void End(string _) { @@ -198,4 +297,132 @@ internal class CodeFlowStatements : StatementRuntimeInformation { return value.Trim().TrimEnd(':').Trim(); } + + private (int TargetLine, bool IsElse) FindElseOrEndIf(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsIfStart(line)) + { + depth++; + continue; + } + + if (line == "end_if") + { + if (depth == 0) + { + return (i, false); + } + + depth--; + continue; + } + + if (line == "else:" && depth == 0) + { + return (i, true); + } + } + + return (-1, false); + } + + private int FindEndIf(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsIfStart(line)) + { + depth++; + continue; + } + + if (line == "end_if") + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private static bool IsIfStart(string line) + { + return line.StartsWith("if ", StringComparison.Ordinal) && line.EndsWith(':'); + } + + private int FindEndWhile(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsWhileStart(line)) + { + depth++; + continue; + } + + if (line == "end_while") + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private int FindWhile(int currentLine) + { + int depth = 0; + + for (int i = currentLine - 1; i >= 0; i--) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (line == "end_while") + { + depth++; + continue; + } + + if (IsWhileStart(line)) + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private static bool IsWhileStart(string line) + { + return line.StartsWith("while ", StringComparison.Ordinal) && line.EndsWith(':'); + } }