Skip to content

Commit

Permalink
Merge pull request #262 from stevehalliwell/dyn-prop
Browse files Browse the repository at this point in the history
Merge dyn-prop into main: Add support for dynamically using fields
  • Loading branch information
stevehalliwell committed Jun 24, 2024
2 parents 62ad87d + 10b5c23 commit cffdd77
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 11 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ class MyFooAndThenSome
var matchingShape1 = anAddress meets Address(); //will be true
var matchingShape2 = anAddress meets myFoo(); //will be false

//ulox also supports the use of fields via subscript syntax.
// This allows for code that does not know the identifier ahead
// of time to be written.
var someFieldNameWeDidntKnow = "a";
myFoo[someFieldNameWeDidntKnow] = 7;

//testing is built into the language,
// they are setup like test fixtures with test cases.
// They are auto run by the vm, in an isolated inner vm.
Expand Down
66 changes: 66 additions & 0 deletions ulox/ulox.core.tests/DynamicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,71 @@ public void Dyanic_RemoveFieldWhenReadOnly_ShouldError()

StringAssert.StartsWith("Cannot remove field from read only", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_GetExists_ShouldMatch()
{
testEngine.Run(@"
var foo = { a = 1 };
var res = foo[""a""];
print(res);");

Assert.AreEqual("1", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_GetDoesNotExists_ShouldError()
{
testEngine.Run(@"
var foo = { a = 1 };
var res = foo[""b""];
print(res);");

StringAssert.StartsWith("No field of name 'b' could be found on instance", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_GetInvalidType_ShouldError()
{
testEngine.Run(@"
var foo = 7;
var res = foo[""b""];
print(res);");

StringAssert.StartsWith("Cannot perform get index on type 'Double'", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_SetExists_ShouldMatch()
{
testEngine.Run(@"
var foo = { a = 1 };
foo[""a""] = 2;
print(foo.a);");

Assert.AreEqual("2", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_SetDoesNotExists_ShouldError()
{
testEngine.Run(@"
var foo = { a = 1 };
foo[""b""] = 2;
print(foo.a);");

StringAssert.StartsWith("Attempted to create a new entry 'b' via Set.", testEngine.InterpreterResult);
}

[Test]
public void DyanicProperty_SetInvalidType_ShouldError()
{
testEngine.Run(@"
var foo = 7;
foo[""b""] = 2;
print(foo.a);");

StringAssert.StartsWith("Cannot perform set index on type 'Double'", testEngine.InterpreterResult);
}
}
}
78 changes: 78 additions & 0 deletions ulox/ulox.core.tests/JsonSerialisationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,5 +222,83 @@ public void DeserialiseViaLibrary_WhenGivenKnownArray_ShouldReturnExpectedOutput

Assert.AreEqual("", testEngine.InterpreterResult);
}

[Test]
public void SerialiseToJson_WhenComplextDataStore_ShouldBeSuccess()
{
testEngine.Run(@"
class ShipCharacter
{
var
accel = 20,
throttleAccel = 2,
maxSpeed = 12,
turnPerSecondThrust = 190,
turnPerSecondFreeSpin = 290,
gravity = 10,
dragSharpness = 0.5,
sidewaysDragSharpness = 3,
backwardDragSharpness = 1.5,
mass = 1,
fireRate = 0.5,
kickback = 1,
projectileSpeed = 10,
projectileTtl = 1,
projectileSpeedInher = 0.5,
maxHealth = 5,
invulnTime = -1,
timeTilRegen = 0,
healthRegenRate = -1,
healthRegenFreeSpinRate = -1,
shotName = ""PlayerSmallShot"",
weaponType = ""PlayerBullet"",
weaponCreator = fun(fromShip){},
pipLifetime = 20,
pipPickupRange = 1,
pipAttractRange = 15,
pipAttractForce = 20,
pipsWhenDestroyed = 0,
sizeRadius = 1,
waterDisplacement = 20,
rudderAccel = 2,
}
var enemyShipData =
{
EnemyA = ShipCharacter() update
{
fireRate = 1,
accel = 25,
maxSpeed = 14,
turnPerSecondThrust = 290,
turnPerSecondNoThrust = 380,
shotName = ""EnemySmallShot"",
weaponType = ""EnemyBullet"",
pipsWhenDestroyed = 1,
sizeRadius = 0.25,
waterDisplacement = 5,
rudderAccel = 3,
},
EnemyB = ShipCharacter() update
{
fireRate = 0.2,
accel = 15,
maxSpeed = 9,
shotName = ""EnemySmallShot"",
weaponType = ""EnemyBullet"",
projectileSpeed = 15,
projectileSpeedInher = 1,
pipsWhenDestroyed = 2,
sizeRadius = 1.5,
waterDisplacement = 10,
rudderAccel = 1,
},
};
print(Serialise.ToJson(enemyShipData));
");

StringAssert.StartsWith("{\r\n \"EnemyA\": {\r\n \"accel\": 25.0,", testEngine.InterpreterResult);
}
}
}
1 change: 0 additions & 1 deletion ulox/ulox.core.tests/SimpleStringSerialisationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using NUnit.Framework;
using System.Text.RegularExpressions;
using ULox;

namespace ULox.Core.Tests
{
Expand Down
42 changes: 33 additions & 9 deletions ulox/ulox.core/Package/Runtime/Engine/VM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,30 @@ public InterpreterResult Run()

case OpCode.GET_INDEX:
{
var (index, listValue) = Pop2OrLocals(packet.b1, packet.b2);
var (indexOrName, targetValue) = Pop2OrLocals(packet.b1, packet.b2);
var res = Value.Null();
if (listValue.val.asInstance is INativeCollection nativeCol)
if (targetValue.type == ValueType.Instance)
{
res = nativeCol.Get(index);
if (targetValue.val.asInstance is INativeCollection nativeCol)
{
res = nativeCol.Get(indexOrName);
}
else
{
var instance = targetValue.val.asInstance;
if (instance.Fields.Get(indexOrName.val.asString, out res))
{
Push(res);
}
else
{
ThrowRuntimeException($"No field of name '{indexOrName.val.asString}' could be found on instance '{instance}'");
}
}
}
else
{
ThrowRuntimeException($"Cannot perform get index on type '{listValue.type}'");
ThrowRuntimeException($"Cannot perform get index on type '{targetValue.type}'");
}

SetLocalFromB3(packet.b3, res);
Expand All @@ -502,7 +517,7 @@ public InterpreterResult Run()
case OpCode.SET_INDEX:
{
var (newValue, index, listValue) = Pop3OrLocals(packet.b1, packet.b2, packet.b3);
DoSetIndexOp(opCode, newValue, index, listValue);
DoSetIndexOp(newValue, index, listValue);
}
break;

Expand Down Expand Up @@ -671,16 +686,25 @@ private void DoTypeOfOp()
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DoSetIndexOp(OpCode opCode, Value newValue, Value index, Value listValue)
private void DoSetIndexOp(Value newValue, Value indexOrName, Value target)
{
if (listValue.val.asInstance is INativeCollection nativeCol)
if (target.type == ValueType.Instance)
{
nativeCol.Set(index, newValue);
if (target.val.asInstance is INativeCollection nativeCol)
{
nativeCol.Set(indexOrName, newValue);
}
else
{
var inst = target.val.asInstance;
inst.Fields.Set(indexOrName.val.asString, newValue);
}

Push(newValue);
return;
}

ThrowRuntimeException($"Cannot perform set index on type '{listValue.type}'");
ThrowRuntimeException($"Cannot perform set index on type '{target.type}'");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ private void WalkField(HashedString name, Value v)

break;

default:
case ValueType.Double:
case ValueType.Bool:
case ValueType.String:
case ValueType.Null:
if (name != null)
_writer.WriteNameAndValue(name.String, v);
else
_writer.WriteValue(v);

break;
default:
break;
}
}
Expand Down

0 comments on commit cffdd77

Please sign in to comment.