From 1ef0fefb89b8b42b1340e9e50b507da057864243 Mon Sep 17 00:00:00 2001 From: mikera Date: Sat, 12 Oct 2024 17:58:11 +0100 Subject: [PATCH] Support for CAD3 Dense Record types --- .../java/convex/core/data/ACAD3Record.java | 44 ++++--- .../src/main/java/convex/core/data/ACell.java | 2 +- .../main/java/convex/core/data/AMapEntry.java | 2 - .../main/java/convex/core/data/ASequence.java | 4 +- .../java/convex/core/data/DenseRecord.java | 116 ++++++++++++------ .../main/java/convex/core/data/Format.java | 6 +- .../main/java/convex/core/data/MapEntry.java | 3 +- .../src/main/java/convex/core/data/Tag.java | 14 +++ .../java/convex/core/data/type/CAD3Type.java | 50 ++++++++ .../java/convex/core/data/type/Types.java | 1 + .../main/java/convex/core/lang/Compiler.java | 4 + .../test/java/convex/core/data/CAD3Test.java | 46 ++++++- 12 files changed, 227 insertions(+), 65 deletions(-) create mode 100644 convex-core/src/main/java/convex/core/data/type/CAD3Type.java diff --git a/convex-core/src/main/java/convex/core/data/ACAD3Record.java b/convex-core/src/main/java/convex/core/data/ACAD3Record.java index 6122016ab..bebc678df 100644 --- a/convex-core/src/main/java/convex/core/data/ACAD3Record.java +++ b/convex-core/src/main/java/convex/core/data/ACAD3Record.java @@ -3,6 +3,7 @@ import convex.core.data.util.BlobBuilder; import convex.core.exceptions.InvalidDataException; import convex.core.lang.RT; +import convex.core.util.Utils; /** * Abstract base class for non-CVM CAD3 Records values. These look like countable sequences to CVM code. @@ -11,43 +12,52 @@ */ public abstract class ACAD3Record extends ASequence { - public ACAD3Record(long count) { - super(count); - } + protected final byte tag; - @Override - public int estimatedEncodingSize() { - return encoding.size(); + protected ACAD3Record(byte tag,long count) { + super(count); + this.tag=tag; } - + @Override public void validateCell() throws InvalidDataException { - // TODO Auto-generated method stub + byte cat=Tag.category(tag); + switch (cat) { + case Tag.DENSE_RECORD_BASE: + case Tag.SPARSE_RECORD_BASE: + break; // seems OK + default: throw new InvalidDataException("Bad tag for CAD3 Record: 0x"+Utils.toHexString(tag),this); + } } @Override public byte getTag() { - return encoding.byteAt(0); + return tag; } @Override public boolean equals(ACell a) { if (a==null) return false; - if (a.getTag()!=getTag()) return false; + if (a.getTag()!=tag) return false; return encoding.equals(a.getEncoding()); } @Override public int encode(byte[] bs, int pos) { - encoding.getBytes(bs, pos); - return pos+encoding.size(); + bs[pos++]=tag; + return encodeRaw(bs,pos); } - + + // subclasses must implement getRefCount and getRef + @Override - public int encodeRaw(byte[] bs, int pos) { - encoding.slice(1).getBytes(bs, pos); - return pos+encoding.size()-1; - } + public abstract int getRefCount(); + + @Override + public abstract Ref getRef(int i); + + @Override + public abstract ACell updateRefs(IRefFunction func); @Override public boolean isCanonical() { diff --git a/convex-core/src/main/java/convex/core/data/ACell.java b/convex-core/src/main/java/convex/core/data/ACell.java index a90f9a965..95ffdd10d 100644 --- a/convex-core/src/main/java/convex/core/data/ACell.java +++ b/convex-core/src/main/java/convex/core/data/ACell.java @@ -245,7 +245,7 @@ public AString toCVMString(long limit) { * * @return The cached blob for this cell, or null if not yet available. */ - public Blob cachedEncoding() { + public final Blob cachedEncoding() { return encoding; } diff --git a/convex-core/src/main/java/convex/core/data/AMapEntry.java b/convex-core/src/main/java/convex/core/data/AMapEntry.java index e02185ccb..227c3ab6b 100644 --- a/convex-core/src/main/java/convex/core/data/AMapEntry.java +++ b/convex-core/src/main/java/convex/core/data/AMapEntry.java @@ -52,8 +52,6 @@ public final V setValue(V value) { @Override public abstract boolean isCanonical(); - - @Override public AVector append(ACell value) { diff --git a/convex-core/src/main/java/convex/core/data/ASequence.java b/convex-core/src/main/java/convex/core/data/ASequence.java index ed8f101bc..61317dec8 100644 --- a/convex-core/src/main/java/convex/core/data/ASequence.java +++ b/convex-core/src/main/java/convex/core/data/ASequence.java @@ -16,6 +16,8 @@ /** * Abstract base class for concrete sequential data structure (immutable persistent lists and vectors etc.) + * + * Implements standard java.util.List interface * * @param Type of list elements */ @@ -169,8 +171,6 @@ public boolean containsKey(ACell key) { return false; } - - /** * Gets the element Ref at the specified index * diff --git a/convex-core/src/main/java/convex/core/data/DenseRecord.java b/convex-core/src/main/java/convex/core/data/DenseRecord.java index e62a17933..6617a0004 100644 --- a/convex-core/src/main/java/convex/core/data/DenseRecord.java +++ b/convex-core/src/main/java/convex/core/data/DenseRecord.java @@ -5,13 +5,46 @@ import java.util.function.Function; import convex.core.data.type.AType; +import convex.core.data.type.Types; +import convex.core.exceptions.BadFormatException; public class DenseRecord extends ACAD3Record { - protected AVector data; + protected final AVector data; - public DenseRecord(long count) { - super(count); + protected DenseRecord(byte tag, AVector data) { + super(tag,data.count()); + this.data=data; + } + + public static DenseRecord create(int tag,AVector data) { + if (data==null) return null; + if (Tag.category(tag)!=Tag.DENSE_RECORD_BASE) return null; // not an extension value + + return new DenseRecord((byte)tag,data); + } + + @Override + public int estimatedEncodingSize() { + return data.estimatedEncodingSize(); + } + + @Override + public int encodeRaw(byte[] bs, int pos) { + return data.encodeRaw(bs, pos); + } + + public static DenseRecord read(byte tag, Blob b, int pos) throws BadFormatException { + AVector data=Vectors.read(b, pos); + + Blob enc=data.cachedEncoding(); + data.attachEncoding(null); // clear invalid encoding + + DenseRecord dr=create(tag,data); + if ((enc!=null)&&(enc.byteAt(0)==tag)) { + dr.attachEncoding(enc); + } + return dr; } @Override @@ -34,112 +67,117 @@ public long longLastIndexOf(ACell value) { return data.longLastIndexOf(value); } + @SuppressWarnings("unchecked") @Override public ASequence map(Function mapper) { - // TODO Auto-generated method stub - return null; + AVector rdata=data.map(mapper); + return (ASequence) rdata; } @Override public void forEach(Consumer action) { - // TODO Auto-generated method stub - + data.forEach(action); } @Override public void visitElementRefs(Consumer> f) { - // TODO Auto-generated method stub - + data.visitElementRefs(f); } @Override public ASequence concat(ASequence vals) { - // TODO Auto-generated method stub - return null; + return data.concat(vals); } @Override public ASequence next() { - // TODO Auto-generated method stub - return null; + return data.next(); } @Override public ASequence empty() { - // TODO Auto-generated method stub - return null; + return Vectors.empty(); } @Override public ACell get(long index) { - // TODO Auto-generated method stub - return null; + return data.get(index); } @Override public Ref getElementRef(long index) { - // TODO Auto-generated method stub - return null; + return data.getElementRef(index); } @Override public ASequence assoc(long i, ACell value) { - // TODO Auto-generated method stub - return null; + AVector newData=data.assoc(i, value); + return newData; } @Override public ASequence conj(ACell value) { - // TODO Auto-generated method stub - return null; + return data.conj(value); } @Override public ASequence slice(long start, long end) { - // TODO Auto-generated method stub - return null; + return data.slice(start,end); } @Override public AList cons(ACell x) { - // TODO Auto-generated method stub - return null; + return data.cons(x); } @Override public AVector subVector(long start, long length) { - // TODO Auto-generated method stub - return null; + return data.subVector(start, length); } @Override protected ListIterator listIterator(long l) { - // TODO Auto-generated method stub - return null; + return data.listIterator(l); } @Override public ASequence reverse() { - // TODO Auto-generated method stub - return null; + return data.reverse(); } @Override public AType getType() { - // TODO Auto-generated method stub - return null; + return Types.CAD3; } @Override public AVector toVector() { - // TODO Auto-generated method stub - return null; + return data; } @Override protected void copyToArray(R[] arr, int offset) { - // TODO Auto-generated method stub - + data.copyToArray(arr, offset); } + @Override + public int getRefCount() { + return data.getRefCount(); + } + + @Override + public Ref getRef(int i) { + return data.getRef(i); + } + + @Override + public ACell updateRefs(IRefFunction func) { + AVector newData=data.updateRefs(func); + if (newData==data) return this; + DenseRecord dr= new DenseRecord(tag,newData); + dr.attachEncoding(getEncoding()); + return dr; + } + + } diff --git a/convex-core/src/main/java/convex/core/data/Format.java b/convex-core/src/main/java/convex/core/data/Format.java index b3c75077a..b78a42e0f 100644 --- a/convex-core/src/main/java/convex/core/data/Format.java +++ b/convex-core/src/main/java/convex/core/data/Format.java @@ -705,7 +705,11 @@ private static T readTransaction(byte tag, Blob b, int pos) th } else if (tag == Tag.MULTI) { return (T) Multi.read(b,pos); } - throw new BadFormatException(badTagMessage(tag)); + + // Might be a generic Dense Record + DenseRecord dr=DenseRecord.read(tag,b,pos); + if (dr==null) throw new BadFormatException(badTagMessage(tag)); + return (T) dr; } /** diff --git a/convex-core/src/main/java/convex/core/data/MapEntry.java b/convex-core/src/main/java/convex/core/data/MapEntry.java index e5772b3a6..1f8f94a97 100644 --- a/convex-core/src/main/java/convex/core/data/MapEntry.java +++ b/convex-core/src/main/java/convex/core/data/MapEntry.java @@ -15,8 +15,7 @@ * implementation class for handling entries in Convex maps, and also to support the Java Map.Entry * interface for compatibility and developer convenience. * - * From a CVM perspective, a MapEntry is just a regular 2 element Vector. As such, MapEntry is *not* canonical - * and getting the canonical form of a MapEntry requires converting to a Vector + * From a CVM perspective, a MapEntry is just a regular 2 element Vector. * * Contains exactly 2 elements, one for key and one for value * diff --git a/convex-core/src/main/java/convex/core/data/Tag.java b/convex-core/src/main/java/convex/core/data/Tag.java index bd9bc7af3..05e753b13 100644 --- a/convex-core/src/main/java/convex/core/data/Tag.java +++ b/convex-core/src/main/java/convex/core/data/Tag.java @@ -66,6 +66,8 @@ public class Tag { // ========================================== // Sparsely coded record (0xAx) + public static final byte SPARSE_RECORD_BASE = (byte) 0xA0; + public static final byte STATE = (byte) 0xA0; public static final byte ACCOUNT_STATUS = (byte) 0xA1; public static final byte PEER_STATUS = (byte) 0xA2; @@ -98,6 +100,8 @@ public class Tag { // ========================================== // Densely coded record (0xDx) + public static final byte DENSE_RECORD_BASE = (byte) 0xD0; + public static final byte INVOKE = (byte) 0xD0; public static final byte TRANSFER = (byte) 0xD1; public static final byte CALL = (byte) 0xD2; @@ -121,6 +125,16 @@ public class Tag { // Illegal / reserved for special values (0xFx) public static final byte ILLEGAL = (byte) 0xFF; + + /** + * Get the general category for a given Tag (high hex digit) + * @param tag Tag Byte + * @return Category e.g. 0xC0 for coded data + */ + public static byte category(int tag) { + return (byte) (tag&0xF0); + } + } diff --git a/convex-core/src/main/java/convex/core/data/type/CAD3Type.java b/convex-core/src/main/java/convex/core/data/type/CAD3Type.java new file mode 100644 index 000000000..235310994 --- /dev/null +++ b/convex-core/src/main/java/convex/core/data/type/CAD3Type.java @@ -0,0 +1,50 @@ +package convex.core.data.type; + +import convex.core.data.ACell; +import convex.core.data.Address; +import convex.core.data.prim.ByteFlagExtended; + +/** + * Type for CAD3 Values not recognised by CVM + */ +public class CAD3Type extends AType { + /** + * Singleton runtime instance + */ + public static final CAD3Type INSTANCE = new CAD3Type(); + + private CAD3Type() { + super (); + } + + @Override + public boolean check(ACell value) { + return value instanceof Address; + } + + @Override + public String toString () { + return "Address"; + } + + @Override + public ACell defaultValue() { + return ByteFlagExtended.create(15); + } + + @Override + public Address implicitCast(ACell a) { + if (a instanceof Address) return (Address)a; + return null; + } + + @Override + public boolean allowsNull() { + return false; + } + + @Override + public Class getJavaClass() { + return ACell.class; + } +} diff --git a/convex-core/src/main/java/convex/core/data/type/Types.java b/convex-core/src/main/java/convex/core/data/type/Types.java index 9b105dd26..1330493db 100644 --- a/convex-core/src/main/java/convex/core/data/type/Types.java +++ b/convex-core/src/main/java/convex/core/data/type/Types.java @@ -53,6 +53,7 @@ public class Types { public static final Transaction TRANSACTION=Transaction.INSTANCE; public static final Countable COUNTABLE = Countable.INSTANCE; + public static final CAD3Type CAD3 = CAD3Type.INSTANCE; diff --git a/convex-core/src/main/java/convex/core/lang/Compiler.java b/convex-core/src/main/java/convex/core/lang/Compiler.java index f5e98e229..f82a5e41b 100644 --- a/convex-core/src/main/java/convex/core/lang/Compiler.java +++ b/convex-core/src/main/java/convex/core/lang/Compiler.java @@ -113,6 +113,10 @@ static Context compile(ACell form, Context context) { if (form instanceof AHashMap) return compileMap((AHashMap) form, context); if (form instanceof ASet) return compileSet((ASet) form, context); if (form instanceof AMap) return compileConstant( context,form); + if (!form.isCVMValue()) { + // This is probably a non-CVM CAD3 structures + return compileConstant(context, form); + } return context.withCompileError("Unexpected data structure: "+form.getClass()); } diff --git a/convex-core/src/test/java/convex/core/data/CAD3Test.java b/convex-core/src/test/java/convex/core/data/CAD3Test.java index 9141a6bc8..b7818a6d2 100644 --- a/convex-core/src/test/java/convex/core/data/CAD3Test.java +++ b/convex-core/src/test/java/convex/core/data/CAD3Test.java @@ -1,17 +1,26 @@ package convex.core.data; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import convex.core.data.prim.CVMLong; +import convex.core.lang.ACVMTest; +import convex.core.lang.Context; import convex.core.lang.Core; import convex.core.lang.Reader; import convex.core.util.Utils; -public class CAD3Test { +import static convex.test.Assertions.*; + +@TestInstance(Lifecycle.PER_CLASS) +public class CAD3Test extends ACVMTest { @Test public void testExtensionValues() { ExtensionValue ev=ExtensionValue.create((byte) 0xe3,100); @@ -33,5 +42,40 @@ public class CAD3Test { assertNull(Reader.read("#[00]")); assertEquals(ExtensionValue.create((byte) 0xe5, 0),Reader.read("#[e500]")); } + + @Test public void testDenseRecords() { + AVector v=Vectors.of(1,2,3); + DenseRecord dr=DenseRecord.create(0xDF,v); + assertEquals(Blob.fromHex("df03110111021103"),dr.getEncoding()); + + assertEquals(3,dr.count); + assertSame(v,dr.toVector()); + assertEquals("#[df03110111021103]",dr.toString()); + + ObjectsTest.doAnyValueTests(dr); + + DenseRecord ed=DenseRecord.create(0xDE,Vectors.empty()); + assertEquals(Blob.fromHex("de00"),ed.getEncoding()); + + ObjectsTest.doAnyValueTests(ed); + } + + /** + * Tests for dense record interactions with core functions + */ + @Test public void testCoreRecords() { + Context ctx=context(); + ctx=exec(ctx,"(def dr #[df03110111021103])"); + DenseRecord dr=ctx.getResult(); + assertNotNull(dr); + + // DenseRecord behaves like a sequence + assertCVMEquals(3,eval(ctx,"(count dr)")); + assertCVMEquals(1,eval(ctx,"(first dr)")); + assertCVMEquals(Vectors.of(1,2,3),eval(ctx,"(vec dr)")); + + assertFalse(evalB(ctx,"(vector? dr)")); + assertFalse(evalB(ctx,"(map? dr)")); + } }