convex.core.data.Syntax Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
The newest version!
package convex.core.data;
import convex.core.data.prim.CVMLong;
import convex.core.data.type.AType;
import convex.core.data.type.Types;
import convex.core.data.util.BlobBuilder;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.InvalidDataException;
import convex.core.lang.RT;
/**
* Class representing a Syntax Object.
*
* A Syntax Object wraps:
*
* - A Form (which may contain nested Syntax Objects)
* - Metadata for the Syntax Object, which may be any arbitrary hashmap
*
*
* Syntax Objects may not wrap another Syntax Object directly, but may contain nested
* Syntax Objects within data structures.
*
* Inspired by Racket.
*
*/
public final class Syntax extends ACell {
public static final Syntax EMPTY = create(null, null);
public static final AString EMPTY_META_PREFIX = Strings.create("^{} ");
/**
* Max encoding size is a Map (with substituted tag) plus an embedded datum
*/
public static final int MAX_ENCODING_LENGTH = 1+ (Maps.MAX_ENCODING_SIZE) + Format.MAX_EMBEDDED_LENGTH;
/**
* Ref to the unwrapped datum value. Cannot refer to another Syntax object
*/
private final Ref datumRef;
/**
* Metadata map
* Never null logically, but if empty, gets encoded as null in byte encoding
*/
private final AHashMap meta;
private Syntax(Ref datumRef, AHashMap props) {
this.datumRef = datumRef;
this.meta = props;
}
@Override
public AType getType() {
return Types.SYNTAX;
}
public static Syntax createUnchecked(ACell value, AHashMap meta) {
return new Syntax(Ref.get(value),meta);
}
/**
* Wraps a value as a Syntax Object, adding the given new metadata
*
* @param value Value to wrap in Syntax Object
* @param meta Metadata to merge, may be null
* @return Syntax instance
*/
public static Syntax create(ACell value, AHashMap meta) {
if (value instanceof Syntax) {
Syntax stx=((Syntax) value);
if (meta==null) return stx;
return stx.mergeMeta(meta);
}
if (meta==null) meta=Maps.empty();
return new Syntax(Ref.get(value), meta);
}
/**
* Wraps a value as a Syntax Object with empty metadata. Does not change existing Syntax objects.
*
* @param value Any CVM value
* @return Syntax instance
*/
public static Syntax create(ACell value) {
if (value instanceof Syntax) return (Syntax) value;
return create(value, Maps.empty());
}
/**
* Wraps a value as a Syntax Object with empty metadata. Does not change existing Syntax objects.
*
* @param value Any value, will be converted to valid CVM type
* @return Syntax instance
*/
public static Syntax of(ACell value) {
return create(value);
}
/**
* Create a Syntax Object with the given value. Converts to appropriate CVM type as a convenience
*
* @param value Value to wrap
* @return Syntax instance
*/
public static Syntax of(Object value) {
return create(RT.cvm(value));
}
/**
* Gets the value datum from this Syntax Object
*
* @param Expected datum type from Syntax object
* @return Value datum
*/
@SuppressWarnings("unchecked")
public R getValue() {
return (R) datumRef.getValue();
}
/**
* Gets the metadata for this syntax object. May be empty, but never null.
*
* @return Metadata for this Syntax Object as a hashmap
*/
public AHashMap getMeta() {
return meta;
}
public Long getStart() {
Object v= meta.get(Keywords.START);
if (v instanceof CVMLong) return ((CVMLong)v).longValue();
return null;
}
public Long getEnd() {
Object v= meta.get(Keywords.END);
if (v instanceof CVMLong) return ((CVMLong)v).longValue();
return null;
}
public String getSource() {
Object v= meta.get(Keywords.SOURCE);
if (v instanceof AString) return v.toString();
return null;
}
@Override
public boolean isCanonical() {
return true;
}
@Override public final boolean isCVMValue() {
return true;
}
@Override public final boolean isDataValue() {
return true;
}
/**
* Decodes a Syntax object from a Blob encoding
*
* @param b Blob to read from
* @param pos Start position in Blob (location of tag byte)
* @return New decoded instance
* @throws BadFormatException In the event of any encoding error
*/
public static Syntax read(Blob b, int pos) throws BadFormatException {
int epos=pos+1; // read position after tag
Ref datum = Format.readRef(b,epos);
epos+=datum.getEncodingLength();
AHashMap props = Format.read(b,epos);
epos+=Format.getEncodingLength(props);
if (props == null) {
props = Maps.empty(); // we encode empty props as null for efficiency
} else {
if (props.isEmpty()) {
throw new BadFormatException("Empty Syntax metadata should be encoded as nil");
}
}
Syntax result=new Syntax(datum,props);
result.attachEncoding(b.slice(pos,epos));
return result;
}
@Override
public int encode(byte[] bs, int pos) {
bs[pos++]=Tag.SYNTAX;
return encodeRaw(bs,pos);
}
@Override
public int encodeRaw(byte[] bs, int pos) {
pos=datumRef.encode(bs,pos);
// encode empty props as null for efficiency
if (meta.isEmpty()) {
bs[pos++]=Tag.NULL;
} else {
pos=Format.write(bs,pos,meta);
}
return pos;
}
@Override
public boolean print(BlobBuilder bb, long limit) {
if (meta==null) {
bb.append(EMPTY_META_PREFIX);
} else {
bb.append('^');
if (!meta.print(bb,limit)) return false;
bb.append(' ');
}
// Print rest of value according to remaining limit
return RT.print(bb, datumRef.getValue(),limit);
}
@Override
public void validateCell() throws InvalidDataException {
if (datumRef == null) throw new InvalidDataException("null datum ref", this);
if (meta == null) throw new InvalidDataException("null metadata", this);
meta.validateCell();
}
@Override
public void validate() throws InvalidDataException {
super.validate();
ACell datum=datumRef.getValue();
if (datum!=null) {
if (datum instanceof Syntax) {
throw new InvalidDataException("Cannot double-wrap a Syntax value",this);
}
if (!datum.isCVMValue()) throw new InvalidDataException("Syntax can only wrap CVM values",this);
}
}
@Override
public int estimatedEncodingSize() {
return 1+meta.estimatedEncodingSize()+Format.MAX_EMBEDDED_LENGTH;
}
@Override
public int getRefCount() {
return 1 + meta.getRefCount();
}
@SuppressWarnings("unchecked")
@Override
public Ref getRef(int i) {
if (i == 0) return (Ref) datumRef;
return meta.getRef(i - 1);
}
@Override
public Syntax updateRefs(IRefFunction func) {
@SuppressWarnings("unchecked")
Ref newDatum = (Ref)func.apply(datumRef);
AHashMap newMeta = meta.updateRefs(func);
if ((datumRef == newDatum) && (meta == newMeta)) return this;
return new Syntax(newDatum, newMeta);
}
/**
* Merges metadata into this syntax object, overriding existing metadata
*
* @param additionalMetadata Extra metadata to merge
* @return Syntax Object with updated metadata
*/
public Syntax mergeMeta(AHashMap additionalMetadata) {
AHashMap mm = meta;
mm = mm.merge(additionalMetadata);
return this.withMeta(mm);
}
/**
* Merge metadata into a Cell, after wrapping as a Syntax Object
*
* @param original Cell to enhance with merged metadata
* @param additional Syntax Object containing additional metadata. Any value will be ignored.
* @return Syntax object with merged metadata
*/
public static Syntax mergeMeta(ACell original, Syntax additional) {
Syntax x=Syntax.create(original);
if (additional!=null) {
x=x.mergeMeta(additional.getMeta());
}
return x;
}
/**
* Replaces metadata on this Syntax Object. Old metadata is discarded.
*
* @param newMetadata New metadata map
* @return Syntax Object with updated metadata
*/
public Syntax withMeta(AHashMap newMetadata) {
if (meta == newMetadata) return this;
return new Syntax(datumRef, newMetadata);
}
/**
* Removes all metadata from this Syntax Object
*
* @return Syntax Object with empty metadata
*/
public Syntax withoutMeta() {
return withMeta(Maps.empty());
}
/**
* Unwraps a Syntax Object to get the underlying value.
*
* If the argument is not a Syntax object, return it unchanged (already unwrapped)
* @param Expected type of value
* @param x Any Object, which may be a Syntax Object
* @return The unwrapped value
*/
@SuppressWarnings("unchecked")
public static R unwrap(ACell x) {
return (x instanceof Syntax) ? ((Syntax) x).getValue() : (R) x;
}
/**
* Recursively unwraps a Syntax object
*
* @param maybeSyntax Syntax Object to unwrap
* @return Unwrapped object
*/
@SuppressWarnings("unchecked")
public static R unwrapAll(ACell maybeSyntax) {
ACell a = unwrap(maybeSyntax);
if (a instanceof ACollection) {
return (R) ((ACollection>) a).map(e -> unwrapAll(e));
} else if (a instanceof AMap) {
AMap, ?> m = (AMap, ?>) a;
return (R) m.reduceEntries((acc, e) -> {
return acc.assoc(unwrapAll(e.getKey()), unwrapAll(e.getValue()));
}, (AMap) Maps.empty());
} else {
// nothing else can contain Syntax objects, so just return normally
return (R) a;
}
}
@Override
public byte getTag() {
return Tag.SYNTAX;
}
@Override
public ACell toCanonical() {
return this;
}
@Override
public boolean equals(ACell o) {
if (!(o instanceof Syntax)) return false; // catches null
Syntax b=(Syntax)o;
if (!meta.equals(b.meta)) return false;
return datumRef.equals(b.datumRef);
}
}