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}!
|
print_line Hello, ${name}!
|
||||||
end_func
|
end_func
|
||||||
|
|
||||||
func add:
|
func add: a, b
|
||||||
var result = %in + %in calc
|
return ${a} + ${b} calc
|
||||||
return ${result}
|
|
||||||
|
|
||||||
call greet with Alice
|
call greet with Alice
|
||||||
call greet with Bob
|
call greet with Bob
|
||||||
|
|||||||
+3
-1
@@ -78,7 +78,7 @@ print_line ${value}
|
|||||||
| Code flow | `lbl name` | `label name:` | Label declaration/target. |
|
| Code flow | `lbl name` | `label name:` | Label declaration/target. |
|
||||||
| Code flow | `jmp name` | `goto name` | Unconditional jump. |
|
| Code flow | `jmp name` | `goto name` | Unconditional jump. |
|
||||||
| Code flow | `jif name \| cond` | `if cond goto name` | Conditional 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` | `call name` | Function call without args. |
|
||||||
| Functions | `cal name \| a,b,c` | `call name with a, b, c` | Function call with 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. |
|
| 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`.
|
- Postfix operations are `calc`, `eval`, and `task`.
|
||||||
- `return` exits the function early (optionally with a value via `return <value>`);
|
- `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.
|
`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
|
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`
|
### Calling a function - `call`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ internal class TextEditor
|
|||||||
if (!isTerminatingStatement && (
|
if (!isTerminatingStatement && (
|
||||||
(trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
(trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||||
|| (trimmed.StartsWith("while ", 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:"))
|
|| trimmed == "else:"))
|
||||||
{
|
{
|
||||||
if (trimmed.StartsWith("if ", StringComparison.Ordinal))
|
if (trimmed.StartsWith("if ", StringComparison.Ordinal))
|
||||||
|
|||||||
@@ -320,4 +320,104 @@ public class FunctionStatementsTests
|
|||||||
|
|
||||||
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
|
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, string> GlobalVariables { get; set; } = [];
|
||||||
public Dictionary<string, int> Functions { get; } = [];
|
public Dictionary<string, int> Functions { get; } = [];
|
||||||
|
public Dictionary<string, List<string>> FunctionParameters { get; } = [];
|
||||||
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
||||||
internal Action PreScanLinesAction { get; set; }
|
internal Action PreScanLinesAction { get; set; }
|
||||||
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
||||||
@@ -176,6 +177,7 @@ internal sealed class RuntimeInformation : IStatementContext
|
|||||||
GlobalVariables.Clear();
|
GlobalVariables.Clear();
|
||||||
Labels.Clear();
|
Labels.Clear();
|
||||||
Functions.Clear();
|
Functions.Clear();
|
||||||
|
FunctionParameters.Clear();
|
||||||
BlockBoundaries.Clear();
|
BlockBoundaries.Clear();
|
||||||
FunctionCallStack.Clear();
|
FunctionCallStack.Clear();
|
||||||
InParametersStack.Clear();
|
InParametersStack.Clear();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace YesNt.Interpreter.Runtime;
|
namespace YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for all classes that host statement handler methods.
|
/// Base class for all classes that host statement handler methods.
|
||||||
/// Subclasses declare methods decorated with <see cref="Attributes.StatementAttribute"/> or
|
/// Subclasses declare methods decorated with <see cref="Attributes.StatementAttribute"/> or
|
||||||
@@ -21,4 +24,78 @@ internal abstract class StatementRuntimeInformation
|
|||||||
{
|
{
|
||||||
return value.Trim().TrimEnd(':').Trim();
|
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()
|
internal void PreScanLines()
|
||||||
{
|
{
|
||||||
runtimeInfo.BlockBoundaries.Clear();
|
runtimeInfo.BlockBoundaries.Clear();
|
||||||
|
runtimeInfo.FunctionParameters.Clear();
|
||||||
lineMatchingHandlers = new List<List<StatementHandler>>(runtimeInfo.Lines.Count);
|
lineMatchingHandlers = new List<List<StatementHandler>>(runtimeInfo.Lines.Count);
|
||||||
|
|
||||||
// Dictionary to track open blocks by their expected end statement name
|
// Dictionary to track open blocks by their expected end statement name
|
||||||
@@ -615,6 +616,12 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
matchingHandlers.Add(handler);
|
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)
|
// Track block starts (skip intermediates — they are handled separately below)
|
||||||
string blockPair = handler.Attribute.BlockPair;
|
string blockPair = handler.Attribute.BlockPair;
|
||||||
if (!string.IsNullOrEmpty(blockPair) && !handler.Attribute.IsBlockIntermediate)
|
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.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||||
RuntimeInfo.InParametersStack.Clear();
|
RuntimeInfo.InParametersStack.Clear();
|
||||||
|
BindNamedParameters(key);
|
||||||
|
|
||||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,17 +19,9 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string functionDeclaration = args.Trim();
|
if (!TryParseFunctionSignature(args, out string key, out _))
|
||||||
if (!functionDeclaration.EndsWith(':'))
|
|
||||||
{
|
{
|
||||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
RuntimeInfo.Exit(args.Trim().Contains(':') ? ExitMessages.InvalidSyntax : ExitMessages.InvalidSyntaxColonRequired, true);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string key = NormalizeBlockName(functionDeclaration);
|
|
||||||
if (string.IsNullOrWhiteSpace(key))
|
|
||||||
{
|
|
||||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +75,7 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
|
|
||||||
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||||
RuntimeInfo.InParametersStack.Clear();
|
RuntimeInfo.InParametersStack.Clear();
|
||||||
|
BindNamedParameters(key);
|
||||||
RuntimeInfo.CurrentLine = string.Empty;
|
RuntimeInfo.CurrentLine = string.Empty;
|
||||||
|
|
||||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||||
|
|||||||
Reference in New Issue
Block a user