diff --git a/CLVMDotNet/src/CLVM/HelperFunctions.cs b/CLVMDotNet/src/CLVM/HelperFunctions.cs index df62a6b..a56eb35 100644 --- a/CLVMDotNet/src/CLVM/HelperFunctions.cs +++ b/CLVMDotNet/src/CLVM/HelperFunctions.cs @@ -8,16 +8,111 @@ namespace CLVMDotNet.CLVM public static class HelperFunctions { private static byte[] nullBytes = new byte[0]; + + public static string PrintLeaves(SExp tree) + { + var a = tree.AsAtom(); + if (a != null) + { + if (a.Length == 0) + return "() "; + + return $"{a[0]} "; + } + + var ret = ""; + var pairs = tree.AsPair(); + var list = new List() { pairs.Item1, pairs.Item2 }; + if (pairs != null) + { + foreach (SExp i in list) + { + ret += PrintLeaves(i); + } + } + + return ret; + } + + public static string PrintTree(SExp tree) + { + var a = tree.AsAtom(); + if (a != null) + { + if (a.Length == 0) + { + return "() "; + } + + return $"{a[0]} "; + } + + var ret = "("; + var pairs = tree.AsPair(); + var list = new List() { pairs!.Item1, pairs.Item2 }; + if (pairs != null) + { + foreach (var i in list) + { + ret += PrintTree(i); + } + } + + ret += ")"; + return ret; + } + + public static void ValidateSExp(SExp sexp) + { + Stack validateStack = new Stack(); + validateStack.Push(sexp); + + while (validateStack.Count > 0) + { + dynamic v = validateStack.Pop(); + + if (!(v is SExp)) + { + throw new InvalidOperationException("v is not an instance of SExp"); + } + + if (v.Pair != null) + { + if (v.Pair.GetType() != typeof(Tuple)) + { + throw new InvalidOperationException("v.pair is not a Tuple"); + } + + Tuple pair = v.Pair; + + if (!HelperFunctions.LooksLikeCLVMObject(pair.Item1) || + !HelperFunctions.LooksLikeCLVMObject(pair.Item2)) + { + throw new InvalidOperationException("One or both elements do not look like CLVM objects"); + } + + var sPair = v.AsPair(); + validateStack.Push(sPair.Item1); + validateStack.Push(sPair.Item2); + } + else + { + if (!(v.Atom is byte[])) + { + throw new InvalidOperationException("v.atom is not a byte array"); + } + } + } + } private static bool IsTuple(dynamic? obj) { var isTuple = false; Type type = obj?.GetType(); - - + + if (type != null) { - if (!isTuple) isTuple = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Tuple<,>); @@ -49,6 +144,7 @@ private static bool IsTuple(dynamic? obj) } } } + return null; } @@ -73,6 +169,7 @@ private static bool IsTuple(dynamic? obj) } } } + return null; } @@ -126,8 +223,8 @@ private static bool IsTuple(dynamic? obj) } if (value is System.Collections.IList list && - list.GetType().IsGenericType && - list.GetType().GetGenericTypeDefinition() == typeof(List<>)) + list.GetType().IsGenericType && + list.GetType().GetGenericTypeDefinition() == typeof(List<>)) { target = stack.Count; stack.Add(new CLVMObject(nullBytes)); @@ -157,19 +254,18 @@ private static bool IsTuple(dynamic? obj) var leftValue = new CLVMObject(stack[stack.Count - 1]); stack.RemoveAt(stack.Count - 1); var rightValue = stack[target].Pair.Item2; - stack[target].Pair = Tuple.Create(leftValue, rightValue); + stack[target].Pair = Tuple.Create(leftValue, rightValue); continue; } if (op == 2) // set right { - var leftValue = stack[target].Pair.Item1; var right = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); var rightValue = new CLVMObject(right); - stack[target].Pair = Tuple.Create(leftValue, rightValue); + stack[target].Pair = Tuple.Create(leftValue, rightValue); continue; } @@ -207,6 +303,7 @@ 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]); diff --git a/CLVMDotNet/src/CLVM/Keywords.cs b/CLVMDotNet/src/CLVM/Keywords.cs deleted file mode 100644 index 60d4c31..0000000 --- a/CLVMDotNet/src/CLVM/Keywords.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace CLVMDotNet.CLVM -{ - - public static class Keywords - { - - public static string[] KEYWORDS = new string[] - { - // core opcodes 0x01-x08 - ". q a i c f r l x ", - - // opcodes on atoms as strings 0x09-0x0f - "= >s sha256 substr strlen concat . ", - - // opcodes on atoms as ints 0x10-0x17 - "+ - * / divmod > ash lsh ", - - // opcodes on atoms as vectors of bools 0x18-0x1c - "logand logior logxor lognot . ", - - // opcodes for bls 1381 0x1d-0x1f - "point_add pubkey_for_exp . ", - - // bool opcodes 0x20-0x23 - "not any all . ", - - // misc 0x24 - "softfork " - }; - - public static Dictionary KEYWORD_FROM_ATOM => InitializeKeywordFromAtom(); - public static Dictionary KEYWORD_TO_ATOM => InitializeKeywordToAtom(); - - private static Dictionary InitializeKeywordFromAtom() - { - var keywordFromAtom = new Dictionary(); - - for (int k = 0; k < KEYWORDS.Length; k++) - { - byte[] keyBytes = BitConverter.GetBytes(k); - keywordFromAtom[keyBytes] = KEYWORDS[k]; - } - - return keywordFromAtom; - } - - private static Dictionary InitializeKeywordToAtom() - { - var keywordToAtom = new Dictionary(); - - for (int k = 0; k < KEYWORDS.Length; k++) - { - keywordToAtom[KEYWORDS[k]] = BitConverter.GetBytes(k); - } - - return keywordToAtom; - } - } -} \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/MoreOps.cs b/CLVMDotNet/src/CLVM/MoreOps.cs index b616732..1a540dc 100644 --- a/CLVMDotNet/src/CLVM/MoreOps.cs +++ b/CLVMDotNet/src/CLVM/MoreOps.cs @@ -269,7 +269,7 @@ public static Tuple OpGrBytes(SExp args) var b0 = a0.AsAtom(); var b1 = a1.AsAtom(); BigInteger cost = Costs.GRS_BASE_COST; - cost += (b0.Length + b1.Length) * Costs.GRS_COST_PER_BYTE; + cost += (b0!.Length + b1!.Length) * Costs.GRS_COST_PER_BYTE; int comparisonResult = b0.AsSpan().SequenceCompareTo(b1.AsSpan()); @@ -358,7 +358,7 @@ public static Tuple OpStrlen(SExp args) throw new EvalError("strlen on list", a0); } - int size = a0.AsAtom().Length; + int size = a0.AsAtom()!.Length; BigInteger cost = Costs.STRLEN_BASE_COST + size * Costs.STRLEN_COST_PER_BYTE; return MallocCost(cost, SExp.To(size)); } diff --git a/CLVMDotNet/src/CLVM/OperatorDict.cs b/CLVMDotNet/src/CLVM/OperatorDict.cs new file mode 100644 index 0000000..1f615aa --- /dev/null +++ b/CLVMDotNet/src/CLVM/OperatorDict.cs @@ -0,0 +1,207 @@ +using System.Numerics; + +namespace CLVMDotNet.CLVM; + +public class OperatorDict +{ + public delegate Tuple DictDelegate(byte[] op, SExp sexp); + public DictDelegate? UnknownOpHandler; + private Dictionary OpDictionary = new Dictionary(); + + public byte[] QuoteAtom { get; set; } = new byte[0]; + public byte[] ApplyAtom { get; set; } = new byte[0]; + + public OperatorDict(OperatorDict d, Dictionary? args, DictDelegate? unknownOp = null) + { + // Set quote_atom and apply_atom properties using kwargs or defaults from d + this.QuoteAtom = args.ContainsKey("quote") ? (byte[])args["quote"] : d.QuoteAtom; + this.ApplyAtom = args.ContainsKey("apply") ? (byte[])args["apply"] : d.ApplyAtom; + OpDictionary = d.OpDictionary; + + // Set unknown_op_handler property using kwargs or default + this.UnknownOpHandler = unknownOp ?? DefaultUnknownOp; + } + + public OperatorDict() + { + } + + public static OperatorDict OPERATOR_LOOKUP() + { + Dictionary ops = new Dictionary(); + var QUOTE_ATOM = Operators.KEYWORD_TO_ATOM()["q"]; + var APPLY_ATOM = Operators.KEYWORD_TO_ATOM()["a"]; + + + //core ops + // ops["0x01"] = (op, sexp) => CoreOps.OpIf(sexp); + // ops["0x02"] = (op, sexp) => CoreOps.OpIf(sexp); + ops["0x03"] = (op, sexp) => CoreOps.OpIf(sexp); + ops["0x04"] = (op, sexp) => CoreOps.OpCons(sexp); + ops["0x05"] = (op, sexp) => CoreOps.OpFirst(sexp); + ops["0x06"] = (op, sexp) => CoreOps.OpRest(sexp); + ops["0x07"] = (op, sexp) => CoreOps.OpListp(sexp); + ops["0x08"] = (op, sexp) => CoreOps.OpRaise(sexp); + ops["0x09"] = (op, sexp) => CoreOps.OpEq(sexp); + + //more ops + ops["0x10"] = (op, sexp) => MoreOps.OpAdd(sexp); + ops["0x11"] = (op, sexp) => MoreOps.OpSubtract(sexp); + ops["0x12"] = (op, sexp) => MoreOps.OpMultiply(sexp); + ops["0x13"] = (op, sexp) => MoreOps.OpDiv(sexp); + ops["0x14"] = (op, sexp) => MoreOps.OpDivmod(sexp); + ops["0x15"] = (op, sexp) => MoreOps.OpGr(sexp); + ops["0x16"] = (op, sexp) => MoreOps.OpAsh(sexp); + ops["0x17"] = (op, sexp) => MoreOps.OpLsh(sexp); + ops["0x18"] = (op, sexp) => MoreOps.OpLogand(sexp); + ops["0x19"] = (op, sexp) => MoreOps.OpLogior(sexp); + ops["0x20"] = (op, sexp) => MoreOps.OpNot(sexp); + ops["0x1A"] = (op, sexp) => MoreOps.OpLogxor(sexp); + ops["0x1B"] = (op, sexp) => MoreOps.OpLogNot(sexp); + ops["0x1D"] = (op, sexp) => MoreOps.OpPointAdd(sexp); + ops["0x1E"] = (op, sexp) => MoreOps.OpPubkeyForExp(sexp); + ops["0x0A"] = (op, sexp) => MoreOps.OpGrBytes(sexp); + ops["0x0B"] = (op, sexp) => MoreOps.OpSha256(sexp); + ops["0x0C"] = (op, sexp) => MoreOps.OpSubstr(sexp); + ops["0x0D"] = (op, sexp) => MoreOps.OpStrlen(sexp); + ops["0x0E"] = (op, sexp) => MoreOps.OpConcat(sexp); + ops["0x21"] = (op, sexp) => MoreOps.OpAny(sexp); + ops["0x22"] = (op, sexp) => MoreOps.OpAll(sexp); + ops["0x23"] = (op, sexp) => MoreOps.OpNot(sexp); + ops["0x24"] = (op, sexp) => MoreOps.OpSoftfork(sexp); + + var d = new Dictionary + { + { "quote", QUOTE_ATOM }, + { "apply", APPLY_ATOM } + }; + + OperatorDict p = new OperatorDict(); + p.OpDictionary = ops; + + return new OperatorDict(p, d); + } + + + public Tuple ApplyOperator(byte[] op, SExp args) + { + string hexString = "0x"; + foreach (byte b in op) + { + hexString += b.ToString("X2"); + } + + var f = OpDictionary[hexString]; + if (f is null) + return UnknownOpHandler(op, args); + else + { + return f(Array.Empty(), args); + } + } + + + public static IEnumerable ArgsLen(string op_name, SExp args) + { + foreach (var arg in args.AsIter()) + { + if (arg.Pair != null) + { + throw new EvalError($"{op_name} requires int args", arg); + } + yield return arg.Atom!.Length; + } + } + + public static Tuple DefaultUnknownOp(byte[] op, SExp args) + { + // any opcode starting with ffff 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"); + } + + // 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. + + byte costFunction = (byte)((op[op.Length - 1] & 0b11000000) >> 6); + // the multiplier cannot be 0. it starts at 1 + + if (op.Length > 5) + { + throw new EvalError("invalid operator"); + } + + 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; + IEnumerator 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){} + break; + case 3: + // like concat + cost = Costs.CONCAT_BASE_COST; + int length = 0; + foreach (SExp arg in args.AsIter()) + { + if (arg.AsPair() != null) + { + throw new EvalError("unknown op on list"); + } + cost += Costs.CONCAT_COST_PER_ARG; + length += arg.AsAtom().Length; + } + cost += length * Costs.CONCAT_COST_PER_BYTE; + break; + default: + throw new EvalError("Invalid cost function"); + } + + cost = (int)(cost * costMultiplier); + if (cost >= (1 << 32)) + { + throw new EvalError("invalid operator"); + } + + return Tuple.Create(cost, SExp.NULL); + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/Operators.cs b/CLVMDotNet/src/CLVM/Operators.cs index 84518ab..cf6f50e 100644 --- a/CLVMDotNet/src/CLVM/Operators.cs +++ b/CLVMDotNet/src/CLVM/Operators.cs @@ -1,255 +1,317 @@ using System.Numerics; +using System.Linq; namespace CLVMDotNet.CLVM { - public static class Operator + public static class Operators { - public static byte[] QuoteAtom { get; set; } = new byte[0]; - public static byte[] ApplyAtom { get; set; } = new byte[0]; - - /// - /// Apply Operator to an atom. - /// - /// This is not the tidiest or most efficient way of executing an operator. The - /// python version uses a dictionary to lookup the function. This will likely be changed to that - /// to make it more efficient, rather than executing 30 if statements which ultimately slows down the function. - /// - /// - /// - /// - /// - /// - public static Tuple ApplyOperator(byte[] atom, SExp args) + public static List KEYWORDS = new List { - // core op codes 0x01-x08 + // core opcodes 0x01-x08 + ".", "q", "a", "i", "c", "f", "r", "l", "x", - if (atom.AsSpan().SequenceEqual(new byte[] { 0x23 })) - { - //. (#) - return CoreOps.OpDefaultUnknown(atom,args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x01 })) - { - //q - return CoreOps.OpDefaultUnknown(atom,args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x02 })) - { - //a - return CoreOps.OpDefaultUnknown(atom, args); - } + // opcodes on atoms as strings 0x09-0x0f + "=", ">s", "sha256", "substr" , "strlen", "concat", ".", - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x03 })) - { - //i - return CoreOps.OpIf(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x04 })) - { - //c - return CoreOps.OpCons(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x05 })) - { - //f - return CoreOps.OpFirst(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x06 })) - { - //r - return CoreOps.OpRest(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x07 })) - { - //l - return CoreOps.OpListp(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x08 })) - { - //x - return CoreOps.OpRaise(args); - } + // opcodes on atoms as ints 0x10-0x17 + "+", "-", "*", "/", "divmod", ">", "ash", "lsh", + // opcodes on atoms as vectors of bools 0x18-0x1c + "logand", "logior", "logxor", "lognot", ".", - //opcodes on atoms as strings 0x09-0x0f - if (atom.AsSpan().SequenceEqual(new byte[] { 0x09 })) - { - //= - return CoreOps.OpEq(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0A })) - { - //>s - return MoreOps.OpGrBytes(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0B })) - { - //sha256 - return MoreOps.OpSha256(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0C })) - { - //substr - return MoreOps.OpSubstr(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0D })) - { - //strlen - return MoreOps.OpStrlen(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0E })) - { - //concat - return MoreOps.OpConcat(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0F })) - { - //. - //return MoreOps.OpSubtract(args); - throw new ArgumentException("Op Not Implemented!"); - } + // opcodes for bls 1381 0x1d-0x1f + "point_add", "pubkey_for_exp", ".", + // bool opcodes 0x20-0x23 + "not", "any", "all", ".", - //op codes on atoms as ints - if (atom.AsSpan().SequenceEqual(new byte[] { 0x10 })) - { - //+ - return MoreOps.OpAdd(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x11 })) - { - //- - return MoreOps.OpSubtract(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x12 })) - { - //* - return MoreOps.OpDiv(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x13 })) - { - // divide - return MoreOps.OpMultiply(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x14 })) - { - //divmod - return MoreOps.OpDivmod(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x15 })) - { - //> - return MoreOps.OpGr(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x16 })) - { - //ash - return MoreOps.OpAsh(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x17 })) - { - //lsh - return MoreOps.OpLsh(args); - } + // misc 0x24 + "softfork" + }; - // opcodes on atoms as vectors of bools 0x18-0x1c - if (atom.AsSpan().SequenceEqual(new byte[] { 0x18 })) - { - //logand - return MoreOps.OpLogand(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x19 })) - { - //logior - return MoreOps.OpLogior(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1A })) - { - //logxor - return MoreOps.OpLogxor(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1B })) - { - //lognot - return MoreOps.OpLogNot(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1C })) + public static Dictionary KEYWORD_FROM_ATOM => KEYWORDS.Select((keyword, index) => new { Index = index, Keyword = keyword }) + .ToDictionary(item => Casts.IntToBytes(item.Index), item => item.Keyword, new ByteArrayComparer()); + + public static Dictionary KEYWORD_TO_ATOM() + { + var dic = new Dictionary(); + foreach (var item in KEYWORD_FROM_ATOM) { - //. - throw new ArgumentException("Op Not Implemented!"); + dic[item.Value] = item.Key; } + return dic; + } + + public static Dictionary OpRewrite => new Dictionary + { + { "+", "add" }, + { "-", "subtract" }, + { "*", "multiply" }, + { "/", "div" }, + { "i", "if" }, + { "c", "cons" }, + { "f", "first" }, + { "r", "rest" }, + { "l", "listp" }, + { "x", "raise" }, + { "=", "eq" }, + { ">", "gr" }, + { ">s", "gr_bytes" } + }; - //opcodes for bls 1381 0x1d-0x1f - if (atom.AsSpan().SequenceEqual(new byte[] { 0x1D })) - { - //point_add - return MoreOps.OpPointAdd(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1E })) - { - //pubkey_for_exp - return MoreOps.OpPubkeyForExp(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1F })) - { - //. - throw new ArgumentException("Op Not Implemented!"); - } + public static byte[] QUOTE_ATOM => KEYWORD_TO_ATOM()["q"]; + public static byte[] APPLY_ATOM => KEYWORD_TO_ATOM()["a"]; - // bool opcodes 0x20-0x23 - if (atom.AsSpan().SequenceEqual(new byte[] { 0x20 })) - { - //not - return MoreOps.OpNot(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x21 })) - { - //any - return MoreOps.OpAny(args); - } - else if (atom.AsSpan().SequenceEqual(new byte[] { 0x22 })) - { - //all - return MoreOps.OpAll(args); - } - if (atom.AsSpan().SequenceEqual(new byte[] { 0x24 })) + // { + // public static byte[] QuoteAtom { get; set; } = new byte[0]; + // public static byte[] ApplyAtom { get; set; } = new byte[0]; + // + // /// + // /// Apply Operator to an atom. + // /// + // /// This is not the tidiest or most efficient way of executing an operator. The + // /// python version uses a dictionary to lookup the function. This will likely be changed to that + // /// to make it more efficient, rather than executing 30 if statements which ultimately slows down the function. + // /// + // /// + // /// + // /// + // /// + // /// + // public static Tuple ApplyOperator(byte[] atom, SExp args) + // { + // // core op codes 0x01-x08 + // + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x23 })) + // { + // //. (#) + // return CoreOps.OpDefaultUnknown(atom,args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x01 })) + // { + // //q + // return CoreOps.OpDefaultUnknown(atom,args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x02 })) + // { + // //a + // return CoreOps.OpDefaultUnknown(atom, args); + // } + // + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x03 })) + // { + // //i + // return CoreOps.OpIf(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x04 })) + // { + // //c + // return CoreOps.OpCons(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x05 })) + // { + // //f + // return CoreOps.OpFirst(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x06 })) + // { + // //r + // return CoreOps.OpRest(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x07 })) + // { + // //l + // return CoreOps.OpListp(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x08 })) + // { + // //x + // return CoreOps.OpRaise(args); + // } + // + // + // //opcodes on atoms as strings 0x09-0x0f + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x09 })) + // { + // //= + // return CoreOps.OpEq(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0A })) + // { + // //>s + // return MoreOps.OpGrBytes(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0B })) + // { + // //sha256 + // return MoreOps.OpSha256(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0C })) + // { + // //substr + // return MoreOps.OpSubstr(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0D })) + // { + // //strlen + // return MoreOps.OpStrlen(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0E })) + // { + // //concat + // return MoreOps.OpConcat(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0F })) + // { + // //. + // throw new ArgumentException("Op Not Implemented!"); + // } + // + // + // //op codes on atoms as ints + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x10 })) + // { + // //+ + // return MoreOps.OpAdd(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x11 })) + // { + // //- + // return MoreOps.OpSubtract(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x12 })) + // { + // //* + // return MoreOps.OpMultiply(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x13 })) + // { + // //divide + // return MoreOps.OpDiv(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x14 })) + // { + // //divmod + // return MoreOps.OpDivmod(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x15 })) + // { + // //> + // return MoreOps.OpGr(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x16 })) + // { + // //ash + // return MoreOps.OpAsh(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x17 })) + // { + // //lsh + // return MoreOps.OpLsh(args); + // } + // + // // opcodes on atoms as vectors of bools 0x18-0x1c + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x18 })) + // { + // //logand + // return MoreOps.OpLogand(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x19 })) + // { + // //logior + // return MoreOps.OpLogior(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1A })) + // { + // //logxor + // return MoreOps.OpLogxor(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1B })) + // { + // //lognot + // return MoreOps.OpLogNot(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1C })) + // { + // //. + // throw new ArgumentException("Op Not Implemented!"); + // } + // + // + // //opcodes for bls 1381 0x1d-0x1f + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x1D })) + // { + // //point_add + // return MoreOps.OpPointAdd(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1E })) + // { + // //pubkey_for_exp + // return MoreOps.OpPubkeyForExp(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1F })) + // { + // //. + // throw new ArgumentException("Op Not Implemented!"); + // } + // + // // bool opcodes 0x20-0x23 + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x20 })) + // { + // //not + // return MoreOps.OpNot(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x21 })) + // { + // //any + // return MoreOps.OpAny(args); + // } + // else if (atom.AsSpan().SequenceEqual(new byte[] { 0x22 })) + // { + // //all + // return MoreOps.OpAll(args); + // } + // if (atom.AsSpan().SequenceEqual(new byte[] { 0x24 })) + // { + // //softfork + // return MoreOps.OpSoftfork(args); + // } + // + // throw new Exception($"{BitConverter.ToString(atom).Replace("-", "")} Operator not found or is unsupported!"); + // } + } + + public class ByteArrayComparer : IEqualityComparer + { + public bool Equals(byte[] x, byte[] y) + { + if (x == null || y == null) + return x == y; + + if (x.Length != y.Length) + return false; + + for (int i = 0; i < x.Length; i++) { - //softfork - return MoreOps.OpSoftfork(args); + if (x[i] != y[i]) + return false; } - throw new Exception($"{BitConverter.ToString(atom).Replace("-", "")} Operator not found or is unsupported!"); + return true; } - public static string KEYWORD_FROM_ATOM(byte atom) => atom switch + public int GetHashCode(byte[] obj) { - 0x23 => ".", - 0x02 => "a", - 0x01 => "q", - 0x03 => "i", - 0x04 => "c", - 0x05 => "f", - 0x06 => "r", - 0x07 => "l", - 0x08 => "x", - _ => throw new Exception("Invalid Atom") - }; + if (obj == null) + throw new ArgumentNullException(nameof(obj)); - public static byte KEYWORD_TO_ATOM(string keyword) => keyword switch - { - "." => 0x23, - "a" => 0x02, - "q" => 0x01, - "i" => 0x03, - "c" => 0x04, - "f" => 0x05, - "r" => 0x06, - "l" => 0x07, - "x" => 0x08, - _ => throw new Exception("Invalid Keyword") - }; + int hash = 17; + foreach (byte b in obj) + { + hash = hash * 31 + b; + } + return hash; + } } - - } \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/Program.cs b/CLVMDotNet/src/CLVM/Program.cs index a44434d..e8a5e98 100644 --- a/CLVMDotNet/src/CLVM/Program.cs +++ b/CLVMDotNet/src/CLVM/Program.cs @@ -1,19 +1,199 @@ -namespace CLVMDotNet.CLVM; - -public class Program -{ - //RunProgram - //TraversePath - //SwapOp - //ConsOp - //ApplyOp - //to_pre_eval_op - - public static byte MSBMask(byte inputByte) - { - inputByte |= (byte)(inputByte >> 1); - inputByte |= (byte)(inputByte >> 2); - inputByte |= (byte)(inputByte >> 4); - return (byte)((inputByte + 1) >> 1); - } -} \ No newline at end of file +// using System.Numerics; +// +// namespace CLVMDotNet.CLVM; +// +// public static class Program +// { +// public static Tuple RunProgram(SExp program, SExp args, BigInteger? maxCost = null) +// { +// var prog = SExp.To(program); +// Stack, BigInteger>> opStack = new Stack, BigInteger>>(); +// +// var valueStack = new Stack(); +// valueStack.Push(prog.Cons(args)); +// opStack.Push(stack => EvalOp(opStack, valueStack)); +// +// BigInteger cost = 0; +// +// while (opStack.Count != 0) +// { +// var f = opStack.Pop(); +// cost += f(valueStack); +// if (maxCost.HasValue && cost > maxCost) +// { +// throw new EvalError("cost exceeded", SExp.To(maxCost)); +// } +// } +// +// return Tuple.Create(new BigInteger(1), prog); +// } +// +// public static (BigInteger, SExp) TraversePath(SExp sexp, SExp env) +// { +// BigInteger cost = Costs.PATH_LOOKUP_BASE_COST; +// cost += Costs.PATH_LOOKUP_COST_PER_LEG; +// +// if (sexp.Nullp()) +// { +// return (cost, SExp.NULL); +// } +// +// byte[] b = sexp.Atom; +// +// int endByteCursor = 0; +// while (endByteCursor < b.Length && b[endByteCursor] == 0) +// { +// endByteCursor++; +// } +// +// cost += endByteCursor * Costs.PATH_LOOKUP_COST_PER_ZERO_BYTE; +// if (endByteCursor == b.Length) +// { +// return (cost, SExp.NULL); +// } +// +// // Create a bitmask for the most significant *set* bit +// // in the last non-zero byte +// byte endBitmask = MSBMask(b[endByteCursor]); +// int byteCursor = b.Length - 1; +// byte bitmask = 0x01; +// while (byteCursor > endByteCursor || bitmask < endBitmask) +// { +// if (env.Pair == null) +// { +// throw new EvalError("path into atom", env); +// } +// +// if ((b[byteCursor] & bitmask) != 0) +// { +// env = env.Rest(); +// } +// else +// { +// env = env.First(); +// } +// +// cost += Costs.PATH_LOOKUP_COST_PER_LEG; +// bitmask <<= 1; +// +// if (bitmask == 0x100) +// { +// byteCursor--; +// bitmask = 0x01; +// } +// } +// return (cost, env); +// } +// +// public static BigInteger EvalOp(Stack, BigInteger>> opStack, Stack valueStack) +// { +// var pair = valueStack.Pop(); +// var sexp = pair.First(); +// var args = pair.Rest(); +// +// if (sexp.Pair == null) +// { +// // sexp is an atom +// var (cost, r) = TraversePath(sexp, args); +// valueStack.Push(r); +// return cost; +// } +// +// var op = sexp.First(); +// if (op.Pair != null) +// { +// var operatorPair = op.AsPair(); //newOperator,must_be_nil +// var (newOperator, mustBeNil) = (operatorPair.Item1, operatorPair.Item2); +// if (newOperator.Pair != null || mustBeNil.Atom != Array.Empty()) +// { +// throw new EvalError("in ((X)...) syntax X must be lone atom", sexp); +// } +// +// var newOperandList = sexp.Rest(); +// valueStack.Append(newOperator); +// valueStack.Append(newOperandList); +// opStack.Push(stack => ApplyOp(opStack, valueStack)); +// return Costs.APPLY_COST; +// } +// +// var op1 = op.AsAtom(); +// var operand_list = sexp.Rest(); +// if (op1 == Operator.QuoteAtom) +// { +// valueStack.Append(operand_list); +// return Costs.QUOTE_COST; +// } +// +// opStack.Append(stack => ConsOp(opStack, valueStack)); +// valueStack.Append(op); +// while (operand_list.Nullp()) +// { +// var ex = operand_list.First(); +// valueStack.Append(ex.Cons(args)); +// opStack.Push(stack => ConsOp(opStack, valueStack)); +// opStack.Push(stack => EvalOp(opStack, valueStack)); +// opStack.Push(stack => SwapOp(opStack, valueStack)); +// operand_list = operand_list.Rest(); +// valueStack.Append(SExp.NULL); +// } +// return BigInteger.Parse("1"); +// } +// +// public static BigInteger ApplyOp(Stack, BigInteger>> opStack, Stack valueStack) +// { +// var operandList = valueStack.Pop(); +// var oper = valueStack.Pop(); +// +// if (oper.Pair != null) +// { +// throw new EvalError("internal error", oper); +// } +// +// var op = oper.AsAtom(); +// +// if (op == Operator.ApplyAtom) +// { +// if (operandList.ListLength() != 2) +// { +// throw new EvalError("apply requires exactly 2 parameters", operandList); +// } +// +// var newProgram = operandList.First(); +// var newArgs = operandList.Rest().First(); +// +// valueStack.Push(newProgram.Cons(newArgs)); +// opStack.Push(stack => EvalOp(opStack, valueStack)); +// return Costs.APPLY_COST; +// } +// +// var result = Operator.ApplyOperator(op, operandList); +// valueStack.Push(result.Item2); +// return result.Item1; +// } +// +// public static BigInteger SwapOp(Stack, BigInteger>> opStack, Stack valueStack) +// { +// var v2 = valueStack.Pop(); +// var v1 = valueStack.Pop(); +// valueStack.Push(v2); +// valueStack.Push(v1); +// return 0; +// } +// +// public static int ConsOp(Stack, BigInteger>> opStack, Stack valueStack) +// { +// var v1 = valueStack.Pop(); +// var v2 = valueStack.Pop(); +// var result = v1.Cons(v2); +// valueStack.Push(result); +// return 0; +// } +// +// public static byte MSBMask(byte inputByte) +// { +// inputByte |= (byte)(inputByte >> 1); +// inputByte |= (byte)(inputByte >> 2); +// inputByte |= (byte)(inputByte >> 4); +// return (byte)((inputByte + 1) >> 1); +// } +// } \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/SExp.cs b/CLVMDotNet/src/CLVM/SExp.cs index 1013c92..51077e8 100644 --- a/CLVMDotNet/src/CLVM/SExp.cs +++ b/CLVMDotNet/src/CLVM/SExp.cs @@ -59,7 +59,7 @@ public SExp Cons(SExp right) return To(new Tuple(this, right)); } - public dynamic First() + public SExp First() { if (Pair is (_,_)) { @@ -69,7 +69,7 @@ public dynamic First() throw new EvalError("first of non-cons", this); } - public dynamic Rest() + public SExp Rest() { if (Pair is (_,_)) { diff --git a/CLVMDotNet/src/CLVMDotNet.csproj b/CLVMDotNet/src/CLVMDotNet.csproj index e1adaf4..dca9ac3 100644 --- a/CLVMDotNet/src/CLVMDotNet.csproj +++ b/CLVMDotNet/src/CLVMDotNet.csproj @@ -9,10 +9,7 @@ - - - - + diff --git a/CLVMDotNet/src/Extensions/SExpExtensions.cs b/CLVMDotNet/src/Extensions/SExpExtensions.cs new file mode 100644 index 0000000..8b2d739 --- /dev/null +++ b/CLVMDotNet/src/Extensions/SExpExtensions.cs @@ -0,0 +1,14 @@ +using CLVMDotNet.CLVM; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CLVMDotNet.Extensions; + +public static class SExpExtensions +{ + public static string AsJSON(this SExp sexp) + { + var sourceJObject = JsonConvert.SerializeObject(sexp); + return sourceJObject; + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/IR/BinUtils.cs b/CLVMDotNet/src/Tools/IR/BinUtils.cs index fc8b483..48bc22c 100644 --- a/CLVMDotNet/src/Tools/IR/BinUtils.cs +++ b/CLVMDotNet/src/Tools/IR/BinUtils.cs @@ -21,7 +21,7 @@ public static SExp AssembleFromIR(SExp ir_sexp) keyword = keyword.Substring(1); } - var atom = x.Keywords.KEYWORD_TO_ATOM.ContainsKey(keyword) ? x.Keywords.KEYWORD_TO_ATOM[keyword] : null; + var atom = CLVM.Operators.KEYWORD_TO_ATOM()[keyword]; if (atom != null) { return SExp.To(atom); @@ -45,8 +45,8 @@ public static SExp AssembleFromIR(SExp ir_sexp) // handle "q" var first = Utils.IrFirst(ir_sexp); - var keyword2 = Utils.IrAsSymbol(first) as string; - if (keyword2 == "q") + keyword = Utils.IrAsSymbol(first) as string; + if (keyword == "q") { // TODO: note that any symbol is legal after this point } @@ -82,7 +82,8 @@ public static BigInteger TypeForAtom(byte[] atom) return IRType.HEX; } - if (BitConverter.ToUInt32(atom, 0) == BitConverter.ToUInt32(atom, 0)) + + if(x.Casts.IntToBytes(x.Casts.IntFromBytes(atom)).SequenceEqual(atom)) { return IRType.INT; } diff --git a/CLVMDotNet/src/Tools/IR/Clvmc.cs b/CLVMDotNet/src/Tools/IR/Clvmc.cs index 4dde14a..9848d2b 100644 --- a/CLVMDotNet/src/Tools/IR/Clvmc.cs +++ b/CLVMDotNet/src/Tools/IR/Clvmc.cs @@ -1,4 +1,5 @@ using CLVMDotNet.CLVM; +using CLVMDotNet.Extensions; namespace CLVMDotNet.Tools.IR { @@ -10,10 +11,13 @@ public static SExp CompileCLVMText(string text, string[] searchPaths) var assembled_sexp = BinUtils.AssembleFromIR(ir_src); var input_sexp = SExp.To((assembled_sexp, Array.Empty())); + // everthing above here matches python + var tree = HelperFunctions.PrintTree(input_sexp); + // var result = Program.RunProgram(null, input_sexp); - //run_program_for_search_paths - //run_program + //Need a RunProgramForSearchPaths + //return result.Item2; return null; } diff --git a/CLVMDotNet/src/Tools/IR/IRReader.cs b/CLVMDotNet/src/Tools/IR/IRReader.cs index 89cae78..82e0cf1 100644 --- a/CLVMDotNet/src/Tools/IR/IRReader.cs +++ b/CLVMDotNet/src/Tools/IR/IRReader.cs @@ -117,13 +117,6 @@ public static (string, int) NextConsToken(IEnumerator<(string, int)> stream) int offset = -1; stream.MoveNext(); - // foreach (var item in stream.) - // { - // token = item.Item1; - // offset = item.Item2; - // break; - // } - token = stream.Current.Item1; offset = stream.Current.Item2; @@ -135,7 +128,7 @@ public static (string, int) NextConsToken(IEnumerator<(string, int)> stream) return (token, offset); } - public static dynamic? TokenizeCons(string token, int offset, IEnumerator<(string, int)> stream) + public static SExp? TokenizeCons(string token, int offset, IEnumerator<(string, int)> stream) { if (token == ")") { @@ -185,7 +178,8 @@ public static (string, int) NextConsToken(IEnumerator<(string, int)> stream) var (t, o) = NextConsToken(stream); token = t; offset = o; - return TokenizeCons(token, offset, stream); + var cons = TokenizeCons(token, offset, stream); + return cons; } var result = TokenizeInt(token, offset); @@ -206,7 +200,7 @@ public static (string, int) NextConsToken(IEnumerator<(string, int)> stream) public static SExp? TokenizeInt(string token, int offset) { - if (int.TryParse(token, out int result)) + if (BigInteger.TryParse(token, out BigInteger result)) { return Utils.IrNew(IRType.INT, result, offset); } @@ -230,7 +224,7 @@ public static (string, int) NextConsToken(IEnumerator<(string, int)> stream) } else { - throw new SyntaxException("invalid hex at " + offset + ": 0x" + token); + throw new SyntaxException($"invalid hex at {offset}:{token}"); } } @@ -278,9 +272,10 @@ public static bool TryParseHex(string token, out byte[] result) return null; } - public static Tuple<(BigInteger, int), string>? TokenizeSymbol(string token, int offset) + public static Tuple<(BigInteger, int), byte[]>? TokenizeSymbol(string token, int offset) { - return Tuple.Create((IRType.SYMBOL, offset), token); + var tokenAsBytes = Encoding.UTF8.GetBytes(token); + return Tuple.Create((IRType.SYMBOL, offset), tokenAsBytes); } public static IEnumerable<(string token, int offset)> TokenStream(string s) @@ -295,7 +290,7 @@ public static bool TryParseHex(string token, out byte[] result) } char c = s[offset]; - if (c == '(' || c == ')') + if ("(.)".Contains(c)) { yield return (c.ToString(), offset); offset++; @@ -340,7 +335,8 @@ public static SExp ReadIR(string s) { var item = enumerator.Current; var ts = TokenizeSexp(item.token, item.offset, enumerator); - return SExp.To(ts); + var sexp = SExp.To(ts); + return sexp; } throw new ArgumentException("unexpected end of stream"); diff --git a/CLVMDotNet/src/Tools/IR/IRType.cs b/CLVMDotNet/src/Tools/IR/IRType.cs index 421f59e..11b4426 100644 --- a/CLVMDotNet/src/Tools/IR/IRType.cs +++ b/CLVMDotNet/src/Tools/IR/IRType.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tools.IR { - public class IRType + public static class IRType { public static BigInteger CONS = Utils.ConvertToBase256("CONS"); // Equivalent to b"CONS" public static BigInteger NULL = Utils.ConvertToBase256("NULL"); // Equivalent to b"NULL" @@ -23,7 +23,7 @@ public static bool Listp() return false; } - public byte[] AsAtom(BigInteger val) + public static byte[] AsAtom(BigInteger val) { return Casts.IntToBytes(val); } diff --git a/CLVMDotNet/src/Tools/IR/Utils.cs b/CLVMDotNet/src/Tools/IR/Utils.cs index 7a5e6c2..1ec571d 100644 --- a/CLVMDotNet/src/Tools/IR/Utils.cs +++ b/CLVMDotNet/src/Tools/IR/Utils.cs @@ -25,7 +25,8 @@ public static BigInteger ConvertToBase256(string s) { if (irSexp.Listp() && IrType(irSexp) == IRType.SYMBOL) { - return Encoding.UTF8.GetString(IrAsSexp(irSexp).AsAtom()); + var sexp = IrAsSexp(irSexp); + return Encoding.UTF8.GetString(sexp.AsAtom()); } return null; diff --git a/CLVMDotNet/src/Tools/NodePath.cs b/CLVMDotNet/src/Tools/NodePath.cs new file mode 100644 index 0000000..5e15a9c --- /dev/null +++ b/CLVMDotNet/src/Tools/NodePath.cs @@ -0,0 +1,108 @@ +using System.Numerics; + +namespace CLVMDotNet.Tools; + +public class NodePath +{ + public static NodePath TOP => new NodePath(1); + public static NodePath Left => TOP.First(); + public static NodePath Right => TOP.Rest(); + public BigInteger ByteCount { get; private set; } + public byte[] Blob { get; private set; } + public BigInteger _index { get; private set; } + + public NodePath(BigInteger index) + { + if (index < 0) + { + var log = index + 1; + int byteCount = (int)Math.Ceiling(BigInteger.Log(log, 256) / 8); + byteCount = (byteCount + 7) / 8; + byte[] blob = index.ToByteArray(); + Array.Reverse(blob); + byte[] resultBlob = new byte[byteCount + 1]; + Array.Copy(blob, 0, resultBlob, 1, byteCount); + _index = new BigInteger(resultBlob); + } + else + { + _index = index; + } + } + + public NodePath First() + { + BigInteger newIndex = _index * 2; + return new NodePath(newIndex); + } + + public NodePath Rest() + { + BigInteger newIndex = _index * 2 + 1; + return new NodePath(newIndex); + } + + public static string ByteArrayToHexString(byte[] byteArray) + { + return string.Join("", byteArray.SkipWhile(b => b == 0).Select(b => b.ToString("X2"))); + } + + public byte[] AsShortPath() + { + BigInteger index = _index; + byte[] byteArray = index.ToByteArray(); + Array.Reverse(byteArray); + + return byteArray; + } + + public NodePath Add(NodePath otherNode) + { + BigInteger resultIndex = ComposePaths(_index, otherNode._index); + return new NodePath(resultIndex); + } + + + /// + /// The binary representation of a path is a 1 (which means "stop"), followed by the path as binary digits, + /// where 0 is "left" and 1 is "right". + /// + /// Look at the diagram at the top for these examples. + /// + /// Example: 9 = 0b1001, so right, left, left + /// Example: 10 = 0b1010, so left, right, left + /// + /// How it works: we write both numbers as binary. We ignore the terminal in path_0, since it's + /// not the terminating condition anymore. We shift path_1 enough places to OR in the rest of path_0. + /// + /// Example: path_0 = 9 = 0b1001, path_1 = 10 = 0b1010. + /// + /// Shift path_1 three places (so there is room for 0b001) to 0b1010000. + /// Then OR in 0b001 to yield 0b1010001 = 81, which is right, left, left, left, right, left. + /// + /// + /// + /// + public BigInteger ComposePaths(BigInteger path0, BigInteger path1) + { + BigInteger mask = 1; + BigInteger temp_path = path0; + + while (temp_path > 1) + { + path1 <<= 1; + mask <<= 1; + temp_path >>= 1; + } + + mask -= 1; + BigInteger path = path1 | (path0 & mask); + + return path; + } + + public override string ToString() + { + return $"NodePath: {_index}"; + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage0.cs b/CLVMDotNet/src/Tools/Stages/Stage0/Stage0.cs similarity index 100% rename from CLVMDotNet/src/Tools/Stages/Stage0.cs rename to CLVMDotNet/src/Tools/Stages/Stage0/Stage0.cs diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Bindings.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Bindings.cs new file mode 100644 index 0000000..71bd07d --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Bindings.cs @@ -0,0 +1,11 @@ +using CLVMDotNet.CLVM; +using CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public static class Bindings +{ + public static SExp Brun => BinUtils.Assemble("(a 2 3)"); + + public static SExp Run => BinUtils.Assemble("(a (opt (com 2)) 3)"); +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Compile.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Compile.cs new file mode 100644 index 0000000..5b19de3 --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Compile.cs @@ -0,0 +1,184 @@ +// using CLVMDotNet.CLVM; +// +// namespace CLVMDotNet.Tools.Stages.Stage2; +// +// public static class Compile +// { +// public static byte[] QUOTE_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["q"]; +// public static byte[] APPLY_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["a"]; +// public static byte[] CONS_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["c"]; +// +// +// public static SExp DoComProg(SExp prog, SExp macroLookup, SExp symbolTable, Func runProgram) +// { +// // Lower "quote" to "q" +// prog = LowerQuote(prog, macroLookup, symbolTable, runProgram); +// +// // Quote atoms +// if (prog.Nullp() || !prog.Listp()) +// { +// var atom = prog.AsAtom(); +// if (atom == HelperFunctions.ConvertAtomToBytes("@")) +// { +// return SExp.To(NodePath.TOP.AsShortPath()); +// } +// foreach (var pair in symbolTable.AsIter()) +// { +// var symbol = pair.First().AsAtom(); +// var value = pair.Rest().First(); +// if (symbol == atom) +// { +// return SExp.To(value); +// } +// } +// return SExp.To(Quote(prog)); +// } +// +// var operatorObj = prog.First(); +// if (operatorObj.Listp()) +// { +// // (com ((OP) . RIGHT)) => (a (com (q OP)) 1) +// var innerExp = Helpers.Eval(SExp.To(new CLVMObject(new List { "com", Quote(operatorObj), Quote(macroLookup), Quote(symbolTable) })), NodePath.TOP.AsShortPath()); +// return SExp.To(new CLVMObject(new List { innerExp })); +// } +// +// var asAtom = operatorObj.AsAtom(); +// +// foreach (var macroPair in macroLookup.AsIter()) +// { +// var macroName = macroPair.First().AsAtom(); +// if (macroName!.SequenceEqual(asAtom)) +// { +// var macroCode = macroPair.Rest().First(); +// var postProg = Helpers.Brun(macroCode, prog.Rest()); +// return Helpers.Eval(SExp.To(new CLVMObject(new List { "com", postProg, Quote(macroLookup), Quote(symbolTable) })), NodePath.TOP.AsShortPath()); +// } +// } +// +// if (COMPILE_BINDINGS.ContainsKey(asAtom)) +// { +// Func, CLVMObject> f = COMPILE_BINDINGS[asAtom]; +// var postProg = f(prog.Rest(), macroLookup, symbolTable, runProgram); +// return Helpers.Eval(SExp.To(Quote(postProg)), NodePath.TOP.AsShortPath()); +// } +// +// if (operatorObj.Equals(QUOTE_ATOM)) +// { +// return prog; +// } +// +// var compiledArgs = prog.Rest().AsIter().Select(arg => DoComProg(arg, macroLookup, symbolTable, runProgram)).ToList(); +// var r = SExp.To(new CLVMObject(new List { operatorObj }.Concat(compiledArgs))); +// +// if (PASS_THROUGH_OPERATORS.Contains(asAtom) || asAtom.StartsWith("_")) +// { +// return r; +// } +// +// foreach (var pair in symbolTable.AsPython()) +// { +// var symbol = pair.First().AsAtom(); +// var value = pair.Rest().First(); +// if (symbol == "*") +// { +// return r; +// } +// if (symbol.SequenceEqual(asAtom)) +// { +// var newArgs = Helpers.Eval(SExp.To(new CLVMObject(new List { "opt", new CLVMObject(new List { "com", Quote(new CLVMObject(new List { "list" }.Concat(prog.Rest().AsIter()))), Quote(macroLookup), Quote(symbolTable) }) })), TOP.AsPath()); +// r = SExp.To(new CLVMObject(new List { APPLY_ATOM, value, new CLVMObject(new List { NodePath.Left.AsShortPath(), newArgs }) })); +// return r; +// } +// } +// +// throw new SyntaxException($"can't compile , unknown operator"); +// //throw new Exception($"can't compile {Disassemble(prog)}, unknown operator"); +// } +// +// private static SExp Quote(SExp sexp) +// { +// return new SExp(new List { "q", sexp }); +// } +// +// public static SExp CompileMacros(SExp args, SExp macroLookup, SExp symbolTable, Func runProgram) +// { +// return SExp.To(Helpers.Quote(macroLookup)); +// } +// +// public static SExp CompileSymbols(SExp args, SExp macroLookup, SExp symbolTable, Func runProgram) +// { +// return SExp.To(Helpers.Quote(symbolTable)); +// } +// +// public static SExp CompileQQ(SExp args, SExp macroLookup, SExp symbolTable, Func runProgram, int level = 1) +// { +// SExp Com(SExp sexp) +// { +// return DoComProg(sexp, macroLookup, symbolTable, runProgram); +// } +// +// var sexp = args.First(); +// if (!sexp.Listp() || sexp.Nullp()) +// { +// // (qq ATOM) => (q . ATOM) +// return SExp.To(Helpers.Quote(sexp)); +// } +// +// if (sexp.Listp() && !sexp.First().Listp()) +// { +// var op = sexp.First().AsAtom(); +// if (op == HelperFunctions.ConvertAtomToBytes("qq")) +// { +// var subexp = CompileQQ(sexp.Rest(), macroLookup, symbolTable, runProgram, level + 1); +// return Com(SExp.To(new CLVMObject(new List { CONS_ATOM, op, new CLVMObject(new List { CONS_ATOM, subexp, Quote(0) }) })), macroLookup, symbolTable, runProgram); +// } +// if (op == HelperFunctions.ConvertAtomToBytes("unquote")) +// { +// if (level == 1) +// { +// // (qq (unquote X)) => X +// +// //return Com(sexp.Rest().First(), macroLookup, symbolTable, runProgram); +// } +// var subexp = CompileQQ(sexp.Rest(), macroLookup, symbolTable, runProgram, level - 1); +// return Com(SExp.To(new CLVMObject(new List { CONS_ATOM, op, new CLVMObject(new List { CONS_ATOM, subexp, Quote(0) }) })), macroLookup, symbolTable, runProgram); +// } +// } +// +// // (qq (a . B)) => (c (qq a) (qq B)) +// var A = Com(SExp.To(new List { "qq", sexp.First() })); +// var B = Com(SExp.To(new List { "qq", sexp.Rest() })); +// return SExp.To(new SExp(new List { CONS_ATOM, A, B })); +// } +// +// public static SExp LowerQuote(SExp prog, SExp? macroLookup = null, SExp? symbolTable = null, Func runProgram = null) +// { +// if (prog.Nullp()) +// { +// return prog; +// } +// +// if (prog.Listp()) +// { +// if (prog.First().AsAtom() == HelperFunctions.ConvertAtomToBytes("quote")) +// { +// // Note: quote should have exactly one arg, so the length of +// // quoted list should be 2: "(quote arg)" +// if (!prog.Rest().Rest().Nullp()) +// { +// throw new SyntaxException($"Compilation error while compiling quote takes exactly one argument."); +// //throw new SyntaxException($"Compilation error while compiling [{Disassemble(prog)}]. quote takes exactly one argument."); +// } +// return SExp.To(Quote(LowerQuote(prog.Rest().First()))); +// } +// else +// { +// return SExp.To((LowerQuote(prog.First()), LowerQuote(prog.Rest()))); +// } +// } +// else +// { +// return prog; +// } +// } +// } \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Defaults.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Defaults.cs new file mode 100644 index 0000000..b3c9ccc --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Defaults.cs @@ -0,0 +1,71 @@ +namespace CLVMDotNet.Tools.Stages.Stage2; + +public class Defaults +{ + public static List DefaultMacrosSrc => new List + { + @" + // we have to compile this externally, since it uses itself + // (defmacro defmacro (name params body) + // (qq (list (unquote name) (mod (unquote params) (unquote body)))) + // ) + (q . (""defmacro"" + (c (q . ""list"") + (c (f 1) + (c (c (q . ""mod"") + (c (f (r 1)) + (c (f (r (r 1))) + (q . ())))) + (q . ())))))) + ", + @" + // (defmacro list ARGS + // ((c (mod args + // (defun compile-list + // (args) + // (if args + // (qq (c (unquote (f args)) + // (unquote (compile-list (r args))))) + // ())) + // (compile-list args) + // ) + // ARGS + // )) + // ) + (q ""list"" + (a (q #a (q #a 2 (c 2 (c 3 (q)))) + (c (q #a (i 5 + (q #c (q . 4) + (c 9 (c (a 2 (c 2 (c 13 (q)))) + (q))) + ) + (q 1)) + 1)) + 1)) + ", + @" + (defmacro function (BODY) + (qq (opt (com (q . (unquote BODY)) + (qq (unquote (macros))) + (qq (unquote (symbols))))))) + ", + @" + (defmacro if (A B C) + (qq (a + (i (unquote A) + (function (unquote B)) + (function (unquote C))) + @))) + ", + // / operator at the clvm layer is becoming deprecated and + // will be implemented using divmod. + @" + (defmacro / (A B) (qq (f (divmod (unquote A) (unquote B))))) + " + }; + + + //DEFAULT_MACRO_LOOKUP + //build_default_macro_lookup + //default_macro_lookup +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Helpers.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Helpers.cs new file mode 100644 index 0000000..8d7fc44 --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Helpers.cs @@ -0,0 +1,32 @@ +using CLVMDotNet.CLVM; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public static class Helpers +{ + public static byte[] QUOTE_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["q"]; + public static byte[] APPLY_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["a"]; + + public static Tuple Quote(SExp sexp) + { + return Tuple.Create(QUOTE_ATOM, sexp); + } + + public static SExp Eval(SExp prog, dynamic args) + { + var eval = SExp.To(new List() { APPLY_ATOM, prog, args }); + return eval; + } + + public static SExp Run(SExp prog, SExp macroLookup) + { + var args = NodePath.TOP.AsShortPath(); + var mac = Quote(macroLookup); + return Helpers.Eval(SExp.To(new List { "com", prog, mac }), args); + } + + public static SExp Brun(SExp prog, SExp args) + { + return Helpers.Eval(SExp.To(Quote(prog)), Quote(args)); + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Mod.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Mod.cs new file mode 100644 index 0000000..953d11c --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Mod.cs @@ -0,0 +1,75 @@ +using CLVMDotNet.CLVM; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public static class Mod +{ + public static byte[] QUOTE_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["q"]; + public static byte[] CONS_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["c"]; + public static byte[] MAIN_NAME => new byte[] { }; + + /// + /// This function takes a Python list of items and turns it into a binary tree + /// of the items, suitable for casting to an s-expression. + /// + /// + public static dynamic BuildTree(List items) + { + var size = items.Count; + if (size == 0) + return new List(); + if (size == 1) + return items[0]; + + int halfSize = size / 2; + dynamic left = BuildTree(items.GetRange(0, halfSize)); + dynamic right = BuildTree(items.GetRange(halfSize, size - halfSize)); + return new Tuple(left, right); + } + + /// + /// his function takes a Python list of items and turns it into a program that + /// builds a binary tree of the items, suitable for casting to an s-expression. + /// + /// + public static List BuildTreeProgram(List items) + { + var size = items.Count; + if (size == 0) + return new List(); + if (size == 1) + return items[0]; + + int halfSize = size / 2; + dynamic left = BuildTreeProgram(items.GetRange(0, halfSize)); + dynamic right = BuildTreeProgram(items.GetRange(halfSize, size - halfSize)); + return new List { CONS_ATOM, left, right }; + } + + public static List Flatten(SExp sexp) + { + if (sexp.Listp()) + { + List result = new List(); + result.AddRange(Flatten(sexp.First())); + result.AddRange(Flatten(sexp.Rest())); + return result; + } + return new List { sexp.AsAtom() }; + } + + + //Flattern + //BuildUsedConstantNames + //ParseInclude + //UnquoteArgs + //DefunInlineToMacro + //ParseModSexp + //CompileModStage1 + //SymbolTableToTree + //BuildMacroLookupTable + //CompileFunctions + //CompileMod + + +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Operators.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Operators.cs new file mode 100644 index 0000000..c6e98eb --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Operators.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using System.Text; +using CLVMDotNet.CLVM; +using CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public static class Operators +{ + public static Tuple DoRead(SExp args) + { + var filename = args.First().AsAtom(); + string filepath = Encoding.UTF8.GetString(filename); + string s = File.ReadAllText(filepath); + var irSexp = SExp.To(IR.IRReader.ReadIR(s)); + var sexp = BinUtils.AssembleFromIR(irSexp); + return new Tuple(1, sexp); + } + + public static Tuple DoWrite(SExp args) + { + var filename = args.First().AsAtom(); + string filepath = Encoding.UTF8.GetString(filename); + string s = File.ReadAllText(filepath); + var irSexp = SExp.To(IR.IRReader.ReadIR(s)); + var sexp = BinUtils.AssembleFromIR(irSexp); + return new Tuple(1, sexp); + } + + + + public static SExp RunProgramProgramForSearchPaths(string[] searchPaths) + { + Dictionary BINDINGS = new Dictionary(); + BINDINGS["com"] = (op, sexp) => DoRead(sexp); + BINDINGS["opt"] = (op, sexp) => DoRead(sexp); + BINDINGS["_full_path_for_name"] = (op, sexp) => DoFullPathForName(sexp); + BINDINGS["_read"] = (op, sexp) => DoRead(sexp); + BINDINGS["_write"] = (op, sexp) => DoWrite(sexp); + + return null; + + Tuple DoFullPathForName(SExp args) + { + var filename = args.First().AsAtom(); + + foreach (string path in searchPaths) + { + string filePath = Path.Combine("", ""); + if (File.Exists(filePath)) + { + var sexp = SExp.To("test"); + return new Tuple(1, sexp); + } + } + throw new EvalError($"can't open {filename}"); + } + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/Optimize.cs b/CLVMDotNet/src/Tools/Stages/Stage2/Optimize.cs new file mode 100644 index 0000000..08de91e --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/Optimize.cs @@ -0,0 +1,282 @@ +using System.Text; +using CLVMDotNet.CLVM; +using CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public static class Optimize +{ + public static byte[] QUOTE_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["q"]; + public static byte[] APPLY_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["a"]; + public static byte[] FIRST_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["f"]; + public static byte[] REST_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["r"]; + public static byte[] CONS_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["c"]; + public static byte[] RAISE_ATOM => CLVM.Operators.KEYWORD_TO_ATOM()["x"]; + public static int DEBUG_OPTIMIZATIONS = 0; + public static SExp CONS_Q_A_OPTIMIZER_PATTERN => BinUtils.Assemble("(a (q . (: . sexp)) (: . args))"); + public static SExp CONS_PATTERN = BinUtils.Assemble("(c (: . first) (: . rest)))"); + public static SExp VAR_CHANGE_OPTIMIZER_CONS_EVAL_PATTERN = BinUtils.Assemble("(a (q . (: . sexp)) (: . args))"); + public static SExp CONS_OPTIMIZER_PATTERN_FIRST = BinUtils.Assemble("(f (c (: . first) (: . rest)))"); + public static SExp CONS_OPTIMIZER_PATTERN_REST = BinUtils.Assemble("(r (c (: . first) (: . rest)))"); + public static SExp FIRST_ATOM_PATTERN = BinUtils.Assemble("(f ($ . atom))"); + public static SExp REST_ATOM_PATTERN = BinUtils.Assemble("(r ($ . atom))"); + public static SExp QUOTE_PATTERN_1 = BinUtils.Assemble("(q . 0)"); + public static SExp APPLY_NULL_PATTERN_1 = BinUtils.Assemble("(a 0 . (: . rest))"); + + /// + /// This applies the transform `(a 0 ARGS)` => `0` + /// + /// + /// + /// + public static SExp ApplyNullOptimizer(SExp r, Func> eval) + { + Dictionary t1 = PatternMatch.Match(APPLY_NULL_PATTERN_1, r); // Define Match method accordingly + + if (t1 != null) + { + return SExp.To(0); + } + + return r; + } + + /// + /// This applies the transform + /// (f N) => A + /// and + /// (r N) => B + /// + /// + /// + /// + public static SExp PathOptimizer(SExp r, Func> eval) + { + Dictionary t1 = PatternMatch.Match(FIRST_ATOM_PATTERN, r); + + if (t1 != null && NonNil(t1["atom"])) + { + var nodeValue = t1["atom"].AsInt(); + NodePath node = new NodePath(nodeValue); + node = node.Add(NodePath.Left); + return SExp.To(node.AsShortPath()); + } + + t1 = PatternMatch.Match(REST_ATOM_PATTERN, r); + if (t1 != null && NonNil(t1["atom"])) + { + var nodeValue = t1["atom"].AsInt(); + NodePath node = new NodePath(nodeValue); + node = node.Add(NodePath.Right); + return SExp.To(node.AsShortPath()); + } + + return r; + } + + /// + /// Recursively apply optimizations to all non-quoted child nodes. + /// + /// + /// + /// + public static SExp ChildrenOptimizer(SExp r, Func> eval) + { + if (!r.Listp()) + { + return r; + } + var operatorSexp = r.First(); + if (!operatorSexp.Listp()) + { + var op = operatorSexp.AsAtom(); + if (op.SequenceEqual(QUOTE_ATOM)) + { + return r; + } + } + + List optimizedChildren = new List(); + foreach (SExp child in r.AsIter()) + { + //TODO: Write OptimizeSexp + //optimizedChildren.Add(OptimizeSexp(child, eval)); + } + + return SExp.To(optimizedChildren); + } + + + /// + /// This applies the transform + /// (f (c A B)) => A + /// and + /// (r (c A B)) => B + /// + /// + public static SExp ConsOptimizer(SExp r, Func> eval) + { + Dictionary t1 = PatternMatch.Match(CONS_OPTIMIZER_PATTERN_FIRST, r); + if (t1 != null) + { + return t1["first"]; + } + + t1 = PatternMatch.Match(CONS_OPTIMIZER_PATTERN_REST, r); + if (t1 != null) + { + return t1["rest"]; + } + + return r; + } + + + /// + /// This applies the transform `(q . 0)` => `0` + /// + /// + /// + /// + public static SExp QuoteNullOptimizer(SExp r, Func> eval) + { + Dictionary t1 = PatternMatch.Match(QUOTE_PATTERN_1, r); // Define Match method accordingly + + if (t1 != null) + { + return SExp.To(0); + } + + return r; + } + + + public static bool NonNil(SExp sexp) + { + return sexp.Listp() || (sexp.AsAtom().Length > 0); + } + + public static bool IsArgsCall(SExp r) + { + if (!r.Listp() && r.AsInt() == 1) + return true; + return false; + } + + public static bool SeemsConstant(SExp sexp) + { + if (!sexp.Listp()) + { + // note that `0` is a constant + return !NonNil(sexp); + } + + var operatorSexp = sexp.First(); + if (!operatorSexp.Listp()) + { + //TODO: test that a byte array can be compared to a byte + var asAtom = operatorSexp.AsAtom(); + if (asAtom.Equals(QUOTE_ATOM)) + { + return true; + } + + if (asAtom.Equals(RAISE_ATOM)) + { + return false; + } + } + else if (!SeemsConstant(operatorSexp)) + { + return false; + } + + return sexp.Rest().AsIter().All(childSexp => SeemsConstant(childSexp)); + } + + public static SExp PathFromArgs(SExp sexp, dynamic newArgs) + { + var v = sexp.AsInt(); + if (v <= 1) + { + return newArgs; + } + + sexp = SExp.To(v >> 1); + if ((v & 1) == 1) + { + return PathFromArgs(sexp, ConsR(newArgs)); + } + + return PathFromArgs(sexp, ConsF(newArgs)); + } + + public static SExp ConsF(SExp args) + { + Dictionary t = PatternMatch.Match(CONS_PATTERN, args); + + if (t != null && t.TryGetValue("first", out var f)) + { + return f; + } + + return SExp.To(new List { FIRST_ATOM, args }); + } + + public static SExp ConsR(SExp args) + { + Dictionary t = PatternMatch.Match(CONS_PATTERN, args); + + if (t != null && t.TryGetValue("rest", out var r)) + { + return r; + } + + return SExp.To(new List { REST_ATOM, args }); + } + + public static SExp SubArgs(SExp sexp, List newArgs) + { + if (sexp.Nullp() || !sexp.Listp()) + { + return PathFromArgs(sexp, newArgs); + } + + var first = sexp.First(); + if (first.Listp()) + { + first = SubArgs(first, newArgs); + } + else + { + var op = first.AsAtom(); + + if (op.Equals(QUOTE_ATOM)) + { + return sexp; + } + } + + List newSexp = new List { first }; + newSexp.AddRange(sexp.Rest().AsIter().Select(_ => SubArgs(_, newArgs))); + return SExp.To(newSexp); + } + + + //DoRead + //DoWrite + //RunProgramForSearchPaths + //SeemsConstant + //ConstantOptimizer + //IsArgsCall + //PathFromArgs + //SubArgs + //var_change_optimizer_cons_eval + //children_optimizer + //cons_optimizer + //path_optimizer + //def quote_null_optimizer(r, eval) + //apply_null_optimizer + //optimize_sexp + //make_do_opt +} \ No newline at end of file diff --git a/CLVMDotNet/src/Tools/Stages/Stage2/PatternMatch.cs b/CLVMDotNet/src/Tools/Stages/Stage2/PatternMatch.cs new file mode 100644 index 0000000..e3d5b72 --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage2/PatternMatch.cs @@ -0,0 +1,89 @@ +using System.Text; +using CLVMDotNet.CLVM; + +namespace CLVMDotNet.Tools.Stages.Stage2; + +public class PatternMatch +{ + public static byte[] ATOM_MATCH => Encoding.UTF8.GetBytes("$"); + public static byte[] SEXP_MATCH => Encoding.UTF8.GetBytes(":"); + + public static Dictionary? UnifyBindings(Dictionary bindings, byte[] newKey, + SExp newValue) + { + string newKeyString = System.Text.Encoding.UTF8.GetString(newKey); + if (bindings.ContainsKey(newKeyString)) + { + var obj = bindings[newKeyString]; + if (!obj.Equals(newValue)) + { + return null; + } + + return bindings; + } + + Dictionary newBindings = new Dictionary(bindings); + newBindings[newKeyString] = newValue; + return newBindings; + } + + public static Dictionary? Match(SExp pattern, SExp sexp, Dictionary knownBindings = null) + { + if (knownBindings == null) + { + knownBindings = new Dictionary(); + } + + if (!pattern.Listp()) + { + if (!sexp.Listp()) + { + return pattern.AsAtom() == sexp.AsAtom() ? knownBindings : null; + } + + return null; + } + + var left = pattern.First(); + var right = pattern.Rest(); + object atom = sexp.AsAtom(); + + if (left.AsAtom() == ATOM_MATCH) + { + if (!sexp.Listp()) + { + return null; + } + + if (right.AsAtom() == ATOM_MATCH) + { + return atom is string ? UnifyBindings(knownBindings, right.AsAtom(), sexp) : null; + } + + return UnifyBindings(knownBindings, right.AsAtom(), sexp); + } + + if (left.AsAtom() == SEXP_MATCH) + { + if (right.AsAtom() == SEXP_MATCH) + { + return atom is string ? UnifyBindings(knownBindings, right.AsAtom(), sexp) : null; + } + + return UnifyBindings(knownBindings, right.AsAtom(), sexp); + } + + if (!sexp.Listp()) + { + return null; + } + + Dictionary newBindings = Match(left, sexp.First(), knownBindings); + if (newBindings == null) + { + return newBindings; + } + return Match(right, sexp.Rest(), newBindings); + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytes.cs b/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytes.cs deleted file mode 100644 index e2e74c0..0000000 --- a/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytes.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Xunit; -using x = CLVMDotNet.CLVM; - -namespace CLVMDotNet.Tests.CLVM.HelperFunctions; - -[Trait("HelperFunctions","ConvertAtomToBytes")] -public class ConvertAtomToBytes -{ - -} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytesTests.cs b/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytesTests.cs new file mode 100644 index 0000000..1b3f16f --- /dev/null +++ b/CLVMDotNet/tests/CLVM/HelperFunctions/ConvertAtomToBytesTests.cs @@ -0,0 +1,25 @@ +using Xunit; +using x = CLVMDotNet.CLVM; + +namespace CLVMDotNet.Tests.CLVM.HelperFunctions; + +[Trait("HelperFunctions","ConvertAtomToBytes")] +public class ConvertAtomToBytesTests +{ + [Theory] + [InlineData("@", new byte[] { 0x40})] + [InlineData("unquote", new byte[] { 0x75, 0x6E, 0x71, 0x75, 0x6F, 0x74, 0x65})] + [InlineData("qq", new byte[] { 0x71, 0x71 })] + [InlineData("quote", new byte[] { 0x71, 0x75, 0x6F, 0x74, 0x65 })] + public void ConvertStringToBytes_returnsCorrectBytes(string val, byte[] expectedBytes) + { + // Arrange + + // Act + var bytes = x.HelperFunctions.ConvertAtomToBytes(val); + + // Assert + Assert.True(bytes.SequenceEqual(expectedBytes)); + + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObject.cs b/CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObjectTests.cs similarity index 79% rename from CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObject.cs rename to CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObjectTests.cs index 14bac6e..4862585 100644 --- a/CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObject.cs +++ b/CLVMDotNet/tests/CLVM/HelperFunctions/LooksLikeCLVMObjectTests.cs @@ -4,7 +4,7 @@ namespace CLVMDotNet.Tests.CLVM.HelperFunctions; [Trait("HelperFunctions","LooksLikeCLVMObject")] -public class LooksLikeCLVMObject +public class LooksLikeCLVMObjectTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/HelperFunctions/MSBMask.cs b/CLVMDotNet/tests/CLVM/HelperFunctions/MSBMaskTests.cs similarity index 96% rename from CLVMDotNet/tests/CLVM/HelperFunctions/MSBMask.cs rename to CLVMDotNet/tests/CLVM/HelperFunctions/MSBMaskTests.cs index 8992b5c..445fbc5 100644 --- a/CLVMDotNet/tests/CLVM/HelperFunctions/MSBMask.cs +++ b/CLVMDotNet/tests/CLVM/HelperFunctions/MSBMaskTests.cs @@ -5,7 +5,7 @@ namespace CLVMDotNet.Tests.CLVM.HelperFunctions { [Trait("HelperFunctions", "MSBMask")] - public class MSBMask + public class MSBMaskTests { [Theory] [InlineData(0x00, 0x00)] diff --git a/CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpType.cs b/CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpTypeTests.cs similarity index 82% rename from CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpType.cs rename to CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpTypeTests.cs index 4d08b0b..6eea954 100644 --- a/CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpType.cs +++ b/CLVMDotNet/tests/CLVM/HelperFunctions/ToSexpTypeTests.cs @@ -4,7 +4,7 @@ namespace CLVMDotNet.Tests.CLVM.HelperFunctions; [Trait("HelperFunctions","ToSexpType")] -public class ToSexpType +public class ToSexpTypeTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs b/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs index d95f7cb..748f2cc 100644 --- a/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs +++ b/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Numerics; using System.Text; using Xunit; @@ -12,55 +13,82 @@ public class OperatorTests { #region OpAdd - [Fact] - public void OpAdd() + [Theory] + [InlineData("1,2,10", new byte[] { 13 }, "1078")] + //[InlineData("12323232,211112323232323,1111468899990", new byte[] { 0x00, 0xC1, 0x04, 0x7A, 0x3A, 0x79 }, "1180")] + public void OpAdd(string numbersToAdd, byte[] resultAtom, string expectedCostStr) { // Arrange - + BigInteger expectedCost = BigInteger.Parse(expectedCostStr); + List ints = numbersToAdd.Split(',') + .Select(s => BigInteger.Parse(s.Trim())) + .ToList(); + + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x10 }, x.SExp.To(new List { 3, 4, 5 })); - + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x10 }, x.SExp.To(ints)); + // Assert + Assert.Equal(expectedCost, result.Item1); + Assert.True(result.Item2.AsAtom().SequenceEqual(resultAtom)); } #endregion - [Fact] - public void OpSubtract() + [Theory] + [InlineData("3,1", new byte[] { 2 }, "755")] + public void OpSubtract(string numbersToSubtract, byte[] resultAtom, string expectedCostStr) { // Arrange - + BigInteger expectedCost = BigInteger.Parse(expectedCostStr); + List ints = numbersToSubtract.Split(',') + .Select(s => BigInteger.Parse(s.Trim())) + .ToList(); + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x11 }, x.SExp.To(new List { 3, 1 })); - + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x11 }, x.SExp.To(ints)); + // Assert + Assert.Equal(expectedCost, result.Item1); + Assert.True(result.Item2.AsAtom().SequenceEqual(resultAtom)); } #region OpDivide - [Fact] - public void OpDivide() + [Theory] + [InlineData("10,2", new byte[] { 5 }, "1006")] + public void OpDivide(string numbersToDivide, byte[] resultAtom, string expectedCostStr) { // Arrange - + BigInteger expectedCost = BigInteger.Parse(expectedCostStr); + List ints = numbersToDivide.Split(',') + .Select(s => BigInteger.Parse(s.Trim())) + .ToList(); + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x13 }, x.SExp.To(ints)); // Assert + Assert.Equal(expectedCost, result.Item1); + Assert.True(result.Item2.AsAtom().SequenceEqual(resultAtom)); } [Fact] public void OpDivideThrowsExceptionIfDividingByZero() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 0 })) + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x13 }, x.SExp.To(new List { 10, 0 })) ); - + // Assert Assert.Contains("div with 0", errorMessage.Message); } @@ -69,37 +97,47 @@ public void OpDivideThrowsExceptionIfDividingByZero() public void OpDivideThrowsExceptionWithNegativeOperand1() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { -1, 5 })) + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x13 }, x.SExp.To(new List { -1, 5 })) ); - + // Assert Assert.Contains("div operator with negative operands is deprecated", errorMessage.Message); } #endregion - [Fact] - public void OpMultiply() + [Theory] + [InlineData("30,5", new byte[] { 0x00, 0x96 }, "1009")] + public void OpMultiply(string numbersToMultiply, byte[] resultAtom, string expectedCostStr) { // Arrange - + BigInteger expectedCost = BigInteger.Parse(expectedCostStr); + List ints = numbersToMultiply.Split(',') + .Select(s => BigInteger.Parse(s.Trim())) + .ToList(); + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 3 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x12 }, x.SExp.To(ints)); // Assert + Assert.Equal(expectedCost, result.Item1); + Assert.True(result.Item2.AsAtom().SequenceEqual(resultAtom)); } [Fact] public void OpDivMod() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x14 }, x.SExp.To(new List { 3, 5 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x14 }, x.SExp.To(new List { 3, 5 })); // Assert } @@ -108,9 +146,10 @@ public void OpDivMod() public void OpConcat() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x0E }, x.SExp.To(new List { "test", "ing" })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0E }, x.SExp.To(new List { "test", "ing" })); // Assert } @@ -123,13 +162,14 @@ public void OpConcat() public void OpSubstrThrowsWhenOutOfBounds(int startIndex) { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin", startIndex })) + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0C }, + x.SExp.To(new List { "kevin", startIndex })) ); - + // Assert Assert.Contains("invalid indices for substr", errorMessage.Message); } @@ -138,13 +178,14 @@ public void OpSubstrThrowsWhenOutOfBounds(int startIndex) public void OpSubstrThrowsWithTwoManyArgs() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin", 1, 2, 3 })) + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0C }, + x.SExp.To(new List { "kevin", 1, 2, 3 })) ); - + // Assert Assert.Contains("substr takes exactly 2 or 3 arguments", errorMessage.Message); } @@ -153,13 +194,14 @@ public void OpSubstrThrowsWithTwoManyArgs() public void OpSubstrThrowsWithTooFewArgs() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin" })) + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin" })) ); - + // Assert Assert.Contains("substr takes exactly 2 or 3 arguments", errorMessage.Message); } @@ -168,17 +210,19 @@ public void OpSubstrThrowsWithTooFewArgs() [InlineData("1", "somelongstring", 4, "longstring")] [InlineData("1", "somelongstring", 0, "somelongstring")] [InlineData("1", "somelongstring", 13, "g")] - public void OpSubstrReturnsSubStringWithCost(string stringCost, string val, int startindex, string expectedResult) + public void OpSubstrReturnsSubStringWithCost(string stringCost, string val, int startindex, + string expectedResult) { // Arrange BigInteger cost = BigInteger.Parse(stringCost); - + // Act var result = - x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { val, startindex })); + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { val, startindex })); var atom = result.Item2.AsAtom(); string text = Encoding.UTF8.GetString(atom); - + // Assert Assert.Equal(expectedResult, text); Assert.Equal(cost, result.Item1); @@ -194,13 +238,13 @@ public void OpSubstrReturnsSubStringOfNumberOfCharactersWithCost(string stringCo { // Arrange BigInteger cost = BigInteger.Parse(stringCost); - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x0C }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { val, startindex, endIndex })); var atom = result.Item2.AsAtom(); string text = Encoding.UTF8.GetString(atom!); - + // Assert Assert.Equal(expectedResult, text); Assert.Equal(cost, result.Item1); @@ -218,31 +262,30 @@ public void OpSubstrReturnsSubStringOfNumberOfCharactersWithCost(string stringCo public void OpStrLen(string val, int length, int cost) { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x0D }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0D }, x.SExp.To(new List { val })); var atom = result.Item2.AsAtom(); var actualLength = new BigInteger(atom!); - + // Assert Assert.Equal(length, actualLength); Assert.Equal(cost, result.Item1); } - [Fact] public void OpStrLenThrowsWithTooManyArgs() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0D }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0D }, x.SExp.To(new List { "THIS", "WILL THROW AN EXCEPTION" })) ); - + // Assert Assert.Contains("strlen takes exactly 1 argument", errorMessage.Message); } @@ -251,29 +294,32 @@ public void OpStrLenThrowsWithTooManyArgs() public void OpStrLenThrowsIfPair() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0D }, x.SExp.To(new List { 3, 1 })) + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0D }, x.SExp.To(new List { 3, 1 })) ); - + // Assert Assert.Contains("strlen takes exactly 1 argument", errorMessage.Message); } #endregion + #region OpSHA256 [Fact] public void OpSHA256() { // Arrange - + // Act var result = - x.Operator.ApplyOperator(new byte[] { 0x0B }, x.SExp.To(new List { "THIS IS A SHA256 HASH" })); + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0B }, + x.SExp.To(new List { "THIS IS A SHA256 HASH" })); var atom = result.Item2.AsAtom(); // Assert @@ -289,69 +335,73 @@ public void OpSHA256() public void OpSHA256OnList_ThrowsError() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0B }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0B }, x.SExp.To(new List { "SOME", "ERror" }))); - + // Assert Assert.Contains("sha256 on list", errorMessage.Message); } #endregion - + #region GrBytes + [Theory] [InlineData(119, "a", "b")] [InlineData(131, "testing", "testing")] public void OpGrBytesReturnsFalse(int expectedCost, string val1, string val2) { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); var areEqual = x.SExp.False.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(expectedCost, result.Item1); } - + [Theory] [InlineData(119, "b", "a")] public void OpGrBytesReturnsTrue(int expectedCost, string val1, string val2) { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); var areEqual = x.SExp.True.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(expectedCost, result.Item1); } - + [Fact] public void OpGrBytesThrowsWithMoreThanTwoParameters() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x0A }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { "val1", "val", "val3" }))); - + // Assert Assert.Contains(">s takes exactly 2 arguments", errorMessage.Message); } - + //TODO: Add test to throw when OpGrBytes is called on a pair + #endregion - + #region OpGr [Theory] @@ -362,16 +412,17 @@ public void OpGrReturnsFalse(int expectedCost, string strVal1, string strVal2, b // Arrange BigInteger val1 = BigInteger.Parse(strVal1); BigInteger val2 = BigInteger.Parse(strVal2); - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); var areEqual = x.SExp.False.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(expectedCost, result.Item1); } - + [Theory] [InlineData(502, "4", "2", true)] [InlineData(502, "-1", "2", true, @@ -381,11 +432,12 @@ public void OpGrReturnsTrue(int expectedCost, string strVal1, string strVal2, bo // Arrange BigInteger val1 = BigInteger.Parse(strVal1); BigInteger val2 = BigInteger.Parse(strVal2); - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); var areEqual = x.SExp.True.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(expectedCost, result.Item1); @@ -395,12 +447,13 @@ public void OpGrReturnsTrue(int expectedCost, string strVal1, string strVal2, bo public void OpGrThrowIfMoreThan2ArgumentsPassed() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1, 2, 3 }))); - + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1, 2, 3 }))); + // Assert Assert.Contains("> takes exactly 2 arguments", errorMessage.Message); } @@ -409,12 +462,13 @@ public void OpGrThrowIfMoreThan2ArgumentsPassed() public void OpGrThrowIfLessThan2ArgumentsPassed() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1 }))); - + x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1 }))); + // Assert Assert.Contains("> takes exactly 2 arguments", errorMessage.Message); } @@ -427,12 +481,12 @@ public void OpGrThrowIfLessThan2ArgumentsPassed() public void OpEqReturnsTrueWhenTwoStringsMatch() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x09 }, x.SExp.To(new List { "SomeString", "SomeString" })); var areEqual = x.SExp.True.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(137, result.Item1); @@ -442,12 +496,12 @@ public void OpEqReturnsTrueWhenTwoStringsMatch() public void OpEqReturnsFalseWhenTwoStringsDoNotMatch() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x09 }, x.SExp.To(new List { "val1", "DOTNOTMATCH" })); var areEqual = x.SExp.False.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(132, result.Item1); @@ -457,12 +511,12 @@ public void OpEqReturnsFalseWhenTwoStringsDoNotMatch() public void OpEqReturnTrueWhenTwoEmptyStringsMatchMatch() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x09 }, x.SExp.To(new List { "", x.SExp.To(new List()) })); var areEqual = x.SExp.True.Equals(result.Item2); - + // Assert Assert.True(areEqual); Assert.Equal(117, result.Item1); @@ -472,13 +526,13 @@ public void OpEqReturnTrueWhenTwoEmptyStringsMatchMatch() public void OpEqThrowsWhenMoreThanTwoArguments() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x09 }, x.SExp.To(new List { "1", "1", x.SExp.To("") }))); - + // Assert Assert.Contains("= takes exactly 2 arguments", errorMessage.Message); } @@ -488,19 +542,20 @@ public void OpEqThrowsWhenMoreThanTwoArguments() public void OpEqThrowsWhenLessThanTwoArguments() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x09 }, x.SExp.To(new List { "SOMESTRING" }))); - + // Assert Assert.Contains("= takes exactly 2 arguments", errorMessage.Message); } #endregion + #region OpLogand [Fact] @@ -509,7 +564,7 @@ public void OpLogAndInt() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { 15, 244 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { 15, 244 })); var atom = result.Item2.AsAtom(); // Assert @@ -526,7 +581,7 @@ public void OpLogEmptyList() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { })); var atom = result.Item2.AsAtom(); // Assert @@ -547,7 +602,7 @@ public void OpLogior() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { })); var atom = result.Item2.AsAtom(); // Assert @@ -563,7 +618,7 @@ public void OpLogiorInt() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { 35, 689 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { 35, 689 })); var atom = result.Item2.AsAtom(); // Assert @@ -584,7 +639,7 @@ public void OpLogxor() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x1A }, x.SExp.To(new List { })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1A }, x.SExp.To(new List { })); var atom = result.Item2.AsAtom(); // Assert @@ -600,7 +655,7 @@ public void OpLogxorInt() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x1A }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1A }, x.SExp.To(new List { 111111, 67452345657 })); var atom = result.Item2.AsAtom(); @@ -622,7 +677,7 @@ public void OpLogNot() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { 1 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { 1 })); var atom = result.Item2.AsAtom(); // Assert @@ -639,7 +694,7 @@ public void OpLogNotNegativeNumbers() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { -1111 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { -1111 })); var atom = result.Item2.AsAtom(); // Assert @@ -658,7 +713,7 @@ public void OpLogNotThrowsWithNoParameters() // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x1B }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { }))); // Assert @@ -680,7 +735,7 @@ public void OpPubKeyForExp() // Act var result = - x.Operator.ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { "this is a test" })); + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { "this is a test" })); var atom = result.Item2.AsAtom(); // Assert @@ -701,7 +756,7 @@ public void OpPubKeyThrowsWithNoArgumentsp() // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { }))); + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { }))); // Assert Assert.Contains("pubkey_for_exp takes exactly 1 arguments", errorMessage.Message); @@ -717,7 +772,7 @@ public void OpAnyReturnsTrueIfListIsNotEmpty() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x21 }, x.SExp.To(new List { 1 })); var areEqual = x.SExp.True.Equals(result.Item2); @@ -732,7 +787,7 @@ public void OpAnyReturnsTrueWithMoreThanOneArg() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x21 }, x.SExp.To(new List { 1, 3, 4, 5 })); var areEqual = x.SExp.True.Equals(result.Item2); @@ -747,7 +802,7 @@ public void OpAnyReturnsFalseWithNoArgs() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x21 }, x.SExp.To(new List { })); var areEqual = x.SExp.False.Equals(result.Item2); @@ -766,7 +821,7 @@ public void OpAllAtomsReturnsTrue() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { 1, 2, 3 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { 1, 2, 3 })); var areEqual = x.SExp.True.Equals(result.Item2); // Assert @@ -780,7 +835,7 @@ public void OpAllWithEmptyAtomsReturnsTrue() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { })); var areEqual = x.SExp.True.Equals(result.Item2); // Assert @@ -794,7 +849,7 @@ public void OpAllWithPairReturnsFalse() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { "+", 1, 2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { "+", 1, 2 })); var areEqual = x.SExp.True.Equals(result.Item2); // Assert @@ -812,7 +867,7 @@ public void OpNotNoneEmptyBytes() // Arrange // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01 })); + var result = x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01 })); var areEqual = x.SExp.False.Equals(result.Item2); // Assert @@ -828,7 +883,7 @@ public void OpNotThrowsIfEmptyBytes() // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { }))); + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { }))); // Assert Assert.Contains("not takes exactly 1 arguments", errorMessage.Message); @@ -841,10 +896,10 @@ public void OpNotThrowsIfMoreThanOneByte() // Act var s = x.SExp.To(new byte[] { 0x01, 0x01 }); - + var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01, 0x01 }))); + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01, 0x01 }))); // Assert Assert.Contains("not takes exactly 1 arguments", errorMessage.Message); @@ -852,15 +907,17 @@ public void OpNotThrowsIfMoreThanOneByte() #endregion + #region OpAsh [Fact] public void OpAsh() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x16 }, x.SExp.To(new List { 1, 2 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x16 }, x.SExp.To(new List { 1, 2 })); // Assert Assert.Equal(new byte[] { 0x04 }, result.Item2.AsAtom()); @@ -871,11 +928,11 @@ public void OpAsh() public void OpAshThrowsWhenMoreThanTwoArguments() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x16 }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x16 }, x.SExp.To(new List { 1, 2, 4 }))); Assert.Contains("ash takes exactly 2 arguments", errorMessage.Message); } @@ -884,13 +941,13 @@ public void OpAshThrowsWhenMoreThanTwoArguments() public void OpAshThrowsWhenLessThanTwoArguments() { // Arrange - + // Act var errorMessage = Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x16 }, + x.OperatorDict.OPERATOR_LOOKUP().ApplyOperator(new byte[] { 0x16 }, x.SExp.To(new List { 1, 2, 4 }))); - + // Assert Assert.Contains("ash takes exactly 2 arguments", errorMessage.Message); } @@ -903,9 +960,10 @@ public void OpAshThrowsWhenLessThanTwoArguments() public void OpLsh() { // Arrange - + // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x17 }, x.SExp.To(new List { 1, 45 })); + var result = x.OperatorDict.OPERATOR_LOOKUP() + .ApplyOperator(new byte[] { 0x17 }, x.SExp.To(new List { 1, 45 })); // Assert Assert.Equal(new byte[] { 32, 0, 0, 0, 0, 0 }, result.Item2.AsAtom()); @@ -914,63 +972,65 @@ public void OpLsh() #endregion - #region OpDefaultUnknown - - [Fact] - public void UnsupportedOpThrowsException() - { - // Arrange - - // Act - var errorMessage = - Assert.Throws(() => - x.Operator.ApplyOperator(new byte[] { 0x3a }, - x.SExp.NULL)); - - // Assert - Assert.Contains("3A Operator not found or is unsupported!", errorMessage.Message); - } - - [Fact] - public void OpDefaultUnknownAtom() - { - // Arrange - - // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x02 }, x.SExp.To(new List { 1, 2 })); - - // Assert - Assert.Null(result.Item2.AsAtom()); - Assert.Equal(1, result.Item1); - } - - [Fact] - public void OpDefaultUnknownQuote() - { - // Arrange - - // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x01 }, x.SExp.To(new List { 1, 2 })); - - // Assert - Assert.Null(result.Item2.AsAtom()); - Assert.Equal(1, result.Item1); - } - - [Fact] - public void OpDefaultUnknownDot() - { - //Arrange - - // Act - var result = x.Operator.ApplyOperator(new byte[] { 0x23 }, x.SExp.To(new List { 1, 2 })); - - // Assert - Assert.Null(result.Item2.AsAtom()); - Assert.Equal(1, result.Item1); - } - - //TODO: Determine how apply operator can be called to call OpdefaultUnknown with op other than 0x01,0x02,0x23 - #endregion +// +// #region OpDefaultUnknown +// +// [Fact] +// public void UnsupportedOpThrowsException() +// { +// // Arrange +// +// // Act +// var errorMessage = +// Assert.Throws(() => +// x.Operator.ApplyOperator(new byte[] { 0x3a }, +// x.SExp.NULL)); +// +// // Assert +// Assert.Contains("3A Operator not found or is unsupported!", errorMessage.Message); +// } +// +// [Fact] +// public void OpDefaultUnknownAtom() +// { +// // Arrange +// +// // Act +// var result = x.Operator.ApplyOperator(new byte[] { 0x02 }, x.SExp.To(new List { 1, 2 })); +// +// // Assert +// Assert.Null(result.Item2.AsAtom()); +// Assert.Equal(1, result.Item1); +// } +// +// [Fact] +// public void OpDefaultUnknownQuote() +// { +// // Arrange +// +// // Act +// var result = x.Operator.ApplyOperator(new byte[] { 0x01 }, x.SExp.To(new List { 1, 2 })); +// +// // Assert +// Assert.Null(result.Item2.AsAtom()); +// Assert.Equal(1, result.Item1); +// } +// +// [Fact] +// public void OpDefaultUnknownDot() +// { +// //Arrange +// +// // Act +// var result = x.Operator.ApplyOperator(new byte[] { 0x23 }, x.SExp.To(new List { 1, 2 })); +// +// // Assert +// Assert.Null(result.Item2.AsAtom()); +// Assert.Equal(1, result.Item1); +// } +// +// //TODO: Determine how apply operator can be called to call OpdefaultUnknown with op other than 0x01,0x02,0x23 +// #endregion +// } } } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs b/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs index 76ac69b..0ca2384 100644 --- a/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs +++ b/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs @@ -1,24 +1,49 @@ using Xunit; -using x = CLVMDotNet.CLVM.Operator; +using x = CLVMDotNet.CLVM; namespace CLVMDotNet.Tests.CLVM.Operators; public class KeywordFromAtomTests { [Theory] - [InlineData(0x23, ".")] - [InlineData(0x02, "a")] - [InlineData(0x01, "q")] - [InlineData(0x03, "i")] - [InlineData(0x04, "c")] - [InlineData(0x05, "f")] + [InlineData(01, "q")] + [InlineData(2, "a")] + [InlineData(3, "i")] + [InlineData(4, "c")] + [InlineData(5, "f")] [InlineData(0x06, "r")] [InlineData(0x07, "l")] [InlineData(0x08, "x")] + [InlineData(0x0b, "sha256")] + [InlineData(0x0c, "substr")] + [InlineData(0x0d, "strlen")] + [InlineData(0x0e, "concat")] + [InlineData(0x0f, ".")] + [InlineData(0x10, "+")] + [InlineData(0x11, "-")] + [InlineData(0x12, "*")] + [InlineData(0x13, "/")] + [InlineData(0x14, "divmod")] + [InlineData(0x15, ">")] + [InlineData(0x16, "ash")] + [InlineData(0x17, "lsh")] + [InlineData(0x18, "logand")] + [InlineData(0x19, "logior")] + [InlineData(0x1a, "logxor")] + [InlineData(0x1b, "lognot")] + [InlineData(0x1c, ".")] + [InlineData(0x1d, "point_add")] + [InlineData(0x1e, "pubkey_for_exp")] + [InlineData(0x1f, ".")] + [InlineData(0x20, "not")] + [InlineData(0x21, "any")] + [InlineData(0x22, "all")] + [InlineData(0x23, ".")] public void KeywordToAtom_Returns_correct_byte(byte atom, string expectedKeyword) { - var result = x.KEYWORD_FROM_ATOM(atom); - Assert.Equal(result, expectedKeyword); + var bytes = new byte[] { atom }; + var result = x.Operators.KEYWORD_FROM_ATOM[bytes]; + Assert.Equal(expectedKeyword, result); } [Fact] @@ -28,10 +53,10 @@ public void UnknownAtom_ThrowsError() // Act var errorMessage = - Assert.Throws(() => - x.KEYWORD_FROM_ATOM(0xaa)); + Assert.Throws(() => + x.Operators.KEYWORD_FROM_ATOM[new byte[] {0xaa}]); // Assert - Assert.Contains("Invalid Atom", errorMessage.Message); + Assert.Contains("was not present in the dictionary", errorMessage.Message); } } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs b/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs index 081195b..3e8eb4c 100644 --- a/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs +++ b/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs @@ -1,24 +1,49 @@ using Xunit; -using clvm = CLVMDotNet.CLVM.Operator; +using x = CLVMDotNet.CLVM; -namespace CLVMDotNet.Tests.CLVM.Operators; +namespace CLVMDotNet.Tests.CLVM; public class KeywordToAtomTests { [Theory] - [InlineData(".", 0x23)] - [InlineData("a", 0x02)] + + [InlineData("q", 0x01)] + [InlineData("a", 0x02)] [InlineData("i", 0x03)] [InlineData("c", 0x04)] [InlineData("f", 0x05)] [InlineData("r", 0x06)] [InlineData("l", 0x07)] [InlineData("x", 0x08)] + [InlineData("=", 0x09)] + [InlineData("sha256", 0x0b)] + [InlineData("substr", 0x0c)] + [InlineData("strlen", 0x0d)] + [InlineData("concat", 0x0e)] + [InlineData("+", 0x10)] + [InlineData("-", 0x11)] + [InlineData("*", 0x12)] + [InlineData("/", 0x13)] + [InlineData("divmod", 0x14)] + [InlineData(">", 0x15)] + [InlineData("ash", 0x16)] + [InlineData("lsh", 0x17)] + [InlineData("logand", 0x18)] + [InlineData("logior", 0x19)] + [InlineData("logxor", 0x1a)] + [InlineData("lognot", 0x1b)] + [InlineData("point_add", 0x1d)] + [InlineData("pubkey_for_exp", 0x1e)] + [InlineData("not", 0x20)] + [InlineData("any", 0x21)] + [InlineData("all", 0x22)] + [InlineData(".", 0x23)] public void KeywordToAtom_Returns_correct_byte(string keyword, byte expectedByte) { - var result = clvm.KEYWORD_TO_ATOM(keyword); - Assert.Equal(result, expectedByte); + var bytes = new byte[] { expectedByte }.Reverse(); + var result = x.Operators.KEYWORD_TO_ATOM()[keyword]; + Assert.True(bytes.SequenceEqual(result)); } [Fact] @@ -28,11 +53,11 @@ public void UnknownKeyword_ThrowsError() // Act var errorMessage = - Assert.Throws(() => - clvm.KEYWORD_TO_ATOM("SomeInvalidKeyword")); + Assert.Throws(() => + x.Operators.KEYWORD_TO_ATOM()["SomeInvalidKeyword"]); // Assert - Assert.Contains("Invalid Keyword", errorMessage.Message); + Assert.Contains("given key 'SomeInvalidKeyword' was not present in the dictionary", errorMessage.Message); } } diff --git a/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs b/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs new file mode 100644 index 0000000..510c786 --- /dev/null +++ b/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs @@ -0,0 +1,19 @@ +// using CLVMDotNet.CLVM; +// using Xunit; +// +// namespace CLVMDotNet.Tests.CLVM.Operators; +// +// public class OperatorDictTests +// { +// [Fact] +// public void TestConstructor1() +// { +// var d = new Dictionary() +// { +// { "1", "hello"}, +// { "2", "goodbye" }, +// }; +// +// Assert.Throws(() => new OperatorDict(d, null)); +// } +// } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/SExp/To.cs b/CLVMDotNet/tests/CLVM/SExp/To.cs index 49e9d90..bc82921 100644 --- a/CLVMDotNet/tests/CLVM/SExp/To.cs +++ b/CLVMDotNet/tests/CLVM/SExp/To.cs @@ -15,7 +15,7 @@ public void builds_correct_tree() // Act var s = clvm.SExp.To(new List { "+", 1, 2 }); - var tree = Common.PrintTree(s); + var tree = clvm.HelperFunctions.PrintTree(s); // Assert Assert.Equal("(43 (1 (2 () )))", tree); @@ -31,7 +31,7 @@ public void test_case_1() var t1 = clvm.SExp.To(new List { 1, sexp }); // Assert - Common.ValidateSExp(t1); + clvm.HelperFunctions.ValidateSExp(t1); } [Fact] @@ -69,7 +69,7 @@ public void TestListConversions() // Act var a = clvm.SExp.To(new List { 1, 2, 3 }); string expectedOutput = "(1 (2 (3 () )))"; - string result = Common.PrintTree(a); + string result = clvm.HelperFunctions.PrintTree(a); // Assert Assert.Equal(expectedOutput, result); @@ -135,10 +135,10 @@ public void arbitrary_underlying_tree() var tree3 = clvm.SExp.To(gentree3); // Assert - Assert.Equal(Common.PrintTree(tree1), + Assert.Equal(clvm.HelperFunctions.PrintTree(tree1), "(((((0 1 )(2 3 ))((4 5 )(6 7 )))(((8 9 )(10 11 ))((12 13 )(14 15 ))))((((16 17 )(18 19 ))((20 21 )(22 23 )))(((24 25 )(26 27 ))((28 29 )(30 31 )))))"); - Assert.Equal(Common.PrintTree(tree2), "(((0 1 )(2 3 ))((4 5 )(6 7 )))"); - Assert.Equal(Common.PrintTree(tree3), "(((10 11 )(12 13 ))((14 15 )(16 17 )))"); + Assert.Equal(clvm.HelperFunctions.PrintTree(tree2), "(((0 1 )(2 3 ))((4 5 )(6 7 )))"); + Assert.Equal(clvm.HelperFunctions.PrintTree(tree3), "(((10 11 )(12 13 ))((14 15 )(16 17 )))"); } } } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVMDotNet.Tests.csproj b/CLVMDotNet/tests/CLVMDotNet.Tests.csproj index 58ad867..c1a56fe 100644 --- a/CLVMDotNet/tests/CLVMDotNet.Tests.csproj +++ b/CLVMDotNet/tests/CLVMDotNet.Tests.csproj @@ -12,6 +12,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CLVMDotNet/tests/Common.cs b/CLVMDotNet/tests/Common.cs index a672452..ee6b95f 100644 --- a/CLVMDotNet/tests/Common.cs +++ b/CLVMDotNet/tests/Common.cs @@ -4,104 +4,5 @@ namespace CLVMDotNet.Tests { public static class Common { - #region test helpers that should probably go into SExp object - - public static string PrintLeaves(clvm.SExp tree) - { - var a = tree.AsAtom(); - if (a != null) - { - if (a.Length == 0) - return "() "; - - return $"{a[0]} "; - } - - var ret = ""; - var pairs = tree.AsPair(); - var list = new List() { pairs.Item1, pairs.Item2 }; - if (pairs != null) - { - foreach (clvm.SExp i in list) - { - ret += PrintLeaves(i); - } - } - - return ret; - } - - public static string PrintTree(clvm.SExp tree) - { - var a = tree.AsAtom(); - if (a != null) - { - if (a.Length == 0) - { - return "() "; - } - - return $"{a[0]} "; - } - - var ret = "("; - var pairs = tree.AsPair(); - var list = new List() { pairs!.Item1, pairs.Item2 }; - if (pairs != null) - { - foreach (var i in list) - { - ret += PrintTree(i); - } - } - - ret += ")"; - return ret; - } - - public static void ValidateSExp(clvm.SExp sexp) - { - Stack validateStack = new Stack(); - validateStack.Push(sexp); - - while (validateStack.Count > 0) - { - dynamic v = validateStack.Pop(); - - if (!(v is clvm.SExp)) - { - throw new InvalidOperationException("v is not an instance of SExp"); - } - - if (v.Pair != null) - { - if (v.Pair.GetType() != typeof(Tuple)) - { - throw new InvalidOperationException("v.pair is not a Tuple"); - } - - Tuple pair = v.Pair; - - if (!clvm.HelperFunctions.LooksLikeCLVMObject(pair.Item1) || - !clvm.HelperFunctions.LooksLikeCLVMObject(pair.Item2)) - { - throw new InvalidOperationException("One or both elements do not look like CLVM objects"); - } - - var sPair = v.AsPair(); - validateStack.Push(sPair.Item1); - validateStack.Push(sPair.Item2); - } - else - { - if (!(v.Atom is byte[])) - { - throw new InvalidOperationException("v.atom is not a byte array"); - } - } - } - } - - #endregion } } \ No newline at end of file diff --git a/CLVMDotNet/tests/Helpers/JSONHelpers.cs b/CLVMDotNet/tests/Helpers/JSONHelpers.cs new file mode 100644 index 0000000..99fb269 --- /dev/null +++ b/CLVMDotNet/tests/Helpers/JSONHelpers.cs @@ -0,0 +1,54 @@ +using CLVMDotNet.CLVM; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace CLVMDotNet.Tests.Helpers; + +public static class JSONHelpers +{ + public static string ConvertToJSON(this SExp sexp) + { + return JsonConvert.SerializeObject(sexp); + } + + public static async Task AssertExpectedJSONResult(this SExp sexp, Object expectObject) + { + var targetJObject = JObject.FromObject(expectObject); + var sourceJObject = JObject.FromObject(sexp); + + if (!JToken.DeepEquals(sourceJObject, targetJObject)) + { + //actual doesn't match expected properties, values + foreach (var targetProp in targetJObject.Properties()) + { + JProperty source = sourceJObject.Property(targetProp.Name); + if (source == null) + { + throw new Exception($"expceted property: {targetProp.Name} - {targetProp.Value} but not present"); + } + + if (!JToken.DeepEquals(source.Value, targetProp.Value)) + { + throw new Exception($"{targetProp.Name} value is {source.Value}, expected {targetProp.Value} but got { source.Value}"); + } + } + + //extra property on response + foreach (var sourceProp in sourceJObject.Properties()) + { + JProperty source = targetJObject.Property(sourceProp.Name); + if (source == null) + { + throw new Exception($"extra property on response : {sourceProp.Name} - {sourceProp.Value}"); + } + } + + } + else + { + Console.WriteLine("Objects are same"); + } + Assert.Equal(1, 1); + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/BinUtils/AssembleFromIRTests.cs b/CLVMDotNet/tests/Tools/BinUtils/AssembleFromIRTests.cs new file mode 100644 index 0000000..c8fabb8 --- /dev/null +++ b/CLVMDotNet/tests/Tools/BinUtils/AssembleFromIRTests.cs @@ -0,0 +1,7 @@ +namespace CLVMDotNet.Tests.Tools.BinUtils +{ + public class AssembleFromIRTests + { + + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/BinUtils/TypeForAtomTests.cs b/CLVMDotNet/tests/Tools/BinUtils/TypeForAtomTests.cs index ac7f849..3f3da89 100644 --- a/CLVMDotNet/tests/Tools/BinUtils/TypeForAtomTests.cs +++ b/CLVMDotNet/tests/Tools/BinUtils/TypeForAtomTests.cs @@ -10,15 +10,40 @@ namespace CLVMDotNet.Tests.Tools.BinUtils [Trait("BinUtils", "TypeForAtom")] public class TypeForAtomTests { - // [Theory] - // [InlineData(4736344, "abdde")] //Quote - // [InlineData(4736344, "134567")] //Hex - // [InlineData(4736344,"23456")] //Int - // public void TestTypeForAtomReturnsCorrectType(BigInteger type, string value) - // { - // byte[] bytes = Encoding.UTF8.GetBytes(value); - // var t = x.BinUtils.TypeForAtom(bytes); - // Assert.Equal(type, t); - // } + [Theory] + [InlineData("this is a test")] + [InlineData("178884")] + [InlineData("aaaa")] + [InlineData("100")] + [InlineData("126")] + public void TypeForAtomReturnsQuote(string value) + { + // Arrange + byte[] bytes = Encoding.UTF8.GetBytes(value); + + // Act + var t = x.BinUtils.TypeForAtom(bytes); + + // Assert + Assert.Equal(IRType.QUOTES, t); + } + + [Theory] + [InlineData("1")] + [InlineData("4")] + [InlineData("99")] + public void TypeForAtomReturnsInt(string value) + { + // Arrange + byte[] bytes = Encoding.UTF8.GetBytes(value); + + // Act + var t = x.BinUtils.TypeForAtom(bytes); + + // Assert + Assert.Equal(IRType.INT, t); + } + + //TODO: ADD Test to check that IRType.HEX is returned } } \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs b/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs index 706f7c8..6b49097 100644 --- a/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs +++ b/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs @@ -1,3 +1,5 @@ +using CLVMDotNet.CLVM; +using CLVMDotNet.Tests.Helpers; using Xunit; namespace CLVMDotNet.Tests.Tools.Clvmc; @@ -8,5 +10,7 @@ public class CompileCLVMText public void RunBasicProgram() { var result = CLVMDotNet.Tools.IR.Clvmc.CompileCLVMText("(/ 10 2)", Array.Empty()); + + var rs = result; } } \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/IrNewTests.cs b/CLVMDotNet/tests/Tools/IRReader/IrNewTests.cs new file mode 100644 index 0000000..6c16c54 --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/IrNewTests.cs @@ -0,0 +1,28 @@ +using Xunit; +using x = CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tests.Tools.IRReader +{ + + public class IrNewTests + { + [Fact] + public void IrNewWithValidIntegerAndOffset() + { + // arrange + var val = "100"; + var offset = 1; + + // act + var result = x.Utils.IrNew(x.IRType.INT, val, offset); + + // assert + Assert.Null(result.Atom); + Assert.NotNull(result.Pair); + Assert.NotNull(result.Pair.Item1); + Assert.True(result.AsPair()!.Item1.AsPair()!.Item1.Atom!.SequenceEqual(new byte[] { 73, 78, 84 })); //INT + Assert.True(result.AsPair()!.Item1.AsPair()!.Item2.Atom!.SequenceEqual(new byte[] { 0x01 })); //1 (offset) + Assert.True(result.AsPair()!.Item2.Atom!.SequenceEqual(new byte[] {49,48,48})); //100 + } + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/ReadIRTests.cs b/CLVMDotNet/tests/Tools/IRReader/ReadIRTests.cs index 4104130..8c5787f 100644 --- a/CLVMDotNet/tests/Tools/IRReader/ReadIRTests.cs +++ b/CLVMDotNet/tests/Tools/IRReader/ReadIRTests.cs @@ -13,7 +13,7 @@ public void IRReader_ignores_comments() // Arrange var script_source = "(equal 7 (+ 5 ;foo bar\n 2))"; var expected_output = "(equal 7 (+ 5 2))"; - + // Act var t = x.IRReader.ReadIR(script_source); var s = x.IRReader.ReadIR(expected_output); @@ -24,5 +24,46 @@ public void IRReader_ignores_comments() //character offsets from source. Assert.False(areEqual); } + + [Fact] + public void ReadIREmptyStringThrowsError() + { + // Arrange + + // Act + var errorMessage = + Assert.Throws(() => + x.IRReader.ReadIR("")); + + // Assert + Assert.Contains("unexpected end of stream", errorMessage.Message); + } + + [Fact(Skip = "Skipping for now!")] + public void ConsList() + { + // Arrange + string sexp_source = "foo"; + + // Act + var sexp = x.IRReader.ReadIR(sexp_source); + + // Assert + var s = sexp; + } + + [Fact] + + public void ConsList1() + { + // Arrange + string sexp_source = "(/ 10 2)"; + + // Act + var sexp = x.IRReader.ReadIR(sexp_source); + + // Assert + var s = sexp; + } } } \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenStreamTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenStreamTests.cs index db38dfa..60df0f6 100644 --- a/CLVMDotNet/tests/Tools/IRReader/TokenStreamTests.cs +++ b/CLVMDotNet/tests/Tools/IRReader/TokenStreamTests.cs @@ -13,6 +13,7 @@ public class TokenStreamTests [InlineData("(equal 7 (+ 5 (+ 1 5)))", 13)] [InlineData("(- 5 4)", 5)] [InlineData("(- 5 (- 6 (- 5 (- 5 6))))", 17)] + [InlineData("(/ 10 2)", 5)] public void TokenStreamReader_matches_python_stream_length(string input, int lengthOfStream) { // Arrange diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeConsTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeConsTests.cs new file mode 100644 index 0000000..dd99795 --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeConsTests.cs @@ -0,0 +1,55 @@ +using Xunit; +using x = CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tests.Tools.IRReader +{ + + public class TokenizeConsTests + { + [Fact] + public void TokenizeCons_ClosingBracket() + { + // Arrange + var stream = x.IRReader.TokenStream("(+ 1 3)"); + + // Act + var tokenized = x.IRReader.TokenizeCons(")", 6, stream.GetEnumerator()); + + // Assert + Assert.Null(tokenized!.Atom); + Assert.NotNull(tokenized.Pair); + Assert.NotNull(tokenized.AsPair()!.Item1); + Assert.Null(tokenized.AsPair()!.Item1.Atom); + Assert.NotNull(tokenized.AsPair()!.Item1.Pair); + Assert.NotNull(tokenized.AsPair()!.Item1.AsPair()); + Assert.True(tokenized.AsPair()!.Item1.AsPair()!.Item1.Atom!.SequenceEqual(new byte[] { 78, 85, 76, 76 })); + Assert.True(tokenized.AsPair()!.Item2.Atom!.SequenceEqual(new byte[] { })); + } + + // [Theory] + // [InlineData("1", new byte[] { 0x01})] + // [InlineData("256", new byte[] { 0x01, 0x00 })] + // [InlineData("12345678910", new byte[] { 0x02, 0xdf, 0xdc, 0x1c, (byte)'>'})] + // [InlineData("1234567891012345678910", new byte[] { 0x42, 0xed, 0x12, 0x3b, 0xda, 0xce, 0x08, 0x4c, (byte)'>' })] + // public void TokenizeInt_Builds_CorrectObject(string number, byte[] expectedBytes) + // { + // // Arrange + // + // // Act + // var tokenized = x.IRReader.TokenizeInt(number, 0); + // + // // Assert + // Assert.Null(tokenized.Atom); + // Assert.NotNull(tokenized.Pair); + // Assert.Null(tokenized.AsPair().Item1.Atom); + // Assert.NotNull(tokenized.AsPair().Item1); + // Assert.NotNull(tokenized.AsPair().Item1.AsPair()); + // + // //INT as bytes + // Assert.True(tokenized.AsPair().Item1.AsPair().Item1.Atom.SequenceEqual(new byte[] { 73, 78, 84})); // INT + // + // //NUMBER as bytes + // Assert.True(tokenized.AsPair().Item2.Atom.SequenceEqual(expectedBytes)); // INT + // } + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeHexTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeHexTests.cs new file mode 100644 index 0000000..69a53ee --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeHexTests.cs @@ -0,0 +1,61 @@ +using CLVMDotNet.Tools; +using Xunit; +using x = CLVMDotNet.Tools.IR; +namespace CLVMDotNet.Tests.Tools.IRReader; + +public class TokenizeHexTests +{ + [Fact] + public void TokenizeHex_DoesNotLook_like_hex_returns_null() + { + // ArrangeA + var invalidHex = "xxxx"; + + // ActW + var result = x.IRReader.TokenizeHex(invalidHex, 0); + + // Assert + Assert.Null(result); + } + + [Fact] + public void TokenizeHex_Invalid_Hex_throws_exception() + { + // ArrangeA + var invalidHex = "0Xzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; + int offset = 1; + + // ActW + var errorMessage = + Assert.Throws(() => + x.IRReader.TokenizeHex(invalidHex, offset)); + + // Assert + Assert.Equal($"invalid hex at {offset}:{invalidHex}",errorMessage.Message); + } + + [Theory] + [InlineData("0x01", new byte[] { 0x01})] + [InlineData("0x2DE684E38", new byte[] { 0x02, 0xde, 0x68, 0x4e, 0x38})] + [InlineData("0x1BFEA1277EF63F25B9D65E79", new byte[] { 0x1b, 0xfe, 0xa1, (byte)'\'', (byte)'~', 0xf6, (byte)'?', (byte)'%', 0xb9, 0xd6, (byte)'^', (byte)'y'})] + public void TokenizeHex_Valid(string number, byte[] expectedBytes) + { + // Arrange + + // Act + var tokenized = x.IRReader.TokenizeHex(number, 0); + + // Assert + Assert.Null(tokenized!.Atom); + Assert.NotNull(tokenized.Pair); + Assert.Null(tokenized.AsPair()!.Item1.Atom); + Assert.NotNull(tokenized.AsPair()!.Item1); + Assert.NotNull(tokenized.AsPair()!.Item1.AsPair()!); + + //HEX as bytes + Assert.True(tokenized.AsPair()!.Item1.AsPair()!.Item1.Atom!.SequenceEqual(new byte[] { 72, 69, 88})); // HEX + + //NUMBER as bytes + Assert.True(tokenized.AsPair()!.Item2.Atom!.SequenceEqual(expectedBytes)); //bytes + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeIntTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeIntTests.cs new file mode 100644 index 0000000..c9efd28 --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeIntTests.cs @@ -0,0 +1,46 @@ +using Xunit; +using x = CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tests.Tools.IRReader; + +public class TokenizeIntTests +{ + [Fact] + public void TokenizeInt_returns_null_when_passing_none_int() + { + // Arrange + var noneNumber = "a"; + + // Act + var tokenized = x.IRReader.TokenizeInt(noneNumber, 0); + + // Assert + Assert.Null(tokenized); + } + + [Theory] + [InlineData("1", new byte[] { 0x01})] + [InlineData("256", new byte[] { 0x01, 0x00 })] + [InlineData("12345678910", new byte[] { 0x02, 0xdf, 0xdc, 0x1c, (byte)'>'})] + [InlineData("1234567891012345678910", new byte[] { 0x42, 0xed, 0x12, 0x3b, 0xda, 0xce, 0x08, 0x4c, (byte)'>' })] + public void TokenizeInt_Builds_CorrectObject(string number, byte[] expectedBytes) + { + // Arrange + + // Act + var tokenized = x.IRReader.TokenizeInt(number, 0); + + // Assert + Assert.Null(tokenized!.Atom); + Assert.NotNull(tokenized.Pair); + Assert.Null(tokenized.AsPair()!.Item1.Atom); + Assert.NotNull(tokenized.AsPair()!.Item1); + Assert.NotNull(tokenized.AsPair()!.Item1.AsPair()); + + //INT as bytes + Assert.True(tokenized.AsPair()!.Item1.AsPair()!.Item1.Atom!.SequenceEqual(new byte[] { 73, 78, 84})); // INT + + //NUMBER as bytes + Assert.True(tokenized.AsPair()!.Item2.Atom!.SequenceEqual(expectedBytes)); // INT + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeQuotesTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeQuotesTests.cs new file mode 100644 index 0000000..b3b70bf --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeQuotesTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.IRReader; + +public class TokenizeQuotesTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeSexpTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeSexpTests.cs new file mode 100644 index 0000000..cad63b3 --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeSexpTests.cs @@ -0,0 +1,22 @@ +using Xunit; +using x = CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tests.Tools.IRReader; + +public class TokenizeSexpTests +{ + [Fact] + public void TestTokenizeSexp() + { + // arrange + var stream = x.IRReader.TokenStream("(100 0x0100)"); + + // act + foreach (var item in stream) + { + + } + + // assert + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/IRReader/TokenizeSymbolTests.cs b/CLVMDotNet/tests/Tools/IRReader/TokenizeSymbolTests.cs new file mode 100644 index 0000000..9889fb7 --- /dev/null +++ b/CLVMDotNet/tests/Tools/IRReader/TokenizeSymbolTests.cs @@ -0,0 +1,24 @@ +using Xunit; +using x = CLVMDotNet.Tools.IR; + +namespace CLVMDotNet.Tests.Tools.IRReader +{ + + public class TokenizeSymbolTests + { + [Fact] + public void TokenizeSymbol_returns_sexp() + { + // Arrange + var offset = 2; + + // Act + var tokenized = x.IRReader.TokenizeSymbol("+", offset); + + // Assert + Assert.Equal(x.IRType.SYMBOL, tokenized.Item1.Item1); + Assert.Equal(offset, tokenized.Item1.Item2); + Assert.True(tokenized.Item2.SequenceEqual(new byte[] { (byte)'+'})); + } + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/NodePathTests.cs b/CLVMDotNet/tests/Tools/NodePathTests.cs new file mode 100644 index 0000000..92e29b9 --- /dev/null +++ b/CLVMDotNet/tests/Tools/NodePathTests.cs @@ -0,0 +1,260 @@ +using System.Globalization; +using System.Numerics; +using CLVMDotNet.Tools; +using Xunit; + +namespace CLVMDotNet.Tests.Tools; + +public class NodePathTests +{ + public NodePath Reset(NodePath n) + { + byte[] pathBlob = n.AsShortPath(); + BigInteger index = new BigInteger(pathBlob); + NodePath newNodePath = new NodePath(index); + return newNodePath; + } + + + public static bool IsValidBinaryString(string binaryString) + { + foreach (char c in binaryString) + { + if (c != '0' && c != '1') + { + return false; + } + } + return true; + } + + public bool CmpToBits(NodePath n, string bits) + { + byte[] pathBlob = n.AsShortPath(); + var hex = NodePath.ByteArrayToHexString(pathBlob); + BigInteger nAsInt = BigInteger.Parse(hex, NumberStyles.HexNumber); + + if (!IsValidBinaryString(bits)) + { + throw new ArgumentException("Invalid binary string", nameof(bits)); + } + + BigInteger result = 0; + for (int i = bits.Length - 1, j = 0; i >= 0; i--, j++) + { + if (bits[i] == '1') + { + result += BigInteger.Pow(2, j); + } + } + + return nAsInt == result; + } + + [Fact] + public void TestNodePath() + { + // Arrange + var left_right_left = NodePath.Left + .Add(NodePath.Right) + .Add(NodePath.Left); + var top = NodePath.TOP; + var n = top; + n.Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("01", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestTopPlusLeft() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("02", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestToplusLeftPlusRight() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("06", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestToplusLeftPlusRightPlusRight() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("0E", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestToplusLeftPlusRightPlusRightPlusLeft() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("16", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestToplusLeftPlusRightPlusRightPlusLeftPlusLeft() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("26", NodePath.ByteArrayToHexString(path)); + } + + [Fact] + public void TestToplusLeftPlusRightPlusRightPlusLeftPlusLeftPlusLeft() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("46", NodePath.ByteArrayToHexString(path)); + } + + [Fact] public void TestToplusLeftPlusRightPlusRightPlusLeftPlusLeftPlusLeftPlusRight() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Right); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.Equal("C6", NodePath.ByteArrayToHexString(path)); + } + + [Fact] public void TestToplusLeftPlusRightPlusRightPlusLeftPlusLeftPlusLeftPlusRightPlusLeft() + { + // Arrange + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Left); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.True(CmpToBits(n, "101000110")); + } + + [Fact] public void TestToplusLeftPlusRightPlusRightPlusLeftPlusLeftPlusLeftPlusRightPlusLeftPlusLeftRightLeft() + { + // Arrange + var LeftRightLeft = NodePath.Left + .Add(NodePath.Right) + .Add(NodePath.Left); + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(LeftRightLeft); + + // Act + var path = n.AsShortPath(); + + // Assert + Assert.True(CmpToBits(n, "101001000110")); + } + + [Fact] public void AddLeftRightLeft() + { + // Arrange + var leftRightLeft = NodePath.Left + .Add(NodePath.Right) + .Add(NodePath.Left); + + var n = NodePath.TOP + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Left) + .Add(NodePath.Right) + .Add(NodePath.Left) + .Add(leftRightLeft) + .Add(leftRightLeft) + .Add(leftRightLeft); + + // Act + var isSameBits = CmpToBits(n, "101001001001000110"); + + // Assert + Assert.True(isSameBits); + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage0/Stage0.cs b/CLVMDotNet/tests/Tools/Stages/Stage0/Stage0.cs new file mode 100644 index 0000000..8bc509a --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage0/Stage0.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage0; + +public class Stage0 +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/BindingTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/BindingTests.cs new file mode 100644 index 0000000..a1c5b73 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/BindingTests.cs @@ -0,0 +1,49 @@ +using CLVMDotNet.Tools.Stages.Stage2; +using Xunit; + +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class BindingTests +{ + [Fact] + public void TestBrun() + { + // Arrange + + // Act + var brun = Bindings.Brun; + + // Assert + Assert.Null(brun.Atom); + Assert.NotNull(brun.Pair); + Assert.True(brun.AsPair()!.Item1.AsAtom()!.SequenceEqual(new byte[] { 0x02 })); + Assert.Null(brun.AsPair()!.Item1.Pair); + Assert.Null(brun.AsPair()!.Item2.Atom); + Assert.NotNull(brun.AsPair()!.Item2.Pair); + Assert.True(brun.AsPair()!.Item2.AsPair()!.Item1.AsAtom()!.SequenceEqual(new byte[] { 0x02})); + Assert.Null(brun.AsPair()!.Item2.AsPair()!.Item2.Atom); + Assert.NotNull(brun.AsPair()!.Item2.AsPair()!.Item2.Pair); + Assert.True(brun.AsPair()!.Item2.AsPair()!.Item2.AsPair()!.Item1.AsAtom()!.SequenceEqual(new byte []{ 0x03})); + Assert.Empty(brun.AsPair()!.Item2.AsPair()!.Item2.AsPair()!.Item2.AsAtom()!); + } + + [Fact(Skip = "Skipping for now")] + public void TestRun() + { + // Arrange + + // Act + var run = Bindings.Run; + + // Assert + Assert.Null(run.Atom); + Assert.True(run.AsPair()!.Item1.AsAtom()!.SequenceEqual(new byte[] { 0x02 })); + Assert.Null(run.AsPair()!.Item1.Pair); + Assert.Null(run.AsPair()!.Item2.Atom); + Assert.NotNull(run.AsPair()!.Item2.Pair); + Assert.NotNull(run.AsPair()!.Item2.AsPair()!.Item2); + Assert.NotNull(run.AsPair()!.Item2.AsPair()!.Item1); + + + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/CompileTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/CompileTests.cs new file mode 100644 index 0000000..4969c94 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/CompileTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class CompileTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/DefaultsTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/DefaultsTests.cs new file mode 100644 index 0000000..80e9c55 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/DefaultsTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class DefaultsTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/HelperTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/HelperTests.cs new file mode 100644 index 0000000..3715c44 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/HelperTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class HelperTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/ModTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/ModTests.cs new file mode 100644 index 0000000..b445c61 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/ModTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class ModTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/OperatorTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/OperatorTests.cs new file mode 100644 index 0000000..ef8fb9a --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/OperatorTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class OperatorTests +{ + +} \ No newline at end of file diff --git a/CLVMDotNet/tests/Tools/Stages/Stage2/OptimizeTests.cs b/CLVMDotNet/tests/Tools/Stages/Stage2/OptimizeTests.cs new file mode 100644 index 0000000..54bb1f6 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Stages/Stage2/OptimizeTests.cs @@ -0,0 +1,6 @@ +namespace CLVMDotNet.Tests.Tools.Stages.Stage2; + +public class OptimizeTests +{ + +} \ No newline at end of file