From 89c82a1983db0703e6c307b4fbea128e1ba6c935 Mon Sep 17 00:00:00 2001 From: mikera Date: Thu, 28 Nov 2024 13:06:38 +0000 Subject: [PATCH] Plumbing for extensible encode reading --- .../main/java/convex/core/data/AEncoder.java | 6 +- .../java/convex/core/data/CAD3Encoder.java | 143 +++++++++++++++++- .../java/convex/core/data/CVMEncoder.java | 18 ++- .../main/java/convex/core/data/Format.java | 20 +-- .../java/convex/core/util/ErrorMessages.java | 6 + 5 files changed, 177 insertions(+), 16 deletions(-) diff --git a/convex-core/src/main/java/convex/core/data/AEncoder.java b/convex-core/src/main/java/convex/core/data/AEncoder.java index ab656b27f..f7534993a 100644 --- a/convex-core/src/main/java/convex/core/data/AEncoder.java +++ b/convex-core/src/main/java/convex/core/data/AEncoder.java @@ -19,7 +19,11 @@ public abstract class AEncoder { */ public abstract Blob encode(T a); - public abstract T decode(Blob encoding) throws BadFormatException; + public T decode(Blob encoding) throws BadFormatException { + return read(encoding,0); + } + + public abstract T read(Blob encoding, int offset) throws BadFormatException; /** * Reads a value from a Blob of data diff --git a/convex-core/src/main/java/convex/core/data/CAD3Encoder.java b/convex-core/src/main/java/convex/core/data/CAD3Encoder.java index e113ec62c..0d2a85e02 100644 --- a/convex-core/src/main/java/convex/core/data/CAD3Encoder.java +++ b/convex-core/src/main/java/convex/core/data/CAD3Encoder.java @@ -1,9 +1,18 @@ package convex.core.data; +import convex.core.data.prim.AByteFlag; +import convex.core.data.prim.ANumeric; +import convex.core.data.prim.CVMBigInteger; +import convex.core.data.prim.CVMChar; +import convex.core.data.prim.CVMDouble; +import convex.core.data.prim.CVMLong; import convex.core.exceptions.BadFormatException; +import convex.core.util.ErrorMessages; /** - * Base Encoder for CAD3 data / stores + * Base Encoder for CAD3 data / stores. + * + * Does NOT directly decode custom CVM value types. Use the derived CVMEncoder if you need that behaviour. */ public class CAD3Encoder extends AEncoder { @@ -11,10 +20,140 @@ public Blob encode(ACell a) { return Cells.encode(a); } + @Override public ACell decode(Blob encoding) throws BadFormatException { - return Format.read(encoding); + if (encoding.count()<1) throw new BadFormatException("Empty encoding"); + return read(encoding,0); + } + + @Override + public ACell read(Blob encoding, int offset) throws BadFormatException { + byte tag = encoding.byteAt(offset); + ACell result= read(tag,encoding,offset); + return result; + } + + protected ACell read(byte tag, Blob encoding, int offset) throws BadFormatException { + switch (tag>>4) { + case 0: // 0x00-0x0F : Only null is valid + if (tag==Tag.NULL) return null; + break; + + case 1: // 0x10-0x1F : Numeric values + return readNumeric(tag,encoding,offset); + + case 2: // 0x20-0x2F : Addresses and references + if (tag == Tag.ADDRESS) return Address.read(encoding,offset); + // Note: 0x20 reference is invalid as a top level encoding + break; + + case 3: // 0x30-0x3F : BAsic string / blob-like objects + return readBasicObject(tag, encoding, offset); + + case 4: case 5: case 6: case 7: // 0x40-0x7F currently reserved + break; + + case 8: // 0x80-0x8F : BAsic string / blob-like objects + return readDataStructure(tag, encoding, offset); + + case 9: // 0x90-0x9F : Crypto / signature objects + return readSignedData(tag,encoding, offset); + + case 10: // 0xA0-0xAF : Sparse records. A is for Airy. + return readSparseRecord(tag,encoding,offset); + + case 11: // 0xB0-0xBF : Byte flags including booleans + return AByteFlag.read(tag); + + case 12: // 0xC0-0xCF : Coded data objects + return readCodedData(tag,encoding, offset); + + case 13: // 0xD0-0xDF : Dense records + return readDenseRecord(tag,encoding, offset); + + case 14: // 0xE0-0xEF : Extension values + return readExtension(tag,encoding, offset); + + case 15: // 0xF0-0xFF : Reserved / failure Values + break; + } + + return Format.read(tag,encoding,offset); + // throw new BadFormatException(ErrorMessages.badTagMessage(tag)); + } + + + protected ANumeric readNumeric(byte tag, Blob blob, int offset) throws BadFormatException { + if (tag<0x19) return CVMLong.read(tag,blob,offset); + if (tag == 0x19) return CVMBigInteger.read(blob,offset); + if (tag == Tag.DOUBLE) return CVMDouble.read(tag,blob,offset); + + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); + } + + protected ACell readBasicObject(byte tag, Blob blob, int offset) throws BadFormatException{ + switch (tag) { + case Tag.SYMBOL: return Symbol.read(blob,offset); + case Tag.KEYWORD: return Keyword.read(blob,offset); + case Tag.BLOB: return Blobs.read(blob,offset); + case Tag.STRING: return Strings.read(blob,offset); + } + + if ((tag&Tag.CHAR_MASK)==Tag.CHAR_BASE) { + int len=CVMChar.byteCountFromTag(tag); + if (len>4) throw new BadFormatException("Can't read char type with length: " + len); + return CVMChar.read(len, blob,offset); // skip tag byte + } + + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); + } + + protected ACell readDataStructure(byte tag, Blob b, int pos) throws BadFormatException { + if (tag == Tag.VECTOR) return Vectors.read(b,pos); + + if (tag == Tag.MAP) return Maps.read(b,pos); + + if (tag == Tag.SYNTAX) return Syntax.read(b,pos); + + if (tag == Tag.SET) return Sets.read(b,pos); + + if (tag == Tag.LIST) return List.read(b,pos); + + if (tag == Tag.INDEX) return Index.read(b,pos); + + throw new BadFormatException("Can't read data structure with tag byte: " + tag); + } + + protected SignedData readSignedData(byte tag,Blob blob, int offset) throws BadFormatException { + if (tag==Tag.SIGNED_DATA) return SignedData.read(blob,offset,true); + if (tag==Tag.SIGNED_DATA_SHORT) return SignedData.read(blob,offset,false); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); + } + + protected ARecord readSparseRecord(byte tag, Blob encoding, int offset) throws BadFormatException { + // TODO spare records + throw new BadFormatException(ErrorMessages.TODO); } + protected ACell readCodedData(byte tag, Blob encoding, int offset) throws BadFormatException { + // TODO Change delegation to proper read + return Format.read(tag,encoding,offset); + } + + protected ACell readDenseRecord(byte tag, Blob encoding, int offset) throws BadFormatException { + // TODO Change delegation to proper read + return Format.read(tag,encoding,offset); + } + + protected ACell readExtension(byte tag, Blob blob, int offset) throws BadFormatException { + // We expect a VLQ Count following the tag + long code=Format.readVLQCount(blob,offset+1); + return ExtensionValue.create(tag, code); + } + + + + /** * Reads a cell value from a Blob of data, allowing for non-embedded branches following the first cell * @param data Data to decode diff --git a/convex-core/src/main/java/convex/core/data/CVMEncoder.java b/convex-core/src/main/java/convex/core/data/CVMEncoder.java index b952c26c3..2f5fcbfa4 100644 --- a/convex-core/src/main/java/convex/core/data/CVMEncoder.java +++ b/convex-core/src/main/java/convex/core/data/CVMEncoder.java @@ -1,9 +1,25 @@ package convex.core.data; +import convex.core.exceptions.BadFormatException; +import convex.core.lang.Core; + +/** + * Encoder for CVM values and data structures + */ public class CVMEncoder extends CAD3Encoder { public static final CVMEncoder INSTANCE = new CVMEncoder(); + @Override + public ACell read(Blob encoding,int offset) throws BadFormatException { + return super.read(encoding,offset); + } - + protected ACell readExtension(byte tag, Blob blob, int offset) throws BadFormatException { + // We expect a VLQ Count following the tag + long code=Format.readVLQCount(blob,offset+1); + if (tag == Tag.CORE_DEF) return Core.fromCode(code); + + return ExtensionValue.create(tag, code); + } } 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 cf5f56382..660406c20 100644 --- a/convex-core/src/main/java/convex/core/data/Format.java +++ b/convex-core/src/main/java/convex/core/data/Format.java @@ -39,6 +39,7 @@ import convex.core.store.AStore; import convex.core.store.Stores; import convex.core.util.Bits; +import convex.core.util.ErrorMessages; import convex.core.util.Trees; import convex.core.util.Utils; @@ -580,7 +581,7 @@ public static T read(String hexString) throws BadFormatExcepti * @throws BadFormatException If encoding is invalid for the given tag */ @SuppressWarnings("unchecked") - private static T read(byte tag, Blob blob, int offset) throws BadFormatException { + static T read(byte tag, Blob blob, int offset) throws BadFormatException { // Fast paths for common one-byte instances. TODO: might switch have better performance if compiled correctly into a table? if (tag==Tag.NULL) return null; @@ -616,27 +617,22 @@ private static T read(byte tag, Blob blob, int offset) throws } catch (Exception e) { throw new BadFormatException("Unexpected Exception when decoding ("+tag+"): "+e.getMessage(), e); } - throw new BadFormatException(badTagMessage(tag)); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); } private static SignedData readSignedData(byte tag,Blob blob, int offset) throws BadFormatException { if (tag==Tag.SIGNED_DATA) return SignedData.read(blob,offset,true); if (tag==Tag.SIGNED_DATA_SHORT) return SignedData.read(blob,offset,false); - throw new BadFormatException(badTagMessage(tag)); - } - - private static String badTagMessage(byte tag) { - return "Unrecognised tag byte 0x"+Utils.toHexString(tag); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); } private static ANumeric readNumeric(byte tag, Blob blob, int offset) throws BadFormatException { - // TODO Auto-generated method stub if (tag<0x19) return CVMLong.read(tag,blob,offset); if (tag == 0x19) return CVMBigInteger.read(blob,offset); if (tag == Tag.DOUBLE) return CVMDouble.read(tag,blob,offset); - throw new BadFormatException(badTagMessage(tag)); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); } private static ACell readBasicObject(byte tag, Blob blob, int offset) throws BadFormatException{ @@ -653,7 +649,7 @@ private static ACell readBasicObject(byte tag, Blob blob, int offset) throws Ba return CVMChar.read(len, blob,offset); // skip tag byte } - throw new BadFormatException(badTagMessage(tag)); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); } @@ -691,7 +687,7 @@ private static T readRecord(byte tag, Blob b, int pos) throw if (tag == Tag.PEER_STATUS) return (T) PeerStatus.read(b,pos); if (tag == Tag.ACCOUNT_STATUS) return (T) AccountStatus.read(b,pos); - throw new BadFormatException(badTagMessage(tag)); + throw new BadFormatException(ErrorMessages.badTagMessage(tag)); } @SuppressWarnings("unchecked") @@ -712,7 +708,7 @@ private static T readTransaction(byte tag, Blob b, int pos) th // Might be a generic Dense Record DenseRecord dr=DenseRecord.read(tag,b,pos); - if (dr==null) throw new BadFormatException(badTagMessage(tag)); + if (dr==null) throw new BadFormatException(ErrorMessages.badTagMessage(tag)); return (T) dr; } diff --git a/convex-core/src/main/java/convex/core/util/ErrorMessages.java b/convex-core/src/main/java/convex/core/util/ErrorMessages.java index 182a84b13..0e688defe 100644 --- a/convex-core/src/main/java/convex/core/util/ErrorMessages.java +++ b/convex-core/src/main/java/convex/core/util/ErrorMessages.java @@ -19,6 +19,8 @@ public class ErrorMessages { + public static final String TODO = "Not yet implemented."; + public static String immutable(Object a) { return "Object is immutable: "+a.getClass(); } @@ -61,4 +63,8 @@ public static ErrorValue nobody(Address address) { public static ErrorValue INVALID_NUMERIC = ErrorValue.create(ErrorCodes.ARGUMENT,"Invalid numeric result"); + public static String badTagMessage(byte tag) { + return "Unrecognised tag byte 0x"+Utils.toHexString(tag); + } + }