Skip to content

Commit

Permalink
feat(clarity): introduce slice functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lgalabru authored and pavitthrap committed Aug 2, 2021
1 parent c5401ec commit 844e5dc
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/vm/analysis/arithmetic_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl<'a> ArithmeticOnlyChecker<'a> {
return Err(Error::FunctionNotPermitted(function));
}
Append | Concat | AsMaxLen | ContractOf | PrincipalOf | ListCons | Print
| AsContract | ElementAt | IndexOf | Map | Filter | Fold => {
| AsContract | ElementAt | IndexOf | Map | Filter | Fold | Slice => {
return Err(Error::FunctionNotPermitted(function));
}
BuffToIntLe | BuffToUIntLe | BuffToIntBe | BuffToUIntBe | IsStandard => {
Expand Down
3 changes: 3 additions & 0 deletions src/vm/analysis/arithmetic_checker/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ fn test_functions_clarity1() {
Err(FunctionNotPermitted(NativeFunctions::Sha512))),
("(define-private (foo) (sha512/256 0))",
Err(FunctionNotPermitted(NativeFunctions::Sha512Trunc256))),
("(define-private (foo (a (list 3 uint)))
(slice a u2 u1))",
Err(FunctionNotPermitted(NativeFunctions::Slice))),

// Clarity2 functions.
(r#"(stx-transfer-memo? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 0x010203)"#,
Expand Down
2 changes: 1 addition & 1 deletion src/vm/analysis/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ impl DiagnosableError for CheckErrors {
CheckErrors::NoSuperType(a, b) => format!("unable to create a supertype for the two types: '{}' and '{}'", a, b),
CheckErrors::UnknownListConstructionFailure => format!("invalid syntax for list definition"),
CheckErrors::ListTypesMustMatch => format!("expecting elements of same type in a list"),
CheckErrors::ConstructedListTooLarge => format!("reached limit of elements in a list"),
CheckErrors::ConstructedListTooLarge => format!("reached limit of elements in a sequence"),
CheckErrors::TypeError(expected_type, found_type) => format!("expecting expression of type '{}', found '{}'", expected_type, found_type),
CheckErrors::TypeLiteralError(expected_type, found_type) => format!("expecting a literal of type '{}', found '{}'", expected_type, found_type),
CheckErrors::TypeValueError(expected_type, found_value) => format!("expecting expression of type '{}', found '{}'", expected_type, found_value),
Expand Down
2 changes: 1 addition & 1 deletion src/vm/analysis/read_only_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl<'a, 'b> ReadOnlyChecker<'a, 'b> {
| Append | Concat | AsMaxLen | ContractOf | PrincipalOf | ListCons | GetBlockInfo
| TupleGet | TupleMerge | Len | Print | AsContract | Begin | FetchVar
| GetStxBalance | StxGetAccount | GetTokenBalance | GetAssetOwner | GetTokenSupply
| ElementAt | IndexOf => {
| ElementAt | IndexOf | Slice => {
// Check all arguments.
self.check_all_read_only(args)
}
Expand Down
1 change: 1 addition & 0 deletions src/vm/analysis/type_checker/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ impl TypedNativeFunction {
Len => Special(SpecialNativeFunction(&sequences::check_special_len)),
ElementAt => Special(SpecialNativeFunction(&sequences::check_special_element_at)),
IndexOf => Special(SpecialNativeFunction(&sequences::check_special_index_of)),
Slice => Special(SpecialNativeFunction(&sequences::check_special_slice)),
ListCons => Special(SpecialNativeFunction(&check_special_list_cons)),
FetchEntry => Special(SpecialNativeFunction(&maps::check_special_fetch_entry)),
SetEntry => Special(SpecialNativeFunction(&maps::check_special_set_entry)),
Expand Down
62 changes: 62 additions & 0 deletions src/vm/analysis/type_checker/natives/sequences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,65 @@ pub fn check_special_index_of(

TypeSignature::new_option(TypeSignature::UIntType).map_err(|e| e.into())
}

pub fn check_special_slice(
checker: &mut TypeChecker,
args: &[SymbolicExpression],
context: &TypingContext,
) -> TypeResult {
check_argument_count(3, args)?;

// Position
let position = match args[1].expr {
SymbolicExpressionType::LiteralValue(Value::UInt(position)) => position,
_ => {
let position_type = checker.type_check(&args[1], context)?;
return Err(CheckErrors::TypeError(TypeSignature::UIntType, position_type).into());
}
};
checker
.type_map
.set_type(&args[1], TypeSignature::UIntType)?;

let position = u32::try_from(position).map_err(|_e| CheckErrors::MaxLengthOverflow)?;

// Length
let length = match args[2].expr {
SymbolicExpressionType::LiteralValue(Value::UInt(expected_len)) => expected_len,
_ => {
let expected_len_type = checker.type_check(&args[2], context)?;
return Err(CheckErrors::TypeError(TypeSignature::UIntType, expected_len_type).into());
}
};
checker
.type_map
.set_type(&args[2], TypeSignature::UIntType)?;

let length = u32::try_from(length).map_err(|_e| CheckErrors::MaxLengthOverflow)?;

// Sequence
let seq_type = checker.type_check(&args[0], context)?;

let (origin_len, resized_seq) = match &seq_type {
TypeSignature::SequenceType(ListType(list)) => {
(list.get_max_len(), TypeSignature::list_of(list.get_list_item_type().clone(), length)?)
},
TypeSignature::SequenceType(BufferType(len)) => {
(len.0, TypeSignature::SequenceType(BufferType(BufferLength(length))))
},
TypeSignature::SequenceType(StringType(ASCII(len))) => {
(len.0, TypeSignature::SequenceType(StringType(ASCII(BufferLength(length)))))
},
TypeSignature::SequenceType(StringType(UTF8(len))) => {
(len.0, TypeSignature::SequenceType(StringType(UTF8(StringUTF8Length(length)))))
},
_ => return Err(CheckErrors::ExpectedSequence(seq_type.clone()).into()),
};

if (position + length) > origin_len {
return Err(CheckErrors::ConstructedListTooLarge.into())
}

Ok(resized_seq)
}

126 changes: 120 additions & 6 deletions src/vm/analysis/type_checker/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,12 +987,12 @@ fn test_buff_fold() {
let good = [
"(define-private (get-len (x (buff 1)) (acc uint)) (+ acc u1))
(fold get-len 0x000102030405 u0)",
"(define-private (slice (x (buff 1)) (acc (tuple (limit uint) (cursor uint) (data (buff 10)))))
"(define-private (slice-v1 (x (buff 1)) (acc (tuple (limit uint) (cursor uint) (data (buff 10)))))
(if (< (get cursor acc) (get limit acc))
(let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u10))))
(tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data)))
acc))
(fold slice 0x00010203040506070809 (tuple (limit u5) (cursor u0) (data 0x)))"];
(fold slice-v1 0x00010203040506070809 (tuple (limit u5) (cursor u0) (data 0x)))"];
let expected = [
"uint",
"(tuple (cursor uint) (data (buff 10)) (limit uint))",
Expand Down Expand Up @@ -1076,6 +1076,120 @@ fn test_native_append() {
}
}

#[test]
fn test_slice_list() {
let good = ["(slice (list 2 3 4 5 6 7 8) u0 u3)", "(slice (list u0 u1 u2 u3 u4) u3 u2)"];
let expected = ["(list 3 int)", "(list 2 uint)"];

for (good_test, expected) in good.iter().zip(expected.iter()) {
assert_eq!(
expected,
&format!("{}", type_check_helper(&good_test).unwrap())
);
}

let bad = [
"(slice (list 2 3) 3 u4)",
"(slice (list 2 3) u3 4)",
"(slice (list u0) u1)",
];

let bad_expected = [
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::IncorrectArgumentCount(3, 2),
];
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
assert_eq!(expected, &type_check_helper(&bad_test).unwrap_err().err);
}
}

#[test]
fn test_slice_buff() {
let good = ["(slice 0x000102030405 u0 u3)", "(slice 0x000102030405 u3 u2)"];
let expected = ["(buff 3)", "(buff 2)"];

for (good_test, expected) in good.iter().zip(expected.iter()) {
assert_eq!(
expected,
&format!("{}", type_check_helper(&good_test).unwrap())
);
}

let bad = [
"(slice 0x000102030405 3 u4)",
"(slice 0x000102030405 u3 4)",
"(slice 0x000102030405 u1)",
];

let bad_expected = [
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::IncorrectArgumentCount(3, 2),
];
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
assert_eq!(expected, &type_check_helper(&bad_test).unwrap_err().err);
}
}


#[test]
fn test_slice_ascii() {
let good = ["(slice \"blockstack\" u4 u5)", "(slice \"blockstack\" u0 u5)"];
let expected = ["(string-ascii 5)", "(string-ascii 5)"];

for (good_test, expected) in good.iter().zip(expected.iter()) {
assert_eq!(
expected,
&format!("{}", type_check_helper(&good_test).unwrap())
);
}

let bad = [
"(slice \"blockstack\" 3 u4)",
"(slice \"blockstack\" u3 4)",
"(slice \"blockstack\" u1)",
];

let bad_expected = [
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::IncorrectArgumentCount(3, 2),
];
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
assert_eq!(expected, &type_check_helper(&bad_test).unwrap_err().err);
}
}


#[test]
fn test_slice_utf8() {
let good = ["(slice u\"blockstack\" u4 u5)", "(slice u\"blockstack\" u4 u5)"];
let expected = ["(string-utf8 5)", "(string-utf8 5)"];

for (good_test, expected) in good.iter().zip(expected.iter()) {
assert_eq!(
expected,
&format!("{}", type_check_helper(&good_test).unwrap())
);
}

let bad = [
"(slice u\"blockstack\" 3 u4)",
"(slice u\"blockstack\" u3 4)",
"(slice u\"blockstack\" u1)",
];

let bad_expected = [
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::TypeError(UIntType, IntType),
CheckErrors::IncorrectArgumentCount(3, 2),
];
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
assert_eq!(expected, &type_check_helper(&bad_test).unwrap_err().err);
}
}

#[test]
fn test_native_concat() {
let good = ["(concat (list 2 3) (list 4 5))"];
Expand Down Expand Up @@ -2302,12 +2416,12 @@ fn test_string_ascii_fold() {
let good = [
"(define-private (get-len (x (string-ascii 1)) (acc uint)) (+ acc u1))
(fold get-len \"blockstack\" u0)",
"(define-private (slice (x (string-ascii 1)) (acc (tuple (limit uint) (cursor uint) (data (string-ascii 10)))))
"(define-private (slice-v1 (x (string-ascii 1)) (acc (tuple (limit uint) (cursor uint) (data (string-ascii 10)))))
(if (< (get cursor acc) (get limit acc))
(let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u10))))
(tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data)))
acc))
(fold slice \"blockstack\" (tuple (limit u5) (cursor u0) (data \"\")))"];
(fold slice-v1 \"blockstack\" (tuple (limit u5) (cursor u0) (data \"\")))"];
let expected = [
"uint",
"(tuple (cursor uint) (data (string-ascii 10)) (limit uint))",
Expand Down Expand Up @@ -2355,12 +2469,12 @@ fn test_string_utf8_fold() {
let good = [
"(define-private (get-len (x (string-utf8 1)) (acc uint)) (+ acc u1))
(fold get-len u\"blockstack\" u0)",
"(define-private (slice (x (string-utf8 1)) (acc (tuple (limit uint) (cursor uint) (data (string-utf8 11)))))
"(define-private (slice-v1 (x (string-utf8 1)) (acc (tuple (limit uint) (cursor uint) (data (string-utf8 11)))))
(if (< (get cursor acc) (get limit acc))
(let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u11))))
(tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data)))
acc))
(fold slice u\"blockstack\\u{1F926}\" (tuple (limit u5) (cursor u0) (data u\"\")))"];
(fold slice-v1 u\"blockstack\\u{1F926}\" (tuple (limit u5) (cursor u0) (data u\"\")))"];
let expected = [
"uint",
"(tuple (cursor uint) (data (string-utf8 11)) (limit uint))",
Expand Down
1 change: 1 addition & 0 deletions src/vm/costs/cost_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ define_named_enum!(ClarityCostFunction {
Append("cost_append"),
Concat("cost_concat"),
AsMaxLen("cost_as_max_len"),
Slice("cost_slice"),
ContractCall("cost_contract_call"),
ContractOf("cost_contract_of"),
PrincipalOf("cost_principal_of"),
Expand Down
16 changes: 16 additions & 0 deletions src/vm/docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,21 @@ supplied), this function returns `none`.
"#,
};

const SLICE_API: SpecialAPI = SpecialAPI {
input_type: "buff|list A|string, uint, uint",
output_type: "(optional buff|list A|string)",
signature: "(slice sequence position length)",
description:
"The `slice` function returns a sub-sequence of size `length` in the provided sequence.
If `length` is 0 or `position + length` is greater than or equal to `(len sequence)`, this function returns `none`.",
example: "(slice \"blockstack\" u5 u5) ;; Returns (some \"stack\")
(slice (list 1 2 3 4 5) u5 u2) ;; Returns none
(slice (list 1 2 3 4 5) (+ u1 u2) u1) ;; Returns (some (list 4))
(slice \"abcd\" u1 u2) ;; Returns (some \"bc\")
(slice 0xfb010203 u1 u3) ;; Returns (some 0x010203)
",
};

const LIST_API: SpecialAPI = SpecialAPI {
input_type: "A, ...",
output_type: "(list A)",
Expand Down Expand Up @@ -1870,6 +1885,7 @@ fn make_api_reference(function: &NativeFunctions) -> FunctionAPI {
Len => make_for_special(&LEN_API, name),
ElementAt => make_for_special(&ELEMENT_AT_API, name),
IndexOf => make_for_special(&INDEX_OF_API, name),
Slice => make_for_special(&SLICE_API, name),
ListCons => make_for_special(&LIST_API, name),
FetchEntry => make_for_special(&FETCH_ENTRY_API, name),
SetEntry => make_for_special(&SET_ENTRY_API, name),
Expand Down
2 changes: 2 additions & 0 deletions src/vm/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ define_versioned_named_enum!(NativeFunctions(ClarityVersion) {
StxTransferMemo("stx-transfer-memo?", ClarityVersion::Clarity2),
StxBurn("stx-burn?", ClarityVersion::Clarity1),
StxGetAccount("stx-account", ClarityVersion::Clarity2),
Slice("slice", ClarityVersion::Clarity2),
});

impl NativeFunctions {
Expand Down Expand Up @@ -310,6 +311,7 @@ pub fn lookup_reserved_functions(name: &str, version: &ClarityVersion) -> Option
NativeHandle::DoubleArg(&sequences::native_index_of),
ClarityCostFunction::IndexOf,
),
Slice => SpecialFunction("special_slice", &sequences::special_slice),
ListCons => SpecialFunction("special_list_cons", &sequences::list_cons),
FetchEntry => SpecialFunction("special_map-get?", &database::special_fetch_entry),
SetEntry => SpecialFunction("special_set-entry", &database::special_set_entry),
Expand Down
31 changes: 31 additions & 0 deletions src/vm/functions/sequences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,34 @@ pub fn native_element_at(sequence: Value, index: Value) -> Result<Value> {
Ok(Value::none())
}
}

pub fn special_slice(
args: &[SymbolicExpression],
env: &mut Environment,
context: &LocalContext,
) -> Result<Value> {
check_argument_count(3, args)?;

runtime_cost(
ClarityCostFunction::Concat,
env,
0,
)?;

let seq = eval(&args[0], env, context)?;
let position = eval(&args[1], env, context)?;
let length = eval(&args[2], env, context)?;

let sliced_seq = match (seq, position, length) {
(Value::Sequence(seq), Value::UInt(position), Value::UInt(length)) => {
let (position, length) = match (usize::try_from(position), usize::try_from(length)) {
(Ok(position), Ok(length)) => (position, length),
_ => return Ok(Value::none())
};

seq.slice(position, length)
},
_ => Err(RuntimeErrorType::BadTypeConstruction.into()),
}?;
Ok(sliced_seq)
}
1 change: 1 addition & 0 deletions src/vm/tests/costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ pub fn get_simple_test(function: &NativeFunctions) -> &'static str {
StxTransferMemo => r#"(stx-transfer-memo? u1 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 0x89995432)"#,
StxBurn => "(stx-burn? u1 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)",
StxGetAccount => "(stx-account 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)",
Slice => "(slice list-bar u1 u1)",
}
}

Expand Down
Loading

0 comments on commit 844e5dc

Please sign in to comment.