Skip to content

Commit

Permalink
feat: Expand and implement Bytes and BigUint factory methods
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanmenzel committed Oct 9, 2024
1 parent af18eb0 commit d013fa9
Show file tree
Hide file tree
Showing 20 changed files with 1,910 additions and 82 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dev:tealscript": "tsx src/cli.ts build examples/tealscript/example.algo.ts",
"dev:approvals": "tsx src/cli.ts build tests/approvals --output-awst --output-awst-json --dry-run",
"dev:expected-output": "tsx src/cli.ts build tests/expected-output --dry-run",
"dev:testing": "tsx src/cli.ts build tests/approvals/named-types.algo.ts --output-awst --output-awst-json --log-level=info",
"dev:testing": "tsx src/cli.ts build tests/approvals/byte-expressions.algo.ts --output-awst --output-awst-json --log-level=info",
"audit": "better-npm-audit audit",
"format": "prettier --write .",
"lint": "eslint \"src/**/*.ts\"",
Expand Down
2 changes: 1 addition & 1 deletion packages/algo-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@algorandfoundation/algorand-typescript",
"version": "0.0.1-alpha.5",
"version": "0.0.1-alpha.6",
"description": "This package contains definitions for the types which comprise Algorand TypeScript which can be compiled to run on the Algorand Virtual Machine using the Puya compiler.",
"private": false,
"main": "index.js",
Expand Down
6 changes: 6 additions & 0 deletions packages/algo-ts/src/impl/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export class AvmError extends Error {
export function avmError(message: string): never {
throw new AvmError(message)
}

export function avmInvariant(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new AvmError(message)
}
}
/**
* Raised when an assertion fails
*/
Expand Down
56 changes: 40 additions & 16 deletions packages/algo-ts/src/impl/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { biguint, BigUintCompat, bytes, BytesCompat, uint64, Uint64Compat } from '../index'
import { DeliberateAny } from '../typescript-helpers'
import { base32ToUint8Array } from './base-32'
import {
base64ToUint8Array,
Expand All @@ -10,7 +9,7 @@ import {
uint8ArrayToUtf8,
utf8ToUint8Array,
} from './encoding-util'
import { avmError, AvmError, internalError } from './errors'
import { avmError, AvmError, avmInvariant, internalError } from './errors'
import { nameOfType } from './name-of-type'

const MAX_UINT8 = 2 ** 8 - 1
Expand Down Expand Up @@ -46,6 +45,25 @@ export const toBytes = (val: unknown): bytes => {
}
}

/**
* Convert a StubUint64Compat value into a 'number' if possible.
* This value may be negative
* @param v
*/
export const getNumber = (v: StubUint64Compat): number => {
if (typeof v == 'boolean') return v ? 1 : 0
if (typeof v == 'number') return v
if (typeof v == 'bigint') {
avmInvariant(
v <= BigInt(Number.MAX_SAFE_INTEGER) && v >= BigInt(Number.MIN_SAFE_INTEGER),
'value cannot be safely converted to a number',
)
return Number(v)
}
if (v instanceof Uint64Cls) return v.asNumber()
internalError(`Cannot convert ${v} to number`)
}

export const isBytes = (v: unknown): v is StubBytesCompat => {
if (typeof v === 'string') return true
if (v instanceof BytesCls) return true
Expand All @@ -59,18 +77,14 @@ export const isUint64 = (v: unknown): v is StubUint64Compat => {
return v instanceof Uint64Cls
}

export const isBigUint = (v: unknown): v is biguint => {
return v instanceof BigUintCls
}

export const checkUint64 = (v: bigint): bigint => {
const u64 = BigInt.asUintN(64, v)
if (u64 !== v) throw new AvmError(`Uint64 over or underflow`)
if (u64 !== v) throw new AvmError(`Uint64 overflow or underflow`)
return u64
}
export const checkBigUint = (v: bigint): bigint => {
const uBig = BigInt.asUintN(64 * 8, v)
if (uBig !== v) throw new AvmError(`BigUint over or underflow`)
if (uBig !== v) throw new AvmError(`BigUint overflow or underflow`)
return uBig
}

Expand Down Expand Up @@ -131,10 +145,6 @@ export class Uint64Cls extends AlgoTsPrimitiveCls {
internalError(`Cannot convert ${v} to uint64`)
}

static getNumber(v: StubUint64Compat): number {
return Uint64Cls.fromCompat(v).asNumber()
}

valueOf(): bigint {
return this.#value
}
Expand Down Expand Up @@ -228,7 +238,7 @@ export class BytesCls extends AlgoTsPrimitiveCls {

at(i: StubUint64Compat): BytesCls {
const start = Uint64Cls.fromCompat(i).asNumber()
return new BytesCls(this.#v.slice(start, start + 1))
return new BytesCls(arrayUtil.arrayAt(this.#v, i))
}

slice(start: StubUint64Compat, end: StubUint64Compat): BytesCls {
Expand Down Expand Up @@ -348,12 +358,26 @@ export class BytesCls extends AlgoTsPrimitiveCls {
}

export const arrayUtil = new (class {
arrayAt<T>(arrayLike: T[], index: StubUint64Compat): T {
return arrayLike.at(Uint64Cls.fromCompat(index).asNumber()) ?? avmError('Index out of bounds')
arrayAt(arrayLike: Uint8Array, index: StubUint64Compat): Uint8Array
arrayAt<T>(arrayLike: T[], index: StubUint64Compat): T
arrayAt<T>(arrayLike: T[] | Uint8Array, index: StubUint64Compat): T | Uint8Array {
const indexNum = getNumber(index)
if (arrayLike instanceof Uint8Array) {
const res = arrayLike.slice(indexNum, indexNum + 1)
avmInvariant(res.length, 'Index out of bounds')
return res
}
return arrayLike.at(indexNum) ?? avmError('Index out of bounds')
}
arraySlice(arrayLike: Uint8Array, start: StubUint64Compat, end: StubUint64Compat): Uint8Array
arraySlice<T>(arrayLike: T[], start: StubUint64Compat, end: StubUint64Compat): T[]
arraySlice<T>(arrayLike: T[] | Uint8Array, start: StubUint64Compat, end: StubUint64Compat) {
return arrayLike.slice(Uint64Cls.getNumber(start), Uint64Cls.getNumber(end)) as DeliberateAny
const startNum = getNumber(start)
const endNum = getNumber(end)
if (arrayLike instanceof Uint8Array) {
return arrayLike.slice(startNum, endNum)
} else {
return arrayLike.slice(startNum, endNum)
}
}
})()
72 changes: 63 additions & 9 deletions packages/algo-ts/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BigUintCls, BytesCls, Uint64Cls } from './impl/primitives'

export type Uint64Compat = uint64 | bigint | boolean | number
export type BigUintCompat = Uint64Compat | bigint | bytes | number
export type BigUintCompat = bigint | bytes | number | boolean
export type StringCompat = string
export type BytesCompat = bytes | string | Uint8Array
export type BytesCompat = bytes | string

/**
* An unsigned integer of exactly 64 bits
Expand All @@ -13,11 +13,16 @@ export type uint64 = {
} & number

/**
* Create a uint64 value
* @param v The value to use
* Create a uint64 from a bigint literal
*/
export function Uint64(v: bigint): uint64
/**
* Create a uint64 from a number literal
*/
export function Uint64(v: number): uint64
/**
* Create a uint64 from a boolean value. True is 1, False is 0
*/
export function Uint64(v: boolean): uint64
export function Uint64(v: Uint64Compat): uint64 {
return Uint64Cls.fromCompat(v).asAlgoTs()
Expand All @@ -33,14 +38,36 @@ export type biguint = {
} & bigint

/**
* Create a biguint value
* @param v The value to use
* Create a biguint from a bigint literal
*/
export function BigUint(v: bigint): biguint
/**
* Create a biguint from a boolean value (true = 1, false = 0)
*/
export function BigUint(v: boolean): biguint
/**
* Create a biguint from a uint64 value
*/
export function BigUint(v: uint64): biguint
/**
* Create a biguint from a number literal
*/
export function BigUint(v: number): biguint
/**
* Create a biguint from a byte array interpreted as a big-endian number
*/
export function BigUint(v: bytes): biguint
export function BigUint(v: BigUintCompat): biguint {
/**
* Create a biguint from a string literal containing the decimal digits
*/
export function BigUint(v: string): biguint
/**
* Create a biguint with the default value of 0
*/
export function BigUint(): biguint
export function BigUint(v?: BigUintCompat | string): biguint {
if (typeof v === 'string') v = BigInt(v)
else if (v === undefined) v = 0n
return BigUintCls.fromCompat(v).asAlgoTs()
}

Expand All @@ -66,12 +93,39 @@ export type bytes = {
toString(): string
}

/**
* Create a byte array from a string interpolation template and compatible replacements
* @param value
* @param replacements
*/
export function Bytes(value: TemplateStringsArray, ...replacements: BytesCompat[]): bytes
export function Bytes(value: BytesCompat): bytes
/**
* Create a byte array from a utf8 string
*/
export function Bytes(value: string): bytes
/**
* No op, returns the provided byte array.
*/
export function Bytes(value: bytes): bytes
/**
* Create a byte array from a biguint value encoded as a variable length big-endian number
*/
export function Bytes(value: biguint): bytes
/**
* Create a byte array from a uint64 value encoded as a fixed length 64-bit number
*/
export function Bytes(value: uint64): bytes
/**
* Create an empty byte array
*/
export function Bytes(): bytes
export function Bytes(value?: BytesCompat | TemplateStringsArray, ...replacements: BytesCompat[]): bytes {
export function Bytes(value?: BytesCompat | TemplateStringsArray | biguint | uint64, ...replacements: BytesCompat[]): bytes {
if (isTemplateStringsArray(value)) {
return BytesCls.fromInterpolation(value, replacements).asAlgoTs()
} else if (typeof value === 'bigint' || value instanceof BigUintCls) {
return BigUintCls.fromCompat(value).toBytes().asAlgoTs()
} else if (typeof value === 'number' || value instanceof Uint64Cls) {
return Uint64Cls.fromCompat(value).toBytes().asAlgoTs()
} else {
return BytesCls.fromCompat(value).asAlgoTs()
}
Expand Down
27 changes: 22 additions & 5 deletions src/awst/node-factory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CodeError } from '../errors'
import type { DeliberateAny, Props } from '../typescript-helpers'
import { codeInvariant, invariant } from '../util'
import type { Expression, Statement } from './nodes'
Expand Down Expand Up @@ -55,16 +56,32 @@ const explicitNodeFactory = {
wtype: wtypes.stringWType,
})
},
uInt64Constant(props: { value: bigint; tealAlias?: string; sourceLocation: SourceLocation }): IntegerConstant {
uInt64Constant({
value,
tealAlias,
sourceLocation,
}: {
value: bigint
tealAlias?: string
sourceLocation: SourceLocation
}): IntegerConstant {
if (value < 0n || value >= 2n ** 64n) {
throw new CodeError(`uint64 overflow or underflow: ${value}`, { sourceLocation })
}
return new IntegerConstant({
...props,
value,
sourceLocation,
wtype: wtypes.uint64WType,
tealAlias: props.tealAlias ?? null,
tealAlias: tealAlias ?? null,
})
},
bigUIntConstant(props: { value: bigint; sourceLocation: SourceLocation }): IntegerConstant {
bigUIntConstant({ value, sourceLocation }: { value: bigint; sourceLocation: SourceLocation }): IntegerConstant {
if (value < 0n || value >= 2n ** 512n) {
throw new CodeError(`biguint overflow or underflow: ${value}`, { sourceLocation })
}
return new IntegerConstant({
...props,
value,
sourceLocation,
wtype: wtypes.biguintWType,
tealAlias: null,
})
Expand Down
Loading

0 comments on commit d013fa9

Please sign in to comment.