mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
Add named function parameters
This commit is contained in:
@@ -45,9 +45,8 @@ func greet:
|
||||
print_line Hello, ${name}!
|
||||
end_func
|
||||
|
||||
func add:
|
||||
var result = %in + %in calc
|
||||
return ${result}
|
||||
func add: a, b
|
||||
return ${a} + ${b} calc
|
||||
|
||||
call greet with Alice
|
||||
call greet with Bob
|
||||
|
||||
+3
-1
@@ -78,7 +78,7 @@ print_line ${value}
|
||||
| Code flow | `lbl name` | `label name:` | Label declaration/target. |
|
||||
| Code flow | `jmp name` | `goto name` | Unconditional jump. |
|
||||
| Code flow | `jif name \| cond` | `if cond goto name` | Conditional jump. |
|
||||
| Functions | `fnc name` | `func name:` | Function declaration. |
|
||||
| Functions | `fnc name` | `func name:` | Function declaration (optional `, ` separated named parameters). |
|
||||
| Functions | `cal name` | `call name` | Function call without args. |
|
||||
| Functions | `cal name \| a,b,c` | `call name with a, b, c` | Function call with args. |
|
||||
| Functions | `in value` | `push_in value` | Push in-arg onto input stack. |
|
||||
@@ -116,4 +116,6 @@ print_line ${value}
|
||||
- Postfix operations are `calc`, `eval`, and `task`.
|
||||
- `return` exits the function early (optionally with a value via `return <value>`);
|
||||
`end_func` is the explicit end-of-function marker. Both terminate the current function call.
|
||||
- Functions may declare named parameters after the name (e.g. `func add: a, b`); each parameter
|
||||
is bound to a local variable from the call arguments, equivalent to `var a = %in` / `var b = %in`.
|
||||
|
||||
|
||||
@@ -370,6 +370,30 @@ func add:
|
||||
end_func
|
||||
```
|
||||
|
||||
#### Named parameters
|
||||
|
||||
```
|
||||
func <name>: <param1>, <param2>, …
|
||||
<body>
|
||||
end_func
|
||||
```
|
||||
|
||||
Parameters can be declared after the function name. They are bound to local variables in
|
||||
declaration order, consuming the values passed to `call <name> with …` (or pushed with `push_in`).
|
||||
`func add: a, b` is equivalent to starting the body with `var a = %in` followed by `var b = %in`.
|
||||
|
||||
```ynt
|
||||
func add: a, b
|
||||
return ${a} + ${b} calc
|
||||
end_func
|
||||
|
||||
call add with 3, 7
|
||||
var total = %out
|
||||
print_line ${total}
|
||||
```
|
||||
|
||||
If fewer arguments are passed than declared parameters, the script terminates with an error.
|
||||
|
||||
### Calling a function - `call`
|
||||
|
||||
```
|
||||
|
||||
@@ -279,7 +279,7 @@ internal class TextEditor
|
||||
if (!isTerminatingStatement && (
|
||||
(trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| (trimmed.StartsWith("while ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| (trimmed.StartsWith("func ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| (trimmed.StartsWith("func ", StringComparison.Ordinal) && trimmed.Contains(':'))
|
||||
|| trimmed == "else:"))
|
||||
{
|
||||
if (trimmed.StartsWith("if ", StringComparison.Ordinal))
|
||||
|
||||
@@ -320,4 +320,104 @@ public class FunctionStatementsTests
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
|
||||
}
|
||||
|
||||
// --- Named parameter tests ---
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParametersTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func add: a, b",
|
||||
"return ${a} + ${b} calc",
|
||||
"label main:",
|
||||
"call add with 3, 4",
|
||||
"var total = %out",
|
||||
"${total}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "7");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParametersRespectOrderTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func pair: first, second",
|
||||
"push_out ${first}",
|
||||
"push_out ${second}",
|
||||
"end_func",
|
||||
"label main:",
|
||||
"call pair with A, B",
|
||||
"var f = %out",
|
||||
"var s = %out",
|
||||
"${f}-${s}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "A-B");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParametersMissingArgumentFailsTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func add: a, b",
|
||||
"return ${a} calc",
|
||||
"label main:",
|
||||
"call add with 5"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "No in argument in stack");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParameterInvalidSyntaxTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"func add: a b"
|
||||
];
|
||||
|
||||
YesNtAssert.ContainsTerminationMessage(lines, "Invalid syntax");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParametersExtraArgumentsRemainTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"goto main",
|
||||
"func probe: a",
|
||||
"global first = ${a}",
|
||||
"global hasExtra = %has_in",
|
||||
"push_out ${hasExtra}",
|
||||
"end_func",
|
||||
"label main:",
|
||||
"call probe with A, B",
|
||||
"var extra = %out",
|
||||
"${extra}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "True");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FunctionNamedParametersSearchEntryTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"call add with 3, 4",
|
||||
"func add: a, b",
|
||||
"return ${a} + ${b} calc",
|
||||
"var total = %out",
|
||||
"${total}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqual(lines, "7");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
||||
|
||||
public Dictionary<string, string> GlobalVariables { get; set; } = [];
|
||||
public Dictionary<string, int> Functions { get; } = [];
|
||||
public Dictionary<string, List<string>> FunctionParameters { get; } = [];
|
||||
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
||||
internal Action PreScanLinesAction { get; set; }
|
||||
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
||||
@@ -176,6 +177,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
||||
GlobalVariables.Clear();
|
||||
Labels.Clear();
|
||||
Functions.Clear();
|
||||
FunctionParameters.Clear();
|
||||
BlockBoundaries.Clear();
|
||||
FunctionCallStack.Clear();
|
||||
InParametersStack.Clear();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all classes that host statement handler methods.
|
||||
/// Subclasses declare methods decorated with <see cref="Attributes.StatementAttribute"/> or
|
||||
@@ -21,4 +24,78 @@ internal abstract class StatementRuntimeInformation
|
||||
{
|
||||
return value.Trim().TrimEnd(':').Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a function declaration (the text after the <c>func</c> keyword), extracting the
|
||||
/// function name and its optional comma-separated named parameters.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the declaration is well formed; otherwise <see langword="false"/>.
|
||||
/// </returns>
|
||||
internal static bool TryParseFunctionSignature(string declaration, out string name, out List<string> parameters)
|
||||
{
|
||||
name = string.Empty;
|
||||
parameters = [];
|
||||
|
||||
string trimmed = declaration.Trim();
|
||||
int colonIndex = trimmed.IndexOf(':');
|
||||
if (colonIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
name = NormalizeBlockName(trimmed[..colonIndex]);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string parameterSection = trimmed[(colonIndex + 1)..].Trim();
|
||||
if (string.IsNullOrWhiteSpace(parameterSection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (string rawParameter in parameterSection.Split(','))
|
||||
{
|
||||
string parameter = rawParameter.Trim();
|
||||
if (string.IsNullOrWhiteSpace(parameter) || parameter.Contains(' '))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
parameters.Add(parameter);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a function's named parameters to local variables by popping values from the
|
||||
/// current call scope's argument stack, mirroring <c>var p = %in</c> for each parameter.
|
||||
/// </summary>
|
||||
protected void BindNamedParameters(string functionKey)
|
||||
{
|
||||
if (!RuntimeInfo.FunctionParameters.TryGetValue(functionKey, out List<string> parameters) || parameters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuntimeInfo.FunctionCallStack.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FunctionScope scope = RuntimeInfo.FunctionCallStack.Peek();
|
||||
foreach (string parameter in parameters)
|
||||
{
|
||||
if (scope.Arguments.Count == 0)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoInArgumentInStack, true);
|
||||
return;
|
||||
}
|
||||
|
||||
scope.Variables[parameter] = scope.Arguments.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,6 +597,7 @@ public class YesNtInterpreter
|
||||
internal void PreScanLines()
|
||||
{
|
||||
runtimeInfo.BlockBoundaries.Clear();
|
||||
runtimeInfo.FunctionParameters.Clear();
|
||||
lineMatchingHandlers = new List<List<StatementHandler>>(runtimeInfo.Lines.Count);
|
||||
|
||||
// Dictionary to track open blocks by their expected end statement name
|
||||
@@ -615,6 +616,12 @@ public class YesNtInterpreter
|
||||
{
|
||||
matchingHandlers.Add(handler);
|
||||
|
||||
if (handler.Attribute.Name == "func"
|
||||
&& StatementRuntimeInformation.TryParseFunctionSignature(content["func ".Length..], out string functionName, out List<string> functionParameters))
|
||||
{
|
||||
runtimeInfo.FunctionParameters[functionName] = functionParameters;
|
||||
}
|
||||
|
||||
// Track block starts (skip intermediates — they are handled separately below)
|
||||
string blockPair = handler.Attribute.BlockPair;
|
||||
if (!string.IsNullOrEmpty(blockPair) && !handler.Attribute.IsBlockIntermediate)
|
||||
|
||||
@@ -248,6 +248,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
||||
{
|
||||
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
BindNamedParameters(key);
|
||||
|
||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||
{
|
||||
|
||||
@@ -19,17 +19,9 @@ internal class FunctionStatements : StatementRuntimeInformation
|
||||
return;
|
||||
}
|
||||
|
||||
string functionDeclaration = args.Trim();
|
||||
if (!functionDeclaration.EndsWith(':'))
|
||||
if (!TryParseFunctionSignature(args, out string key, out _))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string key = NormalizeBlockName(functionDeclaration);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
RuntimeInfo.Exit(args.Trim().Contains(':') ? ExitMessages.InvalidSyntax : ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,6 +75,7 @@ internal class FunctionStatements : StatementRuntimeInformation
|
||||
|
||||
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
BindNamedParameters(key);
|
||||
RuntimeInfo.CurrentLine = string.Empty;
|
||||
|
||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||
|
||||
Reference in New Issue
Block a user