Skip to content

Commit

Permalink
feat: Adds variant literal and simplifies constructions of artificial…
Browse files Browse the repository at this point in the history
… types/values
  • Loading branch information
RomarQ committed Jan 29, 2022
1 parent 90e0f34 commit 0063345
Show file tree
Hide file tree
Showing 8 changed files with 931 additions and 104 deletions.
2 changes: 2 additions & 0 deletions src/core/enums/prim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ export enum Prim {
None = 'None',
Pair = 'Pair',
Elt = 'Elt',
Left = 'Left',
Right = 'Right',
}
67 changes: 59 additions & 8 deletions src/core/literal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Michelson_Type, IRecordVariant } from './type';
import {
Michelson_Type_RecordOrVariant,
TBig_map,
TBls12_381_fr,
TBls12_381_g1,
TBls12_381_g2,
Expand All @@ -22,14 +23,14 @@ import {
TUnit,
TPair,
TMap,
TOr,
} from './type';
import type { Michelson_Type } from './type';
import Utils from '../misc/utils';
import Utils, { composeRightCombLayout } from '../misc/utils';
import { MichelsonJSON, MichelsonMicheline, PairsOfKeys } from '../typings';
import { IType } from '../typings/type';
import { Prim } from './enums/prim';
import { TBig_map } from '.';
import { PrimValue } from '../typings/literal';
import { buildRecordVariantType } from '.';

export type Michelson_LiteralUnion = Michelson_Literal | Michelson_Literal_C1 | Michelson_Record | Michelson_Map;

Expand Down Expand Up @@ -105,7 +106,7 @@ export class Michelson_Literal_C1 {
#elements: Michelson_LiteralUnion[];
type: IType;

constructor(prim: PrimValue, type: Michelson_Type, elements: Michelson_LiteralUnion[]) {
constructor(prim: PrimValue, type: IType, elements: Michelson_LiteralUnion[]) {
this.#prim = prim;
this.type = type;
this.#elements = elements;
Expand All @@ -115,6 +116,8 @@ export class Michelson_Literal_C1 {
switch (this.#prim) {
case Prim.Some:
case Prim.Pair:
case Prim.Left:
case Prim.Right:
return `(${this.#prim} ${this.#elements.map((v) => v.toMicheline()).join(' ')})`;
case Prim.list:
return `{ ${this.#elements.map((v) => v.toMicheline()).join(' ; ')} }`;
Expand All @@ -127,6 +130,8 @@ export class Michelson_Literal_C1 {
switch (this.#prim) {
case Prim.Some:
case Prim.Pair:
case Prim.Left:
case Prim.Right:
return {
prim: this.#prim,
args: this.#elements.map((v) => v.toJSON()),
Expand Down Expand Up @@ -172,7 +177,7 @@ class Michelson_Record {

constructor(fields: Record<string, Michelson_LiteralUnion>, layout?: PairsOfKeys<keyof typeof fields>) {
this.#fields = fields;
this.#layout = layout || Michelson_Type_RecordOrVariant.composeRightCombLayout(Object.keys(fields));
this.#layout = layout || composeRightCombLayout(Object.keys(fields));

this.type = TRecord(
Object.entries(fields).reduce((pv, [key, value]) => {
Expand Down Expand Up @@ -246,6 +251,46 @@ class Michelson_Record {
}
}

const buildRecord = (
fields: Record<string, Michelson_LiteralUnion>,
layout?: PairsOfKeys<string>,
): Michelson_LiteralUnion => {
const buildBranch = (branch: string | PairsOfKeys<string>): Michelson_LiteralUnion => {
if (typeof branch === 'string') {
// Set field annotation
fields[branch].type.setAnnotation(branch);
return fields[branch];
}
const [left, right] = branch;
return Pair(buildBranch(left), buildBranch(right));
};
return buildBranch(layout || composeRightCombLayout(Object.keys(fields)));
};
/**
* @description Build variant literal
* @param branch branch name
* @param value branch value
* @param type variant type
* @returns {Michelson_Literal_C1}
*/
const buildVariant = (target: string, value: Michelson_LiteralUnion, type: IRecordVariant): Michelson_Literal_C1 => {
const [left, right] = type.layout;
if (left === target) {
return Left(value, type);
}
if (right === target) {
return Right(value, type);
}
if (Array.isArray(left) && left.flat().includes(target)) {
return Left(buildVariant(target, value, buildRecordVariantType(type.fields, left, TOr)), type);
}
if (Array.isArray(right) && right.flat().includes(target)) {
return Right(buildVariant(target, value, buildRecordVariantType(type.fields, right, TOr)), type);
}

throw new Error(`Variant (${target}) is invalid.`);
};

// Singletons
export const Nat = (value: number) => new Michelson_Literal(Prim.int, TNat(), value);
export const Int = (value: number) => new Michelson_Literal(Prim.int, TInt(), value);
Expand Down Expand Up @@ -281,9 +326,14 @@ export const Map = (elements: Michelson_LiteralUnion[][], keyType: IType, valueT
new Michelson_Map(TMap(keyType, valueType), elements);
export const Big_map = (elements: Michelson_LiteralUnion[][], keyType: IType, valueType: IType) =>
new Michelson_Map(TBig_map(keyType, valueType), elements);
export const Left = (value: Michelson_LiteralUnion, type: IType) => new Michelson_Literal_C1(Prim.Left, type, [value]);
export const Right = (value: Michelson_LiteralUnion, type: IType) =>
new Michelson_Literal_C1(Prim.Right, type, [value]);
// Artificial containers
export const Record = (fields: Record<string, Michelson_LiteralUnion>, layout?: PairsOfKeys<keyof typeof fields>) =>
new Michelson_Record(fields, layout);
export const Record = (fields: Record<string, Michelson_LiteralUnion>, layout?: PairsOfKeys<string>) =>
buildRecord(fields, layout);
export const Variant = (branch: string, value: Michelson_LiteralUnion, type: IRecordVariant) =>
buildVariant(branch, value, type);

const Literals = {
// Singletons
Expand Down Expand Up @@ -314,6 +364,7 @@ const Literals = {
// Lambda,
// Artificial containers
Record,
Variant,
};

export default Literals;
111 changes: 19 additions & 92 deletions src/core/type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { composeRightCombLayout } from '../misc/utils';
import { MichelsonJSON, MichelsonMicheline, PairsOfKeys } from '../typings';
import { IType, PrimType } from '../typings/type';
import { Prim } from './enums/prim';
Expand Down Expand Up @@ -172,99 +173,25 @@ export class Michelson_Type_With_Param implements IType {
}
}

export class Michelson_Type_RecordOrVariant<T extends Record<string, IType> = Record<string, IType>> implements IType {
_isType = true as const;
#annotation?: string;
#fields: T;
// Default: right combs => https://tezos.gitlab.io/active/michelson.html#operations-on-pairs-and-right-combs
#layout: PairsOfKeys<keyof T>;

constructor(private type: Prim.or | Prim.pair, fields: T, layout?: PairsOfKeys<keyof T>) {
Object.entries(fields).forEach(([key, value]) => value.setAnnotation(key));
this.#fields = fields;
this.#layout = layout || Michelson_Type_RecordOrVariant.composeRightCombLayout(Object.keys(fields));
}

static composeRightCombLayout = <K>(fields: K[]): PairsOfKeys<K> => {
if (fields.length > 2) {
return [fields[0], this.composeRightCombLayout<K>(fields.slice(1))];
export interface IRecordVariant extends IType {
fields: Record<string, IType>;
layout: PairsOfKeys<string>;
}
export const buildRecordVariantType = (
fields: Record<string, IType>,
layout: PairsOfKeys<string>,
container: (leftType: IType, rightType: IType) => IType,
): IRecordVariant => {
const buildBranch = (branch: string | PairsOfKeys<string>): IType => {
if (typeof branch === 'string') {
return fields[branch].setAnnotation(branch);
}
return fields;
const [left, right] = branch;
return container(buildBranch(left), buildBranch(right));
};

/**
* @description Set field annotation
* @link https://tezos.gitlab.io/active/michelson.html#field-and-constructor-annotations
* @param {string} annotation field annotation
*/
public setAnnotation(annotation: string) {
this.#annotation = annotation;
return this;
}

/**
* @description Generate the Micheline representation of the type
* @param fields Record fields
* @param layout Record layout
* @returns {MichelsonMicheline} Micheline representation
*/
private _toMicheline(fields: T, layout: PairsOfKeys<keyof T>): MichelsonMicheline {
const annotation = this.#annotation ? ` %${this.#annotation}` : '';
const innerTypes = layout
.map((layout) => {
if (Array.isArray(layout)) {
return this._toMicheline(fields, layout);
}
return fields[layout].toMicheline();
}, '')
.join(' ');
return `(${this.type}${annotation} ${innerTypes})`;
}

/**
* @description Generate the Micheline representation of the type
* @returns {MichelsonMicheline} Micheline representation
*/
public toMicheline(): MichelsonMicheline {
return this._toMicheline(this.#fields, this.#layout);
}

/**
* @description Generate the JSON representation of the type
* @param fields Record fields
* @param layout Record layout
* @returns {MichelsonMicheline} JSON representation
*/
private _toJSON(fields: T, layout: PairsOfKeys<keyof T>): MichelsonJSON {
return {
prim: this.type,
...(this.#annotation ? { annots: [`%${this.#annotation}`] } : {}),
args: layout.map((layout) => {
if (Array.isArray(layout)) {
return this._toJSON(fields, layout);
}

return fields[layout].toJSON();
}, []),
};
}

/**
* @description Generate the JSON representation of the type
* @returns {MichelsonMicheline} JSON representation
*/
public toJSON(): MichelsonJSON {
return this._toJSON(this.#fields, this.#layout);
}

/**
* @description Resolve type instance to a primitive
* @return {MichelsonJSON} Michelson JSON format
*/
[Symbol.toPrimitive](): MichelsonJSON {
return this.toJSON();
}
}
return Object.assign(buildBranch(layout), { fields, layout });
};

// Singleton types
export const TNat = () => new Michelson_Type(Prim.nat);
Expand Down Expand Up @@ -301,9 +228,9 @@ export const TSapling_transaction = (memoSize: number) =>
new Michelson_Type_With_Param(Prim.sapling_transaction, memoSize);
// Artificial Types
export const TRecord = (fields: Record<string, IType>, layout?: PairsOfKeys<string>) =>
new Michelson_Type_RecordOrVariant(Prim.pair, fields, layout);
buildRecordVariantType(fields, layout || composeRightCombLayout(Object.keys(fields)), TPair);
export const TVariant = (fields: Record<string, IType>, layout?: PairsOfKeys<string>) =>
new Michelson_Type_RecordOrVariant(Prim.or, fields, layout);
buildRecordVariantType(fields, layout || composeRightCombLayout(Object.keys(fields)), TOr);

const Types = {
// Singleton types
Expand Down
16 changes: 16 additions & 0 deletions src/misc/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { PairsOfKeys } from '../typings';

export const capitalizeBoolean = (bool: boolean): string => (bool ? 'True' : 'False');

export const compressHexString = (str: string) => {
return (str.slice(0, 2) === '0x' ? str.slice(2) : str).toLowerCase();
};

/**
* @description Build right aligned nested binary pairs
* @see https://tezos.gitlab.io/active/michelson.html#operations-on-pairs-and-right-combs
* @param fields A sequence of strings
* @returns {PairsOfKeys<K>}
*/
export const composeRightCombLayout = <K>(fields: K[]): PairsOfKeys<K> => {
if (fields.length > 2) {
return [fields[0], composeRightCombLayout<K>(fields.slice(1))];
}
return fields;
};

const Utils = {
capitalizeBoolean,
compressHexString,
composeRightCombLayout,
};

export default Utils;
4 changes: 3 additions & 1 deletion src/typings/literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export type PrimValue =
| Prim.Some
| Prim.None
| Prim.list
| Prim.Pair;
| Prim.Pair
| Prim.Left
| Prim.Right;
2 changes: 0 additions & 2 deletions src/typings/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export interface IType {
toJSON: () => MichelsonJSON;
}

export type ILayout = (string | ILayout)[];

export type PrimType =
// Singleton types
| Prim.unit
Expand Down
Loading

0 comments on commit 0063345

Please sign in to comment.