Skip to content

Commit

Permalink
feat: Expand Bytes factory method to support iterable<int64> (ie. uin…
Browse files Browse the repository at this point in the history
…t8array)
  • Loading branch information
tristanmenzel committed Oct 9, 2024
1 parent d013fa9 commit 88bf321
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 146 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.

12 changes: 2 additions & 10 deletions packages/algo-ts/package-lock.json

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

3 changes: 1 addition & 2 deletions 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.6",
"version": "0.0.1-alpha.7",
"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 Expand Up @@ -31,7 +31,6 @@
"@rollup/plugin-node-resolve": "15.3.0",
"@rollup/plugin-typescript": "12.1.0",
"@tsconfig/node20": "20.1.4",
"@types/lodash": "^4.17.9",
"@types/node": "22.6.1",
"@typescript-eslint/eslint-plugin": "8.7.0",
"@typescript-eslint/parser": "8.7.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/algo-ts/src/impl/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export class BytesCls extends AlgoTsPrimitiveCls {
return isInstanceOfTypeByName(x, BytesCls)
}

static fromCompat(v: StubBytesCompat | undefined): BytesCls {
static fromCompat(v: StubBytesCompat | Uint8Array | undefined): BytesCls {
if (v === undefined) return new BytesCls(new Uint8Array())
if (typeof v === 'string') return new BytesCls(utf8ToUint8Array(v))
if (v instanceof BytesCls) return v
Expand Down
19 changes: 17 additions & 2 deletions packages/algo-ts/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BigUintCls, BytesCls, Uint64Cls } from './impl/primitives'
import { CodeError } from './impl/errors'
import { BigUintCls, BytesCls, getNumber, Uint64Cls } from './impl/primitives'

export type Uint64Compat = uint64 | bigint | boolean | number
export type BigUintCompat = bigint | bytes | number | boolean
Expand Down Expand Up @@ -115,17 +116,31 @@ 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 a byte array from an Iterable<uint64> where each item is interpreted as a single byte and must be between 0 and 255 inclusively
*/
export function Bytes(value: Iterable<uint64>): bytes
/**
* Create an empty byte array
*/
export function Bytes(): bytes
export function Bytes(value?: BytesCompat | TemplateStringsArray | biguint | uint64, ...replacements: BytesCompat[]): bytes {
export function Bytes(
value?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable<number>,
...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 if (typeof value === 'object' && Symbol.iterator in value) {
const valueItems = Array.from(value).map((v) => getNumber(v))
const invalidValue = valueItems.find((v) => v < 0 && v > 255)
if (invalidValue) {
throw new CodeError(`Cannot convert ${invalidValue} to a byte`)
}
return new BytesCls(new Uint8Array(value)).asAlgoTs()
} else {
return BytesCls.fromCompat(value).asAlgoTs()
}
Expand Down
15 changes: 2 additions & 13 deletions src/awst_build/eb/biguint-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import { NotSupported } from '../../errors'
import { logger } from '../../logger'
import { tryConvertEnum } from '../../util'
import type { InstanceType, PType } from '../ptypes'
import { BigUintFunction, biguintPType, boolPType, bytesPType, numberPType, stringPType, uint64PType } from '../ptypes'
import { BigUintFunction, biguintPType, boolPType, bytesPType, stringPType, uint64PType } from '../ptypes'
import { BooleanExpressionBuilder } from './boolean-expression-builder'
import type { InstanceBuilder } from './index'
import { BuilderBinaryOp, BuilderComparisonOp, BuilderUnaryOp, FunctionBuilder, InstanceExpressionBuilder } from './index'
import { BigIntLiteralExpressionBuilder } from './literal/big-int-literal-expression-builder'
import { UInt64ExpressionBuilder } from './uint64-expression-builder'
import { requireExpressionOfType } from './util'
import { parseFunctionArgs } from './util/arg-parsing'
Expand All @@ -29,7 +28,7 @@ export class BigUintFunctionBuilder extends FunctionBuilder {
genericTypeArgs: 0,
callLocation: sourceLocation,
funcName: 'BigUInt',
argSpec: (a) => [a.optional(boolPType, stringPType, bytesPType, biguintPType, numberPType, uint64PType)],
argSpec: (a) => [a.optional(boolPType, stringPType, bytesPType, biguintPType, uint64PType)],
})
let biguint: Expression

Expand Down Expand Up @@ -61,16 +60,6 @@ export class BigUintFunctionBuilder extends FunctionBuilder {
sourceLocation,
wtype: biguintPType.wtype,
})
} else if (initialValue.ptype.equals(numberPType)) {
if (initialValue instanceof BigIntLiteralExpressionBuilder) {
biguint = nodeFactory.bigUIntConstant({
value: initialValue.value,
sourceLocation,
})
} else {
logger.error(initialValue.sourceLocation, 'Only compile time numeric values are supported')
biguint = nodeFactory.bigUIntConstant({ value: 0n, sourceLocation })
}
} else if (initialValue.ptype.equals(uint64PType)) {
const expr = initialValue.resolve()
if (expr instanceof IntegerConstant) {
Expand Down
125 changes: 109 additions & 16 deletions src/awst_build/eb/bytes-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,36 @@ import { wtypes } from '../../awst'
import { intrinsicFactory } from '../../awst/intrinsic-factory'
import { nodeFactory } from '../../awst/node-factory'
import type { Expression } from '../../awst/nodes'
import { BytesBinaryOperator, BytesConstant, BytesEncoding, BytesUnaryOperator, StringConstant } from '../../awst/nodes'
import {
BytesBinaryOperator,
BytesConstant,
BytesEncoding,
BytesUnaryOperator,
IntegerConstant,
StringConstant,
UInt64BinaryOperator,
} from '../../awst/nodes'
import type { SourceLocation } from '../../awst/source-location'
import { stringWType } from '../../awst/wtypes'
import { CodeError, wrapInCodeError } from '../../errors'
import { logger } from '../../logger'
import { base32ToUint8Array, base64ToUint8Array, hexToUint8Array, uint8ArrayToUtf8, utf8ToUint8Array } from '../../util'
import { base32ToUint8Array, base64ToUint8Array, hexToUint8Array, invariant, uint8ArrayToUtf8, utf8ToUint8Array } from '../../util'
import type { InstanceType, PType } from '../ptypes'
import { bigIntPType, biguintPType, BytesFunction, bytesPType, numberPType, NumericLiteralPType, stringPType, uint64PType } from '../ptypes'
import {
ArrayPType,
bigIntPType,
biguintPType,
BytesFunction,
bytesPType,
numberPType,
NumericLiteralPType,
stringPType,
uint64PType,
} from '../ptypes'
import { instanceEb } from '../type-registry'
import type { BuilderComparisonOp, InstanceBuilder, NodeBuilder } from './index'
import { BuilderUnaryOp, FunctionBuilder, InstanceExpressionBuilder, ParameterlessFunctionBuilder } from './index'
import { ArrayLiteralExpressionBuilder } from './literal/array-literal-expression-builder'
import { BigIntLiteralExpressionBuilder } from './literal/big-int-literal-expression-builder'
import { StringExpressionBuilder } from './string-expression-builder'
import { UInt64ExpressionBuilder } from './uint64-expression-builder'
Expand Down Expand Up @@ -66,7 +85,9 @@ export class BytesFunctionBuilder extends FunctionBuilder {
genericTypeArgs: 0,
callLocation: sourceLocation,
funcName: 'Bytes',
argSpec: (a) => [a.optional(numberPType, bigIntPType, uint64PType, biguintPType, stringPType, bytesPType)],
argSpec: (a) => [
a.optional(numberPType, bigIntPType, uint64PType, biguintPType, stringPType, bytesPType, new ArrayPType({ itemType: uint64PType })),
],
})
const empty = nodeFactory.bytesConstant({
sourceLocation,
Expand All @@ -86,8 +107,29 @@ export class BytesFunctionBuilder extends FunctionBuilder {
bytesExpr = initialValue.toBytes(sourceLocation)
} else if (initialValue.ptype.equals(stringPType)) {
bytesExpr = initialValue.toBytes(sourceLocation)
} else {
} else if (initialValue.ptype.equals(bytesPType)) {
return initialValue
} else {
// Array
if (initialValue instanceof ArrayLiteralExpressionBuilder) {
const bytes: number[] = []
for (const item of initialValue.getItemBuilders()) {
const byte = item.resolve()
if (byte instanceof IntegerConstant && byte.value < 256n) {
bytes.push(Number(byte.value))
} else {
logger.error(item.sourceLocation, 'A compile time constant value between 0 and 255 is expected here')
break
}
}
bytesExpr = nodeFactory.bytesConstant({
value: Uint8Array.from(bytes),
sourceLocation: initialValue.sourceLocation,
})
} else {
logger.error(initialValue.sourceLocation, 'Only array literals are supported here')
bytesExpr = empty
}
}
return new BytesExpressionBuilder(bytesExpr)
}
Expand Down Expand Up @@ -235,21 +277,39 @@ export class BytesSliceBuilder extends FunctionBuilder {
super(expr.sourceLocation)
}
call(args: ReadonlyArray<InstanceBuilder>, typeArgs: ReadonlyArray<PType>, sourceLocation: SourceLocation) {
// TODO: Needs to do range check on target and handle negative values
// TODO: Also handle single arg
const [start, stop] = requireExpressionsOfType(args, [uint64PType, uint64PType], sourceLocation)
const {
args: [start, stop],
} = parseFunctionArgs({
args,
typeArgs,
genericTypeArgs: 0,
callLocation: sourceLocation,
funcName: 'slice',
argSpec: (a) => [a.optional(uint64PType, numberPType), a.optional(uint64PType, numberPType)],
})

return new BytesExpressionBuilder(
nodeFactory.sliceExpression({
nodeFactory.intersectionSliceExpression({
base: this.expr,
sourceLocation: sourceLocation,
beginIndex: start,
endIndex: stop,
beginIndex: start ? getBigIntOrUint64Expr(start) : null,
endIndex: stop ? getBigIntOrUint64Expr(stop) : null,
wtype: wtypes.bytesWType,
}),
)
}
}

function getBigIntOrUint64Expr(builder: InstanceBuilder) {
if (builder.ptype.equals(numberPType)) {
invariant(builder instanceof BigIntLiteralExpressionBuilder, 'Builder for number type must be BigIntLiteral')
return builder.value
} else {
invariant(builder.ptype.equals(uint64PType), 'Builder must be uint64 if not number')
return builder.resolve()
}
}

export class ToStringBuilder extends ParameterlessFunctionBuilder {
constructor(private expr: awst.Expression) {
super(
Expand All @@ -272,14 +332,47 @@ export class BytesAtBuilder extends FunctionBuilder {
}

call(args: ReadonlyArray<InstanceBuilder>, typeArgs: ReadonlyArray<PType>, sourceLocation: SourceLocation) {
const [index] = requireExpressionsOfType(args, [uint64PType], sourceLocation)
// TODO: Needs to do range check on target and handle negative values
const {
args: [index],
} = parseFunctionArgs({
args,
typeArgs,
genericTypeArgs: 0,
callLocation: sourceLocation,
funcName: 'at',
argSpec: (a) => [a.required(uint64PType, numberPType)],
})

let indexExpr: Expression

if (index.ptype.equals(numberPType)) {
invariant(index instanceof BigIntLiteralExpressionBuilder, 'Builder for number type must be BigIntLiteral')

if (index.value < 0) {
indexExpr = nodeFactory.uInt64BinaryOperation({
op: UInt64BinaryOperator.sub,
left: intrinsicFactory.bytesLen({
value: this.expr,
sourceLocation,
}),
right: nodeFactory.uInt64Constant({
value: index.value * -1n,
sourceLocation,
}),
sourceLocation,
})
} else {
indexExpr = index.resolveToPType(uint64PType).resolve()
}
} else {
indexExpr = index.resolve()
}

return instanceEb(
nodeFactory.sliceExpression({
nodeFactory.indexExpression({
base: this.expr,
sourceLocation: sourceLocation,
beginIndex: index,
endIndex: nodeFactory.uInt64Constant({ value: 1n, sourceLocation }),
index: indexExpr,
wtype: wtypes.bytesWType,
}),
bytesPType,
Expand Down
16 changes: 14 additions & 2 deletions src/awst_build/eb/literal/big-int-literal-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class BigIntLiteralExpressionBuilder extends LiteralExpressionBuilder {
}

resolvableToPType(ptype: PTypeOrClass): boolean {
if (!isValidLiteralForPType(this.value, ptype)) return false
if (this.ptype instanceof NumericLiteralPType || this.ptype.equals(numberPType)) {
return ptype.equals(uint64PType) || ptype.equals(numberPType) || ptype.equals(this.ptype)
} else if (this.ptype instanceof BigIntLiteralPType || this.ptype.equals(bigIntPType)) {
Expand Down Expand Up @@ -75,7 +76,8 @@ export class BigIntLiteralExpressionBuilder extends LiteralExpressionBuilder {
return this.resolveToPType(other.ptype).binaryOp(other, op, sourceLocation)
}
if (other instanceof BigIntLiteralExpressionBuilder) {
return new BigIntLiteralExpressionBuilder(foldBinaryOp(this.value, other.value, op, sourceLocation), this.ptype, sourceLocation)
const folded = foldBinaryOp(this.value, other.value, op, sourceLocation)
return new BigIntLiteralExpressionBuilder(folded, this.getUpdatedPType(folded), sourceLocation)
}
return super.binaryOp(other, op, sourceLocation)
}
Expand All @@ -91,10 +93,20 @@ export class BigIntLiteralExpressionBuilder extends LiteralExpressionBuilder {
prefixUnaryOp(op: BuilderUnaryOp, sourceLocation: SourceLocation): InstanceBuilder {
switch (op) {
case BuilderUnaryOp.neg:
return new BigIntLiteralExpressionBuilder(-this.value, this.ptype, sourceLocation)
return new BigIntLiteralExpressionBuilder(-this.value, this.getUpdatedPType(-this.value), sourceLocation)
case BuilderUnaryOp.pos:
return new BigIntLiteralExpressionBuilder(this.value, this.ptype, sourceLocation)
}
return super.prefixUnaryOp(op, sourceLocation)
}

private getUpdatedPType(value: bigint) {
if (this.ptype instanceof BigIntLiteralPType) {
return new BigIntLiteralPType({ literalValue: value })
}
if (this.ptype instanceof NumericLiteralPType) {
return new NumericLiteralPType({ literalValue: value })
}
return this.ptype
}
}
4 changes: 2 additions & 2 deletions src/awst_build/eb/util/arg-parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ export function parseFunctionArgs<const TGenericCount extends number, const TArg
return builder
}
}
throw new CodeError(`Arg ${i} of ${funcName} has an incorrect type of ${source.ptype}. Expected ${a.t.join(' or ')}`, {
sourceLocation: callLocation,
throw new CodeError(`Arg ${i} of ${funcName} has an incorrect type of ${source.ptype}. Expected ${a.t.join(' | ')}`, {
sourceLocation: source.sourceLocation,
})
}
}
Expand Down
Loading

0 comments on commit 88bf321

Please sign in to comment.