Skip to content

Commit d790573

Browse files
committed
Fix GS2 array operations and string function dispatch
Fixed: - Consume subarray operands in native bytecode order. - Return matching positions from the indices opcode using script value equality. Changed: - Align tests with global bare variables, function-local temp variables, and single-equals comparisons in conditions. Added: - Regression coverage for slice boundaries, slice independence, and exact string matches. Removed: - Global string-method registrations that shadowed script helpers and receiver dispatch. Validation: 450 passed, 0 failed, 8 existing skips.
1 parent 0b061c7 commit d790573

4 files changed

Lines changed: 90 additions & 64 deletions

File tree

‎Preagonal.Scripting.GS2Engine.UnitTests/ScriptMachineTests.cs‎

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3312,57 +3312,58 @@ function onCreated() {
33123312
}
33133313

33143314
[Fact]
3315-
public async Task Given_assignment_expression_When_compared_Then_assigned_value_is_used()
3315+
public async Task Given_single_equals_in_nested_condition_When_compared_Then_variable_is_not_assigned()
33163316
{
33173317
//Arrange
33183318
const string scriptText = """
3319-
//#CLIENTSIDE
3320-
function onCreated() {
3321-
temp.found = -1;
3319+
//#CLIENTSIDE
3320+
function onCreated() {
3321+
temp.found = -1;
33223322
3323-
if ((temp.found = 3) != -1) {
3324-
return temp.found;
3325-
}
3323+
if ((temp.found = 3) != -1) {
3324+
return temp.found;
3325+
}
33263326
3327-
return 0;
3328-
}
3329-
""";
3327+
return 0;
3328+
}
3329+
""";
33303330
var script = CompileScript(scriptText);
33313331

33323332
//Act
33333333
var result = await script.Call("onCreated");
33343334

33353335
//Assert
3336-
Assert.Equal(3.0d, result.GetValue<double>());
3336+
Assert.Equal(-1.0d, result.GetValue<double>());
33373337
}
33383338

33393339
[Fact]
3340-
public async Task Given_assignment_expression_in_while_When_condition_is_false_Then_loop_exits()
3340+
public async Task Given_single_equals_in_while_When_variable_changes_Then_comparison_ends_loop()
33413341
{
33423342
//Arrange
33433343
const string scriptText = """
3344-
//#CLIENTSIDE
3345-
function onCreated() {
3346-
temp.i = 0;
3347-
temp.count = 0;
3348-
3349-
while ((temp.i = temp.i + 1) != 3) {
3350-
temp.count++;
3351-
if (temp.count > 5) {
3352-
return -99;
3353-
}
3354-
}
3344+
//#CLIENTSIDE
3345+
function onCreated() {
3346+
temp.i = 0;
3347+
temp.count = 0;
3348+
3349+
while (temp.i = 0) {
3350+
temp.count++;
3351+
temp.i = 1;
3352+
if (temp.count > 5) {
3353+
return -99;
3354+
}
3355+
}
33553356
3356-
return temp.count;
3357-
}
3358-
""";
3357+
return temp.count;
3358+
}
3359+
""";
33593360
var script = CompileScript(scriptText);
33603361

33613362
//Act
33623363
var result = await script.Call("onCreated");
33633364

33643365
//Assert
3365-
Assert.Equal(2.0d, result.GetValue<double>());
3366+
Assert.Equal(1.0d, result.GetValue<double>());
33663367
}
33673368

33683369
[Fact]
@@ -5815,7 +5816,7 @@ function second() {
58155816
}
58165817

58175818
[Fact]
5818-
public async Task Given_local_value_in_previous_call_When_reading_name_in_another_function_Then_value_is_not_reused()
5819+
public async Task Given_global_value_in_previous_call_When_reading_name_in_another_function_Then_value_is_preserved()
58195820
{
58205821
const string scriptText = """
58215822
function first() {
@@ -5831,7 +5832,7 @@ function second() {
58315832
await script.Call("first");
58325833
var result = await script.Call("second");
58335834

5834-
Assert.Equal(0, result.GetValue<double>());
5835+
Assert.Equal(3, result.GetValue<double>());
58355836
}
58365837

58375838
[Fact]
@@ -5902,24 +5903,24 @@ public async Task Given_nested_function_uses_same_temp_name_When_returning_to_ca
59025903
{
59035904
//Arrange
59045905
const string scriptText = """
5905-
//#CLIENTSIDE
5906-
function helper() {
5907-
for (i = 0; i < 25; i++) {
5908-
}
5909-
}
5906+
//#CLIENTSIDE
5907+
function helper() {
5908+
for (temp.i = 0; temp.i < 25; temp.i++) {
5909+
}
5910+
}
59105911
5911-
function onCreated() {
5912-
temp.rows = {"a", "b", "c"};
5913-
temp.count = 0;
5912+
function onCreated() {
5913+
temp.rows = {"a", "b", "c"};
5914+
temp.count = 0;
59145915
5915-
for (i = 0; i < temp.rows.size(); i++) {
5916-
helper();
5917-
temp.count++;
5918-
}
5916+
for (temp.i = 0; temp.i < temp.rows.size(); temp.i++) {
5917+
helper();
5918+
temp.count++;
5919+
}
59195920
5920-
return temp.count;
5921-
}
5922-
""";
5921+
return temp.count;
5922+
}
5923+
""";
59235924
var script = CompileScript(scriptText);
59245925

59255926
//Act
@@ -7528,4 +7529,4 @@ public ReceiverPropertyScriptProperties() : base(typeof(Script))
75287529
Compile();
75297530
}
75307531
}
7531-
}
7532+
}

‎Preagonal.Scripting.GS2Engine.UnitTests/ScriptVariableStandardFunctionTests.cs‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ public async Task Call_Given_differently_cased_strings_When_indices_is_called_Th
218218
Assert.Empty(result.GetValue<List<object?>>()!);
219219
}
220220

221+
[Fact]
222+
public async Task Call_Given_repeated_strings_When_indices_is_called_Then_returns_only_exact_matches()
223+
{
224+
var script = CompileScript("temp.values = {\"One\", \"one\", \"One\"}; return temp.values.indices(\"One\");");
225+
226+
var result = await script.Call("onCreated");
227+
228+
Assert.Equal([0.0d, 2.0d], result.GetValue<List<object?>>());
229+
}
230+
221231
[Fact]
222232
public async Task Call_Given_padded_string_When_trim_is_called_Then_removes_outer_whitespace()
223233
{
@@ -465,6 +475,32 @@ public async Task Call_Given_only_start_When_subarray_is_called_Then_returns_rem
465475
Assert.Equal([2.0d, 3.0d], result.GetValue<List<object?>>());
466476
}
467477

478+
[Fact]
479+
public async Task Call_Given_subarray_When_modifying_slice_Then_source_array_is_unchanged()
480+
{
481+
var script = CompileScript("temp.values = {1, 2, 3}; temp.slice = temp.values.subarray(1, 1); temp.slice[0] = 9; return temp.values;");
482+
483+
var result = await script.Call("onCreated");
484+
485+
Assert.Equal([1.0d, 2.0d, 3.0d], result.GetValue<List<object?>>());
486+
}
487+
488+
[Theory]
489+
[InlineData("0, 0", new double[] { })]
490+
[InlineData("-1, 2", new double[] { 1, 2 })]
491+
[InlineData("1, -1", new double[] { 2, 3 })]
492+
[InlineData("1, 20", new double[] { 2, 3 })]
493+
[InlineData("3, 1", new double[] { })]
494+
[InlineData("20, 1", new double[] { })]
495+
public async Task Call_Given_slice_bounds_When_subarray_is_called_Then_clamps_to_array(string arguments, double[] expected)
496+
{
497+
var script = CompileScript($"temp.values = {{1, 2, 3}}; return temp.values.subarray({arguments});");
498+
499+
var result = await script.Call("onCreated");
500+
501+
Assert.Equal(expected.Cast<object?>(), result.GetValue<List<object?>>());
502+
}
503+
468504
[Fact]
469505
public async Task Call_Given_missing_value_When_index_is_called_Then_returns_negative_one()
470506
{
@@ -487,4 +523,4 @@ private static Script CompileScript(string body)
487523
var scriptManager = new ScriptManager(new FakeLogger<ScriptManager>());
488524
return new(scriptManager, "script-variable-standard-functions", compilation.ByteCode);
489525
}
490-
}
526+
}

‎Preagonal.Scripting.GS2Engine/GS2/Script/ScriptMachine.cs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,13 +1106,15 @@ out var command
11061106
stack.Push(vecyVal.ToStackEntry());
11071107
break;
11081108
case Opcode.OP_OBJ_INDICES:
1109+
var indicesNeedle = GetEntry(PopOrZero(), returnStackEntryIfNotFound: true).GetValue();
11091110
var indicesTarget = GetEntry(PopOrZero(), returnStackEntryIfNotFound: true);
11101111
var indicesValues = GetArrayValues(indicesTarget.GetValue());
11111112
var indices = new List<object?>();
11121113
if (indicesValues != null)
11131114
{
11141115
for (var i = 0; i < indicesValues.Count; i++)
1115-
indices.Add((double)i);
1116+
if (ScriptValuesEqual(indicesValues[i], indicesNeedle))
1117+
indices.Add((double)i);
11161118
}
11171119

11181120
stack.Push(indices.ToStackEntry());
@@ -1228,9 +1230,9 @@ out var command
12281230
SetScriptArrayCell2(multiArrayAssignTarget, multiArrayAssignX, multiArrayAssignY, multiArrayValue);
12291231
break;
12301232
case Opcode.OP_OBJ_SUBARRAY:
1233+
var subArrayLength = ToScriptInt(GetEntry(stack.Pop()).GetValue<double>());
1234+
var subArrayStart = ToScriptInt(GetEntry(stack.Pop()).GetValue<double>());
12311235
var subArrayTarget = GetEntry(stack.Pop(), returnStackEntryIfNotFound: true).GetValue();
1232-
var subArrayStart = stack.Count > 0 ? ToScriptInt(GetEntry(stack.Pop()).GetValue<double>()) : 0;
1233-
var subArrayLength = stack.Count > 0 ? ToScriptInt(GetEntry(stack.Pop()).GetValue<double>()) : -1;
12341236
stack.Push(GetScriptSubArray(subArrayTarget, subArrayStart, subArrayLength).ToStackEntry());
12351237
break;
12361238
case Opcode.OP_OBJ_ADDSTRING:
@@ -2721,4 +2723,4 @@ private sealed class ExecutionState
27212723
public bool UseTemp { get; set; }
27222724
public string ActiveEvent { get; set; } = string.Empty;
27232725
}
2724-
}
2726+
}

‎Preagonal.Scripting.GS2Engine/Models/TStringProperties.cs‎

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,6 @@ public class TStringProperties : ScriptProperties<TString>
44
{
55
public TStringProperties() : base(null)
66
{
7-
AddFunctions(
8-
null,
9-
new()
10-
{
11-
{ "lower", "Returns the string converted to lower case.", (_, args) => args.Length > 0 ? Tools.ToScriptString(args[0].GetValue()).ToString().ToLowerInvariant() : string.Empty, [] },
12-
{ "lowercase", "Returns the string converted to lower case.", (_, args) => args.Length > 0 ? Tools.ToScriptString(args[0].GetValue()).ToString().ToLowerInvariant() : string.Empty, [] },
13-
{ "upper", "Returns the string converted to upper case.", (_, args) => args.Length > 0 ? Tools.ToScriptString(args[0].GetValue()).ToString().ToUpperInvariant() : string.Empty, [] },
14-
{ "uppercase", "Returns the string converted to upper case.", (_, args) => args.Length > 0 ? Tools.ToScriptString(args[0].GetValue()).ToString().ToUpperInvariant() : string.Empty, [] },
15-
{ "replace", "Returns the string with every matching substring replaced.", ReplaceAll, [new("search", typeof(string)), new("replacement", typeof(string))] },
16-
{ "replaceall", "Returns the string with every matching substring replaced.", ReplaceAll, [new("search", typeof(string)), new("replacement", typeof(string))] }
17-
}
18-
);
19-
207
AddFunctions(
218
this,
229
new()
@@ -40,4 +27,4 @@ private static string ReplaceAll(TString value, IStackEntry[] args)
4027
var newValue = args.Length > 1 ? Tools.ToScriptString(args[1].GetValue()) : string.Empty;
4128
return oldValue.Length == 0 ? text : text.Replace(oldValue, newValue, System.StringComparison.Ordinal);
4229
}
43-
}
30+
}

0 commit comments

Comments
 (0)