Skip to content

Commit

Permalink
Completed Operators (#26)
Browse files Browse the repository at this point in the history
* .

* Initial commit of testing Applying operators

* Apply Operators

* Divmod

* .

* OpConcat

* Finish opsubstr

* Added OpStrLen

* Added further tests

* Added OpSha256

* Added OpGr

* added negative tests for gr of more than 2 arguments and less than 2 arguments

* Added OpEq

* Added OpAsh & OpLsh - without tests

* Added OpAsh

* Oplsh

* Partial fix for dynamic int sizing IntToBytes()

* .

* Fixed int to bytes

* added OpDefaultUnknown

* Changed Sexp to use List<> rather than [] because python [] and () are handled differently. OpAny added

* Added OpAll

* Added OpLogand

* Added OpLogior

* Added OpLogxor

* Added LogNot

* Added OpPubKeyForExp

* OpPubkeyForExp

* OpGrBytes

* Added KEYWORD_FROM_ATOM && KEYWORD_TO_ATOM

* Added OpDefaultUnknownTests

* .

* Removed warnings on tests when doing implicit conversions between int and bigint

---------

Co-authored-by: kev <kev@darkhorse>
  • Loading branch information
KevinOnFrontEnd and kev authored Jan 23, 2024
1 parent 20cfa46 commit 895a161
Show file tree
Hide file tree
Showing 31 changed files with 1,738 additions and 510 deletions.
2 changes: 1 addition & 1 deletion CLVMDotNet/src/CLVM/CLVMObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public CLVMObject(dynamic? v)
//to make sure if a tuple is used, it cannot have more than 2 items in it.
var type = v?.GetType();
var s = type?.GetGenericArguments();
if (s.Length > 2)
if (s?.Length > 2)
{
throw new ArgumentException("tuples must be of size 2");
}
Expand Down
89 changes: 78 additions & 11 deletions CLVMDotNet/src/CLVM/Casts.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Net.Sockets;
using System.Numerics;

namespace CLVMDotNet.CLVM
Expand All @@ -15,35 +16,101 @@ public static BigInteger IntFromBytes(byte[] blob)
return new BigInteger(blob, isBigEndian: true);
}

/// <summary>
/// In python integers are dynamically sized, so working out the number of bytes required in
/// c# needs to first see if the number will fit into a number of datatypes.
///
/// 0-255 (byte)
///
///
/// This is required to see if the biginteger parameter is able to fit into a smaller
/// datatype and converting them to bytes.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static byte[] IntToBytes(BigInteger v)
{
byte[] byteArray = v.ToByteArray();
if (v == 0)
{
byte[] emptyByteArray = new byte[0];
return emptyByteArray;
}

//v can fit into a byte 0-255 (unsigned)
if (v >= byte.MinValue && v <= byte.MaxValue)
{
var intValue = (byte)v;
var b = (byte)0x00;
if (v < 128)
{
byte[] byteArray = new[] { intValue };
return byteArray;
}
else
{
byte[] byteArray = new[] { b,intValue };
return byteArray;
}
}

if (BitConverter.IsLittleEndian)
//v can fit into an sbyte -128 to 127 (signed)
if (v >= sbyte.MinValue && v <= sbyte.MaxValue)
{
byteArray = byteArray.Reverse().ToArray();
sbyte sbyteValue = (sbyte)v;
byte byteValue = (byte)sbyteValue;
byte[] byteArray = new[] { byteValue };
return byteArray;
}

while (byteArray.Length > 1 && (byteArray[0] == 0xFF || byteArray[0] == 0x00))
//v can fit into a short -32,768 to 32,767 (signed)
if (v >= short.MinValue && v <= short.MaxValue)
{
byteArray = byteArray.Skip(1).ToArray();
short shortValue = (short)v;
byte[] byteArray = BitConverter.GetBytes(shortValue);
Array.Reverse(byteArray);
return byteArray;
}

if (!v.IsZero)
//v can fit into a long -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
if (v >= long.MinValue && v <= long.MaxValue)
{
if (byteArray[0] >= 0x80)
if (v == 0)
{
byteArray = new byte[] { 0 }.Concat(byteArray).ToArray();
return new byte[0];
}

byte[] result = v.ToByteArray();

// if (result[0] == 0x00)
// {
// // Remove leading 0x00 byte if present
// byte[] minimalResult = new byte[result.Length - 1];
// Array.Copy(result, 1, minimalResult, 0, minimalResult.Length);
// return minimalResult;
// }
result = result.Reverse().ToArray();
return result;
}
//python equivalent of numbers larger than a long is a bigInteger
else
{
byteArray = new byte[0];
}
byte[] byteArray = v.ToByteArray();

if (BitConverter.IsLittleEndian)
{
byteArray = byteArray.Reverse().ToArray();
}

return byteArray;
while (byteArray.Length > 1 && (byteArray[0] == 0xFF || byteArray[0] == 0x00))
{
byteArray = byteArray.Skip(1).ToArray();
}

return byteArray;
}
}


public static int LimbsForInt(BigInteger v)
{
return IntToBytes(v).Length;
Expand Down
145 changes: 129 additions & 16 deletions CLVMDotNet/src/CLVM/CoreOps.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,121 @@
using System.Numerics;

namespace CLVMDotNet.CLVM
{
public class CoreOps
{
public static Tuple<int, SExp> OpIf(SExp args)

public static IEnumerable<int> ArgsLen(string opName, SExp args)
{
foreach (var arg in args.AsIter())
{
if (arg.Pair != null)
{
throw new EvalError(string.Format("{0} requires int args", opName), arg);
}
yield return arg.AsAtom().Length;
}
}

public static Tuple<BigInteger, SExp> OpDefaultUnknown(byte[] op, SExp args)
{
// Any opcode starting with 0xFFFF is reserved (i.e., fatal error).
// Opcodes are not allowed to be empty.
if (op.Length == 0 || (op[0] == 0xFF && op[1] == 0xFF))
{
throw new EvalError("reserved operator", SExp.To(op));
}

// All other unknown opcodes are no-ops.
// The cost of the no-ops is determined by the opcode number, except the
// 6 least significant bits.

int costFunction = (op[op.Length - 1] & 0b11000000) >> 6;
// The multiplier cannot be 0. It starts at 1.

if (op.Length > 5)
{
throw new EvalError("invalid operator", SExp.To(op));
}

BigInteger costMultiplier = new BigInteger(op.Take(op.Length - 1).ToArray()) + 1;

// 0 = constant
// 1 = like op_add/op_sub
// 2 = like op_multiply
// 3 = like op_concat
BigInteger cost;
switch (costFunction)
{
case 0:
cost = 1;
break;
case 1:
// Like op_add
cost = Costs.ARITH_BASE_COST;
int argSize = 0;
foreach (int l in ArgsLen("unknown op", args))
{
argSize += l;
cost += Costs.ARITH_COST_PER_ARG;
}

cost += argSize * Costs.ARITH_COST_PER_BYTE;
break;
case 2:
// Like op_multiply
cost = Costs.MUL_BASE_COST;
var operands = ArgsLen("unknown op", args).GetEnumerator();
try
{
int vs = operands.MoveNext() ? operands.Current : 0;
while (operands.MoveNext())
{
int rs = operands.Current;
cost += Costs.MUL_COST_PER_OP;
cost += (rs + vs) * Costs.MUL_LINEAR_COST_PER_BYTE;
cost += (rs * vs) / Costs.MUL_SQUARE_COST_PER_BYTE_DIVIDER;
// This is an estimate, since we don't want to actually multiply the values.
vs += rs;
}
}
catch (Exception)
{
// Handle StopIteration
}

break;
case 3:
// Like concat
cost = Costs.CONCAT_BASE_COST;
int length = 0;
foreach (var arg in args.AsIter())
{
if (arg.Pair != null)
{
throw new EvalError("unknown op on list", arg);
}

cost += Costs.CONCAT_COST_PER_ARG;
length += arg.Atom.Length;
}

cost += length * Costs.CONCAT_COST_PER_BYTE;
break;
default:
throw new EvalError("invalid operator", SExp.To(op));
}

cost *= (int)costMultiplier;
if (cost >= (BigInteger)1 << 32)
{
throw new EvalError("invalid operator", SExp.To(op));
}

return Tuple.Create(cost, SExp.NULL);
}

public static Tuple<BigInteger, SExp> OpIf(SExp args)
{
if (args.ListLength() != 3)
{
Expand All @@ -12,53 +125,53 @@ public static Tuple<int, SExp> OpIf(SExp args)
SExp r = args.Rest();
if (args.First().Nullp())
{
return new Tuple<int, SExp>(Costs.IF_COST, r.Rest().First());
return new Tuple<BigInteger, SExp>(Costs.IF_COST, r.Rest().First());
}

return new Tuple<int, SExp>(Costs.IF_COST, r.First());
return new Tuple<BigInteger, SExp>(Costs.IF_COST, r.First());
}

public static Tuple<int, SExp> OpCons(SExp args)
public static Tuple<BigInteger, SExp> OpCons(SExp args)
{
if (args.ListLength() != 2)
{
throw new EvalError("c takes exactly 2 arguments", args);
}

return new Tuple<int, SExp>(Costs.CONS_COST, args.First().Cons(args.Rest().First()));
return new Tuple<BigInteger, SExp>(Costs.CONS_COST, args.First().Cons(args.Rest().First()));
}

public static Tuple<int, SExp> OpFirst(SExp args)
public static Tuple<BigInteger, SExp> OpFirst(SExp args)
{
if (args.ListLength() != 1)
{
throw new EvalError("f takes exactly 1 argument", args);
}

return new Tuple<int, SExp>(Costs.FIRST_COST, args.First().First());
return new Tuple<BigInteger, SExp>(Costs.FIRST_COST, args.First().First());
}

public static Tuple<int, SExp> OpRest(SExp args)
public static Tuple<BigInteger, SExp> OpRest(SExp args)
{
if (args.ListLength() != 1)
{
throw new EvalError("r takes exactly 1 argument", args);
}

return new Tuple<int, SExp>(Costs.REST_COST, args.First().Rest());
return new Tuple<BigInteger, SExp>(Costs.REST_COST, args.First().Rest());
}

public static Tuple<int, SExp> OpListp(SExp args)
public static Tuple<BigInteger, SExp> OpListp(SExp args)
{
if (args.ListLength() != 1)
{
throw new EvalError("l takes exactly 1 argument", args);
}

return new Tuple<int, SExp>(Costs.LISTP_COST, args.First().Listp() ? SExp.True : SExp.False);
return new Tuple<BigInteger, SExp>(Costs.LISTP_COST, args.First().Listp() ? SExp.True : SExp.False);
}

public static Tuple<int, SExp> OpRaise(SExp args)
public static Tuple<BigInteger, SExp> OpRaise(SExp args)
{
if (args.ListLength() == 1 && !args.First().Listp())
{
Expand All @@ -70,7 +183,7 @@ public static Tuple<int, SExp> OpRaise(SExp args)
}
}

public static Tuple<int, SExp> OpEq(SExp args)
public static Tuple<BigInteger, SExp> OpEq(SExp args)
{
if (args.ListLength() != 2)
{
Expand All @@ -88,10 +201,10 @@ public static Tuple<int, SExp> OpEq(SExp args)
byte[] b0 = a0.AsAtom();
byte[] b1 = a1.AsAtom();

int cost = Costs.EQ_BASE_COST;
cost += (b0.Length + b1.Length) * Costs.EQ_COST_PER_BYTE;
BigInteger cost = Costs.EQ_BASE_COST;
cost += (b0!.Length + b1!.Length) * Costs.EQ_COST_PER_BYTE;

return new Tuple<int, SExp>(cost, b0.Equals(b1) ? SExp.True : SExp.False);
return Tuple.Create(cost, b0.SequenceEqual(b1) ? SExp.True : SExp.False);
}
}
}
8 changes: 7 additions & 1 deletion CLVMDotNet/src/CLVM/HelperFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ private static bool IsTuple(dynamic? obj)
continue;
}

else if (value != null && value.GetType().IsArray && value is not byte[])
if (value is System.Collections.IList list &&
list.GetType().IsGenericType &&
list.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
target = stack.Count;
stack.Add(new CLVMObject(nullBytes));
Expand Down Expand Up @@ -205,6 +207,10 @@ public static byte[] ConvertAtomToBytes(dynamic? v)
{
return Encoding.UTF8.GetBytes(str);
}
if (v is string[] strarray && strarray.Length == 1)
{
return Encoding.UTF8.GetBytes(strarray[0]);
}

if (v is int intValue)
{
Expand Down
Loading

0 comments on commit 895a161

Please sign in to comment.