From e7bf9591baffd31b318d6bae04e6139d161e9ba3 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 6 Aug 2026 20:36:32 +0200 Subject: [PATCH] Rename return to end_func and add proper return statment --- README.md | 5 +- SYNTAX_V2.md | 7 +- docs/README.md | 2 +- docs/editor.md | 2 +- docs/language-reference.md | 45 +++++++-- src/YesNt.CodeEditor/Editor.cs | 2 +- .../FunctionStatementsTests.cs | 97 +++++++++++++++++++ .../Statements/FunctionStatements.cs | 25 ++++- 8 files changed, 167 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d321c33..6768d63 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,11 @@ YesNt is intentionally minimal and is well suited for: func greet: var name = %in print_line Hello, ${name}! -return +end_func func add: var result = %in + %in calc - push_out ${result} -return + return ${result} call greet with Alice call greet with Bob diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index 6669145..3fa7726 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -26,7 +26,7 @@ var name = world func greet: print_line "Hello world" print_line Hello ${name} -return +end_func call greet ``` @@ -87,7 +87,8 @@ print_line ${value} | Functions | `put value` | `push_out value` | Push out-arg in function. | | Functions | `%out` | `%out` | Keep token name for familiarity. | | Functions | `%iso` | `%has_out` | Inline token, bool if out arg exists. | -| Functions | `ret` | `return` | Return from function. | +| Functions | `ret` | `end_func` | Return from function. | +| Functions | `return value` | `return value` | Return from function, optionally pushing a value to the out stack. | | Functions | `ccs` | `clear_call_stack` | Clear call stack. | | Condition-call | `cif name \| cond` | `if cond call name` | Conditional function call. | | Termination | `end` | `exit` | Planned termination. | @@ -113,4 +114,6 @@ print_line ${value} - `%out` is intentionally kept as `%out`. - Postfix operations are `calc`, `eval`, and `task`. +- `return` exits the function early (optionally with a value via `return `); + `end_func` is the explicit end-of-function marker. Both terminate the current function call. diff --git a/docs/README.md b/docs/README.md index 12935e6..75f8127 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,7 +38,7 @@ print_line Hello, ${name}! func greet: var msg = Hello, ${name}! print_line ${msg} -return +end_func var name = Bob call greet diff --git a/docs/editor.md b/docs/editor.md index 36c96d6..02921d8 100644 --- a/docs/editor.md +++ b/docs/editor.md @@ -47,6 +47,6 @@ If you pass a file path, it is loaded on startup. ## Formatter behavior (quick summary) - Indents `func`, `if`, `else`, and `while` blocks. -- Dedents on `return`, `end_if`, and `end_while`. +- Dedents on `return`, `end_func`, `end_if`, and `end_while`. - `exit` / `throw` / `error` close active non-function blocks for following lines. - Comment lines (`# ...`) are kept unindented. diff --git a/docs/language-reference.md b/docs/language-reference.md index ec1e05f..29510f5 100644 --- a/docs/language-reference.md +++ b/docs/language-reference.md @@ -356,18 +356,18 @@ if ${debug} == True call dump_state ``` func : -return +end_func ``` A function declaration registers the function name and the line it starts on. -The body runs until `return` is reached. +The body runs until `end_func` (or an early `return`) is reached. **Nested function declarations are not allowed.** ```ynt func add: var result = %in + %in calc - push_out ${result} -return + return ${result} +end_func ``` ### Calling a function - `call` @@ -410,7 +410,7 @@ popped from the argument stack. func greet: var name = %in print_line Hello, ${name}! -return +end_func ``` #### Check if input argument exists - `%has_in` @@ -421,7 +421,7 @@ Only valid inside a function. ```ynt func greet: if %has_in call do_greet -return +end_func ``` ### Output values @@ -462,6 +462,34 @@ clear_call_stack Discards all frames on the function call stack. Useful for error recovery. +### Return from a function - `end_func` and `return` + +``` +end_func +return +return +``` + +`end_func` and a bare `return` end the current function call and hand control back to the caller. +`return ` does the same but first pushes `` onto the output stack +(it is equivalent to `push_out ` followed by `end_func`), so the caller can read it with `%out`. +`return` may be used anywhere in the body to exit a function early. + +```ynt +func add: + var a = %in + var b = %in + if ${a} == 0: + return 0 + end_if + return ${a} + ${b} calc + +call add with 0, 5 +var total = %out +print_line ${total} +``` + +Only valid inside a function; otherwise the script terminates with an error. --- ## Lists @@ -741,7 +769,7 @@ if ${x} < 0 call validate_fail func validate_fail: throw x must not be negative -return +end_func ``` ### Non-fatal error - `error` @@ -778,7 +806,8 @@ Like `throw` but does **not** cancel background tasks. | `if … call` | `if cond call name` | Conditional function call | | `push_in` | `push_in value` | Push input argument | | `push_out` | `push_out value` | Push output value (inside function) | -| `return` | `return` | Return from function | +| `end_func` | `end_func` | End current function | +| `return` | `return value` | Return from function (optional value via output stack) | | `clear_call_stack` | `clear_call_stack` | Clear call stack | | `list … new` | `list name new` | Create/reset list | | `list … add` | `list name add value` | Append to list | diff --git a/src/YesNt.CodeEditor/Editor.cs b/src/YesNt.CodeEditor/Editor.cs index 25bdead..ea1a70d 100644 --- a/src/YesNt.CodeEditor/Editor.cs +++ b/src/YesNt.CodeEditor/Editor.cs @@ -261,7 +261,7 @@ internal class TextEditor } } } - else if (trimmed == "return") + else if (trimmed == "return" || trimmed == "end_func" || trimmed.StartsWith("return ", StringComparison.Ordinal)) { for (int j = blockStack.Count - 1; j >= 0; j--) { diff --git a/src/YesNt.Interpreter.Tests/FunctionStatementsTests.cs b/src/YesNt.Interpreter.Tests/FunctionStatementsTests.cs index 667ae4f..ac25c40 100644 --- a/src/YesNt.Interpreter.Tests/FunctionStatementsTests.cs +++ b/src/YesNt.Interpreter.Tests/FunctionStatementsTests.cs @@ -223,4 +223,101 @@ public class FunctionStatementsTests YesNtAssert.IsLastLineEqual(lines, "outer_val"); } + + // --- end_func / return value tests --- + + [TestMethod] + public void EndFuncReturnsToCallerTest() + { + List lines = + [ + "goto main", + "func greet:", + "global result = done", + "end_func", + "label main:", + "call greet", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "done"); + } + + [TestMethod] + public void ReturnWithValueTest() + { + List lines = + [ + "goto main", + "func add:", + "var a = %in", + "var b = %in", + "var sum = ${a} + ${b} calc", + "return ${sum}", + "label main:", + "call add with 3, 4", + "var total = %out", + "${total}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "7"); + } + + [TestMethod] + public void ReturnWithLiteralValueTest() + { + List lines = + [ + "goto main", + "func make:", + "return out_value", + "label main:", + "call make", + "var value = %out", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "out_value"); + } + + [TestMethod] + public void ReturnEarlyExitSkipsRestOfFunctionTest() + { + List lines = + [ + "goto main", + "func probe:", + "push_out should_not_escape", + "return", + "push_out should_never_run", + "label main:", + "call probe", + "var value = %out", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "should_not_escape"); + } + + [TestMethod] + public void ReturnWithValueOutsideFunctionFailsTest() + { + List lines = + [ + "return 42" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); + } + + [TestMethod] + public void EndFuncOutsideFunctionFailsTest() + { + List lines = + [ + "end_func" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); + } } \ No newline at end of file diff --git a/src/YesNt.Interpreter/Statements/FunctionStatements.cs b/src/YesNt.Interpreter/Statements/FunctionStatements.cs index d6398fb..fafa39c 100644 --- a/src/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/src/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -133,8 +133,25 @@ internal class FunctionStatements : StatementRuntimeInformation RuntimeInfo.FunctionCallStack.Peek().Results.Push(args); } + [Statement("end_func", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + public void EndFunction(string _) + { + HandleReturn(string.Empty); + } + [Statement("return", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] public void Return(string _) + { + HandleReturn(string.Empty); + } + + [Statement("return", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + public void ReturnWithValue(string args) + { + HandleReturn(args.Trim()); + } + + private void HandleReturn(string value) { if (!RuntimeInfo.IsInFunction) { @@ -153,10 +170,14 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - RuntimeInfo.IsInFunction = false; - if (RuntimeInfo.FunctionCallStack.Count > 0) { + if (!string.IsNullOrWhiteSpace(value)) + { + RuntimeInfo.FunctionCallStack.Peek().Results.Push(value); + } + + RuntimeInfo.IsInFunction = false; FunctionScope functionScope = RuntimeInfo.FunctionCallStack.Pop(); RuntimeInfo.OutParametersStack = new Stack(functionScope.Results);